Compare commits

..

20 Commits

Author SHA1 Message Date
shelly_goldblit 026cf95390 Main commit, missing package.json 2023-12-18 11:20:48 +02:00
Yuri Astrakhan 8eabfa5519 Fix incorrect CI tag filter (#838) 2023-12-18 06:21:43 +02:00
Harel M ad69cbdb20 Vite + TypeScript (#836)
Resolves #803

This is an initial commit to allow migrating to typescript bit by bit.
It introduces vite.
It removes all the webpack configuration (which I have no clue what all
the profiling are needed for, and couldn't find anything in the readme).
It also changes a single file from javascript to typescript:
`urlopen.js` -> `urlopen.ts`
Which was done manually, later on I'll see if I can automate most of the
migration.
For now, everything seems to work as expected.
I also upgrades storybook to use vite and renames the stories to jsx (I
honestly don't know why this complexity is needed here, but I'll keep it
for now).

cc: @damianstasik
2023-12-17 22:52:19 -05:00
Yuri Astrakhan 17eaa3f204 Rename primary branch to main (#837)
Co-authored-by: Harel M <harel.mazor@gmail.com>
2023-12-18 00:09:10 +02:00
Harel M 1df2e36dbb Replace WebDriverIO with Cypress (#831)
This does the following:
1. Moves the WDIO code from javascript to typescript
2. Moves to use files that are `cy.ts` instead of `index.js`
3. Replace e2e to use cypress
4. Introduce back some skipped tests

This is in continue to the conversation here:
https://github.com/HarelM/editor/pull/3

Before:
```
 "spec" Reporter:
------------------------------------------------------------------
[chrome 120.0.6099.71 linux #0-0] Running: chrome (v120.0.6099.71) on linux
[chrome 120.0.6099.71 linux #0-0] Session ID: ee9a87bcfce007ac7721929c6e6234d0
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0] » /test/functional/index.js
[chrome 120.0.6099.71 linux #0-0] maputnik
[chrome 120.0.6099.71 linux #0-0]     history
[chrome 120.0.6099.71 linux #0-0]        - undo/redo
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]     layers
[chrome 120.0.6099.71 linux #0-0]         ops
[chrome 120.0.6099.71 linux #0-0]            ✓ delete
[chrome 120.0.6099.71 linux #0-0]            ✓ duplicate
[chrome 120.0.6099.71 linux #0-0]            ✓ hide
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         background
[chrome 120.0.6099.71 linux #0-0]            ✓ add
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]             modify
[chrome 120.0.6099.71 linux #0-0]                 layer
[chrome 120.0.6099.71 linux #0-0]                    - expand/collapse
[chrome 120.0.6099.71 linux #0-0]                    ✓ id
[chrome 120.0.6099.71 linux #0-0]                    ✓ min-zoom
[chrome 120.0.6099.71 linux #0-0]                    ✓ max-zoom
[chrome 120.0.6099.71 linux #0-0]                    ✓ comments
[chrome 120.0.6099.71 linux #0-0]                    - color
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]                 filter
[chrome 120.0.6099.71 linux #0-0]                    - expand/collapse
[chrome 120.0.6099.71 linux #0-0]                    - compound filter
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]                 paint
[chrome 120.0.6099.71 linux #0-0]                    - expand/collapse
[chrome 120.0.6099.71 linux #0-0]                    - color
[chrome 120.0.6099.71 linux #0-0]                    - pattern
[chrome 120.0.6099.71 linux #0-0]                    - opacity
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]                 json-editor
[chrome 120.0.6099.71 linux #0-0]                    - expand/collapse
[chrome 120.0.6099.71 linux #0-0]                    - modify
[chrome 120.0.6099.71 linux #0-0]                    - parse error
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         fill
[chrome 120.0.6099.71 linux #0-0]            ✓ add
[chrome 120.0.6099.71 linux #0-0]            - change source
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         line
[chrome 120.0.6099.71 linux #0-0]            ✓ add
[chrome 120.0.6099.71 linux #0-0]            - groups
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         symbol
[chrome 120.0.6099.71 linux #0-0]            ✓ add
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         raster
[chrome 120.0.6099.71 linux #0-0]            ✓ add
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         circle
[chrome 120.0.6099.71 linux #0-0]            ✓ add
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         fill extrusion
[chrome 120.0.6099.71 linux #0-0]            ✓ add
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         groups
[chrome 120.0.6099.71 linux #0-0]            ✓ simple
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]     map
[chrome 120.0.6099.71 linux #0-0]         zoom level
[chrome 120.0.6099.71 linux #0-0]            - via url
[chrome 120.0.6099.71 linux #0-0]            - via map controls
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]     modals
[chrome 120.0.6099.71 linux #0-0]         open
[chrome 120.0.6099.71 linux #0-0]            ✓ close
[chrome 120.0.6099.71 linux #0-0]            - upload
[chrome 120.0.6099.71 linux #0-0]            ✓ load from url
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         shortcuts
[chrome 120.0.6099.71 linux #0-0]            ✓ open/close
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         export
[chrome 120.0.6099.71 linux #0-0]            ✓ close
[chrome 120.0.6099.71 linux #0-0]            - download
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         sources
[chrome 120.0.6099.71 linux #0-0]            - active sources
[chrome 120.0.6099.71 linux #0-0]            - public source
[chrome 120.0.6099.71 linux #0-0]            - add new source
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         inspect
[chrome 120.0.6099.71 linux #0-0]            ✓ toggle
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         style settings
[chrome 120.0.6099.71 linux #0-0]            ✓ name
[chrome 120.0.6099.71 linux #0-0]            ✓ owner
[chrome 120.0.6099.71 linux #0-0]            ✓ sprite url
[chrome 120.0.6099.71 linux #0-0]            ✓ glyphs url
[chrome 120.0.6099.71 linux #0-0]            ✓ maptiler access token
[chrome 120.0.6099.71 linux #0-0]            ✓ thunderforest access token
[chrome 120.0.6099.71 linux #0-0]            ✓ style renderer
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]         sources
[chrome 120.0.6099.71 linux #0-0]            - toggle
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]     screenshots
[chrome 120.0.6099.71 linux #0-0]        ✓ front_page
[chrome 120.0.6099.71 linux #0-0]        ✓ open
[chrome 120.0.6099.71 linux #0-0]        ✓ export
[chrome 120.0.6099.71 linux #0-0]        ✓ sources
[chrome 120.0.6099.71 linux #0-0]        ✓ style settings
[chrome 120.0.6099.71 linux #0-0]        ✓ inspect
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]     accessibility
[chrome 120.0.6099.71 linux #0-0]         skip links
[chrome 120.0.6099.71 linux #0-0]            ✓ skip link to layer list
[chrome 120.0.6099.71 linux #0-0]            ✓ skip link to layer editor
[chrome 120.0.6099.71 linux #0-0]            ✓ skip link to map view
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0]     keyboard
[chrome 120.0.6099.71 linux #0-0]         shortcuts
[chrome 120.0.6099.71 linux #0-0]            ✓ ESC should unfocus
[chrome 120.0.6099.71 linux #0-0]            ✓ '?' should show shortcuts modal
[chrome 120.0.6099.71 linux #0-0]            ✓ 'o' should show open modal
[chrome 120.0.6099.71 linux #0-0]            ✓ 'e' should show export modal
[chrome 120.0.6099.71 linux #0-0]            ✓ 'd' should show sources modal
[chrome 120.0.6099.71 linux #0-0]            ✓ 's' should show settings modal
[chrome 120.0.6099.71 linux #0-0]            - 'i' should change map to inspect mode
[chrome 120.0.6099.71 linux #0-0]            ✓ 'm' should focus map
[chrome 120.0.6099.71 linux #0-0]            ✓ '!' should show debug modal
[chrome 120.0.6099.71 linux #0-0]
[chrome 120.0.6099.71 linux #0-0] 44 passing (58.8s)
[chrome 120.0.6099.71 linux #0-0] 23 skipped
```

After:
```
accessibility
    skip links
      - skip link to layer list
      - skip link to layer editor
      - skip link to map view

history
    ✓ undo/redo (4894ms)

keyboard
    shortcuts
      ✓ ESC should unfocus (1912ms)
      ✓ '?' should show shortcuts modal (458ms)
      ✓ 'o' should show open modal (710ms)
      ✓ 'e' should show export modal (692ms)
      ✓ 'd' should show sources modal (588ms)
      ✓ 's' should show settings modal (894ms)
      ✓ 'i' should change map to inspect mode (804ms)
      ✓ 'm' should focus map (837ms)
      ✓ '!' should show debug modal (607ms)

layers
    ops
      ✓ delete (4313ms)
      ✓ duplicate (1780ms)
      ✓ hide (1862ms)
    background
      ✓ add (1675ms)
      modify
        layer
          - expand/collapse
          ✓ id (3735ms)
          ✓ min-zoom (2209ms)
          ✓ max-zoom (2127ms)
          ✓ comments (2515ms)
          ✓ color (2022ms)
        filter
          - expand/collapse
          - compound filter
        paint
          - expand/collapse
          - color
          - pattern
          - opacity
        json-editor
          - expand/collapse
          - modify
          - parse error
    fill
      ✓ add (1831ms)
      - change source
    line
      ✓ add (1844ms)
      ✓ groups (687ms)
    symbol
      ✓ add (2035ms)
    raster
      ✓ add (1814ms)
    circle
      ✓ add (1867ms)
    fill extrusion
      ✓ add (1963ms)
    groups
      ✓ simple (2653ms)

map
    zoom level
      ✓ via url (2279ms)
      ✓ via map controls (733ms)

modals
    open
      ✓ close (2519ms)
      - upload
      ✓ load from url (1557ms)
    shortcuts
      ✓ open/close (1136ms)
    export
      ✓ close (755ms)
      - download
    sources
      - active sources
      - public source
      - add new source
    inspect
      ✓ toggle (1020ms)
    style settings
      ✓ name (1085ms)
      ✓ owner (1060ms)
      ✓ sprite url (1214ms)
      ✓ glyphs url (1553ms)
      ✓ maptiler access token (1111ms)
      ✓ thunderforest access token (1102ms)
      ✓ style renderer (922ms)
    sources
      - toggle



      Spec                                              Tests  Passing  Failing  Pending  Skipped  
  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ ✔  accessibility.cy.ts                       52ms        3        -        -        3        - │
  ├────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ ✔  history.cy.ts                            00:06        1        1        -        -        - │
  ├────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ ✔  keyboard.cy.ts                           00:10        9        9        -        -        - │
  ├────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ ✔  layers.cy.ts                             00:39       28       17        -       11        - │
  ├────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ ✔  map.cy.ts                                00:04        2        2        -        -        - │
  ├────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ ✔  modals.cy.ts                             00:16       18       12        -        6        - │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘
    ✔  All specs passed!                        01:18       61       41        -       20        -  
```

---------

Co-authored-by: Yuri Astrakhan <yuriastrakhan@gmail.com>
2023-12-17 21:49:23 +02:00
Birk Skyum 0c46affda9 Add dependabot (#826)
It has a low limit on how many PRs it's allowed to open at a time, so it
should not flood the repo.
2023-12-17 01:16:47 -05:00
Harel M 18f45e932b Use driver pattern for e2e tests (#829)
I've moved all the logic relevant to WBIO into a single file in order to
be able to replace it.
I have tried to upgrade WDIO in order to use the latest version and it
got stuck on my computer.
Furthermore, I was not able to run it locally which made this whole
cycle very long.
After this will be merged I will replace WDIO with cypress.
This doesn't change anything, only moves some code in the tests to a
single file, removes unneeded files and uses the driver pattern, which
will later allow switching the underline WDIO with Cypress.

cc: @nyurik
2023-12-17 01:14:23 -05:00
Yuri Astrakhan 31d56c9fae Bump CI versions (#830) 2023-12-17 00:49:34 -05:00
Birk Skyum b7838ad6e1 Use latest firefox (#825) 2023-10-23 08:06:02 +02:00
Birk Skyum c92fd12854 Bump to node 18 (#824) 2023-10-22 22:11:06 +02:00
Birk Skyum eb55796461 Merge pull request #823 from birkskyum/new-webdriver
Chrome new headless
2023-10-22 20:20:26 +02:00
Yuri Astrakhan 4b97f82980 Revert "Revert "Resolve #821 (#822)""
This reverts commit e4d559f953.
2023-10-21 18:33:48 -04:00
Yuri Astrakhan 5d0b6e3201 Revert "Revert "Added checkbox for cluster attribute (#810)""
This reverts commit 52f949e152.
2023-10-21 18:33:46 -04:00
Birk Skyum 5ab9be9fdb update node version 2023-10-22 00:27:41 +02:00
Birk Skyum 9659d41b83 chrome new headless 2023-10-22 00:20:55 +02:00
Yuri Astrakhan 52f949e152 Revert "Added checkbox for cluster attribute (#810)"
This reverts commit 393f4a38b9.
2023-10-21 18:19:24 -04:00
Yuri Astrakhan e4d559f953 Revert "Resolve #821 (#822)"
This reverts commit 577663f706.
2023-10-21 18:19:21 -04:00
zstadler 577663f706 Resolve #821 (#822)
Please see code analysis at
https://github.com/maputnik/editor/issues/821#issuecomment-1772360990
2023-10-20 11:01:20 -04:00
Eliav-B 393f4a38b9 Added checkbox for cluster attribute (#810)
* Added additional checkbox to set the cluster attribute when adding a GeoJson source

* Fixed default source function in the 'geojson_json' case

---------

Co-authored-by: shimon <ruisuhsh100@gmail.com>
2023-10-20 10:54:40 -04:00
Kevin Schaul 3727c9ad5e 814/remove mapbox references (#816)
Fixes #814

* fix: remove outdated references to mapbox
* docs: fix references in readme
* chore: fix mapbox references in tests
* chore: fix mapbox references in stories, webpack config
* chore: remove empty array
2023-08-28 22:17:49 -04:00
130 changed files with 9263 additions and 40313 deletions
-21
View File
@@ -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/**"]
}]
]
}
}
}
+20
View File
@@ -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 },
],
},
}
+1 -1
View File
@@ -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) -->
+14
View File
@@ -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
+22 -75
View File
@@ -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
@@ -141,53 +120,21 @@ jobs:
with:
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 }}
+7 -9
View File
@@ -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
+2
View File
@@ -33,3 +33,5 @@ public
/errorShots
/old
/build
/cypress/screenshots
/dist/
+8 -23
View File
@@ -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
View File
@@ -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
+15 -14
View File
@@ -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.
-47
View File
@@ -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();
}
}
-74
View File
@@ -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'
}
]
})
]
};
-68
View File
@@ -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',
})
]
};
-20
View File
@@ -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',
}
}
};
-44
View File
@@ -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'
]
}
];
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});
+39
View File
@@ -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();
});
});
});
+198
View File
@@ -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");
},
};
}
+97
View File
@@ -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",
},
]
);
});
});
+61
View File
@@ -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");
});
});
});
+497
View File
@@ -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");
});
});
});
+25
View File
@@ -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);
});
});
});
+160
View File
@@ -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": []
}
+29
View File
@@ -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": []
}
+18
View File
@@ -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": []
}
+37
View File
@@ -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>
// }
// }
// }
+21
View File
@@ -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')
+4 -5
View File
@@ -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&hellip;</div>
</div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>
+6347 -36840
View File
File diff suppressed because it is too large Load Diff
+32 -84
View File
@@ -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,40 @@
]
}
},
"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",
"@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"
}
}
-5
View File
@@ -1,5 +0,0 @@
{
"container": {
"startScript": "start-sandbox"
}
}
+485 -419
View File
File diff suppressed because it is too large Load Diff
+202 -153
View File
@@ -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>
);
}
}
+3 -3
View File
@@ -21,14 +21,14 @@ 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)
);
return (
<>
{doc &&
{doc &&
<div className="SpecDoc">
<div className="SpecDoc__doc">{doc}</div>
{renderValues &&
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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}
/>
);
}
}
-247
View File
@@ -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>
}
}
}
+269
View File
@@ -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>
);
}
}
}
+4 -5
View File
@@ -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}
/>
+2 -2
View File
@@ -27,9 +27,9 @@ export default class Modal extends React.Component {
document.activeElement.blur();
}
setImmediate(() => {
setTimeout(() => {
this.props.onOpenToggle(false);
});
}, 0);
}
render() {
+3 -3
View File
@@ -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}
+7 -16
View File
@@ -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}
+2 -10
View File
@@ -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>
+1
View File
@@ -128,6 +128,7 @@ class AddSource extends React.Component {
}
case 'geojson_json': return {
type: 'geojson',
cluster: source.cluster || false,
data: {}
}
case 'tilejson_vector': return {
+26 -13
View File
@@ -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>
}
}
-1
View File
@@ -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
View File
@@ -1,5 +1,4 @@
{
"mapbox": "pk.eyJ1IjoibW9yZ2Vua2FmZmVlIiwiYSI6ImNpeHJmNXNmZTAwNHIycXBid2NqdTJibjMifQ.Dv1-GDpTWi0NP6xW9Fct1w",
"openmaptiles": "KDhMfHvorAFkFe64wlZb",
"thunderforest": "b71f7f0ba4064f5eb9e903859a9cf5c6"
}
-4
View File
@@ -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
View File
@@ -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]
}
+2 -2
View File
@@ -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
})
-11
View File
@@ -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, () => {});
+3
View File
@@ -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');
-1
View File
@@ -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,
-65
View File
@@ -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)
})
}
+31
View File
@@ -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())
}
+12 -10
View File
@@ -1,26 +1,28 @@
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>
`;
this._textEl = this._container.querySelector("span");
this.addEventListeners();
return this._container;
}
updateZoomLevel() {
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() {
+21 -23
View File
@@ -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');
}
+1 -1
View File
@@ -30,7 +30,7 @@
line-height: 26px;
}
svg {
img {
width: 30px;
padding-right: $margin-2;
vertical-align: top;
-7
View File
@@ -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