mirror of
https://github.com/maputnik/editor.git
synced 2026-09-01 09:57:27 +00:00
Compare commits
24 Commits
v2.0.0
...
d4f29cf846
| Author | SHA1 | Date | |
|---|---|---|---|
| d4f29cf846 | |||
| f9fc37ebed | |||
| 1958358f7d | |||
| 1a34ec21c0 | |||
| 026cf95390 | |||
| 8eabfa5519 | |||
| ad69cbdb20 | |||
| 17eaa3f204 | |||
| 1df2e36dbb | |||
| 0c46affda9 | |||
| 18f45e932b | |||
| 31d56c9fae | |||
| b7838ad6e1 | |||
| c92fd12854 | |||
| eb55796461 | |||
| 4b97f82980 | |||
| 5d0b6e3201 | |||
| 5ab9be9fdb | |||
| 9659d41b83 | |||
| 52f949e152 | |||
| e4d559f953 | |||
| 577663f706 | |||
| 393f4a38b9 | |||
| 3727c9ad5e |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"static-fs",
|
||||
"react-hot-loader/babel",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/transform-runtime"
|
||||
],
|
||||
"env": {
|
||||
"test": {
|
||||
"plugins": [
|
||||
["istanbul", {
|
||||
"exclude": ["node_modules/**", "test/**"]
|
||||
}]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||
settings: { react: { version: '18.2' } },
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -9,7 +9,7 @@ assignees: ''
|
||||
|
||||
<!-- Thanks for your feedback! Please complete the following information: -->
|
||||
|
||||
**Maputnik version**:<!-- e.g v1.7.0, master -->
|
||||
**Maputnik version**:<!-- e.g v1.7.0, main -->
|
||||
**Browser**:
|
||||
**OS**:<!-- (Windows, macOS, Linux) -->
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 2
|
||||
versioning-strategy: increase
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 2
|
||||
versioning-strategy: increase
|
||||
+21
-74
@@ -2,26 +2,11 @@ name: ci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [ main ]
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
|
||||
# post a comment linking to codesandbox with the current branch
|
||||
# meta-demo-comment:
|
||||
# name: meta/demo-comment
|
||||
# runs-on: ubuntu-latest
|
||||
|
||||
# if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# steps:
|
||||
# - uses: unsplash/comment-on-pr@v1.2.0
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# msg: "Demo: <https://codesandbox.io/embed/github/${{ github.repository }}/tree/${{ github.head_ref }}?view=preview>"
|
||||
|
||||
build-docker:
|
||||
name: build/docker
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -34,8 +19,8 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: docker build -t docker.pkg.github.com/maputnik/editor/editor:master .
|
||||
- uses: actions/checkout@v4
|
||||
- run: docker build -t docker.pkg.github.com/maputnik/editor/editor:main .
|
||||
|
||||
# build the editor
|
||||
build-node:
|
||||
@@ -48,10 +33,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [16.x]
|
||||
node-version: [18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
@@ -74,10 +59,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node-version: [16.x]
|
||||
node-version: [18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
@@ -94,13 +79,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: editor
|
||||
path: build/build
|
||||
- run: npm run profiling-build
|
||||
- name: artifacts/editor-profiling
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: editor-profiling
|
||||
path: build/profiling
|
||||
path: dist
|
||||
- name: artifacts/storybook
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
@@ -115,7 +94,7 @@ jobs:
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: maputnik/desktop
|
||||
ref: master
|
||||
@@ -142,52 +121,20 @@ jobs:
|
||||
name: maputnik-windows
|
||||
path: ./src/github.com/maputnik/desktop/bin/windows/
|
||||
|
||||
# build and test the editor
|
||||
test_selenium_standalone:
|
||||
name: "test/standalone-${{ matrix.browser }} (${{ matrix.os }})"
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
|
||||
|
||||
cypress-run:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node-version: [16]
|
||||
browser: [chrome, firefox]
|
||||
|
||||
container:
|
||||
image: node:${{ matrix.node-version }}
|
||||
options: --network-alias testhost
|
||||
|
||||
services:
|
||||
selenium:
|
||||
# geckodriver-0.31 seems to have problems as of 2022 May 1
|
||||
image: selenium/standalone-${{ matrix.browser == 'firefox' && 'firefox:99.0-geckodriver-0.30-20220427' || matrix.browser }}
|
||||
ports:
|
||||
- 4444:4444
|
||||
options: --shm-size=2gb
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
- run: npm ci
|
||||
- run: BROWSER=${{ matrix.browser }} TEST_NETWORK=testhost DOCKER_HOST=selenium npm run test
|
||||
- if: ${{ matrix.browser == 'chrome' }}
|
||||
run: ./node_modules/.bin/istanbul report --include build/coverage/coverage.json --dir build/coverage html lcov
|
||||
- if: ${{ matrix.browser == 'chrome' }}
|
||||
name: artifacts/coverage
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: coverage
|
||||
path: build/coverage
|
||||
- name: artifacts/screenshots
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: screenshots-${{ matrix.browser }}
|
||||
path: build/screenshots
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- run: npm ci
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v6
|
||||
with:
|
||||
build: npm run build
|
||||
start: npm run start
|
||||
browser: ${{ matrix.browser }}
|
||||
|
||||
@@ -2,13 +2,12 @@ name: deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
branches: [ main ]
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
# publish docker to github registry
|
||||
# publish docker to GitHub registry
|
||||
deploy-docker:
|
||||
name: deploy/docker
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -21,8 +20,7 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u orangemug --password-stdin
|
||||
- run: docker build -t docker.pkg.github.com/maputnik/editor/editor:master .
|
||||
- run: docker push docker.pkg.github.com/maputnik/editor/editor:master
|
||||
|
||||
- run: docker build -t docker.pkg.github.com/maputnik/editor/editor:main .
|
||||
- run: docker push docker.pkg.github.com/maputnik/editor/editor:main
|
||||
|
||||
@@ -33,3 +33,5 @@ public
|
||||
/errorShots
|
||||
/old
|
||||
/build
|
||||
/cypress/screenshots
|
||||
/dist/
|
||||
|
||||
+8
-23
@@ -1,24 +1,9 @@
|
||||
const rules = require('../config/webpack.rules');
|
||||
|
||||
module.exports = {
|
||||
stories: ['../stories/**/*.stories.js'],
|
||||
addons: [
|
||||
'@storybook/addon-actions',
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-a11y/register',
|
||||
'@storybook/addon-storysource',
|
||||
],
|
||||
webpackFinal: async config => {
|
||||
// do mutation to the config
|
||||
console.log("config.module", config.module);
|
||||
|
||||
return {
|
||||
...config,
|
||||
module: {
|
||||
rules: [
|
||||
...rules,
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
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;
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
FROM node:10 as builder
|
||||
FROM node:18 as builder
|
||||
WORKDIR /maputnik
|
||||
|
||||
# Only copy package.json to prevent npm install from running on every build
|
||||
@@ -16,7 +16,7 @@ RUN npm run build
|
||||
FROM python:3-slim
|
||||
WORKDIR /maputnik
|
||||
|
||||
COPY --from=builder /maputnik/build/build .
|
||||
COPY --from=builder /maputnik/dist .
|
||||
|
||||
EXPOSE 8888
|
||||
CMD python -m http.server 8888
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
[github-action-ci]: https://github.com/maputnik/editor/actions?query=workflow%3Aci
|
||||
[license]: https://tldrlegal.com/license/mit-license
|
||||
|
||||
A free and open visual editor for the [Mapbox GL styles](https://www.mapbox.com/mapbox-gl-style-spec/)
|
||||
A free and open visual editor for the [MapLibre GL styles](https://maplibre.org/maplibre-style-spec/)
|
||||
targeted at developers and map designers.
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ The documentation can be found in the [Wiki](https://github.com/maputnik/editor/
|
||||
|
||||
## Develop
|
||||
|
||||
Maputnik is written in ES6 and is using [React](https://github.com/facebook/react) and [Mapbox GL JS](https://www.mapbox.com/mapbox-gl-js/api/).
|
||||
Maputnik is written in ES6 and is using [React](https://github.com/facebook/react) and [MapLibre GL JS](https://maplibre.org/projects/maplibre-gl-js/).
|
||||
|
||||
We ensure building and developing Maputnik works with the [current active LTS Node.js version and above](https://github.com/nodejs/Release#release-schedule).
|
||||
|
||||
@@ -49,18 +49,14 @@ npm install
|
||||
npm run start
|
||||
```
|
||||
|
||||
If you want Maputnik to be accessible externally use the [`--host` option](https://webpack.js.org/configuration/dev-server/#devserverhost):
|
||||
If you want Maputnik to be accessible externally use the [`--host` option](https://vitejs.dev/config/server-options.html#server-host):
|
||||
|
||||
```bash
|
||||
# start externally accessible dev server
|
||||
npm run start -- --host 0.0.0.0
|
||||
```
|
||||
|
||||
The build process will watch for changes to the filesystem, rebuild and autoreload the editor. However note this from the [webpack-dev-server docs](https://webpack.js.org/configuration/dev-server/):
|
||||
|
||||
> webpack uses the file system to get notified of file changes. In some cases this does not work. For example, when using Network File System (NFS). Vagrant also has a lot of problems with this. ([snippet source](https://webpack.js.org/configuration/dev-server/#devserverwatchoptions-))
|
||||
|
||||
To enable polling add `export WEBPACK_DEV_SERVER_POLLING=1` to your environment.
|
||||
The build process will watch for changes to the filesystem, rebuild and autoreload the editor.
|
||||
|
||||
```
|
||||
npm run build
|
||||
@@ -76,9 +72,9 @@ npm run lint-styles
|
||||
|
||||
|
||||
## Tests
|
||||
For testing we use [webdriverio](https://webdriver.io) and [selenium-standalone](https://github.com/webdriverio/selenium-standalone).
|
||||
For E2E testing we use [Cypress](https://www.cypress.io/)
|
||||
|
||||
[selenium-standalone](https://github.com/webdriverio/selenium-standalone) starts a server that will launch browsers on your local machine. You need to have Java installed on your machine as well as *chrome* or *firefox*.
|
||||
[Cypress](https://www.cypress.io/) doesn't starts a server so you'll need to start one manually by running `npm run start`.
|
||||
|
||||
Now open a terminal and run the following using *chrome*:
|
||||
|
||||
@@ -87,10 +83,16 @@ npm run test
|
||||
```
|
||||
or *firefox*:
|
||||
```
|
||||
BROWSER=firefox npm run test
|
||||
npm run test -- --browser firefox
|
||||
```
|
||||
|
||||
After some time you should see a browser launch which will be automated by the test runner.
|
||||
See the following docs for more info: (Launching Browsers)[https://docs.cypress.io/guides/guides/launching-browsers]
|
||||
|
||||
You can also see the tests as they run or select which suites to run by executing:
|
||||
|
||||
```
|
||||
npm run cy:open
|
||||
```
|
||||
|
||||
|
||||
## Related Projects
|
||||
@@ -155,6 +157,5 @@ Sina Martinelli, Nicholas Doiron, Neil Cawse, Urs42, Benedikt Groß, Manuel Roth
|
||||
|
||||
Maputnik is [licensed under MIT](LICENSE) and is Copyright (c) Lukas Martinelli and contributors.
|
||||
|
||||
**Disclaimer** This project is not affiliated with Mapbox or Mapbox Studio. It is an independent style editor for the
|
||||
open source technology in the Mapbox GL ecosystem.
|
||||
**Disclaimer** This is an independent style editor.
|
||||
As contributor please take extra care of not violating any Mapbox trademarks. Do not get inspired by Mapbox Studio and make your own decisions for a good style editor.
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
var webpack = require("webpack");
|
||||
var WebpackDevServer = require("webpack-dev-server");
|
||||
var webpackConfig = require("./webpack.config");
|
||||
var testConfig = require("../test/config/specs");
|
||||
var artifacts = require("../test/artifacts");
|
||||
|
||||
|
||||
var server;
|
||||
var SCREENSHOT_PATH = artifacts.pathSync("screenshots");
|
||||
|
||||
exports.config = {
|
||||
runner: 'local',
|
||||
path: '/wd/hub',
|
||||
specs: [
|
||||
'./test/functional/index.js'
|
||||
],
|
||||
maxInstances: 10,
|
||||
capabilities: [
|
||||
{
|
||||
maxInstances: 5,
|
||||
browserName: (process.env.BROWSER || 'chrome'),
|
||||
}
|
||||
],
|
||||
// geckodriver-0.31 seems to have problems as of 2022 May 1
|
||||
services: process.env.DOCKER_HOST ? [] : [ ['selenium-standalone', { drivers: { firefox: '0.30.0', chrome: 'latest' } } ] ],
|
||||
logLevel: 'info',
|
||||
bail: 0,
|
||||
screenshotPath: SCREENSHOT_PATH,
|
||||
hostname: process.env.DOCKER_HOST || "0.0.0.0",
|
||||
framework: 'mocha',
|
||||
reporters: ['spec'],
|
||||
mochaOpts: {
|
||||
ui: 'bdd',
|
||||
// Because we don't know how long the initial build will take...
|
||||
timeout: 4*60*1000,
|
||||
},
|
||||
onPrepare: async function (config, capabilities) {
|
||||
webpackConfig.devServer.host = testConfig.testNetwork;
|
||||
webpackConfig.devServer.port = testConfig.port;
|
||||
const compiler = webpack(webpackConfig);
|
||||
server = new WebpackDevServer(webpackConfig.devServer, compiler);
|
||||
await server.start();
|
||||
},
|
||||
onComplete: async function (exitCode, config, capabilities) {
|
||||
await server.stop();
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
"use strict";
|
||||
var path = require('path');
|
||||
var rules = require('./webpack.rules');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
var HtmlWebpackInlineSVGPlugin = require('html-webpack-inline-svg-plugin');
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const HOST = process.env.HOST || "127.0.0.1";
|
||||
const PORT = process.env.PORT || "8888";
|
||||
|
||||
module.exports = {
|
||||
target: 'web',
|
||||
mode: 'development',
|
||||
entry: [
|
||||
`webpack-dev-server/client?http://${HOST}:${PORT}`,
|
||||
`webpack/hot/only-dev-server`,
|
||||
`./src/index.jsx` // Your appʼs entry point
|
||||
],
|
||||
devtool: process.env.WEBPACK_DEVTOOL || 'cheap-module-source-map',
|
||||
output: {
|
||||
path: path.join(__dirname, '..', 'public'),
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx']
|
||||
},
|
||||
module: {
|
||||
noParse: [
|
||||
/mapbox-gl\/dist\/mapbox-gl.js/
|
||||
],
|
||||
rules: rules
|
||||
},
|
||||
node: {
|
||||
fs: "empty",
|
||||
net: 'empty',
|
||||
tls: 'empty'
|
||||
},
|
||||
devServer: {
|
||||
// enable HMR
|
||||
hot: true,
|
||||
// serve index.html in place of 404 responses to allow HTML5 history
|
||||
historyApiFallback: true,
|
||||
port: PORT,
|
||||
host: HOST,
|
||||
watchFiles: {
|
||||
options: {
|
||||
// Disabled polling by default as it causes lots of CPU usage and hence drains laptop batteries. To enable polling add WEBPACK_DEV_SERVER_POLLING to your environment
|
||||
// See <https://webpack.js.org/configuration/watch/#watchoptions-poll> for details
|
||||
usePolling: (!!process.env.WEBPACK_DEV_SERVER_POLLING ? true : false),
|
||||
watch: false
|
||||
}
|
||||
}
|
||||
},
|
||||
optimization: {
|
||||
noEmitOnErrors: true,
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'Maputnik',
|
||||
template: './src/template.html'
|
||||
}),
|
||||
new HtmlWebpackInlineSVGPlugin({
|
||||
runPreEmit: true,
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: './src/manifest.json',
|
||||
to: 'manifest.json'
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
var rules = require('./webpack.rules');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
var HtmlWebpackInlineSVGPlugin = require('html-webpack-inline-svg-plugin');
|
||||
var WebpackCleanupPlugin = require('webpack-cleanup-plugin');
|
||||
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
var artifacts = require("../test/artifacts");
|
||||
|
||||
var OUTPATH = artifacts.pathSync("/build");
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './src/index.jsx',
|
||||
},
|
||||
output: {
|
||||
path: OUTPATH,
|
||||
filename: '[name].[contenthash].js',
|
||||
chunkFilename: '[contenthash].js'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx']
|
||||
},
|
||||
module: {
|
||||
noParse: [
|
||||
/mapbox-gl\/dist\/mapbox-gl.js/
|
||||
],
|
||||
rules: rules
|
||||
},
|
||||
node: {
|
||||
fs: "empty",
|
||||
net: 'empty',
|
||||
tls: 'empty'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
new WebpackCleanupPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: './src/template.html',
|
||||
title: 'Maputnik'
|
||||
}),
|
||||
new HtmlWebpackInlineSVGPlugin({
|
||||
runPreEmit: true,
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: './src/manifest.json',
|
||||
to: 'manifest.json'
|
||||
}
|
||||
]
|
||||
}),
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'static',
|
||||
defaultSizes: 'gzip',
|
||||
openAnalyzer: false,
|
||||
generateStatsFile: true,
|
||||
reportFilename: 'bundle-stats.html',
|
||||
statsFilename: 'bundle-stats.json',
|
||||
})
|
||||
]
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
const webpackProdConfig = require('./webpack.production.config');
|
||||
const artifacts = require("../test/artifacts");
|
||||
|
||||
const OUTPATH = artifacts.pathSync("/profiling");
|
||||
|
||||
module.exports = {
|
||||
...webpackProdConfig,
|
||||
output: {
|
||||
...webpackProdConfig.output,
|
||||
path: OUTPATH,
|
||||
},
|
||||
resolve: {
|
||||
...webpackProdConfig.resolve,
|
||||
alias: {
|
||||
...webpackProdConfig.resolve.alias,
|
||||
'react-dom$': 'react-dom/profiling',
|
||||
'scheduler/tracing': 'scheduler/tracing-profiling',
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: [
|
||||
path.resolve(__dirname, '../node_modules')
|
||||
],
|
||||
use: 'babel-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(eot|ttf|woff|woff2)$/,
|
||||
use: 'file-loader?name=fonts/[name].[ext]'
|
||||
},
|
||||
{
|
||||
test: /\.ico$/,
|
||||
use: 'file-loader?name=[name].[ext]'
|
||||
},
|
||||
{
|
||||
test: /\.(gif|jpg|png)$/,
|
||||
use: 'file-loader?name=img/[name].[ext]'
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
'svg-inline-loader'
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /[\/\\](node_modules|global|src)[\/\\].*\.scss$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
"css-loader",
|
||||
"sass-loader"
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /[\/\\](node_modules|global|src)[\/\\].*\.css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader'
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from "cypress";
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import MaputnikDriver from "./driver";
|
||||
|
||||
describe("accessibility", () => {
|
||||
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
|
||||
describe("skip links", () => {
|
||||
beforeEach(() => {
|
||||
when.setStyle("layer");
|
||||
});
|
||||
|
||||
it("skip link to layer list", () => {
|
||||
const selector = "root:skip:layer-list";
|
||||
should.isExists(selector);
|
||||
when.tab();
|
||||
should.beFocused(selector);
|
||||
when.click(selector);
|
||||
should.beFocused("skip-target-layer-list");
|
||||
});
|
||||
|
||||
it("skip link to layer editor", () => {
|
||||
const selector = "root:skip:layer-editor";
|
||||
should.isExists(selector);
|
||||
when.tab().tab();
|
||||
should.beFocused(selector);
|
||||
when.click(selector);
|
||||
should.beFocused("skip-target-layer-editor");
|
||||
});
|
||||
|
||||
it("skip link to map view", () => {
|
||||
const selector = "root:skip:map-view";
|
||||
should.isExists(selector);
|
||||
when.tab().tab().tab();
|
||||
should.beFocused(selector);
|
||||
when.click(selector);
|
||||
should.canvasBeFocused();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,198 @@
|
||||
import { CypressHelper } from "@shellygo/cypress-test-utils";
|
||||
import { v1 as uuid } from "uuid";
|
||||
export default class MaputnikDriver {
|
||||
private helper = new CypressHelper({ defaultDataAttribute: "data-wd-key" });
|
||||
public beforeAndAfter = () => {
|
||||
beforeEach(() => {
|
||||
this.given.setupInterception();
|
||||
this.when.setStyle("both");
|
||||
});
|
||||
};
|
||||
|
||||
public given = {
|
||||
setupInterception: () => {
|
||||
cy.intercept("GET", "http://localhost:8888/example-style.json", {
|
||||
fixture: "example-style.json",
|
||||
}).as("example-style.json");
|
||||
cy.intercept("GET", "http://localhost:8888/example-layer-style.json", {
|
||||
fixture: "example-layer-style.json",
|
||||
});
|
||||
cy.intercept("GET", "http://localhost:8888/geojson-style.json", {
|
||||
fixture: "geojson-style.json",
|
||||
});
|
||||
cy.intercept("GET", "http://localhost:8888/raster-style.json", {
|
||||
fixture: "raster-style.json",
|
||||
});
|
||||
cy.intercept("GET", "http://localhost:8888/geojson-raster-style.json", {
|
||||
fixture: "geojson-raster-style.json",
|
||||
});
|
||||
cy.intercept({ method: "GET", url: "*example.local/*" }, []);
|
||||
cy.intercept({ method: "GET", url: "*example.com/*" }, []);
|
||||
},
|
||||
};
|
||||
|
||||
public when = {
|
||||
within: (selector: string, fn: () => void) => {
|
||||
this.helper.when.within(fn, selector);
|
||||
},
|
||||
tab: () => cy.get("body").tab(),
|
||||
waitForExampleFileRequset: () => {
|
||||
this.helper.when.waitForResponse("example-style.json");
|
||||
},
|
||||
chooseExampleFile: () => {
|
||||
cy.get("input[type='file']").selectFile(
|
||||
"cypress/fixtures/example-style.json",
|
||||
{ force: true }
|
||||
);
|
||||
},
|
||||
setStyle: (
|
||||
styleProperties: "geojson" | "raster" | "both" | "layer" | "",
|
||||
zoom?: number
|
||||
) => {
|
||||
let url = "?debug";
|
||||
switch (styleProperties) {
|
||||
case "geojson":
|
||||
url += "&style=http://localhost:8888/geojson-style.json";
|
||||
break;
|
||||
case "raster":
|
||||
url += "&style=http://localhost:8888/raster-style.json";
|
||||
break;
|
||||
case "both":
|
||||
url += "&style=http://localhost:8888/geojson-raster-style.json";
|
||||
break;
|
||||
case "layer":
|
||||
url += "&style=http://localhost:8888/example-layer-style.json";
|
||||
break;
|
||||
}
|
||||
if (zoom) {
|
||||
url += "#" + zoom + "/41.3805/2.1635";
|
||||
}
|
||||
cy.visit("http://localhost:8888/" + url);
|
||||
if (styleProperties) {
|
||||
cy.on("window:confirm", () => true);
|
||||
}
|
||||
cy.get(".maputnik-toolbar-link").should("be.visible");
|
||||
},
|
||||
fillLayersModal: (opts: any) => {
|
||||
var type = opts.type;
|
||||
var layer = opts.layer;
|
||||
var id;
|
||||
if (opts.id) {
|
||||
id = opts.id;
|
||||
} else {
|
||||
id = `${type}:${uuid()}`;
|
||||
}
|
||||
|
||||
cy.get(
|
||||
this.get.getDataAttribute("add-layer.layer-type", "select")
|
||||
).select(type);
|
||||
cy.get(this.get.getDataAttribute("add-layer.layer-id", "input")).type(id);
|
||||
if (layer) {
|
||||
cy.get(
|
||||
this.get.getDataAttribute("add-layer.layer-source-block", "input")
|
||||
).type(layer);
|
||||
}
|
||||
this.when.click("add-layer");
|
||||
|
||||
return id;
|
||||
},
|
||||
|
||||
typeKeys: (keys: string) => {
|
||||
cy.get("body").type(keys);
|
||||
},
|
||||
|
||||
click: (selector: string) => {
|
||||
this.helper.when.click(selector);
|
||||
// cy.get(selector).click({ force: true });
|
||||
},
|
||||
|
||||
select: (selector: string, value: string) => {
|
||||
cy.get(selector).select(value);
|
||||
},
|
||||
|
||||
focus: (selector: string) => {
|
||||
this.helper.when.focus(selector);
|
||||
},
|
||||
|
||||
setValue: (selector: string, text: string) => {
|
||||
cy.get(selector).clear().type(text, { parseSpecialCharSequences: false });
|
||||
},
|
||||
|
||||
closeModal: (key: string) => {
|
||||
this.helper.when.waitUntil(() => this.helper.get.element(key));
|
||||
this.when.click(key + ".close-modal");
|
||||
},
|
||||
|
||||
openLayersModal: () => {
|
||||
this.helper.when.click("layer-list:add-layer");
|
||||
|
||||
cy.get(this.get.getDataAttribute("modal:add-layer")).should("exist");
|
||||
cy.get(this.get.getDataAttribute("modal:add-layer")).should("be.visible");
|
||||
},
|
||||
};
|
||||
|
||||
public get = {
|
||||
isMac: () => {
|
||||
return Cypress.platform === "darwin";
|
||||
},
|
||||
getStyleFromWindow: (win: Window) => {
|
||||
const styleId = win.localStorage.getItem("maputnik:latest_style");
|
||||
const styleItem = win.localStorage.getItem(`maputnik:style:${styleId}`);
|
||||
const obj = JSON.parse(styleItem || "");
|
||||
return obj;
|
||||
},
|
||||
getExampleFileUrl: () => {
|
||||
return "http://localhost:8888/example-style.json";
|
||||
},
|
||||
getDataAttribute: (key: string, selector?: string): string => {
|
||||
return `*[data-wd-key='${key}'] ${selector || ""}`;
|
||||
},
|
||||
};
|
||||
|
||||
public should = {
|
||||
canvasBeFocused: () => {
|
||||
this.when.within("maplibre:map", () => {
|
||||
cy.get("canvas").should("be.focused");
|
||||
});
|
||||
},
|
||||
notExist: (selector: string) => {
|
||||
cy.get(selector).should("not.exist");
|
||||
},
|
||||
beFocused: (selector: string) => {
|
||||
this.helper.get.element(selector).should("have.focus");
|
||||
},
|
||||
|
||||
notBeFocused: (selector: string) => {
|
||||
this.helper.get.element(selector).should("not.have.focus");
|
||||
},
|
||||
|
||||
beVisible: (selector: string) => {
|
||||
this.helper.get.element(selector).should("be.visible");
|
||||
},
|
||||
|
||||
notBeVisible: (selector: string) => {
|
||||
this.helper.get.element(selector).should("not.be.visible");
|
||||
},
|
||||
|
||||
equalStyleStore: (getter: (obj: any) => any, styleObj: any) => {
|
||||
cy.window().then((win: any) => {
|
||||
const obj = this.get.getStyleFromWindow(win);
|
||||
assert.deepEqual(getter(obj), styleObj);
|
||||
});
|
||||
},
|
||||
|
||||
isStyleStoreEqualToExampleFileData: () => {
|
||||
cy.window().then((win: any) => {
|
||||
const obj = this.get.getStyleFromWindow(win);
|
||||
cy.fixture("example-style.json").should("deep.equal", obj);
|
||||
});
|
||||
},
|
||||
|
||||
isExists: (selector: string) => {
|
||||
this.helper.get.element(selector).should("exist");
|
||||
},
|
||||
isSelected: (selector: string, value: string) => {
|
||||
cy.get(selector).find(`option[value="${value}"]`).should("be.selected");
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import MaputnikDriver from "./driver";
|
||||
|
||||
describe("history", () => {
|
||||
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
|
||||
let undoKeyCombo: string;
|
||||
let redoKeyCombo: string;
|
||||
|
||||
before(() => {
|
||||
const isMac = get.isMac();
|
||||
undoKeyCombo = isMac ? "{meta}z" : "{ctrl}z";
|
||||
redoKeyCombo = isMac ? "{meta}{shift}z" : "{ctrl}y";
|
||||
});
|
||||
|
||||
it("undo/redo", () => {
|
||||
when.setStyle("geojson");
|
||||
when.openLayersModal();
|
||||
|
||||
should.equalStyleStore((a: any) => a.layers, []);
|
||||
|
||||
when.fillLayersModal({
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
when.openLayersModal();
|
||||
when.fillLayersModal({
|
||||
id: "step 2",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
},
|
||||
{
|
||||
id: "step 2",
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
when.typeKeys(undoKeyCombo);
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
when.typeKeys(undoKeyCombo);
|
||||
should.equalStyleStore((a: any) => a.layers, []);
|
||||
|
||||
when.typeKeys(redoKeyCombo);
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
when.typeKeys(redoKeyCombo);
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
},
|
||||
{
|
||||
id: "step 2",
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { default as MaputnikDriver } from "./driver";
|
||||
|
||||
describe("keyboard", () => {
|
||||
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
describe("shortcuts", () => {
|
||||
beforeEach(() => {
|
||||
given.setupInterception();
|
||||
when.setStyle("");
|
||||
});
|
||||
|
||||
it("ESC should unfocus", () => {
|
||||
const targetSelector = "maputnik-select";
|
||||
when.focus(targetSelector);
|
||||
should.beFocused(targetSelector);
|
||||
|
||||
when.typeKeys("{esc}");
|
||||
expect(should.notBeFocused(targetSelector));
|
||||
});
|
||||
|
||||
it("'?' should show shortcuts modal", () => {
|
||||
when.typeKeys("?");
|
||||
should.beVisible("modal:shortcuts");
|
||||
});
|
||||
|
||||
it("'o' should show open modal", () => {
|
||||
when.typeKeys("o");
|
||||
should.beVisible("modal:open");
|
||||
});
|
||||
|
||||
it("'e' should show export modal", () => {
|
||||
when.typeKeys("e");
|
||||
should.beVisible("modal:export");
|
||||
});
|
||||
|
||||
it("'d' should show sources modal", () => {
|
||||
when.typeKeys("d");
|
||||
should.beVisible("modal:sources");
|
||||
});
|
||||
|
||||
it("'s' should show settings modal", () => {
|
||||
when.typeKeys("s");
|
||||
should.beVisible("modal:settings");
|
||||
});
|
||||
|
||||
it("'i' should change map to inspect mode", () => {
|
||||
when.typeKeys("i");
|
||||
should.isSelected(get.getDataAttribute("nav:inspect"), "inspect");
|
||||
});
|
||||
|
||||
it("'m' should focus map", () => {
|
||||
when.typeKeys("m");
|
||||
should.beFocused(".maplibregl-canvas");
|
||||
});
|
||||
|
||||
it("'!' should show debug modal", () => {
|
||||
when.typeKeys("!");
|
||||
should.beVisible("modal:debug");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,497 @@
|
||||
var assert = require("assert");
|
||||
import { v1 as uuid } from "uuid";
|
||||
import MaputnikDriver from "./driver";
|
||||
|
||||
describe("layers", () => {
|
||||
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
beforeEach(() => {
|
||||
when.setStyle("both");
|
||||
when.openLayersModal();
|
||||
});
|
||||
|
||||
describe("ops", () => {
|
||||
it("delete", () => {
|
||||
var id = when.fillLayersModal({
|
||||
type: "background",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
when.click("layer-list-item:" + id + ":delete");
|
||||
|
||||
should.equalStyleStore((a: any) => a.layers, []);
|
||||
});
|
||||
|
||||
it("duplicate", () => {
|
||||
var styleObj;
|
||||
var id = when.fillLayersModal({
|
||||
type: "background",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
when.click("layer-list-item:" + id + ":copy");
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id + "-copy",
|
||||
type: "background",
|
||||
},
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it("hide", () => {
|
||||
var styleObj;
|
||||
var id = when.fillLayersModal({
|
||||
type: "background",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
when.click("layer-list-item:" + id + ":toggle-visibility");
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
layout: {
|
||||
visibility: "none",
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
when.click("layer-list-item:" + id + ":toggle-visibility");
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
layout: {
|
||||
visibility: "visible",
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("background", () => {
|
||||
it("add", () => {
|
||||
var id = when.fillLayersModal({
|
||||
type: "background",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
describe("modify", () => {
|
||||
function createBackground() {
|
||||
// Setup
|
||||
var id = uuid();
|
||||
|
||||
when.select(
|
||||
get.getDataAttribute("add-layer.layer-type", "select"),
|
||||
"background"
|
||||
);
|
||||
when.setValue(
|
||||
get.getDataAttribute("add-layer.layer-id", "input"),
|
||||
"background:" + id
|
||||
);
|
||||
|
||||
when.click("add-layer");
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "background:" + id,
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
// ====> THESE SHOULD BE FROM THE SPEC
|
||||
describe("layer", () => {
|
||||
it("expand/collapse");
|
||||
it("id", () => {
|
||||
var bgId = createBackground();
|
||||
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
|
||||
var id = uuid();
|
||||
when.setValue(
|
||||
get.getDataAttribute("layer-editor.layer-id", "input"),
|
||||
"foobar:" + id
|
||||
);
|
||||
when.click("min-zoom");
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "foobar:" + id,
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it("min-zoom", () => {
|
||||
var bgId = createBackground();
|
||||
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
when.setValue(
|
||||
get.getDataAttribute("min-zoom", 'input[type="text"]'),
|
||||
"1"
|
||||
);
|
||||
|
||||
when.click("layer-editor.layer-id");
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
minzoom: 1,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// AND RESET!
|
||||
// driver.setValue(driver.getDataAttribute("min-zoom", "input"), "")
|
||||
// driver.click(driver.getDataAttribute("max-zoom", "input"));
|
||||
|
||||
// driver.isStyleStoreEqual((a: any) => a.layers, [
|
||||
// {
|
||||
// "id": 'background:'+bgId,
|
||||
// "type": 'background'
|
||||
// }
|
||||
// ]);
|
||||
});
|
||||
|
||||
it("max-zoom", () => {
|
||||
var bgId = createBackground();
|
||||
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
when.setValue(
|
||||
get.getDataAttribute("max-zoom", 'input[type="text"]'),
|
||||
"1"
|
||||
);
|
||||
|
||||
when.click("layer-editor.layer-id");
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
maxzoom: 1,
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it("comments", () => {
|
||||
var bgId = createBackground();
|
||||
var id = uuid();
|
||||
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
when.setValue(get.getDataAttribute("layer-comment", "textarea"), id);
|
||||
|
||||
when.click("layer-editor.layer-id");
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
metadata: {
|
||||
"maputnik:comment": id,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// Unset it again.
|
||||
// TODO: This fails
|
||||
// driver.setValue(driver.getDataAttribute("layer-comment", "textarea"), "");
|
||||
// driver.click(driver.getDataAttribute("min-zoom", "input"));
|
||||
|
||||
// driver.isStyleStoreEqual((a: any) => a.layers, [
|
||||
// {
|
||||
// "id": 'background:'+bgId,
|
||||
// "type": 'background'
|
||||
// }
|
||||
// ]);
|
||||
});
|
||||
|
||||
it("color", () => {
|
||||
var bgId = createBackground();
|
||||
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
|
||||
when.click("spec-field:background-color");
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("filter", () => {
|
||||
it("expand/collapse");
|
||||
it("compound filter");
|
||||
});
|
||||
|
||||
describe("paint", () => {
|
||||
it("expand/collapse");
|
||||
it("color");
|
||||
it("pattern");
|
||||
it("opacity");
|
||||
});
|
||||
// <=====
|
||||
|
||||
describe("json-editor", () => {
|
||||
it("expand/collapse");
|
||||
it("modify");
|
||||
|
||||
// TODO
|
||||
it.skip("parse error", () => {
|
||||
var bgId = createBackground();
|
||||
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
|
||||
var errorSelector = ".CodeMirror-lint-marker-error";
|
||||
should.notExist(errorSelector);
|
||||
|
||||
when.click(".CodeMirror");
|
||||
when.typeKeys(
|
||||
"\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013 {"
|
||||
);
|
||||
should.isExists(errorSelector);
|
||||
|
||||
when.click("layer-editor.layer-id");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("fill", () => {
|
||||
it("add", () => {
|
||||
var id = when.fillLayersModal({
|
||||
type: "fill",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "fill",
|
||||
source: "example",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: Change source
|
||||
it("change source");
|
||||
});
|
||||
|
||||
describe("line", () => {
|
||||
it("add", () => {
|
||||
var id = when.fillLayersModal({
|
||||
type: "line",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "line",
|
||||
source: "example",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it("groups", () => {
|
||||
// TODO
|
||||
// Click each of the layer groups.
|
||||
});
|
||||
});
|
||||
|
||||
describe("symbol", () => {
|
||||
it("add", () => {
|
||||
var id = when.fillLayersModal({
|
||||
type: "symbol",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "symbol",
|
||||
source: "example",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("raster", () => {
|
||||
it("add", () => {
|
||||
var id = when.fillLayersModal({
|
||||
type: "raster",
|
||||
layer: "raster",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "raster",
|
||||
source: "raster",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("circle", () => {
|
||||
it("add", () => {
|
||||
var id = when.fillLayersModal({
|
||||
type: "circle",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "circle",
|
||||
source: "example",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fill extrusion", () => {
|
||||
it("add", () => {
|
||||
var id = when.fillLayersModal({
|
||||
type: "fill-extrusion",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
should.equalStyleStore(
|
||||
(a: any) => a.layers,
|
||||
[
|
||||
{
|
||||
id: id,
|
||||
type: "fill-extrusion",
|
||||
source: "example",
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("groups", () => {
|
||||
it("simple", () => {
|
||||
when.setStyle("geojson");
|
||||
|
||||
when.openLayersModal();
|
||||
when.fillLayersModal({
|
||||
id: "foo",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
when.openLayersModal();
|
||||
when.fillLayersModal({
|
||||
id: "foo_bar",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
when.openLayersModal();
|
||||
when.fillLayersModal({
|
||||
id: "foo_bar_baz",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
should.beVisible("layer-list-item:foo");
|
||||
|
||||
should.notBeVisible("layer-list-item:foo_bar");
|
||||
should.notBeVisible("layer-list-item:foo_bar_baz");
|
||||
|
||||
when.click("layer-list-group:foo-0");
|
||||
|
||||
should.beVisible("layer-list-item:foo");
|
||||
should.beVisible("layer-list-item:foo_bar");
|
||||
should.beVisible("layer-list-item:foo_bar_baz");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import MaputnikDriver from "./driver";
|
||||
|
||||
describe("map", () => {
|
||||
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
describe("zoom level", () => {
|
||||
it("via url", () => {
|
||||
var zoomLevel = 12.37;
|
||||
when.setStyle("geojson", zoomLevel);
|
||||
should.beVisible("maplibre:ctrl-zoom");
|
||||
// HM TODO
|
||||
//driver.getText(".maplibregl-ctrl-zoom") === "Zoom "+(zoomLevel);
|
||||
});
|
||||
|
||||
it("via map controls", () => {
|
||||
var zoomLevel = 12.37;
|
||||
when.setStyle("geojson", zoomLevel);
|
||||
|
||||
when.click("maplibre:ctrl-zoom");
|
||||
should.beVisible("maplibre:ctrl-zoom");
|
||||
// HM TODO
|
||||
//driver.getText(".maplibregl-ctrl-zoom") === "Zoom "+(zoomLevel + 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,160 @@
|
||||
import MaputnikDriver from "./driver";
|
||||
|
||||
describe("modals", () => {
|
||||
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
beforeEach(() => {
|
||||
when.setStyle("");
|
||||
});
|
||||
describe("open", () => {
|
||||
beforeEach(() => {
|
||||
when.click("nav:open");
|
||||
});
|
||||
|
||||
it("close", () => {
|
||||
when.closeModal("modal:open");
|
||||
should.notExist("modal:open");
|
||||
});
|
||||
|
||||
it.skip("upload", () => {
|
||||
// HM: I was not able to make the following choose file actually to select a file and close the modal...
|
||||
when.chooseExampleFile();
|
||||
|
||||
should.isStyleStoreEqualToExampleFileData();
|
||||
});
|
||||
|
||||
it("load from url", () => {
|
||||
var styleFileUrl = get.getExampleFileUrl();
|
||||
|
||||
when.setValue(get.getDataAttribute("modal:open.url.input"), styleFileUrl);
|
||||
when.click("modal:open.url.button");
|
||||
when.waitForExampleFileRequset();
|
||||
|
||||
should.isStyleStoreEqualToExampleFileData();
|
||||
});
|
||||
});
|
||||
|
||||
describe("shortcuts", () => {
|
||||
it("open/close", () => {
|
||||
when.setStyle("");
|
||||
when.typeKeys("?");
|
||||
when.closeModal("modal:shortcuts");
|
||||
should.notExist("modal:shortcuts");
|
||||
});
|
||||
});
|
||||
|
||||
describe("export", () => {
|
||||
beforeEach(() => {
|
||||
when.click("nav:export");
|
||||
});
|
||||
|
||||
it("close", () => {
|
||||
when.closeModal("modal:export");
|
||||
should.notExist("modal:export");
|
||||
});
|
||||
|
||||
// TODO: Work out how to download a file and check the contents
|
||||
it("download");
|
||||
});
|
||||
|
||||
describe("sources", () => {
|
||||
it("active sources");
|
||||
it("public source");
|
||||
it("add new source");
|
||||
});
|
||||
|
||||
describe("inspect", () => {
|
||||
it("toggle", () => {
|
||||
when.setStyle("geojson");
|
||||
|
||||
when.select(get.getDataAttribute("nav:inspect", "select"), "inspect");
|
||||
});
|
||||
});
|
||||
|
||||
describe("style settings", () => {
|
||||
beforeEach(() => {
|
||||
when.click("nav:settings");
|
||||
});
|
||||
|
||||
it("name", () => {
|
||||
when.setValue(get.getDataAttribute("modal:settings.name"), "foobar");
|
||||
when.click("modal:settings.owner");
|
||||
|
||||
should.equalStyleStore((obj) => obj.name, "foobar");
|
||||
});
|
||||
it("owner", () => {
|
||||
when.setValue(get.getDataAttribute("modal:settings.owner"), "foobar");
|
||||
when.click("modal:settings.name");
|
||||
|
||||
should.equalStyleStore((obj) => obj.owner, "foobar");
|
||||
});
|
||||
it("sprite url", () => {
|
||||
when.setValue(
|
||||
get.getDataAttribute("modal:settings.sprite"),
|
||||
"http://example.com"
|
||||
);
|
||||
when.click("modal:settings.name");
|
||||
|
||||
should.equalStyleStore((obj) => obj.sprite, "http://example.com");
|
||||
});
|
||||
it("glyphs url", () => {
|
||||
var glyphsUrl = "http://example.com/{fontstack}/{range}.pbf";
|
||||
when.setValue(get.getDataAttribute("modal:settings.glyphs"), glyphsUrl);
|
||||
when.click("modal:settings.name");
|
||||
|
||||
should.equalStyleStore((obj) => obj.glyphs, glyphsUrl);
|
||||
});
|
||||
|
||||
it("maptiler access token", () => {
|
||||
var apiKey = "testing123";
|
||||
when.setValue(
|
||||
get.getDataAttribute(
|
||||
"modal:settings.maputnik:openmaptiles_access_token"
|
||||
),
|
||||
apiKey
|
||||
);
|
||||
when.click("modal:settings.name");
|
||||
|
||||
should.equalStyleStore(
|
||||
(obj) => obj.metadata["maputnik:openmaptiles_access_token"],
|
||||
apiKey
|
||||
);
|
||||
});
|
||||
|
||||
it("thunderforest access token", () => {
|
||||
var apiKey = "testing123";
|
||||
when.setValue(
|
||||
get.getDataAttribute(
|
||||
"modal:settings.maputnik:thunderforest_access_token"
|
||||
),
|
||||
apiKey
|
||||
);
|
||||
when.click("modal:settings.name");
|
||||
|
||||
should.equalStyleStore(
|
||||
(obj) => obj.metadata["maputnik:thunderforest_access_token"],
|
||||
apiKey
|
||||
);
|
||||
});
|
||||
|
||||
it("style renderer", () => {
|
||||
cy.on("uncaught:exception", () => false); // this is due to the fact that this is an invalid style for openlayers
|
||||
when.select(
|
||||
get.getDataAttribute("modal:settings.maputnik:renderer"),
|
||||
"ol"
|
||||
);
|
||||
should.isSelected(
|
||||
get.getDataAttribute("modal:settings.maputnik:renderer"),
|
||||
"ol"
|
||||
);
|
||||
|
||||
when.click("modal:settings.name");
|
||||
|
||||
should.equalStyleStore((obj) => obj.metadata["maputnik:renderer"], "ol");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sources", () => {
|
||||
it("toggle");
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": 8,
|
||||
"name": "Test Style",
|
||||
"metadata": {
|
||||
"maputnik:renderer": "mbgljs"
|
||||
"maputnik:renderer": "mlgljs"
|
||||
},
|
||||
"sources": {},
|
||||
"glyphs": "https://example.local/fonts/{fontstack}/{range}.pbf",
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": 8,
|
||||
"name": "Test Style",
|
||||
"metadata": {
|
||||
"maputnik:renderer": "mbgljs"
|
||||
"maputnik:renderer": "mlgljs"
|
||||
},
|
||||
"sources": {},
|
||||
"glyphs": "https://example.local/fonts/{fontstack}/{range}.pbf",
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"id": "test-style",
|
||||
"version": 8,
|
||||
"name": "Test Style",
|
||||
"metadata": {
|
||||
"maputnik:renderer": "mlgljs"
|
||||
},
|
||||
"sources": {
|
||||
"example": {
|
||||
"type": "vector",
|
||||
"data": {
|
||||
"type": "FeatureCollection",
|
||||
"features":[{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Dinagat Islands"
|
||||
},
|
||||
"geometry":{
|
||||
"type": "Point",
|
||||
"coordinates": [125.6, 10.1]
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
"raster": {
|
||||
"tileSize": 256,
|
||||
"tiles": ["http://localhost/example/{x}/{y}/{z}"],
|
||||
"type": "raster"
|
||||
}
|
||||
},
|
||||
"glyphs": "https://example.local/fonts/{fontstack}/{range}.pbf",
|
||||
"sprites": "https://example.local/fonts/{fontstack}/{range}.pbf",
|
||||
"layers": []
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"id": "test-style",
|
||||
"version": 8,
|
||||
"name": "Test Style",
|
||||
"metadata": {
|
||||
"maputnik:renderer": "mlgljs"
|
||||
},
|
||||
"sources": {
|
||||
"example": {
|
||||
"type": "vector",
|
||||
"data": {
|
||||
"type": "FeatureCollection",
|
||||
"features":[{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "Dinagat Islands"
|
||||
},
|
||||
"geometry":{
|
||||
"type": "Point",
|
||||
"coordinates": [125.6, 10.1]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"glyphs": "https://example.local/fonts/{fontstack}/{range}.pbf",
|
||||
"sprites": "https://example.local/fonts/{fontstack}/{range}.pbf",
|
||||
"layers": []
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "test-style",
|
||||
"version": 8,
|
||||
"name": "Test Style",
|
||||
"metadata": {
|
||||
"maputnik:renderer": "mlgljs"
|
||||
},
|
||||
"sources": {
|
||||
"raster": {
|
||||
"tileSize": 256,
|
||||
"tiles": ["http://localhost/example/{x}/{y}/{z}"],
|
||||
"type": "raster"
|
||||
}
|
||||
},
|
||||
"glyphs": "https://example.local/fonts/{fontstack}/{range}.pbf",
|
||||
"sprites": "https://example.local/fonts/{fontstack}/{range}.pbf",
|
||||
"layers": []
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
// declare global {
|
||||
// namespace Cypress {
|
||||
// interface Chainable {
|
||||
// login(email: string, password: string): Chainable<void>
|
||||
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,21 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import "cypress-plugin-tab";
|
||||
import "./commands";
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<title>Maputnik</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="manifest" href="src/manifest.json">
|
||||
<style>
|
||||
html {
|
||||
background-color: rgb(28, 31, 36);
|
||||
@@ -23,7 +23,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loading__logo svg {
|
||||
.loading__logo img {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
@@ -38,7 +38,6 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- TODO: Import dynamically -->
|
||||
<!-- From <https://github.com/hail2u/color-blindness-emulation> -->
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@@ -123,10 +122,10 @@
|
||||
<div id="app"></div>
|
||||
<div class="loading">
|
||||
<div class="loading__logo">
|
||||
<!-- replaced by 'html-webpack-inline-svg-plugin' -->
|
||||
<img inline src="node_modules/maputnik-design/logos/logo-loading.svg" />
|
||||
</div>
|
||||
<div class="loading__text">Loading…</div>
|
||||
</div>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+6586
-38132
File diff suppressed because it is too large
Load Diff
+33
-84
@@ -1,22 +1,17 @@
|
||||
{
|
||||
"name": "maputnik",
|
||||
"version": "2.0.0-pre.1",
|
||||
"version": "2.0.0-pre.2",
|
||||
"description": "A MapLibre GL visual style editor",
|
||||
"main": "''",
|
||||
"scripts": {
|
||||
"stats": "webpack --config config/webpack.production.config.js --progress=profile --json > stats.json",
|
||||
"build": "webpack --config config/webpack.production.config.js --progress=profile --color",
|
||||
"profiling-build": "webpack --config config/webpack.profiling.config.js --progress=profile --color",
|
||||
"test": "cross-env NODE_ENV=test wdio config/wdio.conf.js",
|
||||
"test-watch": "cross-env NODE_ENV=test wdio config/wdio.conf.js --watch",
|
||||
"start": "webpack-dev-server --progress=profile --color --config config/webpack.config.js",
|
||||
"start-prod": "webpack-dev-server --progress=profile --color --config config/webpack.production.config.js",
|
||||
"start-sandbox": "webpack-dev-server --disable-host-check --host 0.0.0.0 --progress=profile --color --config config/webpack.production.config.js",
|
||||
"lint-js": "eslint --ext js --ext jsx src test",
|
||||
"start": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint ./src --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0 && npm run lint-css",
|
||||
"test": "cypress run",
|
||||
"cy:open": "cypress open",
|
||||
"lint-css": "stylelint \"src/styles/*.scss\"",
|
||||
"lint": "npm run lint-js && npm run lint-css",
|
||||
"storybook": "start-storybook -h 0.0.0.0 -p 6006",
|
||||
"build-storybook": "build-storybook -o build/storybook"
|
||||
"storybook": "storybook dev -h 0.0.0.0 -p 6006",
|
||||
"build-storybook": "storybook build -o build/storybook"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -26,9 +21,9 @@
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/maputnik/editor#readme",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.9",
|
||||
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",
|
||||
"@maplibre/maplibre-gl-style-spec": "^17.0.1",
|
||||
"@mdi/js": "^6.6.96",
|
||||
"@mdi/react": "^1.5.0",
|
||||
"array-move": "^4.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
@@ -89,87 +84,41 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"eslintConfig": {
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parser": "@babel/eslint-parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"impliedStrict": true,
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.9",
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-flow": "^7.16.7",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@mdi/js": "^6.6.96",
|
||||
"@storybook/addon-a11y": "^6.4.20",
|
||||
"@storybook/addon-actions": "^6.4.20",
|
||||
"@storybook/addon-links": "^6.4.20",
|
||||
"@storybook/addon-storysource": "^6.4.20",
|
||||
"@storybook/addons": "^6.4.20",
|
||||
"@storybook/react": "^6.4.20",
|
||||
"@storybook/theming": "^6.4.20",
|
||||
"@wdio/cli": "^7.19.3",
|
||||
"@wdio/local-runner": "^7.19.3",
|
||||
"@wdio/mocha-framework": "^7.19.3",
|
||||
"@wdio/selenium-standalone-service": "^7.19.1",
|
||||
"@wdio/spec-reporter": "^7.19.1",
|
||||
"babel-loader": "^8.2.4",
|
||||
"babel-plugin-istanbul": "^6.1.1",
|
||||
"babel-plugin-static-fs": "^3.0.0",
|
||||
"copy-webpack-plugin": "^6.4.1",
|
||||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@shellygo/cypress-test-utils": "^2.0.9",
|
||||
"@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/cors": "^2.8.17",
|
||||
"@types/react": "^16.14.52",
|
||||
"@types/react-dom": "^16.9.24",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^5.2.7",
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"cypress": "^13.6.1",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"express": "^4.17.3",
|
||||
"html-webpack-inline-svg-plugin": "^2.3.0",
|
||||
"html-webpack-plugin": "^4.5.2",
|
||||
"istanbul": "^0.4.5",
|
||||
"istanbul-lib-coverage": "^3.2.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mocha": "^9.2.2",
|
||||
"postcss": "^8.4.12",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"sass-loader": "^10.2.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"storybook": "^7.6.5",
|
||||
"stylelint": "^14.6.1",
|
||||
"stylelint-config-recommended-scss": "^6.0.0",
|
||||
"stylelint-scss": "^4.2.0",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
"transform-loader": "^0.2.4",
|
||||
"typescript": "^4.6.3",
|
||||
"typescript": "^5.3.3",
|
||||
"uuid": "^8.3.2",
|
||||
"webdriverio": "^7.19.3",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cleanup-plugin": "^0.5.1",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.8.1"
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"container": {
|
||||
"startScript": "start-sandbox"
|
||||
}
|
||||
}
|
||||
+485
-419
File diff suppressed because it is too large
Load Diff
+202
-153
@@ -1,27 +1,33 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import {detect} from 'detect-browser';
|
||||
import classnames from "classnames";
|
||||
import { detect } from "detect-browser";
|
||||
import PropTypes from "prop-types";
|
||||
import React from "react";
|
||||
|
||||
import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage, MdAssignmentTurnedIn} from 'react-icons/md'
|
||||
|
||||
|
||||
import logoImage from 'maputnik-design/logos/logo-color.svg'
|
||||
import pkgJson from '../../package.json'
|
||||
import {
|
||||
MdAssignmentTurnedIn,
|
||||
MdFileDownload,
|
||||
MdFindInPage,
|
||||
MdHelpOutline,
|
||||
MdLayers,
|
||||
MdOpenInBrowser,
|
||||
MdSettings,
|
||||
} from "react-icons/md";
|
||||
|
||||
import logoImage from "maputnik-design/logos/logo-color.svg";
|
||||
import pkgJson from "../../package.json";
|
||||
|
||||
// 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 colorAccessibilityFiltersEnabled = ['chrome', 'firefox'].indexOf(browser.name) > -1;
|
||||
|
||||
const colorAccessibilityFiltersEnabled =
|
||||
["chrome", "firefox"].indexOf(browser.name) > -1;
|
||||
|
||||
class IconText extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <span className="maputnik-icon-text">{this.props.children}</span>
|
||||
return <span className="maputnik-icon-text">{this.props.children}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,17 +37,19 @@ class ToolbarLink extends React.Component {
|
||||
children: PropTypes.node,
|
||||
href: PropTypes.string,
|
||||
onToggleModal: PropTypes.func,
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <a
|
||||
className={classnames('maputnik-toolbar-link', this.props.className)}
|
||||
href={this.props.href}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{this.props.children}
|
||||
</a>
|
||||
return (
|
||||
<a
|
||||
className={classnames("maputnik-toolbar-link", this.props.className)}
|
||||
href={this.props.href}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{this.props.children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,36 +58,41 @@ class ToolbarLinkHighlighted extends React.Component {
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
href: PropTypes.string,
|
||||
onToggleModal: PropTypes.func
|
||||
}
|
||||
onToggleModal: PropTypes.func,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <a
|
||||
className={classnames('maputnik-toolbar-link', "maputnik-toolbar-link--highlighted", this.props.className)}
|
||||
href={this.props.href}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="maputnik-toolbar-link-wrapper">
|
||||
{this.props.children}
|
||||
</span>
|
||||
</a>
|
||||
return (
|
||||
<a
|
||||
className={classnames(
|
||||
"maputnik-toolbar-link",
|
||||
"maputnik-toolbar-link--highlighted",
|
||||
this.props.className
|
||||
)}
|
||||
href={this.props.href}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="maputnik-toolbar-link-wrapper">
|
||||
{this.props.children}
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ToolbarSelect extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
wdKey: PropTypes.string
|
||||
}
|
||||
wdKey: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div
|
||||
className='maputnik-toolbar-select'
|
||||
data-wd-key={this.props.wdKey}
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
return (
|
||||
<div className="maputnik-toolbar-select" data-wd-key={this.props.wdKey}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,17 +100,19 @@ class ToolbarAction extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
onClick: PropTypes.func,
|
||||
wdKey: PropTypes.string
|
||||
}
|
||||
wdKey: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <button
|
||||
className='maputnik-toolbar-action'
|
||||
data-wd-key={this.props.wdKey}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
{this.props.children}
|
||||
</button>
|
||||
return (
|
||||
<button
|
||||
className="maputnik-toolbar-action"
|
||||
data-wd-key={this.props.wdKey}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
{this.props.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +130,7 @@ export default class AppToolbar extends React.Component {
|
||||
onSetMapState: PropTypes.func,
|
||||
mapState: PropTypes.string,
|
||||
renderer: PropTypes.string,
|
||||
}
|
||||
};
|
||||
|
||||
state = {
|
||||
isOpen: {
|
||||
@@ -124,8 +139,8 @@ export default class AppToolbar extends React.Component {
|
||||
open: false,
|
||||
add: false,
|
||||
export: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
handleSelection(val) {
|
||||
this.props.onSetMapState(val);
|
||||
@@ -133,13 +148,12 @@ export default class AppToolbar extends React.Component {
|
||||
|
||||
onSkip = (target) => {
|
||||
if (target === "map") {
|
||||
document.querySelector(".mapboxgl-canvas").focus();
|
||||
}
|
||||
else {
|
||||
const el = document.querySelector("#skip-target-"+target);
|
||||
document.querySelector(".maplibregl-canvas").focus();
|
||||
} else {
|
||||
const el = document.querySelector("#skip-target-" + target);
|
||||
el.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const views = [
|
||||
@@ -152,7 +166,7 @@ export default class AppToolbar extends React.Component {
|
||||
id: "inspect",
|
||||
group: "general",
|
||||
title: "Inspect",
|
||||
disabled: this.props.renderer !== 'mbgljs',
|
||||
disabled: this.props.renderer === "ol",
|
||||
},
|
||||
{
|
||||
id: "filter-deuteranopia",
|
||||
@@ -184,102 +198,137 @@ export default class AppToolbar extends React.Component {
|
||||
return view.id === this.props.mapState;
|
||||
});
|
||||
|
||||
return <nav className='maputnik-toolbar'>
|
||||
<div className="maputnik-toolbar__inner">
|
||||
<div
|
||||
className="maputnik-toolbar-logo-container"
|
||||
>
|
||||
{/* Keyboard accessible quick links */}
|
||||
<button
|
||||
data-wd-key="root:skip:layer-list"
|
||||
className="maputnik-toolbar-skip"
|
||||
onClick={e => this.onSkip("layer-list")}
|
||||
return (
|
||||
<nav className="maputnik-toolbar">
|
||||
<div className="maputnik-toolbar__inner">
|
||||
<div className="maputnik-toolbar-logo-container">
|
||||
{/* Keyboard accessible quick links */}
|
||||
<button
|
||||
data-wd-key="root:skip:layer-list"
|
||||
className="maputnik-toolbar-skip"
|
||||
onClick={(e) => this.onSkip("layer-list")}
|
||||
>
|
||||
Layers list
|
||||
</button>
|
||||
<button
|
||||
data-wd-key="root:skip:layer-editor"
|
||||
className="maputnik-toolbar-skip"
|
||||
onClick={(e) => this.onSkip("layer-editor")}
|
||||
>
|
||||
Layer editor
|
||||
</button>
|
||||
<button
|
||||
data-wd-key="root:skip:map-view"
|
||||
className="maputnik-toolbar-skip"
|
||||
onClick={(e) => this.onSkip("map")}
|
||||
>
|
||||
Map view
|
||||
</button>
|
||||
<a
|
||||
className="maputnik-toolbar-logo"
|
||||
target="blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://github.com/maputnik/editor"
|
||||
>
|
||||
<span dangerouslySetInnerHTML={{ __html: logoImage }} />
|
||||
<h1>
|
||||
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
|
||||
<span className="maputnik-toolbar-version">
|
||||
v{pkgJson.version}
|
||||
</span>
|
||||
</h1>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="maputnik-toolbar__actions"
|
||||
role="navigation"
|
||||
aria-label="Toolbar"
|
||||
>
|
||||
Layers list
|
||||
</button>
|
||||
<button
|
||||
data-wd-key="root:skip:layer-editor"
|
||||
className="maputnik-toolbar-skip"
|
||||
onClick={e => this.onSkip("layer-editor")}
|
||||
>
|
||||
Layer editor
|
||||
</button>
|
||||
<button
|
||||
data-wd-key="root:skip:map-view"
|
||||
className="maputnik-toolbar-skip"
|
||||
onClick={e => this.onSkip("map")}
|
||||
>
|
||||
Map view
|
||||
</button>
|
||||
<a
|
||||
className="maputnik-toolbar-logo"
|
||||
target="blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://github.com/maputnik/editor"
|
||||
>
|
||||
<span dangerouslySetInnerHTML={{__html: logoImage}} />
|
||||
<h1>
|
||||
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
|
||||
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
||||
</h1>
|
||||
</a>
|
||||
</div>
|
||||
<div className="maputnik-toolbar__actions" role="navigation" aria-label="Toolbar">
|
||||
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
|
||||
<MdOpenInBrowser />
|
||||
<IconText>Open</IconText>
|
||||
</ToolbarAction>
|
||||
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
|
||||
<MdFileDownload />
|
||||
<IconText>Export</IconText>
|
||||
</ToolbarAction>
|
||||
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
|
||||
<MdLayers />
|
||||
<IconText>Data Sources</IconText>
|
||||
</ToolbarAction>
|
||||
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
|
||||
<MdSettings />
|
||||
<IconText>Style Settings</IconText>
|
||||
</ToolbarAction>
|
||||
<ToolbarAction
|
||||
wdKey="nav:open"
|
||||
onClick={this.props.onToggleModal.bind(this, "open")}
|
||||
>
|
||||
<MdOpenInBrowser />
|
||||
<IconText>Open</IconText>
|
||||
</ToolbarAction>
|
||||
<ToolbarAction
|
||||
wdKey="nav:export"
|
||||
onClick={this.props.onToggleModal.bind(this, "export")}
|
||||
>
|
||||
<MdFileDownload />
|
||||
<IconText>Export</IconText>
|
||||
</ToolbarAction>
|
||||
<ToolbarAction
|
||||
wdKey="nav:sources"
|
||||
onClick={this.props.onToggleModal.bind(this, "sources")}
|
||||
>
|
||||
<MdLayers />
|
||||
<IconText>Data Sources</IconText>
|
||||
</ToolbarAction>
|
||||
<ToolbarAction
|
||||
wdKey="nav:settings"
|
||||
onClick={this.props.onToggleModal.bind(this, "settings")}
|
||||
>
|
||||
<MdSettings />
|
||||
<IconText>Style Settings</IconText>
|
||||
</ToolbarAction>
|
||||
|
||||
<ToolbarSelect wdKey="nav:inspect">
|
||||
<MdFindInPage />
|
||||
<label>View
|
||||
<select
|
||||
className="maputnik-select"
|
||||
onChange={(e) => this.handleSelection(e.target.value)}
|
||||
value={currentView.id}
|
||||
>
|
||||
{views.filter(v => v.group === "general").map((item) => {
|
||||
return (
|
||||
<option key={item.id} value={item.id} disabled={item.disabled}>
|
||||
{item.title}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
<optgroup label="Color accessibility">
|
||||
{views.filter(v => v.group === "color-accessibility").map((item) => {
|
||||
return (
|
||||
<option key={item.id} value={item.id} disabled={item.disabled}>
|
||||
{item.title}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</optgroup>
|
||||
</select>
|
||||
</label>
|
||||
</ToolbarSelect>
|
||||
<ToolbarSelect wdKey="nav:inspect">
|
||||
<MdFindInPage />
|
||||
<label>
|
||||
View
|
||||
<select
|
||||
className="maputnik-select"
|
||||
data-wd-key="maputnik-select"
|
||||
onChange={(e) => this.handleSelection(e.target.value)}
|
||||
value={currentView.id}
|
||||
>
|
||||
{views
|
||||
.filter((v) => v.group === "general")
|
||||
.map((item) => {
|
||||
return (
|
||||
<option
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
disabled={item.disabled}
|
||||
data-wd-key={item.id}
|
||||
>
|
||||
{item.title}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
<optgroup label="Color accessibility">
|
||||
{views
|
||||
.filter((v) => v.group === "color-accessibility")
|
||||
.map((item) => {
|
||||
return (
|
||||
<option
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.title}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</optgroup>
|
||||
</select>
|
||||
</label>
|
||||
</ToolbarSelect>
|
||||
|
||||
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
|
||||
<MdHelpOutline />
|
||||
<IconText>Help</IconText>
|
||||
</ToolbarLink>
|
||||
<ToolbarLinkHighlighted href={"https://gregorywolanski.typeform.com/to/cPgaSY"}>
|
||||
<MdAssignmentTurnedIn />
|
||||
<IconText>Take the Maputnik Survey</IconText>
|
||||
</ToolbarLinkHighlighted>
|
||||
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
|
||||
<MdHelpOutline />
|
||||
<IconText>Help</IconText>
|
||||
</ToolbarLink>
|
||||
<ToolbarLinkHighlighted
|
||||
href={"https://gregorywolanski.typeform.com/to/cPgaSY"}
|
||||
>
|
||||
<MdAssignmentTurnedIn />
|
||||
<IconText>Take the Maputnik Survey</IconText>
|
||||
</ToolbarLinkHighlighted>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ export default class Doc extends React.Component {
|
||||
|
||||
const renderValues = (
|
||||
!!values &&
|
||||
// HACK: Currently we merge additional values into the stylespec, so this is required
|
||||
// See <https://github.com/maputnik/editor/blob/master/src/components/fields/PropertyGroup.jsx#L16>
|
||||
// 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>
|
||||
!Array.isArray(values)
|
||||
);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ function createStyleFromFilter (filter) {
|
||||
"id": "tmp",
|
||||
"version": 8,
|
||||
"name": "Empty Style",
|
||||
"metadata": {"maputnik:renderer": "mbgljs"},
|
||||
"metadata": {"maputnik:renderer": "mlgljs"},
|
||||
"sources": {
|
||||
"tmp": {
|
||||
"type": "geojson",
|
||||
|
||||
@@ -32,7 +32,7 @@ function optionsLabelLength(options) {
|
||||
return sum
|
||||
}
|
||||
|
||||
/** Display any field from the Mapbox GL style spec and
|
||||
/** Display any field from the Maplibre GL style spec and
|
||||
* choose the correct field component based on the @{fieldSpec}
|
||||
* to display @{value}. */
|
||||
export default class SpecField extends React.Component {
|
||||
|
||||
+249
-207
@@ -1,47 +1,46 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
|
||||
import PropTypes from "prop-types";
|
||||
import React from "react";
|
||||
import { Button, Menu, MenuItem, Wrapper } from "react-aria-menubutton";
|
||||
|
||||
import FieldJson from './FieldJson'
|
||||
import FilterEditor from './FilterEditor'
|
||||
import PropertyGroup from './PropertyGroup'
|
||||
import LayerEditorGroup from './LayerEditorGroup'
|
||||
import FieldType from './FieldType'
|
||||
import FieldId from './FieldId'
|
||||
import FieldMinZoom from './FieldMinZoom'
|
||||
import FieldMaxZoom from './FieldMaxZoom'
|
||||
import FieldComment from './FieldComment'
|
||||
import FieldSource from './FieldSource'
|
||||
import FieldSourceLayer from './FieldSourceLayer'
|
||||
import {Accordion} from 'react-accessible-accordion';
|
||||
import { Accordion } from "react-accessible-accordion";
|
||||
import FieldComment from "./FieldComment";
|
||||
import FieldId from "./FieldId";
|
||||
import FieldJson from "./FieldJson";
|
||||
import FieldMaxZoom from "./FieldMaxZoom";
|
||||
import FieldMinZoom from "./FieldMinZoom";
|
||||
import FieldSource from "./FieldSource";
|
||||
import FieldSourceLayer from "./FieldSourceLayer";
|
||||
import FieldType from "./FieldType";
|
||||
import FilterEditor from "./FilterEditor";
|
||||
import LayerEditorGroup from "./LayerEditorGroup";
|
||||
import PropertyGroup from "./PropertyGroup";
|
||||
|
||||
import {MdMoreVert} from 'react-icons/md'
|
||||
import { MdMoreVert } from "react-icons/md";
|
||||
|
||||
import { changeType, changeProperty } from '../libs/layer'
|
||||
import layout from '../config/layout.json'
|
||||
import {formatLayerId} from '../util/format';
|
||||
import layout from "../config/layout.json";
|
||||
import { changeProperty, changeType } from "../libs/layer";
|
||||
import { formatLayerId } from "../util/format";
|
||||
|
||||
|
||||
function getLayoutForType (type) {
|
||||
function getLayoutForType(type) {
|
||||
return layout[type] ? layout[type] : layout.invalid;
|
||||
}
|
||||
|
||||
function layoutGroups(layerType) {
|
||||
const layerGroup = {
|
||||
title: 'Layer',
|
||||
type: 'layer'
|
||||
}
|
||||
title: "Layer",
|
||||
type: "layer",
|
||||
};
|
||||
const filterGroup = {
|
||||
title: 'Filter',
|
||||
type: 'filter'
|
||||
}
|
||||
title: "Filter",
|
||||
type: "filter",
|
||||
};
|
||||
const editorGroup = {
|
||||
title: 'JSON Editor',
|
||||
type: 'jsoneditor'
|
||||
}
|
||||
title: "JSON Editor",
|
||||
type: "jsoneditor",
|
||||
};
|
||||
return [layerGroup, filterGroup]
|
||||
.concat(getLayoutForType(layerType).groups)
|
||||
.concat([editorGroup])
|
||||
.concat([editorGroup]);
|
||||
}
|
||||
|
||||
/** Layer editor supporting multiple types of layers. */
|
||||
@@ -61,277 +60,320 @@ export default class LayerEditor extends React.Component {
|
||||
isLastLayer: PropTypes.bool,
|
||||
layerIndex: PropTypes.number,
|
||||
errors: PropTypes.array,
|
||||
}
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onLayerChanged: () => {},
|
||||
onLayerIdChange: () => {},
|
||||
onLayerDestroyed: () => {},
|
||||
}
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
reactIconBase: PropTypes.object
|
||||
}
|
||||
reactIconBase: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
super(props);
|
||||
|
||||
//TODO: Clean this up and refactor into function
|
||||
const editorGroups = {}
|
||||
layoutGroups(this.props.layer.type).forEach(group => {
|
||||
editorGroups[group.title] = true
|
||||
})
|
||||
const editorGroups = {};
|
||||
layoutGroups(this.props.layer.type).forEach((group) => {
|
||||
editorGroups[group.title] = true;
|
||||
});
|
||||
|
||||
this.state = { editorGroups }
|
||||
this.state = { editorGroups };
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
const additionalGroups = { ...state.editorGroups }
|
||||
const additionalGroups = { ...state.editorGroups };
|
||||
|
||||
getLayoutForType(props.layer.type).groups.forEach(group => {
|
||||
if(!(group.title in additionalGroups)) {
|
||||
additionalGroups[group.title] = true
|
||||
getLayoutForType(props.layer.type).groups.forEach((group) => {
|
||||
if (!(group.title in additionalGroups)) {
|
||||
additionalGroups[group.title] = true;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
editorGroups: additionalGroups
|
||||
editorGroups: additionalGroups,
|
||||
};
|
||||
}
|
||||
|
||||
getChildContext () {
|
||||
getChildContext() {
|
||||
return {
|
||||
reactIconBase: {
|
||||
size: 14,
|
||||
color: '#8e8e8e',
|
||||
}
|
||||
}
|
||||
color: "#8e8e8e",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
changeProperty(group, property, newValue) {
|
||||
this.props.onLayerChanged(
|
||||
this.props.layerIndex,
|
||||
changeProperty(this.props.layer, group, property, newValue)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
onGroupToggle(groupTitle, active) {
|
||||
const changedActiveGroups = {
|
||||
...this.state.editorGroups,
|
||||
[groupTitle]: active,
|
||||
}
|
||||
};
|
||||
this.setState({
|
||||
editorGroups: changedActiveGroups
|
||||
})
|
||||
editorGroups: changedActiveGroups,
|
||||
});
|
||||
}
|
||||
|
||||
renderGroupType(type, fields) {
|
||||
let comment = ""
|
||||
if(this.props.layer.metadata) {
|
||||
comment = this.props.layer.metadata['maputnik:comment']
|
||||
let comment = "";
|
||||
if (this.props.layer.metadata) {
|
||||
comment = this.props.layer.metadata["maputnik:comment"];
|
||||
}
|
||||
const {errors, layerIndex} = this.props;
|
||||
const { errors, layerIndex } = this.props;
|
||||
|
||||
const errorData = {};
|
||||
errors.forEach(error => {
|
||||
errors.forEach((error) => {
|
||||
if (
|
||||
error.parsed &&
|
||||
error.parsed.type === "layer" &&
|
||||
error.parsed.data.index == layerIndex
|
||||
) {
|
||||
errorData[error.parsed.data.key] = {
|
||||
message: error.parsed.data.message
|
||||
message: error.parsed.data.message,
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let sourceLayerIds;
|
||||
if(this.props.sources.hasOwnProperty(this.props.layer.source)) {
|
||||
if (this.props.sources.hasOwnProperty(this.props.layer.source)) {
|
||||
sourceLayerIds = this.props.sources[this.props.layer.source].layers;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case 'layer': return <div>
|
||||
<FieldId
|
||||
value={this.props.layer.id}
|
||||
wdKey="layer-editor.layer-id"
|
||||
error={errorData.id}
|
||||
onChange={newId => this.props.onLayerIdChange(this.props.layerIndex, this.props.layer.id, newId)}
|
||||
/>
|
||||
<FieldType
|
||||
disabled={true}
|
||||
error={errorData.type}
|
||||
value={this.props.layer.type}
|
||||
onChange={newType => this.props.onLayerChanged(
|
||||
this.props.layerIndex,
|
||||
changeType(this.props.layer, newType)
|
||||
)}
|
||||
/>
|
||||
{this.props.layer.type !== 'background' && <FieldSource
|
||||
error={errorData.source}
|
||||
sourceIds={Object.keys(this.props.sources)}
|
||||
value={this.props.layer.source}
|
||||
onChange={v => this.changeProperty(null, 'source', v)}
|
||||
/>
|
||||
}
|
||||
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.props.layer.type) < 0 &&
|
||||
<FieldSourceLayer
|
||||
error={errorData['source-layer']}
|
||||
sourceLayerIds={sourceLayerIds}
|
||||
value={this.props.layer['source-layer']}
|
||||
onChange={v => this.changeProperty(null, 'source-layer', v)}
|
||||
/>
|
||||
}
|
||||
<FieldMinZoom
|
||||
error={errorData.minzoom}
|
||||
value={this.props.layer.minzoom}
|
||||
onChange={v => this.changeProperty(null, 'minzoom', v)}
|
||||
/>
|
||||
<FieldMaxZoom
|
||||
error={errorData.maxzoom}
|
||||
value={this.props.layer.maxzoom}
|
||||
onChange={v => this.changeProperty(null, 'maxzoom', v)}
|
||||
/>
|
||||
<FieldComment
|
||||
error={errorData.comment}
|
||||
value={comment}
|
||||
onChange={v => this.changeProperty('metadata', 'maputnik:comment', v == "" ? undefined : v)}
|
||||
/>
|
||||
</div>
|
||||
case 'filter': return <div>
|
||||
<div className="maputnik-filter-editor-wrapper">
|
||||
<FilterEditor
|
||||
switch (type) {
|
||||
case "layer":
|
||||
return (
|
||||
<div>
|
||||
<FieldId
|
||||
value={this.props.layer.id}
|
||||
wdKey="layer-editor.layer-id"
|
||||
error={errorData.id}
|
||||
onChange={(newId) =>
|
||||
this.props.onLayerIdChange(
|
||||
this.props.layerIndex,
|
||||
this.props.layer.id,
|
||||
newId
|
||||
)
|
||||
}
|
||||
/>
|
||||
<FieldType
|
||||
disabled={true}
|
||||
error={errorData.type}
|
||||
value={this.props.layer.type}
|
||||
onChange={(newType) =>
|
||||
this.props.onLayerChanged(
|
||||
this.props.layerIndex,
|
||||
changeType(this.props.layer, newType)
|
||||
)
|
||||
}
|
||||
/>
|
||||
{this.props.layer.type !== "background" && (
|
||||
<FieldSource
|
||||
error={errorData.source}
|
||||
sourceIds={Object.keys(this.props.sources)}
|
||||
value={this.props.layer.source}
|
||||
onChange={(v) => this.changeProperty(null, "source", v)}
|
||||
/>
|
||||
)}
|
||||
{["background", "raster", "hillshade", "heatmap"].indexOf(
|
||||
this.props.layer.type
|
||||
) < 0 && (
|
||||
<FieldSourceLayer
|
||||
error={errorData["source-layer"]}
|
||||
sourceLayerIds={sourceLayerIds}
|
||||
value={this.props.layer["source-layer"]}
|
||||
onChange={(v) => this.changeProperty(null, "source-layer", v)}
|
||||
/>
|
||||
)}
|
||||
<FieldMinZoom
|
||||
error={errorData.minzoom}
|
||||
value={this.props.layer.minzoom}
|
||||
onChange={(v) => this.changeProperty(null, "minzoom", v)}
|
||||
/>
|
||||
<FieldMaxZoom
|
||||
error={errorData.maxzoom}
|
||||
value={this.props.layer.maxzoom}
|
||||
onChange={(v) => this.changeProperty(null, "maxzoom", v)}
|
||||
/>
|
||||
<FieldComment
|
||||
error={errorData.comment}
|
||||
value={comment}
|
||||
onChange={(v) =>
|
||||
this.changeProperty(
|
||||
"metadata",
|
||||
"maputnik:comment",
|
||||
v == "" ? undefined : v
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case "filter":
|
||||
return (
|
||||
<div>
|
||||
<div className="maputnik-filter-editor-wrapper">
|
||||
<FilterEditor
|
||||
errors={errorData}
|
||||
filter={this.props.layer.filter}
|
||||
properties={
|
||||
this.props.vectorLayers[this.props.layer["source-layer"]]
|
||||
}
|
||||
onChange={(f) => this.changeProperty(null, "filter", f)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case "properties":
|
||||
return (
|
||||
<PropertyGroup
|
||||
errors={errorData}
|
||||
filter={this.props.layer.filter}
|
||||
properties={this.props.vectorLayers[this.props.layer['source-layer']]}
|
||||
onChange={f => this.changeProperty(null, 'filter', f)}
|
||||
layer={this.props.layer}
|
||||
groupFields={fields}
|
||||
spec={this.props.spec}
|
||||
onChange={this.changeProperty.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
case 'properties':
|
||||
return <PropertyGroup
|
||||
errors={errorData}
|
||||
layer={this.props.layer}
|
||||
groupFields={fields}
|
||||
spec={this.props.spec}
|
||||
onChange={this.changeProperty.bind(this)}
|
||||
/>
|
||||
case 'jsoneditor':
|
||||
return <FieldJson
|
||||
layer={this.props.layer}
|
||||
onChange={(layer) => {
|
||||
this.props.onLayerChanged(
|
||||
this.props.layerIndex,
|
||||
layer
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "jsoneditor":
|
||||
return (
|
||||
<FieldJson
|
||||
layer={this.props.layer}
|
||||
onChange={(layer) => {
|
||||
this.props.onLayerChanged(this.props.layerIndex, layer);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
moveLayer(offset) {
|
||||
this.props.onMoveLayer({
|
||||
oldIndex: this.props.layerIndex,
|
||||
newIndex: this.props.layerIndex+offset
|
||||
})
|
||||
newIndex: this.props.layerIndex + offset,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const groupIds = [];
|
||||
const layerType = this.props.layer.type
|
||||
const groups = layoutGroups(layerType).filter(group => {
|
||||
return !(layerType === 'background' && group.type === 'source')
|
||||
}).map(group => {
|
||||
const groupId = group.title.replace(/ /g, "_");
|
||||
groupIds.push(groupId);
|
||||
return <LayerEditorGroup
|
||||
data-wd-key={group.title}
|
||||
id={groupId}
|
||||
key={group.title}
|
||||
title={group.title}
|
||||
isActive={this.state.editorGroups[group.title]}
|
||||
onActiveToggle={this.onGroupToggle.bind(this, group.title)}
|
||||
>
|
||||
{this.renderGroupType(group.type, group.fields)}
|
||||
</LayerEditorGroup>
|
||||
})
|
||||
const layerType = this.props.layer.type;
|
||||
const groups = layoutGroups(layerType)
|
||||
.filter((group) => {
|
||||
return !(layerType === "background" && group.type === "source");
|
||||
})
|
||||
.map((group) => {
|
||||
const groupId = group.title.replace(/ /g, "_");
|
||||
groupIds.push(groupId);
|
||||
return (
|
||||
<LayerEditorGroup
|
||||
data-wd-key={group.title}
|
||||
id={groupId}
|
||||
key={group.title}
|
||||
title={group.title}
|
||||
isActive={this.state.editorGroups[group.title]}
|
||||
onActiveToggle={this.onGroupToggle.bind(this, group.title)}
|
||||
>
|
||||
{this.renderGroupType(group.type, group.fields)}
|
||||
</LayerEditorGroup>
|
||||
);
|
||||
});
|
||||
|
||||
const layout = this.props.layer.layout || {}
|
||||
const layout = this.props.layer.layout || {};
|
||||
|
||||
const items = {
|
||||
delete: {
|
||||
text: "Delete",
|
||||
handler: () => this.props.onLayerDestroy(this.props.layerIndex)
|
||||
handler: () => this.props.onLayerDestroy(this.props.layerIndex),
|
||||
},
|
||||
duplicate: {
|
||||
text: "Duplicate",
|
||||
handler: () => this.props.onLayerCopy(this.props.layerIndex)
|
||||
handler: () => this.props.onLayerCopy(this.props.layerIndex),
|
||||
},
|
||||
hide: {
|
||||
text: (layout.visibility === "none") ? "Show" : "Hide",
|
||||
handler: () => this.props.onLayerVisibilityToggle(this.props.layerIndex)
|
||||
text: layout.visibility === "none" ? "Show" : "Hide",
|
||||
handler: () =>
|
||||
this.props.onLayerVisibilityToggle(this.props.layerIndex),
|
||||
},
|
||||
moveLayerUp: {
|
||||
text: "Move layer up",
|
||||
// Not actually used...
|
||||
disabled: this.props.isFirstLayer,
|
||||
handler: () => this.moveLayer(-1)
|
||||
handler: () => this.moveLayer(-1),
|
||||
},
|
||||
moveLayerDown: {
|
||||
text: "Move layer down",
|
||||
// Not actually used...
|
||||
disabled: this.props.isLastLayer,
|
||||
handler: () => this.moveLayer(+1)
|
||||
}
|
||||
}
|
||||
handler: () => this.moveLayer(+1),
|
||||
},
|
||||
};
|
||||
|
||||
function handleSelection(id, event) {
|
||||
event.stopPropagation;
|
||||
items[id].handler();
|
||||
}
|
||||
|
||||
return <section className="maputnik-layer-editor"
|
||||
role="main"
|
||||
aria-label="Layer editor"
|
||||
>
|
||||
<header>
|
||||
<div className="layer-header">
|
||||
<h2 className="layer-header__title">
|
||||
Layer: {formatLayerId(this.props.layer.id)}
|
||||
</h2>
|
||||
<div className="layer-header__info">
|
||||
<Wrapper
|
||||
className='more-menu'
|
||||
onSelection={handleSelection}
|
||||
closeOnSelection={false}
|
||||
>
|
||||
<Button id="skip-target-layer-editor" className='more-menu__button' title="Layer options">
|
||||
<MdMoreVert className="more-menu__button__svg" />
|
||||
</Button>
|
||||
<Menu>
|
||||
<ul className="more-menu__menu">
|
||||
{Object.keys(items).map((id, idx) => {
|
||||
const item = items[id];
|
||||
return <li key={id}>
|
||||
<MenuItem value={id} className='more-menu__menu__item'>
|
||||
{item.text}
|
||||
</MenuItem>
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
</Menu>
|
||||
</Wrapper>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
<Accordion
|
||||
allowMultipleExpanded={true}
|
||||
allowZeroExpanded={true}
|
||||
preExpanded={groupIds}
|
||||
return (
|
||||
<section
|
||||
className="maputnik-layer-editor"
|
||||
role="main"
|
||||
aria-label="Layer editor"
|
||||
>
|
||||
{groups}
|
||||
</Accordion>
|
||||
</section>
|
||||
<header>
|
||||
<div className="layer-header">
|
||||
<h2 className="layer-header__title">
|
||||
Layer: {formatLayerId(this.props.layer.id)}
|
||||
</h2>
|
||||
<div className="layer-header__info">
|
||||
<Wrapper
|
||||
className="more-menu"
|
||||
onSelection={handleSelection}
|
||||
closeOnSelection={false}
|
||||
>
|
||||
<Button
|
||||
data-wd-key="skip-target-layer-editor"
|
||||
id="skip-target-layer-editor"
|
||||
className="more-menu__button"
|
||||
title="Layer options"
|
||||
>
|
||||
<MdMoreVert className="more-menu__button__svg" />
|
||||
</Button>
|
||||
<Menu>
|
||||
<ul className="more-menu__menu">
|
||||
{Object.keys(items).map((id, idx) => {
|
||||
const item = items[id];
|
||||
return (
|
||||
<li key={id}>
|
||||
<MenuItem
|
||||
value={id}
|
||||
className="more-menu__menu__item"
|
||||
>
|
||||
{item.text}
|
||||
</MenuItem>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</Menu>
|
||||
</Wrapper>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<Accordion
|
||||
allowMultipleExpanded={true}
|
||||
allowZeroExpanded={true}
|
||||
preExpanded={groupIds}
|
||||
>
|
||||
{groups}
|
||||
</Accordion>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+175
-154
@@ -1,13 +1,13 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import lodash from 'lodash';
|
||||
import classnames from "classnames";
|
||||
import lodash from "lodash";
|
||||
import PropTypes from "prop-types";
|
||||
import React from "react";
|
||||
|
||||
import LayerListGroup from './LayerListGroup'
|
||||
import LayerListItem from './LayerListItem'
|
||||
import ModalAdd from './ModalAdd'
|
||||
import LayerListGroup from "./LayerListGroup";
|
||||
import LayerListItem from "./LayerListItem";
|
||||
import ModalAdd from "./ModalAdd";
|
||||
|
||||
import {SortableContainer} from 'react-sortable-hoc';
|
||||
import { SortableContainer } from "react-sortable-hoc";
|
||||
|
||||
const layerListPropTypes = {
|
||||
layers: PropTypes.array.isRequired,
|
||||
@@ -15,34 +15,34 @@ const layerListPropTypes = {
|
||||
onLayersChange: PropTypes.func.isRequired,
|
||||
onLayerSelect: PropTypes.func,
|
||||
sources: PropTypes.object.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
function layerPrefix(name) {
|
||||
return name.replace(' ', '-').replace('_', '-').split('-')[0]
|
||||
return name.replace(" ", "-").replace("_", "-").split("-")[0];
|
||||
}
|
||||
|
||||
function findClosestCommonPrefix(layers, idx) {
|
||||
const currentLayerPrefix = layerPrefix(layers[idx].id)
|
||||
let closestIdx = idx
|
||||
const currentLayerPrefix = layerPrefix(layers[idx].id);
|
||||
let closestIdx = idx;
|
||||
for (let i = idx; i > 0; i--) {
|
||||
const previousLayerPrefix = layerPrefix(layers[i-1].id)
|
||||
if(previousLayerPrefix === currentLayerPrefix) {
|
||||
closestIdx = i - 1
|
||||
const previousLayerPrefix = layerPrefix(layers[i - 1].id);
|
||||
if (previousLayerPrefix === currentLayerPrefix) {
|
||||
closestIdx = i - 1;
|
||||
} else {
|
||||
return closestIdx
|
||||
return closestIdx;
|
||||
}
|
||||
}
|
||||
return closestIdx
|
||||
return closestIdx;
|
||||
}
|
||||
|
||||
let UID = 0;
|
||||
|
||||
// List of collapsible layer editors
|
||||
class LayerListContainer extends React.Component {
|
||||
static propTypes = {...layerListPropTypes}
|
||||
static propTypes = { ...layerListPropTypes };
|
||||
static defaultProps = {
|
||||
onLayerSelect: () => {},
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -56,8 +56,8 @@ class LayerListContainer extends React.Component {
|
||||
},
|
||||
isOpen: {
|
||||
add: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
toggleModal(modalName) {
|
||||
@@ -68,79 +68,82 @@ class LayerListContainer extends React.Component {
|
||||
},
|
||||
isOpen: {
|
||||
...this.state.isOpen,
|
||||
[modalName]: !this.state.isOpen[modalName]
|
||||
}
|
||||
})
|
||||
[modalName]: !this.state.isOpen[modalName],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
toggleLayers = () => {
|
||||
let idx=0
|
||||
let idx = 0;
|
||||
|
||||
let newGroups=[]
|
||||
|
||||
this.groupedLayers().forEach(layers => {
|
||||
const groupPrefix = layerPrefix(layers[0].id)
|
||||
const lookupKey = [groupPrefix, idx].join('-')
|
||||
let newGroups = [];
|
||||
|
||||
this.groupedLayers().forEach((layers) => {
|
||||
const groupPrefix = layerPrefix(layers[0].id);
|
||||
const lookupKey = [groupPrefix, idx].join("-");
|
||||
|
||||
if (layers.length > 1) {
|
||||
newGroups[lookupKey] = this.state.areAllGroupsExpanded
|
||||
newGroups[lookupKey] = this.state.areAllGroupsExpanded;
|
||||
}
|
||||
|
||||
layers.forEach((layer) => {
|
||||
idx += 1
|
||||
})
|
||||
idx += 1;
|
||||
});
|
||||
});
|
||||
|
||||
this.setState({
|
||||
collapsedGroups: newGroups,
|
||||
areAllGroupsExpanded: !this.state.areAllGroupsExpanded
|
||||
})
|
||||
}
|
||||
areAllGroupsExpanded: !this.state.areAllGroupsExpanded,
|
||||
});
|
||||
};
|
||||
|
||||
groupedLayers() {
|
||||
const groups = []
|
||||
const groups = [];
|
||||
const layerIdCount = new Map();
|
||||
|
||||
for (let i = 0; i < this.props.layers.length; i++) {
|
||||
const origLayer = this.props.layers[i];
|
||||
const previousLayer = this.props.layers[i-1]
|
||||
layerIdCount.set(origLayer.id,
|
||||
const previousLayer = this.props.layers[i - 1];
|
||||
layerIdCount.set(
|
||||
origLayer.id,
|
||||
layerIdCount.has(origLayer.id) ? layerIdCount.get(origLayer.id) + 1 : 0
|
||||
);
|
||||
const layer = {
|
||||
...origLayer,
|
||||
key: `layers-list-${origLayer.id}-${layerIdCount.get(origLayer.id)}`,
|
||||
}
|
||||
if(previousLayer && layerPrefix(previousLayer.id) == layerPrefix(layer.id)) {
|
||||
const lastGroup = groups[groups.length - 1]
|
||||
lastGroup.push(layer)
|
||||
};
|
||||
if (
|
||||
previousLayer &&
|
||||
layerPrefix(previousLayer.id) == layerPrefix(layer.id)
|
||||
) {
|
||||
const lastGroup = groups[groups.length - 1];
|
||||
lastGroup.push(layer);
|
||||
} else {
|
||||
groups.push([layer])
|
||||
groups.push([layer]);
|
||||
}
|
||||
}
|
||||
return groups
|
||||
return groups;
|
||||
}
|
||||
|
||||
toggleLayerGroup(groupPrefix, idx) {
|
||||
const lookupKey = [groupPrefix, idx].join('-')
|
||||
const newGroups = { ...this.state.collapsedGroups }
|
||||
if(lookupKey in this.state.collapsedGroups) {
|
||||
newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey]
|
||||
const lookupKey = [groupPrefix, idx].join("-");
|
||||
const newGroups = { ...this.state.collapsedGroups };
|
||||
if (lookupKey in this.state.collapsedGroups) {
|
||||
newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey];
|
||||
} else {
|
||||
newGroups[lookupKey] = false
|
||||
newGroups[lookupKey] = false;
|
||||
}
|
||||
this.setState({
|
||||
collapsedGroups: newGroups
|
||||
})
|
||||
collapsedGroups: newGroups,
|
||||
});
|
||||
}
|
||||
|
||||
isCollapsed(groupPrefix, idx) {
|
||||
const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join('-')]
|
||||
return collapsed === undefined ? true : collapsed
|
||||
const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join("-")];
|
||||
return collapsed === undefined ? true : collapsed;
|
||||
}
|
||||
|
||||
shouldComponentUpdate (nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
// Always update on state change
|
||||
if (this.state !== nextState) {
|
||||
return true;
|
||||
@@ -148,28 +151,28 @@ class LayerListContainer extends React.Component {
|
||||
|
||||
// This component tree only requires id and visibility from the layers
|
||||
// objects
|
||||
function getRequiredProps (layer) {
|
||||
function getRequiredProps(layer) {
|
||||
const out = {
|
||||
id: layer.id,
|
||||
};
|
||||
|
||||
if (layer.layout) {
|
||||
out.layout = {
|
||||
visibility: layer.layout.visibility
|
||||
visibility: layer.layout.visibility,
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
const layersEqual = lodash.isEqual(
|
||||
nextProps.layers.map(getRequiredProps),
|
||||
this.props.layers.map(getRequiredProps),
|
||||
this.props.layers.map(getRequiredProps)
|
||||
);
|
||||
|
||||
function withoutLayers (props) {
|
||||
function withoutLayers(props) {
|
||||
const out = {
|
||||
...props
|
||||
...props,
|
||||
};
|
||||
delete out['layers'];
|
||||
delete out["layers"];
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -184,16 +187,16 @@ class LayerListContainer extends React.Component {
|
||||
return propsChanged;
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.selectedLayerIndex !== this.props.selectedLayerIndex) {
|
||||
const selectedItemNode = this.selectedItemRef.current;
|
||||
if (selectedItemNode && selectedItemNode.node) {
|
||||
const target = selectedItemNode.node;
|
||||
const options = {
|
||||
root: this.scrollContainerRef.current,
|
||||
threshold: 1.0
|
||||
}
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
threshold: 1.0,
|
||||
};
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
observer.unobserve(target);
|
||||
if (entries.length > 0 && entries[0].intersectionRatio < 1) {
|
||||
target.scrollIntoView();
|
||||
@@ -206,28 +209,32 @@ class LayerListContainer extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const listItems = []
|
||||
let idx = 0
|
||||
const listItems = [];
|
||||
let idx = 0;
|
||||
const layersByGroup = this.groupedLayers();
|
||||
layersByGroup.forEach(layers => {
|
||||
const groupPrefix = layerPrefix(layers[0].id)
|
||||
if(layers.length > 1) {
|
||||
const grp = <LayerListGroup
|
||||
data-wd-key={[groupPrefix, idx].join('-')}
|
||||
aria-controls={layers.map(l => l.key).join(" ")}
|
||||
key={`group-${groupPrefix}-${idx}`}
|
||||
title={groupPrefix}
|
||||
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
|
||||
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
|
||||
/>
|
||||
listItems.push(grp)
|
||||
layersByGroup.forEach((layers) => {
|
||||
const groupPrefix = layerPrefix(layers[0].id);
|
||||
if (layers.length > 1) {
|
||||
const grp = (
|
||||
<LayerListGroup
|
||||
data-wd-key={[groupPrefix, idx].join("-")}
|
||||
aria-controls={layers.map((l) => l.key).join(" ")}
|
||||
key={`group-${groupPrefix}-${idx}`}
|
||||
title={groupPrefix}
|
||||
isActive={
|
||||
!this.isCollapsed(groupPrefix, idx) ||
|
||||
idx === this.props.selectedLayerIndex
|
||||
}
|
||||
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
|
||||
/>
|
||||
);
|
||||
listItems.push(grp);
|
||||
}
|
||||
|
||||
layers.forEach((layer, idxInGroup) => {
|
||||
const groupIdx = findClosestCommonPrefix(this.props.layers, idx)
|
||||
const groupIdx = findClosestCommonPrefix(this.props.layers, idx);
|
||||
|
||||
const layerError = this.props.errors.find(error => {
|
||||
const layerError = this.props.errors.find((error) => {
|
||||
return (
|
||||
error.parsed &&
|
||||
error.parsed.type === "layer" &&
|
||||
@@ -240,93 +247,107 @@ class LayerListContainer extends React.Component {
|
||||
additionalProps.ref = this.selectedItemRef;
|
||||
}
|
||||
|
||||
const listItem = <LayerListItem
|
||||
className={classnames({
|
||||
'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
|
||||
'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1,
|
||||
'maputnik-layer-list-item--error': !!layerError
|
||||
})}
|
||||
index={idx}
|
||||
key={layer.key}
|
||||
id={layer.key}
|
||||
layerId={layer.id}
|
||||
layerIndex={idx}
|
||||
layerType={layer.type}
|
||||
visibility={(layer.layout || {}).visibility}
|
||||
isSelected={idx === this.props.selectedLayerIndex}
|
||||
onLayerSelect={this.props.onLayerSelect}
|
||||
onLayerDestroy={this.props.onLayerDestroy.bind(this)}
|
||||
onLayerCopy={this.props.onLayerCopy.bind(this)}
|
||||
onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)}
|
||||
{...additionalProps}
|
||||
/>
|
||||
listItems.push(listItem)
|
||||
idx += 1
|
||||
})
|
||||
})
|
||||
const listItem = (
|
||||
<LayerListItem
|
||||
className={classnames({
|
||||
"maputnik-layer-list-item-collapsed":
|
||||
layers.length > 1 &&
|
||||
this.isCollapsed(groupPrefix, groupIdx) &&
|
||||
idx !== this.props.selectedLayerIndex,
|
||||
"maputnik-layer-list-item-group-last":
|
||||
idxInGroup == layers.length - 1 && layers.length > 1,
|
||||
"maputnik-layer-list-item--error": !!layerError,
|
||||
})}
|
||||
index={idx}
|
||||
key={layer.key}
|
||||
id={layer.key}
|
||||
layerId={layer.id}
|
||||
layerIndex={idx}
|
||||
layerType={layer.type}
|
||||
visibility={(layer.layout || {}).visibility}
|
||||
isSelected={idx === this.props.selectedLayerIndex}
|
||||
onLayerSelect={this.props.onLayerSelect}
|
||||
onLayerDestroy={this.props.onLayerDestroy.bind(this)}
|
||||
onLayerCopy={this.props.onLayerCopy.bind(this)}
|
||||
onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(
|
||||
this
|
||||
)}
|
||||
{...additionalProps}
|
||||
/>
|
||||
);
|
||||
listItems.push(listItem);
|
||||
idx += 1;
|
||||
});
|
||||
});
|
||||
|
||||
return <section
|
||||
className="maputnik-layer-list"
|
||||
role="complementary"
|
||||
aria-label="Layers list"
|
||||
ref={this.scrollContainerRef}
|
||||
>
|
||||
<ModalAdd
|
||||
return (
|
||||
<section
|
||||
className="maputnik-layer-list"
|
||||
role="complementary"
|
||||
aria-label="Layers list"
|
||||
ref={this.scrollContainerRef}
|
||||
>
|
||||
<ModalAdd
|
||||
key={this.state.keys.add}
|
||||
layers={this.props.layers}
|
||||
sources={this.props.sources}
|
||||
isOpen={this.state.isOpen.add}
|
||||
onOpenToggle={this.toggleModal.bind(this, 'add')}
|
||||
onOpenToggle={this.toggleModal.bind(this, "add")}
|
||||
onLayersChange={this.props.onLayersChange}
|
||||
/>
|
||||
<header className="maputnik-layer-list-header">
|
||||
<span className="maputnik-layer-list-header-title">Layers</span>
|
||||
<span className="maputnik-space" />
|
||||
<div className="maputnik-default-property">
|
||||
<div className="maputnik-multibutton">
|
||||
<button
|
||||
id="skip-target-layer-list"
|
||||
onClick={this.toggleLayers}
|
||||
className="maputnik-button">
|
||||
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
|
||||
</button>
|
||||
/>
|
||||
<header className="maputnik-layer-list-header">
|
||||
<span className="maputnik-layer-list-header-title">Layers</span>
|
||||
<span className="maputnik-space" />
|
||||
<div className="maputnik-default-property">
|
||||
<div className="maputnik-multibutton">
|
||||
<button
|
||||
id="skip-target-layer-list"
|
||||
data-wd-key="skip-target-layer-list"
|
||||
onClick={this.toggleLayers}
|
||||
className="maputnik-button"
|
||||
>
|
||||
{this.state.areAllGroupsExpanded === true
|
||||
? "Collapse"
|
||||
: "Expand"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="maputnik-default-property">
|
||||
<div className="maputnik-multibutton">
|
||||
<button
|
||||
onClick={this.toggleModal.bind(this, 'add')}
|
||||
data-wd-key="layer-list:add-layer"
|
||||
className="maputnik-button maputnik-button-selected">
|
||||
Add Layer
|
||||
</button>
|
||||
<div className="maputnik-default-property">
|
||||
<div className="maputnik-multibutton">
|
||||
<button
|
||||
onClick={this.toggleModal.bind(this, "add")}
|
||||
data-wd-key="layer-list:add-layer"
|
||||
className="maputnik-button maputnik-button-selected"
|
||||
>
|
||||
Add Layer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div role="navigation" aria-label="Layers list">
|
||||
<ul className="maputnik-layer-list-container">{listItems}</ul>
|
||||
</div>
|
||||
</header>
|
||||
<div
|
||||
role="navigation"
|
||||
aria-label="Layers list"
|
||||
>
|
||||
<ul className="maputnik-layer-list-container">
|
||||
{listItems}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const LayerListContainerSortable = SortableContainer((props) => <LayerListContainer {...props} />)
|
||||
const LayerListContainerSortable = SortableContainer((props) => (
|
||||
<LayerListContainer {...props} />
|
||||
));
|
||||
|
||||
export default class LayerList extends React.Component {
|
||||
static propTypes = {...layerListPropTypes}
|
||||
static propTypes = { ...layerListPropTypes };
|
||||
|
||||
render() {
|
||||
return <LayerListContainerSortable
|
||||
{...this.props}
|
||||
helperClass='sortableHelper'
|
||||
onSortEnd={this.props.onMoveLayer.bind(this)}
|
||||
useDragHandle={true}
|
||||
shouldCancelStart={() => false}
|
||||
/>
|
||||
return (
|
||||
<LayerListContainerSortable
|
||||
{...this.props}
|
||||
helperClass="sortableHelper"
|
||||
onSortEnd={this.props.onMoveLayer.bind(this)}
|
||||
useDragHandle={true}
|
||||
shouldCancelStart={() => false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ReactDOM from 'react-dom'
|
||||
import MapLibreGl from 'maplibre-gl'
|
||||
import MapboxInspect from 'mapbox-gl-inspect'
|
||||
import MapMapboxGlLayerPopup from './MapMapboxGlLayerPopup'
|
||||
import MapMapboxGlFeaturePropertyPopup from './MapMapboxGlFeaturePropertyPopup'
|
||||
import tokens from '../config/tokens.json'
|
||||
import colors from 'mapbox-gl-inspect/lib/colors'
|
||||
import Color from 'color'
|
||||
import ZoomControl from '../libs/zoomcontrol'
|
||||
import { colorHighlightedLayer } from '../libs/highlight'
|
||||
import 'maplibre-gl/dist/maplibre-gl.css'
|
||||
import '../mapboxgl.css'
|
||||
import '../libs/mapbox-rtl'
|
||||
|
||||
|
||||
const IS_SUPPORTED = MapLibreGl.supported();
|
||||
|
||||
function renderPopup(popup, mountNode) {
|
||||
ReactDOM.render(popup, mountNode);
|
||||
return mountNode;
|
||||
}
|
||||
|
||||
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
|
||||
const backgroundLayer = {
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": '#1c1f24',
|
||||
}
|
||||
}
|
||||
|
||||
const layer = colorHighlightedLayer(highlightedLayer)
|
||||
if(layer) {
|
||||
coloredLayers.push(layer)
|
||||
}
|
||||
|
||||
const sources = {}
|
||||
Object.keys(originalMapStyle.sources).forEach(sourceId => {
|
||||
const source = originalMapStyle.sources[sourceId]
|
||||
if(source.type !== 'raster' && source.type !== 'raster-dem') {
|
||||
sources[sourceId] = source
|
||||
}
|
||||
})
|
||||
|
||||
const inspectStyle = {
|
||||
...originalMapStyle,
|
||||
sources: sources,
|
||||
layers: [backgroundLayer].concat(coloredLayers)
|
||||
}
|
||||
return inspectStyle
|
||||
}
|
||||
|
||||
export default class MapMapboxGl extends React.Component {
|
||||
static propTypes = {
|
||||
onDataChange: PropTypes.func,
|
||||
onLayerSelect: PropTypes.func.isRequired,
|
||||
mapStyle: PropTypes.object.isRequired,
|
||||
inspectModeEnabled: PropTypes.bool.isRequired,
|
||||
highlightedLayer: PropTypes.object,
|
||||
options: PropTypes.object,
|
||||
replaceAccessTokens: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onMapLoaded: () => {},
|
||||
onDataChange: () => {},
|
||||
onLayerSelect: () => {},
|
||||
onChange: () => {},
|
||||
mapboxAccessToken: tokens.mapbox,
|
||||
options: {},
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
map: null,
|
||||
inspect: null,
|
||||
}
|
||||
}
|
||||
|
||||
updateMapFromProps(props) {
|
||||
if(!IS_SUPPORTED) return;
|
||||
|
||||
if(!this.state.map) return
|
||||
|
||||
//Mapbox 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, nextState) {
|
||||
let should = false;
|
||||
try {
|
||||
should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState);
|
||||
} catch(e) {
|
||||
// no biggie, carry on
|
||||
}
|
||||
return should;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if(!IS_SUPPORTED) return;
|
||||
|
||||
const map = this.state.map;
|
||||
|
||||
this.updateMapFromProps(this.props);
|
||||
|
||||
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 (this.props.inspectModeEnabled) {
|
||||
// HACK: We need to work out why we need to do this and what's causing
|
||||
// this error. I'm assuming an issue with mapbox-gl update and
|
||||
// mapbox-gl-inspect.
|
||||
try {
|
||||
this.state.inspect.render();
|
||||
} catch(err) {
|
||||
console.error("FIXME: Caught error", err);
|
||||
}
|
||||
}
|
||||
|
||||
map.showTileBoundaries = this.props.options.showTileBoundaries;
|
||||
map.showCollisionBoxes = this.props.options.showCollisionBoxes;
|
||||
map.showOverdrawInspector = this.props.options.showOverdrawInspector;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if(!IS_SUPPORTED) return;
|
||||
|
||||
const mapOpts = {
|
||||
...this.props.options,
|
||||
container: this.container,
|
||||
style: this.props.mapStyle,
|
||||
hash: true,
|
||||
maxZoom: 24
|
||||
}
|
||||
|
||||
const map = new MapLibreGl.Map(mapOpts);
|
||||
|
||||
const mapViewChange = () => {
|
||||
const center = map.getCenter();
|
||||
const zoom = map.getZoom();
|
||||
this.props.onChange({center, zoom});
|
||||
}
|
||||
mapViewChange();
|
||||
|
||||
map.showTileBoundaries = mapOpts.showTileBoundaries;
|
||||
map.showCollisionBoxes = mapOpts.showCollisionBoxes;
|
||||
map.showOverdrawInspector = mapOpts.showOverdrawInspector;
|
||||
|
||||
const zoomControl = new ZoomControl;
|
||||
map.addControl(zoomControl, 'top-right');
|
||||
|
||||
const nav = new MapLibreGl.NavigationControl({visualizePitch:true});
|
||||
map.addControl(nav, 'top-right');
|
||||
|
||||
const tmpNode = document.createElement('div');
|
||||
|
||||
const inspect = new MapboxInspect({
|
||||
popup: new MapLibreGl.Popup({
|
||||
closeOnClick: false
|
||||
}),
|
||||
showMapPopup: true,
|
||||
showMapPopupOnHover: false,
|
||||
showInspectMapPopupOnHover: true,
|
||||
showInspectButton: false,
|
||||
blockHoverPopupOnClick: true,
|
||||
assignLayerColor: (layerId, alpha) => {
|
||||
return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string()
|
||||
},
|
||||
buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
|
||||
renderPopup: features => {
|
||||
if(this.props.inspectModeEnabled) {
|
||||
return renderPopup(<MapMapboxGlFeaturePropertyPopup features={features} />, tmpNode);
|
||||
} else {
|
||||
return renderPopup(<MapMapboxGlLayerPopup features={features} onLayerSelect={this.onLayerSelectById} zoom={this.state.zoom} />, tmpNode);
|
||||
}
|
||||
}
|
||||
})
|
||||
map.addControl(inspect)
|
||||
|
||||
map.on("style.load", () => {
|
||||
this.setState({
|
||||
map,
|
||||
inspect,
|
||||
zoom: map.getZoom()
|
||||
});
|
||||
})
|
||||
|
||||
map.on("data", e => {
|
||||
if(e.dataType !== 'tile') return
|
||||
this.props.onDataChange({
|
||||
map: this.state.map
|
||||
})
|
||||
})
|
||||
|
||||
map.on("error", e => {
|
||||
console.log("ERROR", e);
|
||||
})
|
||||
|
||||
map.on("zoom", e => {
|
||||
this.setState({
|
||||
zoom: map.getZoom()
|
||||
});
|
||||
});
|
||||
|
||||
map.on("dragend", mapViewChange);
|
||||
map.on("zoomend", mapViewChange);
|
||||
}
|
||||
|
||||
onLayerSelectById = (id) => {
|
||||
const index = this.props.mapStyle.layers.findIndex(layer => layer.id === id);
|
||||
this.props.onLayerSelect(index);
|
||||
}
|
||||
|
||||
render() {
|
||||
if(IS_SUPPORTED) {
|
||||
return <div
|
||||
className="maputnik-map__map"
|
||||
role="region"
|
||||
aria-label="Map view"
|
||||
ref={x => this.container = x}
|
||||
></div>
|
||||
}
|
||||
else {
|
||||
return <div
|
||||
className="maputnik-map maputnik-map--error"
|
||||
>
|
||||
<div className="maputnik-map__error-message">
|
||||
Error: Cannot load MapboxGL, WebGL is either unsupported or disabled
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
import Color from "color";
|
||||
import MapboxInspect from "mapbox-gl-inspect";
|
||||
import colors from "mapbox-gl-inspect/lib/colors";
|
||||
import MapLibreGl from "maplibre-gl";
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
import PropTypes from "prop-types";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { colorHighlightedLayer } from "../libs/highlight";
|
||||
import "../libs/maplibre-rtl";
|
||||
import ZoomControl from "../libs/zoomcontrol";
|
||||
import "../maplibregl.css";
|
||||
import MapMaplibreGlFeaturePropertyPopup from "./MapMaplibreGlFeaturePropertyPopup";
|
||||
import MapMaplibreGlLayerPopup from "./MapMaplibreGlLayerPopup";
|
||||
|
||||
const IS_SUPPORTED = MapLibreGl.supported();
|
||||
|
||||
function renderPopup(popup, mountNode) {
|
||||
ReactDOM.render(popup, mountNode);
|
||||
return mountNode;
|
||||
}
|
||||
|
||||
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
|
||||
const backgroundLayer = {
|
||||
id: "background",
|
||||
type: "background",
|
||||
paint: {
|
||||
"background-color": "#1c1f24",
|
||||
},
|
||||
};
|
||||
|
||||
const layer = colorHighlightedLayer(highlightedLayer);
|
||||
if (layer) {
|
||||
coloredLayers.push(layer);
|
||||
}
|
||||
|
||||
const sources = {};
|
||||
Object.keys(originalMapStyle.sources).forEach((sourceId) => {
|
||||
const source = originalMapStyle.sources[sourceId];
|
||||
if (source.type !== "raster" && source.type !== "raster-dem") {
|
||||
sources[sourceId] = source;
|
||||
}
|
||||
});
|
||||
|
||||
const inspectStyle = {
|
||||
...originalMapStyle,
|
||||
sources: sources,
|
||||
layers: [backgroundLayer].concat(coloredLayers),
|
||||
};
|
||||
return inspectStyle;
|
||||
}
|
||||
|
||||
export default class MapMaplibreGl extends React.Component {
|
||||
static propTypes = {
|
||||
onDataChange: PropTypes.func,
|
||||
onLayerSelect: PropTypes.func.isRequired,
|
||||
mapStyle: PropTypes.object.isRequired,
|
||||
inspectModeEnabled: PropTypes.bool.isRequired,
|
||||
highlightedLayer: PropTypes.object,
|
||||
options: PropTypes.object,
|
||||
replaceAccessTokens: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onMapLoaded: () => {},
|
||||
onDataChange: () => {},
|
||||
onLayerSelect: () => {},
|
||||
onChange: () => {},
|
||||
options: {},
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
map: null,
|
||||
inspect: null,
|
||||
};
|
||||
}
|
||||
|
||||
updateMapFromProps(props) {
|
||||
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, nextState) {
|
||||
let should = false;
|
||||
try {
|
||||
should =
|
||||
JSON.stringify(this.props) !== JSON.stringify(nextProps) ||
|
||||
JSON.stringify(this.state) !== JSON.stringify(nextState);
|
||||
} catch (e) {
|
||||
// no biggie, carry on
|
||||
}
|
||||
return should;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (!IS_SUPPORTED) return;
|
||||
|
||||
const map = this.state.map;
|
||||
|
||||
this.updateMapFromProps(this.props);
|
||||
|
||||
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 (this.props.inspectModeEnabled) {
|
||||
// HACK: We need to work out why we need to do this and what's causing
|
||||
// this error. I'm assuming an issue with maplibre-gl update and
|
||||
// mapbox-gl-inspect.
|
||||
try {
|
||||
this.state.inspect.render();
|
||||
} catch (err) {
|
||||
console.error("FIXME: Caught error", err);
|
||||
}
|
||||
}
|
||||
|
||||
map.showTileBoundaries = this.props.options.showTileBoundaries;
|
||||
map.showCollisionBoxes = this.props.options.showCollisionBoxes;
|
||||
map.showOverdrawInspector = this.props.options.showOverdrawInspector;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!IS_SUPPORTED) return;
|
||||
|
||||
const mapOpts = {
|
||||
...this.props.options,
|
||||
container: this.container,
|
||||
style: this.props.mapStyle,
|
||||
hash: true,
|
||||
maxZoom: 24,
|
||||
};
|
||||
|
||||
const map = new MapLibreGl.Map(mapOpts);
|
||||
|
||||
const mapViewChange = () => {
|
||||
const center = map.getCenter();
|
||||
const zoom = map.getZoom();
|
||||
this.props.onChange({ center, zoom });
|
||||
};
|
||||
mapViewChange();
|
||||
|
||||
map.showTileBoundaries = mapOpts.showTileBoundaries;
|
||||
map.showCollisionBoxes = mapOpts.showCollisionBoxes;
|
||||
map.showOverdrawInspector = mapOpts.showOverdrawInspector;
|
||||
|
||||
const zoomControl = new ZoomControl();
|
||||
map.addControl(zoomControl, "top-right");
|
||||
|
||||
const nav = new MapLibreGl.NavigationControl({ visualizePitch: true });
|
||||
map.addControl(nav, "top-right");
|
||||
|
||||
const tmpNode = document.createElement("div");
|
||||
|
||||
const inspect = new MapboxInspect({
|
||||
popup: new MapLibreGl.Popup({
|
||||
closeOnClick: false,
|
||||
}),
|
||||
showMapPopup: true,
|
||||
showMapPopupOnHover: false,
|
||||
showInspectMapPopupOnHover: true,
|
||||
showInspectButton: false,
|
||||
blockHoverPopupOnClick: true,
|
||||
assignLayerColor: (layerId, alpha) => {
|
||||
return Color(colors.brightColor(layerId, alpha))
|
||||
.desaturate(0.5)
|
||||
.string();
|
||||
},
|
||||
buildInspectStyle: (originalMapStyle, coloredLayers) =>
|
||||
buildInspectStyle(
|
||||
originalMapStyle,
|
||||
coloredLayers,
|
||||
this.props.highlightedLayer
|
||||
),
|
||||
renderPopup: (features) => {
|
||||
if (this.props.inspectModeEnabled) {
|
||||
return renderPopup(
|
||||
<MapMaplibreGlFeaturePropertyPopup features={features} />,
|
||||
tmpNode
|
||||
);
|
||||
} else {
|
||||
return renderPopup(
|
||||
<MapMaplibreGlLayerPopup
|
||||
features={features}
|
||||
onLayerSelect={this.onLayerSelectById}
|
||||
zoom={this.state.zoom}
|
||||
/>,
|
||||
tmpNode
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
map.addControl(inspect);
|
||||
|
||||
map.on("style.load", () => {
|
||||
this.setState({
|
||||
map,
|
||||
inspect,
|
||||
zoom: map.getZoom(),
|
||||
});
|
||||
});
|
||||
|
||||
map.on("data", (e) => {
|
||||
if (e.dataType !== "tile") return;
|
||||
this.props.onDataChange({
|
||||
map: this.state.map,
|
||||
});
|
||||
});
|
||||
|
||||
map.on("error", (e) => {
|
||||
console.log("ERROR", e);
|
||||
});
|
||||
|
||||
map.on("zoom", (e) => {
|
||||
this.setState({
|
||||
zoom: map.getZoom(),
|
||||
});
|
||||
});
|
||||
|
||||
map.on("dragend", mapViewChange);
|
||||
map.on("zoomend", mapViewChange);
|
||||
}
|
||||
|
||||
onLayerSelectById = (id) => {
|
||||
const index = this.props.mapStyle.layers.findIndex(
|
||||
(layer) => layer.id === id
|
||||
);
|
||||
this.props.onLayerSelect(index);
|
||||
};
|
||||
|
||||
render() {
|
||||
if (IS_SUPPORTED) {
|
||||
return (
|
||||
<div
|
||||
className="maputnik-map__map"
|
||||
role="region"
|
||||
aria-label="Map view"
|
||||
ref={(x) => (this.container = x)}
|
||||
data-wd-key="maplibre:map"
|
||||
></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,13 +1,12 @@
|
||||
import React from 'react'
|
||||
import {throttle} from 'lodash';
|
||||
import PropTypes from 'prop-types'
|
||||
import { loadJSON } from '../libs/urlopen'
|
||||
|
||||
import MapMapboxGlLayerPopup from './MapMapboxGlLayerPopup';
|
||||
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup';
|
||||
|
||||
import 'ol/ol.css'
|
||||
import {apply} from 'ol-mapbox-style';
|
||||
import {Map, View, Proj, Overlay} from 'ol';
|
||||
import {Map, View, Overlay} from 'ol';
|
||||
|
||||
import {toLonLat} from 'ol/proj';
|
||||
import {toStringHDMS} from 'ol/coordinate';
|
||||
@@ -146,13 +145,13 @@ export default class MapOpenLayers extends React.Component {
|
||||
className="maputnik-popup"
|
||||
>
|
||||
<button
|
||||
className="mapboxgl-popup-close-button"
|
||||
className="maplibregl-popup-close-button"
|
||||
onClick={this.closeOverlay}
|
||||
aria-label="Close popup"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<MapMapboxGlLayerPopup
|
||||
<MapMaplibreGlLayerPopup
|
||||
features={this.state.selectedFeatures || []}
|
||||
onLayerSelect={this.props.onLayerSelect}
|
||||
/>
|
||||
|
||||
@@ -27,9 +27,9 @@ export default class Modal extends React.Component {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
|
||||
setImmediate(() => {
|
||||
setTimeout(() => {
|
||||
this.props.onOpenToggle(false);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -11,7 +11,7 @@ export default class ModalDebug extends React.Component {
|
||||
onChangeMaboxGlDebug: PropTypes.func.isRequired,
|
||||
onChangeOpenlayersDebug: PropTypes.func.isRequired,
|
||||
onOpenToggle: PropTypes.func.isRequired,
|
||||
mapboxGlDebugOptions: PropTypes.object,
|
||||
maplibreGlDebugOptions: PropTypes.object,
|
||||
openlayersDebugOptions: PropTypes.object,
|
||||
mapView: PropTypes.object,
|
||||
}
|
||||
@@ -31,9 +31,9 @@ export default class ModalDebug extends React.Component {
|
||||
>
|
||||
<section className="maputnik-modal-section maputnik-modal-shortcuts">
|
||||
<h1>Options</h1>
|
||||
{this.props.renderer === 'mbgljs' &&
|
||||
{this.props.renderer === 'mlgljs' &&
|
||||
<ul>
|
||||
{Object.entries(this.props.mapboxGlDebugOptions).map(([key, val]) => {
|
||||
{Object.entries(this.props.maplibreGlDebugOptions).map(([key, val]) => {
|
||||
return <li key={key}>
|
||||
<label>
|
||||
<input type="checkbox" checked={val} onClick={(e) => this.props.onChangeMaboxGlDebug(key, e.target.checked)} /> {key}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Slugify from 'slugify'
|
||||
import { saveAs } from 'file-saver'
|
||||
import pkgLockJson from '../../package-lock.json'
|
||||
|
||||
import {saveAs} from 'file-saver'
|
||||
import {version} from 'maplibre-gl'
|
||||
import {format} from '@maplibre/maplibre-gl-style-spec'
|
||||
import FieldString from './FieldString'
|
||||
import FieldCheckbox from './FieldCheckbox'
|
||||
import InputButton from './InputButton'
|
||||
import Modal from './Modal'
|
||||
import {MdFileDownload} from 'react-icons/md'
|
||||
@@ -14,7 +12,7 @@ import style from '../libs/style'
|
||||
import fieldSpecAdditional from '../libs/field-spec-additional'
|
||||
|
||||
|
||||
const MAPBOX_GL_VERSION = pkgLockJson.dependencies["mapbox-gl"].version;
|
||||
const MAPLIBRE_GL_VERSION = version;
|
||||
|
||||
|
||||
export default class ModalExport extends React.Component {
|
||||
@@ -58,8 +56,8 @@ export default class ModalExport extends React.Component {
|
||||
<meta charset="utf-8" />
|
||||
<title>${htmlTitle}</title>
|
||||
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
|
||||
<script src="https://api.mapbox.com/mapbox-gl-js/v${MAPBOX_GL_VERSION}/mapbox-gl.js"></script>
|
||||
<link href="https://api.mapbox.com/mapbox-gl-js/v${MAPBOX_GL_VERSION}/mapbox-gl.css" rel="stylesheet" />
|
||||
<script src="https://unpkg.com/maplibre-gl@${MAPLIBRE_GL_VERSION}/dist/maplibre-gl.js"></script>
|
||||
<link href="https://unpkg.com/maplibre-gl@${MAPLIBRE_GL_VERSION}/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
<style>
|
||||
body { margin: 0; padding: 0; }
|
||||
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
|
||||
@@ -68,12 +66,11 @@ export default class ModalExport extends React.Component {
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<script>
|
||||
mapboxgl.accessToken = 'access_token';
|
||||
const map = new mapboxgl.Map({
|
||||
const map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: ${tokenStyle},
|
||||
});
|
||||
map.addControl(new mapboxgl.NavigationControl());
|
||||
map.addControl(new maplibregl.NavigationControl());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -119,12 +116,6 @@ export default class ModalExport extends React.Component {
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<FieldString
|
||||
label={fieldSpecAdditional.maputnik.mapbox_access_token.label}
|
||||
fieldSpec={fieldSpecAdditional.maputnik.mapbox_access_token}
|
||||
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
|
||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
|
||||
/>
|
||||
<FieldString
|
||||
label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
|
||||
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
|
||||
|
||||
@@ -117,14 +117,6 @@ export default class ModalSettings extends React.Component {
|
||||
onChange={this.changeStyleProperty.bind(this, "glyphs")}
|
||||
/>
|
||||
|
||||
<FieldString {...inputProps}
|
||||
label={fieldSpecAdditional.maputnik.mapbox_access_token.label}
|
||||
fieldSpec={fieldSpecAdditional.maputnik.mapbox_access_token}
|
||||
data-wd-key="modal:settings.maputnik:mapbox_access_token"
|
||||
value={metadata['maputnik:mapbox_access_token']}
|
||||
onChange={onChangeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
|
||||
/>
|
||||
|
||||
<FieldString {...inputProps}
|
||||
label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
|
||||
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
|
||||
@@ -241,10 +233,10 @@ export default class ModalSettings extends React.Component {
|
||||
fieldSpec={fieldSpecAdditional.maputnik.style_renderer}
|
||||
data-wd-key="modal:settings.maputnik:renderer"
|
||||
options={[
|
||||
['mbgljs', 'MapboxGL JS'],
|
||||
['mlgljs', 'MapLibreGL JS'],
|
||||
['ol', 'Open Layers (experimental)'],
|
||||
]}
|
||||
value={metadata['maputnik:renderer'] || 'mbgljs'}
|
||||
value={metadata['maputnik:renderer'] || 'mlgljs'}
|
||||
onChange={onChangeMetadataProperty.bind(this, 'maputnik:renderer')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -128,6 +128,7 @@ class AddSource extends React.Component {
|
||||
}
|
||||
case 'geojson_json': return {
|
||||
type: 'geojson',
|
||||
cluster: source.cluster || false,
|
||||
data: {}
|
||||
}
|
||||
case 'tilejson_vector': return {
|
||||
|
||||
@@ -8,6 +8,7 @@ import FieldSelect from './FieldSelect'
|
||||
import FieldDynamicArray from './FieldDynamicArray'
|
||||
import FieldArray from './FieldArray'
|
||||
import FieldJson from './FieldJson'
|
||||
import FieldCheckbox from './FieldCheckbox'
|
||||
|
||||
|
||||
class TileJSONSourceEditor extends React.Component {
|
||||
@@ -80,7 +81,7 @@ class TileURLSourceEditor extends React.Component {
|
||||
})}
|
||||
/>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
}
|
||||
@@ -205,23 +206,35 @@ class GeoJSONSourceFieldJsonEditor extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Block label={"GeoJSON"} fieldSpec={latest.source_geojson.data}>
|
||||
<FieldJson
|
||||
layer={this.props.source.data}
|
||||
maxHeight={200}
|
||||
mode={{
|
||||
name: "javascript",
|
||||
json: true
|
||||
}}
|
||||
lint={true}
|
||||
onChange={data => {
|
||||
return <div>
|
||||
<Block label={"GeoJSON"} fieldSpec={latest.source_geojson.data}>
|
||||
<FieldJson
|
||||
layer={this.props.source.data}
|
||||
maxHeight={200}
|
||||
mode={{
|
||||
name: "javascript",
|
||||
json: true
|
||||
}}
|
||||
lint={true}
|
||||
onChange={data => {
|
||||
this.props.onChange({
|
||||
...this.props.source,
|
||||
data,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Block>
|
||||
<FieldCheckbox
|
||||
label={'Cluster'}
|
||||
value={this.props.source.cluster}
|
||||
onChange={cluster => {
|
||||
this.props.onChange({
|
||||
...this.props.source,
|
||||
data,
|
||||
cluster: cluster,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Block>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import Fieldset from './Fieldset'
|
||||
const typeMap = {
|
||||
color: () => Block,
|
||||
enum: ({fieldSpec}) => (Object.keys(fieldSpec.values).length <= 3 ? Fieldset : Block),
|
||||
number: () => Block,
|
||||
boolean: () => Block,
|
||||
array: () => Fieldset,
|
||||
resolvedImage: () => Block,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"mapbox": "pk.eyJ1IjoibW9yZ2Vua2FmZmVlIiwiYSI6ImNpeHJmNXNmZTAwNHIycXBid2NqdTJibjMifQ.Dv1-GDpTWi0NP6xW9Fct1w",
|
||||
"openmaptiles": "KDhMfHvorAFkFe64wlZb",
|
||||
"thunderforest": "b71f7f0ba4064f5eb9e903859a9cf5c6"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
const spec = {
|
||||
maputnik: {
|
||||
mapbox_access_token: {
|
||||
label: "Mapbox Access Token",
|
||||
doc: "Public access token for Mapbox services."
|
||||
},
|
||||
maptiler_access_token: {
|
||||
label: "MapTiler Access Token",
|
||||
doc: "Public access token for MapTiler Cloud."
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export function changeProperty(layer, group, property, newValue) {
|
||||
if(group) {
|
||||
const newLayer = {
|
||||
...layer,
|
||||
// Change object so the diff works in ./src/components/map/MapboxGlMap.jsx
|
||||
// Change object so the diff works in ./src/components/map/MaplibreGlMap.jsx
|
||||
[group]: {
|
||||
...layer[group]
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ export default class LayerWatcher {
|
||||
const previousSources = { ...this._sources }
|
||||
|
||||
Object.keys(map.style.sourceCaches).forEach(sourceId => {
|
||||
//NOTE: This heavily depends on the internal API of Mapbox GL
|
||||
//so this breaks between Mapbox GL JS releases
|
||||
//NOTE: This heavily depends on the internal API of Maplibre GL
|
||||
//so this breaks between Maplibre GL JS releases
|
||||
this._sources[sourceId] = map.style.sourceCaches[sourceId]._source.vectorLayerIds
|
||||
})
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import MapLibreGl from "maplibre-gl"
|
||||
import {readFileSync} from 'fs'
|
||||
|
||||
const data = readFileSync(__dirname+"/../../node_modules/@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.js", "utf8");
|
||||
|
||||
const blob = new window.Blob([data], {
|
||||
type: "text/javascript"
|
||||
});
|
||||
const objectUrl = window.URL.createObjectURL(blob);
|
||||
|
||||
MapLibreGl.setRTLTextPlugin(objectUrl, () => {});
|
||||
@@ -0,0 +1,3 @@
|
||||
import MapLibreGl from "maplibre-gl"
|
||||
|
||||
MapLibreGl.setRTLTextPlugin('https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.min.js');
|
||||
@@ -116,7 +116,6 @@ function stripAccessTokens(mapStyle) {
|
||||
const changedMetadata = {
|
||||
...mapStyle.metadata
|
||||
};
|
||||
delete changedMetadata['maputnik:mapbox_access_token'];
|
||||
delete changedMetadata['maputnik:openmaptiles_access_token'];
|
||||
return {
|
||||
...mapStyle,
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import url from 'url'
|
||||
import querystring from 'querystring'
|
||||
import style from './style.js'
|
||||
|
||||
export function initialStyleUrl() {
|
||||
const initialUrl = url.parse(window.location.href, true)
|
||||
return (initialUrl.query || {}).style
|
||||
}
|
||||
|
||||
export function loadStyleUrl(styleUrl, cb) {
|
||||
console.log('Loading style', styleUrl)
|
||||
fetch(styleUrl, {
|
||||
mode: 'cors',
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(body) {
|
||||
cb(style.ensureStyleValidity(body))
|
||||
})
|
||||
.catch(function() {
|
||||
console.warn('Could not fetch default style', styleUrl)
|
||||
cb(style.emptyStyle)
|
||||
})
|
||||
}
|
||||
|
||||
export function removeStyleQuerystring() {
|
||||
const initialUrl = url.parse(window.location.href, true)
|
||||
let qs = querystring.parse(window.location.search.slice(1))
|
||||
delete qs["style"]
|
||||
if(Object.getOwnPropertyNames(qs).length === 0) {
|
||||
qs = ""
|
||||
} else {
|
||||
qs = "?" + querystring.stringify(qs)
|
||||
}
|
||||
let newUrlHash = initialUrl.hash
|
||||
if(newUrlHash === null) {
|
||||
newUrlHash = ""
|
||||
}
|
||||
const newUrl = initialUrl.protocol + "//" + initialUrl.host + initialUrl.pathname + qs + newUrlHash
|
||||
window.history.replaceState({}, document.title, newUrl)
|
||||
}
|
||||
|
||||
export function loadJSON(url, defaultValue, cb) {
|
||||
fetch(url, {
|
||||
mode: 'cors',
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(body) {
|
||||
try {
|
||||
cb(body)
|
||||
} catch(err) {
|
||||
console.error(err)
|
||||
cb(defaultValue)
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
console.error('Can not load JSON from ' + url)
|
||||
cb(defaultValue)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// @ts-ignore
|
||||
import style from './style'
|
||||
|
||||
export function initialStyleUrl() {
|
||||
const initialUrl = new URL(window.location.href);
|
||||
return initialUrl.searchParams.get('style');
|
||||
}
|
||||
|
||||
export function loadStyleUrl(styleUrl: string, cb: (...args: any[]) => void) {
|
||||
console.log('Loading style', styleUrl)
|
||||
fetch(styleUrl, {
|
||||
mode: 'cors',
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(body) {
|
||||
cb(style.ensureStyleValidity(body))
|
||||
})
|
||||
.catch(function() {
|
||||
console.warn('Could not fetch default style', styleUrl)
|
||||
cb(style.emptyStyle)
|
||||
})
|
||||
}
|
||||
|
||||
export function removeStyleQuerystring() {
|
||||
const initialUrl = new URL(window.location.href);
|
||||
initialUrl.searchParams.delete('style');
|
||||
window.history.replaceState({}, document.title, initialUrl.toString())
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
export default class ZoomControl {
|
||||
onAdd(map) {
|
||||
this._map = map;
|
||||
this._container = document.createElement('div');
|
||||
this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group mapboxgl-ctrl-zoom';
|
||||
this._container = document.createElement("div");
|
||||
this._container.className =
|
||||
"maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-zoom";
|
||||
this._container.setAttribute("data-wd-key", "maplibre:ctrl-zoom");
|
||||
this._container.innerHTML = `
|
||||
Zoom: <span></span>
|
||||
`;
|
||||
@@ -17,10 +19,10 @@ export default class ZoomControl {
|
||||
this._textEl.innerHTML = this._map.getZoom().toFixed(2);
|
||||
}
|
||||
|
||||
addEventListeners (){
|
||||
this._map.on('render', this.updateZoomLevel.bind(this) );
|
||||
this._map.on('zoomIn', this.updateZoomLevel.bind(this) );
|
||||
this._map.on('zoomOut', this.updateZoomLevel.bind(this) );
|
||||
addEventListeners() {
|
||||
this._map.on("render", this.updateZoomLevel.bind(this));
|
||||
this._map.on("zoomIn", this.updateZoomLevel.bind(this));
|
||||
this._map.on("zoomOut", this.updateZoomLevel.bind(this));
|
||||
}
|
||||
|
||||
onRemove() {
|
||||
|
||||
@@ -1,93 +1,91 @@
|
||||
/* TODO */
|
||||
|
||||
.mapboxgl-popup-anchor-top .mapboxgl-popup-tip {
|
||||
.maplibregl-popup-anchor-top .maplibregl-popup-tip {
|
||||
border-bottom-color: rgb(28, 31, 36);
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
|
||||
.maplibregl-popup-anchor-left .maplibregl-popup-tip {
|
||||
border-right-color: rgb(28, 31, 36);
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
|
||||
.maplibregl-popup-anchor-right .maplibregl-popup-tip {
|
||||
border-left-color: rgb(28, 31, 36);
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {
|
||||
.maplibregl-popup-anchor-bottom .maplibregl-popup-tip {
|
||||
border-top-color: rgb(28, 31, 36);
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip {
|
||||
.maplibregl-popup-anchor-top-left .maplibregl-popup-tip {
|
||||
border-bottom-color: rgb(28, 31, 36);
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip {
|
||||
.maplibregl-popup-anchor-top-right .maplibregl-popup-tip {
|
||||
border-bottom-color: rgb(28, 31, 36);
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip {
|
||||
.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip {
|
||||
border-top-color: rgb(28, 31, 36);
|
||||
}
|
||||
|
||||
.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip {
|
||||
.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip {
|
||||
border-top-color: rgb(28, 31, 36);
|
||||
}
|
||||
|
||||
.mapboxgl-popup-content {
|
||||
.maplibregl-popup-content {
|
||||
background-color: rgb(28, 31, 36);
|
||||
box-shadow: rgba(0, 0, 0, 0.298039) 0px 0px 5px 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.mapboxgl-popup-close-button {
|
||||
.maplibregl-popup-close-button {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-zoom {
|
||||
.maplibregl-ctrl-zoom {
|
||||
color: #a4a4a4;
|
||||
font-weight: bold;
|
||||
padding: 4px 8px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group {
|
||||
.maplibregl-ctrl-group {
|
||||
background: rgb(28, 31, 36);
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-group > button {
|
||||
.maplibregl-ctrl-group > button {
|
||||
background-color: rgb(28, 31, 36);
|
||||
border-color: rgb(28, 31, 36);
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button:not(:disabled):hover {
|
||||
.maplibregl-ctrl button:not(:disabled):hover {
|
||||
background-color: rgb(86, 83, 83);
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-zoom-in {
|
||||
.maplibregl-ctrl-zoom-in {
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-compass {
|
||||
.maplibregl-ctrl-compass {
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon {
|
||||
.maplibregl-ctrl button.maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23a4a4a4%3B%27%20d%3D%27M%2010%206%20C%209.446%206%209%206.4459904%209%207%20L%209%209%20L%207%209%20C%206.446%209%206%209.446%206%2010%20C%206%2010.554%206.446%2011%207%2011%20L%209%2011%20L%209%2013%20C%209%2013.55401%209.446%2014%2010%2014%20C%2010.554%2014%2011%2013.55401%2011%2013%20L%2011%2011%20L%2013%2011%20C%2013.554%2011%2014%2010.554%2014%2010%20C%2014%209.446%2013.554%209%2013%209%20L%2011%209%20L%2011%207%20C%2011%206.4459904%2010.554%206%2010%206%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon {
|
||||
.maplibregl-ctrl button.maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23a4a4a4%3B%27%20d%3D%27m%207%2C9%20c%20-0.554%2C0%20-1%2C0.446%20-1%2C1%200%2C0.554%200.446%2C1%201%2C1%20l%206%2C0%20c%200.554%2C0%201%2C-0.446%201%2C-1%200%2C-0.554%20-0.446%2C-1%20-1%2C-1%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A")
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon {
|
||||
.maplibregl-ctrl button.maplibregl-ctrl-compass .maplibregl-ctrl-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%0A%09%3Cpolygon%20fill%3D%27%23a4a4a4%27%20points%3D%276%2C9%2010%2C1%2014%2C9%27%2F%3E%0A%09%3Cpolygon%20fill%3D%27%23545454%27%20points%3D%276%2C11%2010%2C19%2014%2C11%20%27%2F%3E%0A%3C%2Fsvg%3E");
|
||||
background-size: 18px;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-inspect {
|
||||
.maplibregl-ctrl-inspect {
|
||||
background-image: url('data:image/svg+xml;charset=utf8,<svg%20xmlns="http://www.w3.org/2000/svg"%20fill="#8e8e8e%22%20preserveAspectRatio=%22xMidYMid%20meet%22%20viewBox=%22-10%20-10%2060%2060%22%3E%3Cg%3E%3Cpath%20d=%22m15%2021.6q0-2%201.5-3.5t3.5-1.5%203.5%201.5%201.5%203.5-1.5%203.6-3.5%201.4-3.5-1.4-1.5-3.6z%20m18.4%2011.1l-6.4-6.5q1.4-2.1%201.4-4.6%200-3.4-2.5-5.8t-5.9-2.4-5.9%202.4-2.5%205.8%202.5%205.9%205.9%202.5q2.4%200%204.6-1.4l7.4%207.4q-0.9%200.6-2%200.6h-20q-1.3%200-2.3-0.9t-1.1-2.3l0.1-26.8q0-1.3%201-2.3t2.3-0.9h13.4l10%2010v19.3z%22%3E%3C/path%3E%3C/g%3E%3C/svg%3E');
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-map {
|
||||
.maplibregl-ctrl-map {
|
||||
background-image: url('data:image/svg+xml;charset=utf8,<svg%20xmlns="http://www.w3.org/2000/svg"%20fill="#8e8e8e%22%20viewBox=%22-10%20-10%2060%2060%22%20preserveAspectRatio=%22xMidYMid%20meet%22%3E%3Cg%3E%3Cpath%20d=%22m25%2031.640000000000004v-19.766666666666673l-10-3.511666666666663v19.766666666666666z%20m9.140000000000008-26.640000000000004q0.8599999999999923%200%200.8599999999999923%200.8600000000000003v25.156666666666666q0%200.625-0.625%200.783333333333335l-9.375%203.1999999999999993-10-3.5133333333333354-8.906666666666668%203.4383333333333326-0.2333333333333334%200.07833333333333314q-0.8616666666666664%200-0.8616666666666664-0.8599999999999994v-25.156666666666663q0-0.625%200.6233333333333331-0.7833333333333332l9.378333333333334-3.198333333333334%2010%203.5133333333333336%208.905000000000001-3.4383333333333344z%22%3E%3C/path%3E%3C/g%3E%3C/svg%3E');
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
svg {
|
||||
img {
|
||||
width: 30px;
|
||||
padding-right: $margin-2;
|
||||
vertical-align: top;
|
||||
|
||||
@@ -18,13 +18,6 @@
|
||||
@import 'codemirror';
|
||||
@import 'react-collapse';
|
||||
|
||||
/**
|
||||
* Hacks for webdriverio isVisibleWithinViewport
|
||||
*/
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.maputnik-layout {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldArray from '../src/components/FieldArray';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldArray',
|
||||
component: FieldArray,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldAutocomplete from '../src/components/FieldAutocomplete';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldAutocomplete',
|
||||
component: FieldAutocomplete,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldCheckbox from '../src/components/FieldCheckbox';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldCheckbox',
|
||||
component: FieldCheckbox,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldColor from '../src/components/FieldColor';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldColor',
|
||||
component: FieldColor,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldDynamicArray from '../src/components/FieldDynamicArray';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldDynamicArray',
|
||||
component: FieldDynamicArray,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldEnum from '../src/components/FieldEnum';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldEnum',
|
||||
component: FieldEnum,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import React from 'react';
|
||||
import FieldFunction from '../src/components/FieldFunction';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
||||
|
||||
|
||||
export default {
|
||||
title: 'FieldFunction',
|
||||
component: FieldFunction,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const Basic = () => {
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldMultiInput from '../src/components/FieldMultiInput';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldMultiInput',
|
||||
component: FieldMultiInput,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldNumber from '../src/components/FieldNumber';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldNumber',
|
||||
component: FieldNumber,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const Basic = () => {
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldSelect from '../src/components/FieldSelect';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldSelect',
|
||||
component: FieldSelect,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldString from '../src/components/FieldString';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldString',
|
||||
component: FieldString,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import FieldUrl from '../src/components/FieldUrl';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'FieldUrl',
|
||||
component: FieldUrl,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import React from 'react';
|
||||
import IconLayer from '../src/components/IconLayer';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'IconLayer',
|
||||
component: IconLayer,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const IconList = () => {
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputArray from '../src/components/InputArray';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputArray',
|
||||
component: InputArray,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputAutocomplete from '../src/components/InputAutocomplete';
|
||||
import {InputContainer} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputAutocomplete',
|
||||
component: InputAutocomplete,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,13 +2,10 @@ import React from 'react';
|
||||
import InputButton from '../src/components/InputButton';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {InputContainer} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'InputButton',
|
||||
component: InputButton,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const Basic = () => (
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputCheckbox from '../src/components/InputCheckbox';
|
||||
import {InputContainer} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputCheckbox',
|
||||
component: InputCheckbox,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputColor from '../src/components/InputColor';
|
||||
import {InputContainer} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputColor',
|
||||
component: InputColor,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputDynamicArray from '../src/components/InputDynamicArray';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputDynamicArray',
|
||||
component: InputDynamicArray,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputEnum from '../src/components/InputEnum';
|
||||
import {InputContainer} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputEnum',
|
||||
component: InputEnum,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,13 +2,10 @@ import React from 'react';
|
||||
import InputJson from '../src/components/InputJson';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'InputJson',
|
||||
component: InputJson,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const Basic = () => {
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputMultiInput from '../src/components/InputMultiInput';
|
||||
import {InputContainer} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputMultiInput',
|
||||
component: InputMultiInput,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputNumber from '../src/components/InputNumber';
|
||||
import {InputContainer} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputNumber',
|
||||
component: InputNumber,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const Basic = () => {
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputSelect from '../src/components/InputSelect';
|
||||
import {InputContainer} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputSelect',
|
||||
component: InputSelect,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputString from '../src/components/InputString';
|
||||
import {InputContainer} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputString',
|
||||
component: InputString,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ import React from 'react';
|
||||
import {useActionState} from './helper';
|
||||
import InputUrl from '../src/components/InputUrl';
|
||||
import {InputContainer} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'InputUrl',
|
||||
component: InputUrl,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import React from 'react';
|
||||
import LayerEditor from '../src/components/LayerEditor';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
||||
|
||||
|
||||
export default {
|
||||
title: 'LayerEditor',
|
||||
component: LayerEditor,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const Background = () => (
|
||||
@@ -1,14 +1,11 @@
|
||||
import React from 'react';
|
||||
import LayerList from '../src/components/LayerList';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'LayerList',
|
||||
component: LayerList,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const Basic = () => (
|
||||
@@ -1,14 +1,9 @@
|
||||
import React from 'react';
|
||||
import MapMapboxGl from '../src/components/MapMapboxGl';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
import MapMaplibreGl from '../src/components/MapMaplibreGl';
|
||||
|
||||
export default {
|
||||
title: 'MapMapboxGl',
|
||||
component: MapMapboxGl,
|
||||
decorators: [withA11y],
|
||||
title: 'MapMaplibreGl',
|
||||
component: MapMaplibreGl,
|
||||
};
|
||||
|
||||
const mapStyle = {
|
||||
@@ -98,7 +93,7 @@ const mapStyle = {
|
||||
|
||||
export const Basic = () => {
|
||||
return <div style={{height: "100vh", width: "100vw"}}>
|
||||
<MapMapboxGl
|
||||
<MapMaplibreGl
|
||||
mapStyle={mapStyle}
|
||||
inspectModeEnabled={false}
|
||||
replaceAccessTokens={(s) => s}
|
||||
@@ -1,14 +1,10 @@
|
||||
import React from 'react';
|
||||
import MapOpenLayers from '../src/components/MapOpenLayers';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'MapOpenLayers',
|
||||
component: MapOpenLayers,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
const mapStyle = {
|
||||
@@ -1,14 +1,11 @@
|
||||
import React from 'react';
|
||||
import Modal from '../src/components/Modal';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'Modal',
|
||||
component: Modal,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const Basic = () => (
|
||||
@@ -1,14 +1,11 @@
|
||||
import React from 'react';
|
||||
import ModalAdd from '../src/components/ModalAdd';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'ModalAdd',
|
||||
component: ModalAdd,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const Basic = () => (
|
||||
@@ -1,14 +1,11 @@
|
||||
import React from 'react';
|
||||
import ModalDebug from '../src/components/ModalDebug';
|
||||
import {action} from '@storybook/addon-actions';
|
||||
import {Wrapper} from './ui';
|
||||
import {withA11y} from '@storybook/addon-a11y';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'ModalDebug',
|
||||
component: ModalDebug,
|
||||
decorators: [withA11y],
|
||||
};
|
||||
|
||||
export const Basic = () => (
|
||||
@@ -16,8 +13,8 @@ export const Basic = () => (
|
||||
<div style={{maxHeight: "200px"}}>
|
||||
<ModalDebug
|
||||
isOpen={true}
|
||||
renderer="mbgljs"
|
||||
mapboxGlDebugOptions={{}}
|
||||
renderer="mlgljs"
|
||||
maplibreGlDebugOptions={{}}
|
||||
mapView={{zoom: 1, center: {lat: 0, lng: 0}}}
|
||||
/>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user