mirror of
https://github.com/maputnik/editor.git
synced 2026-07-08 23:17:26 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a47093a6e |
@@ -15,9 +15,6 @@ updates:
|
||||
vitest:
|
||||
patterns:
|
||||
- "*vitest*"
|
||||
react:
|
||||
patterns:
|
||||
- "*react*"
|
||||
cooldown:
|
||||
default-days: 5
|
||||
semver-major-days: 5
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
|
||||
uses: dependabot/fetch-metadata@08eff52bf64351f401fb50d4972fa95b9f2c2d1b # v2.4.0
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Approve Dependabot PRs
|
||||
|
||||
+30
-56
@@ -21,9 +21,9 @@ jobs:
|
||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with: { persist-credentials: false }
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- run: npm ci
|
||||
@@ -41,22 +41,22 @@ jobs:
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with: { persist-credentials: false }
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- name: artifacts/maputnik
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: maputnik
|
||||
path: dist
|
||||
|
||||
# Build and upload desktop CLI artifacts
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version: ^1.23.x
|
||||
cache-dependency-path: desktop/go.sum
|
||||
@@ -66,19 +66,19 @@ jobs:
|
||||
run: npm run build-desktop
|
||||
|
||||
- name: Artifacts/linux
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: maputnik-linux
|
||||
path: ./desktop/bin/linux/
|
||||
|
||||
- name: Artifacts/darwin
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: maputnik-darwin
|
||||
path: ./desktop/bin/darwin/
|
||||
|
||||
- name: Artifacts/windows
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: maputnik-windows
|
||||
path: ./desktop/bin/windows/
|
||||
@@ -90,16 +90,12 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with: { persist-credentials: false }
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- run: npm ci
|
||||
- run: npx playwright install --with-deps chromium
|
||||
- run: npm run test-unit-ci
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||
with:
|
||||
files: ${{ github.workspace }}/coverage/coverage-final.json
|
||||
verbose: true
|
||||
@@ -112,27 +108,20 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with: { persist-credentials: false }
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- run: npm ci
|
||||
- run: npx playwright install --with-deps chromium
|
||||
- name: Playwright run
|
||||
run: npm run test-e2e
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@7ef72e250a9e564efb4ed4c2433971ada4cc38b4 # v6.10.4
|
||||
with:
|
||||
build: npm run build
|
||||
start: npm run start
|
||||
browser: chrome
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||
with:
|
||||
files: ${{ github.workspace }}/coverage/coverage-final.json
|
||||
files: ${{ github.workspace }}/.nyc_output/out.json
|
||||
verbose: true
|
||||
- name: Upload Playwright report
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
e2e-tests-docker:
|
||||
name: "E2E tests using chrome and docker"
|
||||
@@ -140,32 +129,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with: { persist-credentials: false }
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- run: npm ci
|
||||
- run: npx playwright install --with-deps chromium
|
||||
- name: Build docker image
|
||||
run: docker build -t maputnik .
|
||||
- name: Start maputnik container
|
||||
run: docker run -d --network host --name maputnik maputnik --port=8888
|
||||
- name: Wait for maputnik to be ready
|
||||
run: |
|
||||
for i in $(seq 1 60); do
|
||||
if curl -sSf http://localhost:8888/ > /dev/null; then
|
||||
echo "maputnik is up"; exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "maputnik did not start in time"; docker logs maputnik; exit 1
|
||||
- name: Playwright run
|
||||
run: npm run test-e2e
|
||||
env:
|
||||
E2E_NO_WEBSERVER: "1"
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@7ef72e250a9e564efb4ed4c2433971ada4cc38b4 # v6.10.4
|
||||
with:
|
||||
files: ${{ github.workspace }}/coverage/coverage-final.json
|
||||
build: docker build -t maputnik .
|
||||
start: docker run --rm --network host maputnik --port=8888
|
||||
browser: chrome
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||
with:
|
||||
files: ${{ github.workspace }}/.nyc_output/out.json
|
||||
verbose: true
|
||||
|
||||
@@ -38,11 +38,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -67,4 +67,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
|
||||
@@ -16,13 +16,13 @@ jobs:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
|
||||
- name: Use Node.js from nvmrc
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
./build/bump-version-changelog.js ${{ inputs.version }}
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
commit-message: Bump version to ${{ inputs.version }}
|
||||
branch: bump-version-to-${{ inputs.version }}
|
||||
|
||||
@@ -12,11 +12,11 @@ jobs:
|
||||
contents: write
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with: { persist-credentials: false }
|
||||
|
||||
- name: Use Node.js from nvmrc
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
run: npm run build
|
||||
|
||||
- name: Upload to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
|
||||
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: dist
|
||||
@@ -45,11 +45,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- run: docker build -t ghcr.io/maplibre/maputnik:main .
|
||||
- run: docker push ghcr.io/maplibre/maputnik:main
|
||||
|
||||
@@ -15,20 +15,20 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use Node.js from nvmrc
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
|
||||
- name: Check if version has been updated
|
||||
id: check
|
||||
uses: EndBug/version-check@095362f3cd50f690c8fa0e6afeea81834bd8d320 # latest
|
||||
uses: EndBug/version-check@d17247dd94ca7b39d0b0691399be8d7c510622c9 # latest
|
||||
|
||||
outputs:
|
||||
publish: ${{ steps.check.outputs.changed }}
|
||||
@@ -42,19 +42,19 @@ jobs:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
|
||||
- name: Use Node.js from nvmrc
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Set up Go for desktop build
|
||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version: ^1.23.x
|
||||
cache-dependency-path: desktop/go.sum
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: create_regular_release
|
||||
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
|
||||
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
+1
-5
@@ -33,14 +33,10 @@ node_modules
|
||||
public
|
||||
/errorShots
|
||||
/old
|
||||
/cypress/screenshots
|
||||
/dist/
|
||||
/desktop/version.go
|
||||
|
||||
# Playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
+4
-5
@@ -4,13 +4,12 @@
|
||||
"check-coverage": false,
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": [
|
||||
"e2e/**/*.*",
|
||||
"cypress/**/*.*",
|
||||
"**/*.d.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.browser.test.tsx",
|
||||
"**/*.cy.tsx",
|
||||
"**/*.cy.ts",
|
||||
"./coverage/**",
|
||||
"./e2e/**",
|
||||
"./cypress/**",
|
||||
"./dist/**",
|
||||
"node_modules"
|
||||
],
|
||||
|
||||
@@ -24,22 +24,22 @@ The project type checked and built with:
|
||||
npm run build
|
||||
```
|
||||
|
||||
Install the Playwright browser (first time only):
|
||||
To run the tests make sure that xvfb is installed:
|
||||
|
||||
```
|
||||
npx playwright install --with-deps chromium
|
||||
apt install xvfb
|
||||
```
|
||||
|
||||
Then run the end-to-end tests (Playwright starts the dev server automatically):
|
||||
Run the development server in the background with Vite:
|
||||
|
||||
```
|
||||
npm run test
|
||||
nohup npm run start &
|
||||
```
|
||||
|
||||
Run the unit and component tests with Vitest:
|
||||
Then start the Cypress tests with:
|
||||
|
||||
```
|
||||
npm run test-unit
|
||||
xvfb-run -a npm run test
|
||||
```
|
||||
|
||||
## Pull Requests
|
||||
|
||||
+4
-15
@@ -1,14 +1,5 @@
|
||||
## main
|
||||
|
||||
### ✨ Features and improvements
|
||||
- Replace Cypress with Playwright for end-to-end tests and Vitest browser mode (Playwright provider) for component tests; drop the `@shellygo/cypress-test-utils` helper in favour of a `MaputnikDriver` page object
|
||||
- _...Add new stuff here..._
|
||||
|
||||
### 🐞 Bug fixes
|
||||
- _...Add new stuff here..._
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### ✨ Features and improvements
|
||||
- Added translation to "Links" in debug modal
|
||||
- Add support for hillshade's color arrays and relief-color elevation expression
|
||||
@@ -21,22 +12,20 @@
|
||||
- Upgraded codemirror from version 5 to version 6
|
||||
- Add code editor to allow editing the entire style
|
||||
- Add support for sprite object in setting modal
|
||||
- Set the correct map view when opening a new style on an empty map
|
||||
- Allow root-relative urls in the stylefile
|
||||
- _...Add new stuff here..._
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Improved the local style open modal and local file upload.
|
||||
- Fixed the Expression editor (for long expressions) being able to be float under other components further down
|
||||
- Fixed an issue when clicking on a popup and then clicking on the map again
|
||||
- Fix modal close button position
|
||||
- Fixed an issue with the generation of translations
|
||||
- Fix modal close button possition
|
||||
- Fixed an issue with the generation of tranlations
|
||||
- Fix missing spec info when clicking next to a property
|
||||
- Fix Firefox open file that stopped working due to react upgrade
|
||||
- Fix issue with missing bottom error panel
|
||||
- Fixed headers in left panes (Layers list and Layer editor) to remain visible when scrolling
|
||||
- Fix error when using a source from localhost
|
||||
- Fix an issue with scrolling when using the code editor
|
||||
- _...Add new stuff here..._
|
||||
|
||||
## 3.0.0
|
||||
|
||||
|
||||
@@ -79,45 +79,26 @@ npm run sort-styles
|
||||
```
|
||||
|
||||
## Tests
|
||||
For E2E testing we use [Cypress](https://www.cypress.io/)
|
||||
|
||||
### End-to-end tests
|
||||
[Cypress](https://www.cypress.io/) doesn't start a server so you'll need to start one manually by running `npm run start`.
|
||||
|
||||
For E2E testing we use [Playwright](https://playwright.dev/). The tests live in the [`e2e`](/e2e) directory and drive the app through the `MaputnikDriver` page object.
|
||||
|
||||
The first time you run the tests, install the browser:
|
||||
|
||||
```
|
||||
npx playwright install chromium
|
||||
```
|
||||
|
||||
Playwright automatically starts the dev server (`npm run start`) for you, so you can just run:
|
||||
Now open a terminal and run the following using *chrome*:
|
||||
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
Some useful options:
|
||||
|
||||
or *firefox*:
|
||||
```
|
||||
# see the tests run in a headed browser
|
||||
npm run test -- --headed
|
||||
|
||||
# run a single spec / filter by title
|
||||
npm run test -- e2e/map.spec.ts
|
||||
npm run test -- -g "zoom level"
|
||||
|
||||
# open the interactive UI mode
|
||||
npx playwright test --ui
|
||||
npm run test -- --browser firefox
|
||||
```
|
||||
|
||||
Running the E2E tests also produces a code-coverage report in `coverage/` (collected via istanbul instrumentation of the dev server).
|
||||
See the following docs for more info: (Launching Browsers)[https://docs.cypress.io/guides/guides/launching-browsers]
|
||||
|
||||
### Unit & component tests
|
||||
|
||||
Unit tests and component tests run with [Vitest](https://vitest.dev/); component tests (`*.browser.test.tsx`) use Vitest's browser mode with the Playwright provider.
|
||||
You can also see the tests as they run or select which suites to run by executing:
|
||||
|
||||
```
|
||||
npm run test-unit
|
||||
npm run cy:open
|
||||
```
|
||||
|
||||
## Release process
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { defineConfig } from "cypress";
|
||||
import { createRequire } from "module";
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
export default defineConfig({
|
||||
env: {
|
||||
codeCoverage: {
|
||||
exclude: "cypress/**/*.*",
|
||||
},
|
||||
},
|
||||
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
require("@cypress/code-coverage/task")(on, config);
|
||||
return config;
|
||||
},
|
||||
baseUrl: "http://localhost:8888",
|
||||
scrollBehavior: "center",
|
||||
retries: {
|
||||
runMode: 2,
|
||||
openMode: 0,
|
||||
},
|
||||
},
|
||||
|
||||
component: {
|
||||
devServer: {
|
||||
framework: "react",
|
||||
bundler: "vite",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
describe("accessibility", () => {
|
||||
const { beforeAndAfter, get, when, then } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
|
||||
describe("skip links", () => {
|
||||
beforeEach(() => {
|
||||
when.setStyle("layer");
|
||||
});
|
||||
|
||||
it("skip link to layer list", () => {
|
||||
const selector = "root:skip:layer-list";
|
||||
then(get.elementByTestId(selector)).shouldExist();
|
||||
when.tab();
|
||||
then(get.elementByTestId(selector)).shouldBeFocused();
|
||||
when.click(selector);
|
||||
then(get.skipTargetLayerList()).shouldBeFocused();
|
||||
});
|
||||
|
||||
it("skip link to layer editor", () => {
|
||||
const selector = "root:skip:layer-editor";
|
||||
then(get.elementByTestId(selector)).shouldExist();
|
||||
then(get.elementByTestId("skip-target-layer-editor")).shouldExist();
|
||||
when.tab().tab();
|
||||
then(get.elementByTestId(selector)).shouldBeFocused();
|
||||
when.click(selector);
|
||||
then(get.skipTargetLayerEditor()).shouldBeFocused();
|
||||
});
|
||||
|
||||
it("skip link to map view", () => {
|
||||
const selector = "root:skip:map-view";
|
||||
then(get.elementByTestId(selector)).shouldExist();
|
||||
when.tab().tab().tab();
|
||||
then(get.elementByTestId(selector)).shouldBeFocused();
|
||||
when.click(selector);
|
||||
then(get.canvas()).shouldBeFocused();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
describe("code editor", () => {
|
||||
const { beforeAndAfter, when, get, then } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
|
||||
it("open code editor", () => {
|
||||
when.click("nav:code-editor");
|
||||
then(get.element(".maputnik-code-editor")).shouldExist();
|
||||
});
|
||||
|
||||
it("closes code editor", () => {
|
||||
when.click("nav:code-editor");
|
||||
then(get.element(".maputnik-code-editor")).shouldExist();
|
||||
when.click("nav:code-editor");
|
||||
then(get.element(".maputnik-code-editor")).shouldNotExist();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
describe("history", () => {
|
||||
const { beforeAndAfter, when, get, then } = 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.modal.open();
|
||||
|
||||
when.modal.fillLayers({
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
});
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
when.modal.open();
|
||||
when.modal.fillLayers({
|
||||
id: "step 2",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
},
|
||||
{
|
||||
id: "step 2",
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
when.typeKeys(undoKeyCombo);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
when.typeKeys(undoKeyCombo);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ layers: [] });
|
||||
|
||||
when.typeKeys(redoKeyCombo);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
when.typeKeys(redoKeyCombo);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
},
|
||||
{
|
||||
id: "step 2",
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should not redo after undo and value change", () => {
|
||||
when.setStyle("geojson");
|
||||
when.modal.open();
|
||||
when.modal.fillLayers({
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
when.modal.open();
|
||||
when.modal.fillLayers({
|
||||
id: "step 2",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
when.typeKeys(undoKeyCombo);
|
||||
when.typeKeys(undoKeyCombo);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ layers: [] });
|
||||
|
||||
when.modal.open();
|
||||
when.modal.fillLayers({
|
||||
id: "step 3",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
when.typeKeys(redoKeyCombo);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "step 3",
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
describe("i18n", () => {
|
||||
const { beforeAndAfter, get, when, then } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
|
||||
describe("language detector", () => {
|
||||
it("English", () => {
|
||||
const url = "?lng=en";
|
||||
when.visit(url);
|
||||
then(get.elementByTestId("maputnik-lang-select")).shouldHaveValue("en");
|
||||
});
|
||||
|
||||
it("Japanese", () => {
|
||||
const url = "?lng=ja";
|
||||
when.visit(url);
|
||||
then(get.elementByTestId("maputnik-lang-select")).shouldHaveValue("ja");
|
||||
});
|
||||
});
|
||||
|
||||
describe("language switcher", () => {
|
||||
beforeEach(() => {
|
||||
when.setStyle("layer");
|
||||
});
|
||||
|
||||
it("the language switcher switches to Japanese", () => {
|
||||
const selector = "maputnik-lang-select";
|
||||
then(get.elementByTestId(selector)).shouldExist();
|
||||
when.select(selector, "ja");
|
||||
then(get.elementByTestId(selector)).shouldHaveValue("ja");
|
||||
|
||||
then(get.elementByTestId("nav:settings")).shouldHaveText("スタイル設定");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
describe("keyboard", () => {
|
||||
const { beforeAndAfter, given, when, get, then } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
describe("shortcuts", () => {
|
||||
beforeEach(() => {
|
||||
given.setupMockBackedResponses();
|
||||
when.setStyle("");
|
||||
});
|
||||
|
||||
it("ESC should unfocus", () => {
|
||||
const targetSelector = "maputnik-select";
|
||||
when.focus(targetSelector);
|
||||
then(get.elementByTestId(targetSelector)).shouldBeFocused();
|
||||
when.typeKeys("{esc}");
|
||||
then(get.elementByTestId(targetSelector)).shouldNotBeFocused();
|
||||
});
|
||||
|
||||
it("'?' should show shortcuts modal", () => {
|
||||
when.typeKeys("?");
|
||||
then(get.elementByTestId("modal:shortcuts")).shouldBeVisible();
|
||||
});
|
||||
|
||||
it("'o' should show open modal", () => {
|
||||
when.typeKeys("o");
|
||||
then(get.elementByTestId("modal:open")).shouldBeVisible();
|
||||
});
|
||||
|
||||
it("'e' should show export modal", () => {
|
||||
when.typeKeys("e");
|
||||
then(get.elementByTestId("modal:export")).shouldBeVisible();
|
||||
});
|
||||
|
||||
it("'d' should show sources modal", () => {
|
||||
when.typeKeys("d");
|
||||
then(get.elementByTestId("modal:sources")).shouldBeVisible();
|
||||
});
|
||||
|
||||
it("'s' should show settings modal", () => {
|
||||
when.typeKeys("s");
|
||||
then(get.elementByTestId("modal:settings")).shouldBeVisible();
|
||||
});
|
||||
|
||||
it("'i' should change map to inspect mode", () => {
|
||||
when.typeKeys("i");
|
||||
then(get.inputValue("maputnik-select")).shouldEqual("inspect");
|
||||
});
|
||||
|
||||
it("'m' should focus map", () => {
|
||||
when.typeKeys("m");
|
||||
then(get.canvas()).shouldBeFocused();
|
||||
});
|
||||
|
||||
it("'!' should show debug modal", () => {
|
||||
when.typeKeys("!");
|
||||
then(get.elementByTestId("modal:debug")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,294 @@
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
import { v1 as uuid } from "uuid";
|
||||
|
||||
describe("layer editor", () => {
|
||||
const { beforeAndAfter, get, when, then } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
beforeEach(() => {
|
||||
when.setStyle("both");
|
||||
when.modal.open();
|
||||
});
|
||||
|
||||
function createBackground() {
|
||||
const id = uuid();
|
||||
|
||||
when.selectWithin("add-layer.layer-type", "background");
|
||||
when.setValue("add-layer.layer-id.input", "background:" + id);
|
||||
|
||||
when.click("add-layer");
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "background:" + id,
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
return id;
|
||||
}
|
||||
|
||||
it("expand/collapse");
|
||||
it("id", () => {
|
||||
const bgId = createBackground();
|
||||
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
|
||||
const id = uuid();
|
||||
when.setValue("layer-editor.layer-id.input", "foobar:" + id);
|
||||
when.click("min-zoom");
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "foobar:" + id,
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe("source", () => {
|
||||
it("should show error when the source is invalid", () => {
|
||||
when.modal.fillLayers({
|
||||
type: "circle",
|
||||
layer: "invalid",
|
||||
});
|
||||
then(get.element(".maputnik-input-block--error .maputnik-input-block-label")).shouldHaveCss("color", "rgb(207, 74, 74)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("min-zoom", () => {
|
||||
let bgId: string;
|
||||
|
||||
beforeEach(() => {
|
||||
bgId = createBackground();
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
when.setValue("min-zoom.input-text", "1");
|
||||
when.click("layer-editor.layer-id");
|
||||
});
|
||||
|
||||
it("should update min-zoom in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
minzoom: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("when clicking next layer should update style on local storage", () => {
|
||||
when.type("min-zoom.input-text", "{backspace}");
|
||||
when.click("max-zoom.input-text");
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
minzoom: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("max-zoom", () => {
|
||||
let bgId: string;
|
||||
|
||||
beforeEach(() => {
|
||||
bgId = createBackground();
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
when.setValue("max-zoom.input-text", "1");
|
||||
when.click("layer-editor.layer-id");
|
||||
});
|
||||
|
||||
it("should update style in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
maxzoom: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("comments", () => {
|
||||
let bgId: string;
|
||||
const comment = "42";
|
||||
|
||||
beforeEach(() => {
|
||||
bgId = createBackground();
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
when.setValue("layer-comment.input", comment);
|
||||
when.click("layer-editor.layer-id");
|
||||
});
|
||||
|
||||
it("should update style in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
metadata: {
|
||||
"maputnik:comment": comment,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe("when unsetting", () => {
|
||||
beforeEach(() => {
|
||||
when.clear("layer-comment.input");
|
||||
when.click("min-zoom.input-text");
|
||||
});
|
||||
|
||||
it("should update style in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("color", () => {
|
||||
let bgId: string;
|
||||
beforeEach(() => {
|
||||
bgId = createBackground();
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
when.click("spec-field:background-color");
|
||||
});
|
||||
|
||||
it("should update style in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("opacity", () => {
|
||||
let bgId: string;
|
||||
beforeEach(() => {
|
||||
bgId = createBackground();
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
when.type("spec-field-input:background-opacity", "0.");
|
||||
});
|
||||
|
||||
it("should keep '.' in the input field", () => {
|
||||
then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0.");
|
||||
});
|
||||
|
||||
it("should revert to a valid value when focus out", () => {
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe("filter", () => {
|
||||
it("expand/collapse");
|
||||
it("compound filter");
|
||||
});
|
||||
|
||||
describe("layout", () => {
|
||||
it("text-font", () => {
|
||||
when.setStyle("font");
|
||||
when.collapseGroupInLayerEditor();
|
||||
when.collapseGroupInLayerEditor(1);
|
||||
when.collapseGroupInLayerEditor(2);
|
||||
when.doWithin("spec-field:text-font", () => {
|
||||
get.element(".maputnik-autocomplete input").first().click();
|
||||
});
|
||||
then(get.element(".maputnik-autocomplete-menu-item")).shouldBeVisible();
|
||||
then(get.element(".maputnik-autocomplete-menu-item")).shouldHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("paint", () => {
|
||||
it("expand/collapse");
|
||||
it("color");
|
||||
it("pattern");
|
||||
it("opacity");
|
||||
});
|
||||
|
||||
describe("json-editor", () => {
|
||||
it("add", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "circle",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "circle",
|
||||
source: "example",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const sourceText = get.elementByText('"source"');
|
||||
|
||||
sourceText.click();
|
||||
sourceText.type("\"");
|
||||
|
||||
then(get.element(".cm-lint-marker-error")).shouldExist();
|
||||
});
|
||||
|
||||
|
||||
it("expand/collapse");
|
||||
it("modify");
|
||||
|
||||
it("parse error", () => {
|
||||
const bgId = createBackground();
|
||||
|
||||
when.click("layer-list-item:background:" + bgId);
|
||||
when.collapseGroupInLayerEditor();
|
||||
when.collapseGroupInLayerEditor(1);
|
||||
then(get.element(".cm-lint-marker-error")).shouldNotExist();
|
||||
|
||||
when.appendTextInJsonEditor(
|
||||
"\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013 {"
|
||||
);
|
||||
then(get.element(".cm-lint-marker-error")).shouldExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe("sticky header", () => {
|
||||
it("should keep layer header visible when scrolling properties", () => {
|
||||
// Setup: Create a layer with many properties (e.g., symbol layer)
|
||||
when.modal.fillLayers({
|
||||
type: "symbol",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
when.wait(500);
|
||||
const header = get.elementByTestId("layer-editor.header");
|
||||
then(header).shouldBeVisible();
|
||||
|
||||
get.element(".maputnik-scroll-container").scrollTo("bottom", { ensureScrollable: false });
|
||||
when.wait(200);
|
||||
|
||||
then(header).shouldBeVisible();
|
||||
then(get.elementByTestId("skip-target-layer-editor")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,541 @@
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
describe("layers list", () => {
|
||||
const { beforeAndAfter, get, when, then } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
beforeEach(() => {
|
||||
when.setStyle("both");
|
||||
when.modal.open();
|
||||
});
|
||||
|
||||
describe("ops", () => {
|
||||
let id: string;
|
||||
beforeEach(() => {
|
||||
id = when.modal.fillLayers({
|
||||
type: "background",
|
||||
});
|
||||
});
|
||||
|
||||
it("should update layers in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe("when clicking delete", () => {
|
||||
beforeEach(() => {
|
||||
when.click("layer-list-item:" + id + ":delete");
|
||||
});
|
||||
it("should empty layers in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when clicking duplicate", () => {
|
||||
beforeEach(() => {
|
||||
when.click("layer-list-item:" + id + ":copy");
|
||||
});
|
||||
it("should add copy layer in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id + "-copy",
|
||||
type: "background",
|
||||
},
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when clicking hide", () => {
|
||||
beforeEach(() => {
|
||||
when.click("layer-list-item:" + id + ":toggle-visibility");
|
||||
});
|
||||
|
||||
it("should update visibility to none in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
layout: {
|
||||
visibility: "none",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe("when clicking show", () => {
|
||||
beforeEach(() => {
|
||||
when.click("layer-list-item:" + id + ":toggle-visibility");
|
||||
});
|
||||
|
||||
it("should update visibility to visible in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
layout: {
|
||||
visibility: "visible",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when selecting a layer", () => {
|
||||
let secondId: string;
|
||||
beforeEach(() => {
|
||||
when.modal.open();
|
||||
secondId = when.modal.fillLayers({
|
||||
id: "second-layer",
|
||||
type: "background",
|
||||
});
|
||||
});
|
||||
it("should show the selected layer in the editor", () => {
|
||||
when.realClick("layer-list-item:" + secondId);
|
||||
then(get.elementByTestId("layer-editor.layer-id.input")).shouldHaveValue(secondId);
|
||||
when.realClick("layer-list-item:" + id);
|
||||
then(get.elementByTestId("layer-editor.layer-id.input")).shouldHaveValue(id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("background", () => {
|
||||
it("add", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "background",
|
||||
});
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe("modify", () => {});
|
||||
});
|
||||
|
||||
describe("fill", () => {
|
||||
it("add", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "fill",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "fill",
|
||||
source: "example",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Change source
|
||||
it("change source");
|
||||
});
|
||||
|
||||
describe("line", () => {
|
||||
it("add", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "line",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "line",
|
||||
source: "example",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("groups", () => {
|
||||
when.modal.open();
|
||||
const id1 = when.modal.fillLayers({
|
||||
id: "aa",
|
||||
type: "line",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
when.modal.open();
|
||||
const id2 = when.modal.fillLayers({
|
||||
id: "aa-2",
|
||||
type: "line",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
when.modal.open();
|
||||
const id3 = when.modal.fillLayers({
|
||||
id: "b",
|
||||
type: "line",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
then(get.elementByTestId("layer-list-item:" + id1)).shouldBeVisible();
|
||||
then(get.elementByTestId("layer-list-item:" + id2)).shouldNotBeVisible();
|
||||
then(get.elementByTestId("layer-list-item:" + id3)).shouldBeVisible();
|
||||
when.click("layer-list-group:aa-0");
|
||||
then(get.elementByTestId("layer-list-item:" + id1)).shouldBeVisible();
|
||||
then(get.elementByTestId("layer-list-item:" + id2)).shouldBeVisible();
|
||||
then(get.elementByTestId("layer-list-item:" + id3)).shouldBeVisible();
|
||||
when.click("layer-list-item:" + id2);
|
||||
when.click("skip-target-layer-editor");
|
||||
when.click("menu-move-layer-down");
|
||||
then(get.elementByTestId("layer-list-group:aa-0")).shouldNotExist();
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "aa",
|
||||
type: "line",
|
||||
source: "example",
|
||||
},
|
||||
{
|
||||
id: "b",
|
||||
type: "line",
|
||||
source: "example",
|
||||
},
|
||||
{
|
||||
id: "aa-2",
|
||||
type: "line",
|
||||
source: "example",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("symbol", () => {
|
||||
it("add", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "symbol",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "symbol",
|
||||
source: "example",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should show spec info when hovering and clicking single line property", () => {
|
||||
when.modal.fillLayers({
|
||||
type: "symbol",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
when.hover("spec-field-container:text-rotate");
|
||||
then(get.elementByTestId("field-doc-button-Rotate")).shouldBeVisible();
|
||||
when.click("field-doc-button-Rotate", 0);
|
||||
then(get.elementByTestId("spec-field-doc")).shouldContainText("Rotates the ");
|
||||
});
|
||||
|
||||
it("should show spec info when hovering and clicking multi line property", () => {
|
||||
when.modal.fillLayers({
|
||||
type: "symbol",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
when.hover("spec-field-container:text-offset");
|
||||
then(get.elementByTestId("field-doc-button-Offset")).shouldBeVisible();
|
||||
when.click("field-doc-button-Offset", 0);
|
||||
then(get.elementByTestId("spec-field-doc")).shouldContainText("Offset distance");
|
||||
});
|
||||
|
||||
it("should hide spec info when clicking a second time", () => {
|
||||
when.modal.fillLayers({
|
||||
type: "symbol",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
when.hover("spec-field-container:text-rotate");
|
||||
then(get.elementByTestId("field-doc-button-Rotate")).shouldBeVisible();
|
||||
when.click("field-doc-button-Rotate", 0);
|
||||
when.wait(200);
|
||||
when.click("field-doc-button-Rotate", 0);
|
||||
then(get.elementByTestId("spec-field-doc")).shouldNotBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
describe("raster", () => {
|
||||
it("add", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "raster",
|
||||
layer: "raster",
|
||||
});
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "raster",
|
||||
source: "raster",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("circle", () => {
|
||||
it("add", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "circle",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "circle",
|
||||
source: "example",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("fill extrusion", () => {
|
||||
it("add", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "fill-extrusion",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "fill-extrusion",
|
||||
source: "example",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("hillshade", () => {
|
||||
it("add", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "hillshade",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "hillshade",
|
||||
source: "example",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("set hillshade illumination direction array", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "hillshade",
|
||||
layer: "example",
|
||||
});
|
||||
when.collapseGroupInLayerEditor();
|
||||
when.collapseGroupInLayerEditor(1);
|
||||
when.setValueToPropertyArray("spec-field:hillshade-illumination-direction", "1");
|
||||
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "2");
|
||||
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "3");
|
||||
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "4");
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "hillshade",
|
||||
source: "example",
|
||||
paint: {
|
||||
"hillshade-illumination-direction": [ 1, 2, 3, 4 ]
|
||||
}
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("set hillshade highlight color array", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "hillshade",
|
||||
layer: "example",
|
||||
});
|
||||
when.collapseGroupInLayerEditor();
|
||||
when.setValueToPropertyArray("spec-field:hillshade-highlight-color", "blue");
|
||||
when.addValueToPropertyArray("spec-field:hillshade-highlight-color", "#00ff00");
|
||||
when.addValueToPropertyArray("spec-field:hillshade-highlight-color", "rgba(255, 255, 0, 1)");
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "hillshade",
|
||||
source: "example",
|
||||
paint: {
|
||||
"hillshade-highlight-color": [ "blue", "#00ff00", "rgba(255, 255, 0, 1)" ]
|
||||
}
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("color-relief", () => {
|
||||
it("add", () => {
|
||||
const id = when.modal.fillLayers({
|
||||
type: "color-relief",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: id,
|
||||
type: "color-relief",
|
||||
source: "example",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("adds elevation expression when clicking the elevation button", () => {
|
||||
when.modal.fillLayers({
|
||||
type: "color-relief",
|
||||
layer: "example",
|
||||
});
|
||||
when.collapseGroupInLayerEditor();
|
||||
when.click("make-elevation-function");
|
||||
then(get.element("[data-wd-key='spec-field-container:color-relief-color'] .cm-line")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
describe("groups", () => {
|
||||
it("simple", () => {
|
||||
when.setStyle("geojson");
|
||||
|
||||
when.modal.open();
|
||||
when.modal.fillLayers({
|
||||
id: "foo",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
when.modal.open();
|
||||
when.modal.fillLayers({
|
||||
id: "foo_bar",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
when.modal.open();
|
||||
when.modal.fillLayers({
|
||||
id: "foo_bar_baz",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
then(get.elementByTestId("layer-list-item:foo")).shouldBeVisible();
|
||||
then(get.elementByTestId("layer-list-item:foo_bar")).shouldNotBeVisible();
|
||||
then(
|
||||
get.elementByTestId("layer-list-item:foo_bar_baz")
|
||||
).shouldNotBeVisible();
|
||||
when.click("layer-list-group:foo-0");
|
||||
then(get.elementByTestId("layer-list-item:foo")).shouldBeVisible();
|
||||
then(get.elementByTestId("layer-list-item:foo_bar")).shouldBeVisible();
|
||||
then(
|
||||
get.elementByTestId("layer-list-item:foo_bar_baz")
|
||||
).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
describe("drag and drop", () => {
|
||||
it("move layer should update local storage", () => {
|
||||
when.modal.open();
|
||||
const firstId = when.modal.fillLayers({
|
||||
id: "a",
|
||||
type: "background",
|
||||
});
|
||||
when.modal.open();
|
||||
const secondId = when.modal.fillLayers({
|
||||
id: "b",
|
||||
type: "background",
|
||||
});
|
||||
when.modal.open();
|
||||
const thirdId = when.modal.fillLayers({
|
||||
id: "c",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
when.dragAndDropWithWait("layer-list-item:" + firstId, "layer-list-item:" + thirdId);
|
||||
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: secondId,
|
||||
type: "background",
|
||||
},
|
||||
{
|
||||
id: thirdId,
|
||||
type: "background",
|
||||
},
|
||||
{
|
||||
id: firstId,
|
||||
type: "background",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("sticky header", () => {
|
||||
it("should keep header visible when scrolling layer list", () => {
|
||||
// Setup: Create multiple layers to enable scrolling
|
||||
for (let i = 0; i < 20; i++) {
|
||||
when.modal.open();
|
||||
when.modal.fillLayers({
|
||||
id: `layer-${i}`,
|
||||
type: "background",
|
||||
});
|
||||
}
|
||||
|
||||
when.wait(500);
|
||||
const header = get.elementByTestId("layer-list.header");
|
||||
then(header).shouldBeVisible();
|
||||
|
||||
// Scroll the layer list container (use ensureScrollable: false to avoid flakiness)
|
||||
get.elementByTestId("layer-list").scrollTo("bottom", { ensureScrollable: false });
|
||||
when.wait(200);
|
||||
then(header).shouldBeVisible();
|
||||
then(get.elementByTestId("layer-list:add-layer")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
describe("map", () => {
|
||||
const { beforeAndAfter, get, when, then } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
describe("zoom level", () => {
|
||||
it("via url", () => {
|
||||
const zoomLevel = 12.37;
|
||||
when.setStyle("geojson", zoomLevel);
|
||||
then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible();
|
||||
then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText(
|
||||
"Zoom: " + zoomLevel
|
||||
);
|
||||
});
|
||||
|
||||
it("via map controls", () => {
|
||||
const zoomLevel = 12.37;
|
||||
when.setStyle("geojson", zoomLevel);
|
||||
then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible();
|
||||
when.clickZoomIn();
|
||||
then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText(
|
||||
"Zoom: " + (zoomLevel + 1)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("search", () => {
|
||||
it("should exist", () => {
|
||||
then(get.searchControl()).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
describe("popup", () => {
|
||||
beforeEach(() => {
|
||||
when.setStyle("rectangles");
|
||||
});
|
||||
it("should open on feature click", () => {
|
||||
when.clickCenter("maplibre:map");
|
||||
then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
|
||||
});
|
||||
|
||||
it("should open a second feature after closing popup", () => {
|
||||
when.clickCenter("maplibre:map");
|
||||
then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
|
||||
when.closePopup();
|
||||
then(get.elementByTestId("feature-layer-popup")).shouldNotExist();
|
||||
when.clickCenter("maplibre:map");
|
||||
then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/// <reference types="cypress-real-events" />
|
||||
import { CypressHelper } from "@shellygo/cypress-test-utils";
|
||||
import "cypress-real-events/support";
|
||||
|
||||
export default class MaputnikCypressHelper {
|
||||
private helper = new CypressHelper({ defaultDataAttribute: "data-wd-key" });
|
||||
|
||||
public given = {
|
||||
...this.helper.given,
|
||||
};
|
||||
|
||||
public get = {
|
||||
...this.helper.get,
|
||||
};
|
||||
|
||||
public when = {
|
||||
dragAndDropWithWait: (element: string, targetElement: string) => {
|
||||
this.helper.get.elementByTestId(element).realMouseDown({ button: "left", position: "center" });
|
||||
this.helper.get.elementByTestId(element).realMouseMove(0, 10, { position: "center" });
|
||||
this.helper.get.elementByTestId(targetElement).realMouseMove(0, 0, { position: "center" });
|
||||
this.helper.when.wait(1);
|
||||
this.helper.get.elementByTestId(targetElement).realMouseUp();
|
||||
},
|
||||
clickCenter: (element: string) => {
|
||||
this.helper.get.elementByTestId(element).realMouseDown({ button: "left", position: "center" });
|
||||
this.helper.when.wait(200);
|
||||
this.helper.get.elementByTestId(element).realMouseUp();
|
||||
},
|
||||
openFileByFixture: (fixture: string, buttonTestId: string, inputTestId: string) => {
|
||||
cy.window().then((win) => {
|
||||
const file = {
|
||||
text: cy.stub().resolves(cy.fixture(fixture).then(JSON.stringify)),
|
||||
};
|
||||
const fileHandle = {
|
||||
getFile: cy.stub().resolves(file),
|
||||
};
|
||||
if (!win.showOpenFilePicker) {
|
||||
this.helper.get.elementByTestId(inputTestId).selectFile("cypress/fixtures/" + fixture, { force: true });
|
||||
} else {
|
||||
cy.stub(win, "showOpenFilePicker").resolves([fileHandle]);
|
||||
this.helper.get.elementByTestId(buttonTestId).click();
|
||||
}
|
||||
});
|
||||
},
|
||||
...this.helper.when,
|
||||
};
|
||||
|
||||
public beforeAndAfter = this.helper.beforeAndAfter;
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/// <reference types="cypress-plugin-tab" />
|
||||
|
||||
import { CypressHelper } from "@shellygo/cypress-test-utils";
|
||||
import { Assertable, then } from "@shellygo/cypress-test-utils/assertable";
|
||||
import MaputnikCypressHelper from "./maputnik-cypress-helper";
|
||||
import ModalDriver from "./modal-driver";
|
||||
const baseUrl = "http://localhost:8888/";
|
||||
|
||||
const styleFromWindow = (win: Window) => {
|
||||
const styleId = win.localStorage.getItem("maputnik:latest_style");
|
||||
const styleItemKey = `maputnik:style:${styleId}`;
|
||||
const styleItem = win.localStorage.getItem(styleItemKey);
|
||||
if (!styleItem) throw new Error("Could not get styleItem from localStorage");
|
||||
const obj = JSON.parse(styleItem);
|
||||
return obj;
|
||||
};
|
||||
|
||||
export class MaputnikAssertable<T> extends Assertable<T> {
|
||||
shouldEqualToStoredStyle = () =>
|
||||
then(
|
||||
new CypressHelper().get.window().then((win: Window) => {
|
||||
const style = styleFromWindow(win);
|
||||
then(this.chainable).shouldDeepNestedInclude(style);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export class MaputnikDriver {
|
||||
private helper = new MaputnikCypressHelper();
|
||||
private modalDriver = new ModalDriver();
|
||||
|
||||
public beforeAndAfter = () => {
|
||||
beforeEach(() => {
|
||||
this.given.setupMockBackedResponses();
|
||||
this.when.setStyle("both");
|
||||
});
|
||||
};
|
||||
|
||||
public then = (chainable: Cypress.Chainable<any>) =>
|
||||
new MaputnikAssertable(chainable);
|
||||
|
||||
public given = {
|
||||
...this.helper.given,
|
||||
setupMockBackedResponses: () => {
|
||||
this.helper.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: baseUrl + "example-style.json",
|
||||
response: {
|
||||
fixture: "example-style.json",
|
||||
},
|
||||
alias: "example-style.json",
|
||||
});
|
||||
this.helper.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: baseUrl + "example-layer-style.json",
|
||||
response: {
|
||||
fixture: "example-layer-style.json",
|
||||
},
|
||||
});
|
||||
this.helper.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: baseUrl + "geojson-style.json",
|
||||
response: {
|
||||
fixture: "geojson-style.json",
|
||||
},
|
||||
});
|
||||
this.helper.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: baseUrl + "raster-style.json",
|
||||
response: {
|
||||
fixture: "raster-style.json",
|
||||
},
|
||||
});
|
||||
this.helper.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: baseUrl + "geojson-raster-style.json",
|
||||
response: {
|
||||
fixture: "geojson-raster-style.json",
|
||||
},
|
||||
});
|
||||
this.helper.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: baseUrl + "rectangles-style.json",
|
||||
response: {
|
||||
fixture: "rectangles-style.json",
|
||||
},
|
||||
});
|
||||
this.helper.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: baseUrl + "example-style-with-fonts.json",
|
||||
response: {
|
||||
fixture: "example-style-with-fonts.json",
|
||||
},
|
||||
});
|
||||
this.helper.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: "*example.local/*",
|
||||
response: [],
|
||||
});
|
||||
this.helper.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: "*example.com/*",
|
||||
response: [],
|
||||
});
|
||||
this.helper.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: "https://www.glyph-server.com/*",
|
||||
response: ["Font 1", "Font 2", "Font 3"],
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
public when = {
|
||||
...this.helper.when,
|
||||
modal: this.modalDriver.when,
|
||||
doWithin: (selector: string, fn: () => void) => {
|
||||
this.helper.when.doWithin(fn, selector);
|
||||
},
|
||||
tab: () => this.helper.get.element("body").tab(),
|
||||
waitForExampleFileResponse: () => {
|
||||
this.helper.when.waitForResponse("example-style.json");
|
||||
},
|
||||
chooseExampleFile: () => {
|
||||
this.helper.given.fixture("example-style.json", "example-style.json");
|
||||
this.helper.when.openFileByFixture("example-style.json", "modal:open.file.button", "modal:open.file.input");
|
||||
this.helper.when.wait(200);
|
||||
},
|
||||
setStyle: (
|
||||
styleProperties: "geojson" | "raster" | "both" | "layer" | "rectangles" | "font" | "",
|
||||
zoom?: number
|
||||
) => {
|
||||
const url = new URL(baseUrl);
|
||||
switch (styleProperties) {
|
||||
case "geojson":
|
||||
url.searchParams.set("style", baseUrl + "geojson-style.json");
|
||||
break;
|
||||
case "raster":
|
||||
url.searchParams.set("style", baseUrl + "raster-style.json");
|
||||
break;
|
||||
case "both":
|
||||
url.searchParams.set("style", baseUrl + "geojson-raster-style.json");
|
||||
break;
|
||||
case "layer":
|
||||
url.searchParams.set("style", baseUrl + "example-layer-style.json");
|
||||
break;
|
||||
case "rectangles":
|
||||
url.searchParams.set("style", baseUrl + "rectangles-style.json");
|
||||
break;
|
||||
case "font":
|
||||
url.searchParams.set("style", baseUrl + "example-style-with-fonts.json");
|
||||
break;
|
||||
}
|
||||
|
||||
if (zoom) {
|
||||
url.hash = `${zoom}/41.3805/2.1635`;
|
||||
}
|
||||
this.helper.when.visit(url.toString());
|
||||
if (styleProperties) {
|
||||
this.helper.when.acceptConfirm();
|
||||
}
|
||||
// when methods should not include assertions
|
||||
const toolbarLink = this.helper.get.elementByTestId("toolbar:link");
|
||||
toolbarLink.scrollIntoView();
|
||||
toolbarLink.should("be.visible");
|
||||
},
|
||||
|
||||
typeKeys: (keys: string) => this.helper.get.element("body").type(keys),
|
||||
|
||||
clickZoomIn: () => {
|
||||
this.helper.get.element(".maplibregl-ctrl-zoom-in").click();
|
||||
},
|
||||
|
||||
selectWithin: (selector: string, value: string) => {
|
||||
this.when.doWithin(selector, () => {
|
||||
this.helper.get.element("select").select(value);
|
||||
});
|
||||
},
|
||||
|
||||
select: (selector: string, value: string) => {
|
||||
this.helper.get.elementByTestId(selector).select(value);
|
||||
},
|
||||
|
||||
focus: (selector: string) => {
|
||||
this.helper.when.focus(selector);
|
||||
},
|
||||
|
||||
setValue: (selector: string, text: string) => {
|
||||
this.helper.get
|
||||
.elementByTestId(selector)
|
||||
.clear()
|
||||
.type(text, { parseSpecialCharSequences: false });
|
||||
},
|
||||
|
||||
setValueToPropertyArray: (selector: string, value: string) => {
|
||||
this.when.doWithin(selector, () => {
|
||||
this.helper.get.element(".maputnik-array-block-content input").last().type("{selectall}"+value, {force: true });
|
||||
});
|
||||
},
|
||||
|
||||
addValueToPropertyArray: (selector: string, value: string) => {
|
||||
this.when.doWithin(selector, () => {
|
||||
this.helper.get.element(".maputnik-array-add-value").click({ force: true });
|
||||
this.helper.get.element(".maputnik-array-block-content input").last().type("{selectall}"+value, {force: true });
|
||||
});
|
||||
},
|
||||
|
||||
closePopup: () => {
|
||||
this.helper.get.element(".maplibregl-popup-close-button").click();
|
||||
},
|
||||
|
||||
collapseGroupInLayerEditor: (index = 0) => {
|
||||
this.helper.get.element(".maputnik-layer-editor-group__button").eq(index).realClick();
|
||||
},
|
||||
|
||||
appendTextInJsonEditor: (text: string) => {
|
||||
this.helper.get.element(".cm-line").first().click().type(text, { parseSpecialCharSequences: false });
|
||||
},
|
||||
|
||||
setTextInJsonEditor: (text: string) => {
|
||||
this.helper.get.element(".cm-line").first().click().clear().type(text, { parseSpecialCharSequences: false });
|
||||
}
|
||||
};
|
||||
|
||||
public get = {
|
||||
...this.helper.get,
|
||||
isMac: () => {
|
||||
return Cypress.platform === "darwin";
|
||||
},
|
||||
|
||||
styleFromLocalStorage: () =>
|
||||
this.helper.get.window().then((win) => styleFromWindow(win)),
|
||||
|
||||
exampleFileUrl: () => {
|
||||
return baseUrl + "example-style.json";
|
||||
},
|
||||
skipTargetLayerList: () =>
|
||||
this.helper.get.elementByTestId("skip-target-layer-list"),
|
||||
skipTargetLayerEditor: () =>
|
||||
this.helper.get.elementByTestId("skip-target-layer-editor"),
|
||||
canvas: () => this.helper.get.element("canvas"),
|
||||
searchControl: () => this.helper.get.element(".maplibregl-ctrl-geocoder")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { v1 as uuid } from "uuid";
|
||||
import MaputnikCypressHelper from "./maputnik-cypress-helper";
|
||||
|
||||
export default class ModalDriver {
|
||||
private helper = new MaputnikCypressHelper();
|
||||
|
||||
public when = {
|
||||
fillLayers: (opts: { type: string; layer?: string; id?: string }) => {
|
||||
// Having logic in test code is an anti pattern.
|
||||
// This should be splitted to multiple single responsibility functions
|
||||
const type = opts.type;
|
||||
const layer = opts.layer;
|
||||
let id;
|
||||
if (opts.id) {
|
||||
id = opts.id;
|
||||
} else {
|
||||
id = `${type}:${uuid()}`;
|
||||
}
|
||||
this.helper.when.selectOption("add-layer.layer-type.select", type);
|
||||
this.helper.when.type("add-layer.layer-id.input", id);
|
||||
|
||||
if (layer) {
|
||||
this.helper.when.doWithin(() => {
|
||||
this.helper.get.element("input").clear().type(layer!);
|
||||
}, "add-layer.layer-source-block");
|
||||
}
|
||||
this.helper.when.click("add-layer");
|
||||
|
||||
return id;
|
||||
},
|
||||
|
||||
open: () => {
|
||||
this.helper.when.click("layer-list:add-layer");
|
||||
},
|
||||
|
||||
close: (key: string) => {
|
||||
this.helper.when.click(key + ".close-modal");
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
import tokens from "../../src/config/tokens.json" with {type: "json"};
|
||||
|
||||
describe("modals", () => {
|
||||
const { beforeAndAfter, when, get, given, then } = new MaputnikDriver();
|
||||
beforeAndAfter();
|
||||
|
||||
beforeEach(() => {
|
||||
when.setStyle("");
|
||||
});
|
||||
describe("open", () => {
|
||||
beforeEach(() => {
|
||||
when.click("nav:open");
|
||||
});
|
||||
|
||||
it("close", () => {
|
||||
when.modal.close("modal:open");
|
||||
then(get.elementByTestId("modal:open")).shouldNotExist();
|
||||
});
|
||||
|
||||
it("upload", () => {
|
||||
when.chooseExampleFile();
|
||||
then(get.fixture("example-style.json")).shouldEqualToStoredStyle();
|
||||
});
|
||||
|
||||
describe("when click open url", () => {
|
||||
beforeEach(() => {
|
||||
const styleFileUrl = get.exampleFileUrl();
|
||||
|
||||
when.setValue("modal:open.url.input", styleFileUrl);
|
||||
when.click("modal:open.url.button");
|
||||
when.wait(200);
|
||||
});
|
||||
it("load from url", () => {
|
||||
then(get.responseBody("example-style.json")).shouldEqualToStoredStyle();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("shortcuts", () => {
|
||||
it("open/close", () => {
|
||||
when.setStyle("");
|
||||
when.typeKeys("?");
|
||||
when.modal.close("modal:shortcuts");
|
||||
then(get.elementByTestId("modal:shortcuts")).shouldNotExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe("export", () => {
|
||||
beforeEach(() => {
|
||||
when.click("nav:export");
|
||||
});
|
||||
|
||||
it("close", () => {
|
||||
when.modal.close("modal:export");
|
||||
then(get.elementByTestId("modal:export")).shouldNotExist();
|
||||
});
|
||||
|
||||
// TODO: Work out how to download a file and check the contents
|
||||
it("download");
|
||||
});
|
||||
|
||||
describe("sources", () => {
|
||||
beforeEach(() => {
|
||||
when.setStyle("layer");
|
||||
when.click("nav:sources");
|
||||
});
|
||||
|
||||
it("active sources");
|
||||
it("public source");
|
||||
|
||||
it("add new source", () => {
|
||||
const sourceId = "n1z2v3r";
|
||||
when.setValue("modal:sources.add.source_id", sourceId);
|
||||
when.select("modal:sources.add.source_type", "tile_vector");
|
||||
when.select("modal:sources.add.scheme_type", "tms");
|
||||
when.click("modal:sources.add.add_source");
|
||||
when.wait(200);
|
||||
then(
|
||||
get.styleFromLocalStorage().then((style) => style.sources[sourceId])
|
||||
).shouldInclude({
|
||||
scheme: "tms",
|
||||
});
|
||||
});
|
||||
|
||||
it("add new pmtiles source", () => {
|
||||
const sourceId = "pmtilestest";
|
||||
when.setValue("modal:sources.add.source_id", sourceId);
|
||||
when.select("modal:sources.add.source_type", "pmtiles_vector");
|
||||
when.setValue("modal:sources.add.source_url", "https://data.source.coop/protomaps/openstreetmap/v4.pmtiles");
|
||||
when.click("modal:sources.add.add_source");
|
||||
when.click("modal:sources.add.add_source");
|
||||
when.wait(200);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
sources: {
|
||||
pmtilestest: {
|
||||
type: "vector",
|
||||
url: "pmtiles://https://data.source.coop/protomaps/openstreetmap/v4.pmtiles",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("add new raster source", () => {
|
||||
const sourceId = "rastertest";
|
||||
when.setValue("modal:sources.add.source_id", sourceId);
|
||||
when.select("modal:sources.add.source_type", "tile_raster");
|
||||
when.select("modal:sources.add.scheme_type", "xyz");
|
||||
when.setValue("modal:sources.add.tile_size", "128");
|
||||
when.click("modal:sources.add.add_source");
|
||||
when.wait(200);
|
||||
then(
|
||||
get.styleFromLocalStorage().then((style) => style.sources[sourceId])
|
||||
).shouldInclude({
|
||||
tileSize: 128,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("inspect", () => {
|
||||
it("toggle", () => {
|
||||
// There is no assertion in this test
|
||||
when.setStyle("geojson");
|
||||
when.select("maputnik-select", "inspect");
|
||||
});
|
||||
});
|
||||
|
||||
describe("style settings", () => {
|
||||
beforeEach(() => {
|
||||
when.click("nav:settings");
|
||||
});
|
||||
|
||||
describe("when click name filed spec information", () => {
|
||||
beforeEach(() => {
|
||||
when.click("field-doc-button-Name");
|
||||
});
|
||||
|
||||
it("should show the spec information", () => {
|
||||
then(get.elementsText("spec-field-doc")).shouldInclude(
|
||||
"name for the style"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when set name and click owner", () => {
|
||||
beforeEach(() => {
|
||||
when.setValue("modal:settings.name", "foobar");
|
||||
when.click("modal:settings.owner");
|
||||
when.wait(200);
|
||||
});
|
||||
|
||||
it("show name specifications", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
name: "foobar",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when set owner and click name", () => {
|
||||
beforeEach(() => {
|
||||
when.setValue("modal:settings.owner", "foobar");
|
||||
when.click("modal:settings.name");
|
||||
when.wait(200);
|
||||
});
|
||||
it("should update owner in local storage", () => {
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
owner: "foobar",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("sprite url", () => {
|
||||
when.setTextInJsonEditor("\"http://example.com\"");
|
||||
when.click("modal:settings.name");
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
sprite: "http://example.com",
|
||||
});
|
||||
});
|
||||
|
||||
it("sprite object", () => {
|
||||
when.setTextInJsonEditor(JSON.stringify([{id: "1", url: "2"}]));
|
||||
|
||||
when.click("modal:settings.name");
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
sprite: [{ id: "1", url: "2"}],
|
||||
});
|
||||
});
|
||||
|
||||
it("glyphs url", () => {
|
||||
const glyphsUrl = "http://example.com/{fontstack}/{range}.pbf";
|
||||
when.setValue("modal:settings.glyphs", glyphsUrl);
|
||||
when.click("modal:settings.name");
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
glyphs: glyphsUrl,
|
||||
});
|
||||
});
|
||||
|
||||
it("maptiler access token", () => {
|
||||
const apiKey = "testing123";
|
||||
when.setValue(
|
||||
"modal:settings.maputnik:openmaptiles_access_token",
|
||||
apiKey
|
||||
);
|
||||
when.click("modal:settings.name");
|
||||
then(
|
||||
get.styleFromLocalStorage().then((style) => style.metadata)
|
||||
).shouldInclude({
|
||||
"maputnik:openmaptiles_access_token": apiKey,
|
||||
});
|
||||
});
|
||||
|
||||
it("thunderforest access token", () => {
|
||||
const apiKey = "testing123";
|
||||
when.setValue(
|
||||
"modal:settings.maputnik:thunderforest_access_token",
|
||||
apiKey
|
||||
);
|
||||
when.click("modal:settings.name");
|
||||
then(
|
||||
get.styleFromLocalStorage().then((style) => style.metadata)
|
||||
).shouldInclude({ "maputnik:thunderforest_access_token": apiKey });
|
||||
});
|
||||
|
||||
it("stadia access token", () => {
|
||||
const apiKey = "testing123";
|
||||
when.setValue(
|
||||
"modal:settings.maputnik:stadia_access_token",
|
||||
apiKey
|
||||
);
|
||||
when.click("modal:settings.name");
|
||||
then(
|
||||
get.styleFromLocalStorage().then((style) => style.metadata)
|
||||
).shouldInclude({ "maputnik:stadia_access_token": apiKey });
|
||||
});
|
||||
|
||||
it("locationiq access token", () => {
|
||||
const apiKey = "testing123";
|
||||
when.setValue(
|
||||
"modal:settings.maputnik:locationiq_access_token",
|
||||
apiKey
|
||||
);
|
||||
when.click("modal:settings.name");
|
||||
then(
|
||||
get.styleFromLocalStorage().then((style) => style.metadata)
|
||||
).shouldInclude({ "maputnik:locationiq_access_token": apiKey });
|
||||
});
|
||||
|
||||
it("style projection mercator", () => {
|
||||
when.select("modal:settings.projection", "mercator");
|
||||
then(
|
||||
get.styleFromLocalStorage().then((style) => style.projection)
|
||||
).shouldInclude({ type: "mercator" });
|
||||
});
|
||||
|
||||
it("style projection globe", () => {
|
||||
when.select("modal:settings.projection", "globe");
|
||||
then(
|
||||
get.styleFromLocalStorage().then((style) => style.projection)
|
||||
).shouldInclude({ type: "globe" });
|
||||
});
|
||||
|
||||
|
||||
it("style projection vertical-perspective", () => {
|
||||
when.select("modal:settings.projection", "vertical-perspective");
|
||||
then(
|
||||
get.styleFromLocalStorage().then((style) => style.projection)
|
||||
).shouldInclude({ type: "vertical-perspective" });
|
||||
|
||||
});
|
||||
|
||||
it("style renderer", () => {
|
||||
cy.on("uncaught:exception", () => false); // this is due to the fact that this is an invalid style for openlayers
|
||||
when.select("modal:settings.maputnik:renderer", "ol");
|
||||
then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual(
|
||||
"ol"
|
||||
);
|
||||
|
||||
when.click("modal:settings.name");
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
metadata: { "maputnik:renderer": "ol" },
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
it("inlcude API key when change renderer", () => {
|
||||
|
||||
when.click("modal:settings.close-modal");
|
||||
when.click("nav:open");
|
||||
|
||||
get.elementByAttribute("aria-label", "MapTiler Basic").should("exist").click();
|
||||
when.wait(1000);
|
||||
when.click("nav:settings");
|
||||
|
||||
when.select("modal:settings.maputnik:renderer", "mlgljs");
|
||||
then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual(
|
||||
"mlgljs"
|
||||
);
|
||||
|
||||
when.select("modal:settings.maputnik:renderer", "ol");
|
||||
then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual(
|
||||
"ol"
|
||||
);
|
||||
|
||||
given.intercept("https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=*", "tileRequest", "GET");
|
||||
|
||||
when.select("modal:settings.maputnik:renderer", "mlgljs");
|
||||
then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual(
|
||||
"mlgljs"
|
||||
);
|
||||
|
||||
when.waitForResponse("tileRequest").its("request").its("url").should("include", `https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=${tokens.openmaptiles}`);
|
||||
when.waitForResponse("tileRequest").its("request").its("url").should("include", `https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=${tokens.openmaptiles}`);
|
||||
when.waitForResponse("tileRequest").its("request").its("url").should("include", `https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=${tokens.openmaptiles}`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("add layer", () => {
|
||||
beforeEach(() => {
|
||||
when.setStyle("layer");
|
||||
when.modal.open();
|
||||
});
|
||||
|
||||
it("shows duplicate id error", () => {
|
||||
when.setValue("add-layer.layer-id.input", "background");
|
||||
when.click("add-layer");
|
||||
then(get.elementByTestId("modal:add-layer")).shouldExist();
|
||||
then(get.element(".maputnik-modal-error")).shouldContainText(
|
||||
"Layer ID already exists"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sources", () => {
|
||||
it("toggle");
|
||||
});
|
||||
|
||||
describe("global state", () => {
|
||||
beforeEach(() => {
|
||||
when.click("nav:global-state");
|
||||
});
|
||||
|
||||
it("add variable", () => {
|
||||
when.click("global-state-add-variable");
|
||||
when.wait(100);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
state: { key1: { default: "value" } },
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("add multiple variables", () => {
|
||||
when.click("global-state-add-variable");
|
||||
when.click("global-state-add-variable");
|
||||
when.click("global-state-add-variable");
|
||||
when.wait(100);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
state: { key1: { default: "value" }, key2: { default: "value" }, key3: { default: "value" } },
|
||||
});
|
||||
});
|
||||
|
||||
it("remove variable", () => {
|
||||
when.click("global-state-add-variable");
|
||||
when.click("global-state-add-variable");
|
||||
when.click("global-state-add-variable");
|
||||
when.click("global-state-remove-variable", 0);
|
||||
when.wait(100);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
state: { key2: { default: "value" }, key3: { default: "value" } },
|
||||
});
|
||||
});
|
||||
|
||||
it("edit variable key", () => {
|
||||
when.click("global-state-add-variable");
|
||||
when.setValue("global-state-variable-key:0", "mykey");
|
||||
when.typeKeys("{enter}");
|
||||
when.wait(100);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
state: { mykey: { default: "value" } },
|
||||
});
|
||||
});
|
||||
|
||||
it("edit variable value", () => {
|
||||
when.click("global-state-add-variable");
|
||||
when.setValue("global-state-variable-value:0", "myvalue");
|
||||
when.typeKeys("{enter}");
|
||||
when.wait(100);
|
||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
state: { key1: { default: "myvalue" } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("error panel", () => {
|
||||
it("not visible when no errors", () => {
|
||||
then(get.element("maputnik-message-panel-error")).shouldNotExist();
|
||||
});
|
||||
|
||||
it("visible on style error", () => {
|
||||
when.modal.open();
|
||||
when.modal.fillLayers({
|
||||
type: "circle",
|
||||
layer: "invalid",
|
||||
});
|
||||
then(get.element(".maputnik-message-panel-error")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Handle localStorage QuotaExceededError", () => {
|
||||
it("handles quota exceeded error when opening style from URL", () => {
|
||||
// Clear localStorage to start fresh
|
||||
cy.clearLocalStorage();
|
||||
|
||||
// fill localStorage until we get a QuotaExceededError
|
||||
cy.window().then(win => {
|
||||
let chunkSize = 1000;
|
||||
const chunk = new Array(chunkSize).join("x");
|
||||
let index = 0;
|
||||
|
||||
// Keep adding until we hit the quota
|
||||
while (true) {
|
||||
try {
|
||||
const key = `maputnik:fill-${index++}`;
|
||||
win.localStorage.setItem(key, chunk);
|
||||
} catch (e: any) {
|
||||
// Verify it's a quota error
|
||||
if (e.name === "QuotaExceededError") {
|
||||
if (chunkSize <= 1) return;
|
||||
else {
|
||||
chunkSize /= 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw e; // Unexpected error
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Open the style via URL input
|
||||
when.click("nav:open");
|
||||
when.setValue("modal:open.url.input", get.exampleFileUrl());
|
||||
when.click("modal:open.url.button");
|
||||
|
||||
then(get.responseBody("example-style.json")).shouldEqualToStoredStyle();
|
||||
then(get.styleFromLocalStorage()).shouldExist();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
// declare global {
|
||||
// namespace Cypress {
|
||||
// interface Chainable {
|
||||
// login(email: string, password: string): Chainable<void>
|
||||
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Components App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div data-cy-root></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
// ***********************************************************
|
||||
// This example support/component.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 "./commands";
|
||||
|
||||
import { mount } from "cypress/react";
|
||||
|
||||
// Augment the Cypress namespace to include type definitions for
|
||||
// your custom command.
|
||||
// Alternatively, can be defined in cypress/support/component.d.ts
|
||||
// with a <reference path="./component" /> at the top of your spec.
|
||||
declare global {
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
mount: typeof mount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add("mount", mount);
|
||||
|
||||
// Example use:
|
||||
// cy.mount(<MyComponent />)
|
||||
@@ -0,0 +1,22 @@
|
||||
// ***********************************************************
|
||||
// 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/code-coverage/support";
|
||||
import "cypress-plugin-tab";
|
||||
import "./commands";
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -1,48 +0,0 @@
|
||||
import { test } from "./fixtures";
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
test.describe("accessibility", () => {
|
||||
const { given, get, when, then } = new MaputnikDriver();
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await given.setupMockBackedResponses();
|
||||
await when.setStyle("both");
|
||||
});
|
||||
|
||||
test.describe("skip links", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.setStyle("layer");
|
||||
});
|
||||
|
||||
test("skip link to layer list", async () => {
|
||||
const selector = "root:skip:layer-list";
|
||||
await then(get.elementByTestId(selector)).shouldExist();
|
||||
await when.tab();
|
||||
await then(get.elementByTestId(selector)).shouldBeFocused();
|
||||
await when.click(selector);
|
||||
await then(get.skipTargetLayerList()).shouldBeFocused();
|
||||
});
|
||||
|
||||
test("skip link to layer editor", async () => {
|
||||
const selector = "root:skip:layer-editor";
|
||||
await then(get.elementByTestId(selector)).shouldExist();
|
||||
await then(get.elementByTestId("skip-target-layer-editor")).shouldExist();
|
||||
await when.tab();
|
||||
await when.tab();
|
||||
await then(get.elementByTestId(selector)).shouldBeFocused();
|
||||
await when.click(selector);
|
||||
await then(get.skipTargetLayerEditor()).shouldBeFocused();
|
||||
});
|
||||
|
||||
test("skip link to map view", async () => {
|
||||
const selector = "root:skip:map-view";
|
||||
await then(get.elementByTestId(selector)).shouldExist();
|
||||
await when.tab();
|
||||
await when.tab();
|
||||
await when.tab();
|
||||
await then(get.elementByTestId(selector)).shouldBeFocused();
|
||||
await when.click(selector);
|
||||
await then(get.canvas()).shouldBeFocused();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import { test } from "./fixtures";
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
test.describe("code editor", () => {
|
||||
const { given, get, when, then } = new MaputnikDriver();
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await given.setupMockBackedResponses();
|
||||
await when.setStyle("both");
|
||||
});
|
||||
|
||||
test("open code editor", async () => {
|
||||
await when.click("nav:code-editor");
|
||||
await then(get.element(".maputnik-code-editor")).shouldExist();
|
||||
});
|
||||
|
||||
test("closes code editor", async () => {
|
||||
await when.click("nav:code-editor");
|
||||
await then(get.element(".maputnik-code-editor")).shouldExist();
|
||||
await when.click("nav:code-editor");
|
||||
await then(get.element(".maputnik-code-editor")).shouldNotExist();
|
||||
});
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { Page } from "@playwright/test";
|
||||
|
||||
const OUTPUT_DIR = path.resolve(process.cwd(), ".nyc_output");
|
||||
|
||||
/**
|
||||
* Reads the istanbul coverage object (injected by vite-plugin-istanbul) from the
|
||||
* given page. Returns `null` when the page has not been instrumented.
|
||||
*/
|
||||
export async function readCoverage(page: Page): Promise<unknown | null> {
|
||||
try {
|
||||
return await page.evaluate(() => (window as unknown as { __coverage__?: unknown }).__coverage__ ?? null);
|
||||
} catch {
|
||||
// Page might be navigating/closed.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists a coverage chunk to `.nyc_output` so that `nyc report` can merge it.
|
||||
* istanbul-lib-coverage (used by nyc) sums the hit counts across every file it
|
||||
* finds, so writing one file per chunk is enough to accumulate coverage across
|
||||
* navigations and tests.
|
||||
*/
|
||||
export function writeCoverage(coverage: unknown, id: string): void {
|
||||
if (!coverage) return;
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
fs.writeFileSync(path.join(OUTPUT_DIR, `playwright-${id}.json`), JSON.stringify(coverage));
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { test as base, expect, type Page } from "@playwright/test";
|
||||
import { readCoverage, writeCoverage } from "./coverage";
|
||||
|
||||
let activePage: Page | undefined;
|
||||
const coverageChunks: unknown[] = [];
|
||||
|
||||
/** The page for the currently running test. Throws if used outside a test. */
|
||||
export function currentPage(): Page {
|
||||
if (!activePage) {
|
||||
throw new Error("No active page: a MaputnikDriver method was called outside of a running test.");
|
||||
}
|
||||
return activePage;
|
||||
}
|
||||
|
||||
/** Records a coverage snapshot (called before navigations, which reset __coverage__). */
|
||||
export function recordCoverageChunk(chunk: unknown): void {
|
||||
if (chunk) coverageChunks.push(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto fixture that binds the current test's page for the (page-lazy)
|
||||
* MaputnikDriver, auto-accepts confirm dialogs, and writes the istanbul
|
||||
* coverage collected during the test to `.nyc_output`.
|
||||
*/
|
||||
export const test = base.extend<{ maputnikPage: void }>({
|
||||
maputnikPage: [
|
||||
async ({ page }, use, testInfo) => {
|
||||
activePage = page;
|
||||
coverageChunks.length = 0;
|
||||
// Accept confirm dialogs (e.g. the "replace current style" prompt). These
|
||||
// are dismissed by default, which would cancel loading a style via URL.
|
||||
page.on("dialog", (dialog) => dialog.accept().catch(() => undefined));
|
||||
|
||||
await use();
|
||||
|
||||
const finalCoverage = await readCoverage(page);
|
||||
if (finalCoverage) coverageChunks.push(finalCoverage);
|
||||
coverageChunks.forEach((chunk, index) => writeCoverage(chunk, `${testInfo.testId}-${index}`));
|
||||
coverageChunks.length = 0;
|
||||
activePage = undefined;
|
||||
},
|
||||
{ auto: true },
|
||||
],
|
||||
});
|
||||
|
||||
export { expect };
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"id": "test-style",
|
||||
"center": [50,50],
|
||||
"zoom": 5,
|
||||
"version": 8,
|
||||
"name": "Test Style",
|
||||
"sources": {
|
||||
"rectangles": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "FeatureCollection",
|
||||
"features": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"glyphs": "https://example.local/fonts/{fontstack}/{range}.pbf",
|
||||
"sprites": "https://example.local/fonts/{fontstack}/{range}.pbf",
|
||||
"layers": []
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"id": "test-style",
|
||||
"center": [0,51],
|
||||
"zoom": 7,
|
||||
"version": 8,
|
||||
"name": "Test Style",
|
||||
"sources": {
|
||||
"rectangles": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "FeatureCollection",
|
||||
"features": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"layers": []
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
* Clears the istanbul coverage output directory before the e2e run so stale
|
||||
* coverage from previous runs is not merged into the report.
|
||||
*/
|
||||
export default function globalSetup(): void {
|
||||
const dir = path.resolve(process.cwd(), ".nyc_output");
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
/**
|
||||
* Merges the per-test istanbul coverage chunks collected in `.nyc_output` into a
|
||||
* report (configured by `.nycrc.json`) once the whole e2e run has finished.
|
||||
*/
|
||||
export default function globalTeardown(): void {
|
||||
const dir = path.resolve(process.cwd(), ".nyc_output");
|
||||
const hasCoverage = fs.existsSync(dir) && fs.readdirSync(dir).some((f) => f.endsWith(".json"));
|
||||
if (!hasCoverage) {
|
||||
console.warn("No coverage data collected; skipping coverage report.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
execFileSync("npx", ["nyc", "report"], { stdio: "inherit" });
|
||||
} catch (error) {
|
||||
// Don't fail the whole run if the report can't be generated (e.g. when
|
||||
// running against a container whose source paths differ from the host).
|
||||
console.warn("Failed to generate coverage report:", error);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { test } from "./fixtures";
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
test.describe("history", () => {
|
||||
const { given, get, when, then } = new MaputnikDriver();
|
||||
|
||||
const undoKeyCombo = process.platform === "darwin" ? "{meta}z" : "{ctrl}z";
|
||||
const redoKeyCombo = process.platform === "darwin" ? "{meta}{shift}z" : "{ctrl}y";
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await given.setupMockBackedResponses();
|
||||
await when.setStyle("both");
|
||||
});
|
||||
|
||||
test("undo/redo", async () => {
|
||||
await when.setStyle("geojson");
|
||||
await when.modal.open();
|
||||
|
||||
await when.modal.fillLayers({
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
});
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "step 1", type: "background" }],
|
||||
});
|
||||
|
||||
await when.modal.open();
|
||||
await when.modal.fillLayers({
|
||||
id: "step 2",
|
||||
type: "background",
|
||||
});
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{ id: "step 1", type: "background" },
|
||||
{ id: "step 2", type: "background" },
|
||||
],
|
||||
});
|
||||
|
||||
await when.typeKeys(undoKeyCombo);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "step 1", type: "background" }],
|
||||
});
|
||||
|
||||
await when.typeKeys(undoKeyCombo);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ layers: [] });
|
||||
|
||||
await when.typeKeys(redoKeyCombo);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "step 1", type: "background" }],
|
||||
});
|
||||
|
||||
await when.typeKeys(redoKeyCombo);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{ id: "step 1", type: "background" },
|
||||
{ id: "step 2", type: "background" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("should not redo after undo and value change", async () => {
|
||||
await when.setStyle("geojson");
|
||||
await when.modal.open();
|
||||
await when.modal.fillLayers({
|
||||
id: "step 1",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
await when.modal.open();
|
||||
await when.modal.fillLayers({
|
||||
id: "step 2",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
await when.typeKeys(undoKeyCombo);
|
||||
await when.typeKeys(undoKeyCombo);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ layers: [] });
|
||||
|
||||
await when.modal.open();
|
||||
await when.modal.fillLayers({
|
||||
id: "step 3",
|
||||
type: "background",
|
||||
});
|
||||
|
||||
await when.typeKeys(redoKeyCombo);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "step 3", type: "background" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
import { test } from "./fixtures";
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
test.describe("i18n", () => {
|
||||
const { given, get, when, then } = new MaputnikDriver();
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await given.setupMockBackedResponses();
|
||||
await when.setStyle("both");
|
||||
});
|
||||
|
||||
test.describe("language detector", () => {
|
||||
test("English", async () => {
|
||||
await when.visit("?lng=en");
|
||||
await then(get.elementByTestId("maputnik-lang-select")).shouldHaveValue("en");
|
||||
});
|
||||
|
||||
test("Japanese", async () => {
|
||||
await when.visit("?lng=ja");
|
||||
await then(get.elementByTestId("maputnik-lang-select")).shouldHaveValue("ja");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("language switcher", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.setStyle("layer");
|
||||
});
|
||||
|
||||
test("the language switcher switches to Japanese", async () => {
|
||||
const selector = "maputnik-lang-select";
|
||||
await then(get.elementByTestId(selector)).shouldExist();
|
||||
await when.select(selector, "ja");
|
||||
await then(get.elementByTestId(selector)).shouldHaveValue("ja");
|
||||
|
||||
await then(get.elementByTestId("nav:settings")).shouldHaveText("スタイル設定");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
import { test } from "./fixtures";
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
test.describe("keyboard", () => {
|
||||
const { given, get, when, then } = new MaputnikDriver();
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await given.setupMockBackedResponses();
|
||||
await when.setStyle("both");
|
||||
});
|
||||
|
||||
test.describe("shortcuts", () => {
|
||||
test.beforeEach(async () => {
|
||||
await given.setupMockBackedResponses();
|
||||
await when.setStyle("");
|
||||
});
|
||||
|
||||
test("ESC should unfocus", async () => {
|
||||
const targetSelector = "maputnik-select";
|
||||
await when.focus(targetSelector);
|
||||
await then(get.elementByTestId(targetSelector)).shouldBeFocused();
|
||||
await when.typeKeys("{esc}");
|
||||
await then(get.elementByTestId(targetSelector)).shouldNotBeFocused();
|
||||
});
|
||||
|
||||
test("'?' should show shortcuts modal", async () => {
|
||||
await when.typeKeys("?");
|
||||
await then(get.elementByTestId("modal:shortcuts")).shouldBeVisible();
|
||||
});
|
||||
|
||||
test("'o' should show open modal", async () => {
|
||||
await when.typeKeys("o");
|
||||
await then(get.elementByTestId("modal:open")).shouldBeVisible();
|
||||
});
|
||||
|
||||
test("'e' should show export modal", async () => {
|
||||
await when.typeKeys("e");
|
||||
await then(get.elementByTestId("modal:export")).shouldBeVisible();
|
||||
});
|
||||
|
||||
test("'d' should show sources modal", async () => {
|
||||
await when.typeKeys("d");
|
||||
await then(get.elementByTestId("modal:sources")).shouldBeVisible();
|
||||
});
|
||||
|
||||
test("'s' should show settings modal", async () => {
|
||||
await when.typeKeys("s");
|
||||
await then(get.elementByTestId("modal:settings")).shouldBeVisible();
|
||||
});
|
||||
|
||||
test("'i' should change map to inspect mode", async () => {
|
||||
await when.typeKeys("i");
|
||||
await then(get.inputValue("maputnik-select")).shouldEqual("inspect");
|
||||
});
|
||||
|
||||
test("'m' should focus map", async () => {
|
||||
await when.typeKeys("m");
|
||||
await then(get.canvas()).shouldBeFocused();
|
||||
});
|
||||
|
||||
test("'!' should show debug modal", async () => {
|
||||
await when.typeKeys("!");
|
||||
await then(get.elementByTestId("modal:debug")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,251 +0,0 @@
|
||||
import { v1 as uuid } from "uuid";
|
||||
import { test } from "./fixtures";
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
test.describe("layer editor", () => {
|
||||
const { given, get, when, then } = new MaputnikDriver();
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await given.setupMockBackedResponses();
|
||||
await when.setStyle("both");
|
||||
await when.modal.open();
|
||||
});
|
||||
|
||||
async function createBackground() {
|
||||
const id = uuid();
|
||||
|
||||
await when.selectWithin("add-layer.layer-type", "background");
|
||||
await when.setValue("add-layer.layer-id.input", "background:" + id);
|
||||
|
||||
await when.click("add-layer");
|
||||
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "background:" + id, type: "background" }],
|
||||
});
|
||||
return id;
|
||||
}
|
||||
|
||||
test.skip("expand/collapse", () => {});
|
||||
|
||||
test("id", async () => {
|
||||
const bgId = await createBackground();
|
||||
|
||||
await when.click("layer-list-item:background:" + bgId);
|
||||
|
||||
const id = uuid();
|
||||
await when.setValue("layer-editor.layer-id.input", "foobar:" + id);
|
||||
await when.click("min-zoom");
|
||||
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "foobar:" + id, type: "background" }],
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("source", () => {
|
||||
test("should show error when the source is invalid", async () => {
|
||||
await when.modal.fillLayers({
|
||||
type: "circle",
|
||||
layer: "invalid",
|
||||
});
|
||||
await then(
|
||||
get.element(".maputnik-input-block--error .maputnik-input-block-label")
|
||||
).shouldHaveCss("color", "rgb(207, 74, 74)");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("min-zoom", () => {
|
||||
let bgId: string;
|
||||
|
||||
test.beforeEach(async () => {
|
||||
bgId = await createBackground();
|
||||
await when.click("layer-list-item:background:" + bgId);
|
||||
await when.setValue("min-zoom.input-text", "1");
|
||||
await when.click("layer-editor.layer-id");
|
||||
});
|
||||
|
||||
test("should update min-zoom in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "background:" + bgId, type: "background", minzoom: 1 }],
|
||||
});
|
||||
});
|
||||
|
||||
test("when clicking next layer should update style on local storage", async () => {
|
||||
await when.type("min-zoom.input-text", "{backspace}");
|
||||
await when.click("max-zoom.input-text");
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "background:" + bgId, type: "background", minzoom: 1 }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("max-zoom", () => {
|
||||
let bgId: string;
|
||||
|
||||
test.beforeEach(async () => {
|
||||
bgId = await createBackground();
|
||||
await when.click("layer-list-item:background:" + bgId);
|
||||
await when.setValue("max-zoom.input-text", "1");
|
||||
await when.click("layer-editor.layer-id");
|
||||
});
|
||||
|
||||
test("should update style in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "background:" + bgId, type: "background", maxzoom: 1 }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("comments", () => {
|
||||
let bgId: string;
|
||||
const comment = "42";
|
||||
|
||||
test.beforeEach(async () => {
|
||||
bgId = await createBackground();
|
||||
await when.click("layer-list-item:background:" + bgId);
|
||||
await when.setValue("layer-comment.input", comment);
|
||||
await when.click("layer-editor.layer-id");
|
||||
});
|
||||
|
||||
test("should update style in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id: "background:" + bgId,
|
||||
type: "background",
|
||||
metadata: { "maputnik:comment": comment },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("when unsetting", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.clear("layer-comment.input");
|
||||
await when.click("min-zoom.input-text");
|
||||
});
|
||||
|
||||
test("should update style in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "background:" + bgId, type: "background" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("color", () => {
|
||||
let bgId: string;
|
||||
test.beforeEach(async () => {
|
||||
bgId = await createBackground();
|
||||
await when.click("layer-list-item:background:" + bgId);
|
||||
await when.click("spec-field:background-color");
|
||||
});
|
||||
|
||||
test("should update style in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id: "background:" + bgId, type: "background" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("opacity", () => {
|
||||
let bgId: string;
|
||||
test.beforeEach(async () => {
|
||||
bgId = await createBackground();
|
||||
await when.click("layer-list-item:background:" + bgId);
|
||||
await when.type("spec-field-input:background-opacity", "0.");
|
||||
});
|
||||
|
||||
test("should keep '.' in the input field", async () => {
|
||||
await then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0.");
|
||||
});
|
||||
|
||||
test("should revert to a valid value when focus out", async () => {
|
||||
await when.click("layer-list-item:background:" + bgId);
|
||||
await then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("filter", () => {
|
||||
test.skip("expand/collapse", () => {});
|
||||
test.skip("compound filter", () => {});
|
||||
});
|
||||
|
||||
test.describe("layout", () => {
|
||||
test("text-font", async () => {
|
||||
await when.setStyle("font");
|
||||
await when.collapseGroupInLayerEditor();
|
||||
await when.collapseGroupInLayerEditor(1);
|
||||
await when.collapseGroupInLayerEditor(2);
|
||||
await when.doWithin("spec-field:text-font", async () => {
|
||||
await get.element(".maputnik-autocomplete input").first().click();
|
||||
});
|
||||
await then(get.element(".maputnik-autocomplete-menu-item")).shouldBeVisible();
|
||||
await then(get.element(".maputnik-autocomplete-menu-item")).shouldHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("paint", () => {
|
||||
test.skip("expand/collapse", () => {});
|
||||
test.skip("color", () => {});
|
||||
test.skip("pattern", () => {});
|
||||
test.skip("opacity", () => {});
|
||||
});
|
||||
|
||||
test.describe("json-editor", () => {
|
||||
test("add", async () => {
|
||||
const id = await when.modal.fillLayers({
|
||||
type: "circle",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "circle", source: "example" }],
|
||||
});
|
||||
|
||||
const sourceText = get.elementByText('"source"');
|
||||
await sourceText.click();
|
||||
await when.typeKeys('"');
|
||||
|
||||
await then(get.element(".cm-lint-marker-error")).shouldExist();
|
||||
});
|
||||
|
||||
test.skip("expand/collapse", () => {});
|
||||
test.skip("modify", () => {});
|
||||
|
||||
test("parse error", async () => {
|
||||
const bgId = await createBackground();
|
||||
|
||||
await when.click("layer-list-item:background:" + bgId);
|
||||
await when.collapseGroupInLayerEditor();
|
||||
await when.collapseGroupInLayerEditor(1);
|
||||
await then(get.element(".cm-lint-marker-error")).shouldNotExist();
|
||||
|
||||
// Inject an invalid token (CodeMirror auto-closes brackets/quotes, so a
|
||||
// bare word reliably breaks the JSON) and expect a lint error.
|
||||
await when.appendTextInJsonEditor("zzz");
|
||||
await then(get.element(".cm-lint-marker-error")).shouldExist();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("sticky header", () => {
|
||||
test("should keep layer header visible when scrolling properties", async () => {
|
||||
// Setup: Create a layer with many properties (e.g. symbol layer)
|
||||
await when.modal.fillLayers({
|
||||
type: "symbol",
|
||||
layer: "example",
|
||||
});
|
||||
|
||||
await when.wait(500);
|
||||
const header = get.elementByTestId("layer-editor.header");
|
||||
await then(header).shouldBeVisible();
|
||||
|
||||
await get
|
||||
.element(".maputnik-scroll-container")
|
||||
.evaluate((el) => el.scrollTo(0, el.scrollHeight));
|
||||
await when.wait(200);
|
||||
|
||||
await then(header).shouldBeVisible();
|
||||
await then(get.elementByTestId("skip-target-layer-editor")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,351 +0,0 @@
|
||||
import { test } from "./fixtures";
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
test.describe("layers list", () => {
|
||||
const { given, get, when, then } = new MaputnikDriver();
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await given.setupMockBackedResponses();
|
||||
await when.setStyle("both");
|
||||
await when.modal.open();
|
||||
});
|
||||
|
||||
test.describe("ops", () => {
|
||||
let id: string;
|
||||
test.beforeEach(async () => {
|
||||
id = await when.modal.fillLayers({ type: "background" });
|
||||
});
|
||||
|
||||
test("should update layers in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "background" }],
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("when clicking delete", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.click("layer-list-item:" + id + ":delete");
|
||||
});
|
||||
test("should empty layers in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("when clicking duplicate", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.click("layer-list-item:" + id + ":copy");
|
||||
});
|
||||
test("should add copy layer in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{ id: id + "-copy", type: "background" },
|
||||
{ id, type: "background" },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("when clicking hide", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.click("layer-list-item:" + id + ":toggle-visibility");
|
||||
});
|
||||
|
||||
test("should update visibility to none in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "background", layout: { visibility: "none" } }],
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("when clicking show", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.click("layer-list-item:" + id + ":toggle-visibility");
|
||||
});
|
||||
|
||||
test("should update visibility to visible in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "background", layout: { visibility: "visible" } }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("when selecting a layer", () => {
|
||||
let secondId: string;
|
||||
test.beforeEach(async () => {
|
||||
await when.modal.open();
|
||||
secondId = await when.modal.fillLayers({
|
||||
id: "second-layer",
|
||||
type: "background",
|
||||
});
|
||||
});
|
||||
test("should show the selected layer in the editor", async () => {
|
||||
await when.realClick("layer-list-item:" + secondId);
|
||||
await then(get.elementByTestId("layer-editor.layer-id.input")).shouldHaveValue(secondId);
|
||||
await when.realClick("layer-list-item:" + id);
|
||||
await then(get.elementByTestId("layer-editor.layer-id.input")).shouldHaveValue(id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("background", () => {
|
||||
test("add", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "background" });
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "background" }],
|
||||
});
|
||||
});
|
||||
|
||||
test.skip("modify", () => {});
|
||||
});
|
||||
|
||||
test.describe("fill", () => {
|
||||
test("add", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "fill", layer: "example" });
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "fill", source: "example" }],
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Change source
|
||||
test.skip("change source", () => {});
|
||||
});
|
||||
|
||||
test.describe("line", () => {
|
||||
test("add", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "line", layer: "example" });
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "line", source: "example" }],
|
||||
});
|
||||
});
|
||||
|
||||
test("groups", async () => {
|
||||
await when.modal.open();
|
||||
const id1 = await when.modal.fillLayers({ id: "aa", type: "line", layer: "example" });
|
||||
|
||||
await when.modal.open();
|
||||
const id2 = await when.modal.fillLayers({ id: "aa-2", type: "line", layer: "example" });
|
||||
|
||||
await when.modal.open();
|
||||
const id3 = await when.modal.fillLayers({ id: "b", type: "line", layer: "example" });
|
||||
|
||||
await then(get.elementByTestId("layer-list-item:" + id1)).shouldBeVisible();
|
||||
await then(get.elementByTestId("layer-list-item:" + id2)).shouldNotBeVisible();
|
||||
await then(get.elementByTestId("layer-list-item:" + id3)).shouldBeVisible();
|
||||
await when.click("layer-list-group:aa-0");
|
||||
await then(get.elementByTestId("layer-list-item:" + id1)).shouldBeVisible();
|
||||
await then(get.elementByTestId("layer-list-item:" + id2)).shouldBeVisible();
|
||||
await then(get.elementByTestId("layer-list-item:" + id3)).shouldBeVisible();
|
||||
await when.click("layer-list-item:" + id2);
|
||||
await when.click("skip-target-layer-editor");
|
||||
await when.click("menu-move-layer-down");
|
||||
await then(get.elementByTestId("layer-list-group:aa-0")).shouldNotExist();
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{ id: "aa", type: "line", source: "example" },
|
||||
{ id: "b", type: "line", source: "example" },
|
||||
{ id: "aa-2", type: "line", source: "example" },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("symbol", () => {
|
||||
test("add", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "symbol", layer: "example" });
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "symbol", source: "example" }],
|
||||
});
|
||||
});
|
||||
|
||||
test("should show spec info when hovering and clicking single line property", async () => {
|
||||
await when.modal.fillLayers({ type: "symbol", layer: "example" });
|
||||
|
||||
await when.hover("spec-field-container:text-rotate");
|
||||
await then(get.elementByTestId("field-doc-button-Rotate")).shouldBeVisible();
|
||||
await when.click("field-doc-button-Rotate", 0);
|
||||
await then(get.elementByTestId("spec-field-doc")).shouldContainText("Rotates the ");
|
||||
});
|
||||
|
||||
test("should show spec info when hovering and clicking multi line property", async () => {
|
||||
await when.modal.fillLayers({ type: "symbol", layer: "example" });
|
||||
|
||||
await when.hover("spec-field-container:text-offset");
|
||||
await then(get.elementByTestId("field-doc-button-Offset")).shouldBeVisible();
|
||||
await when.click("field-doc-button-Offset", 0);
|
||||
await then(get.elementByTestId("spec-field-doc")).shouldContainText("Offset distance");
|
||||
});
|
||||
|
||||
test("should hide spec info when clicking a second time", async () => {
|
||||
await when.modal.fillLayers({ type: "symbol", layer: "example" });
|
||||
|
||||
await when.hover("spec-field-container:text-rotate");
|
||||
await then(get.elementByTestId("field-doc-button-Rotate")).shouldBeVisible();
|
||||
await when.click("field-doc-button-Rotate", 0);
|
||||
await when.wait(200);
|
||||
await when.click("field-doc-button-Rotate", 0);
|
||||
await then(get.elementByTestId("spec-field-doc")).shouldNotBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("raster", () => {
|
||||
test("add", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "raster", layer: "raster" });
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "raster", source: "raster" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("circle", () => {
|
||||
test("add", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "circle", layer: "example" });
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "circle", source: "example" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("fill extrusion", () => {
|
||||
test("add", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "fill-extrusion", layer: "example" });
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "fill-extrusion", source: "example" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("hillshade", () => {
|
||||
test("add", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "hillshade", layer: "example" });
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "hillshade", source: "example" }],
|
||||
});
|
||||
});
|
||||
|
||||
test("set hillshade illumination direction array", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "hillshade", layer: "example" });
|
||||
await when.collapseGroupInLayerEditor();
|
||||
await when.collapseGroupInLayerEditor(1);
|
||||
await when.setValueToPropertyArray("spec-field:hillshade-illumination-direction", "1");
|
||||
await when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "2");
|
||||
await when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "3");
|
||||
await when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "4");
|
||||
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id,
|
||||
type: "hillshade",
|
||||
source: "example",
|
||||
paint: { "hillshade-illumination-direction": [1, 2, 3, 4] },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("set hillshade highlight color array", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "hillshade", layer: "example" });
|
||||
await when.collapseGroupInLayerEditor();
|
||||
await when.setValueToPropertyArray("spec-field:hillshade-highlight-color", "blue");
|
||||
await when.addValueToPropertyArray("spec-field:hillshade-highlight-color", "#00ff00");
|
||||
await when.addValueToPropertyArray("spec-field:hillshade-highlight-color", "rgba(255, 255, 0, 1)");
|
||||
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{
|
||||
id,
|
||||
type: "hillshade",
|
||||
source: "example",
|
||||
paint: {
|
||||
"hillshade-highlight-color": ["blue", "#00ff00", "rgba(255, 255, 0, 1)"],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("color-relief", () => {
|
||||
test("add", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "color-relief", layer: "example" });
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [{ id, type: "color-relief", source: "example" }],
|
||||
});
|
||||
});
|
||||
|
||||
test("adds elevation expression when clicking the elevation button", async () => {
|
||||
await when.modal.fillLayers({ type: "color-relief", layer: "example" });
|
||||
await when.collapseGroupInLayerEditor();
|
||||
await when.click("make-elevation-function");
|
||||
await then(
|
||||
get.element("[data-wd-key='spec-field-container:color-relief-color'] .cm-line")
|
||||
).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("groups", () => {
|
||||
test("simple", async () => {
|
||||
await when.setStyle("geojson");
|
||||
|
||||
await when.modal.open();
|
||||
await when.modal.fillLayers({ id: "foo", type: "background" });
|
||||
|
||||
await when.modal.open();
|
||||
await when.modal.fillLayers({ id: "foo_bar", type: "background" });
|
||||
|
||||
await when.modal.open();
|
||||
await when.modal.fillLayers({ id: "foo_bar_baz", type: "background" });
|
||||
|
||||
await then(get.elementByTestId("layer-list-item:foo")).shouldBeVisible();
|
||||
await then(get.elementByTestId("layer-list-item:foo_bar")).shouldNotBeVisible();
|
||||
await then(get.elementByTestId("layer-list-item:foo_bar_baz")).shouldNotBeVisible();
|
||||
await when.click("layer-list-group:foo-0");
|
||||
await then(get.elementByTestId("layer-list-item:foo")).shouldBeVisible();
|
||||
await then(get.elementByTestId("layer-list-item:foo_bar")).shouldBeVisible();
|
||||
await then(get.elementByTestId("layer-list-item:foo_bar_baz")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("drag and drop", () => {
|
||||
test("move layer should update local storage", async () => {
|
||||
await when.modal.open();
|
||||
const firstId = await when.modal.fillLayers({ id: "a", type: "background" });
|
||||
await when.modal.open();
|
||||
const secondId = await when.modal.fillLayers({ id: "b", type: "background" });
|
||||
await when.modal.open();
|
||||
const thirdId = await when.modal.fillLayers({ id: "c", type: "background" });
|
||||
|
||||
await when.dragAndDropWithWait("layer-list-item:" + firstId, "layer-list-item:" + thirdId);
|
||||
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
layers: [
|
||||
{ id: secondId, type: "background" },
|
||||
{ id: thirdId, type: "background" },
|
||||
{ id: firstId, type: "background" },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("sticky header", () => {
|
||||
test("should keep header visible when scrolling layer list", async () => {
|
||||
// Setup: Create multiple layers to enable scrolling
|
||||
for (let i = 0; i < 20; i++) {
|
||||
await when.modal.open();
|
||||
await when.modal.fillLayers({ id: `layer-${i}`, type: "background" });
|
||||
}
|
||||
|
||||
await when.wait(500);
|
||||
const header = get.elementByTestId("layer-list.header");
|
||||
await then(header).shouldBeVisible();
|
||||
|
||||
// Scroll the layer list container
|
||||
await get.elementByTestId("layer-list").evaluate((el) => el.scrollTo(0, el.scrollHeight));
|
||||
await when.wait(200);
|
||||
await then(header).shouldBeVisible();
|
||||
await then(get.elementByTestId("layer-list:add-layer")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,67 +0,0 @@
|
||||
import { test } from "./fixtures";
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
test.describe("map", () => {
|
||||
const { given, get, when, then } = new MaputnikDriver();
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await given.setupMockBackedResponses();
|
||||
await when.setStyle("both");
|
||||
});
|
||||
|
||||
test.describe("zoom level", () => {
|
||||
test("via url", async () => {
|
||||
const zoomLevel = 12.37;
|
||||
await when.setStyle("geojson", zoomLevel);
|
||||
await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible();
|
||||
await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText("Zoom: " + zoomLevel);
|
||||
});
|
||||
|
||||
test("via map controls", async () => {
|
||||
const zoomLevel = 12.37;
|
||||
await when.setStyle("geojson", zoomLevel);
|
||||
await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible();
|
||||
await when.clickZoomIn();
|
||||
await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText("Zoom: " + (zoomLevel + 1));
|
||||
});
|
||||
|
||||
test("via style file definition", async () => {
|
||||
await when.setStyle("zoom_7_center_0_51");
|
||||
await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible();
|
||||
await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText("Zoom: " + 7);
|
||||
await then(get.locationHash()).shouldInclude("#7/51/0");
|
||||
|
||||
// opening another stylefile does not update the map view again
|
||||
// as discussed in https://github.com/maplibre/maputnik/issues/1546
|
||||
await when.openASecondStyleWithDifferentZoomAndCenter();
|
||||
await then(get.locationHash()).shouldInclude("#7/51/0");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("search", () => {
|
||||
test("should exist", async () => {
|
||||
await then(get.searchControl()).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("popup", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.setStyle("rectangles");
|
||||
await then(get.locationHash()).shouldExist();
|
||||
});
|
||||
|
||||
test("should open on feature click", async () => {
|
||||
await when.clickCenter("maplibre:map");
|
||||
await then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
|
||||
});
|
||||
|
||||
test("should open a second feature after closing popup", async () => {
|
||||
await when.clickCenter("maplibre:map");
|
||||
await then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
|
||||
await when.closePopup();
|
||||
await then(get.elementByTestId("feature-layer-popup")).shouldNotExist();
|
||||
await when.clickCenter("maplibre:map");
|
||||
await then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,579 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { expect, type Locator, type Page, type Request } from "@playwright/test";
|
||||
import { readCoverage } from "./coverage";
|
||||
import { currentPage, recordCoverageChunk } from "./fixtures";
|
||||
import { ModalDriver } from "./modal-driver";
|
||||
|
||||
const baseUrl = "http://localhost:8888/";
|
||||
const FIXTURES_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "fixtures");
|
||||
const DATA_ATTRIBUTE = "data-wd-key";
|
||||
|
||||
const isMac = process.platform === "darwin";
|
||||
|
||||
function testIdSelector(testId: string): string {
|
||||
return `[${DATA_ATTRIBUTE}="${testId}"]`;
|
||||
}
|
||||
|
||||
export function readFixture(name: string): any {
|
||||
const contents = fs.readFileSync(path.join(FIXTURES_DIR, name), "utf-8");
|
||||
return JSON.parse(contents);
|
||||
}
|
||||
|
||||
/** Reads the maputnik style currently persisted in localStorage. */
|
||||
function styleFromLocalStorage(page: Page): Promise<any> {
|
||||
return page.evaluate(() => {
|
||||
const styleId = window.localStorage.getItem("maputnik:latest_style");
|
||||
const styleItemKey = `maputnik:style:${styleId}`;
|
||||
const styleItem = window.localStorage.getItem(styleItemKey);
|
||||
if (!styleItem) throw new Error("Could not get styleItem from localStorage");
|
||||
return JSON.parse(styleItem);
|
||||
});
|
||||
}
|
||||
|
||||
async function retry(assertion: () => Promise<void> | void, timeout = 10000, interval = 100): Promise<void> {
|
||||
const start = Date.now();
|
||||
let lastError: unknown;
|
||||
for (;;) {
|
||||
try {
|
||||
await assertion();
|
||||
return;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (Date.now() - start > timeout) throw lastError;
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lazily-evaluated value (e.g. the style in localStorage). Assertions on a
|
||||
* Query re-read the value until they pass, mirroring Cypress' retry-ability.
|
||||
*/
|
||||
export class Query<T> {
|
||||
readonly __maputnikQuery = true as const;
|
||||
constructor(private readonly getter: () => Promise<T>) {}
|
||||
|
||||
get(): Promise<T> {
|
||||
return this.getter();
|
||||
}
|
||||
|
||||
then<U>(mapper: (value: T) => U | Promise<U>): Query<U> {
|
||||
return new Query<U>(async () => mapper(await this.getter()));
|
||||
}
|
||||
}
|
||||
|
||||
function isQuery(target: unknown): target is Query<unknown> {
|
||||
return typeof target === "object" && target !== null && (target as Query<unknown>).__maputnikQuery === true;
|
||||
}
|
||||
|
||||
function isLocator(target: unknown): target is Locator {
|
||||
return (
|
||||
typeof target === "object" &&
|
||||
target !== null &&
|
||||
typeof (target as Locator).count === "function" &&
|
||||
typeof (target as Locator).boundingBox === "function"
|
||||
);
|
||||
}
|
||||
|
||||
/** Asserts that every top-level key in `expected` deep-equals its counterpart in `actual`. */
|
||||
function assertDeepNestedInclude(actual: any, expected: Record<string, unknown>): void {
|
||||
for (const key of Object.keys(expected)) {
|
||||
expect(actual?.[key], `property "${key}"`).toEqual(expected[key]);
|
||||
}
|
||||
}
|
||||
|
||||
export class MaputnikAssertable<T> {
|
||||
constructor(private readonly target: T, private readonly page?: Page) {}
|
||||
|
||||
private locator(): Locator {
|
||||
if (!isLocator(this.target)) throw new Error("Expected a Locator target for this assertion");
|
||||
return this.target;
|
||||
}
|
||||
|
||||
private async assertValue(assertion: (value: any) => void): Promise<void> {
|
||||
const target = this.target;
|
||||
if (isQuery(target)) {
|
||||
await retry(async () => assertion(await target.get()));
|
||||
} else {
|
||||
assertion(await (target as any));
|
||||
}
|
||||
}
|
||||
|
||||
// Element assertions (auto-retrying via Playwright web-first assertions).
|
||||
shouldBeVisible = () => expect(this.locator().first()).toBeVisible();
|
||||
// Some testids resolve to many elements that are always rendered but hidden
|
||||
// (e.g. per-field documentation panels); "not visible" means none is visible.
|
||||
shouldNotBeVisible = () => expect(this.locator().filter({ visible: true })).toHaveCount(0);
|
||||
shouldExist = async () => {
|
||||
if (isLocator(this.target)) {
|
||||
await expect(this.locator().first()).toBeAttached();
|
||||
} else {
|
||||
await this.assertValue((value) => expect(value).toBeTruthy());
|
||||
}
|
||||
};
|
||||
shouldNotExist = () => expect(this.locator()).toHaveCount(0);
|
||||
shouldBeFocused = () => expect(this.locator().first()).toBeFocused();
|
||||
shouldNotBeFocused = () => expect(this.locator().first()).not.toBeFocused();
|
||||
shouldHaveValue = (value: string) => expect(this.locator().first()).toHaveValue(value);
|
||||
shouldContainText = async (text: string) => {
|
||||
const locator = this.locator();
|
||||
// Prefer the visible element when a testid resolves to several (only the
|
||||
// open documentation panel is visible; the rest are hidden in the DOM).
|
||||
const target = (await locator.count()) > 1 ? locator.filter({ visible: true }).first() : locator.first();
|
||||
await expect(target).toContainText(text);
|
||||
};
|
||||
shouldHaveText = (text: string) => expect(this.locator().first()).toHaveText(text);
|
||||
shouldHaveLength = (length: number) => expect(this.locator()).toHaveCount(length);
|
||||
shouldHaveCss = (property: string, value: string) => expect(this.locator().first()).toHaveCSS(property, value);
|
||||
|
||||
// Value assertions (auto-retrying for Query targets).
|
||||
shouldEqual = (value: any) => this.assertValue((actual) => expect(actual).toBe(value));
|
||||
|
||||
shouldInclude = (value: any) =>
|
||||
this.assertValue((actual) => {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
expect(actual).toMatchObject(value);
|
||||
} else {
|
||||
expect(String(actual)).toContain(String(value));
|
||||
}
|
||||
});
|
||||
|
||||
shouldDeepNestedInclude = (value: Record<string, unknown>) =>
|
||||
this.assertValue((actual) => assertDeepNestedInclude(actual, value));
|
||||
|
||||
/**
|
||||
* Asserts that the object under test (a fixture / response body) contains every
|
||||
* top-level property of the style currently stored in localStorage.
|
||||
*/
|
||||
shouldEqualToStoredStyle = async () => {
|
||||
if (!this.page) throw new Error("shouldEqualToStoredStyle requires a page-bound assertable");
|
||||
const expected = await (this.target as any);
|
||||
await retry(async () => {
|
||||
const stored = await styleFromLocalStorage(this.page!);
|
||||
assertDeepNestedInclude(expected, stored);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a Cypress-style key sequence (e.g. "{meta}z", "{esc}", "0.") into
|
||||
* Playwright keyboard actions on the currently focused element.
|
||||
*/
|
||||
async function typeSequence(page: Page, text: string): Promise<void> {
|
||||
const tokens = text.match(/\{[^}]+\}|[^{]+/g) ?? [];
|
||||
const modifierMap: Record<string, string> = { meta: "Meta", ctrl: "Control", shift: "Shift", alt: "Alt" };
|
||||
const namedKeys: Record<string, string> = {
|
||||
esc: "Escape",
|
||||
enter: "Enter",
|
||||
backspace: "Backspace",
|
||||
del: "Delete",
|
||||
tab: "Tab",
|
||||
};
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token = tokens[i];
|
||||
if (!token.startsWith("{") || !token.endsWith("}")) {
|
||||
await page.keyboard.type(token);
|
||||
continue;
|
||||
}
|
||||
const name = token.slice(1, -1).toLowerCase();
|
||||
if (name === "selectall") {
|
||||
await page.keyboard.press(isMac ? "Meta+a" : "Control+a");
|
||||
} else if (namedKeys[name]) {
|
||||
await page.keyboard.press(namedKeys[name]);
|
||||
} else if (modifierMap[name]) {
|
||||
const modifiers = [modifierMap[name]];
|
||||
let j = i + 1;
|
||||
while (j < tokens.length && /^\{(meta|ctrl|shift|alt)\}$/i.test(tokens[j])) {
|
||||
modifiers.push(modifierMap[tokens[j].slice(1, -1).toLowerCase()]);
|
||||
j++;
|
||||
}
|
||||
const key = tokens[j] ?? "";
|
||||
await page.keyboard.press([...modifiers, key].join("+"));
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function centerOf(locator: Locator): Promise<{ x: number; y: number }> {
|
||||
const box = await locator.boundingBox();
|
||||
if (!box) throw new Error("Element has no bounding box");
|
||||
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
||||
}
|
||||
|
||||
export class MaputnikDriver {
|
||||
private scope: Locator | null = null;
|
||||
private readonly recordedRequests = new Map<string, Request[]>();
|
||||
private readonly modalDriver = new ModalDriver(this);
|
||||
|
||||
/**
|
||||
* The page for the currently running test. Resolved lazily so a single driver
|
||||
* instance can be created once per `describe` and reused across its tests.
|
||||
*/
|
||||
private get page(): Page {
|
||||
return currentPage();
|
||||
}
|
||||
|
||||
// ---- Element access ------------------------------------------------------
|
||||
|
||||
private root(): Page | Locator {
|
||||
return this.scope ?? this.page;
|
||||
}
|
||||
|
||||
private testId(testId: string): Locator {
|
||||
return this.root().locator(testIdSelector(testId));
|
||||
}
|
||||
|
||||
then = <T>(target: T) => new MaputnikAssertable(target, this.page);
|
||||
|
||||
// ---- given ---------------------------------------------------------------
|
||||
|
||||
public given = {
|
||||
fixture: (_name: string, _alias?: string) => {
|
||||
// Fixtures are read directly from disk in Playwright, no registration needed.
|
||||
},
|
||||
|
||||
intercept: async (url: string, alias: string, _method = "GET") => {
|
||||
this.recordedRequests.set(alias, []);
|
||||
// Convert the Cypress-style glob (which may contain "?" in a query string)
|
||||
// into a regex so query parameters match reliably.
|
||||
const pattern = new RegExp(
|
||||
"^" + url.replace(/[.+^${}()|[\]\\?]/g, "\\$&").replace(/\*/g, ".*") + "$"
|
||||
);
|
||||
await this.page.route(pattern, (route) => {
|
||||
this.recordedRequests.get(alias)!.push(route.request());
|
||||
route.continue();
|
||||
});
|
||||
},
|
||||
|
||||
interceptAndMockResponse: async (options: {
|
||||
method?: string;
|
||||
url: string | RegExp;
|
||||
response: unknown | { fixture: string };
|
||||
alias?: string;
|
||||
}) => {
|
||||
const { url, response, alias } = options;
|
||||
if (alias) this.recordedRequests.set(alias, []);
|
||||
await this.page.route(url, (route) => {
|
||||
if (alias) this.recordedRequests.get(alias)!.push(route.request());
|
||||
const body =
|
||||
response && typeof response === "object" && "fixture" in (response as any)
|
||||
? readFixture((response as { fixture: string }).fixture)
|
||||
: response;
|
||||
route.fulfill({ json: body });
|
||||
});
|
||||
},
|
||||
|
||||
setupMockBackedResponses: async () => {
|
||||
const styleFixtures = [
|
||||
"example-style.json",
|
||||
"example-layer-style.json",
|
||||
"geojson-style.json",
|
||||
"raster-style.json",
|
||||
"geojson-raster-style.json",
|
||||
"rectangles-style.json",
|
||||
"example-style-with-fonts.json",
|
||||
"example-style-with-zoom-7-and-center-0-51.json",
|
||||
"example-style-with-zoom-5-and-center-50-50.json",
|
||||
];
|
||||
for (const fixture of styleFixtures) {
|
||||
await this.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: baseUrl + fixture,
|
||||
response: { fixture },
|
||||
alias: fixture === "example-style.json" ? "example-style.json" : undefined,
|
||||
});
|
||||
}
|
||||
await this.given.interceptAndMockResponse({ method: "GET", url: /example\.local\//, response: [] });
|
||||
await this.given.interceptAndMockResponse({ method: "GET", url: /example\.com\//, response: [] });
|
||||
await this.given.interceptAndMockResponse({
|
||||
method: "GET",
|
||||
url: "https://www.glyph-server.com/*",
|
||||
response: ["Font 1", "Font 2", "Font 3"],
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// ---- when ----------------------------------------------------------------
|
||||
|
||||
public when = {
|
||||
modal: this.modalDriver.when,
|
||||
|
||||
visit: async (url: string) => {
|
||||
// Snapshot coverage before navigating, since a full page load resets it.
|
||||
recordCoverageChunk(await readCoverage(this.page));
|
||||
const target = url.startsWith("http") ? url : new URL(url, baseUrl).toString();
|
||||
await this.page.goto(target);
|
||||
},
|
||||
|
||||
wait: (ms: number) => this.page.waitForTimeout(ms),
|
||||
|
||||
tab: () => this.page.keyboard.press("Tab"),
|
||||
|
||||
typeKeys: (keys: string) => typeSequence(this.page, keys),
|
||||
|
||||
doWithin: async (selector: string, fn: () => Promise<void> | void) => {
|
||||
const previous = this.scope;
|
||||
this.scope = (previous ?? this.page).locator(testIdSelector(selector));
|
||||
try {
|
||||
await fn();
|
||||
} finally {
|
||||
this.scope = previous;
|
||||
}
|
||||
},
|
||||
|
||||
click: async (testId: string, index = 0) => {
|
||||
// Documentation buttons are wrapped in a <label>/.maputnik-doc-target that
|
||||
// Playwright treats as intercepting the click; bypass the check for them.
|
||||
const force = testId.startsWith("field-doc-button-");
|
||||
await this.testId(testId).nth(index).click({ force });
|
||||
},
|
||||
|
||||
realClick: async (testId: string) => {
|
||||
await this.testId(testId).click();
|
||||
},
|
||||
|
||||
hover: async (testId: string) => {
|
||||
await this.testId(testId).hover();
|
||||
},
|
||||
|
||||
focus: async (testId: string) => {
|
||||
await this.testId(testId).focus();
|
||||
},
|
||||
|
||||
clear: async (testId: string) => {
|
||||
await this.testId(testId).clear();
|
||||
},
|
||||
|
||||
select: async (testId: string, value: string) => {
|
||||
await this.testId(testId).selectOption(value);
|
||||
},
|
||||
|
||||
selectWithin: async (selector: string, value: string) => {
|
||||
await this.root().locator(testIdSelector(selector)).locator("select").selectOption(value);
|
||||
},
|
||||
|
||||
setValue: async (testId: string, text: string) => {
|
||||
const input = this.testId(testId);
|
||||
await input.fill("");
|
||||
await input.fill(text);
|
||||
},
|
||||
|
||||
type: async (testId: string, text: string) => {
|
||||
await this.testId(testId).focus();
|
||||
// Place the caret at the start of the field (matching how the original
|
||||
// Cypress suite typed), so a leading "{backspace}" is a no-op rather than
|
||||
// clearing an already-committed value.
|
||||
await this.page.keyboard.press("Home");
|
||||
await typeSequence(this.page, text);
|
||||
},
|
||||
|
||||
setValueToPropertyArray: async (selector: string, value: string) => {
|
||||
await this.when.doWithin(selector, async () => {
|
||||
const input = this.root().locator(".maputnik-array-block-content input").last();
|
||||
await input.focus();
|
||||
await typeSequence(this.page, "{selectall}" + value);
|
||||
});
|
||||
},
|
||||
|
||||
addValueToPropertyArray: async (selector: string, value: string) => {
|
||||
await this.when.doWithin(selector, async () => {
|
||||
await this.root().locator(".maputnik-array-add-value").click();
|
||||
const input = this.root().locator(".maputnik-array-block-content input").last();
|
||||
await input.focus();
|
||||
await typeSequence(this.page, "{selectall}" + value);
|
||||
});
|
||||
},
|
||||
|
||||
setStyle: async (
|
||||
styleProperties:
|
||||
| "geojson"
|
||||
| "raster"
|
||||
| "both"
|
||||
| "layer"
|
||||
| "rectangles"
|
||||
| "font"
|
||||
| "zoom_7_center_0_51"
|
||||
| "",
|
||||
zoom?: number
|
||||
) => {
|
||||
const styleFileByKey: Record<string, string> = {
|
||||
geojson: "geojson-style.json",
|
||||
raster: "raster-style.json",
|
||||
both: "geojson-raster-style.json",
|
||||
layer: "example-layer-style.json",
|
||||
rectangles: "rectangles-style.json",
|
||||
font: "example-style-with-fonts.json",
|
||||
zoom_7_center_0_51: "example-style-with-zoom-7-and-center-0-51.json",
|
||||
};
|
||||
|
||||
const url = new URL(baseUrl);
|
||||
if (styleProperties && styleFileByKey[styleProperties]) {
|
||||
url.searchParams.set("style", baseUrl + styleFileByKey[styleProperties]);
|
||||
}
|
||||
if (zoom) {
|
||||
url.hash = `${zoom}/41.3805/2.1635`;
|
||||
}
|
||||
|
||||
await this.when.visit(url.toString());
|
||||
|
||||
const toolbarLink = this.testId("toolbar:link");
|
||||
await toolbarLink.scrollIntoViewIfNeeded();
|
||||
await expect(toolbarLink).toBeVisible();
|
||||
},
|
||||
|
||||
openASecondStyleWithDifferentZoomAndCenter: async () => {
|
||||
await this.page.getByRole("button", { name: "Open" }).click();
|
||||
const input = this.testId("modal:open.url.input");
|
||||
await expect(input).toBeEnabled();
|
||||
await input.fill("http://localhost:8888/example-style-with-zoom-5-and-center-50-50.json");
|
||||
await input.press("Enter");
|
||||
},
|
||||
|
||||
chooseExampleFile: async () => {
|
||||
await this.openFileByFixture("example-style.json", "modal:open.dropzone", "modal:open.file.input");
|
||||
await this.when.wait(200);
|
||||
},
|
||||
|
||||
dropExampleFile: async () => {
|
||||
await this.dropFileByFixture("example-style.json", "modal:open.dropzone");
|
||||
await this.when.wait(200);
|
||||
},
|
||||
|
||||
clickZoomIn: async () => {
|
||||
await this.page.locator(".maplibregl-ctrl-zoom-in").click();
|
||||
},
|
||||
|
||||
closePopup: async () => {
|
||||
await this.page.locator(".maplibregl-popup-close-button").click();
|
||||
},
|
||||
|
||||
clickCenter: async (testId: string) => {
|
||||
const { x, y } = await centerOf(this.testId(testId));
|
||||
await this.page.mouse.move(x, y);
|
||||
await this.page.mouse.down();
|
||||
await this.when.wait(200);
|
||||
await this.page.mouse.up();
|
||||
},
|
||||
|
||||
collapseGroupInLayerEditor: async (index = 0) => {
|
||||
await this.page.locator(".maputnik-layer-editor-group__button").nth(index).click();
|
||||
},
|
||||
|
||||
appendTextInJsonEditor: async (text: string) => {
|
||||
await this.page.locator(".cm-line").first().click();
|
||||
// Move to the very start of the document so the inserted text breaks the
|
||||
// root JSON structure (CodeMirror auto-closes brackets otherwise).
|
||||
await this.page.keyboard.press("Home");
|
||||
await typeSequence(this.page, text);
|
||||
},
|
||||
|
||||
setTextInJsonEditor: async (text: string) => {
|
||||
const firstLine = this.page.locator(".cm-line").first();
|
||||
await firstLine.click();
|
||||
await this.page.keyboard.press(isMac ? "Meta+a" : "Control+a");
|
||||
await this.page.keyboard.type(text);
|
||||
},
|
||||
|
||||
dragAndDropWithWait: async (source: string, target: string) => {
|
||||
const from = await centerOf(this.testId(source));
|
||||
const to = await centerOf(this.testId(target));
|
||||
await this.page.mouse.move(from.x, from.y);
|
||||
await this.page.mouse.down();
|
||||
await this.page.mouse.move(from.x, from.y + 10);
|
||||
await this.page.mouse.move(to.x, to.y, { steps: 10 });
|
||||
await this.when.wait(100);
|
||||
await this.page.mouse.up();
|
||||
},
|
||||
|
||||
waitForResponse: async (alias: string) => {
|
||||
const requests = this.recordedRequests.get(alias);
|
||||
if (!requests) throw new Error(`No intercept registered for alias "${alias}"`);
|
||||
await retry(async () => {
|
||||
if (requests.length === 0) throw new Error(`No request recorded for alias "${alias}"`);
|
||||
});
|
||||
return requests[requests.length - 1];
|
||||
},
|
||||
|
||||
waitForExampleFileResponse: () => this.when.waitForResponse("example-style.json"),
|
||||
|
||||
clearLocalStorage: () => this.page.evaluate(() => window.localStorage.clear()),
|
||||
};
|
||||
|
||||
// ---- get -----------------------------------------------------------------
|
||||
|
||||
public get = {
|
||||
isMac: () => isMac,
|
||||
|
||||
element: (selector: string) => this.root().locator(selector),
|
||||
|
||||
elementByTestId: (testId: string) => this.testId(testId),
|
||||
|
||||
elementByText: (text: string) => this.root().getByText(text),
|
||||
|
||||
elementByAttribute: (attribute: string, value: string) =>
|
||||
this.root().locator(`[${attribute}="${value}"]`),
|
||||
|
||||
canvas: () => this.page.locator("canvas"),
|
||||
|
||||
searchControl: () => this.page.locator(".maplibregl-ctrl-geocoder"),
|
||||
|
||||
skipTargetLayerList: () => this.testId("skip-target-layer-list"),
|
||||
|
||||
skipTargetLayerEditor: () => this.testId("skip-target-layer-editor"),
|
||||
|
||||
inputValue: (testId: string) => new Query<string>(() => this.testId(testId).first().inputValue()),
|
||||
|
||||
elementsText: (testId: string) => new Query<string>(() => this.testId(testId).first().innerText()),
|
||||
|
||||
locationHash: () => new Query<string>(async () => new URL(this.page.url()).hash),
|
||||
|
||||
styleFromLocalStorage: () => new Query<any>(() => styleFromLocalStorage(this.page)),
|
||||
|
||||
fixture: (name: string) => Promise.resolve(readFixture(name)),
|
||||
|
||||
responseBody: (alias: string) => {
|
||||
// Our mocked style responses always return the matching fixture.
|
||||
const name = alias.endsWith(".json") ? alias : `${alias}.json`;
|
||||
return Promise.resolve(readFixture(name));
|
||||
},
|
||||
|
||||
exampleFileUrl: () => baseUrl + "example-style.json",
|
||||
};
|
||||
|
||||
// ---- file open helpers ---------------------------------------------------
|
||||
|
||||
private async openFileByFixture(fixture: string, buttonTestId: string, inputTestId: string): Promise<void> {
|
||||
const content = JSON.stringify(readFixture(fixture));
|
||||
const hasPicker = await this.page.evaluate(() => "showOpenFilePicker" in window);
|
||||
if (hasPicker) {
|
||||
await this.page.evaluate((fileContent) => {
|
||||
(window as any).showOpenFilePicker = async () => [
|
||||
{ getFile: async () => ({ text: async () => fileContent }) },
|
||||
];
|
||||
}, content);
|
||||
await this.testId(buttonTestId).click();
|
||||
} else {
|
||||
await this.testId(inputTestId).setInputFiles({
|
||||
name: fixture,
|
||||
mimeType: "application/json",
|
||||
buffer: Buffer.from(content),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async dropFileByFixture(fixture: string, dropzoneTestId: string): Promise<void> {
|
||||
const content = JSON.stringify(readFixture(fixture));
|
||||
const dataTransfer = await this.page.evaluateHandle((fileContent) => {
|
||||
const dt = new DataTransfer();
|
||||
dt.items.add(new File([fileContent], "example-style.json", { type: "application/json" }));
|
||||
return dt;
|
||||
}, content);
|
||||
const dropzone = this.testId(dropzoneTestId);
|
||||
await dropzone.dispatchEvent("dragenter", { dataTransfer });
|
||||
await dropzone.dispatchEvent("dragover", { dataTransfer });
|
||||
await dropzone.dispatchEvent("drop", { dataTransfer });
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { v1 as uuid } from "uuid";
|
||||
import { expect } from "@playwright/test";
|
||||
import type { MaputnikDriver } from "./maputnik-driver";
|
||||
|
||||
export class ModalDriver {
|
||||
constructor(private readonly driver: MaputnikDriver) {}
|
||||
|
||||
public when = {
|
||||
fillLayers: async (opts: { type: string; layer?: string; id?: string }) => {
|
||||
const { when, get } = this.driver;
|
||||
const id = opts.id ?? `${opts.type}:${uuid()}`;
|
||||
|
||||
await when.select("add-layer.layer-type.select", opts.type);
|
||||
await when.type("add-layer.layer-id.input", id);
|
||||
|
||||
if (opts.layer) {
|
||||
await when.doWithin("add-layer.layer-source-block", async () => {
|
||||
const input = get.element("input");
|
||||
await input.click();
|
||||
await input.fill(opts.layer!);
|
||||
// The source input is a controlled downshift combobox; wait for React
|
||||
// to settle on the typed value before submitting.
|
||||
await expect(input).toHaveValue(opts.layer!);
|
||||
});
|
||||
// Close the autocomplete menu so it does not intercept the add button.
|
||||
await get.elementByTestId("add-layer.layer-id.input").click();
|
||||
}
|
||||
await when.click("add-layer");
|
||||
|
||||
return id;
|
||||
},
|
||||
|
||||
open: async () => {
|
||||
// No-op when the add-layer modal is already open (some specs call open()
|
||||
// both in a beforeEach and at the start of the test body).
|
||||
const modal = this.driver.get.elementByTestId("modal:add-layer").first();
|
||||
if (await modal.isVisible()) return;
|
||||
await this.driver.when.click("layer-list:add-layer");
|
||||
},
|
||||
|
||||
close: async (key: string) => {
|
||||
await this.driver.when.click(key + ".close-modal");
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,429 +0,0 @@
|
||||
import { test, expect } from "./fixtures";
|
||||
import { MaputnikDriver } from "./maputnik-driver";
|
||||
import tokens from "../src/config/tokens.json" with { type: "json" };
|
||||
|
||||
test.describe("modals", () => {
|
||||
const { given, get, when, then } = new MaputnikDriver();
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await given.setupMockBackedResponses();
|
||||
// Load a style first so it is persisted to localStorage, then reset the URL
|
||||
// to the root (no style param) — several tests read the stored style.
|
||||
await when.setStyle("both");
|
||||
await when.setStyle("");
|
||||
});
|
||||
|
||||
test.describe("open", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.click("nav:open");
|
||||
});
|
||||
|
||||
test("close", async () => {
|
||||
await when.modal.close("modal:open");
|
||||
await then(get.elementByTestId("modal:open")).shouldNotExist();
|
||||
});
|
||||
|
||||
test("upload", async () => {
|
||||
await when.chooseExampleFile();
|
||||
await then(get.fixture("example-style.json")).shouldEqualToStoredStyle();
|
||||
});
|
||||
|
||||
test("upload via drag and drop", async () => {
|
||||
await when.dropExampleFile();
|
||||
await then(get.fixture("example-style.json")).shouldEqualToStoredStyle();
|
||||
});
|
||||
|
||||
test.describe("when click open url", () => {
|
||||
test.beforeEach(async () => {
|
||||
const styleFileUrl = get.exampleFileUrl();
|
||||
|
||||
await when.setValue("modal:open.url.input", styleFileUrl);
|
||||
await when.click("modal:open.url.button");
|
||||
await when.wait(200);
|
||||
});
|
||||
test("load from url", async () => {
|
||||
await then(get.responseBody("example-style.json")).shouldEqualToStoredStyle();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("shortcuts", () => {
|
||||
test("open/close", async () => {
|
||||
await when.setStyle("");
|
||||
await when.typeKeys("?");
|
||||
await when.modal.close("modal:shortcuts");
|
||||
await then(get.elementByTestId("modal:shortcuts")).shouldNotExist();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("export", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.click("nav:export");
|
||||
});
|
||||
|
||||
test("close", async () => {
|
||||
await when.modal.close("modal:export");
|
||||
await then(get.elementByTestId("modal:export")).shouldNotExist();
|
||||
});
|
||||
|
||||
// TODO: Work out how to download a file and check the contents
|
||||
test.skip("download", () => {});
|
||||
});
|
||||
|
||||
test.describe("sources", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.setStyle("layer");
|
||||
await when.click("nav:sources");
|
||||
});
|
||||
|
||||
test.skip("active sources", () => {});
|
||||
test.skip("public source", () => {});
|
||||
|
||||
test("add new source", async () => {
|
||||
const sourceId = "n1z2v3r";
|
||||
await when.setValue("modal:sources.add.source_id", sourceId);
|
||||
await when.select("modal:sources.add.source_type", "tile_vector");
|
||||
await when.select("modal:sources.add.scheme_type", "tms");
|
||||
await when.click("modal:sources.add.add_source");
|
||||
await when.wait(200);
|
||||
await then(get.styleFromLocalStorage().then((style) => style.sources[sourceId])).shouldInclude({
|
||||
scheme: "tms",
|
||||
});
|
||||
});
|
||||
|
||||
test("add new pmtiles source", async () => {
|
||||
const sourceId = "pmtilestest";
|
||||
await when.setValue("modal:sources.add.source_id", sourceId);
|
||||
await when.select("modal:sources.add.source_type", "pmtiles_vector");
|
||||
await when.setValue(
|
||||
"modal:sources.add.source_url",
|
||||
"https://data.source.coop/protomaps/openstreetmap/v4.pmtiles"
|
||||
);
|
||||
await when.click("modal:sources.add.add_source");
|
||||
await when.click("modal:sources.add.add_source");
|
||||
await when.wait(200);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
sources: {
|
||||
pmtilestest: {
|
||||
type: "vector",
|
||||
url: "pmtiles://https://data.source.coop/protomaps/openstreetmap/v4.pmtiles",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("add new raster source", async () => {
|
||||
const sourceId = "rastertest";
|
||||
await when.setValue("modal:sources.add.source_id", sourceId);
|
||||
await when.select("modal:sources.add.source_type", "tile_raster");
|
||||
await when.select("modal:sources.add.scheme_type", "xyz");
|
||||
await when.setValue("modal:sources.add.tile_size", "128");
|
||||
await when.click("modal:sources.add.add_source");
|
||||
await when.wait(200);
|
||||
await then(get.styleFromLocalStorage().then((style) => style.sources[sourceId])).shouldInclude({
|
||||
tileSize: 128,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("inspect", () => {
|
||||
test("toggle", async () => {
|
||||
// There is no assertion in this test
|
||||
await when.setStyle("geojson");
|
||||
await when.select("maputnik-select", "inspect");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("style settings", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.click("nav:settings");
|
||||
});
|
||||
|
||||
test.describe("when click name filed spec information", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.click("field-doc-button-Name");
|
||||
});
|
||||
|
||||
test("should show the spec information", async () => {
|
||||
await then(get.elementsText("spec-field-doc")).shouldInclude("name for the style");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("when set name and click owner", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.setValue("modal:settings.name", "foobar");
|
||||
await when.click("modal:settings.owner");
|
||||
await when.wait(200);
|
||||
});
|
||||
|
||||
test("show name specifications", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
name: "foobar",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("when set owner and click name", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.setValue("modal:settings.owner", "foobar");
|
||||
await when.click("modal:settings.name");
|
||||
await when.wait(200);
|
||||
});
|
||||
test("should update owner in local storage", async () => {
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
owner: "foobar",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("sprite url", async () => {
|
||||
await when.setTextInJsonEditor('"http://example.com"');
|
||||
await when.click("modal:settings.name");
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
sprite: "http://example.com",
|
||||
});
|
||||
});
|
||||
|
||||
test("sprite object", async () => {
|
||||
await when.setTextInJsonEditor(JSON.stringify([{ id: "1", url: "2" }]));
|
||||
|
||||
await when.click("modal:settings.name");
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
sprite: [{ id: "1", url: "2" }],
|
||||
});
|
||||
});
|
||||
|
||||
test("glyphs url", async () => {
|
||||
const glyphsUrl = "http://example.com/{fontstack}/{range}.pbf";
|
||||
await when.setValue("modal:settings.glyphs", glyphsUrl);
|
||||
await when.click("modal:settings.name");
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
glyphs: glyphsUrl,
|
||||
});
|
||||
});
|
||||
|
||||
test("maptiler access token", async () => {
|
||||
const apiKey = "testing123";
|
||||
await when.setValue("modal:settings.maputnik:openmaptiles_access_token", apiKey);
|
||||
await when.click("modal:settings.name");
|
||||
await then(get.styleFromLocalStorage().then((style) => style.metadata)).shouldInclude({
|
||||
"maputnik:openmaptiles_access_token": apiKey,
|
||||
});
|
||||
});
|
||||
|
||||
test("thunderforest access token", async () => {
|
||||
const apiKey = "testing123";
|
||||
await when.setValue("modal:settings.maputnik:thunderforest_access_token", apiKey);
|
||||
await when.click("modal:settings.name");
|
||||
await then(get.styleFromLocalStorage().then((style) => style.metadata)).shouldInclude({
|
||||
"maputnik:thunderforest_access_token": apiKey,
|
||||
});
|
||||
});
|
||||
|
||||
test("stadia access token", async () => {
|
||||
const apiKey = "testing123";
|
||||
await when.setValue("modal:settings.maputnik:stadia_access_token", apiKey);
|
||||
await when.click("modal:settings.name");
|
||||
await then(get.styleFromLocalStorage().then((style) => style.metadata)).shouldInclude({
|
||||
"maputnik:stadia_access_token": apiKey,
|
||||
});
|
||||
});
|
||||
|
||||
test("locationiq access token", async () => {
|
||||
const apiKey = "testing123";
|
||||
await when.setValue("modal:settings.maputnik:locationiq_access_token", apiKey);
|
||||
await when.click("modal:settings.name");
|
||||
await then(get.styleFromLocalStorage().then((style) => style.metadata)).shouldInclude({
|
||||
"maputnik:locationiq_access_token": apiKey,
|
||||
});
|
||||
});
|
||||
|
||||
test("style projection mercator", async () => {
|
||||
await when.select("modal:settings.projection", "mercator");
|
||||
await then(get.styleFromLocalStorage().then((style) => style.projection)).shouldInclude({
|
||||
type: "mercator",
|
||||
});
|
||||
});
|
||||
|
||||
test("style projection globe", async () => {
|
||||
await when.select("modal:settings.projection", "globe");
|
||||
await then(get.styleFromLocalStorage().then((style) => style.projection)).shouldInclude({
|
||||
type: "globe",
|
||||
});
|
||||
});
|
||||
|
||||
test("style projection vertical-perspective", async () => {
|
||||
await when.select("modal:settings.projection", "vertical-perspective");
|
||||
await then(get.styleFromLocalStorage().then((style) => style.projection)).shouldInclude({
|
||||
type: "vertical-perspective",
|
||||
});
|
||||
});
|
||||
|
||||
test("style renderer", async () => {
|
||||
await when.select("modal:settings.maputnik:renderer", "ol");
|
||||
await then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual("ol");
|
||||
|
||||
await when.click("modal:settings.name");
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
metadata: { "maputnik:renderer": "ol" },
|
||||
});
|
||||
});
|
||||
|
||||
test("include API key when change renderer", async () => {
|
||||
await when.click("modal:settings.close-modal");
|
||||
await when.click("nav:open");
|
||||
|
||||
await get.elementByAttribute("aria-label", "MapTiler Basic").click();
|
||||
await when.wait(1000);
|
||||
await when.click("nav:settings");
|
||||
|
||||
await when.select("modal:settings.maputnik:renderer", "mlgljs");
|
||||
await then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual("mlgljs");
|
||||
|
||||
await when.select("modal:settings.maputnik:renderer", "ol");
|
||||
await then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual("ol");
|
||||
|
||||
await given.intercept(
|
||||
"https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=*",
|
||||
"tileRequest",
|
||||
"GET"
|
||||
);
|
||||
|
||||
await when.select("modal:settings.maputnik:renderer", "mlgljs");
|
||||
await then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual("mlgljs");
|
||||
|
||||
const request = await when.waitForResponse("tileRequest");
|
||||
expect(request.url()).toContain(
|
||||
`https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=${tokens.openmaptiles}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("add layer", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.setStyle("layer");
|
||||
await when.modal.open();
|
||||
});
|
||||
|
||||
test("shows duplicate id error", async () => {
|
||||
await when.setValue("add-layer.layer-id.input", "background");
|
||||
await when.click("add-layer");
|
||||
await then(get.elementByTestId("modal:add-layer")).shouldExist();
|
||||
await then(get.element(".maputnik-modal-error")).shouldContainText("Layer ID already exists");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("sources placeholder", () => {
|
||||
test.skip("toggle", () => {});
|
||||
});
|
||||
|
||||
test.describe("global state", () => {
|
||||
test.beforeEach(async () => {
|
||||
await when.click("nav:global-state");
|
||||
});
|
||||
|
||||
test("add variable", async () => {
|
||||
await when.wait(100);
|
||||
await when.click("global-state-add-variable");
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
state: { key1: { default: "value" } },
|
||||
});
|
||||
});
|
||||
|
||||
test("add multiple variables", async () => {
|
||||
await when.click("global-state-add-variable");
|
||||
await when.click("global-state-add-variable");
|
||||
await when.click("global-state-add-variable");
|
||||
await when.wait(100);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
state: { key1: { default: "value" }, key2: { default: "value" }, key3: { default: "value" } },
|
||||
});
|
||||
});
|
||||
|
||||
test("remove variable", async () => {
|
||||
await when.click("global-state-add-variable");
|
||||
await when.click("global-state-add-variable");
|
||||
await when.click("global-state-add-variable");
|
||||
await when.click("global-state-remove-variable", 0);
|
||||
await when.wait(100);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
state: { key2: { default: "value" }, key3: { default: "value" } },
|
||||
});
|
||||
});
|
||||
|
||||
test("edit variable key", async () => {
|
||||
await when.click("global-state-add-variable");
|
||||
await when.wait(100);
|
||||
await when.setValue("global-state-variable-key:0", "mykey");
|
||||
await when.typeKeys("{enter}");
|
||||
await when.wait(100);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
state: { mykey: { default: "value" } },
|
||||
});
|
||||
});
|
||||
|
||||
test("edit variable value", async () => {
|
||||
await when.click("global-state-add-variable");
|
||||
await when.wait(100);
|
||||
await when.setValue("global-state-variable-value:0", "myvalue");
|
||||
await when.typeKeys("{enter}");
|
||||
await when.wait(100);
|
||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||
state: { key1: { default: "myvalue" } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("error panel", () => {
|
||||
test("not visible when no errors", async () => {
|
||||
await then(get.element("maputnik-message-panel-error")).shouldNotExist();
|
||||
});
|
||||
|
||||
test("visible on style error", async () => {
|
||||
await when.modal.open();
|
||||
await when.modal.fillLayers({
|
||||
type: "circle",
|
||||
layer: "invalid",
|
||||
});
|
||||
await then(get.element(".maputnik-message-panel-error")).shouldBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Handle localStorage QuotaExceededError", () => {
|
||||
test("handles quota exceeded error when opening style from URL", async ({ page }) => {
|
||||
// Clear localStorage to start fresh
|
||||
await when.clearLocalStorage();
|
||||
|
||||
// fill localStorage until we get a QuotaExceededError
|
||||
await page.evaluate(() => {
|
||||
let chunkSize = 1000;
|
||||
const chunk = new Array(chunkSize).join("x");
|
||||
let index = 0;
|
||||
|
||||
// Keep adding until we hit the quota
|
||||
for (;;) {
|
||||
try {
|
||||
const key = `maputnik:fill-${index++}`;
|
||||
window.localStorage.setItem(key, chunk);
|
||||
} catch (e: any) {
|
||||
// Verify it's a quota error
|
||||
if (e.name === "QuotaExceededError") {
|
||||
if (chunkSize <= 1) return;
|
||||
chunkSize /= 2;
|
||||
continue;
|
||||
}
|
||||
throw e; // Unexpected error
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Open the style via URL input
|
||||
await when.click("nav:open");
|
||||
await when.setValue("modal:open.url.input", get.exampleFileUrl());
|
||||
await when.click("modal:open.url.button");
|
||||
|
||||
await then(get.responseBody("example-style.json")).shouldEqualToStoredStyle();
|
||||
await then(get.styleFromLocalStorage()).shouldExist();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
export default {
|
||||
output: "src/locales/$LOCALE/$NAMESPACE.json",
|
||||
locales: [ "de", "fr", "he", "it","ja", "zh" ],
|
||||
|
||||
// Because some keys are dynamically generated, i18next-parser can't detect them.
|
||||
// We add these keys manually, so we don't want to remove them.
|
||||
keepRemoved: true,
|
||||
|
||||
// We use plain English keys, so we disable key and namespace separators.
|
||||
keySeparator: false,
|
||||
namespaceSeparator: false,
|
||||
|
||||
defaultValue: (_locale, _ns, _key) => {
|
||||
// The default value is a string that indicates that the string is not translated.
|
||||
return "__STRING_NOT_TRANSLATED__";
|
||||
}
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
import { defineConfig } from "i18next-cli";
|
||||
|
||||
export default defineConfig({
|
||||
locales: ["de", "fr", "he", "it", "ja", "ko", "tr", "zh"],
|
||||
extract: {
|
||||
input: ["src/**/*.{js,jsx,ts,tsx}"],
|
||||
output: "src/locales/{{language}}/{{namespace}}.json",
|
||||
defaultValue: "__STRING_NOT_TRANSLATED__"
|
||||
}
|
||||
});
|
||||
Generated
+5491
-3630
File diff suppressed because it is too large
Load Diff
+52
-52
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "maputnik",
|
||||
"version": "3.1.0",
|
||||
"version": "3.0.0",
|
||||
"description": "A MapLibre GL visual style editor",
|
||||
"type": "module",
|
||||
"main": "''",
|
||||
@@ -9,12 +9,12 @@
|
||||
"build": "tsc && vite build --mode=production",
|
||||
"build-desktop": "tsc && vite build --mode=desktop && cd desktop && make",
|
||||
"build-linux": "tsc && vite build --mode=desktop && cd desktop && make bin/linux/maputnik",
|
||||
"i18n:extract": "npx i18next-cli extract",
|
||||
"i18n:refresh": "i18next 'src/**/*.{ts,tsx,js,jsx}'",
|
||||
"lint": "eslint",
|
||||
"test": "playwright test",
|
||||
"test-e2e": "playwright test",
|
||||
"test": "cypress run",
|
||||
"test-unit": "vitest",
|
||||
"test-unit-ci": "vitest run --coverage --reporter=json",
|
||||
"cy:open": "cypress open",
|
||||
"lint-css": "stylelint \"src/styles/*.scss\"",
|
||||
"sort-styles": "jq 'sort_by(.id)' src/config/styles.json > tmp.json && mv tmp.json src/config/styles.json"
|
||||
},
|
||||
@@ -27,56 +27,56 @@
|
||||
"homepage": "https://github.com/maplibre/maputnik#readme",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-json": "^6.0.2",
|
||||
"@codemirror/lint": "^6.9.7",
|
||||
"@codemirror/state": "^6.7.0",
|
||||
"@codemirror/lint": "^6.9.2",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@codemirror/view": "^6.43.5",
|
||||
"@codemirror/view": "^6.38.7",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@mapbox/mapbox-gl-rtl-text": "^0.4.0",
|
||||
"@maplibre/maplibre-gl-geocoder": "^1.9.4",
|
||||
"@maplibre/maplibre-gl-inspect": "^1.8.2",
|
||||
"@maplibre/maplibre-gl-style-spec": "^25.0.2",
|
||||
"@mapbox/mapbox-gl-rtl-text": "^0.3.0",
|
||||
"@maplibre/maplibre-gl-geocoder": "^1.9.1",
|
||||
"@maplibre/maplibre-gl-inspect": "^1.8.0",
|
||||
"@maplibre/maplibre-gl-style-spec": "^24.3.1",
|
||||
"array-move": "^4.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.5.1",
|
||||
"codemirror": "^6.0.2",
|
||||
"color": "^5.0.3",
|
||||
"detect-browser": "^5.3.0",
|
||||
"downshift": "^9.4.0",
|
||||
"downshift": "^9.0.10",
|
||||
"events": "^3.3.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^26.3.4",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"i18next": "^25.6.3",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
"json-stringify-pretty-compact": "^4.0.0",
|
||||
"json-to-ast": "^2.1.0",
|
||||
"lodash": "^4.18.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash.capitalize": "^4.2.1",
|
||||
"lodash.clamp": "^4.0.3",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"maplibre-gl": "^5.24.0",
|
||||
"maplibre-gl": "^5.13.0",
|
||||
"maputnik-design": "github:maputnik/design#172b06c",
|
||||
"ol": "^10.9.0",
|
||||
"ol-mapbox-style": "^13.4.1",
|
||||
"pmtiles": "^4.4.1",
|
||||
"ol": "^10.7.0",
|
||||
"ol-mapbox-style": "^13.1.1",
|
||||
"pmtiles": "^4.3.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^19.2.7",
|
||||
"react": "^19.2.0",
|
||||
"react-accessible-accordion": "^5.0.1",
|
||||
"react-aria-menubutton": "^8.0.0",
|
||||
"react-aria-menubutton": "^7.0.3",
|
||||
"react-aria-modal": "^5.0.2",
|
||||
"react-collapse": "^5.1.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-i18next": "^17.0.8",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-i18next": "^16.3.3",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"slugify": "^1.6.9",
|
||||
"slugify": "^1.6.6",
|
||||
"string-hash": "^1.1.3",
|
||||
"url": "^0.11.4"
|
||||
},
|
||||
@@ -98,12 +98,14 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@cypress/code-coverage": "^3.14.7",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||
"@playwright/test": "^1.61.1",
|
||||
"@stylistic/eslint-plugin": "^5.10.0",
|
||||
"@rollup/plugin-replace": "^6.0.2",
|
||||
"@shellygo/cypress-test-utils": "^6.0.4",
|
||||
"@stylistic/eslint-plugin": "^5.6.1",
|
||||
"@types/codemirror": "^5.60.17",
|
||||
"@types/color": "^4.2.1",
|
||||
"@types/color": "^4.2.0",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/geojson": "^7946.0.16",
|
||||
@@ -115,7 +117,7 @@
|
||||
"@types/lodash.isequal": "^4.5.8",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
"@types/randomcolor": "^0.5.9",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-aria-menubutton": "^6.2.14",
|
||||
"@types/react-aria-modal": "^5.0.0",
|
||||
"@types/react-collapse": "^5.0.4",
|
||||
@@ -123,31 +125,29 @@
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/string-hash": "^1.1.3",
|
||||
"@types/wicg-file-system-access": "^2023.10.7",
|
||||
"@vitejs/plugin-react": "5.2",
|
||||
"@vitest/browser": "^4.1.10",
|
||||
"@vitest/browser-playwright": "^4.1.10",
|
||||
"@vitest/coverage-v8": "^4.1.10",
|
||||
"cors": "^2.8.6",
|
||||
"eslint": "^10.6.0",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"@vitest/coverage-v8": "^4.0.14",
|
||||
"cors": "^2.8.5",
|
||||
"cypress": "^15.6.0",
|
||||
"cypress-plugin-tab": "^1.0.5",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.3",
|
||||
"i18next-cli": "^1.65.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.23",
|
||||
"i18next-parser": "^9.3.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"nyc": "^18.0.0",
|
||||
"postcss": "^8.5.16",
|
||||
"postcss": "^8.5.6",
|
||||
"react-hot-loader": "^4.13.1",
|
||||
"sass": "^1.101.0",
|
||||
"stylelint": "^17.14.0",
|
||||
"stylelint-config-recommended-scss": "^17.0.1",
|
||||
"stylelint-scss": "^7.2.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.62.1",
|
||||
"uuid": "^14.0.1",
|
||||
"vite": "^7.3.2",
|
||||
"vite-plugin-istanbul": "^9.0.1",
|
||||
"vitest": "^4.1.10",
|
||||
"vitest-browser-react": "^2.2.0"
|
||||
"sass": "^1.94.2",
|
||||
"stylelint": "^16.26.0",
|
||||
"stylelint-config-recommended-scss": "^16.0.2",
|
||||
"stylelint-scss": "^6.12.1",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.48.0",
|
||||
"uuid": "^13.0.0",
|
||||
"vite": "^7.2.4",
|
||||
"vite-plugin-istanbul": "^7.2.1",
|
||||
"vitest": "^4.0.14"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
const isCI = !!process.env.CI;
|
||||
// When the app is already served elsewhere (e.g. the docker e2e job) set
|
||||
// E2E_NO_WEBSERVER=1 so Playwright does not start its own dev server.
|
||||
const useExternalServer = !!process.env.E2E_NO_WEBSERVER;
|
||||
const baseURL = process.env.E2E_BASE_URL ?? "http://localhost:8888/";
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./e2e",
|
||||
testMatch: "**/*.spec.ts",
|
||||
globalSetup: "./e2e/global-setup.ts",
|
||||
globalTeardown: "./e2e/global-teardown.ts",
|
||||
fullyParallel: true,
|
||||
forbidOnly: isCI,
|
||||
retries: isCI ? 2 : 0,
|
||||
reporter: isCI ? [["list"], ["html", { open: "never" }]] : "list",
|
||||
use: {
|
||||
baseURL,
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
launchOptions: {
|
||||
// Allow WebGL (maplibre) to fall back to software rendering in headless.
|
||||
args: [
|
||||
"--disable-gpu",
|
||||
"--enable-features=AllowSwiftShaderFallback,AllowSoftwareGLFallbackDueToCrashes",
|
||||
"--enable-unsafe-swiftshader",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
webServer: useExternalServer
|
||||
? undefined
|
||||
: {
|
||||
command: "npm run start",
|
||||
url: "http://localhost:8888/maputnik/",
|
||||
reuseExistingServer: !isCI,
|
||||
timeout: 120000,
|
||||
},
|
||||
});
|
||||
+2
-23
@@ -99,7 +99,6 @@ type AppState = {
|
||||
lng: number,
|
||||
lat: number,
|
||||
},
|
||||
_from: "map" | "app"
|
||||
},
|
||||
maplibreGlDebugOptions: Partial<MapOptions> & {
|
||||
showTileBoundaries: boolean,
|
||||
@@ -149,7 +148,6 @@ export default class App extends React.Component<any, AppState> {
|
||||
lng: 0,
|
||||
lat: 0,
|
||||
},
|
||||
_from: "app"
|
||||
},
|
||||
isOpen: {
|
||||
settings: false,
|
||||
@@ -337,13 +335,6 @@ export default class App extends React.Component<any, AppState> {
|
||||
...opts,
|
||||
};
|
||||
|
||||
|
||||
// Detect empty style
|
||||
const oldStyle = this.state.mapStyle;
|
||||
const isEmptySources = !oldStyle.sources || Object.keys(oldStyle.sources).length === 0;
|
||||
const isEmptyLayers = !oldStyle.layers || oldStyle.layers.length === 0;
|
||||
const isEmptyStyle = isEmptySources && isEmptyLayers;
|
||||
|
||||
// For the style object, find the urls that has "{key}" and insert the correct API keys
|
||||
// Without this, going from e.g. MapTiler to OpenLayers and back will lose the maptlier key.
|
||||
|
||||
@@ -450,7 +441,7 @@ export default class App extends React.Component<any, AppState> {
|
||||
if (message) {
|
||||
try {
|
||||
const objPath = message.split(":")[0];
|
||||
// Errors can be deeply nested for example 'layers[0].filter[1][1][0]' we only care upto the property 'layers[0].filter'
|
||||
// Errors can be deply nested for example 'layers[0].filter[1][1][0]' we only care upto the property 'layers[0].filter'
|
||||
const unsetPath = objPath.match(/^\S+?\[\d+\]\.[^[]+/)![0];
|
||||
unset(dirtyMapStyle, unsetPath);
|
||||
}
|
||||
@@ -475,25 +466,15 @@ export default class App extends React.Component<any, AppState> {
|
||||
this.saveStyle(newStyle);
|
||||
}
|
||||
|
||||
const zoom = newStyle?.zoom;
|
||||
const center = newStyle?.center;
|
||||
|
||||
this.setState({
|
||||
mapStyle: newStyle,
|
||||
dirtyMapStyle: dirtyMapStyle,
|
||||
mapView: isEmptyStyle && zoom && center ? {
|
||||
zoom: zoom,
|
||||
center: {
|
||||
lng: center[0],
|
||||
lat: center[1],
|
||||
},
|
||||
_from: "app"
|
||||
} : this.state.mapView,
|
||||
errors: mappedErrors,
|
||||
}, () => {
|
||||
this.fetchSources();
|
||||
this.setStateInUrl();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
onUndo = () => {
|
||||
@@ -684,7 +665,6 @@ export default class App extends React.Component<any, AppState> {
|
||||
lng: number,
|
||||
lat: number,
|
||||
},
|
||||
_from: "map" | "app"
|
||||
}) => {
|
||||
this.setState({
|
||||
mapView,
|
||||
@@ -696,7 +676,6 @@ export default class App extends React.Component<any, AppState> {
|
||||
|
||||
const mapProps = {
|
||||
mapStyle: (dirtyMapStyle || mapStyle),
|
||||
mapView: this.state.mapView,
|
||||
replaceAccessTokens: (mapStyle: StyleSpecification) => {
|
||||
return style.replaceAccessTokens(mapStyle, {
|
||||
allowFallback: true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { formatLayerId } from "../libs/format";
|
||||
import { type LayerSpecification, type StyleSpecification } from "maplibre-gl";
|
||||
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||
import {formatLayerId} from "../libs/format";
|
||||
import {type LayerSpecification, type StyleSpecification} from "maplibre-gl";
|
||||
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
||||
import { type MappedError } from "../libs/definitions";
|
||||
|
||||
type AppMessagePanelInternalProps = {
|
||||
@@ -15,19 +15,21 @@ type AppMessagePanelInternalProps = {
|
||||
|
||||
class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalProps> {
|
||||
static defaultProps = {
|
||||
onLayerSelect: () => { },
|
||||
onLayerSelect: () => {},
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, selectedLayerIndex } = this.props;
|
||||
const {t, selectedLayerIndex} = this.props;
|
||||
const errors = this.props.errors?.map((error, idx) => {
|
||||
let content;
|
||||
if (error.parsed && error.parsed.type === "layer") {
|
||||
const { parsed } = error;
|
||||
const {parsed} = error;
|
||||
const layerId = this.props.mapStyle?.layers[parsed.data.index].id;
|
||||
content = (
|
||||
<>
|
||||
{t("Layer")} <span>{formatLayerId(layerId)}</span>: {parsed.data.message}
|
||||
<Trans t={t}>
|
||||
Layer <span>{formatLayerId(layerId)}</span>: {parsed.data.message}
|
||||
</Trans>
|
||||
{selectedLayerIndex !== parsed.data.index &&
|
||||
<>
|
||||
—
|
||||
@@ -45,13 +47,13 @@ class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalPro
|
||||
else {
|
||||
content = error.message;
|
||||
}
|
||||
return <p key={"error-" + idx} className="maputnik-message-panel-error">
|
||||
return <p key={"error-"+idx} className="maputnik-message-panel-error">
|
||||
{content}
|
||||
</p>;
|
||||
});
|
||||
|
||||
const infos = this.props.infos?.map((m, i) => {
|
||||
return <p key={"info-" + i}>{m}</p>;
|
||||
return <p key={"info-"+i}>{m}</p>;
|
||||
});
|
||||
|
||||
return <div className="maputnik-message-panel">
|
||||
|
||||
@@ -19,7 +19,6 @@ const CodeEditorInternal: React.FC<CodeEditorProps> = (props) => {
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
className={"maputnik-code-editor"}
|
||||
withScroll={true}
|
||||
/>;
|
||||
</>;
|
||||
};
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { render } from "vitest-browser-react";
|
||||
import { page } from "vitest/browser";
|
||||
import InputAutocomplete from "./InputAutocomplete";
|
||||
|
||||
const fruits = ["apple", "banana", "cherry"];
|
||||
|
||||
test("filters options when typing", async () => {
|
||||
render(<InputAutocomplete aria-label="Fruit" options={fruits.map((f) => [f, f])} />);
|
||||
|
||||
const input = page.getByLabelText("Fruit");
|
||||
await input.click();
|
||||
|
||||
const menuItems = page.getByRole("option");
|
||||
await expect.element(menuItems.first()).toBeVisible();
|
||||
expect(menuItems.all()).toHaveLength(3);
|
||||
|
||||
await input.fill("ch");
|
||||
await expect.element(page.getByText("cherry")).toBeVisible();
|
||||
expect(page.getByRole("option").all()).toHaveLength(1);
|
||||
|
||||
await page.getByText("cherry").click();
|
||||
await expect.element(input).toHaveValue("cherry");
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import InputAutocomplete from "./InputAutocomplete";
|
||||
import { mount } from "cypress/react";
|
||||
|
||||
const fruits = ["apple", "banana", "cherry"];
|
||||
|
||||
describe("<InputAutocomplete />", () => {
|
||||
it("filters options when typing", () => {
|
||||
mount(
|
||||
<InputAutocomplete aria-label="Fruit" options={fruits.map(f => [f, f])} />
|
||||
);
|
||||
cy.get("input").focus();
|
||||
cy.get(".maputnik-autocomplete-menu-item").should("have.length", 3);
|
||||
cy.get("input").type("ch");
|
||||
cy.get(".maputnik-autocomplete-menu-item").should("have.length", 1).and("contain", "cherry");
|
||||
cy.get(".maputnik-autocomplete-menu-item").click();
|
||||
cy.get("input").should("have.value", "cherry");
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@ import stringifyPretty from "json-stringify-pretty-compact";
|
||||
|
||||
import {createEditor} from "../libs/codemirror-editor-factory";
|
||||
import type { StylePropertySpecification } from "maplibre-gl";
|
||||
import type { TransactionSpec } from "@codemirror/state";
|
||||
|
||||
export type InputJsonProps = {
|
||||
value: object
|
||||
@@ -17,11 +16,6 @@ export type InputJsonProps = {
|
||||
onBlur?(...args: unknown[]): unknown
|
||||
lintType: "layer" | "style" | "expression" | "json"
|
||||
spec?: StylePropertySpecification | undefined
|
||||
/**
|
||||
* When setting this and using search and replace, the editor will scroll to the selected text
|
||||
* Use this only when the editor is the only element in the page.
|
||||
*/
|
||||
withScroll?: boolean
|
||||
};
|
||||
type InputJsonInternalProps = InputJsonProps & WithTranslation;
|
||||
|
||||
@@ -34,7 +28,6 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
||||
static defaultProps = {
|
||||
onFocus: () => {},
|
||||
onBlur: () => {},
|
||||
withScroll: false
|
||||
};
|
||||
_view: EditorView | undefined;
|
||||
_el: HTMLDivElement | null = null;
|
||||
@@ -81,18 +74,16 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
||||
componentDidUpdate(prevProps: InputJsonProps) {
|
||||
if (!this.state.isEditing && prevProps.value !== this.props.value) {
|
||||
this._cancelNextChange = true;
|
||||
const transactionSpec: TransactionSpec = {
|
||||
const currentSelection = this._view!.state.selection;
|
||||
this._view!.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: this._view!.state.doc.length,
|
||||
insert: this.getPrettyJson(this.props.value)
|
||||
}
|
||||
};
|
||||
if (this.props.withScroll) {
|
||||
transactionSpec.selection = this._view!.state.selection;
|
||||
transactionSpec.scrollIntoView = true;
|
||||
}
|
||||
this._view!.dispatch(transactionSpec);
|
||||
},
|
||||
selection: currentSelection,
|
||||
scrollIntoView: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { type JSX } from "react";
|
||||
import React, {type JSX} from "react";
|
||||
import { Wrapper, Button, Menu, MenuItem } from "react-aria-menubutton";
|
||||
import { Accordion } from "react-accessible-accordion";
|
||||
import { MdMoreVert } from "react-icons/md";
|
||||
import {Accordion} from "react-accessible-accordion";
|
||||
import {MdMoreVert} from "react-icons/md";
|
||||
import { IconContext } from "react-icons";
|
||||
import { type BackgroundLayerSpecification, type LayerSpecification, type SourceSpecification } from "maplibre-gl";
|
||||
import { v8 } from "@maplibre/maplibre-gl-style-spec";
|
||||
import {type BackgroundLayerSpecification, type LayerSpecification, type SourceSpecification} from "maplibre-gl";
|
||||
import {v8} from "@maplibre/maplibre-gl-style-spec";
|
||||
|
||||
import FieldJson from "./FieldJson";
|
||||
import FilterEditor from "./FilterEditor";
|
||||
@@ -18,7 +18,7 @@ import FieldComment from "./FieldComment";
|
||||
import FieldSource from "./FieldSource";
|
||||
import FieldSourceLayer from "./FieldSourceLayer";
|
||||
import { changeType, changeProperty } from "../libs/layer";
|
||||
import { formatLayerId } from "../libs/format";
|
||||
import {formatLayerId} from "../libs/format";
|
||||
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||
import { type TFunction } from "i18next";
|
||||
import { NON_SOURCE_LAYERS } from "../libs/non-source-layers";
|
||||
@@ -93,7 +93,7 @@ function getLayoutForType(type: LayerSpecification["type"], t: TFunction): Maput
|
||||
return groups;
|
||||
}
|
||||
|
||||
function layoutGroups(layerType: LayerSpecification["type"], t: TFunction): { id: string, title: string, type: string, fields?: string[] }[] {
|
||||
function layoutGroups(layerType: LayerSpecification["type"], t: TFunction): {id: string, title: string, type: string, fields?: string[]}[] {
|
||||
const layerGroup = {
|
||||
id: "layer",
|
||||
title: t("Layer"),
|
||||
@@ -116,8 +116,8 @@ function layoutGroups(layerType: LayerSpecification["type"], t: TFunction): { id
|
||||
|
||||
type LayerEditorInternalProps = {
|
||||
layer: LayerSpecification
|
||||
sources: { [key: string]: SourceSpecification & { layers: string[] } }
|
||||
vectorLayers: { [key: string]: any }
|
||||
sources: {[key: string]: SourceSpecification & {layers: string[]}}
|
||||
vectorLayers: {[key: string]: any}
|
||||
spec: any
|
||||
onLayerChanged(index: number, layer: LayerSpecification): void
|
||||
onLayerIdChange(...args: unknown[]): unknown
|
||||
@@ -132,21 +132,21 @@ type LayerEditorInternalProps = {
|
||||
} & WithTranslation;
|
||||
|
||||
type LayerEditorState = {
|
||||
editorGroups: { [keys: string]: boolean }
|
||||
editorGroups: {[keys:string]: boolean}
|
||||
};
|
||||
|
||||
/** Layer editor supporting multiple types of layers. */
|
||||
class LayerEditorInternal extends React.Component<LayerEditorInternalProps, LayerEditorState> {
|
||||
static defaultProps = {
|
||||
onLayerChanged: () => { },
|
||||
onLayerIdChange: () => { },
|
||||
onLayerDestroyed: () => { },
|
||||
onLayerChanged: () => {},
|
||||
onLayerIdChange: () => {},
|
||||
onLayerDestroyed: () => {},
|
||||
};
|
||||
|
||||
constructor(props: LayerEditorInternalProps) {
|
||||
super(props);
|
||||
|
||||
const editorGroups: { [keys: string]: boolean } = {};
|
||||
const editorGroups: {[keys:string]: boolean} = {};
|
||||
for (const group of layoutGroups(this.props.layer.type, props.t)) {
|
||||
editorGroups[group.title] = true;
|
||||
}
|
||||
@@ -158,7 +158,7 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
||||
const additionalGroups = { ...state.editorGroups };
|
||||
|
||||
for (const group of getLayoutForType(props.layer.type, props.t)) {
|
||||
if (!(group.title in additionalGroups)) {
|
||||
if(!(group.title in additionalGroups)) {
|
||||
additionalGroups[group.title] = true;
|
||||
}
|
||||
}
|
||||
@@ -188,10 +188,10 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
||||
|
||||
renderGroupType(type: string, fields?: string[]): JSX.Element {
|
||||
let comment = "";
|
||||
if (this.props.layer.metadata) {
|
||||
if(this.props.layer.metadata) {
|
||||
comment = (this.props.layer.metadata as any)["maputnik:comment"];
|
||||
}
|
||||
const { errors, layerIndex } = this.props;
|
||||
const {errors, layerIndex} = this.props;
|
||||
|
||||
const errorData: MappedLayerErrors = {};
|
||||
errors!.forEach(error => {
|
||||
@@ -208,11 +208,11 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
||||
|
||||
let sourceLayerIds;
|
||||
const layer = this.props.layer as Exclude<LayerSpecification, BackgroundLayerSpecification>;
|
||||
if (Object.prototype.hasOwnProperty.call(this.props.sources, layer.source)) {
|
||||
if(Object.prototype.hasOwnProperty.call(this.props.sources, layer.source)) {
|
||||
sourceLayerIds = this.props.sources[layer.source].layers;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
switch(type) {
|
||||
case "layer": return <div>
|
||||
<FieldId
|
||||
value={this.props.layer.id}
|
||||
@@ -237,12 +237,12 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
||||
/>
|
||||
}
|
||||
{!NON_SOURCE_LAYERS.includes(this.props.layer.type) &&
|
||||
<FieldSourceLayer
|
||||
error={errorData["source-layer"]}
|
||||
sourceLayerIds={sourceLayerIds}
|
||||
value={(this.props.layer as any)["source-layer"]}
|
||||
onChange={v => this.changeProperty(null, "source-layer", v)}
|
||||
/>
|
||||
<FieldSourceLayer
|
||||
error={errorData["source-layer"]}
|
||||
sourceLayerIds={sourceLayerIds}
|
||||
value={(this.props.layer as any)["source-layer"]}
|
||||
onChange={v => this.changeProperty(null, "source-layer", v)}
|
||||
/>
|
||||
}
|
||||
<FieldMinZoom
|
||||
error={errorData.minzoom}
|
||||
@@ -257,7 +257,7 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
||||
<FieldComment
|
||||
error={errorData.comment}
|
||||
value={comment}
|
||||
onChange={v => this.changeProperty("metadata", "maputnik:comment", v == "" ? undefined : v)}
|
||||
onChange={v => this.changeProperty("metadata", "maputnik:comment", v == "" ? undefined : v)}
|
||||
/>
|
||||
</div>;
|
||||
case "filter": return <div>
|
||||
@@ -296,7 +296,7 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
||||
moveLayer(offset: number) {
|
||||
this.props.onMoveLayer({
|
||||
oldIndex: this.props.layerIndex,
|
||||
newIndex: this.props.layerIndex + offset
|
||||
newIndex: this.props.layerIndex+offset
|
||||
});
|
||||
}
|
||||
|
||||
@@ -324,14 +324,12 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
||||
|
||||
const layout = this.props.layer.layout || {};
|
||||
|
||||
const items: {
|
||||
[key: string]: {
|
||||
text: string,
|
||||
handler: () => void,
|
||||
disabled?: boolean,
|
||||
wdKey?: string
|
||||
}
|
||||
} = {
|
||||
const items: {[key: string]: {
|
||||
text: string,
|
||||
handler: () => void,
|
||||
disabled?: boolean,
|
||||
wdKey?: string
|
||||
}} = {
|
||||
delete: {
|
||||
text: t("Delete"),
|
||||
handler: () => this.props.onLayerDestroy(this.props.layerIndex),
|
||||
@@ -366,7 +364,7 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
||||
items[id].handler();
|
||||
}
|
||||
|
||||
return <IconContext.Provider value={{ size: "14px", color: "#8e8e8e" }}>
|
||||
return <IconContext.Provider value={{size: "14px", color: "#8e8e8e"}}>
|
||||
<section className="maputnik-layer-editor"
|
||||
role="main"
|
||||
aria-label={t("Layer editor")}
|
||||
@@ -375,12 +373,12 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
||||
<header data-wd-key="layer-editor.header">
|
||||
<div className="layer-header">
|
||||
<h2 className="layer-header__title">
|
||||
{t("Layer")}: {formatLayerId(this.props.layer.id)}
|
||||
{t("Layer: {{layerId}}", { layerId: formatLayerId(this.props.layer.id) })}
|
||||
</h2>
|
||||
<div className="layer-header__info">
|
||||
<Wrapper
|
||||
className='more-menu'
|
||||
onSelection={(id, event) => handleSelection(id as string, event)}
|
||||
onSelection={handleSelection}
|
||||
closeOnSelection={false}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React from "react";
|
||||
import classnames from "classnames";
|
||||
import { MdContentCopy, MdVisibility, MdVisibilityOff, MdDelete } from "react-icons/md";
|
||||
import {MdContentCopy, MdVisibility, MdVisibilityOff, MdDelete} from "react-icons/md";
|
||||
import { IconContext } from "react-icons";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import {useSortable} from "@dnd-kit/sortable";
|
||||
import {CSS} from "@dnd-kit/utilities";
|
||||
|
||||
import IconLayer from "./IconLayer";
|
||||
import type { VisibilitySpecification } from "maplibre-gl";
|
||||
|
||||
|
||||
type DraggableLabelProps = {
|
||||
@@ -17,7 +16,7 @@ type DraggableLabelProps = {
|
||||
};
|
||||
|
||||
const DraggableLabel: React.FC<DraggableLabelProps> = (props) => {
|
||||
const { dragAttributes, dragListeners } = props;
|
||||
const {dragAttributes, dragListeners} = props;
|
||||
return <div className="maputnik-layer-list-item-handle" {...dragAttributes} {...dragListeners}>
|
||||
<IconLayer
|
||||
className="layer-handle__icon"
|
||||
@@ -40,7 +39,7 @@ type IconActionProps = {
|
||||
|
||||
class IconAction extends React.Component<IconActionProps> {
|
||||
renderIcon() {
|
||||
switch (this.props.action) {
|
||||
switch(this.props.action) {
|
||||
case "duplicate": return <MdContentCopy />;
|
||||
case "show": return <MdVisibility />;
|
||||
case "hide": return <MdVisibilityOff />;
|
||||
@@ -49,7 +48,7 @@ class IconAction extends React.Component<IconActionProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classBlockName, classBlockModifier } = this.props;
|
||||
const {classBlockName, classBlockModifier} = this.props;
|
||||
|
||||
let classAdditions = "";
|
||||
if (classBlockName) {
|
||||
@@ -79,7 +78,7 @@ type LayerListItemProps = {
|
||||
layerId: string
|
||||
layerType: string
|
||||
isSelected?: boolean
|
||||
visibility?: VisibilitySpecification
|
||||
visibility?: string
|
||||
className?: string
|
||||
onLayerSelect(index: number): void;
|
||||
onLayerCopy?(...args: unknown[]): unknown
|
||||
@@ -91,9 +90,9 @@ const LayerListItem = React.forwardRef<HTMLLIElement, LayerListItemProps>((props
|
||||
const {
|
||||
isSelected = false,
|
||||
visibility = "visible",
|
||||
onLayerCopy = () => { },
|
||||
onLayerDestroy = () => { },
|
||||
onLayerVisibilityToggle = () => { },
|
||||
onLayerCopy = () => {},
|
||||
onLayerDestroy = () => {},
|
||||
onLayerVisibilityToggle = () => {},
|
||||
} = props;
|
||||
|
||||
const {
|
||||
@@ -103,7 +102,7 @@ const LayerListItem = React.forwardRef<HTMLLIElement, LayerListItemProps>((props
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id: props.layerId });
|
||||
} = useSortable({id: props.layerId});
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
@@ -116,7 +115,7 @@ const LayerListItem = React.forwardRef<HTMLLIElement, LayerListItemProps>((props
|
||||
// Cast ref to MutableRefObject since we know from the codebase that's what's always passed
|
||||
const refObject = ref as React.MutableRefObject<HTMLLIElement | null> | null;
|
||||
|
||||
return <IconContext.Provider value={{ size: "14px" }}>
|
||||
return <IconContext.Provider value={{size: "14px"}}>
|
||||
<li
|
||||
ref={(node) => {
|
||||
setNodeRef(node);
|
||||
@@ -139,21 +138,21 @@ const LayerListItem = React.forwardRef<HTMLLIElement, LayerListItemProps>((props
|
||||
dragAttributes={attributes}
|
||||
dragListeners={listeners}
|
||||
/>
|
||||
<span style={{ flexGrow: 1 }} />
|
||||
<span style={{flexGrow: 1}} />
|
||||
<IconAction
|
||||
wdKey={"layer-list-item:" + props.layerId + ":delete"}
|
||||
wdKey={"layer-list-item:" + props.layerId+":delete"}
|
||||
action={"delete"}
|
||||
classBlockName="delete"
|
||||
onClick={_e => onLayerDestroy!(props.layerIndex)}
|
||||
/>
|
||||
<IconAction
|
||||
wdKey={"layer-list-item:" + props.layerId + ":copy"}
|
||||
wdKey={"layer-list-item:" + props.layerId+":copy"}
|
||||
action={"duplicate"}
|
||||
classBlockName="duplicate"
|
||||
onClick={_e => onLayerCopy!(props.layerIndex)}
|
||||
/>
|
||||
<IconAction
|
||||
wdKey={"layer-list-item:" + props.layerId + ":toggle-visibility"}
|
||||
wdKey={"layer-list-item:"+props.layerId+":toggle-visibility"}
|
||||
action={visibilityAction}
|
||||
classBlockName="visibility"
|
||||
classBlockModifier={visibilityAction}
|
||||
|
||||
@@ -52,14 +52,6 @@ type MapMaplibreGlInternalProps = {
|
||||
onDataChange?(event: {map: Map | null}): unknown
|
||||
onLayerSelect(index: number): void
|
||||
mapStyle: StyleSpecification
|
||||
mapView: {
|
||||
zoom: number,
|
||||
center: {
|
||||
lng: number,
|
||||
lat: number,
|
||||
},
|
||||
_from: "map" | "app"
|
||||
};
|
||||
inspectModeEnabled: boolean
|
||||
highlightedLayer?: HighlightedLayer
|
||||
options?: Partial<MapOptions> & {
|
||||
@@ -68,7 +60,7 @@ type MapMaplibreGlInternalProps = {
|
||||
showOverdrawInspector?: boolean
|
||||
}
|
||||
replaceAccessTokens(mapStyle: StyleSpecification): StyleSpecification
|
||||
onChange(value: {center: LngLat, zoom: number, _from: "map" | "app"}): unknown
|
||||
onChange(value: {center: LngLat, zoom: number}): unknown
|
||||
} & WithTranslation;
|
||||
|
||||
type MapMaplibreGlState = {
|
||||
@@ -125,11 +117,6 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
||||
map.showTileBoundaries = this.props.options?.showTileBoundaries!;
|
||||
map.showCollisionBoxes = this.props.options?.showCollisionBoxes!;
|
||||
map.showOverdrawInspector = this.props.options?.showOverdrawInspector!;
|
||||
|
||||
// set the map view when the prop was updated from outside
|
||||
if (this.props.mapView._from === "app") {
|
||||
map.jumpTo(this.props.mapView);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.state.inspect && this.props.inspectModeEnabled !== this.state.inspect._showInspectMap) {
|
||||
@@ -152,15 +139,6 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
||||
style: this.props.mapStyle,
|
||||
hash: true,
|
||||
maxZoom: 24,
|
||||
// make root relative urls in stylefiles work as maplibre gl js does
|
||||
// not support this for everything:
|
||||
// https://github.com/maplibre/maplibre-gl-js/issues/6818
|
||||
transformRequest: (url) => {
|
||||
if (url.startsWith("/")) {
|
||||
url = `${window.location.origin}${url}`;
|
||||
}
|
||||
return { url };
|
||||
},
|
||||
// setting to always load glyphs of CJK fonts from server
|
||||
// https://maplibre.org/maplibre-gl-js/docs/examples/local-ideographs/
|
||||
localIdeographFontFamily: false
|
||||
@@ -173,7 +151,7 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
||||
const mapViewChange = () => {
|
||||
const center = map.getCenter();
|
||||
const zoom = map.getZoom();
|
||||
this.props.onChange({center, zoom, _from: "map"});
|
||||
this.props.onChange({center, zoom});
|
||||
};
|
||||
mapViewChange();
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
|
||||
// Default color
|
||||
return "black";
|
||||
}
|
||||
// This is quite complex, just in case there's an edgecase we're missing
|
||||
// This is quite complex, just incase there's an edgecase we're missing
|
||||
// always return black if we get an unexpected error.
|
||||
catch (err) {
|
||||
console.warn("Unable to get feature color, error:", err);
|
||||
|
||||
@@ -21,7 +21,7 @@ import { type MappedLayerErrors } from "../libs/definitions";
|
||||
|
||||
|
||||
function setStopRefs(props: DataPropertyInternalProps, state: DataPropertyState) {
|
||||
// This is initialised below only if required to improved performance.
|
||||
// This is initialsed below only if required to improved performance.
|
||||
let newRefs: {[key: number]: string} | undefined;
|
||||
|
||||
if(props.value && props.value.stops) {
|
||||
|
||||
@@ -24,7 +24,7 @@ import { type MappedLayerErrors } from "../libs/definitions";
|
||||
* When the stops are reordered the references are also updated (see this.orderStops) this allows React to use the same key for the element and keep keyboard focus.
|
||||
*/
|
||||
function setStopRefs(props: ZoomPropertyInternalProps, state: ZoomPropertyState) {
|
||||
// This is initialised below only if required to improved performance.
|
||||
// This is initialsed below only if required to improved performance.
|
||||
let newRefs: {[key: number]: string} = {};
|
||||
|
||||
if(props.value && (props.value as ZoomWithStops).stops) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { type DragEvent, type FormEvent } from "react";
|
||||
import { MdFileUpload } from "react-icons/md";
|
||||
import { MdAddCircleOutline } from "react-icons/md";
|
||||
import React, { type FormEvent } from "react";
|
||||
import {MdFileUpload} from "react-icons/md";
|
||||
import {MdAddCircleOutline} from "react-icons/md";
|
||||
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
||||
|
||||
import ModalLoading from "./ModalLoading";
|
||||
@@ -51,20 +51,16 @@ type ModalOpenInternalProps = {
|
||||
|
||||
type ModalOpenState = {
|
||||
styleUrl: string
|
||||
isDragOver: boolean
|
||||
error?: string | null
|
||||
activeRequest?: any
|
||||
activeRequestUrl?: string | null
|
||||
};
|
||||
|
||||
class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpenState> {
|
||||
private fileInputRef = React.createRef<HTMLInputElement>();
|
||||
|
||||
constructor(props: ModalOpenInternalProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
styleUrl: "",
|
||||
isDragOver: false,
|
||||
styleUrl: ""
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,9 +72,9 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
||||
|
||||
onCancelActiveRequest(e: Event) {
|
||||
// Else the click propagates to the underlying modal
|
||||
if (e) e.stopPropagation();
|
||||
if(e) e.stopPropagation();
|
||||
|
||||
if (this.state.activeRequest) {
|
||||
if(this.state.activeRequest) {
|
||||
this.state.activeRequest.abort();
|
||||
this.setState({
|
||||
activeRequest: null,
|
||||
@@ -96,11 +92,11 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
||||
mode: "cors",
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then(function (response) {
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then((body) => {
|
||||
if (canceled) {
|
||||
if(canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,7 +122,7 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
||||
|
||||
this.setState({
|
||||
activeRequest: {
|
||||
abort: function () {
|
||||
abort: function() {
|
||||
canceled = true;
|
||||
}
|
||||
},
|
||||
@@ -187,7 +183,7 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
||||
try {
|
||||
mapStyle = JSON.parse(e.target?.result as string);
|
||||
}
|
||||
catch (err) {
|
||||
catch(err) {
|
||||
this.setState({
|
||||
error: (err as Error).toString()
|
||||
});
|
||||
@@ -202,45 +198,12 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
||||
|
||||
onOpenToggle() {
|
||||
this.setState({
|
||||
styleUrl: "",
|
||||
isDragOver: false,
|
||||
styleUrl: ""
|
||||
});
|
||||
this.clearError();
|
||||
this.props.onOpenToggle();
|
||||
}
|
||||
|
||||
onBrowseClick = async () => {
|
||||
if (typeof window.showOpenFilePicker === "function") {
|
||||
await this.onOpenFile();
|
||||
return;
|
||||
}
|
||||
|
||||
this.fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
onFileDragOver = (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!this.state.isDragOver) {
|
||||
this.setState({ isDragOver: true });
|
||||
}
|
||||
};
|
||||
|
||||
onFileDragLeave = (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({ isDragOver: false });
|
||||
};
|
||||
|
||||
onFileDrop = (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.setState({ isDragOver: false });
|
||||
this.onFileChanged(e.dataTransfer.files);
|
||||
};
|
||||
|
||||
onChangeUrl = (url: string) => {
|
||||
this.setState({
|
||||
styleUrl: url,
|
||||
@@ -260,7 +223,7 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
||||
});
|
||||
|
||||
let errorElement;
|
||||
if (this.state.error) {
|
||||
if(this.state.error) {
|
||||
errorElement = (
|
||||
<div className="maputnik-modal-error">
|
||||
{this.state.error}
|
||||
@@ -281,35 +244,19 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
||||
<section className="maputnik-modal-section">
|
||||
<h1>{t("Open local Style")}</h1>
|
||||
<p>{t("Open a local JSON style from your computer.")}</p>
|
||||
<div
|
||||
data-wd-key="modal:open.dropzone"
|
||||
className={`maputnik-upload-dropzone${this.state.isDragOver ? " maputnik-upload-dropzone--active" : ""}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onDragOver={this.onFileDragOver}
|
||||
onDragLeave={this.onFileDragLeave}
|
||||
onDrop={this.onFileDrop}
|
||||
onClick={() => void this.onBrowseClick()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
void this.onBrowseClick();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="maputnik-upload-dropzone-content">
|
||||
<MdFileUpload className="maputnik-upload-dropzone-icon" aria-hidden="true" />
|
||||
<p className="maputnik-upload-dropzone-text">
|
||||
{t("Drag and drop a style JSON file here or click to browse")}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
ref={this.fileInputRef}
|
||||
data-wd-key="modal:open.file.input"
|
||||
type="file"
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => this.onFileChanged(e.target.files)}
|
||||
/>
|
||||
<div>
|
||||
{typeof window.showOpenFilePicker === "function" ? (
|
||||
<InputButton
|
||||
data-wd-key="modal:open.file.button"
|
||||
className="maputnik-big-button"
|
||||
onClick={this.onOpenFile}><MdFileUpload/> {t("Open Style")}
|
||||
</InputButton>
|
||||
) : (
|
||||
<label>
|
||||
<a className="maputnik-button maputnik-upload-button" aria-label={t("Open Style")}><MdFileUpload /> {t("Open Style")}</a>
|
||||
<input data-wd-key="modal:open.file.input" type="file" style={{ display: "none" }} onChange={(e) => this.onFileChanged(e.target.files)} />
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -357,7 +304,7 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
||||
isOpen={!!this.state.activeRequest}
|
||||
title={t("Loading style")}
|
||||
onCancel={(e: Event) => this.onCancelActiveRequest(e)}
|
||||
message={t("Loading") + ": " + this.state.activeRequestUrl}
|
||||
message={t("Loading: {{requestUrl}}", { requestUrl: this.state.activeRequestUrl })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,8 +10,6 @@ export const supportedLanguages = {
|
||||
"he": "עברית",
|
||||
"it": "Italiano",
|
||||
"ja": "日本語",
|
||||
"ko": "한국어",
|
||||
"tr": "Türkçe",
|
||||
"zh": "简体中文"
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export default class LayerWatcher {
|
||||
|
||||
// Since we scan over all features we want to avoid this as much as
|
||||
// possible and only do it after a batch of data has loaded because
|
||||
// we only care eventually about knowing the fields in the vector layers
|
||||
// we only care eventuall about knowing the fields in the vector layers
|
||||
this.throttledAnalyzeVectorLayerFields = throttle(this.analyzeVectorLayerFields, 5000);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,12 +36,6 @@ describe("validate", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when URL is root relative", () => {
|
||||
it("should return ErrorType.None", () => {
|
||||
expect(validate("/static/style.json")).toBe(ErrorType.None);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when window.location.protocol is https:", () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window.location, "protocol", {
|
||||
|
||||
+2
-7
@@ -45,13 +45,8 @@ function getProtocolSafe(url: string): { protocol?: string, isLocal?: boolean }
|
||||
}
|
||||
};
|
||||
|
||||
export function validate(url?: string): ErrorType {
|
||||
if (!url) {
|
||||
return ErrorType.None;
|
||||
}
|
||||
|
||||
// allow root-relative URLs ("/static/style.json") but do not allow protocol-relative URLs ("//example.com")
|
||||
if (url.startsWith("/") && !url.startsWith("//")) {
|
||||
export function validate(url: string): ErrorType {
|
||||
if (url === "") {
|
||||
return ErrorType.None;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,12 +49,10 @@ The following users can help you with the relevant languages:
|
||||
| de | German | [@josxha](https://github.com/josxha) |
|
||||
| en | English | [@HarelM](https://github.com/HarelM) |
|
||||
| fr | French | [@lhapaipai](https://github.com/lhapaipai) |
|
||||
| he | Hebrew | [@HarelM](https://github.com/HarelM) |
|
||||
| hr | Hebrew | [@HarelM](https://github.com/HarelM) |
|
||||
| it | Italian | [@napo](https://github.com/napo) |
|
||||
| ja | Japanese | [@keichan34](https://github.com/keichan34) |
|
||||
| ko | Korean | [@jinifor](https://github.com/jinifor) |
|
||||
| zh | Simplified Chinese | [@jieme](https://github.com/jieme) |
|
||||
| tr | Turkish | [@mcihad](https://github.com/mcihad) |
|
||||
|
||||
You can test the UI in different languages using the dropdown in the top menu
|
||||
Note that Maputnik automatically localize based on browser language settings and stores this language in local storage.
|
||||
|
||||
+194
-180
@@ -1,204 +1,218 @@
|
||||
{
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "<0>In OSM öffnen</0>. Öffnet die aktuelle Ansicht auf openstreetmap.org",
|
||||
"Achromatopsia filter": "Achromatopsie-Filter",
|
||||
"Active Sources": "Aktive Quellen",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "Füge eine neue Quelle zu deinem Stil hinzu. Du kannst den Quellentyp und die ID nur bei der Erstellung auswählen!",
|
||||
"Add filter": "Filter hinzufügen",
|
||||
"Add Layer": "Ebene hinzufügen",
|
||||
"Add New Source": "Neue Quelle hinzufügen",
|
||||
"Add one of the publicly available sources to your style.": "Füge eine der öffentlich verfügbaren Quellen zu deinem Stil hinzu.",
|
||||
"Add Source": "Quelle hinzufügen",
|
||||
"Add stop": "Stopp hinzufügen",
|
||||
"Add value": "Wert hinzufügen",
|
||||
"Add Variable": "Variable hinzufügen",
|
||||
"any filter matches": "irgendein Filter passt",
|
||||
"API key for Stadia Maps.": "API-Schlüssel für Stadia Maps.",
|
||||
"Input value": "Eingabewert",
|
||||
"Data value": "Datenwert",
|
||||
"Output value": "Ausgabewert",
|
||||
"Function": "Funktion",
|
||||
"Select a type of data scale (default is 'categorical').": "Wähle eine Art Datenskala (Standard ist 'kategorisch').",
|
||||
"Base": "Basis",
|
||||
"Bearing": "Ausrichtung",
|
||||
"Cancel": "Abbrechen",
|
||||
"Center": "Mittelpunkt",
|
||||
"Choose Public Source": "Öffentliche Quelle auswählen",
|
||||
"Choose the default Maputnik renderer for this style.": "Wähle den Standard-Renderer für diesen Stil aus.",
|
||||
"Click to close the editor": "Klicken Sie hier, um den Editor zu schließen",
|
||||
"Close": "Schließen",
|
||||
"Close modal": "Modale Fenster schließen",
|
||||
"Close popup": "Popup schließen",
|
||||
"Cluster": "Cluster",
|
||||
"Code Editor": "Code-Editor",
|
||||
"Collapse": "Einklappen",
|
||||
"Color accessibility": "Farbzugänglichkeit",
|
||||
"Comment...": "Dein Kommentar...",
|
||||
"Comments": "Kommentare",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "Kommentare zur aktuellen Ebene. Das ist nicht standardmäßig und nicht in der Spezifikation.",
|
||||
"Convert property into a elevation function": "Eigenschaft in eine Höhenfunktion umwandeln",
|
||||
"Input a data property to base styles off of.": "Gib eine Dateneigenschaft ein, um Stile darauf zu basieren.",
|
||||
"Default": "Standard",
|
||||
"Stops": "Stopps",
|
||||
"Zoom": "Zoom",
|
||||
"Add stop": "Stopp hinzufügen",
|
||||
"Convert to expression": "In Ausdruck umwandeln",
|
||||
"Remove zoom level from stop": "Zoom-Stufe vom Stopp entfernen",
|
||||
"Revert from expression": "Vom Ausdruck zurücksetzen",
|
||||
"Delete expression": "Ausdruck löschen",
|
||||
"Convert property into a zoom function": "Eigenschaft in eine Zoom-Funktion umwandeln",
|
||||
"Convert property to data function": "Eigenschaft in eine Datenfunktion umwandeln",
|
||||
"Convert to expression": "In Ausdruck umwandeln",
|
||||
"Coord bottom left": "Koordinate unten links",
|
||||
"Coord bottom right": "Koordinate unten rechts",
|
||||
"Coord top left": "Koordinate oben links",
|
||||
"Coord top right": "Koordinate oben rechts",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "Die CORS-Politik erlaubt es nicht, Ressourcen über http von https zu laden, benutze eine <1>https://</1>-Domain",
|
||||
"Create HTML": "HTML erstellen",
|
||||
"Data Sources": "Datenquellen",
|
||||
"Data Sources modal": "Modale Datenquellen",
|
||||
"Data value": "Datenwert",
|
||||
"Debug": "Debug",
|
||||
"Debug modal": "Debug (modale Fenster)",
|
||||
"Decrease the pitch by 10 degrees.": "Neigung um 10 Grad verringern.",
|
||||
"Decrease the rotation by 15 degrees.": "Rotation um 15 Grad verringern.",
|
||||
"Decrease the zoom level by 1.": "Zoom-Stufe um 1 verringern.",
|
||||
"Decrease the zoom level by 2.": "Zoom-Stufe um 2 verringern.",
|
||||
"Default": "Standard",
|
||||
"Delete": "Löschen",
|
||||
"Delete expression": "Ausdruck löschen",
|
||||
"Delete filter block": "Filterblock löschen",
|
||||
"Deuteranopia filter": "Deuteranopie-Filter",
|
||||
"Duplicate": "Duplizieren",
|
||||
"Encoding": "Kodierung",
|
||||
"Enter URL...": "URL eingeben...",
|
||||
"every filter matches": "alle Filter passen",
|
||||
"Expand": "Ausklappen",
|
||||
"Export modal": "Modale Fenster exportieren",
|
||||
"Filter": "Filter",
|
||||
"Focus map": "Karte fokussieren",
|
||||
"Function": "Funktion",
|
||||
"Gallery Styles": "Galerie-Stile",
|
||||
"General layout properties": "Allgemeine Layouteigenschaften",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"GeoJSON URL": "GeoJSON URL",
|
||||
"Global State": "Globaler Zustand",
|
||||
"Global State Variables": "Globale Zustandsvariablen",
|
||||
"Glyphs URL": "Glyphen-URL",
|
||||
"Help": "Hilfe",
|
||||
"Hide": "Verstecken",
|
||||
"Icon layout properties": "Icon-Layouteigenschaften",
|
||||
"Icon paint properties": "Icon-Darstellungseigenschaften",
|
||||
"If the Map is in focused you can use the following shortcuts": "Wenn die Karte fokussiert ist, kannst du die folgenden Shortcuts benutzen",
|
||||
"Image": "Bild",
|
||||
"Image URL": "Bild-URL",
|
||||
"Increase the pitch by 10 degrees.": "Neigung um 10 Grad erhöhen.",
|
||||
"Increase the rotation by 15 degrees.": "Rotation um 15 Grad erhöhen.",
|
||||
"Increase the zoom level by 1.": "Zoom-Stufe um 1 erhöhen.",
|
||||
"Increase the zoom level by 2.": "Zoom-Stufe um 2 erhöhen.",
|
||||
"Input a data property to base styles off of.": "Gib eine Dateneigenschaft ein, um Stile darauf zu basieren.",
|
||||
"Input value": "Eingabewert",
|
||||
"Convert property into a elevation function": "Eigenschaft in eine Höhenfunktion umwandeln",
|
||||
"Layer <1>{formatLayerId(layerId)}</1>: {parsed.data.message}": "Ebene <1>{formatLayerId(layerId)}</1>: {parsed.data.message}",
|
||||
"switch to layer": "zur Ebene wechseln",
|
||||
"Map": "Karte",
|
||||
"Inspect": "Untersuchen",
|
||||
"JSON Editor": "JSON-Editor",
|
||||
"Key": "Schlüssel",
|
||||
"Layer": "Ebene",
|
||||
"Layer editor": "Ebenen-Editor",
|
||||
"Layer ID already exists": "Layer-ID existiert bereits",
|
||||
"Layers": "Ebenen",
|
||||
"Deuteranopia filter": "Deuteranopie-Filter",
|
||||
"Protanopia filter": "Protanopie-Filter",
|
||||
"Tritanopia filter": "Tritanopie-Filter",
|
||||
"Achromatopsia filter": "Achromatopsie-Filter",
|
||||
"Layers list": "Ebenenliste",
|
||||
"Layer editor": "Ebenen-Editor",
|
||||
"Map view": "Kartenansicht",
|
||||
"Maputnik on GitHub": "Maputnik auf GitHub",
|
||||
"Open": "Öffnen",
|
||||
"Save": "Speichern",
|
||||
"Code Editor": "Code-Editor",
|
||||
"Data Sources": "Datenquellen",
|
||||
"Style Settings": "Stileinstellungen",
|
||||
"Global State": "Globaler Zustand",
|
||||
"View": "Ansicht",
|
||||
"Color accessibility": "Farbzugänglichkeit",
|
||||
"Help": "Hilfe",
|
||||
"Close": "Schließen",
|
||||
"Click to close the editor": "Klicken Sie hier, um den Editor zu schließen",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "Kommentare zur aktuellen Ebene. Das ist nicht standardmäßig und nicht in der Spezifikation.",
|
||||
"Comments": "Kommentare",
|
||||
"Comment...": "Dein Kommentar...",
|
||||
"Max Zoom": "Max-Zoom",
|
||||
"Min Zoom": "Min-Zoom",
|
||||
"Source": "Quelle",
|
||||
"Source Layer": "Quellenebene",
|
||||
"Type": "Typ",
|
||||
"Nested filters are not supported.": "Verschachtelte Filter werden nicht unterstützt.",
|
||||
"Upgrade to expression": "Aufrüsten zu Ausdruck",
|
||||
"Filter": "Filter",
|
||||
"every filter matches": "alle Filter passen",
|
||||
"no filter matches": "kein Filter passt",
|
||||
"any filter matches": "irgendein Filter passt",
|
||||
"Add filter": "Filter hinzufügen",
|
||||
"You've entered an old style filter.": "Du hast einen alten Filter-Stil eingegeben.",
|
||||
"Switch to filter editor.": "Zum Filter-Editor wechseln.",
|
||||
"Delete filter block": "Filterblock löschen",
|
||||
"Add value": "Wert hinzufügen",
|
||||
"Remove array item": "Element aus Array entfernen",
|
||||
"Must provide protocol: <1>https://</1>": "Protokoll erforderlich: <1>https://</1>",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "Protokoll erforderlich: <1>http://</1> oder <3>https://</3>",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "Die CORS-Politik erlaubt es nicht, Ressourcen über http von https zu laden, benutze eine <1>https://</1>-Domain",
|
||||
"General layout properties": "Allgemeine Layouteigenschaften",
|
||||
"Text layout properties": "Text-Layouteigenschaften",
|
||||
"Icon layout properties": "Icon-Layouteigenschaften",
|
||||
"Text paint properties": "Text-Darstellungseigenschaften",
|
||||
"Icon paint properties": "Icon-Darstellungseigenschaften",
|
||||
"Paint properties": "Darstellungseigenschaften",
|
||||
"Layout properties": "Layouteigenschaften",
|
||||
"Learn More": "Mehr erfahren",
|
||||
"Layer": "Ebene",
|
||||
"JSON Editor": "JSON-Editor",
|
||||
"Delete": "Löschen",
|
||||
"Duplicate": "Duplizieren",
|
||||
"Show": "Anzeigen",
|
||||
"Hide": "Verstecken",
|
||||
"Move layer up": "Ebene nach oben verschieben",
|
||||
"Move layer down": "Ebene nach unten verschieben",
|
||||
"Layer: {{layerId}}": "Ebene: {{layerId}}",
|
||||
"Layers": "Ebenen",
|
||||
"Collapse": "Einklappen",
|
||||
"Expand": "Ausklappen",
|
||||
"Add Layer": "Ebene hinzufügen",
|
||||
"Search": "Suche",
|
||||
"Zoom:": "Zoom:",
|
||||
"Close popup": "Popup schließen",
|
||||
"cursor:": "Mauszeiger:",
|
||||
"center:": "Zentrum:",
|
||||
"rotation:": "Rotation:",
|
||||
"Close modal": "Modale Fenster schließen",
|
||||
"Layer ID already exists": "Layer-ID existiert bereits",
|
||||
"Debug": "Debug",
|
||||
"Options": "Optionen",
|
||||
"Links": "Links",
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "<0>In OSM öffnen</0>. Öffnet die aktuelle Ansicht auf openstreetmap.org",
|
||||
"Save Style": "Stil Speichern",
|
||||
"Save the JSON style to your computer.": "Speichere den JSON Stil auf deinem Computer.",
|
||||
"Save as": "Speichern unter",
|
||||
"Create HTML": "HTML erstellen",
|
||||
"Key": "Schlüssel",
|
||||
"Value": "Wert",
|
||||
"Remove variable": "Variable entfernen",
|
||||
"Global State Variables": "Globale Zustandsvariablen",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "Keine globalen Zustandsvariablen definiert. Füge Variablen hinzu, um wiederverwendbare Werte in deinem Stil zu erstellen.",
|
||||
"Add Variable": "Variable hinzufügen",
|
||||
"Cancel": "Abbrechen",
|
||||
"Open Style": "Stil öffnen",
|
||||
"Open local Style": "Lokalen Stil öffnen",
|
||||
"Open a local JSON style from your computer.": "Öffne einen lokalen JSON Stil von deinem Computer.",
|
||||
"Load from URL": "Von URL laden",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "Von einer URL laden. Beachte, dass die URL <1>CORS aktiviert</1> haben muss.",
|
||||
"Style URL": "Stil-URL",
|
||||
"Enter URL...": "URL eingeben...",
|
||||
"Gallery Styles": "Galerie-Stile",
|
||||
"Open one of the publicly available styles to start from.": "Öffne einen der öffentlich verfügbaren Stile, um zu starten.",
|
||||
"Loading style": "Stil wird geladen",
|
||||
"Loading: {{requestUrl}}": "Lädt: {{requestUrl}}",
|
||||
"Name": "Name",
|
||||
"Owner": "Besitzer",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "Besitzer-ID des Stils. Wird von Mapbox oder zukünftigen Style-APIs verwendet.",
|
||||
"Sprite URL": "Sprite-URL",
|
||||
"Glyphs URL": "Glyphen-URL",
|
||||
"Center": "Mittelpunkt",
|
||||
"Bearing": "Ausrichtung",
|
||||
"Pitch": "Neigung",
|
||||
"Light anchor": "Lichtanker",
|
||||
"Light color": "Lichtfarbe",
|
||||
"Light intensity": "Lichtintensität",
|
||||
"Light position": "Lichtposition",
|
||||
"Links": "Links",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "Von einer URL laden. Beachte, dass die URL <1>CORS aktiviert</1> haben muss.",
|
||||
"Load from URL": "Von URL laden",
|
||||
"Loading": "Lädt",
|
||||
"Loading style": "Stil wird geladen",
|
||||
"LocationIQ Access Token": "LocationIQ Zugriffstoken",
|
||||
"Map": "Karte",
|
||||
"Map view": "Kartenansicht",
|
||||
"MapTiler Access Token": "MapTiler Zugriffstoken",
|
||||
"Maputnik on GitHub": "Maputnik auf GitHub",
|
||||
"Max Zoom": "Max-Zoom",
|
||||
"Min Zoom": "Min-Zoom",
|
||||
"Move layer down": "Ebene nach unten verschieben",
|
||||
"Move layer up": "Ebene nach oben verschieben",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "Protokoll erforderlich: <1>http://</1> oder <3>https://</3>",
|
||||
"Must provide protocol: <1>https://</1>": "Protokoll erforderlich: <1>https://</1>",
|
||||
"Name": "Name",
|
||||
"Nested filters are not supported.": "Verschachtelte Filter werden nicht unterstützt.",
|
||||
"no filter matches": "kein Filter passt",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "Keine globalen Zustandsvariablen definiert. Füge Variablen hinzu, um wiederverwendbare Werte in deinem Stil zu erstellen.",
|
||||
"Open": "Öffnen",
|
||||
"Open a local JSON style from your computer.": "Öffne einen lokalen JSON Stil von deinem Computer.",
|
||||
"Terrain source": "Geländequelle",
|
||||
"Terrain exaggeration": "Geländeüberhöhung",
|
||||
"Transition delay": "Übergangsverzögerung",
|
||||
"Transition duration": "Übergangsdauer",
|
||||
"Projection": "Projektion",
|
||||
"Open Layers (experimental)": "Ebenen öffnen (experimentell)",
|
||||
"Open local Style": "Lokalen Stil öffnen",
|
||||
"Shortcuts menu": "Shortcuts-Menü",
|
||||
"Open modal": "Modale Fenster öffnen",
|
||||
"Open one of the publicly available styles to start from.": "Öffne einen der öffentlich verfügbaren Stile, um zu starten.",
|
||||
"Open Style": "Stil öffnen",
|
||||
"Options": "Optionen",
|
||||
"Output value": "Ausgabewert",
|
||||
"Owner": "Besitzer",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "Besitzer-ID des Stils. Wird von Mapbox oder zukünftigen Style-APIs verwendet.",
|
||||
"Paint properties": "Darstellungseigenschaften",
|
||||
"Export modal": "Modale Fenster exportieren",
|
||||
"Data Sources modal": "Modale Datenquellen",
|
||||
"Style Settings modal": "Modale Stileinstellungen",
|
||||
"Toggle inspect": "Inspektion umschalten",
|
||||
"Focus map": "Karte fokussieren",
|
||||
"Debug modal": "Debug (modale Fenster)",
|
||||
"Increase the zoom level by 1.": "Zoom-Stufe um 1 erhöhen.",
|
||||
"Increase the zoom level by 2.": "Zoom-Stufe um 2 erhöhen.",
|
||||
"Decrease the zoom level by 1.": "Zoom-Stufe um 1 verringern.",
|
||||
"Decrease the zoom level by 2.": "Zoom-Stufe um 2 verringern.",
|
||||
"Pan up by 100 pixels.": "Um 100 Pixel nach oben schwenken.",
|
||||
"Pan down by 100 pixels.": "Um 100 Pixel nach unten schwenken.",
|
||||
"Pan left by 100 pixels.": "Um 100 Pixel nach links schwenken.",
|
||||
"Pan right by 100 pixels.": "Um 100 Pixel nach rechts schwenken.",
|
||||
"Pan up by 100 pixels.": "Um 100 Pixel nach oben schwenken.",
|
||||
"Pitch": "Neigung",
|
||||
"PMTiles URL": "PMTiles URL",
|
||||
"Increase the rotation by 15 degrees.": "Rotation um 15 Grad erhöhen.",
|
||||
"Decrease the rotation by 15 degrees.": "Rotation um 15 Grad verringern.",
|
||||
"Increase the pitch by 10 degrees.": "Neigung um 10 Grad erhöhen.",
|
||||
"Decrease the pitch by 10 degrees.": "Neigung um 10 Grad verringern.",
|
||||
"Shortcuts": "Shortcuts",
|
||||
"Press <1>ESC</1> to lose focus of any active elements, then press one of:": "Drück <1>ESC</1>, um den Fokus von aktiven Elementen zu verlieren, und dann drück eine der folgenden Tasten:",
|
||||
"Projection": "Projektion",
|
||||
"Protanopia filter": "Protanopie-Filter",
|
||||
"Public access token for LocationIQ services.": "Öffentlicher Zugriffstoken für LocationIQ-Dienste.",
|
||||
"Public access token for MapTiler Cloud.": "Öffentlicher Zugriffstoken für MapTiler Cloud.",
|
||||
"Public access token for Thunderforest services.": "Öffentlicher Zugriffstoken für Thunderforest-Dienste.",
|
||||
"Raster (Tile URLs)": "Raster (Tile URLs)",
|
||||
"If the Map is in focused you can use the following shortcuts": "Wenn die Karte fokussiert ist, kannst du die folgenden Shortcuts benutzen",
|
||||
"Remove '{{sourceId}}' source": "Quelle '{{sourceId}}' entfernen",
|
||||
"Source ID": "Quellen-ID",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "Eindeutige ID, die die Quelle identifiziert und in der Ebene verwendet wird, um auf die Quelle zu verweisen.",
|
||||
"Source Type": "Quellentyp",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"Vector (TileJSON URL)": "Vektor (TileJSON URL)",
|
||||
"Vector (Tile URLs)": "Vektor (Tile URLs)",
|
||||
"Raster (TileJSON URL)": "Raster (TileJSON URL)",
|
||||
"Raster (Tile URLs)": "Raster (Tile URLs)",
|
||||
"Raster DEM (TileJSON URL)": "Raster DEM (TileJSON URL)",
|
||||
"Raster DEM (XYZ URLs)": "Raster DEM (XYZ URLs)",
|
||||
"Remove '{{sourceId}}' source": "Quelle '{{sourceId}}' entfernen",
|
||||
"Remove array item": "Element aus Array entfernen",
|
||||
"Remove variable": "Variable entfernen",
|
||||
"Remove zoom level from stop": "Zoom-Stufe vom Stopp entfernen",
|
||||
"Revert from expression": "Vom Ausdruck zurücksetzen",
|
||||
"Save": "Speichern",
|
||||
"Save as": "Speichern unter",
|
||||
"Save Style": "Stil Speichern",
|
||||
"Save the JSON style to your computer.": "Speichere den JSON Stil auf deinem Computer.",
|
||||
"Scheme Type": "Schema-Typ",
|
||||
"Search": "Suche",
|
||||
"Select a type of data scale (default is 'categorical').": "Wähle eine Art Datenskala (Standard ist 'kategorisch').",
|
||||
"Shortcuts": "Shortcuts",
|
||||
"Shortcuts menu": "Shortcuts-Menü",
|
||||
"Show": "Anzeigen",
|
||||
"Source": "Quelle",
|
||||
"Source ID": "Quellen-ID",
|
||||
"Source Layer": "Quellenebene",
|
||||
"Source Type": "Quellentyp",
|
||||
"Sources": "Quellen",
|
||||
"Sprite URL": "Sprite-URL",
|
||||
"Stadia Maps API Key": "Stadia Maps API-Schlüssel",
|
||||
"Stops": "Stopps",
|
||||
"Style Renderer": "Stil-Renderer",
|
||||
"Style Settings": "Stileinstellungen",
|
||||
"Style Settings modal": "Modale Stileinstellungen",
|
||||
"Style URL": "Stil-URL",
|
||||
"Switch to filter editor.": "Zum Filter-Editor wechseln.",
|
||||
"switch to layer": "zur Ebene wechseln",
|
||||
"Terrain exaggeration": "Geländeüberhöhung",
|
||||
"Terrain source": "Geländequelle",
|
||||
"Text layout properties": "Text-Layouteigenschaften",
|
||||
"Text paint properties": "Text-Darstellungseigenschaften",
|
||||
"Thunderforest Access Token": "Thunderforest Zugriffstoken",
|
||||
"Tile Size": "Kachelgröße",
|
||||
"Tile URL": "Kachel-URL",
|
||||
"TileJSON URL": "TileJSON URL",
|
||||
"Toggle inspect": "Inspektion umschalten",
|
||||
"Transition delay": "Übergangsverzögerung",
|
||||
"Transition duration": "Übergangsdauer",
|
||||
"Tritanopia filter": "Tritanopie-Filter",
|
||||
"Type": "Typ",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "Eindeutige ID, die die Quelle identifiziert und in der Ebene verwendet wird, um auf die Quelle zu verweisen.",
|
||||
"Upgrade to expression": "Aufrüsten zu Ausdruck",
|
||||
"Value": "Wert",
|
||||
"Vector (PMTiles)": "Vektor (PMTiles)",
|
||||
"Vector (Tile URLs)": "Vektor (Tile URLs)",
|
||||
"Vector (TileJSON URL)": "Vektor (TileJSON URL)",
|
||||
"Image": "Bild",
|
||||
"Video": "Video",
|
||||
"Add Source": "Quelle hinzufügen",
|
||||
"Sources": "Quellen",
|
||||
"Active Sources": "Aktive Quellen",
|
||||
"Choose Public Source": "Öffentliche Quelle auswählen",
|
||||
"Add one of the publicly available sources to your style.": "Füge eine der öffentlich verfügbaren Quellen zu deinem Stil hinzu.",
|
||||
"Add New Source": "Neue Quelle hinzufügen",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "Füge eine neue Quelle zu deinem Stil hinzu. Du kannst den Quellentyp und die ID nur bei der Erstellung auswählen!",
|
||||
"TileJSON URL": "TileJSON URL",
|
||||
"Tile URL": "Kachel-URL",
|
||||
"Scheme Type": "Schema-Typ",
|
||||
"Coord top left": "Koordinate oben links",
|
||||
"Coord top right": "Koordinate oben rechts",
|
||||
"Coord bottom right": "Koordinate unten rechts",
|
||||
"Coord bottom left": "Koordinate unten links",
|
||||
"Image URL": "Bild-URL",
|
||||
"Video URL": "Video-URL",
|
||||
"View": "Ansicht",
|
||||
"You've entered an old style filter.": "Du hast einen alten Filter-Stil eingegeben.",
|
||||
"Zoom": "Zoom",
|
||||
"Drag and drop a style JSON file here or click to browse": "Ziehen Sie eine Style-JSON-Datei hierher oder klicken Sie, um zu durchsuchen"
|
||||
"GeoJSON URL": "GeoJSON URL",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"Cluster": "Cluster",
|
||||
"PMTiles URL": "PMTiles URL",
|
||||
"Tile Size": "Kachelgröße",
|
||||
"Encoding": "Kodierung",
|
||||
"Error:": "Fehler:",
|
||||
"MapTiler Access Token": "MapTiler Zugriffstoken",
|
||||
"Public access token for MapTiler Cloud.": "Öffentlicher Zugriffstoken für MapTiler Cloud.",
|
||||
"Learn More": "Mehr erfahren",
|
||||
"Thunderforest Access Token": "Thunderforest Zugriffstoken",
|
||||
"Public access token for Thunderforest services.": "Öffentlicher Zugriffstoken für Thunderforest-Dienste.",
|
||||
"Stadia Maps API Key": "Stadia Maps API-Schlüssel",
|
||||
"API key for Stadia Maps.": "API-Schlüssel für Stadia Maps.",
|
||||
"LocationIQ Access Token": "LocationIQ Zugriffstoken",
|
||||
"Public access token for LocationIQ services.": "Öffentlicher Zugriffstoken für LocationIQ-Dienste.",
|
||||
"Style Renderer": "Stil-Renderer",
|
||||
"Choose the default Maputnik renderer for this style.": "Wähle den Standard-Renderer für diesen Stil aus.",
|
||||
"Opacity": "Deckkraft",
|
||||
"Radius": "Radius",
|
||||
"Color": "Farbe",
|
||||
"Outline color": "Umrissfarbe",
|
||||
"Width": "Breite",
|
||||
"Height": "Höhe",
|
||||
"Field": "Feld",
|
||||
"Size": "Größe"
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"Drag and drop a style JSON file here or click to browse": "Drag and drop a style JSON file here or click to browse"
|
||||
}
|
||||
+186
-180
@@ -1,204 +1,210 @@
|
||||
{
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "<0>Ouvrir dans OSM</0>. Ouvre la vue actuelle sur openstreetmap.org",
|
||||
"Achromatopsia filter": "Filtre Achromatopsie",
|
||||
"Active Sources": "Sources actives",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "Ajoutez une nouvelle source à votre style. Vous ne pouvez choisir le type et l'ID de la source qu'au moment de la création !",
|
||||
"Add filter": "Ajouter un filtre",
|
||||
"Add Layer": "Ajouter un calque",
|
||||
"Add New Source": "Ajouter une nouvelle source",
|
||||
"Add one of the publicly available sources to your style.": "Ajoutez l'une des sources publiques disponibles à votre style.",
|
||||
"Add Source": "Ajouter une source",
|
||||
"Add stop": "Ajouter un arrêt",
|
||||
"Add value": "Ajouter une valeur",
|
||||
"Add Variable": "Ajouter une variable",
|
||||
"any filter matches": "l'un des filtres correspond",
|
||||
"API key for Stadia Maps.": "Clé d'API pour Stadia Maps.",
|
||||
"Input value": "Valeur d'entrée",
|
||||
"Data value": "Valeur des données",
|
||||
"Output value": "Valeur de sortie",
|
||||
"Function": "Fonction",
|
||||
"Select a type of data scale (default is 'categorical').": "Sélectionnez un type d'échelle de données (par défaut 'categorical').",
|
||||
"Base": "Base",
|
||||
"Bearing": "Orientation",
|
||||
"Cancel": "Annuler",
|
||||
"Center": "Centre",
|
||||
"Choose Public Source": "Choisir une source publique",
|
||||
"Choose the default Maputnik renderer for this style.": "Choisissez le moteur de rendu Maputnik par défaut pour ce style.",
|
||||
"Click to close the editor": "Cliquez pour fermer l'éditeur",
|
||||
"Close": "Fermer",
|
||||
"Close modal": "Fermer la fenêtre modale",
|
||||
"Close popup": "Fermer la fenêtre",
|
||||
"Cluster": "Cluster",
|
||||
"Code Editor": "Éditeur de code",
|
||||
"Collapse": "Réduire",
|
||||
"Color accessibility": "Accessibilité des couleurs",
|
||||
"Comment...": "Votre commentaire...",
|
||||
"Comments": "Commentaires",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "Commentaires pour le calque actuel. Ceci n'est pas standard et n'est pas dans la spécification.",
|
||||
"Convert property into a elevation function": "Convertir la propriété en fonction d'élévation",
|
||||
"Input a data property to base styles off of.": "Entrez une propriété de données pour baser les styles.",
|
||||
"Default": "Par défaut",
|
||||
"Stops": "Arrêts",
|
||||
"Zoom": "Zoom",
|
||||
"Add stop": "Ajouter un arrêt",
|
||||
"Convert to expression": "Convertir en expression",
|
||||
"Remove zoom level from stop": "Supprimer le niveau de zoom de l'arrêt",
|
||||
"Revert from expression": "Annuler l'expression",
|
||||
"Delete expression": "Supprimer l'expression",
|
||||
"Convert property into a zoom function": "Convertir la propriété en fonction de zoom",
|
||||
"Convert property to data function": "Convertir la propriété en fonction de données",
|
||||
"Convert to expression": "Convertir en expression",
|
||||
"Coord bottom left": "Coordonnée en bas à gauche",
|
||||
"Coord bottom right": "Coordonnée en bas à droite",
|
||||
"Coord top left": "Coordonnée en haut à gauche",
|
||||
"Coord top right": "Coordonnée en haut à droite",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "La politique CORS ne permet pas de récupérer des ressources servies sur http depuis https, utilisez un protocole <1>https://</1>",
|
||||
"Create HTML": "Créer le HTML",
|
||||
"Data Sources": "Sources de données",
|
||||
"Data Sources modal": "Sources de données (modale)",
|
||||
"Data value": "Valeur des données",
|
||||
"Debug": "Déboguer",
|
||||
"Debug modal": "Déboguer (modale)",
|
||||
"Decrease the pitch by 10 degrees.": "Diminuer l'inclinaison de 10 degrés.",
|
||||
"Decrease the rotation by 15 degrees.": "Diminuer l'angle de rotation de 15 degrés.",
|
||||
"Decrease the zoom level by 1.": "Diminuer le niveau de zoom de 1.",
|
||||
"Decrease the zoom level by 2.": "Diminuer le niveau de zoom de 2.",
|
||||
"Default": "Par défaut",
|
||||
"Delete": "Supprimer",
|
||||
"Delete expression": "Supprimer l'expression",
|
||||
"Delete filter block": "Supprimer le bloc de filtre",
|
||||
"Deuteranopia filter": "Filtre Deutéranopie",
|
||||
"Duplicate": "Dupliquer",
|
||||
"Encoding": "Encodage",
|
||||
"Enter URL...": "Entrez l'URL...",
|
||||
"every filter matches": "tous les filtres correspondent",
|
||||
"Expand": "Développer",
|
||||
"Export modal": "Exporter (modale)",
|
||||
"Filter": "Filtre",
|
||||
"Focus map": "Focus sur la carte",
|
||||
"Function": "Fonction",
|
||||
"Gallery Styles": "Styles de la galerie",
|
||||
"General layout properties": "Propriétés générales de mise en page",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"GeoJSON URL": "URL GeoJSON",
|
||||
"Global State": "État global",
|
||||
"Global State Variables": "Variables d'état global",
|
||||
"Glyphs URL": "URL des glyphes",
|
||||
"Help": "Aide",
|
||||
"Hide": "Cacher",
|
||||
"Icon layout properties": "Propriétés de mise en page de l'icône",
|
||||
"Icon paint properties": "Propriétés de peinture de l'icône",
|
||||
"If the Map is in focused you can use the following shortcuts": "Si la carte a le focus, vous pouvez utiliser les raccourcis suivants",
|
||||
"Image": "Image",
|
||||
"Image URL": "URL de l'image",
|
||||
"Increase the pitch by 10 degrees.": "Augmenter l'inclinaison de 10 degrés.",
|
||||
"Increase the rotation by 15 degrees.": "Augmenter l'angle de rotation de 15 degrés.",
|
||||
"Increase the zoom level by 1.": "Augmenter le niveau de zoom de 1.",
|
||||
"Increase the zoom level by 2.": "Augmenter le niveau de zoom de 2.",
|
||||
"Input a data property to base styles off of.": "Entrez une propriété de données pour baser les styles.",
|
||||
"Input value": "Valeur d'entrée",
|
||||
"Convert property into a elevation function": "Convertir la propriété en fonction d'élévation",
|
||||
"Layer <1>{formatLayerId(layerId)}</1>: {parsed.data.message}": "Calque <1>{formatLayerId(layerId)}</1> : {parsed.data.message}",
|
||||
"switch to layer": "changer de calque",
|
||||
"Map": "Carte",
|
||||
"Inspect": "Inspecter",
|
||||
"JSON Editor": "Éditeur JSON",
|
||||
"Key": "Clé",
|
||||
"Layer": "Calque",
|
||||
"Layer editor": "Éditeur de calque",
|
||||
"Layer ID already exists": "L'identifiant du calque existe déjà",
|
||||
"Layers": "Calques",
|
||||
"Deuteranopia filter": "Filtre Deutéranopie",
|
||||
"Protanopia filter": "Filtre Protanopie",
|
||||
"Tritanopia filter": "Filtre Tritanopie",
|
||||
"Achromatopsia filter": "Filtre Achromatopsie",
|
||||
"Layers list": "Liste des calques",
|
||||
"Layer editor": "Éditeur de calque",
|
||||
"Map view": "Vue de la carte",
|
||||
"Maputnik on GitHub": "Maputnik sur GitHub",
|
||||
"Open": "Ouvrir",
|
||||
"Save": "Enregistrer",
|
||||
"Code Editor": "Éditeur de code",
|
||||
"Data Sources": "Sources de données",
|
||||
"Style Settings": "Paramètres du style",
|
||||
"Global State": "État global",
|
||||
"View": "Vue",
|
||||
"Color accessibility": "Accessibilité des couleurs",
|
||||
"Help": "Aide",
|
||||
"Close": "Fermer",
|
||||
"Click to close the editor": "Cliquez pour fermer l'éditeur",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "Commentaires pour le calque actuel. Ceci n'est pas standard et n'est pas dans la spécification.",
|
||||
"Comments": "Commentaires",
|
||||
"Comment...": "Votre commentaire...",
|
||||
"Max Zoom": "Zoom max",
|
||||
"Min Zoom": "Zoom min",
|
||||
"Source": "Source",
|
||||
"Source Layer": "Calque Source",
|
||||
"Type": "Type",
|
||||
"Nested filters are not supported.": "Les filtres imbriqués ne sont pas pris en charge.",
|
||||
"Upgrade to expression": "Mettre à niveau vers une expression",
|
||||
"Filter": "Filtre",
|
||||
"every filter matches": "tous les filtres correspondent",
|
||||
"no filter matches": "aucun filtre ne correspond",
|
||||
"any filter matches": "l'un des filtres correspond",
|
||||
"Add filter": "Ajouter un filtre",
|
||||
"You've entered an old style filter.": "Vous avez entré un ancien style de filtre.",
|
||||
"Switch to filter editor.": "Passer à l'éditeur de filtre.",
|
||||
"Delete filter block": "Supprimer le bloc de filtre",
|
||||
"Add value": "Ajouter une valeur",
|
||||
"Remove array item": "Supprimer l'élément de tableau",
|
||||
"Must provide protocol: <1>https://</1>": "Protocole requis : <1>https://</1>",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "Protocole requis : <1>http://</1> ou <3>https://</3>",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "La politique CORS ne permet pas de récupérer des ressources servies sur http depuis https, utilisez un protocole <1>https://</1>",
|
||||
"General layout properties": "Propriétés générales de mise en page",
|
||||
"Text layout properties": "Propriétés de mise en page du texte",
|
||||
"Icon layout properties": "Propriétés de mise en page de l'icône",
|
||||
"Text paint properties": "Propriétés de peinture du texte",
|
||||
"Icon paint properties": "Propriétés de peinture de l'icône",
|
||||
"Paint properties": "Propriétés de peinture",
|
||||
"Layout properties": "Propriétés de mise en page",
|
||||
"Learn More": "En savoir plus",
|
||||
"Layer": "Calque",
|
||||
"JSON Editor": "Éditeur JSON",
|
||||
"Delete": "Supprimer",
|
||||
"Duplicate": "Dupliquer",
|
||||
"Show": "Afficher",
|
||||
"Hide": "Cacher",
|
||||
"Move layer up": "Monter le calque",
|
||||
"Move layer down": "Descendre le calque",
|
||||
"Layer: {{layerId}}": "Calque : {{layerId}}",
|
||||
"Layers": "Calques",
|
||||
"Collapse": "Réduire",
|
||||
"Expand": "Développer",
|
||||
"Add Layer": "Ajouter un calque",
|
||||
"Search": "Recherche",
|
||||
"Zoom:": "Zoom :",
|
||||
"Close popup": "Fermer la fenêtre",
|
||||
"cursor:": "curseur :",
|
||||
"center:": "centre :",
|
||||
"rotation:": "rotation :",
|
||||
"Close modal": "Fermer la fenêtre modale",
|
||||
"Layer ID already exists": "L'identifiant du calque existe déjà",
|
||||
"Debug": "Déboguer",
|
||||
"Options": "Options",
|
||||
"Links": "Liens",
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "<0>Ouvrir dans OSM</0>. Ouvre la vue actuelle sur openstreetmap.org",
|
||||
"Save Style": "Enregistrer le style",
|
||||
"Save the JSON style to your computer.": "Enregistrer le style JSON sur votre ordinateur.",
|
||||
"Save as": "Enregistrer sous",
|
||||
"Create HTML": "Créer le HTML",
|
||||
"Key": "Clé",
|
||||
"Value": "Valeur",
|
||||
"Remove variable": "Supprimer la variable",
|
||||
"Global State Variables": "Variables d'état global",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "Aucune variable d'état global définie. Ajoutez des variables pour créer des valeurs réutilisables dans votre style.",
|
||||
"Add Variable": "Ajouter une variable",
|
||||
"Cancel": "Annuler",
|
||||
"Open Style": "Ouvrir le style",
|
||||
"Open local Style": "Ouvrir un style local",
|
||||
"Open a local JSON style from your computer.": "Ouvrir un style JSON local depuis votre ordinateur.",
|
||||
"Load from URL": "Charger depuis une URL",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "Charger depuis une URL. Notez que l'URL doit avoir les <1>CORS activés</1>.",
|
||||
"Style URL": "URL du style",
|
||||
"Enter URL...": "Entrez l'URL...",
|
||||
"Gallery Styles": "Styles de la galerie",
|
||||
"Open one of the publicly available styles to start from.": "Ouvrez l'un des styles publics disponibles pour commencer.",
|
||||
"Loading style": "Chargement du style",
|
||||
"Loading: {{requestUrl}}": "Chargement : {{requestUrl}}",
|
||||
"Name": "Nom",
|
||||
"Owner": "Propriétaire",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "ID du propriétaire du style. Utilisé par Mapbox ou les futures API de style.",
|
||||
"Sprite URL": "URL du sprite",
|
||||
"Glyphs URL": "URL des glyphes",
|
||||
"Center": "Centre",
|
||||
"Bearing": "Orientation",
|
||||
"Pitch": "Inclinaison",
|
||||
"Light anchor": "Ancrage de la lumière",
|
||||
"Light color": "Couleur de la lumière",
|
||||
"Light intensity": "Intensité de la lumière",
|
||||
"Light position": "Position de la lumière",
|
||||
"Links": "Liens",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "Charger depuis une URL. Notez que l'URL doit avoir les <1>CORS activés</1>.",
|
||||
"Load from URL": "Charger depuis une URL",
|
||||
"Loading": "Chargement",
|
||||
"Loading style": "Chargement du style",
|
||||
"LocationIQ Access Token": "Jeton d'accès LocationIQ",
|
||||
"Map": "Carte",
|
||||
"Map view": "Vue de la carte",
|
||||
"MapTiler Access Token": "Jeton d'accès MapTiler",
|
||||
"Maputnik on GitHub": "Maputnik sur GitHub",
|
||||
"Max Zoom": "Zoom max",
|
||||
"Min Zoom": "Zoom min",
|
||||
"Move layer down": "Descendre le calque",
|
||||
"Move layer up": "Monter le calque",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "Protocole requis : <1>http://</1> ou <3>https://</3>",
|
||||
"Must provide protocol: <1>https://</1>": "Protocole requis : <1>https://</1>",
|
||||
"Name": "Nom",
|
||||
"Nested filters are not supported.": "Les filtres imbriqués ne sont pas pris en charge.",
|
||||
"no filter matches": "aucun filtre ne correspond",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "Aucune variable d'état global définie. Ajoutez des variables pour créer des valeurs réutilisables dans votre style.",
|
||||
"Open": "Ouvrir",
|
||||
"Open a local JSON style from your computer.": "Ouvrir un style JSON local depuis votre ordinateur.",
|
||||
"Terrain source": "Source du terrain",
|
||||
"Terrain exaggeration": "Exagération du terrain",
|
||||
"Transition delay": "Délai de transition",
|
||||
"Transition duration": "Durée de transition",
|
||||
"Projection": "Projection",
|
||||
"Open Layers (experimental)": "Open Layers (expérimental)",
|
||||
"Open local Style": "Ouvrir un style local",
|
||||
"Shortcuts menu": "Menu des raccourcis",
|
||||
"Open modal": "Ouvrir (modale)",
|
||||
"Open one of the publicly available styles to start from.": "Ouvrez l'un des styles publics disponibles pour commencer.",
|
||||
"Open Style": "Ouvrir le style",
|
||||
"Options": "Options",
|
||||
"Output value": "Valeur de sortie",
|
||||
"Owner": "Propriétaire",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "ID du propriétaire du style. Utilisé par Mapbox ou les futures API de style.",
|
||||
"Paint properties": "Propriétés de peinture",
|
||||
"Export modal": "Exporter (modale)",
|
||||
"Data Sources modal": "Sources de données (modale)",
|
||||
"Style Settings modal": "Paramètres de style (modale)",
|
||||
"Toggle inspect": "Activer/désactiver l'inspection",
|
||||
"Focus map": "Focus sur la carte",
|
||||
"Debug modal": "Déboguer (modale)",
|
||||
"Increase the zoom level by 1.": "Augmenter le niveau de zoom de 1.",
|
||||
"Increase the zoom level by 2.": "Augmenter le niveau de zoom de 2.",
|
||||
"Decrease the zoom level by 1.": "Diminuer le niveau de zoom de 1.",
|
||||
"Decrease the zoom level by 2.": "Diminuer le niveau de zoom de 2.",
|
||||
"Pan up by 100 pixels.": "Déplacer vers le haut de 100 pixels.",
|
||||
"Pan down by 100 pixels.": "Déplacer vers le bas de 100 pixels.",
|
||||
"Pan left by 100 pixels.": "Déplacer vers la gauche de 100 pixels.",
|
||||
"Pan right by 100 pixels.": "Déplacer vers la droite de 100 pixels.",
|
||||
"Pan up by 100 pixels.": "Déplacer vers le haut de 100 pixels.",
|
||||
"Pitch": "Inclinaison",
|
||||
"PMTiles URL": "URL PMTiles",
|
||||
"Increase the rotation by 15 degrees.": "Augmenter l'angle de rotation de 15 degrés.",
|
||||
"Decrease the rotation by 15 degrees.": "Diminuer l'angle de rotation de 15 degrés.",
|
||||
"Increase the pitch by 10 degrees.": "Augmenter l'inclinaison de 10 degrés.",
|
||||
"Decrease the pitch by 10 degrees.": "Diminuer l'inclinaison de 10 degrés.",
|
||||
"Shortcuts": "Raccourcis",
|
||||
"Press <1>ESC</1> to lose focus of any active elements, then press one of:": "Appuyez sur <1>Échap</1> pour perdre le focus des éléments actifs, puis appuyez sur l'un des éléments suivants :",
|
||||
"Projection": "Projection",
|
||||
"Protanopia filter": "Filtre Protanopie",
|
||||
"Public access token for LocationIQ services.": "Jeton d'accès public pour les services LocationIQ.",
|
||||
"Public access token for MapTiler Cloud.": "Jeton d'accès public pour MapTiler Cloud.",
|
||||
"Public access token for Thunderforest services.": "Jeton d'accès public pour les services Thunderforest.",
|
||||
"Raster (Tile URLs)": "Raster (URLs Tile)",
|
||||
"If the Map is in focused you can use the following shortcuts": "Si la carte a le focus, vous pouvez utiliser les raccourcis suivants",
|
||||
"Remove '{{sourceId}}' source": "Supprimer la source '{{sourceId}}'",
|
||||
"Source ID": "ID de la source",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "ID unique qui identifie la source et est utilisé dans le calque pour référencer la source.",
|
||||
"Source Type": "Type de source",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"Vector (TileJSON URL)": "Vecteur (URL TileJSON)",
|
||||
"Vector (Tile URLs)": "Vecteur (URLs Tile)",
|
||||
"Raster (TileJSON URL)": "Raster (URL TileJSON)",
|
||||
"Raster (Tile URLs)": "Raster (URLs Tile)",
|
||||
"Raster DEM (TileJSON URL)": "Raster DEM (URL TileJSON)",
|
||||
"Raster DEM (XYZ URLs)": "Raster DEM (URLs XYZ)",
|
||||
"Remove '{{sourceId}}' source": "Supprimer la source '{{sourceId}}'",
|
||||
"Remove array item": "Supprimer l'élément de tableau",
|
||||
"Remove variable": "Supprimer la variable",
|
||||
"Remove zoom level from stop": "Supprimer le niveau de zoom de l'arrêt",
|
||||
"Revert from expression": "Annuler l'expression",
|
||||
"Save": "Enregistrer",
|
||||
"Save as": "Enregistrer sous",
|
||||
"Save Style": "Enregistrer le style",
|
||||
"Save the JSON style to your computer.": "Enregistrer le style JSON sur votre ordinateur.",
|
||||
"Scheme Type": "Type de schéma",
|
||||
"Search": "Recherche",
|
||||
"Select a type of data scale (default is 'categorical').": "Sélectionnez un type d'échelle de données (par défaut 'categorical').",
|
||||
"Shortcuts": "Raccourcis",
|
||||
"Shortcuts menu": "Menu des raccourcis",
|
||||
"Show": "Afficher",
|
||||
"Source": "Source",
|
||||
"Source ID": "ID de la source",
|
||||
"Source Layer": "Calque Source",
|
||||
"Source Type": "Type de source",
|
||||
"Sources": "Sources",
|
||||
"Sprite URL": "URL du sprite",
|
||||
"Stadia Maps API Key": "Clé d'API Stadia Maps",
|
||||
"Stops": "Arrêts",
|
||||
"Style Renderer": "Moteur de rendu pour le style",
|
||||
"Style Settings": "Paramètres du style",
|
||||
"Style Settings modal": "Paramètres de style (modale)",
|
||||
"Style URL": "URL du style",
|
||||
"Switch to filter editor.": "Passer à l'éditeur de filtre.",
|
||||
"switch to layer": "changer de calque",
|
||||
"Terrain exaggeration": "Exagération du terrain",
|
||||
"Terrain source": "Source du terrain",
|
||||
"Text layout properties": "Propriétés de mise en page du texte",
|
||||
"Text paint properties": "Propriétés de peinture du texte",
|
||||
"Thunderforest Access Token": "Jeton d'accès Thunderforest",
|
||||
"Tile Size": "Dimension d'une tuile",
|
||||
"Tile URL": "URL de la tuile",
|
||||
"TileJSON URL": "URL TileJSON",
|
||||
"Toggle inspect": "Activer/désactiver l'inspection",
|
||||
"Transition delay": "Délai de transition",
|
||||
"Transition duration": "Durée de transition",
|
||||
"Tritanopia filter": "Filtre Tritanopie",
|
||||
"Type": "Type",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "ID unique qui identifie la source et est utilisé dans le calque pour référencer la source.",
|
||||
"Upgrade to expression": "Mettre à niveau vers une expression",
|
||||
"Value": "Valeur",
|
||||
"Vector (PMTiles)": "Vecteur (PMTiles)",
|
||||
"Vector (Tile URLs)": "Vecteur (URLs Tile)",
|
||||
"Vector (TileJSON URL)": "Vecteur (URL TileJSON)",
|
||||
"Image": "Image",
|
||||
"Video": "Vidéo",
|
||||
"Add Source": "Ajouter une source",
|
||||
"Sources": "Sources",
|
||||
"Active Sources": "Sources actives",
|
||||
"Choose Public Source": "Choisir une source publique",
|
||||
"Add one of the publicly available sources to your style.": "Ajoutez l'une des sources publiques disponibles à votre style.",
|
||||
"Add New Source": "Ajouter une nouvelle source",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "Ajoutez une nouvelle source à votre style. Vous ne pouvez choisir le type et l'ID de la source qu'au moment de la création !",
|
||||
"TileJSON URL": "URL TileJSON",
|
||||
"Tile URL": "URL de la tuile",
|
||||
"Scheme Type": "Type de schéma",
|
||||
"Coord top left": "Coordonnée en haut à gauche",
|
||||
"Coord top right": "Coordonnée en haut à droite",
|
||||
"Coord bottom right": "Coordonnée en bas à droite",
|
||||
"Coord bottom left": "Coordonnée en bas à gauche",
|
||||
"Image URL": "URL de l'image",
|
||||
"Video URL": "URL de la vidéo",
|
||||
"View": "Vue",
|
||||
"You've entered an old style filter.": "Vous avez entré un ancien style de filtre.",
|
||||
"Zoom": "Zoom",
|
||||
"Drag and drop a style JSON file here or click to browse": "Faites glisser un fichier JSON de style ici ou cliquez pour parcourir"
|
||||
"GeoJSON URL": "URL GeoJSON",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"Cluster": "Cluster",
|
||||
"PMTiles URL": "URL PMTiles",
|
||||
"Tile Size": "Dimension d'une tuile",
|
||||
"Encoding": "Encodage",
|
||||
"Error:": "Erreur :",
|
||||
"MapTiler Access Token": "Jeton d'accès MapTiler",
|
||||
"Public access token for MapTiler Cloud.": "Jeton d'accès public pour MapTiler Cloud.",
|
||||
"Learn More": "En savoir plus",
|
||||
"Thunderforest Access Token": "Jeton d'accès Thunderforest",
|
||||
"Public access token for Thunderforest services.": "Jeton d'accès public pour les services Thunderforest.",
|
||||
"Stadia Maps API Key": "Clé d'API Stadia Maps",
|
||||
"API key for Stadia Maps.": "Clé d'API pour Stadia Maps.",
|
||||
"LocationIQ Access Token": "Jeton d'accès LocationIQ",
|
||||
"Public access token for LocationIQ services.": "Jeton d'accès public pour les services LocationIQ.",
|
||||
"Style Renderer": "Moteur de rendu pour le style",
|
||||
"Choose the default Maputnik renderer for this style.": "Choisissez le moteur de rendu Maputnik par défaut pour ce style."
|
||||
}
|
||||
|
||||
+186
-180
@@ -1,204 +1,210 @@
|
||||
{
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "<0>פתיחה ב-OSM</0>. פתיחה של התצוגה הנוכחית ב- openstreetmap.org",
|
||||
"Achromatopsia filter": "Achromatopsia filter",
|
||||
"Active Sources": "מקורות פעילים",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "הוספת מקור לסטייל, ביכולתכם לעשות זאת רק בזמן מסויים",
|
||||
"Add filter": "הוספת סינון",
|
||||
"Add Layer": "הוספת שכבה",
|
||||
"Add New Source": "הוספת מקור חדש",
|
||||
"Add one of the publicly available sources to your style.": "הוספת אחד המקורות הציבוריים הזמינים לסטייל שלך.",
|
||||
"Add Source": "הוספת מקור",
|
||||
"Add stop": "הוספת עצירה",
|
||||
"Add value": "הוספת ערך",
|
||||
"Add Variable": "הוסף משתנה",
|
||||
"any filter matches": "אחד הסינוים מתאימים",
|
||||
"API key for Stadia Maps.": "API key for Stadia Maps",
|
||||
"Input value": "ערך קבלה",
|
||||
"Data value": "ערך מידע",
|
||||
"Output value": "ערך החזרה",
|
||||
"Function": "פונקציה",
|
||||
"Select a type of data scale (default is 'categorical').": "בחרו סוג מידע לטובת הגדלה (ברירת המחדל היא קטגוריה)",
|
||||
"Base": "בסיס",
|
||||
"Bearing": "כיוון",
|
||||
"Cancel": "ביטול",
|
||||
"Center": "מרכז",
|
||||
"Choose Public Source": "בחירת מקור ציבורי",
|
||||
"Choose the default Maputnik renderer for this style.": "בחירת צייר ברירת המחדל של מפוטניק עבור הסטייל הזה",
|
||||
"Click to close the editor": "לחץ לסגירת העורך",
|
||||
"Close": "סגירה",
|
||||
"Close modal": "סגירת חלונית",
|
||||
"Close popup": "סגירת החלון",
|
||||
"Cluster": "קיבוץ",
|
||||
"Code Editor": "עורך קוד",
|
||||
"Collapse": "הקטנה",
|
||||
"Color accessibility": "נגישות צבעים",
|
||||
"Comment...": "הערה...",
|
||||
"Comments": "הערות",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "הערות על השכבה הנוכחית. זה לא חלק מהספסיפיקציות",
|
||||
"Convert property into a elevation function": "המרה לפונקציית גובה",
|
||||
"Input a data property to base styles off of.": "הכניסו מידע עליו יש לבסס את הסטייל",
|
||||
"Default": "ברירת המחדל",
|
||||
"Stops": "עצירות",
|
||||
"Zoom": "זום",
|
||||
"Add stop": "הוספת עצירה",
|
||||
"Convert to expression": "המחרה לביטוי",
|
||||
"Remove zoom level from stop": "הסרת רמת זום מעצירה",
|
||||
"Revert from expression": "החזרה מביטוי",
|
||||
"Delete expression": "מחיקת ביטוי",
|
||||
"Convert property into a zoom function": "המרה לפונקציית זום",
|
||||
"Convert property to data function": "המרה לפונקציית מידע",
|
||||
"Convert to expression": "המחרה לביטוי",
|
||||
"Coord bottom left": "מיקום שמאלי תחתון",
|
||||
"Coord bottom right": "מיקום ימני תחתון",
|
||||
"Coord top left": "מיקום שמאלי עליון",
|
||||
"Coord top right": "מיקום ימני עליון",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain",
|
||||
"Create HTML": "צור HTML",
|
||||
"Data Sources": "מקורות מידע",
|
||||
"Data Sources modal": "חלונית מקורות",
|
||||
"Data value": "ערך מידע",
|
||||
"Debug": "דיבאג",
|
||||
"Debug modal": "חלונית דיבאג",
|
||||
"Decrease the pitch by 10 degrees.": "הורדה של 10 מעלות מהזווית",
|
||||
"Decrease the rotation by 15 degrees.": "הורדת 15 מעלות לסיבוב",
|
||||
"Decrease the zoom level by 1.": "הורדה של הזום ב-1",
|
||||
"Decrease the zoom level by 2.": "הורדה של הזום ב-2",
|
||||
"Default": "ברירת המחדל",
|
||||
"Delete": "מחיקה",
|
||||
"Delete expression": "מחיקת ביטוי",
|
||||
"Delete filter block": "מחיקת גוש מסנן",
|
||||
"Deuteranopia filter": "Deuteranopia filter",
|
||||
"Duplicate": "שכפול",
|
||||
"Encoding": "קידוד",
|
||||
"Enter URL...": "הכנסו כתובת",
|
||||
"every filter matches": "כל הסינונים מתאימים",
|
||||
"Expand": "הגדלה",
|
||||
"Export modal": "חלונית ייצוא",
|
||||
"Filter": "סינון",
|
||||
"Focus map": "פיקוס המפה",
|
||||
"Function": "פונקציה",
|
||||
"Gallery Styles": "גלריית סטיילים",
|
||||
"General layout properties": "תכונות פריסה כלליות",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"GeoJSON URL": "כתובת GeoJSON",
|
||||
"Global State": "מצב גלובלי",
|
||||
"Global State Variables": "משתני מצב גלובלי",
|
||||
"Glyphs URL": "כתובת סמלילים",
|
||||
"Help": "עזרה",
|
||||
"Hide": "הסתרה",
|
||||
"Icon layout properties": "תכונות פריסה של סמליל",
|
||||
"Icon paint properties": "תכונות ציור של סמליל",
|
||||
"If the Map is in focused you can use the following shortcuts": "אם המפה נמצאת בפוקוס תוכלו להשתמש בקיצורי הדרך",
|
||||
"Image": "תמונה",
|
||||
"Image URL": "כתובת תמונה",
|
||||
"Increase the pitch by 10 degrees.": "הוספה של 10 מעלות לזיווית",
|
||||
"Increase the rotation by 15 degrees.": "הוספת 15 מעלות לסיבוב",
|
||||
"Increase the zoom level by 1.": "העלאה של הזום ב-1",
|
||||
"Increase the zoom level by 2.": "העלאה של הזום ב-2",
|
||||
"Input a data property to base styles off of.": "הכניסו מידע עליו יש לבסס את הסטייל",
|
||||
"Input value": "ערך קבלה",
|
||||
"Convert property into a elevation function": "המרה לפונקציית גובה",
|
||||
"Layer <1>{formatLayerId(layerId)}</1>: {parsed.data.message}": "שכבה <1>{formatLayerId(layerId)}</1>: {parsed.data.message}",
|
||||
"switch to layer": "שינוי לשכבה",
|
||||
"Map": "מפה",
|
||||
"Inspect": "בדיקה",
|
||||
"JSON Editor": "עורך JSON",
|
||||
"Key": "מפתח",
|
||||
"Layer": "שכבה",
|
||||
"Layer editor": "עורך השכבות",
|
||||
"Layer ID already exists": "מזהה השכבה כבר קיים",
|
||||
"Layers": "שכבות",
|
||||
"Deuteranopia filter": "Deuteranopia filter",
|
||||
"Protanopia filter": "Protanopia filter",
|
||||
"Tritanopia filter": "Tritanopia filter",
|
||||
"Achromatopsia filter": "Achromatopsia filter",
|
||||
"Layers list": "רשימת שכבות",
|
||||
"Layer editor": "עורך השכבות",
|
||||
"Map view": "תצוגת מפה",
|
||||
"Maputnik on GitHub": "מפוטניק בגיטהב",
|
||||
"Open": "פתיחה",
|
||||
"Save": "שמור",
|
||||
"Code Editor": "עורך קוד",
|
||||
"Data Sources": "מקורות מידע",
|
||||
"Style Settings": "הגדרות הסטייל",
|
||||
"Global State": "מצב גלובלי",
|
||||
"View": "תצוגה",
|
||||
"Color accessibility": "נגישות צבעים",
|
||||
"Help": "עזרה",
|
||||
"Close": "סגירה",
|
||||
"Click to close the editor": "לחץ לסגירת העורך",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "הערות על השכבה הנוכחית. זה לא חלק מהספסיפיקציות",
|
||||
"Comments": "הערות",
|
||||
"Comment...": "הערה...",
|
||||
"Max Zoom": "זום מקסימאלי",
|
||||
"Min Zoom": "זום מינימאלי",
|
||||
"Source": "מקור",
|
||||
"Source Layer": "שכבת מקור",
|
||||
"Type": "סוג",
|
||||
"Nested filters are not supported.": "סינונים מקוננים אינם נתמכים",
|
||||
"Upgrade to expression": "שדרוג לביטוי",
|
||||
"Filter": "סינון",
|
||||
"every filter matches": "כל הסינונים מתאימים",
|
||||
"no filter matches": "אף סינון אינו מתאים",
|
||||
"any filter matches": "אחד הסינוים מתאימים",
|
||||
"Add filter": "הוספת סינון",
|
||||
"You've entered an old style filter.": "הכנסתם סינון מסוג ישן,",
|
||||
"Switch to filter editor.": "עברו לעורך הסינונים",
|
||||
"Delete filter block": "מחיקת גוש מסנן",
|
||||
"Add value": "הוספת ערך",
|
||||
"Remove array item": "הסרת איבר מערך",
|
||||
"Must provide protocol: <1>https://</1>": "נדרש פרוטוקול: <1>https://</1>",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "נדרשים פרוטוקולים: <1>http://</1> או <3>https://</3>",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain",
|
||||
"General layout properties": "תכונות פריסה כלליות",
|
||||
"Text layout properties": "תכונות פריסה של טקסט",
|
||||
"Icon layout properties": "תכונות פריסה של סמליל",
|
||||
"Text paint properties": "תכונות ציור של טקסט",
|
||||
"Icon paint properties": "תכונות ציור של סמליל",
|
||||
"Paint properties": "תכונות ציור",
|
||||
"Layout properties": "תכונות פריסה",
|
||||
"Learn More": "מידע נוסף",
|
||||
"Layer": "שכבה",
|
||||
"JSON Editor": "עורך JSON",
|
||||
"Delete": "מחיקה",
|
||||
"Duplicate": "שכפול",
|
||||
"Show": "הצגה",
|
||||
"Hide": "הסתרה",
|
||||
"Move layer up": "העלה שכבה",
|
||||
"Move layer down": "הורד שכבה",
|
||||
"Layer: {{layerId}}": "שכבה: {{layerId}}",
|
||||
"Layers": "שכבות",
|
||||
"Collapse": "הקטנה",
|
||||
"Expand": "הגדלה",
|
||||
"Add Layer": "הוספת שכבה",
|
||||
"Search": "חיפוש",
|
||||
"Zoom:": "זום:",
|
||||
"Close popup": "סגירת החלון",
|
||||
"cursor:": "סמן",
|
||||
"center:": "מרכז:",
|
||||
"rotation:": "סיבוב:",
|
||||
"Close modal": "סגירת חלונית",
|
||||
"Layer ID already exists": "מזהה השכבה כבר קיים",
|
||||
"Debug": "דיבאג",
|
||||
"Options": "אפשרויות",
|
||||
"Links": "קישורים",
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "<0>פתיחה ב-OSM</0>. פתיחה של התצוגה הנוכחית ב- openstreetmap.org",
|
||||
"Save Style": "שמירת הסטייל",
|
||||
"Save the JSON style to your computer.": "שמירת הסטייל JSON במחשב שלך.",
|
||||
"Save as": "שמירה בשם",
|
||||
"Create HTML": "צור HTML",
|
||||
"Key": "מפתח",
|
||||
"Value": "ערך",
|
||||
"Remove variable": "הסר משתנה",
|
||||
"Global State Variables": "משתני מצב גלובלי",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "לא הוגדרו משתני מצב גלובלי. הוסף משתנים כדי ליצור ערכים שניתן לעשות בהם שימוש חוזר בסטייל שלך.",
|
||||
"Add Variable": "הוסף משתנה",
|
||||
"Cancel": "ביטול",
|
||||
"Open Style": "פתיחת סטייל",
|
||||
"Open local Style": "פתיחת סטייל מקומי",
|
||||
"Open a local JSON style from your computer.": "פתיחת סטייל JSON מקומי מהמחשב שלך.",
|
||||
"Load from URL": "פתיחה מתוך כתובת",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "פתיחה מכתובת, שימו לב: הכתובת צריכה לתמוך ב- CORS",
|
||||
"Style URL": "כתוסת סטייל",
|
||||
"Enter URL...": "הכנסו כתובת",
|
||||
"Gallery Styles": "גלריית סטיילים",
|
||||
"Open one of the publicly available styles to start from.": "פתיחת אחד הסטייליםפ הציבוריים על מנת להתחיל מהם.",
|
||||
"Loading style": "טוען סטייל",
|
||||
"Loading: {{requestUrl}}": "טוען: {{requestUrl}}",
|
||||
"Name": "שם",
|
||||
"Owner": "שייך ל",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "מזהה השייכות של הסטייל.",
|
||||
"Sprite URL": "כתובת ספרייט",
|
||||
"Glyphs URL": "כתובת סמלילים",
|
||||
"Center": "מרכז",
|
||||
"Bearing": "כיוון",
|
||||
"Pitch": "זווית",
|
||||
"Light anchor": "עוגן אור",
|
||||
"Light color": "צבע האור",
|
||||
"Light intensity": "חוזק האור",
|
||||
"Light position": "מיקום האור",
|
||||
"Links": "קישורים",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "פתיחה מכתובת, שימו לב: הכתובת צריכה לתמוך ב- CORS",
|
||||
"Load from URL": "פתיחה מתוך כתובת",
|
||||
"Loading": "טוען",
|
||||
"Loading style": "טוען סטייל",
|
||||
"LocationIQ Access Token": "LocationIQ Access Token",
|
||||
"Map": "מפה",
|
||||
"Map view": "תצוגת מפה",
|
||||
"MapTiler Access Token": "MapTiler Access Token",
|
||||
"Maputnik on GitHub": "מפוטניק בגיטהב",
|
||||
"Max Zoom": "זום מקסימאלי",
|
||||
"Min Zoom": "זום מינימאלי",
|
||||
"Move layer down": "הורד שכבה",
|
||||
"Move layer up": "העלה שכבה",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "נדרשים פרוטוקולים: <1>http://</1> או <3>https://</3>",
|
||||
"Must provide protocol: <1>https://</1>": "נדרש פרוטוקול: <1>https://</1>",
|
||||
"Name": "שם",
|
||||
"Nested filters are not supported.": "סינונים מקוננים אינם נתמכים",
|
||||
"no filter matches": "אף סינון אינו מתאים",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "לא הוגדרו משתני מצב גלובלי. הוסף משתנים כדי ליצור ערכים שניתן לעשות בהם שימוש חוזר בסטייל שלך.",
|
||||
"Open": "פתיחה",
|
||||
"Open a local JSON style from your computer.": "פתיחת סטייל JSON מקומי מהמחשב שלך.",
|
||||
"Terrain source": "מקור terrain",
|
||||
"Terrain exaggeration": "כמה \"להגזים\"",
|
||||
"Transition delay": "זמן לפני מעבר",
|
||||
"Transition duration": "זמן מעבר",
|
||||
"Projection": "הטלה",
|
||||
"Open Layers (experimental)": "Open Layers (experimental)",
|
||||
"Open local Style": "פתיחת סטייל מקומי",
|
||||
"Shortcuts menu": "תפריט קיצורי דרך",
|
||||
"Open modal": "פתיחת חלונית",
|
||||
"Open one of the publicly available styles to start from.": "פתיחת אחד הסטייליםפ הציבוריים על מנת להתחיל מהם.",
|
||||
"Open Style": "פתיחת סטייל",
|
||||
"Options": "אפשרויות",
|
||||
"Output value": "ערך החזרה",
|
||||
"Owner": "שייך ל",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "מזהה השייכות של הסטייל.",
|
||||
"Paint properties": "תכונות ציור",
|
||||
"Export modal": "חלונית ייצוא",
|
||||
"Data Sources modal": "חלונית מקורות",
|
||||
"Style Settings modal": "חלונית הגדרות סטייל",
|
||||
"Toggle inspect": "שינוי בדיקה",
|
||||
"Focus map": "פיקוס המפה",
|
||||
"Debug modal": "חלונית דיבאג",
|
||||
"Increase the zoom level by 1.": "העלאה של הזום ב-1",
|
||||
"Increase the zoom level by 2.": "העלאה של הזום ב-2",
|
||||
"Decrease the zoom level by 1.": "הורדה של הזום ב-1",
|
||||
"Decrease the zoom level by 2.": "הורדה של הזום ב-2",
|
||||
"Pan up by 100 pixels.": "תזוזה למעלה ב-100 פיקסלים",
|
||||
"Pan down by 100 pixels.": "תזוזה למטה ב-100 פיקסלים",
|
||||
"Pan left by 100 pixels.": "תזוזה שמאלה ב-100 פיקסלים",
|
||||
"Pan right by 100 pixels.": "תזוזה ימינה ב-100 פיקסלים",
|
||||
"Pan up by 100 pixels.": "תזוזה למעלה ב-100 פיקסלים",
|
||||
"Pitch": "זווית",
|
||||
"PMTiles URL": "כתובת PMTiles",
|
||||
"Increase the rotation by 15 degrees.": "הוספת 15 מעלות לסיבוב",
|
||||
"Decrease the rotation by 15 degrees.": "הורדת 15 מעלות לסיבוב",
|
||||
"Increase the pitch by 10 degrees.": "הוספה של 10 מעלות לזיווית",
|
||||
"Decrease the pitch by 10 degrees.": "הורדה של 10 מעלות מהזווית",
|
||||
"Shortcuts": "קיצורי דרך",
|
||||
"Press <1>ESC</1> to lose focus of any active elements, then press one of:": "לחיצה על <1>ESC</1> על מנת לאבד פוקוס מכל פקד פעיל, ואז ליחצו על אחד מאלו:",
|
||||
"Projection": "הטלה",
|
||||
"Protanopia filter": "Protanopia filter",
|
||||
"Public access token for LocationIQ services.": "Public access token for LocationIQ services.",
|
||||
"Public access token for MapTiler Cloud.": "Public access token for MapTiler Cloud.",
|
||||
"Public access token for Thunderforest services.": "Public access token for Thunderforest services.",
|
||||
"Raster (Tile URLs)": "Raster (Tile URLs)",
|
||||
"If the Map is in focused you can use the following shortcuts": "אם המפה נמצאת בפוקוס תוכלו להשתמש בקיצורי הדרך",
|
||||
"Remove '{{sourceId}}' source": "הורדה של מקור '{{sourceId}}'",
|
||||
"Source ID": "מזהה מקור",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "מזהה מקור ייחודי לטובת שימוש בשכבה",
|
||||
"Source Type": "סוג מקור",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"Vector (TileJSON URL)": "Vector (TileJSON URL)",
|
||||
"Vector (Tile URLs)": "Vector (Tile URLs)",
|
||||
"Raster (TileJSON URL)": "Raster (TileJSON URL)",
|
||||
"Raster (Tile URLs)": "Raster (Tile URLs)",
|
||||
"Raster DEM (TileJSON URL)": "Raster DEM (TileJSON URL)",
|
||||
"Raster DEM (XYZ URLs)": "Raster DEM (XYZ URLs)",
|
||||
"Remove '{{sourceId}}' source": "הורדה של מקור '{{sourceId}}'",
|
||||
"Remove array item": "הסרת איבר מערך",
|
||||
"Remove variable": "הסר משתנה",
|
||||
"Remove zoom level from stop": "הסרת רמת זום מעצירה",
|
||||
"Revert from expression": "החזרה מביטוי",
|
||||
"Save": "שמור",
|
||||
"Save as": "שמירה בשם",
|
||||
"Save Style": "שמירת הסטייל",
|
||||
"Save the JSON style to your computer.": "שמירת הסטייל JSON במחשב שלך.",
|
||||
"Scheme Type": "סוג סכמה",
|
||||
"Search": "חיפוש",
|
||||
"Select a type of data scale (default is 'categorical').": "בחרו סוג מידע לטובת הגדלה (ברירת המחדל היא קטגוריה)",
|
||||
"Shortcuts": "קיצורי דרך",
|
||||
"Shortcuts menu": "תפריט קיצורי דרך",
|
||||
"Show": "הצגה",
|
||||
"Source": "מקור",
|
||||
"Source ID": "מזהה מקור",
|
||||
"Source Layer": "שכבת מקור",
|
||||
"Source Type": "סוג מקור",
|
||||
"Sources": "מקורות",
|
||||
"Sprite URL": "כתובת ספרייט",
|
||||
"Stadia Maps API Key": "Stadia Maps API Key",
|
||||
"Stops": "עצירות",
|
||||
"Style Renderer": "צייר הסטייל",
|
||||
"Style Settings": "הגדרות הסטייל",
|
||||
"Style Settings modal": "חלונית הגדרות סטייל",
|
||||
"Style URL": "כתוסת סטייל",
|
||||
"Switch to filter editor.": "עברו לעורך הסינונים",
|
||||
"switch to layer": "שינוי לשכבה",
|
||||
"Terrain exaggeration": "כמה \"להגזים\"",
|
||||
"Terrain source": "מקור terrain",
|
||||
"Text layout properties": "תכונות פריסה של טקסט",
|
||||
"Text paint properties": "תכונות ציור של טקסט",
|
||||
"Thunderforest Access Token": "Thunderforest Access Token",
|
||||
"Tile Size": "גודל אריח",
|
||||
"Tile URL": "כתובת אריחים",
|
||||
"TileJSON URL": "כתובת TileJSON",
|
||||
"Toggle inspect": "שינוי בדיקה",
|
||||
"Transition delay": "זמן לפני מעבר",
|
||||
"Transition duration": "זמן מעבר",
|
||||
"Tritanopia filter": "Tritanopia filter",
|
||||
"Type": "סוג",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "מזהה מקור ייחודי לטובת שימוש בשכבה",
|
||||
"Upgrade to expression": "שדרוג לביטוי",
|
||||
"Value": "ערך",
|
||||
"Vector (PMTiles)": "Vector (PMTiles)",
|
||||
"Vector (Tile URLs)": "Vector (Tile URLs)",
|
||||
"Vector (TileJSON URL)": "Vector (TileJSON URL)",
|
||||
"Image": "תמונה",
|
||||
"Video": "וידאו",
|
||||
"Add Source": "הוספת מקור",
|
||||
"Sources": "מקורות",
|
||||
"Active Sources": "מקורות פעילים",
|
||||
"Choose Public Source": "בחירת מקור ציבורי",
|
||||
"Add one of the publicly available sources to your style.": "הוספת אחד המקורות הציבוריים הזמינים לסטייל שלך.",
|
||||
"Add New Source": "הוספת מקור חדש",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "הוספת מקור לסטייל, ביכולתכם לעשות זאת רק בזמן מסויים",
|
||||
"TileJSON URL": "כתובת TileJSON",
|
||||
"Tile URL": "כתובת אריחים",
|
||||
"Scheme Type": "סוג סכמה",
|
||||
"Coord top left": "מיקום שמאלי עליון",
|
||||
"Coord top right": "מיקום ימני עליון",
|
||||
"Coord bottom right": "מיקום ימני תחתון",
|
||||
"Coord bottom left": "מיקום שמאלי תחתון",
|
||||
"Image URL": "כתובת תמונה",
|
||||
"Video URL": "כתובת וידאו",
|
||||
"View": "תצוגה",
|
||||
"You've entered an old style filter.": "הכנסתם סינון מסוג ישן,",
|
||||
"Zoom": "זום",
|
||||
"Drag and drop a style JSON file here or click to browse": "גרור ושחרר כאן קובץ JSON של סגנון או לחץ כדי לעיין"
|
||||
"GeoJSON URL": "כתובת GeoJSON",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"Cluster": "קיבוץ",
|
||||
"PMTiles URL": "כתובת PMTiles",
|
||||
"Tile Size": "גודל אריח",
|
||||
"Encoding": "קידוד",
|
||||
"Error:": "שגיאה",
|
||||
"MapTiler Access Token": "MapTiler Access Token",
|
||||
"Public access token for MapTiler Cloud.": "Public access token for MapTiler Cloud.",
|
||||
"Learn More": "מידע נוסף",
|
||||
"Thunderforest Access Token": "Thunderforest Access Token",
|
||||
"Public access token for Thunderforest services.": "Public access token for Thunderforest services.",
|
||||
"Stadia Maps API Key": "Stadia Maps API Key",
|
||||
"API key for Stadia Maps.": "API key for Stadia Maps",
|
||||
"LocationIQ Access Token": "LocationIQ Access Token",
|
||||
"Public access token for LocationIQ services.": "Public access token for LocationIQ services.",
|
||||
"Style Renderer": "צייר הסטייל",
|
||||
"Choose the default Maputnik renderer for this style.": "בחירת צייר ברירת המחדל של מפוטניק עבור הסטייל הזה"
|
||||
}
|
||||
|
||||
+186
-180
@@ -1,204 +1,210 @@
|
||||
{
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "<0>Apri in OSM</0>. Apre la vista corrente su openstreetmap.org",
|
||||
"Achromatopsia filter": "Filtro acromatopsia",
|
||||
"Active Sources": "Sorgenti attive",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "Aggiungi una nuova sorgente al tuo stile. Puoi scegliere il tipo di sorgente e l'ID solo al momento della creazione!",
|
||||
"Add filter": "Aggiungi filtro",
|
||||
"Add Layer": "Aggiungi Livello",
|
||||
"Add New Source": "Aggiungi nuova sorgente",
|
||||
"Add one of the publicly available sources to your style.": "Aggiungi una delle sorgente pubbliche disponibili al tuo stile.",
|
||||
"Add Source": "Aggiungi sorgente",
|
||||
"Add stop": "Aggiungi fermata",
|
||||
"Add value": "Aggiungi valore",
|
||||
"Add Variable": "Aggiungi variabile",
|
||||
"any filter matches": "qualsiasi filtro corrisponde",
|
||||
"API key for Stadia Maps.": "Chiave API per Stadia Maps.",
|
||||
"Input value": "Valore di input",
|
||||
"Data value": "Valore dei dati",
|
||||
"Output value": "Valore di output",
|
||||
"Function": "Funzione",
|
||||
"Select a type of data scale (default is 'categorical').": "Seleziona un tipo di scala dati (predefinito 'categorical').",
|
||||
"Base": "Base",
|
||||
"Bearing": "Direzione",
|
||||
"Cancel": "Annulla",
|
||||
"Center": "Centro",
|
||||
"Choose Public Source": "Scegli una sorgente pubblica",
|
||||
"Choose the default Maputnik renderer for this style.": "Scegli il renderer predefinito di Maputnik per questo stile.",
|
||||
"Click to close the editor": "Clicca per chiudere l'editor",
|
||||
"Close": "Chiudi",
|
||||
"Close modal": "Chiudi finestra modale",
|
||||
"Close popup": "Chiudi popup",
|
||||
"Cluster": "Cluster",
|
||||
"Code Editor": "Editor codice",
|
||||
"Collapse": "Coprimi",
|
||||
"Color accessibility": "Accessibilità colori",
|
||||
"Comment...": "Il tuo commento...",
|
||||
"Comments": "Commenti",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "Commenti per il livello attuale. Questo non è standard e non è nella specifica.",
|
||||
"Convert property into a elevation function": "Converti proprietà in una funzione di elevazione",
|
||||
"Input a data property to base styles off of.": "Inserisci una proprietà dati su cui basare gli stili.",
|
||||
"Default": "Predefinito",
|
||||
"Stops": "Fermate",
|
||||
"Zoom": "Zoom",
|
||||
"Add stop": "Aggiungi fermata",
|
||||
"Convert to expression": "Converti in espressione",
|
||||
"Remove zoom level from stop": "Rimuovi il livello di zoom dalla fermata",
|
||||
"Revert from expression": "Ripristina dall'espressione",
|
||||
"Delete expression": "Elimina espressione",
|
||||
"Convert property into a zoom function": "Converti proprietà in una funzione di zoom",
|
||||
"Convert property to data function": "Converti proprietà in una funzione dati",
|
||||
"Convert to expression": "Converti in espressione",
|
||||
"Coord bottom left": "Coordinate in basso a sinistra",
|
||||
"Coord bottom right": "Coordinate in basso a destra",
|
||||
"Coord top left": "Coordinate in alto a sinistra",
|
||||
"Coord top right": "Coordinate in alto a destra",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "La politica CORS non permette di caricare risorse tramite http da https, utilizza un dominio <1>https://</1>.",
|
||||
"Create HTML": "Crea HTML",
|
||||
"Data Sources": "Fonti dati",
|
||||
"Data Sources modal": "Finestra modale sorgente dati",
|
||||
"Data value": "Valore dei dati",
|
||||
"Debug": "Debug",
|
||||
"Debug modal": "Finestra modale di debug",
|
||||
"Decrease the pitch by 10 degrees.": "Aumenta la rotazione di 10 gradi.",
|
||||
"Decrease the rotation by 15 degrees.": "Aumenta la rotazione di 15 gradi.",
|
||||
"Decrease the zoom level by 1.": "Riduci il livello di zoom di 1.",
|
||||
"Decrease the zoom level by 2.": "Riduci il livello di zoom di 1..",
|
||||
"Default": "Predefinito",
|
||||
"Delete": "Elimina",
|
||||
"Delete expression": "Elimina espressione",
|
||||
"Delete filter block": "Elimina blocco filtro",
|
||||
"Deuteranopia filter": "Filtro deuteranopia",
|
||||
"Duplicate": "Duplica",
|
||||
"Encoding": "Codifica",
|
||||
"Enter URL...": "Inserisci URL...",
|
||||
"every filter matches": "tutti i filtri corrispondono",
|
||||
"Expand": "Espandi",
|
||||
"Export modal": "Esporta finestra modale",
|
||||
"Filter": "Filtro",
|
||||
"Focus map": "Metti a fuoco la mappa",
|
||||
"Function": "Funzione",
|
||||
"Gallery Styles": "Galleria degli stili",
|
||||
"General layout properties": "Proprietà generali del layout",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"GeoJSON URL": "GeoJSON URL",
|
||||
"Global State": "Stato globale",
|
||||
"Global State Variables": "Variabili di stato globale",
|
||||
"Glyphs URL": "URL Glyphs",
|
||||
"Help": "Aiuto",
|
||||
"Hide": "Nascondi",
|
||||
"Icon layout properties": "Proprietà di layout dell'icona",
|
||||
"Icon paint properties": "Proprietà di pittura dell'icona",
|
||||
"If the Map is in focused you can use the following shortcuts": "Se la mappa è attiva, puoi utilizzare le seguenti scorciatoie:",
|
||||
"Image": "Immagine",
|
||||
"Image URL": "Indirizzo immagine",
|
||||
"Increase the pitch by 10 degrees.": "Incrementa la rotazione di 10 gradi",
|
||||
"Increase the rotation by 15 degrees.": "Incrementa la rotazione di 15 gradi.",
|
||||
"Increase the zoom level by 1.": "Aumenta il livello di zoom di 1.",
|
||||
"Increase the zoom level by 2.": "Aumenta il livello di zoom di 2.",
|
||||
"Input a data property to base styles off of.": "Inserisci una proprietà dati su cui basare gli stili.",
|
||||
"Input value": "Valore di input",
|
||||
"Convert property into a elevation function": "Converti proprietà in una funzione di elevazione",
|
||||
"Layer <1>{formatLayerId(layerId)}</1>: {parsed.data.message}": "Livello <1>{formatLayerId(layerId)}</1>: {parsed.data.message}",
|
||||
"switch to layer": "passa al livello",
|
||||
"Map": "Mappa",
|
||||
"Inspect": "Esamina",
|
||||
"JSON Editor": "Editor JSON",
|
||||
"Key": "Chiave",
|
||||
"Layer": "Livello",
|
||||
"Layer editor": "Editor livelli",
|
||||
"Layer ID already exists": "L'ID del layer esiste già",
|
||||
"Layers": "Livelli",
|
||||
"Deuteranopia filter": "Filtro deuteranopia",
|
||||
"Protanopia filter": "Filtro protanopia",
|
||||
"Tritanopia filter": "Filtro tritanopia",
|
||||
"Achromatopsia filter": "Filtro acromatopsia",
|
||||
"Layers list": "Elenco livelli",
|
||||
"Layer editor": "Editor livelli",
|
||||
"Map view": "Vista mappa",
|
||||
"Maputnik on GitHub": "Maputnik su GitHub",
|
||||
"Open": "Apri",
|
||||
"Save": "Salva",
|
||||
"Code Editor": "Editor codice",
|
||||
"Data Sources": "Fonti dati",
|
||||
"Style Settings": "Impostazioni stile",
|
||||
"Global State": "Stato globale",
|
||||
"View": "Vista",
|
||||
"Color accessibility": "Accessibilità colori",
|
||||
"Help": "Aiuto",
|
||||
"Close": "Chiudi",
|
||||
"Click to close the editor": "Clicca per chiudere l'editor",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "Commenti per il livello attuale. Questo non è standard e non è nella specifica.",
|
||||
"Comments": "Commenti",
|
||||
"Comment...": "Il tuo commento...",
|
||||
"Max Zoom": "Zoom massimo",
|
||||
"Min Zoom": "Zoom minimo",
|
||||
"Source": "Sorgente",
|
||||
"Source Layer": "Livello sorgente",
|
||||
"Type": "Tipo",
|
||||
"Nested filters are not supported.": "I filtri nidificati non sono supportati.",
|
||||
"Upgrade to expression": "Aggiorna a espressione",
|
||||
"Filter": "Filtro",
|
||||
"every filter matches": "tutti i filtri corrispondono",
|
||||
"no filter matches": "nessun filtro corrisponde",
|
||||
"any filter matches": "qualsiasi filtro corrisponde",
|
||||
"Add filter": "Aggiungi filtro",
|
||||
"You've entered an old style filter.": "Hai inserito uno stile filtro obsoleto.",
|
||||
"Switch to filter editor.": "Passa all'editor dei filtri.",
|
||||
"Delete filter block": "Elimina blocco filtro",
|
||||
"Add value": "Aggiungi valore",
|
||||
"Remove array item": "Rimuovi elemento dall'array",
|
||||
"Must provide protocol: <1>https://</1>": "Protocollo richiesto: <1>https://</1>",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "Protocollo richiesto: <1>http://</1> o <3>https://</3>",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "La politica CORS non permette di caricare risorse tramite http da https, utilizza un dominio <1>https://</1>.",
|
||||
"General layout properties": "Proprietà generali del layout",
|
||||
"Text layout properties": "Proprietà di layout del testo",
|
||||
"Icon layout properties": "Proprietà di layout dell'icona",
|
||||
"Text paint properties": "Proprietà di pittura del testo",
|
||||
"Icon paint properties": "Proprietà di pittura dell'icona",
|
||||
"Paint properties": "Proprietà di pittura",
|
||||
"Layout properties": "Proprietà di layout",
|
||||
"Learn More": "Scopri di più",
|
||||
"Layer": "Livello",
|
||||
"JSON Editor": "Editor JSON",
|
||||
"Delete": "Elimina",
|
||||
"Duplicate": "Duplica",
|
||||
"Show": "Mostra",
|
||||
"Hide": "Nascondi",
|
||||
"Move layer up": "Sposta il livello in su",
|
||||
"Move layer down": "Sposta il livello in giù",
|
||||
"Layer: {{layerId}}": "Livello: {{layerId}}",
|
||||
"Layers": "Livelli",
|
||||
"Collapse": "Coprimi",
|
||||
"Expand": "Espandi",
|
||||
"Add Layer": "Aggiungi Livello",
|
||||
"Search": "Cerca",
|
||||
"Zoom:": "Zoom:",
|
||||
"Close popup": "Chiudi popup",
|
||||
"cursor:": "cursore:",
|
||||
"center:": "centro:",
|
||||
"rotation:": "rotazione:",
|
||||
"Close modal": "Chiudi finestra modale",
|
||||
"Layer ID already exists": "L'ID del layer esiste già",
|
||||
"Debug": "Debug",
|
||||
"Options": "Opzioni",
|
||||
"Links": "Collegamenti",
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "<0>Apri in OSM</0>. Apre la vista corrente su openstreetmap.org",
|
||||
"Save Style": "Opzioni stile",
|
||||
"Save the JSON style to your computer.": "Salva lo style JSON nel tuo computer.",
|
||||
"Save as": "Salva con nome",
|
||||
"Create HTML": "Crea HTML",
|
||||
"Key": "Chiave",
|
||||
"Value": "Valore",
|
||||
"Remove variable": "Rimuovi variabile",
|
||||
"Global State Variables": "Variabili di stato globale",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "Nessuna variabile di stato globale definita. Aggiungi variabili per creare valori riutilizzabili nel tuo stile.",
|
||||
"Add Variable": "Aggiungi variabile",
|
||||
"Cancel": "Annulla",
|
||||
"Open Style": "Apri stile",
|
||||
"Open local Style": "Apri stile locale",
|
||||
"Open a local JSON style from your computer.": "Apri uno stile JSON dal tuo computer.",
|
||||
"Load from URL": "Carica da URL",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "Carica da URL. Nota che l'URL deve avere l'abilitazione <1>CORS attivata</1>.",
|
||||
"Style URL": "Stile URL",
|
||||
"Enter URL...": "Inserisci URL...",
|
||||
"Gallery Styles": "Galleria degli stili",
|
||||
"Open one of the publicly available styles to start from.": "Apro uno degli stili pubblici a partire da.",
|
||||
"Loading style": "Caricamento stile",
|
||||
"Loading: {{requestUrl}}": "Caricamento: {{requestUrl}}",
|
||||
"Name": "Nome",
|
||||
"Owner": "Proprietario",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "ID del proprietario dello stile. Utilizzato da Mapbox o dalle future API di stile.",
|
||||
"Sprite URL": "URL Sprite",
|
||||
"Glyphs URL": "URL Glyphs",
|
||||
"Center": "Centro",
|
||||
"Bearing": "Direzione",
|
||||
"Pitch": "Inclinazione",
|
||||
"Light anchor": "Ancora della luce",
|
||||
"Light color": "Colore della luce",
|
||||
"Light intensity": "Intensità della luce",
|
||||
"Light position": "Posizione della luce",
|
||||
"Links": "Collegamenti",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "Carica da URL. Nota che l'URL deve avere l'abilitazione <1>CORS attivata</1>.",
|
||||
"Load from URL": "Carica da URL",
|
||||
"Loading": "Caricamento",
|
||||
"Loading style": "Caricamento stile",
|
||||
"LocationIQ Access Token": "Token di accesso LocationIQ",
|
||||
"Map": "Mappa",
|
||||
"Map view": "Vista mappa",
|
||||
"MapTiler Access Token": "Accesso token MapTiler",
|
||||
"Maputnik on GitHub": "Maputnik su GitHub",
|
||||
"Max Zoom": "Zoom massimo",
|
||||
"Min Zoom": "Zoom minimo",
|
||||
"Move layer down": "Sposta il livello in giù",
|
||||
"Move layer up": "Sposta il livello in su",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "Protocollo richiesto: <1>http://</1> o <3>https://</3>",
|
||||
"Must provide protocol: <1>https://</1>": "Protocollo richiesto: <1>https://</1>",
|
||||
"Name": "Nome",
|
||||
"Nested filters are not supported.": "I filtri nidificati non sono supportati.",
|
||||
"no filter matches": "nessun filtro corrisponde",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "Nessuna variabile di stato globale definita. Aggiungi variabili per creare valori riutilizzabili nel tuo stile.",
|
||||
"Open": "Apri",
|
||||
"Open a local JSON style from your computer.": "Apri uno stile JSON dal tuo computer.",
|
||||
"Terrain source": "Sorgente del terreno",
|
||||
"Terrain exaggeration": "Esagerazione del terreno",
|
||||
"Transition delay": "Ritardo della transizione",
|
||||
"Transition duration": "Durate della transizione",
|
||||
"Projection": "Proiezione",
|
||||
"Open Layers (experimental)": "Apri livelli (sperimentale)",
|
||||
"Open local Style": "Apri stile locale",
|
||||
"Shortcuts menu": "Scorciatoie del menu",
|
||||
"Open modal": "Apri finestra modale",
|
||||
"Open one of the publicly available styles to start from.": "Apro uno degli stili pubblici a partire da.",
|
||||
"Open Style": "Apri stile",
|
||||
"Options": "Opzioni",
|
||||
"Output value": "Valore di output",
|
||||
"Owner": "Proprietario",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "ID del proprietario dello stile. Utilizzato da Mapbox o dalle future API di stile.",
|
||||
"Paint properties": "Proprietà di pittura",
|
||||
"Export modal": "Esporta finestra modale",
|
||||
"Data Sources modal": "Finestra modale sorgente dati",
|
||||
"Style Settings modal": "Finestra modale impostazione degli stili",
|
||||
"Toggle inspect": "Attiva/disattiva ispezione",
|
||||
"Focus map": "Metti a fuoco la mappa",
|
||||
"Debug modal": "Finestra modale di debug",
|
||||
"Increase the zoom level by 1.": "Aumenta il livello di zoom di 1.",
|
||||
"Increase the zoom level by 2.": "Aumenta il livello di zoom di 2.",
|
||||
"Decrease the zoom level by 1.": "Riduci il livello di zoom di 1.",
|
||||
"Decrease the zoom level by 2.": "Riduci il livello di zoom di 1..",
|
||||
"Pan up by 100 pixels.": "Sposta in alto di 100 pixels.",
|
||||
"Pan down by 100 pixels.": "Sposta in basso di 100 pixels",
|
||||
"Pan left by 100 pixels.": "Sposta a sinistra di 100 pixels",
|
||||
"Pan right by 100 pixels.": "Sposta a destra di 100 pixels",
|
||||
"Pan up by 100 pixels.": "Sposta in alto di 100 pixels.",
|
||||
"Pitch": "Inclinazione",
|
||||
"PMTiles URL": "PMTiles URL",
|
||||
"Increase the rotation by 15 degrees.": "Incrementa la rotazione di 15 gradi.",
|
||||
"Decrease the rotation by 15 degrees.": "Aumenta la rotazione di 15 gradi.",
|
||||
"Increase the pitch by 10 degrees.": "Incrementa la rotazione di 10 gradi",
|
||||
"Decrease the pitch by 10 degrees.": "Aumenta la rotazione di 10 gradi.",
|
||||
"Shortcuts": "Scorciatoie",
|
||||
"Press <1>ESC</1> to lose focus of any active elements, then press one of:": "Premi <1>ESC</1> per perdere il focus su qualsiasi elemento attivo, poi premi uno dei seguenti tasti:",
|
||||
"Projection": "Proiezione",
|
||||
"Protanopia filter": "Filtro protanopia",
|
||||
"Public access token for LocationIQ services.": "Token di accesso pubblico per i servizi LocationIQ.",
|
||||
"Public access token for MapTiler Cloud.": "Token di accesso pubblico per MapTiler Cloud.",
|
||||
"Public access token for Thunderforest services.": "Token di accesso pubblico per i servizi Thunderforest.",
|
||||
"Raster (Tile URLs)": "Raster (Tile URLs)",
|
||||
"If the Map is in focused you can use the following shortcuts": "Se la mappa è attiva, puoi utilizzare le seguenti scorciatoie:",
|
||||
"Remove '{{sourceId}}' source": "Rimuovi sorgente '{{sourceId}}'",
|
||||
"Source ID": "ID sorgente",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "ID univoco che identifica la sorgente ed è utilizzato nel livello per fare riferimento alla sorgente.",
|
||||
"Source Type": "Tipo sorgente",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"Vector (TileJSON URL)": "Vettore (TileJSON URL)",
|
||||
"Vector (Tile URLs)": "Vettore (Tile URLs)",
|
||||
"Raster (TileJSON URL)": "Raster (TileJSON URL)",
|
||||
"Raster (Tile URLs)": "Raster (Tile URLs)",
|
||||
"Raster DEM (TileJSON URL)": "Raster DEM (TileJSON URL)",
|
||||
"Raster DEM (XYZ URLs)": "Raster DEM (XYZ URLs)",
|
||||
"Remove '{{sourceId}}' source": "Rimuovi sorgente '{{sourceId}}'",
|
||||
"Remove array item": "Rimuovi elemento dall'array",
|
||||
"Remove variable": "Rimuovi variabile",
|
||||
"Remove zoom level from stop": "Rimuovi il livello di zoom dalla fermata",
|
||||
"Revert from expression": "Ripristina dall'espressione",
|
||||
"Save": "Salva",
|
||||
"Save as": "Salva con nome",
|
||||
"Save Style": "Opzioni stile",
|
||||
"Save the JSON style to your computer.": "Salva lo style JSON nel tuo computer.",
|
||||
"Scheme Type": "Tipo di schema",
|
||||
"Search": "Cerca",
|
||||
"Select a type of data scale (default is 'categorical').": "Seleziona un tipo di scala dati (predefinito 'categorical').",
|
||||
"Shortcuts": "Scorciatoie",
|
||||
"Shortcuts menu": "Scorciatoie del menu",
|
||||
"Show": "Mostra",
|
||||
"Source": "Sorgente",
|
||||
"Source ID": "ID sorgente",
|
||||
"Source Layer": "Livello sorgente",
|
||||
"Source Type": "Tipo sorgente",
|
||||
"Sources": "Sorgenti",
|
||||
"Sprite URL": "URL Sprite",
|
||||
"Stadia Maps API Key": "Chiave API di Stadia Maps.",
|
||||
"Stops": "Fermate",
|
||||
"Style Renderer": "Renderer dello stile",
|
||||
"Style Settings": "Impostazioni stile",
|
||||
"Style Settings modal": "Finestra modale impostazione degli stili",
|
||||
"Style URL": "Stile URL",
|
||||
"Switch to filter editor.": "Passa all'editor dei filtri.",
|
||||
"switch to layer": "passa al livello",
|
||||
"Terrain exaggeration": "Esagerazione del terreno",
|
||||
"Terrain source": "Sorgente del terreno",
|
||||
"Text layout properties": "Proprietà di layout del testo",
|
||||
"Text paint properties": "Proprietà di pittura del testo",
|
||||
"Thunderforest Access Token": "Accesso token Thunderforest",
|
||||
"Tile Size": "Dimensioni tile",
|
||||
"Tile URL": "Tile-URL",
|
||||
"TileJSON URL": "TileJSON URL",
|
||||
"Toggle inspect": "Attiva/disattiva ispezione",
|
||||
"Transition delay": "Ritardo della transizione",
|
||||
"Transition duration": "Durate della transizione",
|
||||
"Tritanopia filter": "Filtro tritanopia",
|
||||
"Type": "Tipo",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "ID univoco che identifica la sorgente ed è utilizzato nel livello per fare riferimento alla sorgente.",
|
||||
"Upgrade to expression": "Aggiorna a espressione",
|
||||
"Value": "Valore",
|
||||
"Vector (PMTiles)": "Vettore (PMTiles)",
|
||||
"Vector (Tile URLs)": "Vettore (Tile URLs)",
|
||||
"Vector (TileJSON URL)": "Vettore (TileJSON URL)",
|
||||
"Image": "Immagine",
|
||||
"Video": "Video",
|
||||
"Add Source": "Aggiungi sorgente",
|
||||
"Sources": "Sorgenti",
|
||||
"Active Sources": "Sorgenti attive",
|
||||
"Choose Public Source": "Scegli una sorgente pubblica",
|
||||
"Add one of the publicly available sources to your style.": "Aggiungi una delle sorgente pubbliche disponibili al tuo stile.",
|
||||
"Add New Source": "Aggiungi nuova sorgente",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "Aggiungi una nuova sorgente al tuo stile. Puoi scegliere il tipo di sorgente e l'ID solo al momento della creazione!",
|
||||
"TileJSON URL": "TileJSON URL",
|
||||
"Tile URL": "Tile-URL",
|
||||
"Scheme Type": "Tipo di schema",
|
||||
"Coord top left": "Coordinate in alto a sinistra",
|
||||
"Coord top right": "Coordinate in alto a destra",
|
||||
"Coord bottom right": "Coordinate in basso a destra",
|
||||
"Coord bottom left": "Coordinate in basso a sinistra",
|
||||
"Image URL": "Indirizzo immagine",
|
||||
"Video URL": "Indirizzo video",
|
||||
"View": "Vista",
|
||||
"You've entered an old style filter.": "Hai inserito uno stile filtro obsoleto.",
|
||||
"Zoom": "Zoom",
|
||||
"Drag and drop a style JSON file here or click to browse": "Trascina e rilascia qui un file JSON dello stile o fai clic per sfogliare"
|
||||
"GeoJSON URL": "GeoJSON URL",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"Cluster": "Cluster",
|
||||
"PMTiles URL": "PMTiles URL",
|
||||
"Tile Size": "Dimensioni tile",
|
||||
"Encoding": "Codifica",
|
||||
"Error:": "Errore:",
|
||||
"MapTiler Access Token": "Accesso token MapTiler",
|
||||
"Public access token for MapTiler Cloud.": "Token di accesso pubblico per MapTiler Cloud.",
|
||||
"Learn More": "Scopri di più",
|
||||
"Thunderforest Access Token": "Accesso token Thunderforest",
|
||||
"Public access token for Thunderforest services.": "Token di accesso pubblico per i servizi Thunderforest.",
|
||||
"Stadia Maps API Key": "Chiave API di Stadia Maps.",
|
||||
"API key for Stadia Maps.": "Chiave API per Stadia Maps.",
|
||||
"LocationIQ Access Token": "Token di accesso LocationIQ",
|
||||
"Public access token for LocationIQ services.": "Token di accesso pubblico per i servizi LocationIQ.",
|
||||
"Style Renderer": "Renderer dello stile",
|
||||
"Choose the default Maputnik renderer for this style.": "Scegli il renderer predefinito di Maputnik per questo stile."
|
||||
}
|
||||
|
||||
+186
-180
@@ -1,204 +1,210 @@
|
||||
{
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "現在のビューを <0>openstreetmap.org で開く</0>",
|
||||
"Achromatopsia filter": "全色盲フィルタ",
|
||||
"Active Sources": "使用中ソース",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "スタイルに新規ソースを追加します。注意: 作成時にソースタイプとIDのみを選択できます。",
|
||||
"Add filter": "フィルタを追加",
|
||||
"Add Layer": "レイヤー追加",
|
||||
"Add New Source": "新規ソースを追加",
|
||||
"Add one of the publicly available sources to your style.": "公開ソースをスタイルに追加しましょう",
|
||||
"Add Source": "ソースを追加",
|
||||
"Add stop": "ストップを追加",
|
||||
"Add value": "値を追加する",
|
||||
"Add Variable": "変数を追加",
|
||||
"any filter matches": "いずれかのフィルタが一致",
|
||||
"API key for Stadia Maps.": "Stadia Maps の API キー",
|
||||
"Input value": "入力値",
|
||||
"Data value": "データ値",
|
||||
"Output value": "値",
|
||||
"Function": "関数",
|
||||
"Select a type of data scale (default is 'categorical').": "データスケールの種類を選択してください(デフォルトは「categorical」です)。",
|
||||
"Base": "ベース",
|
||||
"Bearing": "方位",
|
||||
"Cancel": "キャンセル",
|
||||
"Center": "中央",
|
||||
"Choose Public Source": "公開ソースから選択",
|
||||
"Choose the default Maputnik renderer for this style.": "このスタイルのデフォルトの Maputnik レンダラを選択してください",
|
||||
"Click to close the editor": "エディタを閉じるにはクリックしてください",
|
||||
"Close": "閉じる",
|
||||
"Close modal": "モーダルを閉じる",
|
||||
"Close popup": "ポップアップを閉じる",
|
||||
"Cluster": "クラスタ",
|
||||
"Code Editor": "コードエディタ",
|
||||
"Collapse": "畳む",
|
||||
"Color accessibility": "色のアクセシビリティ",
|
||||
"Comment...": "コメントを書く",
|
||||
"Comments": "コメント",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "現在のレイヤーのコメント。注意:この機能は標準ではないため、他のライブラリとの互換性状況はわかりません。",
|
||||
"Convert property into a elevation function": "プロパティを標高関数に変換する",
|
||||
"Input a data property to base styles off of.": "スタイルのベースとなるデータプロパティを入力してください。",
|
||||
"Default": "デフォルト",
|
||||
"Stops": "ストップ",
|
||||
"Zoom": "ズーム",
|
||||
"Add stop": "ストップを追加",
|
||||
"Convert to expression": "フィルタ式に変換",
|
||||
"Remove zoom level from stop": "ズームレベルをストップから削除",
|
||||
"Revert from expression": "式から戻す",
|
||||
"Delete expression": "式を削除",
|
||||
"Convert property into a zoom function": "プロパティをズーム関数に変換する",
|
||||
"Convert property to data function": "プロパティをデータ関数に変換する",
|
||||
"Convert to expression": "フィルタ式に変換",
|
||||
"Coord bottom left": "左下座標",
|
||||
"Coord bottom right": "右下座標",
|
||||
"Coord top left": "左上座標",
|
||||
"Coord top right": "右上座標",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "CORS使用時は、http経由で提供されるリソースをhttpsから取得することはできません。<1>https://</1> ドメインを使用してください。",
|
||||
"Create HTML": "HTMLを作成",
|
||||
"Data Sources": "データソース",
|
||||
"Data Sources modal": "データソースのモーダル",
|
||||
"Data value": "データ値",
|
||||
"Debug": "デバッグ",
|
||||
"Debug modal": "デバッグモーダル",
|
||||
"Decrease the pitch by 10 degrees.": "ピッチを10度減らす",
|
||||
"Decrease the rotation by 15 degrees.": "回転角度を15度減らす",
|
||||
"Decrease the zoom level by 1.": "ズームレベルを1減らす",
|
||||
"Decrease the zoom level by 2.": "ズームレベルを2減らす",
|
||||
"Default": "デフォルト",
|
||||
"Delete": "削除",
|
||||
"Delete expression": "式を削除",
|
||||
"Delete filter block": "フィルタブロックを削除",
|
||||
"Deuteranopia filter": "緑色盲フィルタ",
|
||||
"Duplicate": "複製",
|
||||
"Encoding": "エンコーディング",
|
||||
"Enter URL...": "URLを入力",
|
||||
"every filter matches": "全てのフィルタが一致",
|
||||
"Expand": "展開",
|
||||
"Export modal": "書き出しのモーダル",
|
||||
"Filter": "フィルタ",
|
||||
"Focus map": "地図にフォーカス",
|
||||
"Function": "関数",
|
||||
"Gallery Styles": "ギャラリースタイル",
|
||||
"General layout properties": "一般レイアウトプロパティ",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"GeoJSON URL": "GeoJSON URL",
|
||||
"Global State": "グローバルステート",
|
||||
"Global State Variables": "グローバルステート変数",
|
||||
"Glyphs URL": "フォントグリフURL",
|
||||
"Help": "ヘルプ",
|
||||
"Hide": "非表示",
|
||||
"Icon layout properties": "アイコンレイアウトプロパティ",
|
||||
"Icon paint properties": "アイコンペイントプロパティ",
|
||||
"If the Map is in focused you can use the following shortcuts": "地図がフォーカスされている場合、以下のショートカットを使用できます",
|
||||
"Image": "画像",
|
||||
"Image URL": "画像URL",
|
||||
"Increase the pitch by 10 degrees.": "ピッチを10度増やす",
|
||||
"Increase the rotation by 15 degrees.": "回転角度を15度増やす",
|
||||
"Increase the zoom level by 1.": "ズームレベルを1増やす",
|
||||
"Increase the zoom level by 2.": "ズームレベルを2増やす",
|
||||
"Input a data property to base styles off of.": "スタイルのベースとなるデータプロパティを入力してください。",
|
||||
"Input value": "入力値",
|
||||
"Convert property into a elevation function": "プロパティを標高関数に変換する",
|
||||
"Layer <1>{formatLayerId(layerId)}</1>: {parsed.data.message}": "レイヤ<1>{formatLayerId(layerId)}</1>: {parsed.data.message}",
|
||||
"switch to layer": "レイヤへ切替",
|
||||
"Map": "地図",
|
||||
"Inspect": "検査",
|
||||
"JSON Editor": "JSONエディタ",
|
||||
"Key": "キー",
|
||||
"Layer": "レイヤー",
|
||||
"Layer editor": "レイヤーエディタ",
|
||||
"Layer ID already exists": "レイヤーIDは既に存在します",
|
||||
"Layers": "レイヤー",
|
||||
"Deuteranopia filter": "緑色盲フィルタ",
|
||||
"Protanopia filter": "赤色盲フィルタ",
|
||||
"Tritanopia filter": "青色盲フィルタ",
|
||||
"Achromatopsia filter": "全色盲フィルタ",
|
||||
"Layers list": "レイヤー一覧",
|
||||
"Layer editor": "レイヤーエディタ",
|
||||
"Map view": "地図画面",
|
||||
"Maputnik on GitHub": "GitHubのMaputnik",
|
||||
"Open": "開く",
|
||||
"Save": "保存",
|
||||
"Code Editor": "コードエディタ",
|
||||
"Data Sources": "データソース",
|
||||
"Style Settings": "スタイル設定",
|
||||
"Global State": "グローバルステート",
|
||||
"View": "表示",
|
||||
"Color accessibility": "色のアクセシビリティ",
|
||||
"Help": "ヘルプ",
|
||||
"Close": "閉じる",
|
||||
"Click to close the editor": "エディタを閉じるにはクリックしてください",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "現在のレイヤーのコメント。注意:この機能は標準ではないため、他のライブラリとの互換性状況はわかりません。",
|
||||
"Comments": "コメント",
|
||||
"Comment...": "コメントを書く",
|
||||
"Max Zoom": "最大ズーム",
|
||||
"Min Zoom": "最小ズーム",
|
||||
"Source": "ソース",
|
||||
"Source Layer": "ソースレイヤ",
|
||||
"Type": "タイプ",
|
||||
"Nested filters are not supported.": "ネストされたフィルタはサポートされていません。",
|
||||
"Upgrade to expression": "式にアップグレード",
|
||||
"Filter": "フィルタ",
|
||||
"every filter matches": "全てのフィルタが一致",
|
||||
"no filter matches": "フィルタが一致しない",
|
||||
"any filter matches": "いずれかのフィルタが一致",
|
||||
"Add filter": "フィルタを追加",
|
||||
"You've entered an old style filter.": "旧型フィルタを使用しております。",
|
||||
"Switch to filter editor.": "新型に変換",
|
||||
"Delete filter block": "フィルタブロックを削除",
|
||||
"Add value": "値を追加する",
|
||||
"Remove array item": "配列の項目を削除",
|
||||
"Must provide protocol: <1>https://</1>": "プロトコルを指定してください: <1>https://</1>",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "プロトコルを指定してください: <1>http://</1> または <3>https://</3>",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "CORS使用時は、http経由で提供されるリソースをhttpsから取得することはできません。<1>https://</1> ドメインを使用してください。",
|
||||
"General layout properties": "一般レイアウトプロパティ",
|
||||
"Text layout properties": "文字レイアウトプロパティ",
|
||||
"Icon layout properties": "アイコンレイアウトプロパティ",
|
||||
"Text paint properties": "文字ペイントプロパティ",
|
||||
"Icon paint properties": "アイコンペイントプロパティ",
|
||||
"Paint properties": "ペイントプロパティ",
|
||||
"Layout properties": "レイアウトプロパティ",
|
||||
"Learn More": "詳細はこちら",
|
||||
"Layer": "レイヤー",
|
||||
"JSON Editor": "JSONエディタ",
|
||||
"Delete": "削除",
|
||||
"Duplicate": "複製",
|
||||
"Show": "表示",
|
||||
"Hide": "非表示",
|
||||
"Move layer up": "上に移動",
|
||||
"Move layer down": "下に移動",
|
||||
"Layer: {{layerId}}": "レイヤー {{layerId}}",
|
||||
"Layers": "レイヤー",
|
||||
"Collapse": "畳む",
|
||||
"Expand": "展開",
|
||||
"Add Layer": "レイヤー追加",
|
||||
"Search": "検索",
|
||||
"Zoom:": "ズーム:",
|
||||
"Close popup": "ポップアップを閉じる",
|
||||
"cursor:": "カーソル",
|
||||
"center:": "中央:",
|
||||
"rotation:": "回転角度:",
|
||||
"Close modal": "モーダルを閉じる",
|
||||
"Layer ID already exists": "レイヤーIDは既に存在します",
|
||||
"Debug": "デバッグ",
|
||||
"Options": "設定",
|
||||
"Links": "リンク",
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "現在のビューを <0>openstreetmap.org で開く</0>",
|
||||
"Save Style": "スタイルを保存",
|
||||
"Save the JSON style to your computer.": "JSONスタイルをコンピュータに保存します。",
|
||||
"Save as": "名前を付けて保存",
|
||||
"Create HTML": "HTMLを作成",
|
||||
"Key": "キー",
|
||||
"Value": "値",
|
||||
"Remove variable": "変数を削除",
|
||||
"Global State Variables": "グローバルステート変数",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "グローバルステート変数が定義されていません。スタイルで再利用可能な値を作成するには、変数を追加してください。",
|
||||
"Add Variable": "変数を追加",
|
||||
"Cancel": "キャンセル",
|
||||
"Open Style": "スタイルを開く",
|
||||
"Open local Style": "ローカルスタイルを開く",
|
||||
"Open a local JSON style from your computer.": "コンピュータからローカルJSONスタイルを開きます。",
|
||||
"Load from URL": "URLから読み込む",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "URLから読み込む。注意: URLは <1>CORSを有効にする</1> 必要があります。",
|
||||
"Style URL": "スタイルURL",
|
||||
"Enter URL...": "URLを入力",
|
||||
"Gallery Styles": "ギャラリースタイル",
|
||||
"Open one of the publicly available styles to start from.": "公開スタイルを選んで開始しましょう。",
|
||||
"Loading style": "スタイル読込中",
|
||||
"Loading: {{requestUrl}}": "{{requestUrl}} を読み込み中",
|
||||
"Name": "名前",
|
||||
"Owner": "所有者",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "スタイルの所有者 ID。Mapbox または将来のスタイル API によって使用されます。",
|
||||
"Sprite URL": "スプライトURL",
|
||||
"Glyphs URL": "フォントグリフURL",
|
||||
"Center": "中央",
|
||||
"Bearing": "方位",
|
||||
"Pitch": "ピッチ",
|
||||
"Light anchor": "ライトアンカー",
|
||||
"Light color": "ライトカラー",
|
||||
"Light intensity": "ライト強度",
|
||||
"Light position": "ライト位置",
|
||||
"Links": "リンク",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "URLから読み込む。注意: URLは <1>CORSを有効にする</1> 必要があります。",
|
||||
"Load from URL": "URLから読み込む",
|
||||
"Loading": "を読み込み中",
|
||||
"Loading style": "スタイル読込中",
|
||||
"LocationIQ Access Token": "LocationIQ アクセストークン",
|
||||
"Map": "地図",
|
||||
"Map view": "地図画面",
|
||||
"MapTiler Access Token": "MapTiler アクセストークン",
|
||||
"Maputnik on GitHub": "GitHubのMaputnik",
|
||||
"Max Zoom": "最大ズーム",
|
||||
"Min Zoom": "最小ズーム",
|
||||
"Move layer down": "下に移動",
|
||||
"Move layer up": "上に移動",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "プロトコルを指定してください: <1>http://</1> または <3>https://</3>",
|
||||
"Must provide protocol: <1>https://</1>": "プロトコルを指定してください: <1>https://</1>",
|
||||
"Name": "名前",
|
||||
"Nested filters are not supported.": "ネストされたフィルタはサポートされていません。",
|
||||
"no filter matches": "フィルタが一致しない",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "グローバルステート変数が定義されていません。スタイルで再利用可能な値を作成するには、変数を追加してください。",
|
||||
"Open": "開く",
|
||||
"Open a local JSON style from your computer.": "コンピュータからローカルJSONスタイルを開きます。",
|
||||
"Terrain source": "地形ソース",
|
||||
"Terrain exaggeration": "地形の誇張",
|
||||
"Transition delay": "遷移遅延",
|
||||
"Transition duration": "遷移期間",
|
||||
"Projection": "投影",
|
||||
"Open Layers (experimental)": "Open Layers (実験的)",
|
||||
"Open local Style": "ローカルスタイルを開く",
|
||||
"Shortcuts menu": "ショートカットメニュー",
|
||||
"Open modal": "モーダルを開く",
|
||||
"Open one of the publicly available styles to start from.": "公開スタイルを選んで開始しましょう。",
|
||||
"Open Style": "スタイルを開く",
|
||||
"Options": "設定",
|
||||
"Output value": "値",
|
||||
"Owner": "所有者",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "スタイルの所有者 ID。Mapbox または将来のスタイル API によって使用されます。",
|
||||
"Paint properties": "ペイントプロパティ",
|
||||
"Export modal": "書き出しのモーダル",
|
||||
"Data Sources modal": "データソースのモーダル",
|
||||
"Style Settings modal": "スタイル設定のモーダル",
|
||||
"Toggle inspect": "検査の切り替え",
|
||||
"Focus map": "地図にフォーカス",
|
||||
"Debug modal": "デバッグモーダル",
|
||||
"Increase the zoom level by 1.": "ズームレベルを1増やす",
|
||||
"Increase the zoom level by 2.": "ズームレベルを2増やす",
|
||||
"Decrease the zoom level by 1.": "ズームレベルを1減らす",
|
||||
"Decrease the zoom level by 2.": "ズームレベルを2減らす",
|
||||
"Pan up by 100 pixels.": "100ピクセル上に移動",
|
||||
"Pan down by 100 pixels.": "100ピクセル下に移動",
|
||||
"Pan left by 100 pixels.": "100ピクセル左に移動",
|
||||
"Pan right by 100 pixels.": "100ピクセル右に移動",
|
||||
"Pan up by 100 pixels.": "100ピクセル上に移動",
|
||||
"Pitch": "ピッチ",
|
||||
"PMTiles URL": "PMTiles URL",
|
||||
"Increase the rotation by 15 degrees.": "回転角度を15度増やす",
|
||||
"Decrease the rotation by 15 degrees.": "回転角度を15度減らす",
|
||||
"Increase the pitch by 10 degrees.": "ピッチを10度増やす",
|
||||
"Decrease the pitch by 10 degrees.": "ピッチを10度減らす",
|
||||
"Shortcuts": "ショートカット",
|
||||
"Press <1>ESC</1> to lose focus of any active elements, then press one of:": "<1>ESC</1> を押してアクティブな要素からフォーカスを外し、次に以下のいずれかを押してください:",
|
||||
"Projection": "投影",
|
||||
"Protanopia filter": "赤色盲フィルタ",
|
||||
"Public access token for LocationIQ services.": "LocationIQ サービス用のパブリックアクセストークン。",
|
||||
"Public access token for MapTiler Cloud.": "MapTiler Cloud の公開用アクセストークン",
|
||||
"Public access token for Thunderforest services.": "Thunderforest サービスの公開用アクセストークン",
|
||||
"Raster (Tile URLs)": "ラスタ (Tile URLs)",
|
||||
"If the Map is in focused you can use the following shortcuts": "地図がフォーカスされている場合、以下のショートカットを使用できます",
|
||||
"Remove '{{sourceId}}' source": "'{{sourceId}}' ソースを削除",
|
||||
"Source ID": "ソースID",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "ソースを識別するためのユニークID。レイヤーでソースを参照するために使用されます。",
|
||||
"Source Type": "ソースタイプ",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"Vector (TileJSON URL)": "ベクトル (TileJSON URL)",
|
||||
"Vector (Tile URLs)": "ベクトル (Tile URLs)",
|
||||
"Raster (TileJSON URL)": "ラスタ (TileJSON URL)",
|
||||
"Raster (Tile URLs)": "ラスタ (Tile URLs)",
|
||||
"Raster DEM (TileJSON URL)": "ラスタ DEM (TileJSON URL)",
|
||||
"Raster DEM (XYZ URLs)": "ラスタ DEM (XYZ URL)",
|
||||
"Remove '{{sourceId}}' source": "'{{sourceId}}' ソースを削除",
|
||||
"Remove array item": "配列の項目を削除",
|
||||
"Remove variable": "変数を削除",
|
||||
"Remove zoom level from stop": "ズームレベルをストップから削除",
|
||||
"Revert from expression": "式から戻す",
|
||||
"Save": "保存",
|
||||
"Save as": "名前を付けて保存",
|
||||
"Save Style": "スタイルを保存",
|
||||
"Save the JSON style to your computer.": "JSONスタイルをコンピュータに保存します。",
|
||||
"Scheme Type": "スキーマ",
|
||||
"Search": "検索",
|
||||
"Select a type of data scale (default is 'categorical').": "データスケールの種類を選択してください(デフォルトは「categorical」です)。",
|
||||
"Shortcuts": "ショートカット",
|
||||
"Shortcuts menu": "ショートカットメニュー",
|
||||
"Show": "表示",
|
||||
"Source": "ソース",
|
||||
"Source ID": "ソースID",
|
||||
"Source Layer": "ソースレイヤ",
|
||||
"Source Type": "ソースタイプ",
|
||||
"Sources": "ソース一覧",
|
||||
"Sprite URL": "スプライトURL",
|
||||
"Stadia Maps API Key": "Stadia Maps API キー",
|
||||
"Stops": "ストップ",
|
||||
"Style Renderer": "スタイルレンダラ",
|
||||
"Style Settings": "スタイル設定",
|
||||
"Style Settings modal": "スタイル設定のモーダル",
|
||||
"Style URL": "スタイルURL",
|
||||
"Switch to filter editor.": "新型に変換",
|
||||
"switch to layer": "レイヤへ切替",
|
||||
"Terrain exaggeration": "地形の誇張",
|
||||
"Terrain source": "地形ソース",
|
||||
"Text layout properties": "文字レイアウトプロパティ",
|
||||
"Text paint properties": "文字ペイントプロパティ",
|
||||
"Thunderforest Access Token": "Thunderforest アクセストークン",
|
||||
"Tile Size": "タイルサイズ",
|
||||
"Tile URL": "タイルURL",
|
||||
"TileJSON URL": "TileJSON URL",
|
||||
"Toggle inspect": "検査の切り替え",
|
||||
"Transition delay": "遷移遅延",
|
||||
"Transition duration": "遷移期間",
|
||||
"Tritanopia filter": "青色盲フィルタ",
|
||||
"Type": "タイプ",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "ソースを識別するためのユニークID。レイヤーでソースを参照するために使用されます。",
|
||||
"Upgrade to expression": "式にアップグレード",
|
||||
"Value": "値",
|
||||
"Vector (PMTiles)": "ベクトル (PMTiles)",
|
||||
"Vector (Tile URLs)": "ベクトル (Tile URLs)",
|
||||
"Vector (TileJSON URL)": "ベクトル (TileJSON URL)",
|
||||
"Image": "画像",
|
||||
"Video": "動画",
|
||||
"Add Source": "ソースを追加",
|
||||
"Sources": "ソース一覧",
|
||||
"Active Sources": "使用中ソース",
|
||||
"Choose Public Source": "公開ソースから選択",
|
||||
"Add one of the publicly available sources to your style.": "公開ソースをスタイルに追加しましょう",
|
||||
"Add New Source": "新規ソースを追加",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "スタイルに新規ソースを追加します。注意: 作成時にソースタイプとIDのみを選択できます。",
|
||||
"TileJSON URL": "TileJSON URL",
|
||||
"Tile URL": "タイルURL",
|
||||
"Scheme Type": "スキーマ",
|
||||
"Coord top left": "左上座標",
|
||||
"Coord top right": "右上座標",
|
||||
"Coord bottom right": "右下座標",
|
||||
"Coord bottom left": "左下座標",
|
||||
"Image URL": "画像URL",
|
||||
"Video URL": "動画URL",
|
||||
"View": "表示",
|
||||
"You've entered an old style filter.": "旧型フィルタを使用しております。",
|
||||
"Zoom": "ズーム",
|
||||
"Drag and drop a style JSON file here or click to browse": "ここにスタイルのJSONファイルをドラッグ&ドロップするか、クリックして参照してください"
|
||||
"GeoJSON URL": "GeoJSON URL",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"Cluster": "クラスタ",
|
||||
"PMTiles URL": "PMTiles URL",
|
||||
"Tile Size": "タイルサイズ",
|
||||
"Encoding": "エンコーディング",
|
||||
"Error:": "エラー:",
|
||||
"MapTiler Access Token": "MapTiler アクセストークン",
|
||||
"Public access token for MapTiler Cloud.": "MapTiler Cloud の公開用アクセストークン",
|
||||
"Learn More": "詳細はこちら",
|
||||
"Thunderforest Access Token": "Thunderforest アクセストークン",
|
||||
"Public access token for Thunderforest services.": "Thunderforest サービスの公開用アクセストークン",
|
||||
"Stadia Maps API Key": "Stadia Maps API キー",
|
||||
"API key for Stadia Maps.": "Stadia Maps の API キー",
|
||||
"LocationIQ Access Token": "LocationIQ アクセストークン",
|
||||
"Public access token for LocationIQ services.": "LocationIQ サービス用のパブリックアクセストークン。",
|
||||
"Style Renderer": "スタイルレンダラ",
|
||||
"Choose the default Maputnik renderer for this style.": "このスタイルのデフォルトの Maputnik レンダラを選択してください"
|
||||
}
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
{
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "<0>OSM에서 열기</0>. 현재 뷰를 openstreetmap.org에서 엽니다",
|
||||
"Achromatopsia filter": "전색맹 필터",
|
||||
"Active Sources": "활성 소스",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "스타일에 새 소스를 추가합니다. 소스 유형과 ID는 생성 시에만 선택할 수 있습니다!",
|
||||
"Add filter": "필터 추가",
|
||||
"Add Layer": "레이어 추가",
|
||||
"Add New Source": "새 소스 추가",
|
||||
"Add one of the publicly available sources to your style.": "공개적으로 사용 가능한 소스 중 하나를 스타일에 추가하세요.",
|
||||
"Add Source": "소스 추가",
|
||||
"Add stop": "기준점 추가",
|
||||
"Add value": "값 추가",
|
||||
"Add Variable": "변수 추가",
|
||||
"any filter matches": "일부 필터 일치",
|
||||
"API key for Stadia Maps.": "Stadia Maps용 API 키 입니다.",
|
||||
"Base": "베이스",
|
||||
"Bearing": "방위각",
|
||||
"Cancel": "취소",
|
||||
"Center": "중심 좌표",
|
||||
"Choose Public Source": "공개 소스 선택",
|
||||
"Choose the default Maputnik renderer for this style.": "이 스타일의 기본 Maputnik 렌더러를 선택하세요.",
|
||||
"Click to close the editor": "편집기를 닫으려면 클릭하세요",
|
||||
"Close": "닫기",
|
||||
"Close modal": "모달 닫기",
|
||||
"Close popup": "팝업 닫기",
|
||||
"Cluster": "클러스터",
|
||||
"Code Editor": "코드 편집기",
|
||||
"Collapse": "접기",
|
||||
"Color accessibility": "색상 접근성",
|
||||
"Comment...": "주석 입력...",
|
||||
"Comments": "주석",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "현재 레이어에 대한 설명입니다. 이것은 비표준이며 사양에 포함되지 않습니다.",
|
||||
"Convert property into a elevation function": "속성을 고도 함수로 변환",
|
||||
"Convert property into a zoom function": "속성을 줌 함수로 변환",
|
||||
"Convert property to data function": "속성을 데이터 함수로 변환",
|
||||
"Convert to expression": "표현식으로 변환",
|
||||
"Coord bottom left": "좌하단 좌표",
|
||||
"Coord bottom right": "우하단 좌표",
|
||||
"Coord top left": "좌상단 좌표",
|
||||
"Coord top right": "우상단 좌표",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "CORS 정책으로 인해 https에서 http 리소스를 가져올 수 없습니다. <1>https://</1> 도메인을 사용하세요",
|
||||
"Create HTML": "HTML 생성",
|
||||
"Data Sources": "데이터 소스",
|
||||
"Data Sources modal": "데이터 소스 모달",
|
||||
"Data value": "데이터 값",
|
||||
"Debug": "디버그",
|
||||
"Debug modal": "디버그 모달",
|
||||
"Decrease the pitch by 10 degrees.": "경사각을 10도 감소시킵니다.",
|
||||
"Decrease the rotation by 15 degrees.": "회전을 15도 감소시킵니다.",
|
||||
"Decrease the zoom level by 1.": "줌 레벨을 1 감소시킵니다.",
|
||||
"Decrease the zoom level by 2.": "줌 레벨을 2 감소시킵니다.",
|
||||
"Default": "기본값",
|
||||
"Delete": "삭제",
|
||||
"Delete expression": "표현식 삭제",
|
||||
"Delete filter block": "필터 블록 삭제",
|
||||
"Deuteranopia filter": "녹색맹 필터",
|
||||
"Duplicate": "복제",
|
||||
"Encoding": "인코딩",
|
||||
"Enter URL...": "URL 입력...",
|
||||
"every filter matches": "모든 필터 일치",
|
||||
"Expand": "펼치기",
|
||||
"Export modal": "모달 내보내기",
|
||||
"Filter": "필터",
|
||||
"Focus map": "포커스 맵",
|
||||
"Function": "함수",
|
||||
"Gallery Styles": "갤러리 스타일",
|
||||
"General layout properties": "일반 레이아웃 속성",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"GeoJSON URL": "GeoJSON URL",
|
||||
"Global State": "전역 상태",
|
||||
"Global State Variables": "전역 상태 변수",
|
||||
"Glyphs URL": "글리프 URL",
|
||||
"Help": "도움말",
|
||||
"Hide": "숨기기",
|
||||
"Icon layout properties": "아이콘 레이아웃 속성",
|
||||
"Icon paint properties": "아이콘 페인트 속성",
|
||||
"If the Map is in focused you can use the following shortcuts": "맵에 포커스가 맞춰진 경우 다음 단축키를 사용할 수 있습니다",
|
||||
"Image": "이미지",
|
||||
"Image URL": "이미지 URL",
|
||||
"Increase the pitch by 10 degrees.": "경사각을 10도 증가시킵니다.",
|
||||
"Increase the rotation by 15 degrees.": "회전을 15도 증가시킵니다.",
|
||||
"Increase the zoom level by 1.": "줌 레벨을 1 증가시킵니다.",
|
||||
"Increase the zoom level by 2.": "줌 레벨을 2 증가시킵니다.",
|
||||
"Input a data property to base styles off of.": "스타일의 기준이 될 데이터 속성을 입력하세요.",
|
||||
"Input value": "입력 값",
|
||||
"Inspect": "검사",
|
||||
"JSON Editor": "JSON 편집기",
|
||||
"Key": "키",
|
||||
"Layer": "레이어",
|
||||
"Layer editor": "레이어 편집기",
|
||||
"Layer ID already exists": "레이어 ID가 이미 존재합니다",
|
||||
"Layers": "레이어",
|
||||
"Layers list": "레이어 목록",
|
||||
"Layout properties": "레이아웃 속성",
|
||||
"Learn More": "자세히 알아보기",
|
||||
"Light anchor": "라이트 고정점",
|
||||
"Light color": "라이트 색상",
|
||||
"Light intensity": "라이트 강도",
|
||||
"Light position": "라이트 위치",
|
||||
"Links": "링크",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "URL에서 불러옵니다. URL은 <1>CORS가 활성화</1>되어 있어야 합니다.",
|
||||
"Load from URL": "URL에서 불러오기",
|
||||
"Loading": "로딩",
|
||||
"Loading style": "스타일 로딩 중",
|
||||
"LocationIQ Access Token": "LocationIQ 액세스 토큰",
|
||||
"Map": "맵",
|
||||
"Map view": "맵 뷰",
|
||||
"MapTiler Access Token": "MapTiler 액세스 토큰",
|
||||
"Maputnik on GitHub": "GitHub의 Maputnik",
|
||||
"Max Zoom": "최대 줌",
|
||||
"Min Zoom": "최소 줌",
|
||||
"Move layer down": "레이어 아래로 이동",
|
||||
"Move layer up": "레이어 위로 이동",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "프로토콜을 포함해주세요: <1>http://</1> 또는 <3>https://</3>",
|
||||
"Must provide protocol: <1>https://</1>": "프로토콜을 포함해주세요: <1>https://</1>",
|
||||
"Name": "이름",
|
||||
"Nested filters are not supported.": "중첩 필터는 지원되지 않습니다.",
|
||||
"no filter matches": "필터 불일치",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "전역 상태 변수가 정의되지 않았습니다. 스타일에서 재사용 가능한 값을 생성하려면 변수를 추가하세요.",
|
||||
"Open": "열기",
|
||||
"Open a local JSON style from your computer.": "컴퓨터에서 로컬 JSON 스타일을 엽니다.",
|
||||
"Open Layers (experimental)": "레이어 열기 (실험적)",
|
||||
"Open local Style": "로컬 스타일 열기",
|
||||
"Open modal": "모달 열기",
|
||||
"Open one of the publicly available styles to start from.": "공개 스타일 중 하나를 선택하여 시작하세요.",
|
||||
"Open Style": "스타일 열기",
|
||||
"Options": "옵션",
|
||||
"Output value": "값",
|
||||
"Owner": "소유자",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "스타일 소유자의 ID입니다. Mapbox 또는 향후 스타일 API에서 사용됩니다.",
|
||||
"Paint properties": "페인트 속성",
|
||||
"Pan down by 100 pixels.": "100픽셀 아래로 이동합니다.",
|
||||
"Pan left by 100 pixels.": "100픽셀 왼쪽으로 이동합니다.",
|
||||
"Pan right by 100 pixels.": "100픽셀 오른쪽으로 이동합니다.",
|
||||
"Pan up by 100 pixels.": "100픽셀 위로 이동합니다.",
|
||||
"Pitch": "경사각",
|
||||
"PMTiles URL": "PMTiles URL",
|
||||
"Press <1>ESC</1> to lose focus of any active elements, then press one of:": "<1>ESC</1>를 눌러 활성 요소의 포커스를 해제한 후, 다음 중 하나를 누르세요:",
|
||||
"Projection": "투영법",
|
||||
"Protanopia filter": "적색맹 필터",
|
||||
"Public access token for LocationIQ services.": "LocationIQ 서비스용 공개 액세스 토큰입니다.",
|
||||
"Public access token for MapTiler Cloud.": "MapTiler Cloud용 공개 액세스 토큰입니다.",
|
||||
"Public access token for Thunderforest services.": "Thunderforest 서비스용 공개 액세스 토큰입니다.",
|
||||
"Raster (Tile URLs)": "래스터 (Tile URLs)",
|
||||
"Raster (TileJSON URL)": "래스터 (TileJSON URL)",
|
||||
"Raster DEM (TileJSON URL)": "래스터 DEM (TileJSON URL)",
|
||||
"Raster DEM (XYZ URLs)": "래스터 DEM (XYZ URLs)",
|
||||
"Remove '{{sourceId}}' source": "'{{sourceId}}' 소스 제거",
|
||||
"Remove array item": "배열 항목 제거",
|
||||
"Remove variable": "변수 제거",
|
||||
"Remove zoom level from stop": "기준점에서 줌 레벨 제거",
|
||||
"Revert from expression": "표현식에서 되돌리기",
|
||||
"Save": "저장",
|
||||
"Save as": "다른 이름으로 저장",
|
||||
"Save Style": "스타일 저장",
|
||||
"Save the JSON style to your computer.": "JSON 스타일을 컴퓨터에 저장합니다.",
|
||||
"Scheme Type": "스키마 유형",
|
||||
"Search": "검색",
|
||||
"Select a type of data scale (default is 'categorical').": "데이터 스케일 유형을 선택하세요 (기본값 'categorical')",
|
||||
"Shortcuts": "단축키",
|
||||
"Shortcuts menu": "단축키 메뉴",
|
||||
"Show": "표시",
|
||||
"Source": "소스",
|
||||
"Source ID": "소스 ID",
|
||||
"Source Layer": "소스 레이어",
|
||||
"Source Type": "소스 유형",
|
||||
"Sources": "소스",
|
||||
"Sprite URL": "스프라이트 URL",
|
||||
"Stadia Maps API Key": "Stadia Maps API 키",
|
||||
"Stops": "기준점",
|
||||
"Style Renderer": "스타일 렌더러",
|
||||
"Style Settings": "스타일 설정",
|
||||
"Style Settings modal": "스타일 설정 모달",
|
||||
"Style URL": "스타일 URL",
|
||||
"Switch to filter editor.": "필터 편집기로 전환합니다.",
|
||||
"switch to layer": "레이어 전환",
|
||||
"Terrain exaggeration": "터레인 고도 배율",
|
||||
"Terrain source": "터레인 소스",
|
||||
"Text layout properties": "텍스트 레이아웃 속성",
|
||||
"Text paint properties": "텍스트 페인트 속성",
|
||||
"Thunderforest Access Token": "Thunderforest 액세스 토큰",
|
||||
"Tile Size": "타일 크기",
|
||||
"Tile URL": "타일 URL",
|
||||
"TileJSON URL": "TileJSON URL",
|
||||
"Toggle inspect": "검사 전환",
|
||||
"Transition delay": "전환 지연",
|
||||
"Transition duration": "전환 지속시간",
|
||||
"Tritanopia filter": "청색맹 필터",
|
||||
"Type": "유형",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "소스를 식별하고 레이어에서 소스를 참조하는 데 사용되는 고유 ID입니다.",
|
||||
"Upgrade to expression": "표현식으로 업그레이드",
|
||||
"Value": "값",
|
||||
"Vector (PMTiles)": "벡터 (PMTiles)",
|
||||
"Vector (Tile URLs)": "벡터 (Tile URLs)",
|
||||
"Vector (TileJSON URL)": "벡터 (TileJSON URL)",
|
||||
"Video": "비디오",
|
||||
"Video URL": "비디오 URL",
|
||||
"View": "보기",
|
||||
"You've entered an old style filter.": "이전 스타일 필터를 입력했습니다.",
|
||||
"Zoom": "줌",
|
||||
"Drag and drop a style JSON file here or click to browse": "여기에 스타일 JSON 파일을 끌어다 놓거나 클릭하여 찾아보세요"
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
{
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "<0>OSM'de aç</0>. Mevcut görünümü openstreetmap.org üzerinde açar",
|
||||
"Achromatopsia filter": "Akromatopsi filtresi",
|
||||
"Active Sources": "Aktif Kaynaklar",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "Stilinize yeni bir kaynak ekleyin. Kaynak türünü ve kimliğini yalnızca oluşturma sırasında seçebilirsiniz!",
|
||||
"Add filter": "Filtre ekle",
|
||||
"Add Layer": "Katman Ekle",
|
||||
"Add New Source": "Yeni Kaynak Ekle",
|
||||
"Add one of the publicly available sources to your style.": "Stilinize herkese açık kaynaklardan birini ekleyin.",
|
||||
"Add Source": "Kaynak Ekle",
|
||||
"Add stop": "Durak ekle",
|
||||
"Add value": "Değer ekle",
|
||||
"Add Variable": "Değişken Ekle",
|
||||
"any filter matches": "herhangi bir filtre eşleşirse",
|
||||
"API key for Stadia Maps.": "Stadia Maps için API anahtarı.",
|
||||
"Base": "Temel",
|
||||
"Bearing": "Yön",
|
||||
"Cancel": "İptal",
|
||||
"Center": "Merkez",
|
||||
"Choose Public Source": "Herkese Açık Kaynak Seç",
|
||||
"Choose the default Maputnik renderer for this style.": "Bu stil için varsayılan Maputnik işleyicisini seçin.",
|
||||
"Click to close the editor": "Düzenleyiciyi kapatmak için tıklayın",
|
||||
"Close": "Kapat",
|
||||
"Close modal": "Modalı kapat",
|
||||
"Close popup": "Açılır pencereyi kapat",
|
||||
"Cluster": "Küme",
|
||||
"Code Editor": "Kod Düzenleyici",
|
||||
"Collapse": "Daralt",
|
||||
"Color accessibility": "Renk erişilebilirliği",
|
||||
"Comment...": "Yorum...",
|
||||
"Comments": "Yorumlar",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "Mevcut katman için yorumlar. Bu standart dışıdır ve spesifikasyonda yoktur.",
|
||||
"Convert property into a elevation function": "Özelliği bir yükseklik fonksiyonuna dönüştür",
|
||||
"Convert property into a zoom function": "Özelliği bir yakınlaştırma fonksiyonuna dönüştür",
|
||||
"Convert property to data function": "Özelliği veri fonksiyonuna dönüştür",
|
||||
"Convert to expression": "İfadeye dönüştür",
|
||||
"Coord bottom left": "Koordinat sol alt",
|
||||
"Coord bottom right": "Koordinat sağ alt",
|
||||
"Coord top left": "Koordinat sol üst",
|
||||
"Coord top right": "Koordinat sağ üst",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "CORS politikası, https üzerinden http ile sunulan kaynakların alınmasına izin vermez; <1>https://</1> alan adı kullanın",
|
||||
"Create HTML": "HTML Oluştur",
|
||||
"Data Sources": "Veri Kaynakları",
|
||||
"Data Sources modal": "Veri Kaynakları modalı",
|
||||
"Data value": "Veri değeri",
|
||||
"Debug": "Hata Ayıklama",
|
||||
"Debug modal": "Hata ayıklama modalı",
|
||||
"Decrease the pitch by 10 degrees.": "Eğimi 10 derece azalt.",
|
||||
"Decrease the rotation by 15 degrees.": "Dönüşü 15 derece azalt.",
|
||||
"Decrease the zoom level by 1.": "Yakınlaştırma seviyesini 1 azalt.",
|
||||
"Decrease the zoom level by 2.": "Yakınlaştırma seviyesini 2 azalt.",
|
||||
"Default": "Varsayılan",
|
||||
"Delete": "Sil",
|
||||
"Delete expression": "İfadeyi sil",
|
||||
"Delete filter block": "Filtre bloğunu sil",
|
||||
"Deuteranopia filter": "Döteranopi filtresi",
|
||||
"Drag and drop a style JSON file here or click to browse": "Bir stil JSON dosyasını buraya sürükleyip bırakın veya göz atmak için tıklayın",
|
||||
"Duplicate": "Kopyala",
|
||||
"Encoding": "Kodlama",
|
||||
"Enter URL...": "URL girin...",
|
||||
"every filter matches": "tüm filtreler eşleşirse",
|
||||
"Expand": "Genişlet",
|
||||
"Export modal": "Dışa aktarma modalı",
|
||||
"Filter": "Filtre",
|
||||
"Focus map": "Haritaya odaklan",
|
||||
"Function": "Fonksiyon",
|
||||
"Gallery Styles": "Galeri Stilleri",
|
||||
"General layout properties": "Genel yerleşim özellikleri",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL)",
|
||||
"GeoJSON URL": "GeoJSON URL",
|
||||
"Global State": "Genel Durum",
|
||||
"Global State Variables": "Genel Durum Değişkenleri",
|
||||
"Glyphs URL": "Glif URL'si",
|
||||
"Help": "Yardım",
|
||||
"Hide": "Gizle",
|
||||
"Icon layout properties": "Simge yerleşim özellikleri",
|
||||
"Icon paint properties": "Simge boyama özellikleri",
|
||||
"If the Map is in focused you can use the following shortcuts": "Harita odaktaysa aşağıdaki kısayolları kullanabilirsiniz",
|
||||
"Image": "Görsel",
|
||||
"Image URL": "Görsel URL'si",
|
||||
"Increase the pitch by 10 degrees.": "Eğimi 10 derece artır.",
|
||||
"Increase the rotation by 15 degrees.": "Dönüşü 15 derece artır.",
|
||||
"Increase the zoom level by 1.": "Yakınlaştırma seviyesini 1 artır.",
|
||||
"Increase the zoom level by 2.": "Yakınlaştırma seviyesini 2 artır.",
|
||||
"Input a data property to base styles off of.": "Stilleri temel almak için bir veri özelliği girin.",
|
||||
"Input value": "Giriş değeri",
|
||||
"Inspect": "İncele",
|
||||
"JSON Editor": "JSON Düzenleyici",
|
||||
"Key": "Anahtar",
|
||||
"Layer": "Katman",
|
||||
"Layer editor": "Katman düzenleyici",
|
||||
"Layer ID already exists": "Katman kimliği zaten var",
|
||||
"Layers": "Katmanlar",
|
||||
"Layers list": "Katman listesi",
|
||||
"Layout properties": "Yerleşim özellikleri",
|
||||
"Learn More": "Daha Fazla Bilgi",
|
||||
"Light anchor": "Işık çapası",
|
||||
"Light color": "Işık rengi",
|
||||
"Light intensity": "Işık yoğunluğu",
|
||||
"Light position": "Işık konumu",
|
||||
"Links": "Bağlantılar",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "Bir URL'den yükleyin. URL'de <1>CORS etkin</1> olmalıdır.",
|
||||
"Load from URL": "URL'den yükle",
|
||||
"Loading": "Yükleniyor",
|
||||
"Loading style": "Stil yükleniyor",
|
||||
"LocationIQ Access Token": "LocationIQ Erişim Anahtarı",
|
||||
"Map": "Harita",
|
||||
"Map view": "Harita görünümü",
|
||||
"MapTiler Access Token": "MapTiler Erişim Anahtarı",
|
||||
"Maputnik on GitHub": "GitHub'da Maputnik",
|
||||
"Max Zoom": "Maks Yakınlaştırma",
|
||||
"Min Zoom": "Min Yakınlaştırma",
|
||||
"Move layer down": "Katmanı aşağı taşı",
|
||||
"Move layer up": "Katmanı yukarı taşı",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "Protokol belirtilmeli: <1>http://</1> veya <3>https://</3>",
|
||||
"Must provide protocol: <1>https://</1>": "Protokol belirtilmeli: <1>https://</1>",
|
||||
"Name": "Ad",
|
||||
"Nested filters are not supported.": "İç içe filtreler desteklenmiyor.",
|
||||
"no filter matches": "hiçbir filtre eşleşmiyorsa",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "Genel durum değişkeni tanımlanmadı. Stilinizde yeniden kullanılabilir değerler oluşturmak için değişken ekleyin.",
|
||||
"Open": "Aç",
|
||||
"Open a local JSON style from your computer.": "Bilgisayarınızdan yerel bir JSON stili açın.",
|
||||
"Open Layers (experimental)": "Open Layers (deneysel)",
|
||||
"Open local Style": "Yerel Stili Aç",
|
||||
"Open modal": "Modalı aç",
|
||||
"Open one of the publicly available styles to start from.": "Başlamak için herkese açık stillerden birini açın.",
|
||||
"Open Style": "Stili Aç",
|
||||
"Options": "Seçenekler",
|
||||
"Output value": "Çıkış değeri",
|
||||
"Owner": "Sahip",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "Stilin sahip kimliği. Mapbox veya gelecekteki stil API'leri tarafından kullanılır.",
|
||||
"Paint properties": "Boyama özellikleri",
|
||||
"Pan down by 100 pixels.": "100 piksel aşağı kaydır.",
|
||||
"Pan left by 100 pixels.": "100 piksel sola kaydır.",
|
||||
"Pan right by 100 pixels.": "100 piksel sağa kaydır.",
|
||||
"Pan up by 100 pixels.": "100 piksel yukarı kaydır.",
|
||||
"Pitch": "Eğim",
|
||||
"PMTiles URL": "PMTiles URL",
|
||||
"Press <1>ESC</1> to lose focus of any active elements, then press one of:": "Etkin öğelerin odağını kaldırmak için <1>ESC</1> tuşuna basın, ardından şunlardan birine basın:",
|
||||
"Projection": "Projeksiyon",
|
||||
"Protanopia filter": "Protanopi filtresi",
|
||||
"Public access token for LocationIQ services.": "LocationIQ hizmetleri için genel erişim anahtarı.",
|
||||
"Public access token for MapTiler Cloud.": "MapTiler Cloud için genel erişim anahtarı.",
|
||||
"Public access token for Thunderforest services.": "Thunderforest hizmetleri için genel erişim anahtarı.",
|
||||
"Raster (Tile URLs)": "Raster (Döşeme URL'leri)",
|
||||
"Raster (TileJSON URL)": "Raster (TileJSON URL)",
|
||||
"Raster DEM (TileJSON URL)": "Raster DEM (TileJSON URL)",
|
||||
"Raster DEM (XYZ URLs)": "Raster DEM (XYZ URL'leri)",
|
||||
"Remove '{{sourceId}}' source": "'{{sourceId}}' kaynağını kaldır",
|
||||
"Remove array item": "Dizi öğesini kaldır",
|
||||
"Remove variable": "Değişkeni kaldır",
|
||||
"Remove zoom level from stop": "Duraktan yakınlaştırma seviyesini kaldır",
|
||||
"Revert from expression": "İfadeden geri dön",
|
||||
"Save": "Kaydet",
|
||||
"Save as": "Farklı kaydet",
|
||||
"Save Style": "Stili Kaydet",
|
||||
"Save the JSON style to your computer.": "JSON stilini bilgisayarınıza kaydedin.",
|
||||
"Scheme Type": "Şema Türü",
|
||||
"Search": "Ara",
|
||||
"Select a type of data scale (default is 'categorical').": "Bir veri ölçeği türü seçin (varsayılan 'categorical').",
|
||||
"Shortcuts": "Kısayollar",
|
||||
"Shortcuts menu": "Kısayollar menüsü",
|
||||
"Show": "Göster",
|
||||
"Source": "Kaynak",
|
||||
"Source ID": "Kaynak Kimliği",
|
||||
"Source Layer": "Kaynak Katmanı",
|
||||
"Source Type": "Kaynak Türü",
|
||||
"Sources": "Kaynaklar",
|
||||
"Sprite URL": "Sprite URL'si",
|
||||
"Stadia Maps API Key": "Stadia Maps API Anahtarı",
|
||||
"Stops": "Duraklar",
|
||||
"Style Renderer": "Stil İşleyici",
|
||||
"Style Settings": "Stil Ayarları",
|
||||
"Style Settings modal": "Stil ayarları modalı",
|
||||
"Style URL": "Stil URL'si",
|
||||
"Switch to filter editor.": "Filtre düzenleyiciye geç.",
|
||||
"switch to layer": "katmana geç",
|
||||
"Terrain exaggeration": "Arazi abartısı",
|
||||
"Terrain source": "Arazi kaynağı",
|
||||
"Text layout properties": "Metin yerleşim özellikleri",
|
||||
"Text paint properties": "Metin boyama özellikleri",
|
||||
"Thunderforest Access Token": "Thunderforest Erişim Anahtarı",
|
||||
"Tile Size": "Döşeme Boyutu",
|
||||
"Tile URL": "Döşeme URL'si",
|
||||
"TileJSON URL": "TileJSON URL",
|
||||
"Toggle inspect": "İncelemeyi aç/kapat",
|
||||
"Transition delay": "Geçiş gecikmesi",
|
||||
"Transition duration": "Geçiş süresi",
|
||||
"Tritanopia filter": "Tritanopi filtresi",
|
||||
"Type": "Tür",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "Kaynağı tanımlayan ve katmanda kaynağa referans vermek için kullanılan benzersiz kimlik.",
|
||||
"Upgrade to expression": "İfadeye yükselt",
|
||||
"Value": "Değer",
|
||||
"Vector (PMTiles)": "Vektör (PMTiles)",
|
||||
"Vector (Tile URLs)": "Vektör (Döşeme URL'leri)",
|
||||
"Vector (TileJSON URL)": "Vektör (TileJSON URL)",
|
||||
"Video": "Video",
|
||||
"Video URL": "Video URL'si",
|
||||
"View": "Görünüm",
|
||||
"You've entered an old style filter.": "Eski stilde bir filtre girdiniz.",
|
||||
"Zoom": "Yakınlaştırma"
|
||||
}
|
||||
+186
-180
@@ -1,204 +1,210 @@
|
||||
{
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "在 openstreetmap.org 打开当前视图",
|
||||
"Achromatopsia filter": "全色盲滤镜",
|
||||
"Active Sources": "激活的源",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "向您的样式添加新源。在创建时,您只能选择源类型和ID!",
|
||||
"Add filter": "添加过滤器",
|
||||
"Add Layer": "添加图层",
|
||||
"Add New Source": "添加新源",
|
||||
"Add one of the publicly available sources to your style.": "将公开可用的源之一添加到您的样式中。",
|
||||
"Add Source": "添加源",
|
||||
"Add stop": "添加停靠点",
|
||||
"Add value": "添加值",
|
||||
"Add Variable": "添加变量",
|
||||
"any filter matches": "任何过滤器匹配",
|
||||
"API key for Stadia Maps.": "Stadia Maps 的 API 密钥",
|
||||
"Input value": "输入值",
|
||||
"Data value": "数据值",
|
||||
"Output value": "输出值",
|
||||
"Function": "函数",
|
||||
"Select a type of data scale (default is 'categorical').": "选择数据尺度的类型(默认为“分类”)。",
|
||||
"Base": "基础",
|
||||
"Bearing": "方位",
|
||||
"Cancel": "取消",
|
||||
"Center": "中心",
|
||||
"Choose Public Source": "选择公共源",
|
||||
"Choose the default Maputnik renderer for this style.": "为这种样式选择默认的Maputnik渲染器。",
|
||||
"Click to close the editor": "点击关闭编辑器",
|
||||
"Close": "关闭",
|
||||
"Close modal": "关闭模态框",
|
||||
"Close popup": "关闭弹出窗口",
|
||||
"Cluster": "聚合",
|
||||
"Code Editor": "代码编辑器",
|
||||
"Collapse": "折叠",
|
||||
"Color accessibility": "颜色可访问性",
|
||||
"Comment...": "写注释...",
|
||||
"Comments": "注释",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "当前图层的注释。注意:这不是标准功能,可能与其他库不兼容。",
|
||||
"Convert property into a elevation function": "将属性转换为高程函数",
|
||||
"Input a data property to base styles off of.": "输入一个数据属性作为样式的基础。",
|
||||
"Default": "默认",
|
||||
"Stops": "停靠点",
|
||||
"Zoom": "缩放",
|
||||
"Add stop": "添加停靠点",
|
||||
"Convert to expression": "转换为表达式",
|
||||
"Remove zoom level from stop": "从停靠点移除缩放级别",
|
||||
"Revert from expression": "从表达式恢复",
|
||||
"Delete expression": "删除表达式",
|
||||
"Convert property into a zoom function": "将属性转换为缩放函数",
|
||||
"Convert property to data function": "将属性转换为数据函数",
|
||||
"Convert to expression": "转换为表达式",
|
||||
"Coord bottom left": "左下角坐标",
|
||||
"Coord bottom right": "右下角坐标",
|
||||
"Coord top left": "左上角坐标",
|
||||
"Coord top right": "右上角坐标",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "CORS 策略不允许从 https 访问通过 http 提供的资源,请使用 <1>https://</1> 域名。",
|
||||
"Create HTML": "创建HTML",
|
||||
"Data Sources": "数据源",
|
||||
"Data Sources modal": "数据源模态框",
|
||||
"Data value": "数据值",
|
||||
"Debug": "调试",
|
||||
"Debug modal": "调试模态框",
|
||||
"Decrease the pitch by 10 degrees.": "将俯仰角减少10度。",
|
||||
"Decrease the rotation by 15 degrees.": "将旋转角度减少15度。",
|
||||
"Decrease the zoom level by 1.": "将缩放级别减少1。",
|
||||
"Decrease the zoom level by 2.": "将缩放级别减少2。",
|
||||
"Default": "默认",
|
||||
"Delete": "删除",
|
||||
"Delete expression": "删除表达式",
|
||||
"Delete filter block": "删除过滤器块",
|
||||
"Deuteranopia filter": "绿色盲滤镜",
|
||||
"Duplicate": "复制",
|
||||
"Encoding": "编码",
|
||||
"Enter URL...": "输入URL...",
|
||||
"every filter matches": "每个过滤器都匹配",
|
||||
"Expand": "展开",
|
||||
"Export modal": "导出模态框",
|
||||
"Filter": "过滤器",
|
||||
"Focus map": "聚焦地图",
|
||||
"Function": "函数",
|
||||
"Gallery Styles": "画廊样式",
|
||||
"General layout properties": "常规布局属性",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON格式)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL格式)",
|
||||
"GeoJSON URL": "GeoJSON URL",
|
||||
"Global State": "全局状态",
|
||||
"Global State Variables": "全局状态变量",
|
||||
"Glyphs URL": "字形URL",
|
||||
"Help": "帮助",
|
||||
"Hide": "隐藏",
|
||||
"Icon layout properties": "图标布局属性",
|
||||
"Icon paint properties": "图标绘制属性",
|
||||
"If the Map is in focused you can use the following shortcuts": "如果地图处于焦点状态,您可以使用以下快捷键",
|
||||
"Image": "图像",
|
||||
"Image URL": "图像URL",
|
||||
"Increase the pitch by 10 degrees.": "将俯仰角增加10度。",
|
||||
"Increase the rotation by 15 degrees.": "将旋转角度增加15度。",
|
||||
"Increase the zoom level by 1.": "将缩放级别增加1。",
|
||||
"Increase the zoom level by 2.": "将缩放级别增加2。",
|
||||
"Input a data property to base styles off of.": "输入一个数据属性作为样式的基础。",
|
||||
"Input value": "输入值",
|
||||
"Convert property into a elevation function": "将属性转换为高程函数",
|
||||
"Layer <1>{formatLayerId(layerId)}</1>: {parsed.data.message}": "图层<1>{formatLayerId(layerId)}</1>: {parsed.data.message}",
|
||||
"switch to layer": "切换到图层",
|
||||
"Map": "地图",
|
||||
"Inspect": "检查",
|
||||
"JSON Editor": "JSON编辑器",
|
||||
"Key": "键",
|
||||
"Layer": "图层",
|
||||
"Layer editor": "图层编辑器",
|
||||
"Layer ID already exists": "图层ID已存在",
|
||||
"Layers": "图层",
|
||||
"Deuteranopia filter": "绿色盲滤镜",
|
||||
"Protanopia filter": "红色盲滤镜",
|
||||
"Tritanopia filter": "蓝色盲滤镜",
|
||||
"Achromatopsia filter": "全色盲滤镜",
|
||||
"Layers list": "图层列表",
|
||||
"Layer editor": "图层编辑器",
|
||||
"Map view": "地图视图",
|
||||
"Maputnik on GitHub": "GitHub上的Maputnik",
|
||||
"Open": "打开",
|
||||
"Save": "保存",
|
||||
"Code Editor": "代码编辑器",
|
||||
"Data Sources": "数据源",
|
||||
"Style Settings": "样式设置",
|
||||
"Global State": "全局状态",
|
||||
"View": "视图",
|
||||
"Color accessibility": "颜色可访问性",
|
||||
"Help": "帮助",
|
||||
"Close": "关闭",
|
||||
"Click to close the editor": "点击关闭编辑器",
|
||||
"Comments for the current layer. This is non-standard and not in the spec.": "当前图层的注释。注意:这不是标准功能,可能与其他库不兼容。",
|
||||
"Comments": "注释",
|
||||
"Comment...": "写注释...",
|
||||
"Max Zoom": "最大缩放",
|
||||
"Min Zoom": "最小缩放",
|
||||
"Source": "源",
|
||||
"Source Layer": "源图层",
|
||||
"Type": "类型",
|
||||
"Nested filters are not supported.": "不支持嵌套过滤器。",
|
||||
"Upgrade to expression": "升级为表达式",
|
||||
"Filter": "过滤器",
|
||||
"every filter matches": "每个过滤器都匹配",
|
||||
"no filter matches": "没有过滤器匹配",
|
||||
"any filter matches": "任何过滤器匹配",
|
||||
"Add filter": "添加过滤器",
|
||||
"You've entered an old style filter.": "您输入了一个旧风格的过滤器。",
|
||||
"Switch to filter editor.": "切换到过滤器编辑器。",
|
||||
"Delete filter block": "删除过滤器块",
|
||||
"Add value": "添加值",
|
||||
"Remove array item": "移除数组项目",
|
||||
"Must provide protocol: <1>https://</1>": "必须提供协议:<1>https://</1>",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "必须提供协议:<1>http://</1> 或 <3>https://</3>",
|
||||
"CORS policy won't allow fetching resources served over http from https, use a <1>https://</1> domain": "CORS 策略不允许从 https 访问通过 http 提供的资源,请使用 <1>https://</1> 域名。",
|
||||
"General layout properties": "常规布局属性",
|
||||
"Text layout properties": "文本布局属性",
|
||||
"Icon layout properties": "图标布局属性",
|
||||
"Text paint properties": "文本绘制属性",
|
||||
"Icon paint properties": "图标绘制属性",
|
||||
"Paint properties": "绘制属性",
|
||||
"Layout properties": "布局属性",
|
||||
"Learn More": "了解更多",
|
||||
"Layer": "图层",
|
||||
"JSON Editor": "JSON编辑器",
|
||||
"Delete": "删除",
|
||||
"Duplicate": "复制",
|
||||
"Show": "显示",
|
||||
"Hide": "隐藏",
|
||||
"Move layer up": "向上移动图层",
|
||||
"Move layer down": "向下移动图层",
|
||||
"Layer: {{layerId}}": "图层: {{layerId}}",
|
||||
"Layers": "图层",
|
||||
"Collapse": "折叠",
|
||||
"Expand": "展开",
|
||||
"Add Layer": "添加图层",
|
||||
"Search": "搜索",
|
||||
"Zoom:": "缩放:",
|
||||
"Close popup": "关闭弹出窗口",
|
||||
"cursor:": "光标:",
|
||||
"center:": "中心:",
|
||||
"rotation:": "旋转:",
|
||||
"Close modal": "关闭模态框",
|
||||
"Layer ID already exists": "图层ID已存在",
|
||||
"Debug": "调试",
|
||||
"Options": "选项",
|
||||
"Links": "链接",
|
||||
"<0>Open in OSM</0>. Opens the current view on openstreetmap.org": "在 openstreetmap.org 打开当前视图",
|
||||
"Save Style": "保存样式",
|
||||
"Save the JSON style to your computer.": "将JSON样式保存到您的计算机。",
|
||||
"Save as": "另存为",
|
||||
"Create HTML": "创建HTML",
|
||||
"Key": "键",
|
||||
"Value": "值",
|
||||
"Remove variable": "移除变量",
|
||||
"Global State Variables": "全局状态变量",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "未定义全局状态变量。添加变量以在样式中创建可重用的值。",
|
||||
"Add Variable": "添加变量",
|
||||
"Cancel": "取消",
|
||||
"Open Style": "打开样式",
|
||||
"Open local Style": "打开本地样式",
|
||||
"Open a local JSON style from your computer.": "从您的计算机打开本地JSON样式。",
|
||||
"Load from URL": "从URL加载",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "从URL加载。注意:URL必须启用 <1>CORS</1>。",
|
||||
"Style URL": "样式URL",
|
||||
"Enter URL...": "输入URL...",
|
||||
"Gallery Styles": "画廊样式",
|
||||
"Open one of the publicly available styles to start from.": "打开一个公开可用的样式开始。",
|
||||
"Loading style": "正在加载样式",
|
||||
"Loading: {{requestUrl}}": "正在加载: {{requestUrl}}",
|
||||
"Name": "名称",
|
||||
"Owner": "所有者",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "样式的所有者ID。由Mapbox或未来的样式API使用。",
|
||||
"Sprite URL": "精灵URL",
|
||||
"Glyphs URL": "字形URL",
|
||||
"Center": "中心",
|
||||
"Bearing": "方位",
|
||||
"Pitch": "俯仰角",
|
||||
"Light anchor": "光源锚点",
|
||||
"Light color": "光源颜色",
|
||||
"Light intensity": "光源强度",
|
||||
"Light position": "光源位置",
|
||||
"Links": "链接",
|
||||
"Load from a URL. Note that the URL must have <1>CORS enabled</1>.": "从URL加载。注意:URL必须启用 <1>CORS</1>。",
|
||||
"Load from URL": "从URL加载",
|
||||
"Loading": "正在加载",
|
||||
"Loading style": "正在加载样式",
|
||||
"LocationIQ Access Token": "LocationIQ 访问令牌",
|
||||
"Map": "地图",
|
||||
"Map view": "地图视图",
|
||||
"MapTiler Access Token": "MapTiler 访问令牌",
|
||||
"Maputnik on GitHub": "GitHub上的Maputnik",
|
||||
"Max Zoom": "最大缩放",
|
||||
"Min Zoom": "最小缩放",
|
||||
"Move layer down": "向下移动图层",
|
||||
"Move layer up": "向上移动图层",
|
||||
"Must provide protocol: <1>http://</1> or <3>https://</3>": "必须提供协议:<1>http://</1> 或 <3>https://</3>",
|
||||
"Must provide protocol: <1>https://</1>": "必须提供协议:<1>https://</1>",
|
||||
"Name": "名称",
|
||||
"Nested filters are not supported.": "不支持嵌套过滤器。",
|
||||
"no filter matches": "没有过滤器匹配",
|
||||
"No global state variables defined. Add variables to create reusable values in your style.": "未定义全局状态变量。添加变量以在样式中创建可重用的值。",
|
||||
"Open": "打开",
|
||||
"Open a local JSON style from your computer.": "从您的计算机打开本地JSON样式。",
|
||||
"Terrain source": "地形源",
|
||||
"Terrain exaggeration": "地形夸张",
|
||||
"Transition delay": "过渡延迟",
|
||||
"Transition duration": "过渡持续时间",
|
||||
"Projection": "投影",
|
||||
"Open Layers (experimental)": "开放图层(实验性)",
|
||||
"Open local Style": "打开本地样式",
|
||||
"Shortcuts menu": "快捷方式菜单",
|
||||
"Open modal": "打开模态框",
|
||||
"Open one of the publicly available styles to start from.": "打开一个公开可用的样式开始。",
|
||||
"Open Style": "打开样式",
|
||||
"Options": "选项",
|
||||
"Output value": "输出值",
|
||||
"Owner": "所有者",
|
||||
"Owner ID of the style. Used by Mapbox or future style APIs.": "样式的所有者ID。由Mapbox或未来的样式API使用。",
|
||||
"Paint properties": "绘制属性",
|
||||
"Export modal": "导出模态框",
|
||||
"Data Sources modal": "数据源模态框",
|
||||
"Style Settings modal": "样式设置模态框",
|
||||
"Toggle inspect": "切换检查",
|
||||
"Focus map": "聚焦地图",
|
||||
"Debug modal": "调试模态框",
|
||||
"Increase the zoom level by 1.": "将缩放级别增加1。",
|
||||
"Increase the zoom level by 2.": "将缩放级别增加2。",
|
||||
"Decrease the zoom level by 1.": "将缩放级别减少1。",
|
||||
"Decrease the zoom level by 2.": "将缩放级别减少2。",
|
||||
"Pan up by 100 pixels.": "向上平移100像素。",
|
||||
"Pan down by 100 pixels.": "向下平移100像素。",
|
||||
"Pan left by 100 pixels.": "向左平移100像素。",
|
||||
"Pan right by 100 pixels.": "向右平移100像素。",
|
||||
"Pan up by 100 pixels.": "向上平移100像素。",
|
||||
"Pitch": "俯仰角",
|
||||
"PMTiles URL": "PMTiles URL",
|
||||
"Increase the rotation by 15 degrees.": "将旋转角度增加15度。",
|
||||
"Decrease the rotation by 15 degrees.": "将旋转角度减少15度。",
|
||||
"Increase the pitch by 10 degrees.": "将俯仰角增加10度。",
|
||||
"Decrease the pitch by 10 degrees.": "将俯仰角减少10度。",
|
||||
"Shortcuts": "快捷键",
|
||||
"Press <1>ESC</1> to lose focus of any active elements, then press one of:": "按下 <1>ESC</1> 退出任何活动元素的焦点,然后按下以下之一:",
|
||||
"Projection": "投影",
|
||||
"Protanopia filter": "红色盲滤镜",
|
||||
"Public access token for LocationIQ services.": "LocationIQ 服务的公共访问令牌。",
|
||||
"Public access token for MapTiler Cloud.": "MapTiler Cloud 的公共访问令牌。",
|
||||
"Public access token for Thunderforest services.": "Thunderforest 服务的公共访问令牌。",
|
||||
"Raster (Tile URLs)": "栅格数据 (Tile URLs)",
|
||||
"If the Map is in focused you can use the following shortcuts": "如果地图处于焦点状态,您可以使用以下快捷键",
|
||||
"Remove '{{sourceId}}' source": "移除 '{{sourceId}}' 源",
|
||||
"Source ID": "源ID",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "用于标识源并在图层中引用源的唯一ID。",
|
||||
"Source Type": "源类型",
|
||||
"GeoJSON (JSON)": "GeoJSON (JSON格式)",
|
||||
"GeoJSON (URL)": "GeoJSON (URL格式)",
|
||||
"Vector (TileJSON URL)": "矢量数据 (TileJSON URL)",
|
||||
"Vector (Tile URLs)": "矢量数据 (Tile URLs)",
|
||||
"Raster (TileJSON URL)": "栅格数据 (TileJSON URL)",
|
||||
"Raster (Tile URLs)": "栅格数据 (Tile URLs)",
|
||||
"Raster DEM (TileJSON URL)": "栅格高程数据 (TileJSON URL)",
|
||||
"Raster DEM (XYZ URLs)": "栅格高程数据 (XYZ URLs)",
|
||||
"Remove '{{sourceId}}' source": "移除 '{{sourceId}}' 源",
|
||||
"Remove array item": "移除数组项目",
|
||||
"Remove variable": "移除变量",
|
||||
"Remove zoom level from stop": "从停靠点移除缩放级别",
|
||||
"Revert from expression": "从表达式恢复",
|
||||
"Save": "保存",
|
||||
"Save as": "另存为",
|
||||
"Save Style": "保存样式",
|
||||
"Save the JSON style to your computer.": "将JSON样式保存到您的计算机。",
|
||||
"Scheme Type": "瓦片方案",
|
||||
"Search": "搜索",
|
||||
"Select a type of data scale (default is 'categorical').": "选择数据尺度的类型(默认为“分类”)。",
|
||||
"Shortcuts": "快捷键",
|
||||
"Shortcuts menu": "快捷方式菜单",
|
||||
"Show": "显示",
|
||||
"Source": "源",
|
||||
"Source ID": "源ID",
|
||||
"Source Layer": "源图层",
|
||||
"Source Type": "源类型",
|
||||
"Sources": "源列表",
|
||||
"Sprite URL": "精灵URL",
|
||||
"Stadia Maps API Key": "Stadia Maps API 密钥",
|
||||
"Stops": "停靠点",
|
||||
"Style Renderer": "样式渲染器",
|
||||
"Style Settings": "样式设置",
|
||||
"Style Settings modal": "样式设置模态框",
|
||||
"Style URL": "样式URL",
|
||||
"Switch to filter editor.": "切换到过滤器编辑器。",
|
||||
"switch to layer": "切换到图层",
|
||||
"Terrain exaggeration": "地形夸张",
|
||||
"Terrain source": "地形源",
|
||||
"Text layout properties": "文本布局属性",
|
||||
"Text paint properties": "文本绘制属性",
|
||||
"Thunderforest Access Token": "Thunderforest 访问令牌",
|
||||
"Tile Size": "瓦片大小",
|
||||
"Tile URL": "瓦片URL",
|
||||
"TileJSON URL": "TileJSON URL",
|
||||
"Toggle inspect": "切换检查",
|
||||
"Transition delay": "过渡延迟",
|
||||
"Transition duration": "过渡持续时间",
|
||||
"Tritanopia filter": "蓝色盲滤镜",
|
||||
"Type": "类型",
|
||||
"Unique ID that identifies the source and is used in the layer to reference the source.": "用于标识源并在图层中引用源的唯一ID。",
|
||||
"Upgrade to expression": "升级为表达式",
|
||||
"Value": "值",
|
||||
"Vector (PMTiles)": "矢量数据 (PMTiles)",
|
||||
"Vector (Tile URLs)": "矢量数据 (Tile URLs)",
|
||||
"Vector (TileJSON URL)": "矢量数据 (TileJSON URL)",
|
||||
"Image": "图像",
|
||||
"Video": "视频",
|
||||
"Add Source": "添加源",
|
||||
"Sources": "源列表",
|
||||
"Active Sources": "激活的源",
|
||||
"Choose Public Source": "选择公共源",
|
||||
"Add one of the publicly available sources to your style.": "将公开可用的源之一添加到您的样式中。",
|
||||
"Add New Source": "添加新源",
|
||||
"Add a new source to your style. You can only choose the source type and id at creation time!": "向您的样式添加新源。在创建时,您只能选择源类型和ID!",
|
||||
"TileJSON URL": "TileJSON URL",
|
||||
"Tile URL": "瓦片URL",
|
||||
"Scheme Type": "瓦片方案",
|
||||
"Coord top left": "左上角坐标",
|
||||
"Coord top right": "右上角坐标",
|
||||
"Coord bottom right": "右下角坐标",
|
||||
"Coord bottom left": "左下角坐标",
|
||||
"Image URL": "图像URL",
|
||||
"Video URL": "视频URL",
|
||||
"View": "视图",
|
||||
"You've entered an old style filter.": "您输入了一个旧风格的过滤器。",
|
||||
"Zoom": "缩放",
|
||||
"Drag and drop a style JSON file here or click to browse": "将样式 JSON 文件拖放到此处或点击以浏览"
|
||||
"GeoJSON URL": "GeoJSON URL",
|
||||
"GeoJSON": "GeoJSON",
|
||||
"Cluster": "聚合",
|
||||
"PMTiles URL": "PMTiles URL",
|
||||
"Tile Size": "瓦片大小",
|
||||
"Encoding": "编码",
|
||||
"Error:": "错误:",
|
||||
"MapTiler Access Token": "MapTiler 访问令牌",
|
||||
"Public access token for MapTiler Cloud.": "MapTiler Cloud 的公共访问令牌。",
|
||||
"Learn More": "了解更多",
|
||||
"Thunderforest Access Token": "Thunderforest 访问令牌",
|
||||
"Public access token for Thunderforest services.": "Thunderforest 服务的公共访问令牌。",
|
||||
"Stadia Maps API Key": "Stadia Maps API 密钥",
|
||||
"API key for Stadia Maps.": "Stadia Maps 的 API 密钥",
|
||||
"LocationIQ Access Token": "LocationIQ 访问令牌",
|
||||
"Public access token for LocationIQ services.": "LocationIQ 服务的公共访问令牌。",
|
||||
"Style Renderer": "样式渲染器",
|
||||
"Choose the default Maputnik renderer for this style.": "为这种样式选择默认的Maputnik渲染器。"
|
||||
}
|
||||
|
||||
@@ -88,47 +88,6 @@
|
||||
@extend .maputnik-big-button !optional; /* stylelint-disable-line */
|
||||
}
|
||||
|
||||
.maputnik-upload-dropzone {
|
||||
border: 2px dashed vars.$color-midgray;
|
||||
border-radius: 6px;
|
||||
padding: vars.$margin-4 vars.$margin-3;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: border-color 120ms ease, background-color 120ms ease;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
border-color: vars.$color-lowgray;
|
||||
background-color: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid vars.$color-lowgray;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.maputnik-upload-dropzone--active {
|
||||
border-color: vars.$color-lowgray;
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.maputnik-upload-dropzone-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.maputnik-upload-dropzone-icon {
|
||||
font-size: 28px;
|
||||
color: vars.$color-midgray;
|
||||
}
|
||||
|
||||
.maputnik-upload-dropzone-text {
|
||||
margin: vars.$margin-2 0 0;
|
||||
color: vars.$color-lowgray;
|
||||
}
|
||||
|
||||
.maputnik-style-gallery-container {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
|
||||
// HACK: Override
|
||||
// HACK: Overide
|
||||
.maputnik-input-block {
|
||||
margin: vars.$margin-2;
|
||||
}
|
||||
|
||||
+10
-3
@@ -3,7 +3,7 @@
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"types": ["geojson", "@types/wicg-file-system-access", "node"],
|
||||
"types": ["geojson", "@types/wicg-file-system-access"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
@@ -21,7 +21,14 @@
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src", "e2e"],
|
||||
"include": ["src", "cypress/e2e"],
|
||||
"exclude": ["dist"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
"references": [{ "path": "./tsconfig.node.json" }],
|
||||
// TODO: Remove when issue is resolved https://github.com/cypress-io/cypress/issues/27448
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -1,3 +1,4 @@
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
import istanbul from "vite-plugin-istanbul";
|
||||
@@ -10,11 +11,20 @@ export default defineConfig(({ mode }) => ({
|
||||
sourcemap: true
|
||||
},
|
||||
plugins: [
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
include: /\/jsonlint-lines-primitives\/lib\/jsonlint.js/,
|
||||
delimiters: ["", ""],
|
||||
values: {
|
||||
"_token_stack:": "",
|
||||
},
|
||||
}),
|
||||
react(),
|
||||
istanbul({
|
||||
cypress: true,
|
||||
requireEnv: false,
|
||||
nycrcPath: "./.nycrc.json",
|
||||
forceBuildInstrument: true, // Instrument the source so e2e runs can collect coverage
|
||||
forceBuildInstrument: true, //Instrument the source code for cypress runs
|
||||
}),
|
||||
],
|
||||
base: mode === "desktop" ? "/" : "/maputnik/",
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { playwright } from "@vitest/browser-playwright";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
projects: [
|
||||
{
|
||||
extends: true,
|
||||
test: {
|
||||
name: "unit",
|
||||
environment: "node",
|
||||
include: ["src/**/*.test.{ts,tsx}"],
|
||||
exclude: ["src/**/*.browser.test.{ts,tsx}"],
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: true,
|
||||
test: {
|
||||
name: "browser",
|
||||
include: ["src/**/*.browser.test.{ts,tsx}"],
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: playwright(),
|
||||
headless: true,
|
||||
instances: [{ browser: "chromium" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
reporter: ["json", "lcov", "text-summary"],
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user