mirror of
https://github.com/maputnik/editor.git
synced 2026-08-31 09:27:30 +00:00
Compare commits
117 Commits
gh-pages
...
f87a2ae707
| Author | SHA1 | Date | |
|---|---|---|---|
| f87a2ae707 | |||
| c096b51957 | |||
| ade6ce03a0 | |||
| 1a266e8a5c | |||
| ff15b77b7f | |||
| 355b663e7e | |||
| 3c043fd5e0 | |||
| 5f54dd0ccf | |||
| 3727f5da48 | |||
| 079c5f67cc | |||
| a304d4e060 | |||
| 7ac1b03b5a | |||
| b9e32894b3 | |||
| bc5ecfade6 | |||
| c84c7a7b96 | |||
| cb77c6b4e2 | |||
| ea42f434eb | |||
| 6f82c12861 | |||
| 3b95b25777 | |||
| 1da65f2116 | |||
| a62db148cd | |||
| 6ed10a862f | |||
| 123e84f19b | |||
| d9b1b6f3ae | |||
| e0cef99c07 | |||
| eb48bed32a | |||
| 7265bf0aa4 | |||
| c9504fcaed | |||
| b7ef0943f4 | |||
| 4661677387 | |||
| 77ed14a340 | |||
| e24d390f7c | |||
| 698fdfc958 | |||
| 77b3655c3c | |||
| c264cd1771 | |||
| 1495d11462 | |||
| fda11e52e7 | |||
| d9e3aa6ac4 | |||
| aeca95a27f | |||
| 7dfcdac202 | |||
| 4f156ee3fd | |||
| 6d00214f55 | |||
| 1e7b6e809c | |||
| cdcc61e234 | |||
| 9c63172d36 | |||
| 6f21fd8dff | |||
| 0e788c5841 | |||
| 7229df704a | |||
| 686fd27b35 | |||
| 293342e4fb | |||
| 03d9b946e7 | |||
| 4a3825fa89 | |||
| 5371b0f9fb | |||
| 538cea7f45 | |||
| fdfc470ccc | |||
| 3153eea1da | |||
| 8471d0af3d | |||
| 1ce2d59b9b | |||
| d951256b1c | |||
| ec753869d5 | |||
| 2e58be1c90 | |||
| 562a4f7322 | |||
| e52a63e1dd | |||
| 4533fd06ed | |||
| b4fc62632c | |||
| e3a9a8a38c | |||
| 7c4e982fb3 | |||
| 85dd22b09a | |||
| 18e15eeb5c | |||
| 3a45b8dd41 | |||
| 5b8412765b | |||
| 69519df82f | |||
| 8052701021 | |||
| 35c0150522 | |||
| c55278e7da | |||
| d3ecef3de6 | |||
| 3ef0a90de4 | |||
| 87290889fd | |||
| 1997e31b6b | |||
| 5b21a2fa4f | |||
| d314add6a9 | |||
| 50d61cdb0e | |||
| 4ffea21c5f | |||
| d29a79e79f | |||
| 004177a3c8 | |||
| b9e70a943f | |||
| de853eb2d7 | |||
| f242c2c015 | |||
| d895cf079c | |||
| 147cbc1580 | |||
| 3e46c0d3ba | |||
| d233e0a14d | |||
| a73b2fd7e1 | |||
| 69f63f2844 | |||
| 10136c07db | |||
| 5e3156ab21 | |||
| 6be8959951 | |||
| d2cd84de2b | |||
| 60bea1777a | |||
| ce9216b2d5 | |||
| 35ed202cd0 | |||
| 0d77518a02 | |||
| 11375008fa | |||
| 009f4e105d | |||
| b1af4917e5 | |||
| 5b712d74ae | |||
| ca2df37c79 | |||
| 7d1890156d | |||
| ecab640a9a | |||
| f8cb0619f3 | |||
| d874b2503b | |||
| 3d09f2a0f3 | |||
| 1f1580276d | |||
| 0421a7f099 | |||
| 8b722fc967 | |||
| 66e3ce8743 | |||
| 45eb3a01e6 |
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"packages": [],
|
|
||||||
"sandboxes": ["/"]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
name: ci-desktop
|
||||||
|
|
||||||
|
on: [ push ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./desktop
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.18
|
||||||
|
|
||||||
|
- name: Make
|
||||||
|
run: make
|
||||||
|
|
||||||
|
- name: Test --help
|
||||||
|
run: ./bin/linux/maputnik --help
|
||||||
|
|
||||||
|
- name: Test --version
|
||||||
|
run: ./bin/linux/maputnik --version
|
||||||
|
|
||||||
|
- name: Get style
|
||||||
|
run: wget https://maputnik.github.io/osm-liberty/style.json
|
||||||
|
|
||||||
|
- name: Test --watch
|
||||||
|
run: ./bin/linux/maputnik --watch --file style.json & sleep 5; kill $!
|
||||||
|
|
||||||
|
- name: Artifacts/linux
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: maputnik-linux
|
||||||
|
path: bin/linux/
|
||||||
|
|
||||||
|
- name: Artifacts/darwin
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: maputnik-darwin
|
||||||
|
path: bin/darwin/
|
||||||
|
|
||||||
|
- name: Artifacts/windows
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: maputnik-windows
|
||||||
|
path: bin/windows/
|
||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: docker build -t docker.pkg.github.com/maputnik/editor/editor:main .
|
- run: docker build -t test-docker-image-build .
|
||||||
|
|
||||||
# build the editor
|
# build the editor
|
||||||
build-node:
|
build-node:
|
||||||
@@ -53,17 +53,11 @@ jobs:
|
|||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npm run build-storybook
|
- name: artifacts/maputnik
|
||||||
- name: artifacts/editor
|
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: editor
|
name: maputnik
|
||||||
path: dist
|
path: dist
|
||||||
- name: artifacts/storybook
|
|
||||||
uses: actions/upload-artifact@v1
|
|
||||||
with:
|
|
||||||
name: storybook
|
|
||||||
path: build/storybook
|
|
||||||
|
|
||||||
# Build and upload desktop CLI artifacts
|
# Build and upload desktop CLI artifacts
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
|
|||||||
@@ -3,24 +3,49 @@ name: deploy
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
deploy-pages:
|
||||||
|
name: deploy/pages
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Use Node.js from nvmrc
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Upload to GitHub Pages
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: dist
|
||||||
|
|
||||||
# publish docker to GitHub registry
|
# publish docker to GitHub registry
|
||||||
deploy-docker:
|
deploy-docker:
|
||||||
name: deploy/docker
|
name: deploy/docker
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u orangemug --password-stdin
|
- run: docker build -t ghcr.io/maplibre/maputnik:main .
|
||||||
- run: docker build -t docker.pkg.github.com/maputnik/editor/editor:main .
|
- run: docker push ghcr.io/maplibre/maputnik:main
|
||||||
- run: docker push docker.pkg.github.com/maputnik/editor/editor:main
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
const config = {
|
|
||||||
stories: ['../stories/**/*.stories.jsx'],
|
|
||||||
addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-a11y/register', '@storybook/addon-storysource'],
|
|
||||||
framework: {
|
|
||||||
name: '@storybook/react-vite',
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export default config;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { addons } from '@storybook/addons';
|
|
||||||
import { themes } from '@storybook/theming';
|
|
||||||
import theme from './maputnik.theme';
|
|
||||||
|
|
||||||
addons.setConfig({
|
|
||||||
theme: theme,
|
|
||||||
});
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { create } from '@storybook/theming/create';
|
|
||||||
|
|
||||||
export default create({
|
|
||||||
base: 'light',
|
|
||||||
|
|
||||||
brandTitle: 'Maputnik',
|
|
||||||
brandUrl: 'https://github.com/maputnik/editor',
|
|
||||||
});
|
|
||||||
+3
-3
@@ -2,12 +2,12 @@ FROM node:18 as builder
|
|||||||
WORKDIR /maputnik
|
WORKDIR /maputnik
|
||||||
|
|
||||||
# Only copy package.json to prevent npm install from running on every build
|
# Only copy package.json to prevent npm install from running on every build
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json .npmrc ./
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
|
|
||||||
# Build maputnik
|
# Build maputnik
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN npx vite build
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Create a clean nginx-alpine slim image with just the build results
|
# Create a clean nginx-alpine slim image with just the build results
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Lukas Martinelli
|
||||||
Copyright (c) 2024 MapLibre contributors
|
Copyright (c) 2024 MapLibre contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<img width="200" alt="Maputnik logo" src="https://cdn.jsdelivr.net/gh/maputnik/design/logos/logo-color.png" />
|
<img width="200" alt="Maputnik logo" src="https://cdn.jsdelivr.net/gh/maputnik/design/logos/logo-color.png" />
|
||||||
|
|
||||||
# Maputnik
|
# Maputnik
|
||||||
[][github-action-ci]
|
[][github-action-ci]
|
||||||
[][license]
|
[][license]
|
||||||
|
|
||||||
[github-action-ci]: https://github.com/maputnik/editor/actions?query=workflow%3Aci
|
[github-action-ci]: https://github.com/maplibre/maputnik/actions?query=workflow%3Aci
|
||||||
[license]: https://tldrlegal.com/license/mit-license
|
[license]: https://tldrlegal.com/license/mit-license
|
||||||
|
|
||||||
A free and open visual editor for the [MapLibre GL styles](https://maplibre.org/maplibre-style-spec/)
|
A free and open visual editor for the [MapLibre GL styles](https://maplibre.org/maplibre-style-spec/)
|
||||||
@@ -14,18 +14,18 @@ targeted at developers and map designers.
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
- :link: Design your maps online at **<https://www.maplibre.org/maputnik/>** (all in local storage)
|
- :link: Design your maps online at **<https://www.maplibre.org/maputnik/>** (all in local storage)
|
||||||
- :link: Use the [Maputnik CLI](https://github.com/maputnik/editor/wiki/Maputnik-CLI) for local style development
|
- :link: Use the [Maputnik CLI](https://github.com/maplibre/maputnik/wiki/Maputnik-CLI) for local style development
|
||||||
- In a Docker, run this command and browse to http://localhost:8888, Ctrl+C to stop the server.
|
- In a Docker, run this command and browse to http://localhost:8888, Ctrl+C to stop the server.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -it --rm -p 8888:8888 maputnik/editor
|
docker run -it --rm -p 8888:80 ghcr.io/maplibre/maputnik:main
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The documentation can be found in the [Wiki](https://github.com/maputnik/editor/wiki). You are welcome to collaborate!
|
The documentation can be found in the [Wiki](https://github.com/maplibre/maputnik/wiki). You are welcome to collaborate!
|
||||||
|
|
||||||
- :link: **Study the [Maputnik Wiki](https://github.com/maputnik/editor/wiki)**
|
- :link: **Study the [Maputnik Wiki](https://github.com/maplibre/maputnik/wiki)**
|
||||||
- :video_camera: Design a map from Scratch https://youtu.be/XoDh0gEnBQo
|
- :video_camera: Design a map from Scratch https://youtu.be/XoDh0gEnBQo
|
||||||
|
|
||||||
[](https://youtu.be/XoDh0gEnBQo)
|
[](https://youtu.be/XoDh0gEnBQo)
|
||||||
@@ -93,11 +93,6 @@ You can also see the tests as they run or select which suites to run by executin
|
|||||||
npm run cy:open
|
npm run cy:open
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Related Projects
|
|
||||||
|
|
||||||
- [maputnik-dev-server](https://github.com/nycplanning/labs-maputnik-dev-server) - An express.js server that allows for quickly loading the style from any mapboxGL map into mapuntnik.
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
Thanks to the supporters of the **[Kickstarter campaign](https://www.kickstarter.com/projects/174808720/maputnik-visual-map-editor-for-mapbox-gl)**. This project would not be possible without these commercial and individual sponsors.
|
Thanks to the supporters of the **[Kickstarter campaign](https://www.kickstarter.com/projects/174808720/maputnik-visual-map-editor-for-mapbox-gl)**. This project would not be possible without these commercial and individual sponsors.
|
||||||
|
|||||||
@@ -87,4 +87,39 @@ describe("history", () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not redo after undo and value change", () => {
|
||||||
|
when.setStyle("geojson");
|
||||||
|
when.modal.open();
|
||||||
|
when.modal.fillLayers({
|
||||||
|
id: "step 1",
|
||||||
|
type: "background",
|
||||||
|
});
|
||||||
|
|
||||||
|
when.modal.open();
|
||||||
|
when.modal.fillLayers({
|
||||||
|
id: "step 2",
|
||||||
|
type: "background",
|
||||||
|
});
|
||||||
|
|
||||||
|
when.typeKeys(undoKeyCombo);
|
||||||
|
when.typeKeys(undoKeyCombo);
|
||||||
|
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ layers: [] });
|
||||||
|
|
||||||
|
when.modal.open();
|
||||||
|
when.modal.fillLayers({
|
||||||
|
id: "step 3",
|
||||||
|
type: "background",
|
||||||
|
});
|
||||||
|
|
||||||
|
when.typeKeys(redoKeyCombo);
|
||||||
|
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
id: "step 3",
|
||||||
|
type: "background",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -280,6 +280,25 @@ describe("layers", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("opacity", () => {
|
||||||
|
let bgId: string;
|
||||||
|
beforeEach(() => {
|
||||||
|
bgId = createBackground();
|
||||||
|
when.click("layer-list-item:background:" + bgId);
|
||||||
|
when.type("spec-field-input:background-opacity", "0.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should keep '.' in the input field", () => {
|
||||||
|
then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should revert to a valid value when focus out", () => {
|
||||||
|
when.click("layer-list-item:background:" + bgId);
|
||||||
|
then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue('0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("filter", () => {
|
describe("filter", () => {
|
||||||
|
|||||||
@@ -23,4 +23,10 @@ describe("map", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("search", () => {
|
||||||
|
it('should exist', () => {
|
||||||
|
then(get.searchControl()).shouldBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/// <reference types="cypress-plugin-tab" />
|
||||||
|
|
||||||
import { CypressHelper } from "@shellygo/cypress-test-utils";
|
import { CypressHelper } from "@shellygo/cypress-test-utils";
|
||||||
import { Assertable, then } from "@shellygo/cypress-test-utils/assertable";
|
import { Assertable, then } from "@shellygo/cypress-test-utils/assertable";
|
||||||
import MaputnikCypressHelper from "./maputnik-cypress-helper";
|
import MaputnikCypressHelper from "./maputnik-cypress-helper";
|
||||||
@@ -14,7 +16,7 @@ const styleFromWindow = (win: Window) => {
|
|||||||
export class MaputnikAssertable<T> extends Assertable<T> {
|
export class MaputnikAssertable<T> extends Assertable<T> {
|
||||||
shouldEqualToStoredStyle = () =>
|
shouldEqualToStoredStyle = () =>
|
||||||
then(
|
then(
|
||||||
new CypressHelper().get.window().then((win) => {
|
new CypressHelper().get.window().then((win: Window) => {
|
||||||
const style = styleFromWindow(win);
|
const style = styleFromWindow(win);
|
||||||
then(this.chainable).shouldDeepNestedInclude(style);
|
then(this.chainable).shouldDeepNestedInclude(style);
|
||||||
})
|
})
|
||||||
@@ -177,5 +179,6 @@ export class MaputnikDriver {
|
|||||||
skipTargetLayerEditor: () =>
|
skipTargetLayerEditor: () =>
|
||||||
this.helper.get.elementByTestId("skip-target-layer-editor"),
|
this.helper.get.elementByTestId("skip-target-layer-editor"),
|
||||||
canvas: () => this.helper.get.element("canvas"),
|
canvas: () => this.helper.get.element("canvas"),
|
||||||
|
searchControl: () => this.helper.get.element('.maplibregl-ctrl-geocoder')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,12 +79,12 @@ describe("modals", () => {
|
|||||||
when.click("nav:settings");
|
when.click("nav:settings");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when click name", () => {
|
describe("when click name filed spec information", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
when.click("field-doc-button-Name");
|
when.click("field-doc-button-Name");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("name", () => {
|
it("should show the spec information", () => {
|
||||||
then(get.elementsText("spec-field-doc")).shouldInclude(
|
then(get.elementsText("spec-field-doc")).shouldInclude(
|
||||||
"name for the style"
|
"name for the style"
|
||||||
);
|
);
|
||||||
@@ -142,7 +142,7 @@ describe("modals", () => {
|
|||||||
);
|
);
|
||||||
when.click("modal:settings.name");
|
when.click("modal:settings.name");
|
||||||
then(
|
then(
|
||||||
get.styleFromLocalStorage().pipe((style) => style.metadata)
|
get.styleFromLocalStorage().then((style) => style.metadata)
|
||||||
).shouldInclude({
|
).shouldInclude({
|
||||||
"maputnik:openmaptiles_access_token": apiKey,
|
"maputnik:openmaptiles_access_token": apiKey,
|
||||||
});
|
});
|
||||||
@@ -156,7 +156,7 @@ describe("modals", () => {
|
|||||||
);
|
);
|
||||||
when.click("modal:settings.name");
|
when.click("modal:settings.name");
|
||||||
then(
|
then(
|
||||||
get.styleFromLocalStorage().pipe((style) => style.metadata)
|
get.styleFromLocalStorage().then((style) => style.metadata)
|
||||||
).shouldInclude({ "maputnik:thunderforest_access_token": apiKey });
|
).shouldInclude({ "maputnik:thunderforest_access_token": apiKey });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
editor
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
# Binary version of pubilic/editor
|
||||||
|
rice-box.go
|
||||||
|
|
||||||
|
# Built binary
|
||||||
|
maputnik
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Maputnik
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
SOURCEDIR=.
|
||||||
|
SOURCES := $(shell find $(SOURCEDIR) -name '*.go')
|
||||||
|
BINARY=maputnik
|
||||||
|
EDITOR_VERSION ?= v1.7.0
|
||||||
|
GOPATH := $(if $(GOPATH),$(GOPATH),$(HOME)/go)
|
||||||
|
GOBIN := $(if $(GOBIN),$(GOBIN),$(HOME)/go/bin)
|
||||||
|
|
||||||
|
all: $(BINARY)
|
||||||
|
|
||||||
|
$(BINARY): $(GOBIN)/gox $(SOURCES) rice-box.go
|
||||||
|
$(GOBIN)/gox -osarch "windows/amd64 linux/amd64 darwin/amd64" -output "bin/{{.OS}}/${BINARY}"
|
||||||
|
|
||||||
|
editor/create_folder:
|
||||||
|
mkdir -p editor
|
||||||
|
|
||||||
|
editor/pull_release: editor/create_folder
|
||||||
|
# if the directory /home/runner/work/editor/editor/build/build exists, we assume that we are are running the makefile within the editor ci workflow
|
||||||
|
test -d /home/runner/work/editor/editor/build/build && echo "exists" && cd editor && cp -R /home/runner/work/editor/editor/build/build public/ || (echo "does not exist" && cd editor && rm -rf public && curl -L https://github.com/maputnik/editor/releases/download/$(EDITOR_VERSION)/public.zip --output public.zip && unzip public.zip && rm public.zip)
|
||||||
|
|
||||||
|
$(GOBIN)/gox:
|
||||||
|
go install github.com/mitchellh/gox@v1.0.1
|
||||||
|
|
||||||
|
$(GOBIN)/rice:
|
||||||
|
go install github.com/GeertJohan/go.rice/rice@v1.0.3
|
||||||
|
|
||||||
|
rice-box.go: $(GOBIN)/rice editor/pull_release
|
||||||
|
$(GOBIN)/rice embed-go
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf editor/public && rm -f rice-box.go && rm -rf bin
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Maputnik Desktop [](https://github.com/maputnik/desktop/actions?query=workflow%3Aci)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
A Golang based cross platform executable for integrating Maputnik locally.
|
||||||
|
This binary packages up the JavaScript and CSS bundle produced by [maputnik/editor](https://github.com/maputnik/desktop)
|
||||||
|
and embeds it in the program for easy distribution. It also allows
|
||||||
|
exposing a local style file and work on it both in Maputnik and with your favorite
|
||||||
|
editor.
|
||||||
|
|
||||||
|
Report issues on [maputnik/editor](https://github.com/maputnik/editor).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
You can download a single binary for Linux, OSX or Windows from [the latest releases of **maputnik/editor**](https://github.com/maputnik/editor/releases/latest).
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Simply start up a web server and access the Maputnik editor GUI at `localhost:8000`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
maputnik
|
||||||
|
```
|
||||||
|
|
||||||
|
Expose a local style file to Maputnik allowing the web based editor
|
||||||
|
to save to the local filesystem.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
maputnik --file basic-v9.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Watch the local style for changes and inform the editor via web socket.
|
||||||
|
This makes it possible to edit the style with a local text editor and still
|
||||||
|
use Maputnik.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
maputnik --watch --file basic-v9.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Choose a local port to listen on, instead of using the default port 8000.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
maputnik --port 8001
|
||||||
|
```
|
||||||
|
|
||||||
|
Specify a path to a directory which, if it exists, will be served under http://localhost:8000/static/ .
|
||||||
|
Could be used to serve sprites and glyphs.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
maputnik --static ./localFolder
|
||||||
|
```
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
`maputnik` exposes the configured styles via a HTTP API.
|
||||||
|
|
||||||
|
| Method | Description
|
||||||
|
|---------------------------------|---------------------------------------
|
||||||
|
| `GET /styles` | List the ID of all configured style files
|
||||||
|
| `GET /styles/{filename}` | Get contents of a single style file
|
||||||
|
| `PUT /styles/{filename}` | Update contents of a style file
|
||||||
|
| `WEBSOCKET /ws` | Listen to change events for the configured style files
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
Clone the repository. Make sure you clone it into the correct directory `$GOPATH/src/github.com/maputnik`.
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone git@github.com:maputnik/desktop.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Run `make` to install the 3rd party dependencies and build the `maputnik` binary embedding the editor.
|
||||||
|
|
||||||
|
```
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now find the `maputnik` binary in your `bin` directory.
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StyleFileAccessor(filename string) styleFileAccessor {
|
||||||
|
return styleFileAccessor{filename, styleId(filename)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func styleId(filename string) string {
|
||||||
|
raw, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var spec styleSpec
|
||||||
|
err = json.Unmarshal(raw, &spec)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec.Id == "" {
|
||||||
|
fmt.Println("No id in style")
|
||||||
|
}
|
||||||
|
return spec.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
type styleSpec struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows access to a single style file
|
||||||
|
type styleFileAccessor struct {
|
||||||
|
filename string
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fa styleFileAccessor) ListFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.Encode([]string{fa.id})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fa styleFileAccessor) ReadFile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
_ = vars["styleId"]
|
||||||
|
|
||||||
|
//TODO: Choose right file
|
||||||
|
// right now we just return the single file we know of
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
raw, err := ioutil.ReadFile(fa.filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
w.Write(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fa styleFileAccessor) SaveFile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
_ = vars["styleId"]
|
||||||
|
|
||||||
|
//TODO: Save to right file
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
|
var out bytes.Buffer
|
||||||
|
json.Indent(&out, body, "", " ")
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(fa.filename, out.Bytes(), 0666); err != nil {
|
||||||
|
log.Fatalf("Can not copy from request to file: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package filewatch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
|
}
|
||||||
|
|
||||||
|
func writer(ws *websocket.Conn, filename string) {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-watcher.Events:
|
||||||
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
|
log.Println("Modified file:", event.Name)
|
||||||
|
var p []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p, err = ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p != nil {
|
||||||
|
if err := ws.WriteMessage(websocket.TextMessage, p); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
log.Println("Watch error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = watcher.Add(filename); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServeWebsocketFileWatcher(filename string, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ws, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(websocket.HandshakeError); !ok {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writer(ws, filename)
|
||||||
|
defer ws.Close()
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
module maputnik/desktop
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/GeertJohan/go.rice v1.0.3
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
|
github.com/gorilla/handlers v1.5.1
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
github.com/maputnik/desktop v1.0.7
|
||||||
|
github.com/urfave/cli v1.22.12
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/GeertJohan/go.incremental v1.0.0 // indirect
|
||||||
|
github.com/akavel/rsrc v0.8.0 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
|
github.com/daaku/go.zipexe v1.0.2 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||||
|
github.com/jessevdk/go-flags v1.4.0 // indirect
|
||||||
|
github.com/nkovacs/streamquote v1.0.0 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.0.1 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg=
|
||||||
|
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||||
|
github.com/GeertJohan/go.rice v1.0.3 h1:k5viR+xGtIhF61125vCE1cmJ5957RQGXG6dmbaWZSmI=
|
||||||
|
github.com/GeertJohan/go.rice v1.0.3/go.mod h1:XVdrU4pW00M4ikZed5q56tPf1v2KwnIKeIdc9CBYNt4=
|
||||||
|
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
|
||||||
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/daaku/go.zipexe v1.0.2 h1:Zg55YLYTr7M9wjKn8SY/WcpuuEi+kR2u4E8RhvpyXmk=
|
||||||
|
github.com/daaku/go.zipexe v1.0.2/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||||
|
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
|
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||||
|
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||||
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/maputnik/desktop v1.0.7 h1:rdFg7emIJOT3YsZpwqSChmWtMOvu+T4h6WwVQAZP9n4=
|
||||||
|
github.com/maputnik/desktop v1.0.7/go.mod h1:wmDjHUztx9jOBz0I22589yWguAGdV/sEM57YANpN8oQ=
|
||||||
|
github.com/nkovacs/streamquote v1.0.0 h1:PmVIV08Zlx2lZK5fFZlMZ04eHcDTIFJCv/5/0twVUow=
|
||||||
|
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8=
|
||||||
|
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
|
||||||
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/GeertJohan/go.rice"
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/maputnik/desktop/filewatch"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "maputnik"
|
||||||
|
app.Usage = "Server for integrating Maputnik locally"
|
||||||
|
app.Version = "Editor: 1.7.0; Desktop: 1.1.0"
|
||||||
|
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "file, f",
|
||||||
|
Usage: "Allow access to JSON style from web client",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "watch",
|
||||||
|
Usage: "Notify web client about JSON style file changes",
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "port",
|
||||||
|
Value: 8000,
|
||||||
|
Usage: "TCP port to listen on",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "static",
|
||||||
|
Usage: "Serve directory under /static/",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Action = func(c *cli.Context) error {
|
||||||
|
gui := http.FileServer(rice.MustFindBox("editor/public").HTTPBox())
|
||||||
|
|
||||||
|
router := mux.NewRouter().StrictSlash(true)
|
||||||
|
|
||||||
|
filename := c.String("file")
|
||||||
|
if filename != "" {
|
||||||
|
fmt.Printf("%s is accessible via Maputnik\n", filename)
|
||||||
|
// Allow access to reading and writing file on the local system
|
||||||
|
path, _ := filepath.Abs(filename)
|
||||||
|
accessor := StyleFileAccessor(path)
|
||||||
|
router.Path("/styles").Methods("GET").HandlerFunc(accessor.ListFiles)
|
||||||
|
router.Path("/styles/{styleId}").Methods("GET").HandlerFunc(accessor.ReadFile)
|
||||||
|
router.Path("/styles/{styleId}").Methods("PUT").HandlerFunc(accessor.SaveFile)
|
||||||
|
|
||||||
|
// Register websocket to notify we clients about file changes
|
||||||
|
if c.Bool("watch") {
|
||||||
|
router.Path("/ws").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
filewatch.ServeWebsocketFileWatcher(filename, w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
staticDir := c.String("static")
|
||||||
|
if staticDir != "" {
|
||||||
|
h := http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))
|
||||||
|
router.PathPrefix("/static/").Handler(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.PathPrefix("/").Handler(http.StripPrefix("/", gui))
|
||||||
|
loggedRouter := handlers.LoggingHandler(os.Stdout, router)
|
||||||
|
corsRouter := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}), handlers.AllowedMethods([]string{"GET", "PUT"}), handlers.AllowedOrigins([]string{"*"}), handlers.AllowCredentials())(loggedRouter)
|
||||||
|
|
||||||
|
fmt.Printf("Exposing Maputnik on http://localhost:%d\n", c.Int("port"))
|
||||||
|
return http.ListenAndServe(fmt.Sprintf(":%d", c.Int("port")), corsRouter)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 65 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
Generated
+2290
-10750
File diff suppressed because it is too large
Load Diff
+53
-61
@@ -1,41 +1,43 @@
|
|||||||
{
|
{
|
||||||
"name": "maputnik",
|
"name": "maputnik",
|
||||||
"version": "2.0.0-pre.2",
|
"version": "2.0.0",
|
||||||
"description": "A MapLibre GL visual style editor",
|
"description": "A MapLibre GL visual style editor",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "''",
|
"main": "''",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build --base=/maputnik/",
|
||||||
"lint": "eslint ./src ./cypress --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint ./src ./cypress --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"test": "cypress run",
|
"test": "cypress run",
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"lint-css": "stylelint \"src/styles/*.scss\"",
|
"lint-css": "stylelint \"src/styles/*.scss\""
|
||||||
"storybook": "storybook dev -h 0.0.0.0 -p 6006",
|
|
||||||
"build-storybook": "storybook build -o build/storybook"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/maputnik/editor"
|
"url": "https://github.com/maplibre/maputnik"
|
||||||
},
|
},
|
||||||
"author": "Lukas Martinelli",
|
"author": "Lukas Martinelli",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/maputnik/editor#readme",
|
"homepage": "https://github.com/maplibre/maputnik#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",
|
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",
|
||||||
"@maplibre/maplibre-gl-style-spec": "^17.0.1",
|
"@maplibre/maplibre-gl-geocoder": "^1.5.0",
|
||||||
"@mdi/js": "^6.6.96",
|
"@maplibre/maplibre-gl-inspect": "^1.6.3",
|
||||||
"@mdi/react": "^1.5.0",
|
"@maplibre/maplibre-gl-style-spec": "^20.1.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
"@mdi/js": "^7.4.47",
|
||||||
"@typescript-eslint/parser": "^6.16.0",
|
"@mdi/react": "^1.6.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||||
|
"@typescript-eslint/parser": "^7.3.1",
|
||||||
"array-move": "^4.0.0",
|
"array-move": "^4.0.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.5.1",
|
||||||
"codemirror": "^5.65.2",
|
"codemirror": "^5.65.2",
|
||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
|
"cypress-plugin-tab": "^1.0.5",
|
||||||
"detect-browser": "^5.3.0",
|
"detect-browser": "^5.3.0",
|
||||||
|
"events": "^3.3.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"json-stringify-pretty-compact": "^3.0.0",
|
"json-stringify-pretty-compact": "^4.0.0",
|
||||||
"json-to-ast": "^2.1.0",
|
"json-to-ast": "^2.1.0",
|
||||||
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@@ -45,30 +47,29 @@
|
|||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"mapbox-gl-inspect": "^1.3.1",
|
"maplibre-gl": "^4.1.2",
|
||||||
"maplibre-gl": "^2.4.0",
|
|
||||||
"maputnik-design": "github:maputnik/design#172b06c",
|
"maputnik-design": "github:maputnik/design#172b06c",
|
||||||
"ol": "^6.14.1",
|
"ol": "^6.14.1",
|
||||||
"ol-mapbox-style": "^7.1.1",
|
"ol-mapbox-style": "^7.1.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^16.0.0",
|
"react": "^18.2.0",
|
||||||
"react-accessible-accordion": "^4.0.0",
|
"react-accessible-accordion": "^5.0.0",
|
||||||
"react-aria-menubutton": "^7.0.3",
|
"react-aria-menubutton": "^7.0.3",
|
||||||
"react-aria-modal": "^4.0.1",
|
"react-aria-modal": "^5.0.2",
|
||||||
"react-autobind": "^1.0.6",
|
"react-autobind": "^1.0.6",
|
||||||
"react-autocomplete": "^1.8.1",
|
"react-autocomplete": "^1.8.1",
|
||||||
"react-collapse": "^5.1.1",
|
"react-collapse": "^5.1.1",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^16.0.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-file-reader-input": "^2.0.0",
|
"react-file-reader-input": "^2.0.0",
|
||||||
"react-icon-base": "^2.1.2",
|
"react-icon-base": "^2.1.2",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^5.0.1",
|
||||||
"react-sortable-hoc": "^2.0.0",
|
"react-sortable-hoc": "^2.0.0",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"sass": "^1.50.0",
|
"sass": "^1.72.0",
|
||||||
"slugify": "^1.6.5",
|
"slugify": "^1.6.6",
|
||||||
"string-hash": "^1.1.3",
|
"string-hash": "^1.1.3",
|
||||||
"url": "^0.11.0"
|
"url": "^0.11.3"
|
||||||
},
|
},
|
||||||
"jshintConfig": {
|
"jshintConfig": {
|
||||||
"esversion": 6
|
"esversion": 6
|
||||||
@@ -88,24 +89,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cypress/code-coverage": "^3.12.15",
|
"@cypress/code-coverage": "^3.12.30",
|
||||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||||
"@rollup/plugin-replace": "^5.0.5",
|
"@rollup/plugin-replace": "^5.0.5",
|
||||||
"@shellygo/cypress-test-utils": "^2.0.17",
|
"@shellygo/cypress-test-utils": "^2.0.52",
|
||||||
"@storybook/addon-a11y": "^7.6.5",
|
|
||||||
"@storybook/addon-actions": "^7.6.5",
|
|
||||||
"@storybook/addon-links": "^7.6.5",
|
|
||||||
"@storybook/addon-storysource": "^7.6.5",
|
|
||||||
"@storybook/addons": "^7.6.5",
|
|
||||||
"@storybook/builder-vite": "^7.6.5",
|
|
||||||
"@storybook/react": "^7.6.5",
|
|
||||||
"@storybook/react-vite": "^7.6.5",
|
|
||||||
"@storybook/theming": "^7.6.5",
|
|
||||||
"@types/codemirror": "^5.60.15",
|
"@types/codemirror": "^5.60.15",
|
||||||
"@types/color": "^3.0.6",
|
"@types/color": "^3.0.6",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/geojson": "^7946.0.13",
|
"@types/geojson": "^7946.0.14",
|
||||||
"@types/json-to-ast": "^2.1.4",
|
"@types/json-to-ast": "^2.1.4",
|
||||||
"@types/lodash.capitalize": "^4.2.9",
|
"@types/lodash.capitalize": "^4.2.9",
|
||||||
"@types/lodash.clamp": "^4.0.9",
|
"@types/lodash.clamp": "^4.0.9",
|
||||||
@@ -113,37 +105,37 @@
|
|||||||
"@types/lodash.get": "^4.4.9",
|
"@types/lodash.get": "^4.4.9",
|
||||||
"@types/lodash.isequal": "^4.5.8",
|
"@types/lodash.isequal": "^4.5.8",
|
||||||
"@types/lodash.throttle": "^4.1.9",
|
"@types/lodash.throttle": "^4.1.9",
|
||||||
"@types/react": "^16.14.52",
|
"@types/mocha": "^10.0.6",
|
||||||
"@types/react-aria-menubutton": "^6.2.13",
|
"@types/randomcolor": "^0.5.9",
|
||||||
"@types/react-aria-modal": "^4.0.9",
|
"@types/react": "^18.2.67",
|
||||||
"@types/react-autocomplete": "^1.8.9",
|
"@types/react-aria-menubutton": "^6.2.14",
|
||||||
|
"@types/react-aria-modal": "^4.0.10",
|
||||||
|
"@types/react-autocomplete": "^1.8.10",
|
||||||
"@types/react-collapse": "^5.0.4",
|
"@types/react-collapse": "^5.0.4",
|
||||||
"@types/react-color": "^3.0.10",
|
"@types/react-color": "^3.0.12",
|
||||||
"@types/react-dom": "^16.9.24",
|
"@types/react-dom": "^18.2.22",
|
||||||
"@types/react-file-reader-input": "^2.0.4",
|
"@types/react-file-reader-input": "^2.0.4",
|
||||||
"@types/react-icon-base": "^2.1.6",
|
"@types/react-icon-base": "^2.1.6",
|
||||||
"@types/string-hash": "^1.1.3",
|
"@types/string-hash": "^1.1.3",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.8",
|
||||||
"@vitejs/plugin-react": "^4.2.0",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cypress": "^13.6.1",
|
"cypress": "^13.7.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.34.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
"eslint-plugin-react-refresh": "^0.4.6",
|
||||||
"express": "^4.17.3",
|
|
||||||
"istanbul": "^0.4.5",
|
"istanbul": "^0.4.5",
|
||||||
"istanbul-lib-coverage": "^3.2.0",
|
"istanbul-lib-coverage": "^3.2.2",
|
||||||
"mocha": "^9.2.2",
|
"mocha": "^10.3.0",
|
||||||
"postcss": "^8.4.12",
|
"postcss": "^8.4.38",
|
||||||
"react-hot-loader": "^4.13.0",
|
"react-hot-loader": "^4.13.1",
|
||||||
"storybook": "^7.6.5",
|
"stylelint": "^16.2.1",
|
||||||
"stylelint": "^14.6.1",
|
"stylelint-config-recommended-scss": "^14.0.0",
|
||||||
"stylelint-config-recommended-scss": "^6.0.0",
|
"stylelint-scss": "^6.2.1",
|
||||||
"stylelint-scss": "^4.2.0",
|
"typescript": "^5.4.3",
|
||||||
"typescript": "^5.3.3",
|
"uuid": "^9.0.1",
|
||||||
"uuid": "^8.3.2",
|
"vite": "^5.2.2",
|
||||||
"vite": "^5.0.0",
|
"vite-plugin-istanbul": "^6.0.0"
|
||||||
"vite-plugin-istanbul": "^5.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {unset} from 'lodash'
|
|||||||
import {arrayMoveMutable} from 'array-move'
|
import {arrayMoveMutable} from 'array-move'
|
||||||
import hash from "string-hash";
|
import hash from "string-hash";
|
||||||
import {Map, LayerSpecification, StyleSpecification, ValidationError, SourceSpecification} from 'maplibre-gl'
|
import {Map, LayerSpecification, StyleSpecification, ValidationError, SourceSpecification} from 'maplibre-gl'
|
||||||
import {latest, validate} from '@maplibre/maplibre-gl-style-spec'
|
import {latest, validateStyleMin} from '@maplibre/maplibre-gl-style-spec'
|
||||||
|
|
||||||
import MapMaplibreGl from './MapMaplibreGl'
|
import MapMaplibreGl from './MapMaplibreGl'
|
||||||
import MapOpenLayers from './MapOpenLayers'
|
import MapOpenLayers from './MapOpenLayers'
|
||||||
@@ -379,8 +379,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
this.getInitialStateFromUrl(newStyle);
|
this.getInitialStateFromUrl(newStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This "any" can be removed in latest version of maplibre where maplibre re-exported types from style-spec
|
const errors: ValidationError[] = validateStyleMin(newStyle) || [];
|
||||||
const errors = validate(newStyle as any, latest) || [];
|
|
||||||
|
|
||||||
// The validate function doesn't give us errors for duplicate error with
|
// The validate function doesn't give us errors for duplicate error with
|
||||||
// empty string for layer.id, manually deal with that here.
|
// empty string for layer.id, manually deal with that here.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {LayerSpecification, StyleSpecification} from 'maplibre-gl';
|
|||||||
|
|
||||||
type AppMessagePanelProps = {
|
type AppMessagePanelProps = {
|
||||||
errors?: unknown[]
|
errors?: unknown[]
|
||||||
infos?: unknown[]
|
infos?: string[]
|
||||||
mapStyle?: StyleSpecification
|
mapStyle?: StyleSpecification
|
||||||
onLayerSelect?(...args: unknown[]): unknown
|
onLayerSelect?(...args: unknown[]): unknown
|
||||||
currentLayer?: LayerSpecification
|
currentLayer?: LayerSpecification
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import React from 'react'
|
|||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import {detect} from 'detect-browser';
|
import {detect} from 'detect-browser';
|
||||||
|
|
||||||
import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage, MdAssignmentTurnedIn} from 'react-icons/md'
|
import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage} from 'react-icons/md'
|
||||||
import pkgJson from '../../package.json'
|
import pkgJson from '../../package.json'
|
||||||
|
//@ts-ignore
|
||||||
|
import maputnikLogo from 'maputnik-design/logos/logo-color.svg?inline'
|
||||||
|
|
||||||
// This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of.
|
// This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of.
|
||||||
const browser = detect();
|
const browser = detect();
|
||||||
@@ -43,29 +44,6 @@ class ToolbarLink extends React.Component<ToolbarLinkProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolbarLinkHighlightedProps = {
|
|
||||||
className?: string
|
|
||||||
children?: React.ReactNode
|
|
||||||
href?: string
|
|
||||||
onToggleModal?(...args: unknown[]): unknown
|
|
||||||
};
|
|
||||||
|
|
||||||
class ToolbarLinkHighlighted extends React.Component<ToolbarLinkHighlightedProps> {
|
|
||||||
render() {
|
|
||||||
return <a
|
|
||||||
className={classnames('maputnik-toolbar-link', "maputnik-toolbar-link--highlighted", this.props.className)}
|
|
||||||
href={this.props.href}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
data-wd-key="toolbar:link-highlighted"
|
|
||||||
>
|
|
||||||
<span className="maputnik-toolbar-link-wrapper">
|
|
||||||
{this.props.children}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ToolbarSelectProps = {
|
type ToolbarSelectProps = {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
wdKey?: string
|
wdKey?: string
|
||||||
@@ -216,9 +194,9 @@ export default class AppToolbar extends React.Component<AppToolbarProps> {
|
|||||||
className="maputnik-toolbar-logo"
|
className="maputnik-toolbar-logo"
|
||||||
target="blank"
|
target="blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
href="https://github.com/maputnik/editor"
|
href="https://github.com/maplibre/maputnik"
|
||||||
>
|
>
|
||||||
<img src="node_modules/maputnik-design/logos/logo-color.svg" />
|
<img src={maputnikLogo} alt="Maputnik on GitHub" />
|
||||||
<h1>
|
<h1>
|
||||||
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
|
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
|
||||||
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
||||||
@@ -272,14 +250,10 @@ export default class AppToolbar extends React.Component<AppToolbarProps> {
|
|||||||
</label>
|
</label>
|
||||||
</ToolbarSelect>
|
</ToolbarSelect>
|
||||||
|
|
||||||
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
|
<ToolbarLink href={"https://github.com/maplibre/maputnik/wiki"}>
|
||||||
<MdHelpOutline />
|
<MdHelpOutline />
|
||||||
<IconText>Help</IconText>
|
<IconText>Help</IconText>
|
||||||
</ToolbarLink>
|
</ToolbarLink>
|
||||||
<ToolbarLinkHighlighted href={"https://gregorywolanski.typeform.com/to/cPgaSY"}>
|
|
||||||
<MdAssignmentTurnedIn />
|
|
||||||
<IconText>Take the Maputnik Survey</IconText>
|
|
||||||
</ToolbarLinkHighlighted>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, {SyntheticEvent} from 'react'
|
import React, {PropsWithChildren, SyntheticEvent} from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import FieldDocLabel from './FieldDocLabel'
|
import FieldDocLabel from './FieldDocLabel'
|
||||||
import Doc from './Doc'
|
import Doc from './Doc'
|
||||||
|
|
||||||
|
|
||||||
type BlockProps = {
|
type BlockProps = PropsWithChildren & {
|
||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
label?: string
|
label?: string
|
||||||
action?: React.ReactElement
|
action?: React.ReactElement
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default class Doc extends React.Component<DocProps> {
|
|||||||
const renderValues = (
|
const renderValues = (
|
||||||
!!values &&
|
!!values &&
|
||||||
// HACK: Currently we merge additional values into the style spec, so this is required
|
// HACK: Currently we merge additional values into the style spec, so this is required
|
||||||
// See <https://github.com/maputnik/editor/blob/main/src/components/PropertyGroup.jsx#L16>
|
// See <https://github.com/maplibre/maputnik/blob/main/src/components/PropertyGroup.jsx#L16>
|
||||||
!Array.isArray(values)
|
!Array.isArray(values)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import {MdInfoOutline, MdHighlightOff} from 'react-icons/md'
|
import {MdInfoOutline, MdHighlightOff} from 'react-icons/md'
|
||||||
|
|
||||||
type FieldDocLabelProps = {
|
type FieldDocLabelProps = {
|
||||||
label: object | string | undefined
|
label: JSX.Element | string | undefined
|
||||||
fieldSpec?: {
|
fieldSpec?: {
|
||||||
doc?: string
|
doc?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputString from './InputString'
|
import InputString from './InputString'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from './InputNumber'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from './InputNumber'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputAutocomplete from './InputAutocomplete'
|
import InputAutocomplete from './InputAutocomplete'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputSelect from './InputSelect'
|
import InputSelect from './InputSelect'
|
||||||
import InputString from './InputString'
|
import InputString from './InputString'
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { PropsWithChildren, ReactElement } from 'react'
|
||||||
import FieldDocLabel from './FieldDocLabel'
|
import FieldDocLabel from './FieldDocLabel'
|
||||||
import Doc from './Doc'
|
import Doc from './Doc'
|
||||||
import generateUniqueId from '../libs/document-uid';
|
import generateUniqueId from '../libs/document-uid';
|
||||||
|
|
||||||
type FieldsetProps = {
|
type FieldsetProps = PropsWithChildren & {
|
||||||
label?: string,
|
label?: string,
|
||||||
fieldSpec?: { doc?: string },
|
fieldSpec?: { doc?: string },
|
||||||
action?: ReactElement,
|
action?: ReactElement,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ function createStyleFromFilter(filter: LegacyFilterSpecification | ExpressionSpe
|
|||||||
"sources": {
|
"sources": {
|
||||||
"tmp": {
|
"tmp": {
|
||||||
"type": "geojson",
|
"type": "geojson",
|
||||||
"data": {}
|
"data": ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sprite": "",
|
"sprite": "",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react'
|
import React, { PropsWithChildren } from 'react'
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import {MdDelete} from 'react-icons/md'
|
import {MdDelete} from 'react-icons/md'
|
||||||
|
|
||||||
type FilterEditorBlockProps = {
|
type FilterEditorBlockProps = PropsWithChildren & {
|
||||||
onDelete(...args: unknown[]): unknown
|
onDelete(...args: unknown[]): unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ type InputNumberState = {
|
|||||||
editing: boolean
|
editing: boolean
|
||||||
editingRange?: boolean
|
editingRange?: boolean
|
||||||
value?: number
|
value?: number
|
||||||
dirtyValue?: number
|
/**
|
||||||
|
* This is the value that is currently being edited. It can be an invalid value.
|
||||||
|
*/
|
||||||
|
dirtyValue?: number | string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class InputNumber extends React.Component<InputNumberProps, InputNumberState> {
|
export default class InputNumber extends React.Component<InputNumberProps, InputNumberState> {
|
||||||
@@ -66,7 +69,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
dirtyValue: newValue === "" ? undefined : value,
|
dirtyValue: newValue === "" ? undefined : newValue,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +128,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
// for example we might go from 13 to 13.23, however because we know
|
// for example we might go from 13 to 13.23, however because we know
|
||||||
// that came from a keyboard event we always want to increase by a
|
// that came from a keyboard event we always want to increase by a
|
||||||
// single step value.
|
// single step value.
|
||||||
if (value < this.state.dirtyValue!) {
|
if (value < +this.state.dirtyValue!) {
|
||||||
value = this.state.value! - step;
|
value = this.state.value! - step;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export default class SpecField extends React.Component<SpecFieldProps> {
|
|||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
default: this.props.fieldSpec?.default,
|
default: this.props.fieldSpec?.default,
|
||||||
name: this.props.fieldName,
|
name: this.props.fieldName,
|
||||||
|
"data-wd-key": "spec-field-input:" + this.props.fieldName,
|
||||||
onChange: (newValue: number | undefined | (string | number | undefined)[]) => this.props.onChange!(this.props.fieldName, newValue),
|
onChange: (newValue: number | undefined | (string | number | undefined)[]) => this.props.onChange!(this.props.fieldName, newValue),
|
||||||
'aria-label': this.props['aria-label'],
|
'aria-label': this.props['aria-label'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,6 @@ class LayerListItem extends React.Component<LayerListItemProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LayerListItemSortable = SortableElement((props: LayerListItemProps) => <LayerListItem {...props} />);
|
const LayerListItemSortable = SortableElement<LayerListItemProps>((props: LayerListItemProps) => <LayerListItem {...props} />);
|
||||||
|
|
||||||
export default LayerListItemSortable;
|
export default LayerListItemSortable;
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import React, {type JSX} from 'react'
|
import React, {type JSX} from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import MapLibreGl, {LayerSpecification, LngLat, Map, MapOptions, SourceSpecification, StyleSpecification} from 'maplibre-gl'
|
import MapLibreGl, {LayerSpecification, LngLat, Map, MapOptions, SourceSpecification, StyleSpecification} from 'maplibre-gl'
|
||||||
// @ts-ignore
|
import MaplibreInspect from '@maplibre/maplibre-gl-inspect'
|
||||||
import MapboxInspect from 'mapbox-gl-inspect'
|
import colors from '@maplibre/maplibre-gl-inspect/lib/colors'
|
||||||
// @ts-ignore
|
|
||||||
import colors from 'mapbox-gl-inspect/lib/colors'
|
|
||||||
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup'
|
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup'
|
||||||
import MapMaplibreGlFeaturePropertyPopup, { InspectFeature } from './MapMaplibreGlFeaturePropertyPopup'
|
import MapMaplibreGlFeaturePropertyPopup, { InspectFeature } from './MapMaplibreGlFeaturePropertyPopup'
|
||||||
import Color from 'color'
|
import Color from 'color'
|
||||||
@@ -13,13 +11,13 @@ import { HighlightedLayer, colorHighlightedLayer } from '../libs/highlight'
|
|||||||
import 'maplibre-gl/dist/maplibre-gl.css'
|
import 'maplibre-gl/dist/maplibre-gl.css'
|
||||||
import '../maplibregl.css'
|
import '../maplibregl.css'
|
||||||
import '../libs/maplibre-rtl'
|
import '../libs/maplibre-rtl'
|
||||||
|
//@ts-ignore
|
||||||
|
import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder';
|
||||||
|
import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css';
|
||||||
|
|
||||||
|
function renderPopup(popup: JSX.Element, mountNode: ReactDOM.Container): HTMLElement {
|
||||||
const IS_SUPPORTED = MapLibreGl.supported();
|
|
||||||
|
|
||||||
function renderPopup(popup: JSX.Element, mountNode: ReactDOM.Container) {
|
|
||||||
ReactDOM.render(popup, mountNode);
|
ReactDOM.render(popup, mountNode);
|
||||||
return mountNode;
|
return mountNode as HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers: HighlightedLayer[], highlightedLayer?: HighlightedLayer) {
|
function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers: HighlightedLayer[], highlightedLayer?: HighlightedLayer) {
|
||||||
@@ -37,6 +35,7 @@ function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sources: {[key:string]: SourceSpecification} = {}
|
const sources: {[key:string]: SourceSpecification} = {}
|
||||||
|
|
||||||
Object.keys(originalMapStyle.sources).forEach(sourceId => {
|
Object.keys(originalMapStyle.sources).forEach(sourceId => {
|
||||||
const source = originalMapStyle.sources[sourceId]
|
const source = originalMapStyle.sources[sourceId]
|
||||||
if(source.type !== 'raster' && source.type !== 'raster-dem') {
|
if(source.type !== 'raster' && source.type !== 'raster-dem') {
|
||||||
@@ -69,7 +68,7 @@ type MapMaplibreGlProps = {
|
|||||||
|
|
||||||
type MapMaplibreGlState = {
|
type MapMaplibreGlState = {
|
||||||
map: Map | null
|
map: Map | null
|
||||||
inspect: MapboxInspect | null
|
inspect: MaplibreInspect | null
|
||||||
zoom?: number
|
zoom?: number
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,18 +90,6 @@ export default class MapMaplibreGl extends React.Component<MapMaplibreGlProps, M
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMapFromProps(props: MapMaplibreGlProps) {
|
|
||||||
if(!IS_SUPPORTED) return;
|
|
||||||
|
|
||||||
if(!this.state.map) return
|
|
||||||
|
|
||||||
//Maplibre GL now does diffing natively so we don't need to calculate
|
|
||||||
//the necessary operations ourselves!
|
|
||||||
this.state.map.setStyle(
|
|
||||||
this.props.replaceAccessTokens(props.mapStyle),
|
|
||||||
{diff: true}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: MapMaplibreGlProps, nextState: MapMaplibreGlState) {
|
shouldComponentUpdate(nextProps: MapMaplibreGlProps, nextState: MapMaplibreGlState) {
|
||||||
let should = false;
|
let should = false;
|
||||||
@@ -115,46 +102,42 @@ export default class MapMaplibreGl extends React.Component<MapMaplibreGlProps, M
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if(!IS_SUPPORTED) return;
|
|
||||||
|
|
||||||
const map = this.state.map;
|
const map = this.state.map;
|
||||||
|
|
||||||
this.updateMapFromProps(this.props);
|
const styleWithTokens = this.props.replaceAccessTokens(this.props.mapStyle);
|
||||||
|
|
||||||
if(this.state.inspect && this.props.inspectModeEnabled !== this.state.inspect._showInspectMap) {
|
|
||||||
// HACK: Fix for <https://github.com/maputnik/editor/issues/576>, while we wait for a proper fix.
|
|
||||||
// eslint-disable-next-line
|
|
||||||
this.state.inspect._popupBlocked = false;
|
|
||||||
this.state.inspect.toggleInspector()
|
|
||||||
}
|
|
||||||
if (map) {
|
if (map) {
|
||||||
if (this.props.inspectModeEnabled) {
|
// Maplibre GL now does diffing natively so we don't need to calculate
|
||||||
// HACK: We need to work out why we need to do this and what's causing
|
// the necessary operations ourselves!
|
||||||
// this error. I'm assuming an issue with maplibre-gl update and
|
// We also need to update the style for inspect to work properly
|
||||||
// mapbox-gl-inspect.
|
map.setStyle(styleWithTokens, {diff: true});
|
||||||
try {
|
|
||||||
this.state.inspect.render();
|
|
||||||
} catch(err) {
|
|
||||||
console.error("FIXME: Caught error", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
map.showTileBoundaries = this.props.options?.showTileBoundaries!;
|
map.showTileBoundaries = this.props.options?.showTileBoundaries!;
|
||||||
map.showCollisionBoxes = this.props.options?.showCollisionBoxes!;
|
map.showCollisionBoxes = this.props.options?.showCollisionBoxes!;
|
||||||
map.showOverdrawInspector = this.props.options?.showOverdrawInspector!;
|
map.showOverdrawInspector = this.props.options?.showOverdrawInspector!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.state.inspect && this.props.inspectModeEnabled !== this.state.inspect._showInspectMap) {
|
||||||
|
this.state.inspect.toggleInspector()
|
||||||
|
}
|
||||||
|
if (this.state.inspect && this.props.inspectModeEnabled) {
|
||||||
|
this.state.inspect.setOriginalStyle(styleWithTokens);
|
||||||
|
// In case the sources are the same, there's a need to refresh the style
|
||||||
|
setTimeout(() => {
|
||||||
|
this.state.inspect!.render();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if(!IS_SUPPORTED) return;
|
|
||||||
|
|
||||||
const mapOpts = {
|
const mapOpts = {
|
||||||
...this.props.options,
|
...this.props.options,
|
||||||
container: this.container!,
|
container: this.container!,
|
||||||
style: this.props.mapStyle,
|
style: this.props.mapStyle,
|
||||||
hash: true,
|
hash: true,
|
||||||
maxZoom: 24
|
maxZoom: 24,
|
||||||
}
|
// setting to always load glyphs of CJK fonts from server
|
||||||
|
// https://maplibre.org/maplibre-gl-js/docs/examples/local-ideographs/
|
||||||
|
localIdeographFontFamily: false
|
||||||
|
} satisfies MapOptions;
|
||||||
|
|
||||||
const map = new MapLibreGl.Map(mapOpts);
|
const map = new MapLibreGl.Map(mapOpts);
|
||||||
|
|
||||||
@@ -169,6 +152,8 @@ export default class MapMaplibreGl extends React.Component<MapMaplibreGlProps, M
|
|||||||
map.showCollisionBoxes = mapOpts.showCollisionBoxes!;
|
map.showCollisionBoxes = mapOpts.showCollisionBoxes!;
|
||||||
map.showOverdrawInspector = mapOpts.showOverdrawInspector!;
|
map.showOverdrawInspector = mapOpts.showOverdrawInspector!;
|
||||||
|
|
||||||
|
this.initGeocoder(map);
|
||||||
|
|
||||||
const zoomControl = new ZoomControl;
|
const zoomControl = new ZoomControl;
|
||||||
map.addControl(zoomControl, 'top-right');
|
map.addControl(zoomControl, 'top-right');
|
||||||
|
|
||||||
@@ -177,7 +162,7 @@ export default class MapMaplibreGl extends React.Component<MapMaplibreGlProps, M
|
|||||||
|
|
||||||
const tmpNode = document.createElement('div');
|
const tmpNode = document.createElement('div');
|
||||||
|
|
||||||
const inspect = new MapboxInspect({
|
const inspect = new MaplibreInspect({
|
||||||
popup: new MapLibreGl.Popup({
|
popup: new MapLibreGl.Popup({
|
||||||
closeOnClick: false
|
closeOnClick: false
|
||||||
}),
|
}),
|
||||||
@@ -234,25 +219,55 @@ export default class MapMaplibreGl extends React.Component<MapMaplibreGlProps, M
|
|||||||
this.props.onLayerSelect(index);
|
this.props.onLayerSelect(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initGeocoder(map: Map) {
|
||||||
|
const geocoderConfig = {
|
||||||
|
forwardGeocode: async (config:{query: string, limit: number, language: string[]}) => {
|
||||||
|
const features = [];
|
||||||
|
try {
|
||||||
|
const request = `https://nominatim.openstreetmap.org/search?q=${config.query}&format=geojson&polygon_geojson=1&addressdetails=1`;
|
||||||
|
const response = await fetch(request);
|
||||||
|
const geojson = await response.json();
|
||||||
|
for (const feature of geojson.features) {
|
||||||
|
const center = [
|
||||||
|
feature.bbox[0] +
|
||||||
|
(feature.bbox[2] - feature.bbox[0]) / 2,
|
||||||
|
feature.bbox[1] +
|
||||||
|
(feature.bbox[3] - feature.bbox[1]) / 2
|
||||||
|
];
|
||||||
|
const point = {
|
||||||
|
type: 'Feature',
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: center
|
||||||
|
},
|
||||||
|
place_name: feature.properties.display_name,
|
||||||
|
properties: feature.properties,
|
||||||
|
text: feature.properties.display_name,
|
||||||
|
place_type: ['place'],
|
||||||
|
center
|
||||||
|
};
|
||||||
|
features.push(point);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to forwardGeocode with error: ${e}`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
features
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const geocoder = new MaplibreGeocoder(geocoderConfig, {maplibregl: MapLibreGl});
|
||||||
|
map.addControl(geocoder, 'top-left');
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if(IS_SUPPORTED) {
|
return <div
|
||||||
return <div
|
className="maputnik-map__map"
|
||||||
className="maputnik-map__map"
|
role="region"
|
||||||
role="region"
|
aria-label="Map view"
|
||||||
aria-label="Map view"
|
ref={x => this.container = x}
|
||||||
ref={x => this.container = x}
|
data-wd-key="maplibre:map"
|
||||||
data-wd-key="maplibre:map"
|
></div>
|
||||||
></div>
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return <div
|
|
||||||
className="maputnik-map maputnik-map--error"
|
|
||||||
>
|
|
||||||
<div className="maputnik-map__error-message">
|
|
||||||
Error: Cannot load MaplibreGL, WebGL is either unsupported or disabled
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Block from './Block'
|
import type { GeoJSONFeatureWithSourceLayer } from '@maplibre/maplibre-gl-inspect'
|
||||||
import FieldString from './FieldString'
|
|
||||||
|
|
||||||
export type InspectFeature = {
|
export type InspectFeature = GeoJSONFeatureWithSourceLayer & {
|
||||||
id: string
|
|
||||||
properties: {[key:string]: any}
|
|
||||||
layer: {[key:string]: any}
|
|
||||||
geometry: GeoJSON.Geometry
|
|
||||||
sourceLayer: string
|
|
||||||
inspectModeCounter?: number
|
inspectModeCounter?: number
|
||||||
counter?: number
|
counter?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayValue(value: string | number | Date | object) {
|
function displayValue(value: string | number | Date | object | undefined) {
|
||||||
if (typeof value === 'undefined' || value === null) return value;
|
if (typeof value === 'undefined' || value === null) return value;
|
||||||
if (value instanceof Date) return value.toLocaleString();
|
if (value instanceof Date) return value.toLocaleString();
|
||||||
if (typeof value === 'object' ||
|
if (typeof value === 'object' ||
|
||||||
@@ -21,30 +15,25 @@ function displayValue(value: string | number | Date | object) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderProperties(feature: InspectFeature) {
|
function renderKeyValueTableRow(key: string, value: string | undefined) {
|
||||||
return Object.keys(feature.properties).map(propertyName => {
|
return <tr key={key}>
|
||||||
const property = feature.properties[propertyName]
|
<td className="maputnik-popup-table-cell">{key}</td>
|
||||||
return <Block key={propertyName} label={propertyName}>
|
<td className="maputnik-popup-table-cell">{value}</td>
|
||||||
<FieldString value={displayValue(property)} style={{backgroundColor: 'transparent'}}/>
|
</tr>
|
||||||
</Block>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderFeatureId(feature: InspectFeature) {
|
|
||||||
return <Block key={"feature-id"} label={"feature_id"}>
|
|
||||||
<FieldString value={displayValue(feature.id)} style={{backgroundColor: 'transparent'}} />
|
|
||||||
</Block>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFeature(feature: InspectFeature, idx: number) {
|
function renderFeature(feature: InspectFeature, idx: number) {
|
||||||
return <div key={`${feature.sourceLayer}-${idx}`}>
|
return <React.Fragment key={idx}>
|
||||||
<div className="maputnik-popup-layer-id">{feature.layer['source']}: {feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div>
|
<tr>
|
||||||
<Block key={"property-type"} label={"$type"}>
|
<td colSpan={2} className="maputnik-popup-layer-id">{feature.layer['source']}: {feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</td>
|
||||||
<FieldString value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
|
</tr>
|
||||||
</Block>
|
{renderKeyValueTableRow("$type", feature.geometry.type)}
|
||||||
{renderFeatureId(feature)}
|
{renderKeyValueTableRow("Feature ID", displayValue(feature.id))}
|
||||||
{renderProperties(feature)}
|
{Object.keys(feature.properties).map(propertyName => {
|
||||||
</div>
|
const property = feature.properties[propertyName];
|
||||||
|
return renderKeyValueTableRow(propertyName, displayValue(property))
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeDuplicatedFeatures(features: InspectFeature[]) {
|
function removeDuplicatedFeatures(features: InspectFeature[]) {
|
||||||
@@ -78,7 +67,11 @@ class FeaturePropertyPopup extends React.Component<FeaturePropertyPopupProps> {
|
|||||||
render() {
|
render() {
|
||||||
const features = removeDuplicatedFeatures(this.props.features)
|
const features = removeDuplicatedFeatures(this.props.features)
|
||||||
return <div className="maputnik-feature-property-popup">
|
return <div className="maputnik-feature-property-popup">
|
||||||
{features.map(renderFeature)}
|
<table className="maputnik-popup-table">
|
||||||
|
<tbody>
|
||||||
|
{features.map(renderFeature)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,16 @@ function groupFeaturesBySourceLayer(features: InspectFeature[]) {
|
|||||||
const returnedFeatures: {[key: string]: number} = {}
|
const returnedFeatures: {[key: string]: number} = {}
|
||||||
|
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
|
const sourceKey = feature.layer['source-layer'] as string;
|
||||||
if(Object.prototype.hasOwnProperty.call(returnedFeatures, feature.layer.id)) {
|
if(Object.prototype.hasOwnProperty.call(returnedFeatures, feature.layer.id)) {
|
||||||
returnedFeatures[feature.layer.id]++
|
returnedFeatures[feature.layer.id]++
|
||||||
|
|
||||||
const featureObject = sources[feature.layer['source-layer']].find((f: InspectFeature) => f.layer.id === feature.layer.id)
|
const featureObject = sources[sourceKey].find((f: InspectFeature) => f.layer.id === feature.layer.id)
|
||||||
|
|
||||||
featureObject!.counter = returnedFeatures[feature.layer.id]
|
featureObject!.counter = returnedFeatures[feature.layer.id]
|
||||||
} else {
|
} else {
|
||||||
sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || []
|
sources[sourceKey] = sources[sourceKey] || []
|
||||||
sources[feature.layer['source-layer']].push(feature)
|
sources[sourceKey].push(feature)
|
||||||
|
|
||||||
returnedFeatures[feature.layer.id] = 1
|
returnedFeatures[feature.layer.id] = 1
|
||||||
}
|
}
|
||||||
@@ -40,29 +41,21 @@ class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const paintProps = feature.layer.paint;
|
const paintProps = feature.layer.paint;
|
||||||
let propName;
|
|
||||||
|
|
||||||
if(Object.prototype.hasOwnProperty.call(paintProps, "text-color") && paintProps["text-color"]) {
|
if("text-color" in paintProps && paintProps["text-color"]) {
|
||||||
propName = "text-color";
|
return String(paintProps["text-color"]);
|
||||||
}
|
}
|
||||||
else if (Object.prototype.hasOwnProperty.call(paintProps, "fill-color") && paintProps["fill-color"]) {
|
if ("fill-color" in paintProps && paintProps["fill-color"]) {
|
||||||
propName = "fill-color";
|
return String(paintProps["fill-color"]);
|
||||||
}
|
}
|
||||||
else if (Object.prototype.hasOwnProperty.call(paintProps, "line-color") && paintProps["line-color"]) {
|
if ("line-color" in paintProps && paintProps["line-color"]) {
|
||||||
propName = "line-color";
|
return String(paintProps["line-color"]);
|
||||||
}
|
}
|
||||||
else if (Object.prototype.hasOwnProperty.call(paintProps, "fill-extrusion-color") && paintProps["fill-extrusion-color"]) {
|
if ("fill-extrusion-color" in paintProps && paintProps["fill-extrusion-color"]) {
|
||||||
propName = "fill-extrusion-color";
|
return String(paintProps["fill-extrusion-color"]);
|
||||||
}
|
|
||||||
|
|
||||||
if(propName) {
|
|
||||||
const color = feature.layer.paint[propName];
|
|
||||||
return String(color);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Default color
|
|
||||||
return "black";
|
|
||||||
}
|
}
|
||||||
|
// Default color
|
||||||
|
return "black";
|
||||||
}
|
}
|
||||||
// This is quite complex, just incase there's an edgecase we're missing
|
// This is quite complex, just incase there's an edgecase we're missing
|
||||||
// always return black if we get an unexpected error.
|
// always return black if we get an unexpected error.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react'
|
import React, { PropsWithChildren } from 'react'
|
||||||
import {MdClose} from 'react-icons/md'
|
import {MdClose} from 'react-icons/md'
|
||||||
import AriaModal from 'react-aria-modal'
|
import AriaModal from 'react-aria-modal'
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
|
||||||
type ModalProps = {
|
type ModalProps = PropsWithChildren & {
|
||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
title: string
|
title: string
|
||||||
@@ -20,7 +20,7 @@ export default class Modal extends React.Component<ModalProps> {
|
|||||||
underlayClickExits: true
|
underlayClickExits: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// See <https://github.com/maputnik/editor/issues/416>
|
// See <https://github.com/maplibre/maputnik/issues/416>
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
if (document.activeElement) {
|
if (document.activeElement) {
|
||||||
(document.activeElement as HTMLElement).blur();
|
(document.activeElement as HTMLElement).blur();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import type {LightSpecification, StyleSpecification, TransitionSpecification} from 'maplibre-gl'
|
import type {LightSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from 'maplibre-gl'
|
||||||
|
|
||||||
import FieldArray from './FieldArray'
|
import FieldArray from './FieldArray'
|
||||||
import FieldNumber from './FieldNumber'
|
import FieldNumber from './FieldNumber'
|
||||||
@@ -58,6 +58,25 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeTerrainProperty(property: keyof TerrainSpecification, value: any) {
|
||||||
|
const terrain = {
|
||||||
|
...this.props.mapStyle.terrain,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === undefined) {
|
||||||
|
delete terrain[property];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// @ts-ignore
|
||||||
|
terrain[property] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onStyleChanged({
|
||||||
|
...this.props.mapStyle,
|
||||||
|
terrain,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
changeStyleProperty(property: keyof StyleSpecification | "owner", value: any) {
|
changeStyleProperty(property: keyof StyleSpecification | "owner", value: any) {
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...this.props.mapStyle,
|
...this.props.mapStyle,
|
||||||
@@ -77,10 +96,10 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
render() {
|
render() {
|
||||||
const metadata = this.props.mapStyle.metadata || {} as any;
|
const metadata = this.props.mapStyle.metadata || {} as any;
|
||||||
const {onChangeMetadataProperty, mapStyle} = this.props;
|
const {onChangeMetadataProperty, mapStyle} = this.props;
|
||||||
const inputProps = { }
|
|
||||||
|
|
||||||
const light = this.props.mapStyle.light || {};
|
const light = this.props.mapStyle.light || {};
|
||||||
const transition = this.props.mapStyle.transition || {};
|
const transition = this.props.mapStyle.transition || {};
|
||||||
|
const terrain = this.props.mapStyle.terrain || {} as TerrainSpecification;
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="modal:settings"
|
data-wd-key="modal:settings"
|
||||||
@@ -89,21 +108,21 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
title={'Style Settings'}
|
title={'Style Settings'}
|
||||||
>
|
>
|
||||||
<div className="modal:settings">
|
<div className="modal:settings">
|
||||||
<FieldString {...inputProps}
|
<FieldString
|
||||||
label={"Name"}
|
label={"Name"}
|
||||||
fieldSpec={latest.$root.name}
|
fieldSpec={latest.$root.name}
|
||||||
data-wd-key="modal:settings.name"
|
data-wd-key="modal:settings.name"
|
||||||
value={this.props.mapStyle.name}
|
value={this.props.mapStyle.name}
|
||||||
onChange={this.changeStyleProperty.bind(this, "name")}
|
onChange={this.changeStyleProperty.bind(this, "name")}
|
||||||
/>
|
/>
|
||||||
<FieldString {...inputProps}
|
<FieldString
|
||||||
label={"Owner"}
|
label={"Owner"}
|
||||||
fieldSpec={{doc: "Owner ID of the style. Used by Mapbox or future style APIs."}}
|
fieldSpec={{doc: "Owner ID of the style. Used by Mapbox or future style APIs."}}
|
||||||
data-wd-key="modal:settings.owner"
|
data-wd-key="modal:settings.owner"
|
||||||
value={(this.props.mapStyle as any).owner}
|
value={(this.props.mapStyle as any).owner}
|
||||||
onChange={this.changeStyleProperty.bind(this, "owner")}
|
onChange={this.changeStyleProperty.bind(this, "owner")}
|
||||||
/>
|
/>
|
||||||
<FieldUrl {...inputProps}
|
<FieldUrl
|
||||||
fieldSpec={latest.$root.sprite}
|
fieldSpec={latest.$root.sprite}
|
||||||
label="Sprite URL"
|
label="Sprite URL"
|
||||||
data-wd-key="modal:settings.sprite"
|
data-wd-key="modal:settings.sprite"
|
||||||
@@ -111,7 +130,7 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
onChange={this.changeStyleProperty.bind(this, "sprite")}
|
onChange={this.changeStyleProperty.bind(this, "sprite")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldUrl {...inputProps}
|
<FieldUrl
|
||||||
label="Glyphs URL"
|
label="Glyphs URL"
|
||||||
fieldSpec={latest.$root.glyphs}
|
fieldSpec={latest.$root.glyphs}
|
||||||
data-wd-key="modal:settings.glyphs"
|
data-wd-key="modal:settings.glyphs"
|
||||||
@@ -119,7 +138,7 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
onChange={this.changeStyleProperty.bind(this, "glyphs")}
|
onChange={this.changeStyleProperty.bind(this, "glyphs")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldString {...inputProps}
|
<FieldString
|
||||||
label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
|
label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
|
||||||
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
|
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
|
||||||
data-wd-key="modal:settings.maputnik:openmaptiles_access_token"
|
data-wd-key="modal:settings.maputnik:openmaptiles_access_token"
|
||||||
@@ -127,7 +146,7 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldString {...inputProps}
|
<FieldString
|
||||||
label={fieldSpecAdditional.maputnik.thunderforest_access_token.label}
|
label={fieldSpecAdditional.maputnik.thunderforest_access_token.label}
|
||||||
fieldSpec={fieldSpecAdditional.maputnik.thunderforest_access_token}
|
fieldSpec={fieldSpecAdditional.maputnik.thunderforest_access_token}
|
||||||
data-wd-key="modal:settings.maputnik:thunderforest_access_token"
|
data-wd-key="modal:settings.maputnik:thunderforest_access_token"
|
||||||
@@ -141,21 +160,19 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
length={2}
|
length={2}
|
||||||
type="number"
|
type="number"
|
||||||
value={mapStyle.center || []}
|
value={mapStyle.center || []}
|
||||||
default={latest.$root.center.default || [0, 0]}
|
default={[0, 0]}
|
||||||
onChange={this.changeStyleProperty.bind(this, "center")}
|
onChange={this.changeStyleProperty.bind(this, "center")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
{...inputProps}
|
|
||||||
label={"Zoom"}
|
label={"Zoom"}
|
||||||
fieldSpec={latest.$root.zoom}
|
fieldSpec={latest.$root.zoom}
|
||||||
value={mapStyle.zoom}
|
value={mapStyle.zoom}
|
||||||
default={latest.$root.zoom.default || 0}
|
default={0}
|
||||||
onChange={this.changeStyleProperty.bind(this, "zoom")}
|
onChange={this.changeStyleProperty.bind(this, "zoom")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
{...inputProps}
|
|
||||||
label={"Bearing"}
|
label={"Bearing"}
|
||||||
fieldSpec={latest.$root.bearing}
|
fieldSpec={latest.$root.bearing}
|
||||||
value={mapStyle.bearing}
|
value={mapStyle.bearing}
|
||||||
@@ -164,7 +181,6 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
{...inputProps}
|
|
||||||
label={"Pitch"}
|
label={"Pitch"}
|
||||||
fieldSpec={latest.$root.pitch}
|
fieldSpec={latest.$root.pitch}
|
||||||
value={mapStyle.pitch}
|
value={mapStyle.pitch}
|
||||||
@@ -173,7 +189,6 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldEnum
|
<FieldEnum
|
||||||
{...inputProps}
|
|
||||||
label={"Light anchor"}
|
label={"Light anchor"}
|
||||||
fieldSpec={latest.light.anchor}
|
fieldSpec={latest.light.anchor}
|
||||||
name="light-anchor"
|
name="light-anchor"
|
||||||
@@ -184,7 +199,6 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldColor
|
<FieldColor
|
||||||
{...inputProps}
|
|
||||||
label={"Light color"}
|
label={"Light color"}
|
||||||
fieldSpec={latest.light.color}
|
fieldSpec={latest.light.color}
|
||||||
value={light.color as string}
|
value={light.color as string}
|
||||||
@@ -193,7 +207,6 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
{...inputProps}
|
|
||||||
label={"Light intensity"}
|
label={"Light intensity"}
|
||||||
fieldSpec={latest.light.intensity}
|
fieldSpec={latest.light.intensity}
|
||||||
value={light.intensity as number}
|
value={light.intensity as number}
|
||||||
@@ -202,7 +215,6 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldArray
|
<FieldArray
|
||||||
{...inputProps}
|
|
||||||
label={"Light position"}
|
label={"Light position"}
|
||||||
fieldSpec={latest.light.position}
|
fieldSpec={latest.light.position}
|
||||||
type="number"
|
type="number"
|
||||||
@@ -212,8 +224,23 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
onChange={this.changeLightProperty.bind(this, "position")}
|
onChange={this.changeLightProperty.bind(this, "position")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FieldString
|
||||||
|
label={"Terrain source"}
|
||||||
|
fieldSpec={latest.terrain.source}
|
||||||
|
data-wd-key="modal:settings.maputnik:terrain_source"
|
||||||
|
value={terrain.source}
|
||||||
|
onChange={this.changeTerrainProperty.bind(this, "source")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FieldNumber
|
||||||
|
label={"Terrain exaggeration"}
|
||||||
|
fieldSpec={latest.terrain.exaggeration}
|
||||||
|
value={terrain.exaggeration}
|
||||||
|
default={latest.terrain.exaggeration.default}
|
||||||
|
onChange={this.changeTerrainProperty.bind(this, "exaggeration")}
|
||||||
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
{...inputProps}
|
|
||||||
label={"Transition delay"}
|
label={"Transition delay"}
|
||||||
fieldSpec={latest.transition.delay}
|
fieldSpec={latest.transition.delay}
|
||||||
value={transition.delay}
|
value={transition.delay}
|
||||||
@@ -222,7 +249,6 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
{...inputProps}
|
|
||||||
label={"Transition duration"}
|
label={"Transition duration"}
|
||||||
fieldSpec={latest.transition.duration}
|
fieldSpec={latest.transition.duration}
|
||||||
value={transition.duration}
|
value={transition.duration}
|
||||||
@@ -230,7 +256,7 @@ export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
|||||||
onChange={this.changeTransitionProperty.bind(this, "duration")}
|
onChange={this.changeTransitionProperty.bind(this, "duration")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldSelect {...inputProps}
|
<FieldSelect
|
||||||
label={fieldSpecAdditional.maputnik.style_renderer.label}
|
label={fieldSpecAdditional.maputnik.style_renderer.label}
|
||||||
fieldSpec={fieldSpecAdditional.maputnik.style_renderer}
|
fieldSpec={fieldSpecAdditional.maputnik.style_renderer}
|
||||||
data-wd-key="modal:settings.maputnik:renderer"
|
data-wd-key="modal:settings.maputnik:renderer"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
|
import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import type {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, StyleSpecification, VectorSourceSpecification} from 'maplibre-gl'
|
import type {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, StyleSpecification, VectorSourceSpecification} from 'maplibre-gl'
|
||||||
|
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
@@ -134,7 +134,7 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
|||||||
case 'geojson_json': return {
|
case 'geojson_json': return {
|
||||||
type: 'geojson',
|
type: 'geojson',
|
||||||
cluster: (source as GeoJSONSourceSpecification).cluster || false,
|
cluster: (source as GeoJSONSourceSpecification).cluster || false,
|
||||||
data: {}
|
data: ''
|
||||||
}
|
}
|
||||||
case 'tilejson_vector': return {
|
case 'tilejson_vector': return {
|
||||||
type: 'vector',
|
type: 'vector',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
|
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
|
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import InputSpec from './InputSpec'
|
import InputSpec from './InputSpec'
|
||||||
@@ -302,7 +302,7 @@ export default class DataProperty extends React.Component<DataPropertyProps, Dat
|
|||||||
<div className="maputnik-data-spec-property-input">
|
<div className="maputnik-data-spec-property-input">
|
||||||
<InputSpec
|
<InputSpec
|
||||||
fieldName={"base"}
|
fieldName={"base"}
|
||||||
fieldSpec={latest.function.base}
|
fieldSpec={latest.function.base as typeof latest.function.base & { type: "number" }}
|
||||||
value={this.props.value?.base}
|
value={this.props.value?.base}
|
||||||
onChange={(_, newValue) => this.changeBase(newValue as number)}
|
onChange={(_, newValue) => this.changeBase(newValue as number)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
|
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
|
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import InputSpec from './InputSpec'
|
import InputSpec from './InputSpec'
|
||||||
@@ -207,7 +207,7 @@ export default class ZoomProperty extends React.Component<ZoomPropertyProps, Zoo
|
|||||||
<div className="maputnik-data-spec-property-input">
|
<div className="maputnik-data-spec-property-input">
|
||||||
<InputSpec
|
<InputSpec
|
||||||
fieldName={"base"}
|
fieldName={"base"}
|
||||||
fieldSpec={latest.function.base}
|
fieldSpec={latest.function.base as typeof latest.function.base & { type: "number" }}
|
||||||
value={this.props.value?.base}
|
value={this.props.value?.base}
|
||||||
onChange={(_, newValue) => this.changeBase(newValue as number | undefined)}
|
onChange={(_, newValue) => this.changeBase(newValue as number | undefined)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"openmaptiles": "KDhMfHvorAFkFe64wlZb",
|
"openmaptiles": "get_your_own_OpIi9ZULNHzrESv6T2vL",
|
||||||
"thunderforest": "b71f7f0ba4064f5eb9e903859a9cf5c6"
|
"thunderforest": "b71f7f0ba4064f5eb9e903859a9cf5c6"
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -1,15 +1,15 @@
|
|||||||
import { IconContext } from "react-icons";
|
import { IconContext } from "react-icons";
|
||||||
import ReactDOM from 'react-dom';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import './favicon.ico'
|
import './favicon.ico'
|
||||||
import './styles/index.scss'
|
import './styles/index.scss'
|
||||||
import App from './components/App';
|
import App from './components/App';
|
||||||
|
|
||||||
ReactDOM.render(
|
const root = createRoot(document.querySelector("#app"));
|
||||||
|
root.render(
|
||||||
<IconContext.Provider value={{className: 'react-icons'}}>
|
<IconContext.Provider value={{className: 'react-icons'}}>
|
||||||
<App/>
|
<App/>
|
||||||
</IconContext.Provider>,
|
</IconContext.Provider>
|
||||||
document.querySelector("#app")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hide the loader.
|
// Hide the loader.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import jsonlint from 'jsonlint';
|
import jsonlint from 'jsonlint';
|
||||||
import CodeMirror, { MarkerRange } from 'codemirror';
|
import CodeMirror, { MarkerRange } from 'codemirror';
|
||||||
import jsonToAst from 'json-to-ast';
|
import jsonToAst from 'json-to-ast';
|
||||||
import {expression, validate} from '@maplibre/maplibre-gl-style-spec';
|
import {expression, validateStyleMin} from '@maplibre/maplibre-gl-style-spec';
|
||||||
|
|
||||||
type MarkerRangeWithMessage = MarkerRange & {message: string};
|
type MarkerRangeWithMessage = MarkerRange & {message: string};
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ CodeMirror.registerHelper("lint", "mgl", (text: string, opts: any, doc: any) =>
|
|||||||
let out: ReturnType<typeof expression.createExpression> | null = null;
|
let out: ReturnType<typeof expression.createExpression> | null = null;
|
||||||
if (context === "layer") {
|
if (context === "layer") {
|
||||||
// Just an empty style so we can validate a layer.
|
// Just an empty style so we can validate a layer.
|
||||||
const errors = validate({
|
const errors = validateStyleMin({
|
||||||
"version": 8,
|
"version": 8,
|
||||||
"name": "Empty Style",
|
"name": "Empty Style",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
|
|
||||||
export const combiningFilterOps = ['all', 'any', 'none'];
|
export const combiningFilterOps = ['all', 'any', 'none'];
|
||||||
export const setFilterOps = ['in', '!in'];
|
export const setFilterOps = ['in', '!in'];
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// @ts-ignore
|
import stylegen from '@maplibre/maplibre-gl-inspect/lib/stylegen'
|
||||||
import stylegen from 'mapbox-gl-inspect/lib/stylegen'
|
import colors from '@maplibre/maplibre-gl-inspect/lib/colors'
|
||||||
// @ts-ignore
|
|
||||||
import colors from 'mapbox-gl-inspect/lib/colors'
|
|
||||||
import type {FilterSpecification,LayerSpecification } from 'maplibre-gl'
|
import type {FilterSpecification,LayerSpecification } from 'maplibre-gl'
|
||||||
|
|
||||||
export type HighlightedLayer = LayerSpecification & {filter?: FilterSpecification};
|
export type HighlightedLayer = LayerSpecification & {filter?: FilterSpecification};
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import MapLibreGl from "maplibre-gl"
|
import MapLibreGl from "maplibre-gl"
|
||||||
|
|
||||||
MapLibreGl.setRTLTextPlugin('https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.min.js', () => {});
|
MapLibreGl.setRTLTextPlugin('https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.min.js', false);
|
||||||
|
|||||||
@@ -19,10 +19,13 @@ export class RevisionStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addRevision(revision: StyleSpecification & {id: string}) {
|
addRevision(revision: StyleSpecification & {id: string}) {
|
||||||
//TODO: compare new revision style id with old ones
|
// clear any "redo" revisions once a change is made
|
||||||
//and ensure that it is always the same id
|
// and ensure current index is at end of list
|
||||||
|
this.revisions = this.revisions.slice(0, this.currentIdx + 1);
|
||||||
|
|
||||||
this.revisions.push(revision)
|
this.revisions.push(revision)
|
||||||
this.currentIdx++
|
this.currentIdx++
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
|
|||||||
@@ -36,3 +36,12 @@
|
|||||||
margin-top: $margin-2;
|
margin-top: $margin-2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-popup-table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-popup-table-cell {
|
||||||
|
color: $color-lowgray;
|
||||||
|
padding-left: $margin-2;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import '../src/styles/index.scss';
|
|
||||||
import React from 'react';
|
|
||||||
import {Describe} from './ui';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Welcome',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ToStorybook = () => {
|
|
||||||
return (
|
|
||||||
<Describe>
|
|
||||||
<h1>Maputnik component library</h1>
|
|
||||||
<p>
|
|
||||||
This is the Maputnik component library, which shows the uses of some commonly used components from the Maputnik editor. This is a stand alone place where we can better refine them and improve their API separate from their use inside the editor.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This should also help us better refine our CSS and make it more modular as currently we rely on the cascade quite a bit in a number of places.
|
|
||||||
</p>
|
|
||||||
</Describe>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ToStorybook.story = {
|
|
||||||
name: 'Intro',
|
|
||||||
};
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldArray from '../src/components/FieldArray';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldArray',
|
|
||||||
component: FieldArray,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const NumberType = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", [1,2,3]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldArray
|
|
||||||
label="Foobar"
|
|
||||||
type="number"
|
|
||||||
value={value}
|
|
||||||
length={3}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StringType = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", ["a", "b", "c"]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldArray
|
|
||||||
label="Foobar"
|
|
||||||
type="string"
|
|
||||||
value={value}
|
|
||||||
length={3}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldAutocomplete from '../src/components/FieldAutocomplete';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldAutocomplete',
|
|
||||||
component: FieldAutocomplete,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const options = [["FOO", "foo"], ["BAR", "bar"], ["BAZ", "baz"]];
|
|
||||||
const [value, setValue] = useActionState("onChange", "bar");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldAutocomplete
|
|
||||||
label="Foobar"
|
|
||||||
options={options}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldCheckbox from '../src/components/FieldCheckbox';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldCheckbox',
|
|
||||||
component: FieldCheckbox,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const BasicUnchecked = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldCheckbox
|
|
||||||
label="Foobar"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BasicChecked = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", true);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldCheckbox
|
|
||||||
label="Foobar"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldColor from '../src/components/FieldColor';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldColor',
|
|
||||||
component: FieldColor,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const [color, setColor] = useActionState("onChange", "#ff0000");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldColor
|
|
||||||
label="Foobar"
|
|
||||||
value={color}
|
|
||||||
onChange={setColor}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldDynamicArray from '../src/components/FieldDynamicArray';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldDynamicArray',
|
|
||||||
component: FieldDynamicArray,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const NumberType = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", [1,2,3]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldDynamicArray
|
|
||||||
label="Foobar"
|
|
||||||
type="number"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UrlType = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", ["http://example.com"]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldDynamicArray
|
|
||||||
label="Foobar"
|
|
||||||
type="url"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EnumType = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", ["foo"]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldDynamicArray
|
|
||||||
label="Foobar"
|
|
||||||
fieldSpec={{values: {"foo": null, "bar": null, "baz": null}}}
|
|
||||||
type="enum"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldEnum from '../src/components/FieldEnum';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldEnum',
|
|
||||||
component: FieldEnum,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const BasicFew = () => {
|
|
||||||
const options = ["Foo", "Bar", "Baz"];
|
|
||||||
const [value, setValue] = useActionState("onChange", "Foo");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldEnum
|
|
||||||
label="Foobar"
|
|
||||||
options={options}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BasicFewWithDefault = () => {
|
|
||||||
const options = ["Foo", "Bar", "Baz"];
|
|
||||||
const [value, setValue] = useActionState("onChange", null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldEnum
|
|
||||||
label="Foobar"
|
|
||||||
options={options}
|
|
||||||
default={"Baz"}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BasicMany = () => {
|
|
||||||
const options = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
|
|
||||||
const [value, setValue] = useActionState("onChange", "a");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldEnum
|
|
||||||
label="Foobar"
|
|
||||||
options={options}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BasicManyWithDefault = () => {
|
|
||||||
const options = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
|
|
||||||
const [value, setValue] = useActionState("onChange", "a");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldEnum
|
|
||||||
label="Foobar"
|
|
||||||
options={options}
|
|
||||||
default={"h"}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import FieldFunction from '../src/components/FieldFunction';
|
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldFunction',
|
|
||||||
component: FieldFunction,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const value = {
|
|
||||||
"property": "rank",
|
|
||||||
"type": "categorical",
|
|
||||||
"default": "#222",
|
|
||||||
"stops": [
|
|
||||||
[
|
|
||||||
{"zoom": 6, "value": ""},
|
|
||||||
["#777"]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{"zoom": 10, "value": ""},
|
|
||||||
["#444"]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
return <div style={{width: "360px"}}>
|
|
||||||
<FieldFunction
|
|
||||||
onChange={() => {}}
|
|
||||||
value={value}
|
|
||||||
errors={[]}
|
|
||||||
fieldName={"Color"}
|
|
||||||
fieldType={"color"}
|
|
||||||
fieldSpec={latest['paint_fill']['fill-color']}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldMultiInput from '../src/components/FieldMultiInput';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldMultiInput',
|
|
||||||
component: FieldMultiInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const options = [["FOO", "foo"], ["BAR", "bar"], ["BAZ", "baz"]];
|
|
||||||
const [value, setValue] = useActionState("onChange", "FOO");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldMultiInput
|
|
||||||
label="Foobar"
|
|
||||||
options={options}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldNumber from '../src/components/FieldNumber';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldNumber',
|
|
||||||
component: FieldNumber,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", 1);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldNumber
|
|
||||||
label="number"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Range = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", 1);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldNumber
|
|
||||||
label="number"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
min={1}
|
|
||||||
max={24}
|
|
||||||
allowRange={true}
|
|
||||||
rangeStep={1}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldSelect from '../src/components/FieldSelect';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldSelect',
|
|
||||||
component: FieldSelect,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const options = [["FOO", "Foo"], ["BAR", "Bar"], ["BAZ", "Baz"]];
|
|
||||||
const [value, setValue] = useActionState("onChange", "FOO");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldSelect
|
|
||||||
label="Foobar"
|
|
||||||
options={options}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldString from '../src/components/FieldString';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldString',
|
|
||||||
component: FieldString,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", "Hello world");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldString
|
|
||||||
label="Foobar"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import FieldUrl from '../src/components/FieldUrl';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'FieldUrl',
|
|
||||||
component: FieldUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Valid = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", "http://example.com");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldUrl
|
|
||||||
label="Foobar"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
onInput={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Invalid = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", "foo");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<FieldUrl
|
|
||||||
label="Foobar"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
onInput={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import IconLayer from '../src/components/IconLayer';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'IconLayer',
|
|
||||||
component: IconLayer,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const IconList = () => {
|
|
||||||
const types = [
|
|
||||||
'fill-extrusion',
|
|
||||||
'raster',
|
|
||||||
'hillshade',
|
|
||||||
'heatmap',
|
|
||||||
'fill',
|
|
||||||
'background',
|
|
||||||
'line',
|
|
||||||
'symbol',
|
|
||||||
'circle',
|
|
||||||
'INVALID',
|
|
||||||
]
|
|
||||||
|
|
||||||
return <Wrapper>
|
|
||||||
<table style={{textAlign: "left"}}>
|
|
||||||
<thead style={{borderBottom: "solid 1px white"}}>
|
|
||||||
<tr>
|
|
||||||
<td>ID</td>
|
|
||||||
<td>Preview</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{types.map(type => (
|
|
||||||
<tr>
|
|
||||||
<td style={{paddingRight: "1em"}}>
|
|
||||||
<code>{type}</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<IconLayer type={type} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</Wrapper>
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputArray from '../src/components/InputArray';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputArray',
|
|
||||||
component: InputArray,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const NumberType = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", [1,2,3]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<InputArray
|
|
||||||
type="number"
|
|
||||||
value={value}
|
|
||||||
length={3}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StringType = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", ["a", "b", "c"]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<InputArray
|
|
||||||
type="string"
|
|
||||||
value={value}
|
|
||||||
length={3}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputAutocomplete from '../src/components/InputAutocomplete';
|
|
||||||
import {InputContainer} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputAutocomplete',
|
|
||||||
component: InputAutocomplete,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const options = [["FOO", "foo"], ["BAR", "bar"], ["BAZ", "baz"]];
|
|
||||||
const [value, setValue] = useActionState("onChange", "bar");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputAutocomplete
|
|
||||||
label="Foobar"
|
|
||||||
options={options}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import InputButton from '../src/components/InputButton';
|
|
||||||
import {action} from '@storybook/addon-actions';
|
|
||||||
import {InputContainer} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputButton',
|
|
||||||
component: InputButton,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Basic = () => (
|
|
||||||
<InputContainer>
|
|
||||||
<InputButton onClick={action('onClick')}>
|
|
||||||
Hello InputButton
|
|
||||||
</InputButton>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputCheckbox from '../src/components/InputCheckbox';
|
|
||||||
import {InputContainer} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputCheckbox',
|
|
||||||
component: InputCheckbox,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const BasicUnchecked = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputCheckbox
|
|
||||||
label="Foobar"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BasicChecked = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", true);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputCheckbox
|
|
||||||
label="Foobar"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputColor from '../src/components/InputColor';
|
|
||||||
import {InputContainer} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputColor',
|
|
||||||
component: InputColor,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const [color, setColor] = useActionState("onChange", "#ff0000");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputColor
|
|
||||||
label="Foobar"
|
|
||||||
value={color}
|
|
||||||
onChange={setColor}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputDynamicArray from '../src/components/InputDynamicArray';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputDynamicArray',
|
|
||||||
component: InputDynamicArray,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const NumberType = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", [1,2,3]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<InputDynamicArray
|
|
||||||
type="number"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UrlType = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", ["http://example.com"]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<InputDynamicArray
|
|
||||||
type="url"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EnumType = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", ["foo"]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<InputDynamicArray
|
|
||||||
fieldSpec={{values: {"foo": null, "bar": null, "baz": null}}}
|
|
||||||
type="enum"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputEnum from '../src/components/InputEnum';
|
|
||||||
import {InputContainer} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputEnum',
|
|
||||||
component: InputEnum,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const BasicFew = () => {
|
|
||||||
const options = ["Foo", "Bar", "Baz"];
|
|
||||||
const [value, setValue] = useActionState("onChange", "Foo");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputEnum
|
|
||||||
options={options}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BasicFewWithDefault = () => {
|
|
||||||
const options = ["Foo", "Bar", "Baz"];
|
|
||||||
const [value, setValue] = useActionState("onChange", null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputEnum
|
|
||||||
options={options}
|
|
||||||
default={"Baz"}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BasicMany = () => {
|
|
||||||
const options = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
|
|
||||||
const [value, setValue] = useActionState("onChange", "a");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputEnum
|
|
||||||
options={options}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BasicManyWithDefault = () => {
|
|
||||||
const options = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
|
|
||||||
const [value, setValue] = useActionState("onChange", "a");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputEnum
|
|
||||||
options={options}A
|
|
||||||
default={"h"}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import InputJson from '../src/components/InputJson';
|
|
||||||
import {action} from '@storybook/addon-actions';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputJson',
|
|
||||||
component: InputJson,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const layer = {
|
|
||||||
id: "background",
|
|
||||||
type: "background",
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Wrapper>
|
|
||||||
<InputJson
|
|
||||||
layer={layer}
|
|
||||||
onClick={action('onClick')}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputMultiInput from '../src/components/InputMultiInput';
|
|
||||||
import {InputContainer} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputMultiInput',
|
|
||||||
component: InputMultiInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const options = [["FOO", "foo"], ["BAR", "bar"], ["BAZ", "baz"]];
|
|
||||||
const [value, setValue] = useActionState("onChange", "FOO");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputMultiInput
|
|
||||||
options={options}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputNumber from '../src/components/InputNumber';
|
|
||||||
import {InputContainer} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputNumber',
|
|
||||||
component: InputNumber,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", 1);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputNumber
|
|
||||||
name="number"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Range = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", 1);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputNumber
|
|
||||||
name="number"
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
min={1}
|
|
||||||
max={24}
|
|
||||||
allowRange={true}
|
|
||||||
rangeStep={1}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputSelect from '../src/components/InputSelect';
|
|
||||||
import {InputContainer} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputSelect',
|
|
||||||
component: InputSelect,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const options = [["FOO", "Foo"], ["BAR", "Bar"], ["BAZ", "Baz"]];
|
|
||||||
const [value, setValue] = useActionState("onChange", "FOO");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputSelect
|
|
||||||
options={options}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputString from '../src/components/InputString';
|
|
||||||
import {InputContainer} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputString',
|
|
||||||
component: InputString,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", "Hello world");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputString
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithDefault = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputString
|
|
||||||
value={value}
|
|
||||||
default={"Edit me..."}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Multiline = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", "Hello\nworld");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputString
|
|
||||||
multi={true}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MultilineWithDefault = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputString
|
|
||||||
multi={true}
|
|
||||||
default={"Edit\nme.."}
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useActionState} from './helper';
|
|
||||||
import InputUrl from '../src/components/InputUrl';
|
|
||||||
import {InputContainer} from './ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'InputUrl',
|
|
||||||
component: InputUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const Valid = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", "http://example.com");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputUrl
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
onInput={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Invalid = () => {
|
|
||||||
const [value, setValue] = useActionState("onChange", "foo");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputContainer>
|
|
||||||
<InputUrl
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
onInput={setValue}
|
|
||||||
/>
|
|
||||||
</InputContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import LayerEditor from '../src/components/LayerEditor';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'LayerEditor',
|
|
||||||
component: LayerEditor,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Background = () => (
|
|
||||||
<div>
|
|
||||||
<LayerEditor
|
|
||||||
layer={{id: "background", type: "background"}}
|
|
||||||
sources={{}}
|
|
||||||
vectorLayers={{}}
|
|
||||||
spec={latest}
|
|
||||||
errors={[]}
|
|
||||||
onLayerChanged={() => {}}
|
|
||||||
onLayerIdChange={() => {}}
|
|
||||||
onMoveLayer={() => {}}
|
|
||||||
onLayerDestroy={() => {}}
|
|
||||||
onLayerCopy={() => {}}
|
|
||||||
onLayerVisibilityToggle={() => {}}
|
|
||||||
isFirstLayer={true}
|
|
||||||
isLastLayer={false}
|
|
||||||
layerIndex={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Fill = () => (
|
|
||||||
<div>
|
|
||||||
<LayerEditor
|
|
||||||
layer={{id: "fill", type: "fill", source: "openmaptiles"}}
|
|
||||||
sources={{}}
|
|
||||||
vectorLayers={{}}
|
|
||||||
spec={latest}
|
|
||||||
errors={[]}
|
|
||||||
onLayerChanged={() => {}}
|
|
||||||
onLayerIdChange={() => {}}
|
|
||||||
onMoveLayer={() => {}}
|
|
||||||
onLayerDestroy={() => {}}
|
|
||||||
onLayerCopy={() => {}}
|
|
||||||
onLayerVisibilityToggle={() => {}}
|
|
||||||
isFirstLayer={true}
|
|
||||||
isLastLayer={false}
|
|
||||||
layerIndex={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Line = () => (
|
|
||||||
<div>
|
|
||||||
<LayerEditor
|
|
||||||
layer={{id: "line", type: "line", source: "openmaptiles"}}
|
|
||||||
sources={{}}
|
|
||||||
vectorLayers={{}}
|
|
||||||
spec={latest}
|
|
||||||
errors={[]}
|
|
||||||
onLayerChanged={() => {}}
|
|
||||||
onLayerIdChange={() => {}}
|
|
||||||
onMoveLayer={() => {}}
|
|
||||||
onLayerDestroy={() => {}}
|
|
||||||
onLayerCopy={() => {}}
|
|
||||||
onLayerVisibilityToggle={() => {}}
|
|
||||||
isFirstLayer={true}
|
|
||||||
isLastLayer={false}
|
|
||||||
layerIndex={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Symbol = () => (
|
|
||||||
<div>
|
|
||||||
<LayerEditor
|
|
||||||
layer={{id: "symbol", type: "symbol", source: "openmaptiles"}}
|
|
||||||
sources={{}}
|
|
||||||
vectorLayers={{}}
|
|
||||||
spec={latest}
|
|
||||||
errors={[]}
|
|
||||||
onLayerChanged={() => {}}
|
|
||||||
onLayerIdChange={() => {}}
|
|
||||||
onMoveLayer={() => {}}
|
|
||||||
onLayerDestroy={() => {}}
|
|
||||||
onLayerCopy={() => {}}
|
|
||||||
onLayerVisibilityToggle={() => {}}
|
|
||||||
isFirstLayer={true}
|
|
||||||
isLastLayer={false}
|
|
||||||
layerIndex={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Raster = () => (
|
|
||||||
<div>
|
|
||||||
<LayerEditor
|
|
||||||
layer={{id: "raster", type: "raster", source: "openmaptiles"}}
|
|
||||||
sources={{}}
|
|
||||||
vectorLayers={{}}
|
|
||||||
spec={latest}
|
|
||||||
errors={[]}
|
|
||||||
onLayerChanged={() => {}}
|
|
||||||
onLayerIdChange={() => {}}
|
|
||||||
onMoveLayer={() => {}}
|
|
||||||
onLayerDestroy={() => {}}
|
|
||||||
onLayerCopy={() => {}}
|
|
||||||
onLayerVisibilityToggle={() => {}}
|
|
||||||
isFirstLayer={true}
|
|
||||||
isLastLayer={false}
|
|
||||||
layerIndex={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Cirlce = () => (
|
|
||||||
<div>
|
|
||||||
<LayerEditor
|
|
||||||
layer={{id: "circle", type: "circle", source: "openmaptiles"}}
|
|
||||||
sources={{}}
|
|
||||||
vectorLayers={{}}
|
|
||||||
spec={latest}
|
|
||||||
errors={[]}
|
|
||||||
onLayerChanged={() => {}}
|
|
||||||
onLayerIdChange={() => {}}
|
|
||||||
onMoveLayer={() => {}}
|
|
||||||
onLayerDestroy={() => {}}
|
|
||||||
onLayerCopy={() => {}}
|
|
||||||
onLayerVisibilityToggle={() => {}}
|
|
||||||
isFirstLayer={true}
|
|
||||||
isLastLayer={false}
|
|
||||||
layerIndex={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const FillExtrusion = () => (
|
|
||||||
<div>
|
|
||||||
<LayerEditor
|
|
||||||
layer={{id: "fill-extrusion", type: "fill-extrusion", source: "openmaptiles"}}
|
|
||||||
sources={{}}
|
|
||||||
vectorLayers={{}}
|
|
||||||
spec={latest}
|
|
||||||
errors={[]}
|
|
||||||
onLayerChanged={() => {}}
|
|
||||||
onLayerIdChange={() => {}}
|
|
||||||
onMoveLayer={() => {}}
|
|
||||||
onLayerDestroy={() => {}}
|
|
||||||
onLayerCopy={() => {}}
|
|
||||||
onLayerVisibilityToggle={() => {}}
|
|
||||||
isFirstLayer={true}
|
|
||||||
isLastLayer={false}
|
|
||||||
layerIndex={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Heatmap = () => (
|
|
||||||
<div>
|
|
||||||
<LayerEditor
|
|
||||||
layer={{id: "heatmap", type: "heatmap", source: "openmaptiles"}}
|
|
||||||
sources={{}}
|
|
||||||
vectorLayers={{}}
|
|
||||||
spec={latest}
|
|
||||||
errors={[]}
|
|
||||||
onLayerChanged={() => {}}
|
|
||||||
onLayerIdChange={() => {}}
|
|
||||||
onMoveLayer={() => {}}
|
|
||||||
onLayerDestroy={() => {}}
|
|
||||||
onLayerCopy={() => {}}
|
|
||||||
onLayerVisibilityToggle={() => {}}
|
|
||||||
isFirstLayer={true}
|
|
||||||
isLastLayer={false}
|
|
||||||
layerIndex={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Hillshade = () => (
|
|
||||||
<div>
|
|
||||||
<LayerEditor
|
|
||||||
layer={{id: "hillshade", type: "hillshade", source: "openmaptiles"}}
|
|
||||||
sources={{}}
|
|
||||||
vectorLayers={{}}
|
|
||||||
spec={latest}
|
|
||||||
errors={[]}
|
|
||||||
onLayerChanged={() => {}}
|
|
||||||
onLayerIdChange={() => {}}
|
|
||||||
onMoveLayer={() => {}}
|
|
||||||
onLayerDestroy={() => {}}
|
|
||||||
onLayerCopy={() => {}}
|
|
||||||
onLayerVisibilityToggle={() => {}}
|
|
||||||
isFirstLayer={true}
|
|
||||||
isLastLayer={false}
|
|
||||||
layerIndex={0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import LayerList from '../src/components/LayerList';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'LayerList',
|
|
||||||
component: LayerList,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Basic = () => (
|
|
||||||
<Wrapper>
|
|
||||||
<div style={{width: "200px"}}>
|
|
||||||
<LayerList
|
|
||||||
layers={[
|
|
||||||
{
|
|
||||||
id: "background",
|
|
||||||
type: "background",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "water",
|
|
||||||
type: "fill",
|
|
||||||
source: "openmaptiles"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "road_trunk",
|
|
||||||
type: "line",
|
|
||||||
source: "openmaptiles"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "road_minor",
|
|
||||||
type: "line",
|
|
||||||
source: "openmaptiles"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "building",
|
|
||||||
type: "fill",
|
|
||||||
source: "openmaptiles"
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
selectedLayerIndex={0}
|
|
||||||
onLayersChange={() => {}}
|
|
||||||
onLayerSelect={() => {}}
|
|
||||||
onLayerDestroy={() => {}}
|
|
||||||
onLayerCopy={() => {}}
|
|
||||||
onLayerVisibilityToggle={() => {}}
|
|
||||||
onMoveLayer={() => {}}
|
|
||||||
sources={{}}
|
|
||||||
errors={[]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import MapMaplibreGl from '../src/components/MapMaplibreGl';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'MapMaplibreGl',
|
|
||||||
component: MapMaplibreGl,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStyle = {
|
|
||||||
"version": 8,
|
|
||||||
"sources": {
|
|
||||||
"test1": {
|
|
||||||
"type": "geojson",
|
|
||||||
"data": {
|
|
||||||
"type": "FeatureCollection",
|
|
||||||
"features": [
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "Point",
|
|
||||||
"coordinates": [0, -10]
|
|
||||||
},
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test2": {
|
|
||||||
"type": "geojson",
|
|
||||||
"data": {
|
|
||||||
"type": "FeatureCollection",
|
|
||||||
"features": [
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "Point",
|
|
||||||
"coordinates": [15, 10]
|
|
||||||
},
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test3": {
|
|
||||||
"type": "geojson",
|
|
||||||
"data": {
|
|
||||||
"type": "FeatureCollection",
|
|
||||||
"features": [
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "Point",
|
|
||||||
"coordinates": [-15, 10]
|
|
||||||
},
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sprite": "",
|
|
||||||
"glyphs": "https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf",
|
|
||||||
"layers": [
|
|
||||||
{
|
|
||||||
"id": "test1",
|
|
||||||
"type": "circle",
|
|
||||||
"source": "test1",
|
|
||||||
"paint": {
|
|
||||||
"circle-radius": 40,
|
|
||||||
"circle-color": "red"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "test2",
|
|
||||||
"type": "circle",
|
|
||||||
"source": "test2",
|
|
||||||
"paint": {
|
|
||||||
"circle-radius": 40,
|
|
||||||
"circle-color": "green"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "test3",
|
|
||||||
"type": "circle",
|
|
||||||
"source": "test3",
|
|
||||||
"paint": {
|
|
||||||
"circle-radius": 40,
|
|
||||||
"circle-color": "blue"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
return <div style={{height: "100vh", width: "100vw"}}>
|
|
||||||
<MapMaplibreGl
|
|
||||||
mapStyle={mapStyle}
|
|
||||||
inspectModeEnabled={false}
|
|
||||||
replaceAccessTokens={(s) => s}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import MapOpenLayers from '../src/components/MapOpenLayers';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'MapOpenLayers',
|
|
||||||
component: MapOpenLayers,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStyle = {
|
|
||||||
"version": 8,
|
|
||||||
"sources": {
|
|
||||||
"test1": {
|
|
||||||
"type": "geojson",
|
|
||||||
"data": {
|
|
||||||
"type": "FeatureCollection",
|
|
||||||
"features": [
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "Point",
|
|
||||||
"coordinates": [0, -10]
|
|
||||||
},
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test2": {
|
|
||||||
"type": "geojson",
|
|
||||||
"data": {
|
|
||||||
"type": "FeatureCollection",
|
|
||||||
"features": [
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "Point",
|
|
||||||
"coordinates": [15, 10]
|
|
||||||
},
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test3": {
|
|
||||||
"type": "geojson",
|
|
||||||
"data": {
|
|
||||||
"type": "FeatureCollection",
|
|
||||||
"features": [
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "Point",
|
|
||||||
"coordinates": [-15, 10]
|
|
||||||
},
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sprite": "",
|
|
||||||
"glyphs": "https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf",
|
|
||||||
"layers": [
|
|
||||||
{
|
|
||||||
"id": "test1",
|
|
||||||
"type": "circle",
|
|
||||||
"source": "test1",
|
|
||||||
"paint": {
|
|
||||||
"circle-radius": 40,
|
|
||||||
"circle-color": "red"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "test2",
|
|
||||||
"type": "circle",
|
|
||||||
"source": "test2",
|
|
||||||
"paint": {
|
|
||||||
"circle-radius": 40,
|
|
||||||
"circle-color": "green"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "test3",
|
|
||||||
"type": "circle",
|
|
||||||
"source": "test3",
|
|
||||||
"paint": {
|
|
||||||
"circle-radius": 40,
|
|
||||||
"circle-color": "blue"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Basic = () => {
|
|
||||||
return <div style={{height: "100vh", width: "100vw", display: "flex"}}>
|
|
||||||
<MapOpenLayers
|
|
||||||
mapStyle={mapStyle}
|
|
||||||
inspectModeEnabled={false}
|
|
||||||
replaceAccessTokens={(s) => s}
|
|
||||||
onChange={() => {}}
|
|
||||||
debugToolbox={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import Modal from '../src/components/Modal';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Modal',
|
|
||||||
component: Modal,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Basic = () => (
|
|
||||||
<Wrapper>
|
|
||||||
<div style={{maxHeight: "200px"}}>
|
|
||||||
<Modal title="hello" isOpen={true}>
|
|
||||||
{[...Array(50).keys()].map(() => {
|
|
||||||
return <p>Some text</p>
|
|
||||||
})}
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import ModalAdd from '../src/components/ModalAdd';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'ModalAdd',
|
|
||||||
component: ModalAdd,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Basic = () => (
|
|
||||||
<Wrapper>
|
|
||||||
<div style={{maxHeight: "200px"}}>
|
|
||||||
<ModalAdd
|
|
||||||
layers={[]}
|
|
||||||
sources={{}}
|
|
||||||
isOpen={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import ModalDebug from '../src/components/ModalDebug';
|
|
||||||
import {Wrapper} from './ui';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'ModalDebug',
|
|
||||||
component: ModalDebug,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Basic = () => (
|
|
||||||
<Wrapper>
|
|
||||||
<div style={{maxHeight: "200px"}}>
|
|
||||||
<ModalDebug
|
|
||||||
isOpen={true}
|
|
||||||
renderer="mlgljs"
|
|
||||||
maplibreGlDebugOptions={{}}
|
|
||||||
mapView={{zoom: 1, center: {lat: 0, lng: 0}}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user