mirror of
https://github.com/maputnik/editor.git
synced 2026-08-03 03:37:25 +00:00
Compare commits
2 Commits
v2.1.0
..
285dca8d2c
| Author | SHA1 | Date | |
|---|---|---|---|
| 285dca8d2c | |||
| 2a3b6aa770 |
@@ -1,11 +0,0 @@
|
|||||||
## Launch Checklist
|
|
||||||
|
|
||||||
<!-- Thanks for the PR! Feel free to add or remove items from the checklist. -->
|
|
||||||
|
|
||||||
|
|
||||||
- [ ] Briefly describe the changes in this PR.
|
|
||||||
- [ ] Link to related issues.
|
|
||||||
- [ ] Include before/after visuals or gifs if this PR includes visual changes.
|
|
||||||
- [ ] Write tests for all new functionality.
|
|
||||||
- [ ] Add an entry to `CHANGELOG.md` under the `## main` section.
|
|
||||||
|
|
||||||
+18
-12
@@ -54,39 +54,45 @@ jobs:
|
|||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- name: artifacts/maputnik
|
- name: artifacts/maputnik
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: maputnik
|
name: maputnik
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
# Build and upload desktop CLI artifacts
|
# Build and upload desktop CLI artifacts
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ^1.23.x
|
go-version: ^1.19.x
|
||||||
cache-dependency-path: desktop/go.sum
|
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Build desktop artifacts
|
- name: Check out code into the Go module directory
|
||||||
run: npm run build-desktop
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: maputnik/desktop
|
||||||
|
ref: master
|
||||||
|
path: ./src/github.com/maputnik/desktop/
|
||||||
|
|
||||||
|
- name: Make
|
||||||
|
run: cd src/github.com/maputnik/desktop/ && make
|
||||||
|
|
||||||
- name: Artifacts/linux
|
- name: Artifacts/linux
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: maputnik-linux
|
name: maputnik-linux
|
||||||
path: ./desktop/bin/linux/
|
path: ./src/github.com/maputnik/desktop/bin/linux/
|
||||||
|
|
||||||
- name: Artifacts/darwin
|
- name: Artifacts/darwin
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: maputnik-darwin
|
name: maputnik-darwin
|
||||||
path: ./desktop/bin/darwin/
|
path: ./src/github.com/maputnik/desktop/bin/darwin/
|
||||||
|
|
||||||
- name: Artifacts/windows
|
- name: Artifacts/windows
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: maputnik-windows
|
name: maputnik-windows
|
||||||
path: ./desktop/bin/windows/
|
path: ./src/github.com/maputnik/desktop/bin/windows/
|
||||||
|
|
||||||
e2e-tests:
|
e2e-tests:
|
||||||
name: "E2E tests using ${{ matrix.browser }}"
|
name: "E2E tests using ${{ matrix.browser }}"
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
name: Create bump version PR
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: Version to change to.
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
bump-version-pr:
|
|
||||||
name: Bump version PR
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: main
|
|
||||||
|
|
||||||
- name: Use Node.js from nvmrc
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version-file: ".nvmrc"
|
|
||||||
|
|
||||||
- name: Bump version
|
|
||||||
run: |
|
|
||||||
npm version --commit-hooks false --git-tag-version false ${{ inputs.version }}
|
|
||||||
./build/bump-version-changelog.js ${{ inputs.version }}
|
|
||||||
|
|
||||||
- name: Create Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@v6
|
|
||||||
with:
|
|
||||||
commit-message: Bump version to ${{ inputs.version }}
|
|
||||||
branch: bump-version-to-${{ inputs.version }}
|
|
||||||
title: Bump version to ${{ inputs.version }}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release-check:
|
|
||||||
name: Check if version changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: main
|
|
||||||
|
|
||||||
- name: Use Node.js from nvmrc
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version-file: ".nvmrc"
|
|
||||||
|
|
||||||
- name: Check if version has been updated
|
|
||||||
id: check
|
|
||||||
uses: EndBug/version-check@v2
|
|
||||||
|
|
||||||
outputs:
|
|
||||||
publish: ${{ steps.check.outputs.changed }}
|
|
||||||
|
|
||||||
release:
|
|
||||||
name: Release
|
|
||||||
needs: release-check
|
|
||||||
if: ${{ needs.release-check.outputs.publish == 'true' }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: main
|
|
||||||
|
|
||||||
- name: Use Node.js from nvmrc
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version-file: ".nvmrc"
|
|
||||||
registry-url: "https://registry.npmjs.org"
|
|
||||||
|
|
||||||
- name: Set up Go for desktop build
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ^1.23.x
|
|
||||||
cache-dependency-path: desktop/go.sum
|
|
||||||
id: go
|
|
||||||
|
|
||||||
- name: Get version
|
|
||||||
id: package-version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.3.1
|
|
||||||
|
|
||||||
- name: Install
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
npm run build
|
|
||||||
npm run build-desktop
|
|
||||||
|
|
||||||
- name: Tag commit and push
|
|
||||||
id: tag_version
|
|
||||||
uses: mathieudutour/github-tag-action@v6.2
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
custom_tag: ${{ steps.package-version.outputs.current-version }}
|
|
||||||
|
|
||||||
- name: Create Archives
|
|
||||||
run: |
|
|
||||||
zip -r dist dist
|
|
||||||
zip -r desktop desktop/bin/
|
|
||||||
|
|
||||||
- name: Build Release Notes
|
|
||||||
id: release_notes
|
|
||||||
run: |
|
|
||||||
RELEASE_NOTES_PATH="${PWD}/release_notes.txt"
|
|
||||||
./build/release-notes.js > ${RELEASE_NOTES_PATH}
|
|
||||||
echo "release_notes=${RELEASE_NOTES_PATH}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
|
||||||
id: create_regular_release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
tag: ${{ steps.tag_version.outputs.new_tag }}
|
|
||||||
name: ${{ steps.tag_version.outputs.new_tag }}
|
|
||||||
bodyFile: ${{ steps.release_notes.outputs.release_notes }}
|
|
||||||
artifacts: "dist.zip,desktop.zip"
|
|
||||||
allowUpdates: true
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
|
|
||||||
+1
-1
@@ -33,6 +33,6 @@ node_modules
|
|||||||
public
|
public
|
||||||
/errorShots
|
/errorShots
|
||||||
/old
|
/old
|
||||||
|
/build
|
||||||
/cypress/screenshots
|
/cypress/screenshots
|
||||||
/dist/
|
/dist/
|
||||||
/desktop/version.go
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
## main
|
|
||||||
|
|
||||||
### ✨ Features and improvements
|
|
||||||
- _...Add new stuff here..._
|
|
||||||
|
|
||||||
### 🐞 Bug fixes
|
|
||||||
- _...Add new stuff here..._
|
|
||||||
|
|
||||||
## 2.1.0
|
|
||||||
|
|
||||||
### ✨ Features and improvements
|
|
||||||
|
|
||||||
- Add GitHub workflows for releasing new versions
|
|
||||||
- Update desktop build to pull from this repo (#922)
|
|
||||||
|
|
||||||
## 2.0.0
|
|
||||||
|
|
||||||
- Update MapLibre to version 4 (#872)
|
|
||||||
- Start continuous deployment of maputnik website
|
|
||||||
|
|
||||||
## 1.7.0
|
|
||||||
|
|
||||||
- See release notes at https://maputnik.github.io/blog/2020/04/23/release-v1.7.0
|
|
||||||
|
|
||||||
+2
-2
@@ -2,8 +2,8 @@ FROM node:18 as builder
|
|||||||
WORKDIR /maputnik
|
WORKDIR /maputnik
|
||||||
|
|
||||||
# Only copy package.json to prevent npm install from running on every build
|
# Only copy package.json to prevent npm install from running on every build
|
||||||
COPY package.json package-lock.json .npmrc ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci
|
RUN npm install
|
||||||
|
|
||||||
# Build maputnik
|
# Build maputnik
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ targeted at developers and map designers.
|
|||||||
- In a Docker, run this command and browse to http://localhost:8888, Ctrl+C to stop the server.
|
- In a Docker, run this command and browse to http://localhost:8888, Ctrl+C to stop the server.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -it --rm -p 8888:80 ghcr.io/maplibre/maputnik:main
|
docker run -it --rm -p 8888:8888 ghcr.io/maplibre/maputnik:main
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
@@ -66,8 +66,7 @@ Lint the JavaScript code.
|
|||||||
```
|
```
|
||||||
# run linter
|
# run linter
|
||||||
npm run lint
|
npm run lint
|
||||||
npm run lint-css
|
npm run lint-styles
|
||||||
npm run sort-styles
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -94,15 +93,10 @@ You can also see the tests as they run or select which suites to run by executin
|
|||||||
npm run cy:open
|
npm run cy:open
|
||||||
```
|
```
|
||||||
|
|
||||||
## Release process
|
|
||||||
|
|
||||||
1. Review [`CHANGELOG.md`](/CHANGELOG.md)
|
## Related Projects
|
||||||
- Double-check that all changes included in the release are appropriately documented.
|
|
||||||
- To-be-released changes should be under the "main" header.
|
|
||||||
- Commit any final changes to the changelog.
|
|
||||||
2. Run [Create bump version PR](https://github.com/maplibre/maputnik/actions/workflows/create-bump-version-pr.yml) by manual workflow dispatch and set the version number in the input. This will create a PR that changes the changelog and `package.json` file to review and merge.
|
|
||||||
3. Once merged, an automatic process will kick in and creates a GitHub release and uploads release assets.
|
|
||||||
|
|
||||||
|
- [maputnik-dev-server](https://github.com/nycplanning/labs-maputnik-dev-server) - An express.js server that allows for quickly loading the style from any mapboxGL map into mapuntnik.
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
# Build Scripts
|
|
||||||
|
|
||||||
This folder holds common build scripts used by some of the Github workflows.
|
|
||||||
|
|
||||||
The scripts are borrowed from [maplibre/maplibre-gl-js](https://github.com/maplibre/maplibre-gl-js/tree/bc70bc559cea5c987fa1b79fd44766cef68bbe28/build).
|
|
||||||
|
|
||||||
## Generate Release Notes
|
|
||||||
|
|
||||||
`bump-version-changelog.js` Used to update the changelog with the current notes, and set up a space for new notes
|
|
||||||
|
|
||||||
`release-notes.js` Used to generate release notes when releasing a new version
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This script updates the changelog.md file with the version given in the arguments
|
|
||||||
* It replaces ## main with ## <version>
|
|
||||||
* Removes _...Add new stuff here..._
|
|
||||||
* And adds on top a ## main with add stuff here.
|
|
||||||
*
|
|
||||||
* Copied from maplibre/maplibre-gl-js
|
|
||||||
* https://github.com/maplibre/maplibre-gl-js/blob/bc70bc559cea5c987fa1b79fd44766cef68bbe28/build/release-notes.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
const changelogPath = 'CHANGELOG.md';
|
|
||||||
let changelog = fs.readFileSync(changelogPath, 'utf8');
|
|
||||||
changelog = changelog.replace('## main', `## ${process.argv[2]}`);
|
|
||||||
changelog = changelog.replaceAll('- _...Add new stuff here..._\n', '');
|
|
||||||
changelog = `## main
|
|
||||||
|
|
||||||
### ✨ Features and improvements
|
|
||||||
- _...Add new stuff here..._
|
|
||||||
|
|
||||||
### 🐞 Bug fixes
|
|
||||||
- _...Add new stuff here..._
|
|
||||||
|
|
||||||
` + changelog;
|
|
||||||
|
|
||||||
fs.writeFileSync(changelogPath, changelog, 'utf8');
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
// Copied from maplibre/maplibre-gl-js
|
|
||||||
// https://github.com/maplibre/maplibre-gl-js/blob/bc70bc559cea5c987fa1b79fd44766cef68bbe28/build/release-notes.js
|
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
const changelogPath = 'CHANGELOG.md';
|
|
||||||
const changelog = fs.readFileSync(changelogPath, 'utf8');
|
|
||||||
|
|
||||||
/*
|
|
||||||
Parse the raw changelog text and split it into individual releases.
|
|
||||||
|
|
||||||
This regular expression:
|
|
||||||
- Matches lines starting with "## x.x.x".
|
|
||||||
- Groups the version number.
|
|
||||||
- Skips the (optional) release date.
|
|
||||||
- Groups the changelog content.
|
|
||||||
- Ends when another "## x.x.x" is found.
|
|
||||||
*/
|
|
||||||
const regex = /^## (\d+\.\d+\.\d+.*?)\n(.+?)(?=\n^## \d+\.\d+\.\d+.*?\n)/gms;
|
|
||||||
|
|
||||||
let releaseNotes = [];
|
|
||||||
let match;
|
|
||||||
// eslint-disable-next-line no-cond-assign
|
|
||||||
while (match = regex.exec(changelog)) {
|
|
||||||
releaseNotes.push({
|
|
||||||
'version': match[1],
|
|
||||||
'changelog': match[2].trim(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const latest = releaseNotes[0];
|
|
||||||
const previous = releaseNotes[1];
|
|
||||||
|
|
||||||
// Print the release notes template.
|
|
||||||
|
|
||||||
let header = 'Changes since previous version'
|
|
||||||
if (previous) {
|
|
||||||
header = `https://github.com/maplibre/maputnik
|
|
||||||
[Changes](https://github.com/maplibre/maputnik/compare/v${previous.version}...v${latest.version}) since [Maputnik v${previous.version}](https://github.com/maplibre/maputnik/releases/tag/v${previous.version})`
|
|
||||||
}
|
|
||||||
const templatedReleaseNotes = `${header}
|
|
||||||
|
|
||||||
${latest.changelog}
|
|
||||||
|
|
||||||
// eslint-disable-next-line eol-last
|
|
||||||
process.stdout.write(templatedReleaseNotes.trimEnd());
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { MaputnikDriver } from "./maputnik-driver";
|
|
||||||
|
|
||||||
describe("i18n", () => {
|
|
||||||
let { 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("スタイル設定");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -280,25 +280,6 @@ describe("layers", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("opacity", () => {
|
|
||||||
let bgId: string;
|
|
||||||
beforeEach(() => {
|
|
||||||
bgId = createBackground();
|
|
||||||
when.click("layer-list-item:background:" + bgId);
|
|
||||||
when.type("spec-field-input:background-opacity", "0.");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should keep '.' in the input field", () => {
|
|
||||||
then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0.");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should revert to a valid value when focus out", () => {
|
|
||||||
when.click("layer-list-item:background:" + bgId);
|
|
||||||
then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue('0');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("filter", () => {
|
describe("filter", () => {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/// <reference types="cypress-plugin-tab" />
|
|
||||||
|
|
||||||
import { CypressHelper } from "@shellygo/cypress-test-utils";
|
import { CypressHelper } from "@shellygo/cypress-test-utils";
|
||||||
import { Assertable, then } from "@shellygo/cypress-test-utils/assertable";
|
import { Assertable, then } from "@shellygo/cypress-test-utils/assertable";
|
||||||
import MaputnikCypressHelper from "./maputnik-cypress-helper";
|
import MaputnikCypressHelper from "./maputnik-cypress-helper";
|
||||||
@@ -16,7 +14,7 @@ const styleFromWindow = (win: Window) => {
|
|||||||
export class MaputnikAssertable<T> extends Assertable<T> {
|
export class MaputnikAssertable<T> extends Assertable<T> {
|
||||||
shouldEqualToStoredStyle = () =>
|
shouldEqualToStoredStyle = () =>
|
||||||
then(
|
then(
|
||||||
new CypressHelper().get.window().then((win: Window) => {
|
new CypressHelper().get.window().then((win) => {
|
||||||
const style = styleFromWindow(win);
|
const style = styleFromWindow(win);
|
||||||
then(this.chainable).shouldDeepNestedInclude(style);
|
then(this.chainable).shouldDeepNestedInclude(style);
|
||||||
})
|
})
|
||||||
@@ -131,9 +129,7 @@ export class MaputnikDriver {
|
|||||||
this.helper.when.acceptConfirm();
|
this.helper.when.acceptConfirm();
|
||||||
}
|
}
|
||||||
// when methods should not include assertions
|
// when methods should not include assertions
|
||||||
const toolbarLink = this.helper.get.elementByTestId("toolbar:link")
|
this.helper.get.elementByTestId("toolbar:link").should("be.visible");
|
||||||
toolbarLink.scrollIntoView();
|
|
||||||
toolbarLink.should("be.visible");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
typeKeys: (keys: string) => this.helper.get.element("body").type(keys),
|
typeKeys: (keys: string) => this.helper.get.element("body").type(keys),
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ describe("modals", () => {
|
|||||||
);
|
);
|
||||||
when.click("modal:settings.name");
|
when.click("modal:settings.name");
|
||||||
then(
|
then(
|
||||||
get.styleFromLocalStorage().then((style) => style.metadata)
|
get.styleFromLocalStorage().pipe((style) => style.metadata)
|
||||||
).shouldInclude({
|
).shouldInclude({
|
||||||
"maputnik:openmaptiles_access_token": apiKey,
|
"maputnik:openmaptiles_access_token": apiKey,
|
||||||
});
|
});
|
||||||
@@ -156,7 +156,7 @@ describe("modals", () => {
|
|||||||
);
|
);
|
||||||
when.click("modal:settings.name");
|
when.click("modal:settings.name");
|
||||||
then(
|
then(
|
||||||
get.styleFromLocalStorage().then((style) => style.metadata)
|
get.styleFromLocalStorage().pipe((style) => style.metadata)
|
||||||
).shouldInclude({ "maputnik:thunderforest_access_token": apiKey });
|
).shouldInclude({ "maputnik:thunderforest_access_token": apiKey });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
editor
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
||||||
*.prof
|
|
||||||
|
|
||||||
# Binary version of pubilic/editor
|
|
||||||
rice-box.go
|
|
||||||
|
|
||||||
# Built binary
|
|
||||||
maputnik
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016 Maputnik
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
SOURCEDIR=.
|
|
||||||
SOURCES := $(shell find $(SOURCEDIR) -name '*.go')
|
|
||||||
BINARY=maputnik
|
|
||||||
DESKTOP_VERSION := 1.1.1
|
|
||||||
EDITOR_VERSION := $(shell node -p "require('../package.json').version")
|
|
||||||
GOPATH := $(if $(GOPATH),$(GOPATH),$(HOME)/go)
|
|
||||||
GOBIN := $(if $(GOBIN),$(GOBIN),$(HOME)/go/bin)
|
|
||||||
|
|
||||||
all: $(BINARY)
|
|
||||||
|
|
||||||
$(BINARY): $(GOBIN)/gox $(SOURCES) version.go rice-box.go
|
|
||||||
$(GOBIN)/gox -osarch "windows/amd64 linux/amd64 darwin/amd64" -output "bin/{{.OS}}/${BINARY}"
|
|
||||||
|
|
||||||
# Copy the current release into ./editor/maputnik so it can be
|
|
||||||
# embedded in the binary
|
|
||||||
editor/pull_release:
|
|
||||||
mkdir -p editor
|
|
||||||
cp -r ../dist/* editor
|
|
||||||
|
|
||||||
$(GOBIN)/gox:
|
|
||||||
go install github.com/mitchellh/gox@v1.0.1
|
|
||||||
|
|
||||||
$(GOBIN)/rice:
|
|
||||||
go install github.com/GeertJohan/go.rice/rice@v1.0.3
|
|
||||||
|
|
||||||
# Embed the current version numbers in the executable by writing version.go
|
|
||||||
.PHONY: version.go
|
|
||||||
version.go:
|
|
||||||
@echo "// DO NOT EDIT: Autogenerated by Makefile\n" > version.go
|
|
||||||
@echo "package main\n" >> version.go
|
|
||||||
@echo "const DesktopVersion = \"$(DESKTOP_VERSION)\"" >> version.go
|
|
||||||
@echo "const EditorVersion = \"$(EDITOR_VERSION)\"" >> version.go
|
|
||||||
|
|
||||||
rice-box.go: $(GOBIN)/rice editor/pull_release
|
|
||||||
$(GOBIN)/rice embed-go
|
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean:
|
|
||||||
rm -rf editor && rm -f rice-box.go && rm -rf bin
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# Maputnik Desktop [][github-action-ci]
|
|
||||||
---
|
|
||||||
|
|
||||||
A Golang based cross platform executable for integrating Maputnik locally.
|
|
||||||
This binary packages up the JavaScript and CSS bundle produced by maputnik
|
|
||||||
and embeds it in the program for easy distribution. It also allows
|
|
||||||
exposing a local style file and work on it both in Maputnik and with your favorite
|
|
||||||
editor.
|
|
||||||
|
|
||||||
Report issues on [maplibre/maputnik](https://github.com/maplibre/maputnik).
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
You can download a single binary for Linux, OSX or Windows from [the latest releases of **maplibre/maputnik**](https://github.com/maplibre/maputnik/editor/releases/latest).
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Simply start up a web server and access the Maputnik editor GUI at `localhost:8000`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
maputnik
|
|
||||||
```
|
|
||||||
|
|
||||||
Expose a local style file to Maputnik allowing the web based editor
|
|
||||||
to save to the local filesystem.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
maputnik --file basic-v9.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Watch the local style for changes and inform the editor via web socket.
|
|
||||||
This makes it possible to edit the style with a local text editor and still
|
|
||||||
use Maputnik.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
maputnik --watch --file basic-v9.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Choose a local port to listen on, instead of using the default port 8000.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
maputnik --port 8001
|
|
||||||
```
|
|
||||||
|
|
||||||
Specify a path to a directory which, if it exists, will be served under http://localhost:8000/static/ .
|
|
||||||
Could be used to serve sprites and glyphs.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
maputnik --static ./localFolder
|
|
||||||
```
|
|
||||||
|
|
||||||
### API
|
|
||||||
|
|
||||||
`maputnik` exposes the configured styles via a HTTP API.
|
|
||||||
|
|
||||||
| Method | Description
|
|
||||||
|---------------------------------|---------------------------------------
|
|
||||||
| `GET /styles` | List the ID of all configured style files
|
|
||||||
| `GET /styles/{filename}` | Get contents of a single style file
|
|
||||||
| `PUT /styles/{filename}` | Update contents of a style file
|
|
||||||
| `WEBSOCKET /ws` | Listen to change events for the configured style files
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
From the root of the [maplibre/maputnik](https://github.com/maplibre/maputnik) project, install the deps and run the desktop-build command.
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install
|
|
||||||
npm run build-desktop
|
|
||||||
```
|
|
||||||
|
|
||||||
You should now find the `maputnik` binary in your `bin` directory.
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StyleFileAccessor(filename string) styleFileAccessor {
|
|
||||||
return styleFileAccessor{filename, styleId(filename)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func styleId(filename string) string {
|
|
||||||
raw, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var spec styleSpec
|
|
||||||
err = json.Unmarshal(raw, &spec)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if spec.Id == "" {
|
|
||||||
fmt.Println("No id in style")
|
|
||||||
}
|
|
||||||
return spec.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
type styleSpec struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allows access to a single style file
|
|
||||||
type styleFileAccessor struct {
|
|
||||||
filename string
|
|
||||||
id string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fa styleFileAccessor) ListFiles(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
encoder := json.NewEncoder(w)
|
|
||||||
encoder.Encode([]string{fa.id})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fa styleFileAccessor) ReadFile(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
_ = vars["styleId"]
|
|
||||||
|
|
||||||
//TODO: Choose right file
|
|
||||||
// right now we just return the single file we know of
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
raw, err := ioutil.ReadFile(fa.filename)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
w.Write(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fa styleFileAccessor) SaveFile(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
_ = vars["styleId"]
|
|
||||||
|
|
||||||
//TODO: Save to right file
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
body, _ := ioutil.ReadAll(r.Body)
|
|
||||||
var out bytes.Buffer
|
|
||||||
json.Indent(&out, body, "", " ")
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile(fa.filename, out.Bytes(), 0666); err != nil {
|
|
||||||
log.Fatalf("Can not copy from request to file: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package filewatch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
CheckOrigin: func(r *http.Request) bool { return true },
|
|
||||||
}
|
|
||||||
|
|
||||||
func writer(ws *websocket.Conn, filename string) {
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer watcher.Close()
|
|
||||||
|
|
||||||
done := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <-watcher.Events:
|
|
||||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
|
||||||
log.Println("Modified file:", event.Name)
|
|
||||||
var p []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
p, err = ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p != nil {
|
|
||||||
if err := ws.WriteMessage(websocket.TextMessage, p); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case err := <-watcher.Errors:
|
|
||||||
log.Println("Watch error:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = watcher.Add(filename); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
<-done
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServeWebsocketFileWatcher(filename string, w http.ResponseWriter, r *http.Request) {
|
|
||||||
ws, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(websocket.HandshakeError); !ok {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writer(ws, filename)
|
|
||||||
defer ws.Close()
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
module maputnik/desktop
|
|
||||||
|
|
||||||
go 1.19
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/GeertJohan/go.rice v1.0.3
|
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
|
||||||
github.com/gorilla/handlers v1.5.1
|
|
||||||
github.com/gorilla/mux v1.8.0
|
|
||||||
github.com/gorilla/websocket v1.5.0
|
|
||||||
github.com/maputnik/desktop v1.0.7
|
|
||||||
github.com/urfave/cli v1.22.12
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/GeertJohan/go.incremental v1.0.0 // indirect
|
|
||||||
github.com/akavel/rsrc v0.8.0 // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
|
||||||
github.com/daaku/go.zipexe v1.0.2 // indirect
|
|
||||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
|
||||||
github.com/jessevdk/go-flags v1.4.0 // indirect
|
|
||||||
github.com/nkovacs/streamquote v1.0.0 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
|
||||||
github.com/valyala/fasttemplate v1.0.1 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
|
|
||||||
)
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
|
||||||
github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg=
|
|
||||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
|
||||||
github.com/GeertJohan/go.rice v1.0.3 h1:k5viR+xGtIhF61125vCE1cmJ5957RQGXG6dmbaWZSmI=
|
|
||||||
github.com/GeertJohan/go.rice v1.0.3/go.mod h1:XVdrU4pW00M4ikZed5q56tPf1v2KwnIKeIdc9CBYNt4=
|
|
||||||
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
|
|
||||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/daaku/go.zipexe v1.0.2 h1:Zg55YLYTr7M9wjKn8SY/WcpuuEi+kR2u4E8RhvpyXmk=
|
|
||||||
github.com/daaku/go.zipexe v1.0.2/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
|
||||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
|
||||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
|
||||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
|
||||||
github.com/maputnik/desktop v1.0.7 h1:rdFg7emIJOT3YsZpwqSChmWtMOvu+T4h6WwVQAZP9n4=
|
|
||||||
github.com/maputnik/desktop v1.0.7/go.mod h1:wmDjHUztx9jOBz0I22589yWguAGdV/sEM57YANpN8oQ=
|
|
||||||
github.com/nkovacs/streamquote v1.0.0 h1:PmVIV08Zlx2lZK5fFZlMZ04eHcDTIFJCv/5/0twVUow=
|
|
||||||
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8=
|
|
||||||
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
|
||||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
|
||||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
|
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
"github.com/gorilla/handlers"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/maputnik/desktop/filewatch"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
app := cli.NewApp()
|
|
||||||
app.Name = "maputnik"
|
|
||||||
app.Usage = "Server for integrating Maputnik locally"
|
|
||||||
app.Version = "Editor: " + EditorVersion + "; Desktop: " + DesktopVersion
|
|
||||||
|
|
||||||
app.Flags = []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "file, f",
|
|
||||||
Usage: "Allow access to JSON style from web client",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "watch",
|
|
||||||
Usage: "Notify web client about JSON style file changes",
|
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "port",
|
|
||||||
Value: 8000,
|
|
||||||
Usage: "TCP port to listen on",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "static",
|
|
||||||
Usage: "Serve directory under /static/",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Action = func(c *cli.Context) error {
|
|
||||||
gui := http.FileServer(rice.MustFindBox("editor").HTTPBox())
|
|
||||||
|
|
||||||
router := mux.NewRouter().StrictSlash(true)
|
|
||||||
|
|
||||||
filename := c.String("file")
|
|
||||||
if filename != "" {
|
|
||||||
fmt.Printf("%s is accessible via Maputnik\n", filename)
|
|
||||||
// Allow access to reading and writing file on the local system
|
|
||||||
path, _ := filepath.Abs(filename)
|
|
||||||
accessor := StyleFileAccessor(path)
|
|
||||||
router.Path("/styles").Methods("GET").HandlerFunc(accessor.ListFiles)
|
|
||||||
router.Path("/styles/{styleId}").Methods("GET").HandlerFunc(accessor.ReadFile)
|
|
||||||
router.Path("/styles/{styleId}").Methods("PUT").HandlerFunc(accessor.SaveFile)
|
|
||||||
|
|
||||||
// Register websocket to notify we clients about file changes
|
|
||||||
if c.Bool("watch") {
|
|
||||||
router.Path("/ws").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
filewatch.ServeWebsocketFileWatcher(filename, w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
staticDir := c.String("static")
|
|
||||||
if staticDir != "" {
|
|
||||||
h := http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))
|
|
||||||
router.PathPrefix("/static/").Handler(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
router.PathPrefix("/").Handler(http.StripPrefix("/", gui))
|
|
||||||
loggedRouter := handlers.LoggingHandler(os.Stdout, router)
|
|
||||||
corsRouter := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}), handlers.AllowedMethods([]string{"GET", "PUT"}), handlers.AllowedOrigins([]string{"*"}), handlers.AllowCredentials())(loggedRouter)
|
|
||||||
|
|
||||||
fmt.Printf("Exposing Maputnik on http://localhost:%d\n", c.Int("port"))
|
|
||||||
return http.ListenAndServe(fmt.Sprintf(":%d", c.Int("port")), corsRouter)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Run(os.Args)
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export default {
|
|
||||||
output: 'src/locales/$LOCALE/$NAMESPACE.json',
|
|
||||||
locales: [ 'ja', 'he','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__';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
+6359
-3596
File diff suppressed because it is too large
Load Diff
+47
-58
@@ -1,19 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "maputnik",
|
"name": "maputnik",
|
||||||
"version": "2.1.0",
|
"version": "2.0.0",
|
||||||
"description": "A MapLibre GL visual style editor",
|
"description": "A MapLibre GL visual style editor",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "''",
|
"main": "''",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "tsc && vite build --base=/maputnik/",
|
"build": "tsc && vite build --base=/maputnik/",
|
||||||
"build-desktop": "tsc && vite build --base=/ && cd desktop && make",
|
|
||||||
"i18n:refresh": "i18next 'src/**/*.{ts,tsx,js,jsx}'",
|
|
||||||
"lint": "eslint ./src ./cypress --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint ./src ./cypress --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"test": "cypress run",
|
"test": "cypress run",
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"lint-css": "stylelint \"src/styles/*.scss\"",
|
"lint-css": "stylelint \"src/styles/*.scss\""
|
||||||
"sort-styles": "jq 'sort_by(.id)' src/config/styles.json > tmp.json && mv tmp.json src/config/styles.json"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -24,26 +21,20 @@
|
|||||||
"homepage": "https://github.com/maplibre/maputnik#readme",
|
"homepage": "https://github.com/maplibre/maputnik#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",
|
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",
|
||||||
"@maplibre/maplibre-gl-geocoder": "^1.6.0",
|
"@maplibre/maplibre-gl-geocoder": "^1.5.0",
|
||||||
"@maplibre/maplibre-gl-inspect": "^1.6.3",
|
"@maplibre/maplibre-gl-style-spec": "^20.1.0",
|
||||||
"@maplibre/maplibre-gl-style-spec": "^20.1.1",
|
"@mdi/js": "^6.6.96",
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/react": "^1.5.0",
|
||||||
"@mdi/react": "^1.6.1",
|
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
"@typescript-eslint/parser": "^6.16.0",
|
||||||
"@typescript-eslint/parser": "^7.3.1",
|
|
||||||
"array-move": "^4.0.0",
|
"array-move": "^4.0.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.3.1",
|
||||||
"codemirror": "^5.65.2",
|
"codemirror": "^5.65.2",
|
||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
"cypress-plugin-tab": "^1.0.5",
|
|
||||||
"detect-browser": "^5.3.0",
|
"detect-browser": "^5.3.0",
|
||||||
"events": "^3.3.0",
|
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"i18next": "^23.12.2",
|
"json-stringify-pretty-compact": "^3.0.0",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
|
||||||
"i18next-resources-to-backend": "^1.2.1",
|
|
||||||
"json-stringify-pretty-compact": "^4.0.0",
|
|
||||||
"json-to-ast": "^2.1.0",
|
"json-to-ast": "^2.1.0",
|
||||||
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@@ -53,30 +44,30 @@
|
|||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"maplibre-gl": "^4.1.2",
|
"mapbox-gl-inspect": "^1.3.1",
|
||||||
|
"maplibre-gl": "^4.0.0",
|
||||||
"maputnik-design": "github:maputnik/design#172b06c",
|
"maputnik-design": "github:maputnik/design#172b06c",
|
||||||
"ol": "^6.14.1",
|
"ol": "^6.14.1",
|
||||||
"ol-mapbox-style": "^7.1.1",
|
"ol-mapbox-style": "^7.1.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^16.0.0",
|
||||||
"react-accessible-accordion": "^5.0.0",
|
"react-accessible-accordion": "^4.0.0",
|
||||||
"react-aria-menubutton": "^7.0.3",
|
"react-aria-menubutton": "^7.0.3",
|
||||||
"react-aria-modal": "^5.0.2",
|
"react-aria-modal": "^4.0.1",
|
||||||
"react-autobind": "^1.0.6",
|
"react-autobind": "^1.0.6",
|
||||||
"react-autocomplete": "^1.8.1",
|
"react-autocomplete": "^1.8.1",
|
||||||
"react-collapse": "^5.1.1",
|
"react-collapse": "^5.1.1",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^16.0.0",
|
||||||
"react-file-reader-input": "^2.0.0",
|
"react-file-reader-input": "^2.0.0",
|
||||||
"react-i18next": "^15.0.1",
|
|
||||||
"react-icon-base": "^2.1.2",
|
"react-icon-base": "^2.1.2",
|
||||||
"react-icons": "^5.0.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-sortable-hoc": "^2.0.0",
|
"react-sortable-hoc": "^2.0.0",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"sass": "^1.72.0",
|
"sass": "^1.50.0",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.5",
|
||||||
"string-hash": "^1.1.3",
|
"string-hash": "^1.1.3",
|
||||||
"url": "^0.11.3"
|
"url": "^0.11.0"
|
||||||
},
|
},
|
||||||
"jshintConfig": {
|
"jshintConfig": {
|
||||||
"esversion": 6
|
"esversion": 6
|
||||||
@@ -96,15 +87,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cypress/code-coverage": "^3.12.30",
|
"@cypress/code-coverage": "^3.12.15",
|
||||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||||
"@rollup/plugin-replace": "^5.0.5",
|
"@rollup/plugin-replace": "^5.0.5",
|
||||||
"@shellygo/cypress-test-utils": "^2.1.9",
|
"@shellygo/cypress-test-utils": "^2.0.17",
|
||||||
"@types/codemirror": "^5.60.15",
|
"@types/codemirror": "^5.60.15",
|
||||||
"@types/color": "^3.0.6",
|
"@types/color": "^3.0.6",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/geojson": "^7946.0.14",
|
"@types/geojson": "^7946.0.13",
|
||||||
"@types/json-to-ast": "^2.1.4",
|
"@types/json-to-ast": "^2.1.4",
|
||||||
"@types/lodash.capitalize": "^4.2.9",
|
"@types/lodash.capitalize": "^4.2.9",
|
||||||
"@types/lodash.clamp": "^4.0.9",
|
"@types/lodash.clamp": "^4.0.9",
|
||||||
@@ -112,38 +103,36 @@
|
|||||||
"@types/lodash.get": "^4.4.9",
|
"@types/lodash.get": "^4.4.9",
|
||||||
"@types/lodash.isequal": "^4.5.8",
|
"@types/lodash.isequal": "^4.5.8",
|
||||||
"@types/lodash.throttle": "^4.1.9",
|
"@types/lodash.throttle": "^4.1.9",
|
||||||
"@types/mocha": "^10.0.6",
|
"@types/react": "^16.14.52",
|
||||||
"@types/randomcolor": "^0.5.9",
|
"@types/react-aria-menubutton": "^6.2.13",
|
||||||
"@types/react": "^18.2.67",
|
"@types/react-aria-modal": "^4.0.9",
|
||||||
"@types/react-aria-menubutton": "^6.2.14",
|
"@types/react-autocomplete": "^1.8.9",
|
||||||
"@types/react-aria-modal": "^4.0.10",
|
|
||||||
"@types/react-autocomplete": "^1.8.10",
|
|
||||||
"@types/react-collapse": "^5.0.4",
|
"@types/react-collapse": "^5.0.4",
|
||||||
"@types/react-color": "^3.0.12",
|
"@types/react-color": "^3.0.10",
|
||||||
"@types/react-dom": "^18.2.22",
|
"@types/react-dom": "^16.9.24",
|
||||||
"@types/react-file-reader-input": "^2.0.4",
|
"@types/react-file-reader-input": "^2.0.4",
|
||||||
"@types/react-icon-base": "^2.1.6",
|
"@types/react-icon-base": "^2.1.6",
|
||||||
"@types/string-hash": "^1.1.3",
|
"@types/string-hash": "^1.1.3",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.7",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cypress": "^13.13.0",
|
"cypress": "^13.6.1",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-plugin-react": "^7.34.1",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.6",
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
"i18next-parser": "^9.0.1",
|
"express": "^4.17.3",
|
||||||
"istanbul": "^0.4.5",
|
"istanbul": "^0.4.5",
|
||||||
"istanbul-lib-coverage": "^3.2.2",
|
"istanbul-lib-coverage": "^3.2.0",
|
||||||
"mocha": "^10.3.0",
|
"mocha": "^9.2.2",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.12",
|
||||||
"react-hot-loader": "^4.13.1",
|
"react-hot-loader": "^4.13.0",
|
||||||
"stylelint": "^16.2.1",
|
"stylelint": "^14.6.1",
|
||||||
"stylelint-config-recommended-scss": "^14.0.0",
|
"stylelint-config-recommended-scss": "^6.0.0",
|
||||||
"stylelint-scss": "^6.2.1",
|
"stylelint-scss": "^4.2.0",
|
||||||
"typescript": "^5.4.3",
|
"typescript": "^5.3.3",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^8.3.2",
|
||||||
"vite": "^5.2.6",
|
"vite": "^5.0.12",
|
||||||
"vite-plugin-istanbul": "^6.0.0"
|
"vite-plugin-istanbul": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import ModalExport from './ModalExport'
|
|||||||
import ModalSources from './ModalSources'
|
import ModalSources from './ModalSources'
|
||||||
import ModalOpen from './ModalOpen'
|
import ModalOpen from './ModalOpen'
|
||||||
import ModalShortcuts from './ModalShortcuts'
|
import ModalShortcuts from './ModalShortcuts'
|
||||||
|
import ModalSurvey from './ModalSurvey'
|
||||||
import ModalDebug from './ModalDebug'
|
import ModalDebug from './ModalDebug'
|
||||||
|
|
||||||
import {downloadGlyphsMetadata, downloadSpriteMetadata} from '../libs/metadata'
|
import {downloadGlyphsMetadata, downloadSpriteMetadata} from '../libs/metadata'
|
||||||
@@ -127,6 +128,7 @@ type AppState = {
|
|||||||
open: boolean
|
open: boolean
|
||||||
shortcuts: boolean
|
shortcuts: boolean
|
||||||
export: boolean
|
export: boolean
|
||||||
|
survey: boolean
|
||||||
debug: boolean
|
debug: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,6 +137,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
revisionStore: RevisionStore;
|
revisionStore: RevisionStore;
|
||||||
styleStore: StyleStore | ApiStyleStore;
|
styleStore: StyleStore | ApiStyleStore;
|
||||||
layerWatcher: LayerWatcher;
|
layerWatcher: LayerWatcher;
|
||||||
|
shortcutEl: ModalShortcuts | null = null;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -274,6 +277,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
shortcuts: false,
|
shortcuts: false,
|
||||||
export: false,
|
export: false,
|
||||||
// TODO: Disabled for now, this should be opened on the Nth visit to the editor
|
// TODO: Disabled for now, this should be opened on the Nth visit to the editor
|
||||||
|
survey: false,
|
||||||
debug: false,
|
debug: false,
|
||||||
},
|
},
|
||||||
maplibreGlDebugOptions: {
|
maplibreGlDebugOptions: {
|
||||||
@@ -835,6 +839,10 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setModal(modalName: keyof AppState["isOpen"], value: boolean) {
|
setModal(modalName: keyof AppState["isOpen"], value: boolean) {
|
||||||
|
if(modalName === 'survey' && value === false) {
|
||||||
|
localStorage.setItem('survey', '');
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isOpen: {
|
isOpen: {
|
||||||
...this.state.isOpen,
|
...this.state.isOpen,
|
||||||
@@ -934,6 +942,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
mapView={this.state.mapView}
|
mapView={this.state.mapView}
|
||||||
/>
|
/>
|
||||||
<ModalShortcuts
|
<ModalShortcuts
|
||||||
|
ref={(el) => this.shortcutEl = el}
|
||||||
isOpen={this.state.isOpen.shortcuts}
|
isOpen={this.state.isOpen.shortcuts}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'shortcuts')}
|
onOpenToggle={this.toggleModal.bind(this, 'shortcuts')}
|
||||||
/>
|
/>
|
||||||
@@ -961,6 +970,10 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
isOpen={this.state.isOpen.sources}
|
isOpen={this.state.isOpen.sources}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'sources')}
|
onOpenToggle={this.toggleModal.bind(this, 'sources')}
|
||||||
/>
|
/>
|
||||||
|
<ModalSurvey
|
||||||
|
isOpen={this.state.isOpen.survey}
|
||||||
|
onOpenToggle={this.toggleModal.bind(this, 'survey')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
return <AppLayout
|
return <AppLayout
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ScrollContainer from './ScrollContainer'
|
import ScrollContainer from './ScrollContainer'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type AppLayoutInternalProps = {
|
type AppLayoutProps = {
|
||||||
toolbar: React.ReactElement
|
toolbar: React.ReactElement
|
||||||
layerList: React.ReactElement
|
layerList: React.ReactElement
|
||||||
layerEditor?: React.ReactElement
|
layerEditor?: React.ReactElement
|
||||||
map: React.ReactElement
|
map: React.ReactElement
|
||||||
bottom?: React.ReactElement
|
bottom?: React.ReactElement
|
||||||
modals?: React.ReactNode
|
modals?: React.ReactNode
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class AppLayoutInternal extends React.Component<AppLayoutInternalProps> {
|
class AppLayout extends React.Component<AppLayoutProps> {
|
||||||
static childContextTypes = {
|
static childContextTypes = {
|
||||||
reactIconBase: PropTypes.object
|
reactIconBase: PropTypes.object
|
||||||
}
|
}
|
||||||
@@ -24,21 +23,17 @@ class AppLayoutInternal extends React.Component<AppLayoutInternalProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
document.body.dir = this.props.i18n.dir();
|
|
||||||
|
|
||||||
return <div className="maputnik-layout">
|
return <div className="maputnik-layout">
|
||||||
{this.props.toolbar}
|
{this.props.toolbar}
|
||||||
<div className="maputnik-layout-main">
|
<div className="maputnik-layout-list">
|
||||||
<div className="maputnik-layout-list">
|
{this.props.layerList}
|
||||||
{this.props.layerList}
|
|
||||||
</div>
|
|
||||||
<div className="maputnik-layout-drawer">
|
|
||||||
<ScrollContainer>
|
|
||||||
{this.props.layerEditor}
|
|
||||||
</ScrollContainer>
|
|
||||||
</div>
|
|
||||||
{this.props.map}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="maputnik-layout-drawer">
|
||||||
|
<ScrollContainer>
|
||||||
|
{this.props.layerEditor}
|
||||||
|
</ScrollContainer>
|
||||||
|
</div>
|
||||||
|
{this.props.map}
|
||||||
{this.props.bottom && <div className="maputnik-layout-bottom">
|
{this.props.bottom && <div className="maputnik-layout-bottom">
|
||||||
{this.props.bottom}
|
{this.props.bottom}
|
||||||
</div>
|
</div>
|
||||||
@@ -48,5 +43,4 @@ class AppLayoutInternal extends React.Component<AppLayoutInternalProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppLayout = withTranslation()(AppLayoutInternal);
|
export default AppLayout
|
||||||
export default AppLayout;
|
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {formatLayerId} from '../libs/format';
|
import {formatLayerId} from '../libs/format';
|
||||||
import {LayerSpecification, StyleSpecification} from 'maplibre-gl';
|
import {LayerSpecification, StyleSpecification} from 'maplibre-gl';
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type AppMessagePanelInternalProps = {
|
type AppMessagePanelProps = {
|
||||||
errors?: unknown[]
|
errors?: unknown[]
|
||||||
infos?: string[]
|
infos?: unknown[]
|
||||||
mapStyle?: StyleSpecification
|
mapStyle?: StyleSpecification
|
||||||
onLayerSelect?(...args: unknown[]): unknown
|
onLayerSelect?(...args: unknown[]): unknown
|
||||||
currentLayer?: LayerSpecification
|
currentLayer?: LayerSpecification
|
||||||
selectedLayerIndex?: number
|
selectedLayerIndex?: number
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalProps> {
|
export default class AppMessagePanel extends React.Component<AppMessagePanelProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {t, selectedLayerIndex} = this.props;
|
const {selectedLayerIndex} = this.props;
|
||||||
const errors = this.props.errors?.map((error: any, idx) => {
|
const errors = this.props.errors?.map((error: any, idx) => {
|
||||||
let content;
|
let content;
|
||||||
if (error.parsed && error.parsed.type === "layer") {
|
if (error.parsed && error.parsed.type === "layer") {
|
||||||
@@ -26,9 +25,7 @@ class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalPro
|
|||||||
const layerId = this.props.mapStyle?.layers[parsed.data.index].id;
|
const layerId = this.props.mapStyle?.layers[parsed.data.index].id;
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
<Trans t={t}>
|
Layer <span>{formatLayerId(layerId)}</span>: {parsed.data.message}
|
||||||
Layer <span>{formatLayerId(layerId)}</span>: {parsed.data.message}
|
|
||||||
</Trans>
|
|
||||||
{selectedLayerIndex !== parsed.data.index &&
|
{selectedLayerIndex !== parsed.data.index &&
|
||||||
<>
|
<>
|
||||||
—
|
—
|
||||||
@@ -36,7 +33,7 @@ class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalPro
|
|||||||
className="maputnik-message-panel__switch-button"
|
className="maputnik-message-panel__switch-button"
|
||||||
onClick={() => this.props.onLayerSelect!(parsed.data.index)}
|
onClick={() => this.props.onLayerSelect!(parsed.data.index)}
|
||||||
>
|
>
|
||||||
{t("switch to layer")}
|
switch to layer
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@@ -62,5 +59,3 @@ class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalPro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppMessagePanel = withTranslation()(AppMessagePanelInternal);
|
|
||||||
export default AppMessagePanel;
|
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ import React from 'react'
|
|||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import {detect} from 'detect-browser';
|
import {detect} from 'detect-browser';
|
||||||
|
|
||||||
import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage, MdLanguage} from 'react-icons/md'
|
import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage} from 'react-icons/md'
|
||||||
import pkgJson from '../../package.json'
|
import pkgJson from '../../package.json'
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import maputnikLogo from 'maputnik-design/logos/logo-color.svg?inline'
|
import maputnikLogo from 'maputnik-design/logos/logo-color.svg?inline'
|
||||||
import { withTranslation, WithTranslation } from 'react-i18next';
|
|
||||||
import { supportedLanguages } from '../i18n';
|
|
||||||
|
|
||||||
// This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of.
|
// This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of.
|
||||||
const browser = detect();
|
const browser = detect();
|
||||||
@@ -82,7 +80,7 @@ class ToolbarAction extends React.Component<ToolbarActionProps> {
|
|||||||
|
|
||||||
export type MapState = "map" | "inspect" | "filter-achromatopsia" | "filter-deuteranopia" | "filter-protanopia" | "filter-tritanopia";
|
export type MapState = "map" | "inspect" | "filter-achromatopsia" | "filter-deuteranopia" | "filter-protanopia" | "filter-tritanopia";
|
||||||
|
|
||||||
type AppToolbarInternalProps = {
|
type AppToolbarProps = {
|
||||||
mapStyle: object
|
mapStyle: object
|
||||||
inspectModeEnabled: boolean
|
inspectModeEnabled: boolean
|
||||||
onStyleChanged(...args: unknown[]): unknown
|
onStyleChanged(...args: unknown[]): unknown
|
||||||
@@ -95,9 +93,9 @@ type AppToolbarInternalProps = {
|
|||||||
onSetMapState(mapState: MapState): unknown
|
onSetMapState(mapState: MapState): unknown
|
||||||
mapState?: MapState
|
mapState?: MapState
|
||||||
renderer?: string
|
renderer?: string
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
export default class AppToolbar extends React.Component<AppToolbarProps> {
|
||||||
state = {
|
state = {
|
||||||
isOpen: {
|
isOpen: {
|
||||||
settings: false,
|
settings: false,
|
||||||
@@ -112,10 +110,6 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
this.props.onSetMapState(val);
|
this.props.onSetMapState(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLanguageChange(val: string) {
|
|
||||||
this.props.i18n.changeLanguage(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSkip = (target: string) => {
|
onSkip = (target: string) => {
|
||||||
if (target === "map") {
|
if (target === "map") {
|
||||||
(document.querySelector(".maplibregl-canvas") as HTMLCanvasElement).focus();
|
(document.querySelector(".maplibregl-canvas") as HTMLCanvasElement).focus();
|
||||||
@@ -127,41 +121,40 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const views = [
|
const views = [
|
||||||
{
|
{
|
||||||
id: "map",
|
id: "map",
|
||||||
group: "general",
|
group: "general",
|
||||||
title: t("Map"),
|
title: "Map",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "inspect",
|
id: "inspect",
|
||||||
group: "general",
|
group: "general",
|
||||||
title: t("Inspect"),
|
title: "Inspect",
|
||||||
disabled: this.props.renderer === 'ol',
|
disabled: this.props.renderer === 'ol',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "filter-deuteranopia",
|
id: "filter-deuteranopia",
|
||||||
group: "color-accessibility",
|
group: "color-accessibility",
|
||||||
title: t("Deuteranopia filter"),
|
title: "Deuteranopia filter",
|
||||||
disabled: !colorAccessibilityFiltersEnabled,
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "filter-protanopia",
|
id: "filter-protanopia",
|
||||||
group: "color-accessibility",
|
group: "color-accessibility",
|
||||||
title: t("Protanopia filter"),
|
title: "Protanopia filter",
|
||||||
disabled: !colorAccessibilityFiltersEnabled,
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "filter-tritanopia",
|
id: "filter-tritanopia",
|
||||||
group: "color-accessibility",
|
group: "color-accessibility",
|
||||||
title: t("Tritanopia filter"),
|
title: "Tritanopia filter",
|
||||||
disabled: !colorAccessibilityFiltersEnabled,
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "filter-achromatopsia",
|
id: "filter-achromatopsia",
|
||||||
group: "color-accessibility",
|
group: "color-accessibility",
|
||||||
title: t("Achromatopsia filter"),
|
title: "Achromatopsia filter",
|
||||||
disabled: !colorAccessibilityFiltersEnabled,
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -181,21 +174,21 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
className="maputnik-toolbar-skip"
|
className="maputnik-toolbar-skip"
|
||||||
onClick={_e => this.onSkip("layer-list")}
|
onClick={_e => this.onSkip("layer-list")}
|
||||||
>
|
>
|
||||||
{t("Layers list")}
|
Layers list
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
data-wd-key="root:skip:layer-editor"
|
data-wd-key="root:skip:layer-editor"
|
||||||
className="maputnik-toolbar-skip"
|
className="maputnik-toolbar-skip"
|
||||||
onClick={_e => this.onSkip("layer-editor")}
|
onClick={_e => this.onSkip("layer-editor")}
|
||||||
>
|
>
|
||||||
{t("Layer editor")}
|
Layer editor
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
data-wd-key="root:skip:map-view"
|
data-wd-key="root:skip:map-view"
|
||||||
className="maputnik-toolbar-skip"
|
className="maputnik-toolbar-skip"
|
||||||
onClick={_e => this.onSkip("map")}
|
onClick={_e => this.onSkip("map")}
|
||||||
>
|
>
|
||||||
{t("Map view")}
|
Map view
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
className="maputnik-toolbar-logo"
|
className="maputnik-toolbar-logo"
|
||||||
@@ -203,7 +196,7 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
href="https://github.com/maplibre/maputnik"
|
href="https://github.com/maplibre/maputnik"
|
||||||
>
|
>
|
||||||
<img src={maputnikLogo} alt={t("Maputnik on GitHub")} />
|
<img src={maputnikLogo} alt="Maputnik on GitHub" />
|
||||||
<h1>
|
<h1>
|
||||||
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
|
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
|
||||||
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
||||||
@@ -213,24 +206,24 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
<div className="maputnik-toolbar__actions" role="navigation" aria-label="Toolbar">
|
<div className="maputnik-toolbar__actions" role="navigation" aria-label="Toolbar">
|
||||||
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
|
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
|
||||||
<MdOpenInBrowser />
|
<MdOpenInBrowser />
|
||||||
<IconText>{t("Open")}</IconText>
|
<IconText>Open</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
|
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
|
||||||
<MdFileDownload />
|
<MdFileDownload />
|
||||||
<IconText>{t("Export")}</IconText>
|
<IconText>Export</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
|
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
|
||||||
<MdLayers />
|
<MdLayers />
|
||||||
<IconText>{t("Data Sources")}</IconText>
|
<IconText>Data Sources</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
|
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
|
||||||
<MdSettings />
|
<MdSettings />
|
||||||
<IconText>{t("Style Settings")}</IconText>
|
<IconText>Style Settings</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
|
|
||||||
<ToolbarSelect wdKey="nav:inspect">
|
<ToolbarSelect wdKey="nav:inspect">
|
||||||
<MdFindInPage />
|
<MdFindInPage />
|
||||||
<label>{t("View")}
|
<label>View
|
||||||
<select
|
<select
|
||||||
className="maputnik-select"
|
className="maputnik-select"
|
||||||
data-wd-key="maputnik-select"
|
data-wd-key="maputnik-select"
|
||||||
@@ -244,7 +237,7 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
</option>
|
</option>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<optgroup label={t("Color accessibility")}>
|
<optgroup label="Color accessibility">
|
||||||
{views.filter(v => v.group === "color-accessibility").map((item) => {
|
{views.filter(v => v.group === "color-accessibility").map((item) => {
|
||||||
return (
|
return (
|
||||||
<option key={item.id} value={item.id} disabled={item.disabled}>
|
<option key={item.id} value={item.id} disabled={item.disabled}>
|
||||||
@@ -257,35 +250,12 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
</label>
|
</label>
|
||||||
</ToolbarSelect>
|
</ToolbarSelect>
|
||||||
|
|
||||||
<ToolbarSelect wdKey="nav:language">
|
|
||||||
<MdLanguage />
|
|
||||||
<label>{t("Language")}
|
|
||||||
<select
|
|
||||||
className="maputnik-select"
|
|
||||||
data-wd-key="maputnik-lang-select"
|
|
||||||
onChange={(e) => this.handleLanguageChange(e.target.value)}
|
|
||||||
value={this.props.i18n.language}
|
|
||||||
>
|
|
||||||
{Object.entries(supportedLanguages).map(([code, name]) => {
|
|
||||||
return (
|
|
||||||
<option key={code} value={code}>
|
|
||||||
{name}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</ToolbarSelect>
|
|
||||||
|
|
||||||
<ToolbarLink href={"https://github.com/maplibre/maputnik/wiki"}>
|
<ToolbarLink href={"https://github.com/maplibre/maputnik/wiki"}>
|
||||||
<MdHelpOutline />
|
<MdHelpOutline />
|
||||||
<IconText>{t("Help")}</IconText>
|
<IconText>Help</IconText>
|
||||||
</ToolbarLink>
|
</ToolbarLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppToolbar = withTranslation()(AppToolbarInternal);
|
|
||||||
export default AppToolbar;
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, {PropsWithChildren, SyntheticEvent} from 'react'
|
import React, {SyntheticEvent} from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import FieldDocLabel from './FieldDocLabel'
|
import FieldDocLabel from './FieldDocLabel'
|
||||||
import Doc from './Doc'
|
import Doc from './Doc'
|
||||||
|
|
||||||
|
|
||||||
type BlockProps = PropsWithChildren & {
|
type BlockProps = {
|
||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
label?: string
|
label?: string
|
||||||
action?: React.ReactElement
|
action?: React.ReactElement
|
||||||
|
|||||||
@@ -2,23 +2,21 @@ import React from 'react'
|
|||||||
|
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputString from './InputString'
|
import InputString from './InputString'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type FieldCommentInternalProps = {
|
type FieldCommentProps = {
|
||||||
value?: string
|
value?: string
|
||||||
onChange(value: string | undefined): unknown
|
onChange(value: string | undefined): unknown
|
||||||
error: {message: string}
|
error: {message: string}
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class FieldCommentInternal extends React.Component<FieldCommentInternalProps> {
|
export default class FieldComment extends React.Component<FieldCommentProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const fieldSpec = {
|
const fieldSpec = {
|
||||||
doc: t("Comments for the current layer. This is non-standard and not in the spec."),
|
doc: "Comments for the current layer. This is non-standard and not in the spec."
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Block
|
return <Block
|
||||||
label={t("Comments")}
|
label={"Comments"}
|
||||||
fieldSpec={fieldSpec}
|
fieldSpec={fieldSpec}
|
||||||
data-wd-key="layer-comment"
|
data-wd-key="layer-comment"
|
||||||
error={this.props.error}
|
error={this.props.error}
|
||||||
@@ -27,12 +25,9 @@ class FieldCommentInternal extends React.Component<FieldCommentInternalProps> {
|
|||||||
multi={true}
|
multi={true}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
default={t("Comment...")}
|
default="Comment..."
|
||||||
data-wd-key="layer-comment.input"
|
data-wd-key="layer-comment.input"
|
||||||
/>
|
/>
|
||||||
</Block>
|
</Block>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldComment = withTranslation()(FieldCommentInternal);
|
|
||||||
export default FieldComment;
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import {MdInfoOutline, MdHighlightOff} from 'react-icons/md'
|
import {MdInfoOutline, MdHighlightOff} from 'react-icons/md'
|
||||||
|
|
||||||
type FieldDocLabelProps = {
|
type FieldDocLabelProps = {
|
||||||
label: JSX.Element | string | undefined
|
label: object | string | undefined
|
||||||
fieldSpec?: {
|
fieldSpec?: {
|
||||||
doc?: string
|
doc?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export default class FieldFunction extends React.Component<FieldFunctionProps, F
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<FieldFunctionProps>, state: FieldFunctionState) {
|
static getDerivedStateFromProps(props: FieldFunctionProps, state: FieldFunctionState) {
|
||||||
// Because otherwise when editing values we end up accidentally changing field type.
|
// Because otherwise when editing values we end up accidentally changing field type.
|
||||||
if (state.isEditing) {
|
if (state.isEditing) {
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ type FieldIdProps = {
|
|||||||
|
|
||||||
export default class FieldId extends React.Component<FieldIdProps> {
|
export default class FieldId extends React.Component<FieldIdProps> {
|
||||||
render() {
|
render() {
|
||||||
return <Block label="ID" fieldSpec={latest.layer.id}
|
return <Block label={"ID"} fieldSpec={latest.layer.id}
|
||||||
|
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
error={this.props.error}
|
error={this.props.error}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,18 +3,16 @@ import React from 'react'
|
|||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from './InputNumber'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type FieldMaxZoomInternalProps = {
|
type FieldMaxZoomProps = {
|
||||||
value?: number
|
value?: number
|
||||||
onChange(value: number | undefined): unknown
|
onChange(value: number | undefined): unknown
|
||||||
error?: {message: string}
|
error?: {message: string}
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class FieldMaxZoomInternal extends React.Component<FieldMaxZoomInternalProps> {
|
export default class FieldMaxZoom extends React.Component<FieldMaxZoomProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
return <Block label={"Max Zoom"} fieldSpec={latest.layer.maxzoom}
|
||||||
return <Block label={t("Max Zoom")} fieldSpec={latest.layer.maxzoom}
|
|
||||||
error={this.props.error}
|
error={this.props.error}
|
||||||
data-wd-key="max-zoom"
|
data-wd-key="max-zoom"
|
||||||
>
|
>
|
||||||
@@ -30,6 +28,3 @@ class FieldMaxZoomInternal extends React.Component<FieldMaxZoomInternalProps> {
|
|||||||
</Block>
|
</Block>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldMaxZoom = withTranslation()(FieldMaxZoomInternal);
|
|
||||||
export default FieldMaxZoom;
|
|
||||||
|
|||||||
@@ -3,18 +3,16 @@ import React from 'react'
|
|||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from './InputNumber'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type FieldMinZoomInternalProps = {
|
type FieldMinZoomProps = {
|
||||||
value?: number
|
value?: number
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
error?: {message: string}
|
error?: {message: string}
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class FieldMinZoomInternal extends React.Component<FieldMinZoomInternalProps> {
|
export default class FieldMinZoom extends React.Component<FieldMinZoomProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
return <Block label={"Min Zoom"} fieldSpec={latest.layer.minzoom}
|
||||||
return <Block label={t("Min Zoom")} fieldSpec={latest.layer.minzoom}
|
|
||||||
error={this.props.error}
|
error={this.props.error}
|
||||||
data-wd-key="min-zoom"
|
data-wd-key="min-zoom"
|
||||||
>
|
>
|
||||||
@@ -30,6 +28,3 @@ class FieldMinZoomInternal extends React.Component<FieldMinZoomInternalProps> {
|
|||||||
</Block>
|
</Block>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldMinZoom = withTranslation()(FieldMinZoomInternal);
|
|
||||||
export default FieldMinZoom;
|
|
||||||
|
|||||||
@@ -3,26 +3,24 @@ import React from 'react'
|
|||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputAutocomplete from './InputAutocomplete'
|
import InputAutocomplete from './InputAutocomplete'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type FieldSourceInternalProps = {
|
type FieldSourceProps = {
|
||||||
value?: string
|
value?: string
|
||||||
wdKey?: string
|
wdKey?: string
|
||||||
onChange?(value: string| undefined): unknown
|
onChange?(value: string| undefined): unknown
|
||||||
sourceIds?: unknown[]
|
sourceIds?: unknown[]
|
||||||
error?: {message: string}
|
error?: {message: string}
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class FieldSourceInternal extends React.Component<FieldSourceInternalProps> {
|
export default class FieldSource extends React.Component<FieldSourceProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
sourceIds: [],
|
sourceIds: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <Block
|
return <Block
|
||||||
label={t("Source")}
|
label={"Source"}
|
||||||
fieldSpec={latest.layer.source}
|
fieldSpec={latest.layer.source}
|
||||||
error={this.props.error}
|
error={this.props.error}
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
@@ -35,6 +33,3 @@ class FieldSourceInternal extends React.Component<FieldSourceInternalProps> {
|
|||||||
</Block>
|
</Block>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldSource = withTranslation()(FieldSourceInternal);
|
|
||||||
export default FieldSource;
|
|
||||||
|
|||||||
@@ -3,17 +3,16 @@ import React from 'react'
|
|||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputAutocomplete from './InputAutocomplete'
|
import InputAutocomplete from './InputAutocomplete'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type FieldSourceLayerInternalProps = {
|
type FieldSourceLayerProps = {
|
||||||
value?: string
|
value?: string
|
||||||
onChange?(...args: unknown[]): unknown
|
onChange?(...args: unknown[]): unknown
|
||||||
sourceLayerIds?: unknown[]
|
sourceLayerIds?: unknown[]
|
||||||
isFixed?: boolean
|
isFixed?: boolean
|
||||||
error?: {message: string}
|
error?: {message: string}
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class FieldSourceLayerInternal extends React.Component<FieldSourceLayerInternalProps> {
|
export default class FieldSourceLayer extends React.Component<FieldSourceLayerProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
sourceLayerIds: [],
|
sourceLayerIds: [],
|
||||||
@@ -21,9 +20,8 @@ class FieldSourceLayerInternal extends React.Component<FieldSourceLayerInternalP
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <Block
|
return <Block
|
||||||
label={t("Source Layer")}
|
label={"Source Layer"}
|
||||||
fieldSpec={latest.layer['source-layer']}
|
fieldSpec={latest.layer['source-layer']}
|
||||||
data-wd-key="layer-source-layer"
|
data-wd-key="layer-source-layer"
|
||||||
error={this.props.error}
|
error={this.props.error}
|
||||||
@@ -37,6 +35,3 @@ class FieldSourceLayerInternal extends React.Component<FieldSourceLayerInternalP
|
|||||||
</Block>
|
</Block>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldSourceLayer = withTranslation()(FieldSourceLayerInternal);
|
|
||||||
export default FieldSourceLayer;
|
|
||||||
|
|||||||
@@ -4,24 +4,22 @@ import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
|||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputSelect from './InputSelect'
|
import InputSelect from './InputSelect'
|
||||||
import InputString from './InputString'
|
import InputString from './InputString'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type FieldTypeInternalProps = {
|
type FieldTypeProps = {
|
||||||
value: string
|
value: string
|
||||||
wdKey?: string
|
wdKey?: string
|
||||||
onChange(value: string): unknown
|
onChange(value: string): unknown
|
||||||
error?: {message: string}
|
error?: {message: string}
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class FieldTypeInternal extends React.Component<FieldTypeInternalProps> {
|
export default class FieldType extends React.Component<FieldTypeProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
return <Block label={"Type"} fieldSpec={latest.layer.type}
|
||||||
return <Block label={t("Type")} fieldSpec={latest.layer.type}
|
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
error={this.props.error}
|
error={this.props.error}
|
||||||
>
|
>
|
||||||
@@ -52,6 +50,3 @@ class FieldTypeInternal extends React.Component<FieldTypeInternalProps> {
|
|||||||
</Block>
|
</Block>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldType = withTranslation()(FieldTypeInternal);
|
|
||||||
export default FieldType;
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { PropsWithChildren, ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import FieldDocLabel from './FieldDocLabel'
|
import FieldDocLabel from './FieldDocLabel'
|
||||||
import Doc from './Doc'
|
import Doc from './Doc'
|
||||||
import generateUniqueId from '../libs/document-uid';
|
import generateUniqueId from '../libs/document-uid';
|
||||||
|
|
||||||
type FieldsetProps = PropsWithChildren & {
|
type FieldsetProps = {
|
||||||
label?: string,
|
label?: string,
|
||||||
fieldSpec?: { doc?: string },
|
fieldSpec?: { doc?: string },
|
||||||
action?: ReactElement,
|
action?: ReactElement,
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ import FilterEditorBlock from './FilterEditorBlock'
|
|||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import Doc from './Doc'
|
import Doc from './Doc'
|
||||||
import ExpressionProperty from './_ExpressionProperty';
|
import ExpressionProperty from './_ExpressionProperty';
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
|
|
||||||
function combiningFilter(props: FilterEditorInternalProps): LegacyFilterSpecification | ExpressionSpecification {
|
function combiningFilter(props: FilterEditorProps): LegacyFilterSpecification | ExpressionSpecification {
|
||||||
const filter = props.filter || ['all'];
|
const filter = props.filter || ['all'];
|
||||||
|
|
||||||
if (!Array.isArray(filter)) {
|
if (!Array.isArray(filter)) {
|
||||||
@@ -90,13 +89,13 @@ function hasNestedCombiningFilter(filter: LegacyFilterSpecification | Expression
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterEditorInternalProps = {
|
type FilterEditorProps = {
|
||||||
/** Properties of the vector layer and the available fields */
|
/** Properties of the vector layer and the available fields */
|
||||||
properties?: {[key:string]: any}
|
properties?: {[key:string]: any}
|
||||||
filter?: any[]
|
filter?: any[]
|
||||||
errors?: {[key:string]: any}
|
errors?: {[key:string]: any}
|
||||||
onChange(value: LegacyFilterSpecification | ExpressionSpecification): unknown
|
onChange(value: LegacyFilterSpecification | ExpressionSpecification): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
type FilterEditorState = {
|
type FilterEditorState = {
|
||||||
showDoc: boolean
|
showDoc: boolean
|
||||||
@@ -104,12 +103,12 @@ type FilterEditorState = {
|
|||||||
valueIsSimpleFilter?: boolean
|
valueIsSimpleFilter?: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
class FilterEditorInternal extends React.Component<FilterEditorInternalProps, FilterEditorState> {
|
export default class FilterEditor extends React.Component<FilterEditorProps, FilterEditorState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
filter: ["all"],
|
filter: ["all"],
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (props: FilterEditorInternalProps) {
|
constructor (props: FilterEditorProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
showDoc: false,
|
showDoc: false,
|
||||||
@@ -156,17 +155,17 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<FilterEditorInternalProps>, state: FilterEditorState) {
|
static getDerivedStateFromProps(props: FilterEditorProps, currentState: FilterEditorState) {
|
||||||
const displaySimpleFilter = checkIfSimpleFilter(combiningFilter(props));
|
const displaySimpleFilter = checkIfSimpleFilter(combiningFilter(props));
|
||||||
|
|
||||||
// Upgrade but never downgrade
|
// Upgrade but never downgrade
|
||||||
if (!displaySimpleFilter && state.displaySimpleFilter === true) {
|
if (!displaySimpleFilter && currentState.displaySimpleFilter === true) {
|
||||||
return {
|
return {
|
||||||
displaySimpleFilter: false,
|
displaySimpleFilter: false,
|
||||||
valueIsSimpleFilter: false,
|
valueIsSimpleFilter: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (displaySimpleFilter && state.displaySimpleFilter === false) {
|
else if (displaySimpleFilter && currentState.displaySimpleFilter === false) {
|
||||||
return {
|
return {
|
||||||
valueIsSimpleFilter: true,
|
valueIsSimpleFilter: true,
|
||||||
}
|
}
|
||||||
@@ -179,7 +178,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {errors, t} = this.props;
|
const {errors} = this.props;
|
||||||
const {displaySimpleFilter} = this.state;
|
const {displaySimpleFilter} = this.state;
|
||||||
const fieldSpec={
|
const fieldSpec={
|
||||||
doc: latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."
|
doc: latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."
|
||||||
@@ -191,16 +190,16 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
if (isNestedCombiningFilter) {
|
if (isNestedCombiningFilter) {
|
||||||
return <div className="maputnik-filter-editor-unsupported">
|
return <div className="maputnik-filter-editor-unsupported">
|
||||||
<p>
|
<p>
|
||||||
{t("Nested filters are not supported.")}
|
Nested filters are not supported.
|
||||||
</p>
|
</p>
|
||||||
<InputButton
|
<InputButton
|
||||||
onClick={this.makeExpression}
|
onClick={this.makeExpression}
|
||||||
title={t("Convert to expression")}
|
title="Convert to expression"
|
||||||
>
|
>
|
||||||
<svg style={{marginRight: "0.2em", width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
<svg style={{marginRight: "0.2em", width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d={mdiFunctionVariant} />
|
<path fill="currentColor" d={mdiFunctionVariant} />
|
||||||
</svg>
|
</svg>
|
||||||
{t("Upgrade to expression")}
|
Upgrade to expression
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -213,7 +212,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
<div>
|
<div>
|
||||||
<InputButton
|
<InputButton
|
||||||
onClick={this.makeExpression}
|
onClick={this.makeExpression}
|
||||||
title={t("Convert to expression")}
|
title="Convert to expression"
|
||||||
className="maputnik-make-zoom-function"
|
className="maputnik-make-zoom-function"
|
||||||
>
|
>
|
||||||
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
||||||
@@ -248,17 +247,13 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
<Block
|
<Block
|
||||||
key="top"
|
key="top"
|
||||||
fieldSpec={fieldSpec}
|
fieldSpec={fieldSpec}
|
||||||
label={t("Filter")}
|
label={"Filter"}
|
||||||
action={actions}
|
action={actions}
|
||||||
>
|
>
|
||||||
<InputSelect
|
<InputSelect
|
||||||
value={combiningOp}
|
value={combiningOp}
|
||||||
onChange={(v: [string, any]) => this.onFilterPartChanged(0, v)}
|
onChange={(v: [string, any]) => this.onFilterPartChanged(0, v)}
|
||||||
options={[
|
options={[["all", "every filter matches"], ["none", "no filter matches"], ["any", "any filter matches"]]}
|
||||||
["all", t("every filter matches")],
|
|
||||||
["none", t("no filter matches")],
|
|
||||||
["any", t("any filter matches")]
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Block>
|
</Block>
|
||||||
{editorBlocks}
|
{editorBlocks}
|
||||||
@@ -273,7 +268,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
>
|
>
|
||||||
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d={mdiTableRowPlusAfter} />
|
<path fill="currentColor" d={mdiTableRowPlusAfter} />
|
||||||
</svg> {t("Add filter")}
|
</svg> Add filter
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -304,13 +299,12 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
/>
|
/>
|
||||||
{this.state.valueIsSimpleFilter &&
|
{this.state.valueIsSimpleFilter &&
|
||||||
<div className="maputnik-expr-infobox">
|
<div className="maputnik-expr-infobox">
|
||||||
{t("You've entered an old style filter.")}
|
You've entered a old style filter,{' '}
|
||||||
{' '}
|
|
||||||
<button
|
<button
|
||||||
onClick={this.makeFilter}
|
onClick={this.makeFilter}
|
||||||
className="maputnik-expr-infobox__button"
|
className="maputnik-expr-infobox__button"
|
||||||
>
|
>
|
||||||
{t("Switch to filter editor.")}
|
switch to filter editor
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -319,6 +313,3 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterEditor = withTranslation()(FilterEditorInternal);
|
|
||||||
export default FilterEditor;
|
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
import React, { PropsWithChildren } from 'react'
|
import React from 'react'
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import {MdDelete} from 'react-icons/md'
|
import {MdDelete} from 'react-icons/md'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type FilterEditorBlockInternalProps = PropsWithChildren & {
|
type FilterEditorBlockProps = {
|
||||||
onDelete(...args: unknown[]): unknown
|
onDelete(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class FilterEditorBlockInternal extends React.Component<FilterEditorBlockInternalProps> {
|
export default class FilterEditorBlock extends React.Component<FilterEditorBlockProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <div className="maputnik-filter-editor-block">
|
return <div className="maputnik-filter-editor-block">
|
||||||
<div className="maputnik-filter-editor-block-action">
|
<div className="maputnik-filter-editor-block-action">
|
||||||
<InputButton
|
<InputButton
|
||||||
className="maputnik-delete-filter"
|
className="maputnik-delete-filter"
|
||||||
onClick={this.props.onDelete}
|
onClick={this.props.onDelete}
|
||||||
title={t("Delete filter block")}
|
title="Delete filter block"
|
||||||
>
|
>
|
||||||
<MdDelete />
|
<MdDelete />
|
||||||
</InputButton>
|
</InputButton>
|
||||||
@@ -27,5 +25,3 @@ class FilterEditorBlockInternal extends React.Component<FilterEditorBlockInterna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterEditorBlock = withTranslation()(FilterEditorBlockInternal);
|
|
||||||
export default FilterEditorBlock;
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default class FieldArray extends React.Component<FieldArrayProps, FieldAr
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<FieldArrayProps>, state: FieldArrayState) {
|
static getDerivedStateFromProps(props: FieldArrayProps, state: FieldArrayState) {
|
||||||
const value: any[] = [];
|
const value: any[] = [];
|
||||||
const initialPropsValue = state.initialPropsValue.slice(0);
|
const initialPropsValue = state.initialPropsValue.slice(0);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import capitalize from 'lodash.capitalize'
|
import capitalize from 'lodash.capitalize'
|
||||||
import {MdDelete} from 'react-icons/md'
|
import {MdDelete} from 'react-icons/md'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import InputString from './InputString'
|
import InputString from './InputString'
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from './InputNumber'
|
||||||
@@ -22,11 +21,10 @@ export type FieldDynamicArrayProps = {
|
|||||||
}
|
}
|
||||||
'aria-label'?: string
|
'aria-label'?: string
|
||||||
label: string
|
label: string
|
||||||
}
|
};
|
||||||
|
|
||||||
type FieldDynamicArrayInternalProps = FieldDynamicArrayProps & WithTranslation;
|
|
||||||
|
|
||||||
class FieldDynamicArrayInternal extends React.Component<FieldDynamicArrayInternalProps> {
|
export default class FieldDynamicArray extends React.Component<FieldDynamicArrayProps> {
|
||||||
changeValue(idx: number, newValue: string | number | undefined) {
|
changeValue(idx: number, newValue: string | number | undefined) {
|
||||||
const values = this.values.slice(0)
|
const values = this.values.slice(0)
|
||||||
values[idx] = newValue
|
values[idx] = newValue
|
||||||
@@ -64,13 +62,8 @@ class FieldDynamicArrayInternal extends React.Component<FieldDynamicArrayInterna
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const i18nProps = { t, i18n: this.props.i18n, tReady: this.props.tReady };
|
|
||||||
const inputs = this.values.map((v, i) => {
|
const inputs = this.values.map((v, i) => {
|
||||||
const deleteValueBtn= <DeleteValueInputButton
|
const deleteValueBtn= <DeleteValueInputButton onClick={this.deleteValue.bind(this, i)} />
|
||||||
onClick={this.deleteValue.bind(this, i)}
|
|
||||||
{...i18nProps}
|
|
||||||
/>;
|
|
||||||
let input;
|
let input;
|
||||||
if(this.props.type === 'url') {
|
if(this.props.type === 'url') {
|
||||||
input = <InputUrl
|
input = <InputUrl
|
||||||
@@ -124,27 +117,23 @@ class FieldDynamicArrayInternal extends React.Component<FieldDynamicArrayInterna
|
|||||||
className="maputnik-array-add-value"
|
className="maputnik-array-add-value"
|
||||||
onClick={this.addValue}
|
onClick={this.addValue}
|
||||||
>
|
>
|
||||||
{t("Add value")}
|
Add value
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldDynamicArray = withTranslation()(FieldDynamicArrayInternal);
|
|
||||||
export default FieldDynamicArray;
|
|
||||||
|
|
||||||
type DeleteValueInputButtonProps = {
|
type DeleteValueInputButtonProps = {
|
||||||
onClick?(...args: unknown[]): unknown
|
onClick?(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class DeleteValueInputButton extends React.Component<DeleteValueInputButtonProps> {
|
class DeleteValueInputButton extends React.Component<DeleteValueInputButtonProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <InputButton
|
return <InputButton
|
||||||
className="maputnik-delete-stop"
|
className="maputnik-delete-stop"
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
title={t("Remove array item")}
|
title="Remove array item"
|
||||||
>
|
>
|
||||||
<FieldDocLabel
|
<FieldDocLabel
|
||||||
label={<MdDelete />}
|
label={<MdDelete />}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import CodeMirror, { ModeSpec } from 'codemirror';
|
import CodeMirror, { ModeSpec } from 'codemirror';
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import 'codemirror/mode/javascript/javascript'
|
import 'codemirror/mode/javascript/javascript'
|
||||||
import 'codemirror/addon/lint/lint'
|
import 'codemirror/addon/lint/lint'
|
||||||
@@ -28,7 +27,6 @@ export type InputJsonProps = {
|
|||||||
mode?: ModeSpec<any>
|
mode?: ModeSpec<any>
|
||||||
lint?: boolean | object
|
lint?: boolean | object
|
||||||
};
|
};
|
||||||
type InputJsonInternalProps = InputJsonProps & WithTranslation;
|
|
||||||
|
|
||||||
type InputJsonState = {
|
type InputJsonState = {
|
||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
@@ -36,7 +34,7 @@ type InputJsonState = {
|
|||||||
prevValue: string
|
prevValue: string
|
||||||
};
|
};
|
||||||
|
|
||||||
class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJsonState> {
|
export default class InputJson extends React.Component<InputJsonProps, InputJsonState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
lineWrapping: false,
|
lineWrapping: false,
|
||||||
@@ -54,7 +52,7 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
_el: HTMLDivElement | null = null;
|
_el: HTMLDivElement | null = null;
|
||||||
_cancelNextChange: boolean = false;
|
_cancelNextChange: boolean = false;
|
||||||
|
|
||||||
constructor(props: InputJsonInternalProps) {
|
constructor(props: InputJsonProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this._keyEvent = "keyboard";
|
this._keyEvent = "keyboard";
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -158,7 +156,6 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const {showMessage} = this.state;
|
const {showMessage} = this.state;
|
||||||
const style = {} as {maxHeight?: number};
|
const style = {} as {maxHeight?: number};
|
||||||
if (this.props.maxHeight) {
|
if (this.props.maxHeight) {
|
||||||
@@ -167,9 +164,7 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
|
|
||||||
return <div className="JSONEditor" onPointerDown={this.onPointerDown} aria-hidden="true">
|
return <div className="JSONEditor" onPointerDown={this.onPointerDown} aria-hidden="true">
|
||||||
<div className={classnames("JSONEditor__message", {"JSONEditor__message--on": showMessage})}>
|
<div className={classnames("JSONEditor__message", {"JSONEditor__message--on": showMessage})}>
|
||||||
<Trans t={t}>
|
Press <kbd>ESC</kbd> to lose focus
|
||||||
Press <kbd>ESC</kbd> to lose focus
|
|
||||||
</Trans>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classnames("codemirror-container", this.props.className)}
|
className={classnames("codemirror-container", this.props.className)}
|
||||||
@@ -179,6 +174,3 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputJson = withTranslation()(InputJsonInternal);
|
|
||||||
export default InputJson;
|
|
||||||
|
|||||||
@@ -19,10 +19,7 @@ type InputNumberState = {
|
|||||||
editing: boolean
|
editing: boolean
|
||||||
editingRange?: boolean
|
editingRange?: boolean
|
||||||
value?: number
|
value?: number
|
||||||
/**
|
dirtyValue?: number
|
||||||
* This is the value that is currently being edited. It can be an invalid value.
|
|
||||||
*/
|
|
||||||
dirtyValue?: number | string | undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class InputNumber extends React.Component<InputNumberProps, InputNumberState> {
|
export default class InputNumber extends React.Component<InputNumberProps, InputNumberState> {
|
||||||
@@ -41,7 +38,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<InputNumberProps>, state: InputNumberState) {
|
static getDerivedStateFromProps(props: InputNumberProps, state: InputNumberState) {
|
||||||
if (!state.editing && props.value !== state.value) {
|
if (!state.editing && props.value !== state.value) {
|
||||||
return {
|
return {
|
||||||
value: props.value,
|
value: props.value,
|
||||||
@@ -69,7 +66,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
dirtyValue: newValue === "" ? undefined : newValue,
|
dirtyValue: newValue === "" ? undefined : value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +125,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
// for example we might go from 13 to 13.23, however because we know
|
// for example we might go from 13 to 13.23, however because we know
|
||||||
// that came from a keyboard event we always want to increase by a
|
// that came from a keyboard event we always want to increase by a
|
||||||
// single step value.
|
// single step value.
|
||||||
if (value < +this.state.dirtyValue!) {
|
if (value < this.state.dirtyValue!) {
|
||||||
value = this.state.value! - step;
|
value = this.state.value! - step;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ export default class SpecField extends React.Component<SpecFieldProps> {
|
|||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
default: this.props.fieldSpec?.default,
|
default: this.props.fieldSpec?.default,
|
||||||
name: this.props.fieldName,
|
name: this.props.fieldName,
|
||||||
"data-wd-key": "spec-field-input:" + this.props.fieldName,
|
|
||||||
onChange: (newValue: number | undefined | (string | number | undefined)[]) => this.props.onChange!(this.props.fieldName, newValue),
|
onChange: (newValue: number | undefined | (string | number | undefined)[]) => this.props.onChange!(this.props.fieldName, newValue),
|
||||||
'aria-label': this.props['aria-label'],
|
'aria-label': this.props['aria-label'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default class InputString extends React.Component<InputStringProps, Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<InputStringProps>, state: InputStringState) {
|
static getDerivedStateFromProps(props: InputStringProps, state: InputStringState) {
|
||||||
if (!state.editing) {
|
if (!state.editing) {
|
||||||
return {
|
return {
|
||||||
value: props.value
|
value: props.value
|
||||||
|
|||||||
+17
-28
@@ -1,10 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import InputString from './InputString'
|
import InputString from './InputString'
|
||||||
import SmallError from './SmallError'
|
import SmallError from './SmallError'
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
import { TFunction } from 'i18next';
|
|
||||||
|
|
||||||
function validate(url: string, t: TFunction): JSX.Element | undefined {
|
|
||||||
|
function validate(url: string) {
|
||||||
if (url === "") {
|
if (url === "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -23,19 +22,15 @@ function validate(url: string, t: TFunction): JSX.Element | undefined {
|
|||||||
const isSsl = window.location.protocol === "https:";
|
const isSsl = window.location.protocol === "https:";
|
||||||
|
|
||||||
if (!protocol) {
|
if (!protocol) {
|
||||||
if (isSsl) {
|
error = (
|
||||||
error = (
|
<SmallError>
|
||||||
<SmallError>
|
Must provide protocol {
|
||||||
<Trans t={t}>Must provide protocol: <code>https://</code></Trans>
|
isSsl
|
||||||
</SmallError>
|
? <code>https://</code>
|
||||||
);
|
: <><code>http://</code> or <code>https://</code></>
|
||||||
} else {
|
}
|
||||||
error = (
|
</SmallError>
|
||||||
<SmallError>
|
);
|
||||||
<Trans t={t}>Must provide protocol: <code>http://</code> or <code>https://</code></Trans>
|
|
||||||
</SmallError>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (
|
else if (
|
||||||
protocol &&
|
protocol &&
|
||||||
@@ -44,9 +39,7 @@ function validate(url: string, t: TFunction): JSX.Element | undefined {
|
|||||||
) {
|
) {
|
||||||
error = (
|
error = (
|
||||||
<SmallError>
|
<SmallError>
|
||||||
<Trans t={t}>
|
CORS policy won't allow fetching resources served over http from https, use a <code>https://</code> domain
|
||||||
CORS policy won't allow fetching resources served over http from https, use a <code>https://</code> domain
|
|
||||||
</Trans>
|
|
||||||
</SmallError>
|
</SmallError>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -68,34 +61,32 @@ export type FieldUrlProps = {
|
|||||||
className?: string
|
className?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
type FieldUrlInternalProps = FieldUrlProps & WithTranslation;
|
|
||||||
|
|
||||||
type FieldUrlState = {
|
type FieldUrlState = {
|
||||||
error?: React.ReactNode
|
error?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
class FieldUrlInternal extends React.Component<FieldUrlInternalProps, FieldUrlState> {
|
export default class FieldUrl extends React.Component<FieldUrlProps, FieldUrlState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onInput: () => {},
|
onInput: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (props: FieldUrlInternalProps) {
|
constructor (props: FieldUrlProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
error: validate(props.value, props.t),
|
error: validate(props.value)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onInput = (url: string) => {
|
onInput = (url: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: validate(url, this.props.t),
|
error: validate(url)
|
||||||
});
|
});
|
||||||
if (this.props.onInput) this.props.onInput(url);
|
if (this.props.onInput) this.props.onInput(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (url: string) => {
|
onChange = (url: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: validate(url, this.props.t),
|
error: validate(url)
|
||||||
});
|
});
|
||||||
this.props.onChange(url);
|
this.props.onChange(url);
|
||||||
}
|
}
|
||||||
@@ -115,5 +106,3 @@ class FieldUrlInternal extends React.Component<FieldUrlInternalProps, FieldUrlSt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldUrl = withTranslation()(FieldUrlInternal);
|
|
||||||
export default FieldUrl;
|
|
||||||
|
|||||||
@@ -19,45 +19,31 @@ import FieldSourceLayer from './FieldSourceLayer'
|
|||||||
import { changeType, changeProperty } from '../libs/layer'
|
import { changeType, changeProperty } from '../libs/layer'
|
||||||
import layout from '../config/layout.json'
|
import layout from '../config/layout.json'
|
||||||
import {formatLayerId} from '../libs/format';
|
import {formatLayerId} from '../libs/format';
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
import { TFunction } from 'i18next';
|
|
||||||
|
|
||||||
|
|
||||||
function getLayoutForType(type: LayerSpecification["type"], t: TFunction) {
|
function getLayoutForType(type: LayerSpecification["type"]) {
|
||||||
return layout[type] ? {
|
return layout[type] ? layout[type] : layout.invalid;
|
||||||
...layout[type],
|
|
||||||
groups: layout[type].groups.map(group => {
|
|
||||||
return {
|
|
||||||
...group,
|
|
||||||
id: group.title.replace(/ /g, "_"),
|
|
||||||
title: t(group.title)
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
} : layout.invalid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function layoutGroups(layerType: LayerSpecification["type"], t: TFunction): {id: string, title: string, type: string, fields?: string[]}[] {
|
function layoutGroups(layerType: LayerSpecification["type"]): {title: string, type: string, fields?: string[]}[] {
|
||||||
const layerGroup = {
|
const layerGroup = {
|
||||||
id: 'layer',
|
title: 'Layer',
|
||||||
title: t('Layer'),
|
|
||||||
type: 'layer'
|
type: 'layer'
|
||||||
}
|
}
|
||||||
const filterGroup = {
|
const filterGroup = {
|
||||||
id: 'filter',
|
title: 'Filter',
|
||||||
title: t('Filter'),
|
|
||||||
type: 'filter'
|
type: 'filter'
|
||||||
}
|
}
|
||||||
const editorGroup = {
|
const editorGroup = {
|
||||||
id: 'jsoneditor',
|
title: 'JSON Editor',
|
||||||
title: t('JSON Editor'),
|
|
||||||
type: 'jsoneditor'
|
type: 'jsoneditor'
|
||||||
}
|
}
|
||||||
return [layerGroup, filterGroup]
|
return [layerGroup, filterGroup]
|
||||||
.concat(getLayoutForType(layerType, t).groups)
|
.concat(getLayoutForType(layerType).groups)
|
||||||
.concat([editorGroup])
|
.concat([editorGroup])
|
||||||
}
|
}
|
||||||
|
|
||||||
type LayerEditorInternalProps = {
|
type LayerEditorProps = {
|
||||||
layer: LayerSpecification
|
layer: LayerSpecification
|
||||||
sources: {[key: string]: SourceSpecification}
|
sources: {[key: string]: SourceSpecification}
|
||||||
vectorLayers: {[key: string]: any}
|
vectorLayers: {[key: string]: any}
|
||||||
@@ -72,14 +58,14 @@ type LayerEditorInternalProps = {
|
|||||||
isLastLayer?: boolean
|
isLastLayer?: boolean
|
||||||
layerIndex: number
|
layerIndex: number
|
||||||
errors?: any[]
|
errors?: any[]
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
type LayerEditorState = {
|
type LayerEditorState = {
|
||||||
editorGroups: {[keys:string]: boolean}
|
editorGroups: {[keys:string]: boolean}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Layer editor supporting multiple types of layers. */
|
/** Layer editor supporting multiple types of layers. */
|
||||||
class LayerEditorInternal extends React.Component<LayerEditorInternalProps, LayerEditorState> {
|
export default class LayerEditor extends React.Component<LayerEditorProps, LayerEditorState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onLayerChanged: () => {},
|
onLayerChanged: () => {},
|
||||||
onLayerIdChange: () => {},
|
onLayerIdChange: () => {},
|
||||||
@@ -90,22 +76,22 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
reactIconBase: PropTypes.object
|
reactIconBase: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: LayerEditorInternalProps) {
|
constructor(props: LayerEditorProps) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
//TODO: Clean this up and refactor into function
|
//TODO: Clean this up and refactor into function
|
||||||
const editorGroups: {[keys:string]: boolean} = {}
|
const editorGroups: {[keys:string]: boolean} = {}
|
||||||
layoutGroups(this.props.layer.type, props.t).forEach(group => {
|
layoutGroups(this.props.layer.type).forEach(group => {
|
||||||
editorGroups[group.title] = true
|
editorGroups[group.title] = true
|
||||||
})
|
})
|
||||||
|
|
||||||
this.state = { editorGroups }
|
this.state = { editorGroups }
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<LayerEditorInternalProps>, state: LayerEditorState) {
|
static getDerivedStateFromProps(props: LayerEditorProps, state: LayerEditorState) {
|
||||||
const additionalGroups = { ...state.editorGroups }
|
const additionalGroups = { ...state.editorGroups }
|
||||||
|
|
||||||
getLayoutForType(props.layer.type, props.t).groups.forEach(group => {
|
getLayoutForType(props.layer.type).groups.forEach(group => {
|
||||||
if(!(group.title in additionalGroups)) {
|
if(!(group.title in additionalGroups)) {
|
||||||
additionalGroups[group.title] = true
|
additionalGroups[group.title] = true
|
||||||
}
|
}
|
||||||
@@ -256,19 +242,17 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
|
|
||||||
const groupIds: string[] = [];
|
const groupIds: string[] = [];
|
||||||
const layerType = this.props.layer.type
|
const layerType = this.props.layer.type
|
||||||
const groups = layoutGroups(layerType, t).filter(group => {
|
const groups = layoutGroups(layerType).filter(group => {
|
||||||
return !(layerType === 'background' && group.type === 'source')
|
return !(layerType === 'background' && group.type === 'source')
|
||||||
}).map(group => {
|
}).map(group => {
|
||||||
const groupId = group.id;
|
const groupId = group.title.replace(/ /g, "_");
|
||||||
groupIds.push(groupId);
|
groupIds.push(groupId);
|
||||||
return <LayerEditorGroup
|
return <LayerEditorGroup
|
||||||
data-wd-key={group.title}
|
data-wd-key={group.title}
|
||||||
id={groupId}
|
id={groupId}
|
||||||
key={groupId}
|
key={group.title}
|
||||||
title={group.title}
|
title={group.title}
|
||||||
isActive={this.state.editorGroups[group.title]}
|
isActive={this.state.editorGroups[group.title]}
|
||||||
onActiveToggle={this.onGroupToggle.bind(this, group.title)}
|
onActiveToggle={this.onGroupToggle.bind(this, group.title)}
|
||||||
@@ -281,25 +265,25 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
|
|
||||||
const items: {[key: string]: {text: string, handler: () => void, disabled?: boolean}} = {
|
const items: {[key: string]: {text: string, handler: () => void, disabled?: boolean}} = {
|
||||||
delete: {
|
delete: {
|
||||||
text: t("Delete"),
|
text: "Delete",
|
||||||
handler: () => this.props.onLayerDestroy(this.props.layerIndex)
|
handler: () => this.props.onLayerDestroy(this.props.layerIndex)
|
||||||
},
|
},
|
||||||
duplicate: {
|
duplicate: {
|
||||||
text: t("Duplicate"),
|
text: "Duplicate",
|
||||||
handler: () => this.props.onLayerCopy(this.props.layerIndex)
|
handler: () => this.props.onLayerCopy(this.props.layerIndex)
|
||||||
},
|
},
|
||||||
hide: {
|
hide: {
|
||||||
text: (layout.visibility === "none") ? t("Show") : t("Hide"),
|
text: (layout.visibility === "none") ? "Show" : "Hide",
|
||||||
handler: () => this.props.onLayerVisibilityToggle(this.props.layerIndex)
|
handler: () => this.props.onLayerVisibilityToggle(this.props.layerIndex)
|
||||||
},
|
},
|
||||||
moveLayerUp: {
|
moveLayerUp: {
|
||||||
text: t("Move layer up"),
|
text: "Move layer up",
|
||||||
// Not actually used...
|
// Not actually used...
|
||||||
disabled: this.props.isFirstLayer,
|
disabled: this.props.isFirstLayer,
|
||||||
handler: () => this.moveLayer(-1)
|
handler: () => this.moveLayer(-1)
|
||||||
},
|
},
|
||||||
moveLayerDown: {
|
moveLayerDown: {
|
||||||
text: t("Move layer down"),
|
text: "Move layer down",
|
||||||
// Not actually used...
|
// Not actually used...
|
||||||
disabled: this.props.isLastLayer,
|
disabled: this.props.isLastLayer,
|
||||||
handler: () => this.moveLayer(+1)
|
handler: () => this.moveLayer(+1)
|
||||||
@@ -313,12 +297,12 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
|
|
||||||
return <section className="maputnik-layer-editor"
|
return <section className="maputnik-layer-editor"
|
||||||
role="main"
|
role="main"
|
||||||
aria-label={t("Layer editor")}
|
aria-label="Layer editor"
|
||||||
>
|
>
|
||||||
<header>
|
<header>
|
||||||
<div className="layer-header">
|
<div className="layer-header">
|
||||||
<h2 className="layer-header__title">
|
<h2 className="layer-header__title">
|
||||||
{t("Layer: {{layerId}}", { layerId: formatLayerId(this.props.layer.id) })}
|
Layer: {formatLayerId(this.props.layer.id)}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="layer-header__info">
|
<div className="layer-header__info">
|
||||||
<Wrapper
|
<Wrapper
|
||||||
@@ -326,11 +310,7 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
onSelection={handleSelection}
|
onSelection={handleSelection}
|
||||||
closeOnSelection={false}
|
closeOnSelection={false}
|
||||||
>
|
>
|
||||||
<Button
|
<Button id="skip-target-layer-editor" data-wd-key="skip-target-layer-editor" className='more-menu__button' title="Layer options">
|
||||||
id="skip-target-layer-editor"
|
|
||||||
data-wd-key="skip-target-layer-editor"
|
|
||||||
className='more-menu__button'
|
|
||||||
title={"Layer options"}>
|
|
||||||
<MdMoreVert className="more-menu__button__svg" />
|
<MdMoreVert className="more-menu__button__svg" />
|
||||||
</Button>
|
</Button>
|
||||||
<Menu>
|
<Menu>
|
||||||
@@ -360,6 +340,3 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LayerEditor = withTranslation()(LayerEditorInternal);
|
|
||||||
export default LayerEditor;
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {SortEndHandler, SortableContainer} from 'react-sortable-hoc';
|
|||||||
import type {LayerSpecification} from 'maplibre-gl';
|
import type {LayerSpecification} from 'maplibre-gl';
|
||||||
import generateUniqueId from '../libs/document-uid';
|
import generateUniqueId from '../libs/document-uid';
|
||||||
import { findClosestCommonPrefix, layerPrefix } from '../libs/layer';
|
import { findClosestCommonPrefix, layerPrefix } from '../libs/layer';
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type LayerListContainerProps = {
|
type LayerListContainerProps = {
|
||||||
layers: LayerSpecification[]
|
layers: LayerSpecification[]
|
||||||
@@ -23,7 +22,6 @@ type LayerListContainerProps = {
|
|||||||
sources: object
|
sources: object
|
||||||
errors: any[]
|
errors: any[]
|
||||||
};
|
};
|
||||||
type LayerListContainerInternalProps = LayerListContainerProps & WithTranslation;
|
|
||||||
|
|
||||||
type LayerListContainerState = {
|
type LayerListContainerState = {
|
||||||
collapsedGroups: {[ket: string]: boolean}
|
collapsedGroups: {[ket: string]: boolean}
|
||||||
@@ -33,14 +31,14 @@ type LayerListContainerState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// List of collapsible layer editors
|
// List of collapsible layer editors
|
||||||
class LayerListContainerInternal extends React.Component<LayerListContainerInternalProps, LayerListContainerState> {
|
class LayerListContainer extends React.Component<LayerListContainerProps, LayerListContainerState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
}
|
}
|
||||||
selectedItemRef: React.RefObject<any>;
|
selectedItemRef: React.RefObject<any>;
|
||||||
scrollContainerRef: React.RefObject<HTMLElement>;
|
scrollContainerRef: React.RefObject<HTMLElement>;
|
||||||
|
|
||||||
constructor(props: LayerListContainerInternalProps) {
|
constructor(props: LayerListContainerProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.selectedItemRef = React.createRef();
|
this.selectedItemRef = React.createRef();
|
||||||
this.scrollContainerRef = React.createRef();
|
this.scrollContainerRef = React.createRef();
|
||||||
@@ -261,12 +259,10 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const t = this.props.t;
|
|
||||||
|
|
||||||
return <section
|
return <section
|
||||||
className="maputnik-layer-list"
|
className="maputnik-layer-list"
|
||||||
role="complementary"
|
role="complementary"
|
||||||
aria-label={t("Layers list")}
|
aria-label="Layers list"
|
||||||
ref={this.scrollContainerRef}
|
ref={this.scrollContainerRef}
|
||||||
>
|
>
|
||||||
<ModalAdd
|
<ModalAdd
|
||||||
@@ -278,7 +274,7 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
onLayersChange={this.props.onLayersChange}
|
onLayersChange={this.props.onLayersChange}
|
||||||
/>
|
/>
|
||||||
<header className="maputnik-layer-list-header">
|
<header className="maputnik-layer-list-header">
|
||||||
<span className="maputnik-layer-list-header-title">{t("Layers")}</span>
|
<span className="maputnik-layer-list-header-title">Layers</span>
|
||||||
<span className="maputnik-space" />
|
<span className="maputnik-space" />
|
||||||
<div className="maputnik-default-property">
|
<div className="maputnik-default-property">
|
||||||
<div className="maputnik-multibutton">
|
<div className="maputnik-multibutton">
|
||||||
@@ -287,11 +283,7 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
data-wd-key="skip-target-layer-list"
|
data-wd-key="skip-target-layer-list"
|
||||||
onClick={this.toggleLayers}
|
onClick={this.toggleLayers}
|
||||||
className="maputnik-button">
|
className="maputnik-button">
|
||||||
{this.state.areAllGroupsExpanded === true ?
|
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
|
||||||
t("Collapse")
|
|
||||||
:
|
|
||||||
t("Expand")
|
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -301,14 +293,14 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
onClick={this.toggleModal.bind(this, 'add')}
|
onClick={this.toggleModal.bind(this, 'add')}
|
||||||
data-wd-key="layer-list:add-layer"
|
data-wd-key="layer-list:add-layer"
|
||||||
className="maputnik-button maputnik-button-selected">
|
className="maputnik-button maputnik-button-selected">
|
||||||
{t("Add Layer")}
|
Add Layer
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div
|
<div
|
||||||
role="navigation"
|
role="navigation"
|
||||||
aria-label={t("Layers list")}
|
aria-label="Layers list"
|
||||||
>
|
>
|
||||||
<ul className="maputnik-layer-list-container">
|
<ul className="maputnik-layer-list-container">
|
||||||
{listItems}
|
{listItems}
|
||||||
@@ -318,13 +310,6 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The next two lines have react-refresh/only-export-components disabled because they are
|
|
||||||
// internal components that are not intended to be used outside of this file.
|
|
||||||
// For some reason, the linter is not recognizing these components correctly.
|
|
||||||
// When these components are migrated to functional components, the HOCs will no longer be needed
|
|
||||||
// and the comments can be removed.
|
|
||||||
// eslint-disable-next-line react-refresh/only-export-components
|
|
||||||
const LayerListContainer = withTranslation()(LayerListContainerInternal);
|
|
||||||
// eslint-disable-next-line react-refresh/only-export-components
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
const LayerListContainerSortable = SortableContainer((props: LayerListContainerProps) => <LayerListContainer {...props} />)
|
const LayerListContainerSortable = SortableContainer((props: LayerListContainerProps) => <LayerListContainer {...props} />)
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,6 @@ class LayerListItem extends React.Component<LayerListItemProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LayerListItemSortable = SortableElement<LayerListItemProps>((props: LayerListItemProps) => <LayerListItem {...props} />);
|
const LayerListItemSortable = SortableElement((props: LayerListItemProps) => <LayerListItem {...props} />);
|
||||||
|
|
||||||
export default LayerListItemSortable;
|
export default LayerListItemSortable;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import React, {type JSX} from 'react'
|
import React, {type JSX} from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import MapLibreGl, {LayerSpecification, LngLat, Map, MapOptions, SourceSpecification, StyleSpecification} from 'maplibre-gl'
|
import MapLibreGl, {LayerSpecification, LngLat, Map, MapOptions, SourceSpecification, StyleSpecification} from 'maplibre-gl'
|
||||||
import MaplibreInspect from '@maplibre/maplibre-gl-inspect'
|
// @ts-ignore
|
||||||
import colors from '@maplibre/maplibre-gl-inspect/lib/colors'
|
import MapboxInspect from 'mapbox-gl-inspect'
|
||||||
|
// @ts-ignore
|
||||||
|
import colors from 'mapbox-gl-inspect/lib/colors'
|
||||||
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup'
|
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup'
|
||||||
import MapMaplibreGlFeaturePropertyPopup, { InspectFeature } from './MapMaplibreGlFeaturePropertyPopup'
|
import MapMaplibreGlFeaturePropertyPopup, { InspectFeature } from './MapMaplibreGlFeaturePropertyPopup'
|
||||||
import Color from 'color'
|
import Color from 'color'
|
||||||
@@ -11,13 +13,13 @@ import { HighlightedLayer, colorHighlightedLayer } from '../libs/highlight'
|
|||||||
import 'maplibre-gl/dist/maplibre-gl.css'
|
import 'maplibre-gl/dist/maplibre-gl.css'
|
||||||
import '../maplibregl.css'
|
import '../maplibregl.css'
|
||||||
import '../libs/maplibre-rtl'
|
import '../libs/maplibre-rtl'
|
||||||
import MaplibreGeocoder, { MaplibreGeocoderApi, MaplibreGeocoderApiConfig } from '@maplibre/maplibre-gl-geocoder';
|
//@ts-ignore
|
||||||
|
import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder';
|
||||||
import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css';
|
import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css';
|
||||||
import { withTranslation, WithTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
function renderPopup(popup: JSX.Element, mountNode: ReactDOM.Container): HTMLElement {
|
function renderPopup(popup: JSX.Element, mountNode: ReactDOM.Container) {
|
||||||
ReactDOM.render(popup, mountNode);
|
ReactDOM.render(popup, mountNode);
|
||||||
return mountNode as HTMLElement;
|
return mountNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers: HighlightedLayer[], highlightedLayer?: HighlightedLayer) {
|
function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers: HighlightedLayer[], highlightedLayer?: HighlightedLayer) {
|
||||||
@@ -35,7 +37,6 @@ function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sources: {[key:string]: SourceSpecification} = {}
|
const sources: {[key:string]: SourceSpecification} = {}
|
||||||
|
|
||||||
Object.keys(originalMapStyle.sources).forEach(sourceId => {
|
Object.keys(originalMapStyle.sources).forEach(sourceId => {
|
||||||
const source = originalMapStyle.sources[sourceId]
|
const source = originalMapStyle.sources[sourceId]
|
||||||
if(source.type !== 'raster' && source.type !== 'raster-dem') {
|
if(source.type !== 'raster' && source.type !== 'raster-dem') {
|
||||||
@@ -51,7 +52,7 @@ function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers:
|
|||||||
return inspectStyle
|
return inspectStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
type MapMaplibreGlInternalProps = {
|
type MapMaplibreGlProps = {
|
||||||
onDataChange?(event: {map: Map | null}): unknown
|
onDataChange?(event: {map: Map | null}): unknown
|
||||||
onLayerSelect(...args: unknown[]): unknown
|
onLayerSelect(...args: unknown[]): unknown
|
||||||
mapStyle: StyleSpecification
|
mapStyle: StyleSpecification
|
||||||
@@ -64,15 +65,15 @@ type MapMaplibreGlInternalProps = {
|
|||||||
}
|
}
|
||||||
replaceAccessTokens(mapStyle: StyleSpecification): StyleSpecification
|
replaceAccessTokens(mapStyle: StyleSpecification): StyleSpecification
|
||||||
onChange(value: {center: LngLat, zoom: number}): unknown
|
onChange(value: {center: LngLat, zoom: number}): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
type MapMaplibreGlState = {
|
type MapMaplibreGlState = {
|
||||||
map: Map | null
|
map: Map | null
|
||||||
inspect: MaplibreInspect | null
|
inspect: MapboxInspect | null
|
||||||
zoom?: number
|
zoom?: number
|
||||||
};
|
};
|
||||||
|
|
||||||
class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps, MapMaplibreGlState> {
|
export default class MapMaplibreGl extends React.Component<MapMaplibreGlProps, MapMaplibreGlState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onMapLoaded: () => {},
|
onMapLoaded: () => {},
|
||||||
onDataChange: () => {},
|
onDataChange: () => {},
|
||||||
@@ -82,7 +83,7 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
}
|
}
|
||||||
container: HTMLDivElement | null = null
|
container: HTMLDivElement | null = null
|
||||||
|
|
||||||
constructor(props: MapMaplibreGlInternalProps) {
|
constructor(props: MapMaplibreGlProps) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
map: null,
|
map: null,
|
||||||
@@ -90,8 +91,18 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateMapFromProps(props: MapMaplibreGlProps) {
|
||||||
|
if(!this.state.map) return
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: MapMaplibreGlInternalProps, nextState: MapMaplibreGlState) {
|
//Maplibre GL now does diffing natively so we don't need to calculate
|
||||||
|
//the necessary operations ourselves!
|
||||||
|
this.state.map.setStyle(
|
||||||
|
this.props.replaceAccessTokens(props.mapStyle),
|
||||||
|
{diff: true}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps: MapMaplibreGlProps, nextState: MapMaplibreGlState) {
|
||||||
let should = false;
|
let should = false;
|
||||||
try {
|
try {
|
||||||
should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState);
|
should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState);
|
||||||
@@ -104,27 +115,30 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const map = this.state.map;
|
const map = this.state.map;
|
||||||
|
|
||||||
const styleWithTokens = this.props.replaceAccessTokens(this.props.mapStyle);
|
this.updateMapFromProps(this.props);
|
||||||
|
|
||||||
|
if(this.state.inspect && this.props.inspectModeEnabled !== this.state.inspect._showInspectMap) {
|
||||||
|
// HACK: Fix for <https://github.com/maplibre/maputnik/issues/576>, while we wait for a proper fix.
|
||||||
|
// eslint-disable-next-line
|
||||||
|
this.state.inspect._popupBlocked = false;
|
||||||
|
this.state.inspect.toggleInspector()
|
||||||
|
}
|
||||||
if (map) {
|
if (map) {
|
||||||
// Maplibre GL now does diffing natively so we don't need to calculate
|
if (this.props.inspectModeEnabled) {
|
||||||
// the necessary operations ourselves!
|
// HACK: We need to work out why we need to do this and what's causing
|
||||||
// We also need to update the style for inspect to work properly
|
// this error. I'm assuming an issue with maplibre-gl update and
|
||||||
map.setStyle(styleWithTokens, {diff: true});
|
// mapbox-gl-inspect.
|
||||||
|
try {
|
||||||
|
this.state.inspect.render();
|
||||||
|
} catch(err) {
|
||||||
|
console.error("FIXME: Caught error", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
map.showTileBoundaries = this.props.options?.showTileBoundaries!;
|
map.showTileBoundaries = this.props.options?.showTileBoundaries!;
|
||||||
map.showCollisionBoxes = this.props.options?.showCollisionBoxes!;
|
map.showCollisionBoxes = this.props.options?.showCollisionBoxes!;
|
||||||
map.showOverdrawInspector = this.props.options?.showOverdrawInspector!;
|
map.showOverdrawInspector = this.props.options?.showOverdrawInspector!;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.inspect && this.props.inspectModeEnabled !== this.state.inspect._showInspectMap) {
|
|
||||||
this.state.inspect.toggleInspector()
|
|
||||||
}
|
|
||||||
if (this.state.inspect && this.props.inspectModeEnabled) {
|
|
||||||
this.state.inspect.setOriginalStyle(styleWithTokens);
|
|
||||||
// In case the sources are the same, there's a need to refresh the style
|
|
||||||
setTimeout(() => {
|
|
||||||
this.state.inspect!.render();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -133,11 +147,8 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
container: this.container!,
|
container: this.container!,
|
||||||
style: this.props.mapStyle,
|
style: this.props.mapStyle,
|
||||||
hash: true,
|
hash: true,
|
||||||
maxZoom: 24,
|
maxZoom: 24
|
||||||
// setting to always load glyphs of CJK fonts from server
|
}
|
||||||
// https://maplibre.org/maplibre-gl-js/docs/examples/local-ideographs/
|
|
||||||
localIdeographFontFamily: false
|
|
||||||
} satisfies MapOptions;
|
|
||||||
|
|
||||||
const map = new MapLibreGl.Map(mapOpts);
|
const map = new MapLibreGl.Map(mapOpts);
|
||||||
|
|
||||||
@@ -154,7 +165,7 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
|
|
||||||
this.initGeocoder(map);
|
this.initGeocoder(map);
|
||||||
|
|
||||||
const zoomControl = new ZoomControl(this.props.t("Zoom:"));
|
const zoomControl = new ZoomControl;
|
||||||
map.addControl(zoomControl, 'top-right');
|
map.addControl(zoomControl, 'top-right');
|
||||||
|
|
||||||
const nav = new MapLibreGl.NavigationControl({visualizePitch:true});
|
const nav = new MapLibreGl.NavigationControl({visualizePitch:true});
|
||||||
@@ -162,7 +173,7 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
|
|
||||||
const tmpNode = document.createElement('div');
|
const tmpNode = document.createElement('div');
|
||||||
|
|
||||||
const inspect = new MaplibreInspect({
|
const inspect = new MapboxInspect({
|
||||||
popup: new MapLibreGl.Popup({
|
popup: new MapLibreGl.Popup({
|
||||||
closeOnClick: false
|
closeOnClick: false
|
||||||
}),
|
}),
|
||||||
@@ -221,7 +232,7 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
|
|
||||||
initGeocoder(map: Map) {
|
initGeocoder(map: Map) {
|
||||||
const geocoderConfig = {
|
const geocoderConfig = {
|
||||||
forwardGeocode: async (config: MaplibreGeocoderApiConfig) => {
|
forwardGeocode: async (config:{query: string, limit: number, language: string[]}) => {
|
||||||
const features = [];
|
const features = [];
|
||||||
try {
|
try {
|
||||||
const request = `https://nominatim.openstreetmap.org/search?q=${config.query}&format=geojson&polygon_geojson=1&addressdetails=1`;
|
const request = `https://nominatim.openstreetmap.org/search?q=${config.query}&format=geojson&polygon_geojson=1&addressdetails=1`;
|
||||||
@@ -254,26 +265,20 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
return {
|
return {
|
||||||
features
|
features
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
} as unknown as MaplibreGeocoderApi;
|
};
|
||||||
const geocoder = new MaplibreGeocoder(geocoderConfig, {
|
const geocoder = new MaplibreGeocoder(geocoderConfig, {maplibregl: MapLibreGl});
|
||||||
placeholder: this.props.t("Search"),
|
|
||||||
maplibregl: MapLibreGl,
|
|
||||||
});
|
|
||||||
map.addControl(geocoder, 'top-left');
|
map.addControl(geocoder, 'top-left');
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <div
|
return <div
|
||||||
className="maputnik-map__map"
|
className="maputnik-map__map"
|
||||||
role="region"
|
role="region"
|
||||||
aria-label={t("Map view")}
|
aria-label="Map view"
|
||||||
ref={x => this.container = x}
|
ref={x => this.container = x}
|
||||||
data-wd-key="maplibre:map"
|
data-wd-key="maplibre:map"
|
||||||
></div>
|
></div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MapMaplibreGl = withTranslation()(MapMaplibreGlInternal);
|
|
||||||
export default MapMaplibreGl;
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type { GeoJSONFeatureWithSourceLayer } from '@maplibre/maplibre-gl-inspect'
|
|
||||||
|
|
||||||
export type InspectFeature = GeoJSONFeatureWithSourceLayer & {
|
export type InspectFeature = {
|
||||||
|
id: string
|
||||||
|
properties: {[key:string]: any}
|
||||||
|
layer: {[key:string]: any}
|
||||||
|
geometry: GeoJSON.Geometry
|
||||||
|
sourceLayer: string
|
||||||
inspectModeCounter?: number
|
inspectModeCounter?: number
|
||||||
counter?: number
|
counter?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayValue(value: string | number | Date | object | undefined) {
|
function displayValue(value: string | number | Date | object) {
|
||||||
if (typeof value === 'undefined' || value === null) return value;
|
if (typeof value === 'undefined' || value === null) return value;
|
||||||
if (value instanceof Date) return value.toLocaleString();
|
if (value instanceof Date) return value.toLocaleString();
|
||||||
if (typeof value === 'object' ||
|
if (typeof value === 'object' ||
|
||||||
@@ -15,25 +19,25 @@ function displayValue(value: string | number | Date | object | undefined) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderKeyValueTableRow(key: string, value: string | undefined) {
|
|
||||||
return <tr key={key}>
|
|
||||||
<td className="maputnik-popup-table-cell">{key}</td>
|
|
||||||
<td className="maputnik-popup-table-cell">{value}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderFeature(feature: InspectFeature, idx: number) {
|
function renderFeature(feature: InspectFeature, idx: number) {
|
||||||
return <React.Fragment key={idx}>
|
return <div key={`${feature.sourceLayer}-${idx}`}>
|
||||||
<tr>
|
<div className="maputnik-popup-layer-id">{feature.layer['source']}: {feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div>
|
||||||
<td colSpan={2} className="maputnik-popup-layer-id">{feature.layer['source']}: {feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</td>
|
<div className="maputnik-popup-layer">
|
||||||
</tr>
|
<div className="maputnik-popup-feature-left">$type</div>
|
||||||
{renderKeyValueTableRow("$type", feature.geometry.type)}
|
<div className="maputnik-popup-feature-right">{feature.geometry.type}</div>
|
||||||
{renderKeyValueTableRow("$id", displayValue(feature.id))}
|
</div>
|
||||||
|
<div className="maputnik-popup-layer">
|
||||||
|
<div className="maputnik-popup-feature-left">feature_id</div>
|
||||||
|
<div className="maputnik-popup-feature-right">{displayValue(feature.id)}</div>
|
||||||
|
</div>
|
||||||
{Object.keys(feature.properties).map(propertyName => {
|
{Object.keys(feature.properties).map(propertyName => {
|
||||||
const property = feature.properties[propertyName];
|
const property = feature.properties[propertyName];
|
||||||
return renderKeyValueTableRow(propertyName, displayValue(property))
|
return <div key={propertyName} className="maputnik-popup-layer">
|
||||||
|
<div className="maputnik-popup-feature-left">{propertyName}</div>
|
||||||
|
<div className="maputnik-popup-feature-right">{displayValue(property)}</div>
|
||||||
|
</div>
|
||||||
})}
|
})}
|
||||||
</React.Fragment>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeDuplicatedFeatures(features: InspectFeature[]) {
|
function removeDuplicatedFeatures(features: InspectFeature[]) {
|
||||||
@@ -67,11 +71,7 @@ class FeaturePropertyPopup extends React.Component<FeaturePropertyPopupProps> {
|
|||||||
render() {
|
render() {
|
||||||
const features = removeDuplicatedFeatures(this.props.features)
|
const features = removeDuplicatedFeatures(this.props.features)
|
||||||
return <div className="maputnik-feature-property-popup">
|
return <div className="maputnik-feature-property-popup">
|
||||||
<table className="maputnik-popup-table">
|
{features.map(renderFeature)}
|
||||||
<tbody>
|
|
||||||
{features.map(renderFeature)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,16 +8,15 @@ function groupFeaturesBySourceLayer(features: InspectFeature[]) {
|
|||||||
const returnedFeatures: {[key: string]: number} = {}
|
const returnedFeatures: {[key: string]: number} = {}
|
||||||
|
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
const sourceKey = feature.layer['source-layer'] as string;
|
|
||||||
if(Object.prototype.hasOwnProperty.call(returnedFeatures, feature.layer.id)) {
|
if(Object.prototype.hasOwnProperty.call(returnedFeatures, feature.layer.id)) {
|
||||||
returnedFeatures[feature.layer.id]++
|
returnedFeatures[feature.layer.id]++
|
||||||
|
|
||||||
const featureObject = sources[sourceKey].find((f: InspectFeature) => f.layer.id === feature.layer.id)
|
const featureObject = sources[feature.layer['source-layer']].find((f: InspectFeature) => f.layer.id === feature.layer.id)
|
||||||
|
|
||||||
featureObject!.counter = returnedFeatures[feature.layer.id]
|
featureObject!.counter = returnedFeatures[feature.layer.id]
|
||||||
} else {
|
} else {
|
||||||
sources[sourceKey] = sources[sourceKey] || []
|
sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || []
|
||||||
sources[sourceKey].push(feature)
|
sources[feature.layer['source-layer']].push(feature)
|
||||||
|
|
||||||
returnedFeatures[feature.layer.id] = 1
|
returnedFeatures[feature.layer.id] = 1
|
||||||
}
|
}
|
||||||
@@ -41,21 +40,29 @@ class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const paintProps = feature.layer.paint;
|
const paintProps = feature.layer.paint;
|
||||||
|
let propName;
|
||||||
|
|
||||||
if("text-color" in paintProps && paintProps["text-color"]) {
|
if(Object.prototype.hasOwnProperty.call(paintProps, "text-color") && paintProps["text-color"]) {
|
||||||
return String(paintProps["text-color"]);
|
propName = "text-color";
|
||||||
}
|
}
|
||||||
if ("fill-color" in paintProps && paintProps["fill-color"]) {
|
else if (Object.prototype.hasOwnProperty.call(paintProps, "fill-color") && paintProps["fill-color"]) {
|
||||||
return String(paintProps["fill-color"]);
|
propName = "fill-color";
|
||||||
}
|
}
|
||||||
if ("line-color" in paintProps && paintProps["line-color"]) {
|
else if (Object.prototype.hasOwnProperty.call(paintProps, "line-color") && paintProps["line-color"]) {
|
||||||
return String(paintProps["line-color"]);
|
propName = "line-color";
|
||||||
}
|
}
|
||||||
if ("fill-extrusion-color" in paintProps && paintProps["fill-extrusion-color"]) {
|
else if (Object.prototype.hasOwnProperty.call(paintProps, "fill-extrusion-color") && paintProps["fill-extrusion-color"]) {
|
||||||
return String(paintProps["fill-extrusion-color"]);
|
propName = "fill-extrusion-color";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(propName) {
|
||||||
|
const color = feature.layer.paint[propName];
|
||||||
|
return String(color);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Default color
|
||||||
|
return "black";
|
||||||
}
|
}
|
||||||
// Default color
|
|
||||||
return "black";
|
|
||||||
}
|
}
|
||||||
// This is quite complex, just incase there's an edgecase we're missing
|
// This is quite complex, just incase there's an edgecase we're missing
|
||||||
// always return black if we get an unexpected error.
|
// always return black if we get an unexpected error.
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {throttle} from 'lodash';
|
import {throttle} from 'lodash';
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup';
|
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup';
|
||||||
|
|
||||||
import 'ol/ol.css'
|
import 'ol/ol.css'
|
||||||
//@ts-ignore
|
|
||||||
import {apply} from 'ol-mapbox-style';
|
import {apply} from 'ol-mapbox-style';
|
||||||
import {Map, View, Overlay} from 'ol';
|
import {Map, View, Overlay} from 'ol';
|
||||||
|
|
||||||
@@ -24,7 +22,7 @@ function renderCoords (coords: string[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type MapOpenLayersInternalProps = {
|
type MapOpenLayersProps = {
|
||||||
onDataChange?(...args: unknown[]): unknown
|
onDataChange?(...args: unknown[]): unknown
|
||||||
mapStyle: object
|
mapStyle: object
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
@@ -33,7 +31,7 @@ type MapOpenLayersInternalProps = {
|
|||||||
debugToolbox: boolean
|
debugToolbox: boolean
|
||||||
replaceAccessTokens(...args: unknown[]): unknown
|
replaceAccessTokens(...args: unknown[]): unknown
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
type MapOpenLayersState = {
|
type MapOpenLayersState = {
|
||||||
zoom: string
|
zoom: string
|
||||||
@@ -43,7 +41,7 @@ type MapOpenLayersState = {
|
|||||||
selectedFeatures?: any[]
|
selectedFeatures?: any[]
|
||||||
};
|
};
|
||||||
|
|
||||||
class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps, MapOpenLayersState> {
|
export default class MapOpenLayers extends React.Component<MapOpenLayersProps, MapOpenLayersState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onMapLoaded: () => {},
|
onMapLoaded: () => {},
|
||||||
onDataChange: () => {},
|
onDataChange: () => {},
|
||||||
@@ -55,7 +53,7 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
overlay: Overlay | undefined;
|
overlay: Overlay | undefined;
|
||||||
popupContainer: HTMLElement | null = null;
|
popupContainer: HTMLElement | null = null;
|
||||||
|
|
||||||
constructor(props: MapOpenLayersInternalProps) {
|
constructor(props: MapOpenLayersProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
zoom: "0",
|
zoom: "0",
|
||||||
@@ -74,7 +72,7 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
apply(this.map, newMapStyle);
|
apply(this.map, newMapStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: MapOpenLayersInternalProps) {
|
componentDidUpdate(prevProps: MapOpenLayersProps) {
|
||||||
if (this.props.mapStyle !== prevProps.mapStyle) {
|
if (this.props.mapStyle !== prevProps.mapStyle) {
|
||||||
this.updateStyle(
|
this.updateStyle(
|
||||||
this.props.replaceAccessTokens(this.props.mapStyle)
|
this.props.replaceAccessTokens(this.props.mapStyle)
|
||||||
@@ -152,7 +150,6 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <div className="maputnik-ol-container">
|
return <div className="maputnik-ol-container">
|
||||||
<div
|
<div
|
||||||
ref={x => this.popupContainer = x}
|
ref={x => this.popupContainer = x}
|
||||||
@@ -162,7 +159,7 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
<button
|
<button
|
||||||
className="maplibregl-popup-close-button"
|
className="maplibregl-popup-close-button"
|
||||||
onClick={this.closeOverlay}
|
onClick={this.closeOverlay}
|
||||||
aria-label={t("Close popup")}
|
aria-label="Close popup"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
@@ -172,20 +169,20 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-ol-zoom">
|
<div className="maputnik-ol-zoom">
|
||||||
{t("Zoom:")} {this.state.zoom}
|
Zoom: {this.state.zoom}
|
||||||
</div>
|
</div>
|
||||||
{this.props.debugToolbox &&
|
{this.props.debugToolbox &&
|
||||||
<div className="maputnik-ol-debug">
|
<div className="maputnik-ol-debug">
|
||||||
<div>
|
<div>
|
||||||
<label>{t("cursor:")} </label>
|
<label>cursor: </label>
|
||||||
<span>{renderCoords(this.state.cursor)}</span>
|
<span>{renderCoords(this.state.cursor)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>{t("center:")} </label>
|
<label>center: </label>
|
||||||
<span>{renderCoords(this.state.center)}</span>
|
<span>{renderCoords(this.state.center)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>{t("rotation:")} </label>
|
<label>rotation: </label>
|
||||||
<span>{this.state.rotation}</span>
|
<span>{this.state.rotation}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -194,7 +191,7 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
className="maputnik-ol"
|
className="maputnik-ol"
|
||||||
ref={x => this.container = x}
|
ref={x => this.container = x}
|
||||||
role="region"
|
role="region"
|
||||||
aria-label={t("Map view")}
|
aria-label="Map view"
|
||||||
style={{
|
style={{
|
||||||
...this.props.style,
|
...this.props.style,
|
||||||
}}>
|
}}>
|
||||||
@@ -203,5 +200,3 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MapOpenLayers = withTranslation()(MapOpenLayersInternal);
|
|
||||||
export default MapOpenLayers;
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { PropsWithChildren } from 'react'
|
import React from 'react'
|
||||||
import {MdClose} from 'react-icons/md'
|
import {MdClose} from 'react-icons/md'
|
||||||
import AriaModal from 'react-aria-modal'
|
import AriaModal from 'react-aria-modal'
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type ModalInternalProps = PropsWithChildren & {
|
|
||||||
|
type ModalProps = {
|
||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
title: string
|
title: string
|
||||||
@@ -12,10 +12,10 @@ type ModalInternalProps = PropsWithChildren & {
|
|||||||
underlayClickExits?: boolean
|
underlayClickExits?: boolean
|
||||||
underlayProps?: any
|
underlayProps?: any
|
||||||
className?: string
|
className?: string
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
|
|
||||||
class ModalInternal extends React.Component<ModalInternalProps> {
|
export default class Modal extends React.Component<ModalProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
underlayClickExits: true
|
underlayClickExits: true
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,6 @@ class ModalInternal extends React.Component<ModalInternalProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
if(this.props.isOpen) {
|
if(this.props.isOpen) {
|
||||||
return <AriaModal
|
return <AriaModal
|
||||||
titleText={this.props.title}
|
titleText={this.props.title}
|
||||||
@@ -50,7 +49,7 @@ class ModalInternal extends React.Component<ModalInternalProps> {
|
|||||||
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
|
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
|
||||||
<span className="maputnik-modal-header-space"></span>
|
<span className="maputnik-modal-header-space"></span>
|
||||||
<button className="maputnik-modal-header-toggle"
|
<button className="maputnik-modal-header-toggle"
|
||||||
title={t("Close modal")}
|
title="Close modal"
|
||||||
onClick={this.onClose}
|
onClick={this.onClose}
|
||||||
data-wd-key={this.props["data-wd-key"]+".close-modal"}
|
data-wd-key={this.props["data-wd-key"]+".close-modal"}
|
||||||
>
|
>
|
||||||
@@ -69,5 +68,3 @@ class ModalInternal extends React.Component<ModalInternalProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modal = withTranslation()(ModalInternal);
|
|
||||||
export default Modal;
|
|
||||||
|
|||||||
@@ -7,16 +7,15 @@ import FieldId from './FieldId'
|
|||||||
import FieldSource from './FieldSource'
|
import FieldSource from './FieldSource'
|
||||||
import FieldSourceLayer from './FieldSourceLayer'
|
import FieldSourceLayer from './FieldSourceLayer'
|
||||||
import type {LayerSpecification} from 'maplibre-gl'
|
import type {LayerSpecification} from 'maplibre-gl'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type ModalAddInternalProps = {
|
type ModalAddProps = {
|
||||||
layers: LayerSpecification[]
|
layers: LayerSpecification[]
|
||||||
onLayersChange(layers: LayerSpecification[]): unknown
|
onLayersChange(layers: LayerSpecification[]): unknown
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onOpenToggle(open: boolean): unknown
|
onOpenToggle(open: boolean): unknown
|
||||||
// A dict of source id's and the available source layers
|
// A dict of source id's and the available source layers
|
||||||
sources: any
|
sources: any
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
type ModalAddState = {
|
type ModalAddState = {
|
||||||
type: LayerSpecification["type"]
|
type: LayerSpecification["type"]
|
||||||
@@ -25,7 +24,7 @@ type ModalAddState = {
|
|||||||
'source-layer'?: string
|
'source-layer'?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddState> {
|
export default class ModalAdd extends React.Component<ModalAddProps, ModalAddState> {
|
||||||
addLayer = () => {
|
addLayer = () => {
|
||||||
const changedLayers = this.props.layers.slice(0)
|
const changedLayers = this.props.layers.slice(0)
|
||||||
const layer: ModalAddState = {
|
const layer: ModalAddState = {
|
||||||
@@ -46,7 +45,7 @@ class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddSt
|
|||||||
this.props.onOpenToggle(false)
|
this.props.onOpenToggle(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: ModalAddInternalProps) {
|
constructor(props: ModalAddProps) {
|
||||||
super(props)
|
super(props)
|
||||||
const state: ModalAddState = {
|
const state: ModalAddState = {
|
||||||
type: 'fill',
|
type: 'fill',
|
||||||
@@ -55,12 +54,12 @@ class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddSt
|
|||||||
|
|
||||||
if(props.sources.length > 0) {
|
if(props.sources.length > 0) {
|
||||||
state.source = Object.keys(this.props.sources)[0];
|
state.source = Object.keys(this.props.sources)[0];
|
||||||
state['source-layer'] = this.props.sources[state.source as keyof ModalAddInternalProps["sources"]][0]
|
state['source-layer'] = this.props.sources[state.source as keyof ModalAddProps["sources"]][0]
|
||||||
}
|
}
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(_prevProps: ModalAddInternalProps, prevState: ModalAddState) {
|
componentDidUpdate(_prevProps: ModalAddProps, prevState: ModalAddState) {
|
||||||
// Check if source is valid for new type
|
// Check if source is valid for new type
|
||||||
const oldType = prevState.type;
|
const oldType = prevState.type;
|
||||||
const newType = this.state.type;
|
const newType = this.state.type;
|
||||||
@@ -126,14 +125,13 @@ class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddSt
|
|||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const sources = this.getSources(this.state.type);
|
const sources = this.getSources(this.state.type);
|
||||||
const layers = this.getLayersForSource(this.state.source!);
|
const layers = this.getLayersForSource(this.state.source!);
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Add Layer')}
|
title={'Add Layer'}
|
||||||
data-wd-key="modal:add-layer"
|
data-wd-key="modal:add-layer"
|
||||||
className="maputnik-add-modal"
|
className="maputnik-add-modal"
|
||||||
>
|
>
|
||||||
@@ -171,12 +169,10 @@ class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddSt
|
|||||||
onClick={this.addLayer}
|
onClick={this.addLayer}
|
||||||
data-wd-key="add-layer"
|
data-wd-key="add-layer"
|
||||||
>
|
>
|
||||||
{t("Add Layer")}
|
Add Layer
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalAdd = withTranslation()(ModalAddInternal);
|
|
||||||
export default ModalAdd;
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
|
||||||
type ModalDebugInternalProps = {
|
type ModalDebugProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
renderer: string
|
renderer: string
|
||||||
onChangeMaplibreGlDebug(key: string, checked: boolean): unknown
|
onChangeMaplibreGlDebug(key: string, checked: boolean): unknown
|
||||||
@@ -19,12 +18,12 @@ type ModalDebugInternalProps = {
|
|||||||
lat: number
|
lat: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
|
|
||||||
class ModalDebugInternal extends React.Component<ModalDebugInternalProps> {
|
export default class ModalDebug extends React.Component<ModalDebugProps> {
|
||||||
render() {
|
render() {
|
||||||
const {t, mapView} = this.props;
|
const {mapView} = this.props;
|
||||||
|
|
||||||
const osmZoom = Math.round(mapView.zoom)+1;
|
const osmZoom = Math.round(mapView.zoom)+1;
|
||||||
const osmLon = +(mapView.center.lng).toFixed(5);
|
const osmLon = +(mapView.center.lng).toFixed(5);
|
||||||
@@ -34,10 +33,10 @@ class ModalDebugInternal extends React.Component<ModalDebugInternalProps> {
|
|||||||
data-wd-key="modal:debug"
|
data-wd-key="modal:debug"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Debug')}
|
title={'Debug'}
|
||||||
>
|
>
|
||||||
<section className="maputnik-modal-section maputnik-modal-shortcuts">
|
<section className="maputnik-modal-section maputnik-modal-shortcuts">
|
||||||
<h1>{t("Options")}</h1>
|
<h1>Options</h1>
|
||||||
{this.props.renderer === 'mlgljs' &&
|
{this.props.renderer === 'mlgljs' &&
|
||||||
<ul>
|
<ul>
|
||||||
{Object.entries(this.props.maplibreGlDebugOptions!).map(([key, val]) => {
|
{Object.entries(this.props.maplibreGlDebugOptions!).map(([key, val]) => {
|
||||||
@@ -64,20 +63,16 @@ class ModalDebugInternal extends React.Component<ModalDebugInternalProps> {
|
|||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h1>Links</h1>
|
<h1>Links</h1>
|
||||||
<p>
|
<p>
|
||||||
<Trans t={t}>
|
<a
|
||||||
<a
|
target="_blank"
|
||||||
target="_blank"
|
rel="noopener noreferrer"
|
||||||
rel="noopener noreferrer"
|
href={`https://www.openstreetmap.org/#map=${osmZoom}/${osmLat}/${osmLon}`}
|
||||||
href={`https://www.openstreetmap.org/#map=${osmZoom}/${osmLat}/${osmLon}`}
|
>
|
||||||
>
|
Open in OSM
|
||||||
Open in OSM
|
</a> — Opens the current view on openstreetmap.org
|
||||||
</a> — Opens the current view on openstreetmap.org
|
|
||||||
</Trans>
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalDebug = withTranslation()(ModalDebugInternal);
|
|
||||||
export default ModalDebug;
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {version} from 'maplibre-gl/package.json'
|
|||||||
import {format} from '@maplibre/maplibre-gl-style-spec'
|
import {format} from '@maplibre/maplibre-gl-style-spec'
|
||||||
import type {StyleSpecification} from 'maplibre-gl'
|
import type {StyleSpecification} from 'maplibre-gl'
|
||||||
import {MdFileDownload} from 'react-icons/md'
|
import {MdFileDownload} from 'react-icons/md'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import FieldString from './FieldString'
|
import FieldString from './FieldString'
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
@@ -17,15 +16,15 @@ import fieldSpecAdditional from '../libs/field-spec-additional'
|
|||||||
const MAPLIBRE_GL_VERSION = version;
|
const MAPLIBRE_GL_VERSION = version;
|
||||||
|
|
||||||
|
|
||||||
type ModalExportInternalProps = {
|
type ModalExportProps = {
|
||||||
mapStyle: StyleSpecification & { id: string }
|
mapStyle: StyleSpecification & { id: string }
|
||||||
onStyleChanged(...args: unknown[]): unknown
|
onStyleChanged(...args: unknown[]): unknown
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onOpenToggle(...args: unknown[]): unknown
|
onOpenToggle(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
|
|
||||||
class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
export default class ModalExport extends React.Component<ModalExportProps> {
|
||||||
|
|
||||||
tokenizedStyle () {
|
tokenizedStyle () {
|
||||||
return format(
|
return format(
|
||||||
@@ -49,7 +48,7 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
|
|
||||||
downloadHtml() {
|
downloadHtml() {
|
||||||
const tokenStyle = this.tokenizedStyle();
|
const tokenStyle = this.tokenizedStyle();
|
||||||
const htmlTitle = this.props.mapStyle.name || this.props.t("Map");
|
const htmlTitle = this.props.mapStyle.name || "Map";
|
||||||
const html = `<!DOCTYPE html>
|
const html = `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -101,32 +100,30 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const fsa = fieldSpecAdditional(t);
|
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="modal:export"
|
data-wd-key="modal:export"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Export Style')}
|
title={'Export Style'}
|
||||||
className="maputnik-export-modal"
|
className="maputnik-export-modal"
|
||||||
>
|
>
|
||||||
|
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h1>{t("Download Style")}</h1>
|
<h1>Download Style</h1>
|
||||||
<p>
|
<p>
|
||||||
{t("Download a JSON style to your computer.")}
|
Download a JSON style to your computer.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FieldString
|
<FieldString
|
||||||
label={fsa.maputnik.maptiler_access_token.label}
|
label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.maptiler_access_token}
|
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
|
||||||
value={(this.props.mapStyle.metadata || {} as any)['maputnik:openmaptiles_access_token']}
|
value={(this.props.mapStyle.metadata || {} as any)['maputnik:openmaptiles_access_token']}
|
||||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
||||||
/>
|
/>
|
||||||
<FieldString
|
<FieldString
|
||||||
label={fsa.maputnik.thunderforest_access_token.label}
|
label={fieldSpecAdditional.maputnik.thunderforest_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.thunderforest_access_token}
|
fieldSpec={fieldSpecAdditional.maputnik.thunderforest_access_token}
|
||||||
value={(this.props.mapStyle.metadata || {} as any)['maputnik:thunderforest_access_token']}
|
value={(this.props.mapStyle.metadata || {} as any)['maputnik:thunderforest_access_token']}
|
||||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
|
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
|
||||||
/>
|
/>
|
||||||
@@ -137,14 +134,14 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
onClick={this.downloadStyle.bind(this)}
|
onClick={this.downloadStyle.bind(this)}
|
||||||
>
|
>
|
||||||
<MdFileDownload />
|
<MdFileDownload />
|
||||||
{t("Download Style")}
|
Download Style
|
||||||
</InputButton>
|
</InputButton>
|
||||||
|
|
||||||
<InputButton
|
<InputButton
|
||||||
onClick={this.downloadHtml.bind(this)}
|
onClick={this.downloadHtml.bind(this)}
|
||||||
>
|
>
|
||||||
<MdFileDownload />
|
<MdFileDownload />
|
||||||
{t("Download HTML")}
|
Download HTML
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -153,5 +150,3 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalExport = withTranslation()(ModalExportInternal);
|
|
||||||
export default ModalExport;
|
|
||||||
|
|||||||
@@ -2,25 +2,23 @@ import React from 'react'
|
|||||||
|
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
|
|
||||||
type ModalLoadingInternalProps = {
|
type ModalLoadingProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onCancel(...args: unknown[]): unknown
|
onCancel(...args: unknown[]): unknown
|
||||||
title: string
|
title: string
|
||||||
message: React.ReactNode
|
message: React.ReactNode
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
|
|
||||||
class ModalLoadingInternal extends React.Component<ModalLoadingInternalProps> {
|
export default class ModalLoading extends React.Component<ModalLoadingProps> {
|
||||||
underlayOnClick(e: Event) {
|
underlayOnClick(e: Event) {
|
||||||
// This stops click events falling through to underlying modals.
|
// This stops click events falling through to underlying modals.
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="modal:loading"
|
data-wd-key="modal:loading"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
@@ -37,12 +35,10 @@ class ModalLoadingInternal extends React.Component<ModalLoadingInternalProps> {
|
|||||||
</p>
|
</p>
|
||||||
<p className="maputnik-dialog__buttons">
|
<p className="maputnik-dialog__buttons">
|
||||||
<InputButton onClick={(e) => this.props.onCancel(e)}>
|
<InputButton onClick={(e) => this.props.onCancel(e)}>
|
||||||
{t("Cancel")}
|
Cancel
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</p>
|
</p>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalLoading = withTranslation()(ModalLoadingInternal);
|
|
||||||
export default ModalLoading;
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React, { FormEvent } from 'react'
|
|||||||
import {MdFileUpload} from 'react-icons/md'
|
import {MdFileUpload} from 'react-icons/md'
|
||||||
import {MdAddCircleOutline} from 'react-icons/md'
|
import {MdAddCircleOutline} from 'react-icons/md'
|
||||||
import FileReaderInput, { Result } from 'react-file-reader-input'
|
import FileReaderInput, { Result } from 'react-file-reader-input'
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import ModalLoading from './ModalLoading'
|
import ModalLoading from './ModalLoading'
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
@@ -43,11 +42,11 @@ class PublicStyle extends React.Component<PublicStyleProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModalOpenInternalProps = {
|
type ModalOpenProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onOpenToggle(...args: unknown[]): unknown
|
onOpenToggle(...args: unknown[]): unknown
|
||||||
onStyleOpen(...args: unknown[]): unknown
|
onStyleOpen(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
type ModalOpenState = {
|
type ModalOpenState = {
|
||||||
styleUrl: string
|
styleUrl: string
|
||||||
@@ -56,8 +55,8 @@ type ModalOpenState = {
|
|||||||
activeRequestUrl?: string | null
|
activeRequestUrl?: string | null
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpenState> {
|
export default class ModalOpen extends React.Component<ModalOpenProps, ModalOpenState> {
|
||||||
constructor(props: ModalOpenInternalProps) {
|
constructor(props: ModalOpenProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
styleUrl: ""
|
styleUrl: ""
|
||||||
@@ -175,7 +174,6 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const styleOptions = publicStyles.map(style => {
|
const styleOptions = publicStyles.map(style => {
|
||||||
return <PublicStyle
|
return <PublicStyle
|
||||||
key={style.id}
|
key={style.id}
|
||||||
@@ -202,31 +200,29 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
data-wd-key="modal:open"
|
data-wd-key="modal:open"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={() => this.onOpenToggle()}
|
onOpenToggle={() => this.onOpenToggle()}
|
||||||
title={t('Open Style')}
|
title={'Open Style'}
|
||||||
>
|
>
|
||||||
{errorElement}
|
{errorElement}
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h1>{t("Upload Style")}</h1>
|
<h1>Upload Style</h1>
|
||||||
<p>{t("Upload a JSON style from your computer.")}</p>
|
<p>Upload a JSON style from your computer.</p>
|
||||||
<FileReaderInput onChange={this.onUpload} tabIndex={-1} aria-label={t("Style file")}>
|
<FileReaderInput onChange={this.onUpload} tabIndex={-1} aria-label="Style file">
|
||||||
<InputButton className="maputnik-upload-button"><MdFileUpload /> {t("Upload")}</InputButton>
|
<InputButton className="maputnik-upload-button"><MdFileUpload /> Upload</InputButton>
|
||||||
</FileReaderInput>
|
</FileReaderInput>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<form onSubmit={this.onSubmitUrl}>
|
<form onSubmit={this.onSubmitUrl}>
|
||||||
<h1>{t("Load from URL")}</h1>
|
<h1>Load from URL</h1>
|
||||||
<p>
|
<p>
|
||||||
<Trans t={t}>
|
Load from a URL. Note that the URL must have <a href="https://enable-cors.org" target="_blank" rel="noopener noreferrer">CORS enabled</a>.
|
||||||
Load from a URL. Note that the URL must have <a href="https://enable-cors.org" target="_blank" rel="noopener noreferrer">CORS enabled</a>.
|
|
||||||
</Trans>
|
|
||||||
</p>
|
</p>
|
||||||
<InputUrl
|
<InputUrl
|
||||||
aria-label={t("Style URL")}
|
aria-label="Style URL"
|
||||||
data-wd-key="modal:open.url.input"
|
data-wd-key="modal:open.url.input"
|
||||||
type="text"
|
type="text"
|
||||||
className="maputnik-input"
|
className="maputnik-input"
|
||||||
default={t("Enter URL...")}
|
default="Enter URL..."
|
||||||
value={this.state.styleUrl}
|
value={this.state.styleUrl}
|
||||||
onInput={this.onChangeUrl}
|
onInput={this.onChangeUrl}
|
||||||
onChange={this.onChangeUrl}
|
onChange={this.onChangeUrl}
|
||||||
@@ -243,9 +239,9 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="maputnik-modal-section maputnik-modal-section--shrink">
|
<section className="maputnik-modal-section maputnik-modal-section--shrink">
|
||||||
<h1>{t("Gallery Styles")}</h1>
|
<h1>Gallery Styles</h1>
|
||||||
<p>
|
<p>
|
||||||
{t("Open one of the publicly available styles to start from.")}
|
Open one of the publicly available styles to start from.
|
||||||
</p>
|
</p>
|
||||||
<div className="maputnik-style-gallery-container">
|
<div className="maputnik-style-gallery-container">
|
||||||
{styleOptions}
|
{styleOptions}
|
||||||
@@ -255,14 +251,12 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
|
|
||||||
<ModalLoading
|
<ModalLoading
|
||||||
isOpen={!!this.state.activeRequest}
|
isOpen={!!this.state.activeRequest}
|
||||||
title={t('Loading style')}
|
title={'Loading style'}
|
||||||
onCancel={(e: Event) => this.onCancelActiveRequest(e)}
|
onCancel={(e: Event) => this.onCancelActiveRequest(e)}
|
||||||
message={t("Loading: {{requestUrl}}", { requestUrl: this.state.activeRequestUrl })}
|
message={"Loading: "+this.state.activeRequestUrl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalOpen = withTranslation()(ModalOpenInternal);
|
|
||||||
export default ModalOpen;
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import type {LightSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from 'maplibre-gl'
|
import type {LightSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from 'maplibre-gl'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import FieldArray from './FieldArray'
|
import FieldArray from './FieldArray'
|
||||||
import FieldNumber from './FieldNumber'
|
import FieldNumber from './FieldNumber'
|
||||||
@@ -13,15 +12,15 @@ import FieldColor from './FieldColor'
|
|||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import fieldSpecAdditional from '../libs/field-spec-additional'
|
import fieldSpecAdditional from '../libs/field-spec-additional'
|
||||||
|
|
||||||
type ModalSettingsInternalProps = {
|
type ModalSettingsProps = {
|
||||||
mapStyle: StyleSpecification
|
mapStyle: StyleSpecification
|
||||||
onStyleChanged(...args: unknown[]): unknown
|
onStyleChanged(...args: unknown[]): unknown
|
||||||
onChangeMetadataProperty(...args: unknown[]): unknown
|
onChangeMetadataProperty(...args: unknown[]): unknown
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onOpenToggle(...args: unknown[]): unknown
|
onOpenToggle(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps> {
|
export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
||||||
changeTransitionProperty(property: keyof TransitionSpecification, value: number | undefined) {
|
changeTransitionProperty(property: keyof TransitionSpecification, value: number | undefined) {
|
||||||
const transition = {
|
const transition = {
|
||||||
...this.props.mapStyle.transition,
|
...this.props.mapStyle.transition,
|
||||||
@@ -96,8 +95,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const metadata = this.props.mapStyle.metadata || {} as any;
|
const metadata = this.props.mapStyle.metadata || {} as any;
|
||||||
const {t, onChangeMetadataProperty, mapStyle} = this.props;
|
const {onChangeMetadataProperty, mapStyle} = this.props;
|
||||||
const fsa = fieldSpecAdditional(t);
|
|
||||||
|
|
||||||
const light = this.props.mapStyle.light || {};
|
const light = this.props.mapStyle.light || {};
|
||||||
const transition = this.props.mapStyle.transition || {};
|
const transition = this.props.mapStyle.transition || {};
|
||||||
@@ -107,33 +105,33 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
data-wd-key="modal:settings"
|
data-wd-key="modal:settings"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Style Settings')}
|
title={'Style Settings'}
|
||||||
>
|
>
|
||||||
<div className="modal:settings">
|
<div className="modal:settings">
|
||||||
<FieldString
|
<FieldString
|
||||||
label={t("Name")}
|
label={"Name"}
|
||||||
fieldSpec={latest.$root.name}
|
fieldSpec={latest.$root.name}
|
||||||
data-wd-key="modal:settings.name"
|
data-wd-key="modal:settings.name"
|
||||||
value={this.props.mapStyle.name}
|
value={this.props.mapStyle.name}
|
||||||
onChange={this.changeStyleProperty.bind(this, "name")}
|
onChange={this.changeStyleProperty.bind(this, "name")}
|
||||||
/>
|
/>
|
||||||
<FieldString
|
<FieldString
|
||||||
label={t("Owner")}
|
label={"Owner"}
|
||||||
fieldSpec={{doc: t("Owner ID of the style. Used by Mapbox or future style APIs.")}}
|
fieldSpec={{doc: "Owner ID of the style. Used by Mapbox or future style APIs."}}
|
||||||
data-wd-key="modal:settings.owner"
|
data-wd-key="modal:settings.owner"
|
||||||
value={(this.props.mapStyle as any).owner}
|
value={(this.props.mapStyle as any).owner}
|
||||||
onChange={this.changeStyleProperty.bind(this, "owner")}
|
onChange={this.changeStyleProperty.bind(this, "owner")}
|
||||||
/>
|
/>
|
||||||
<FieldUrl
|
<FieldUrl
|
||||||
fieldSpec={latest.$root.sprite}
|
fieldSpec={latest.$root.sprite}
|
||||||
label={t("Sprite URL")}
|
label="Sprite URL"
|
||||||
data-wd-key="modal:settings.sprite"
|
data-wd-key="modal:settings.sprite"
|
||||||
value={this.props.mapStyle.sprite as string}
|
value={this.props.mapStyle.sprite as string}
|
||||||
onChange={this.changeStyleProperty.bind(this, "sprite")}
|
onChange={this.changeStyleProperty.bind(this, "sprite")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldUrl
|
<FieldUrl
|
||||||
label={t("Glyphs URL")}
|
label="Glyphs URL"
|
||||||
fieldSpec={latest.$root.glyphs}
|
fieldSpec={latest.$root.glyphs}
|
||||||
data-wd-key="modal:settings.glyphs"
|
data-wd-key="modal:settings.glyphs"
|
||||||
value={this.props.mapStyle.glyphs as string}
|
value={this.props.mapStyle.glyphs as string}
|
||||||
@@ -141,23 +139,23 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldString
|
<FieldString
|
||||||
label={fsa.maputnik.maptiler_access_token.label}
|
label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.maptiler_access_token}
|
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
|
||||||
data-wd-key="modal:settings.maputnik:openmaptiles_access_token"
|
data-wd-key="modal:settings.maputnik:openmaptiles_access_token"
|
||||||
value={metadata['maputnik:openmaptiles_access_token']}
|
value={metadata['maputnik:openmaptiles_access_token']}
|
||||||
onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldString
|
<FieldString
|
||||||
label={fsa.maputnik.thunderforest_access_token.label}
|
label={fieldSpecAdditional.maputnik.thunderforest_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.thunderforest_access_token}
|
fieldSpec={fieldSpecAdditional.maputnik.thunderforest_access_token}
|
||||||
data-wd-key="modal:settings.maputnik:thunderforest_access_token"
|
data-wd-key="modal:settings.maputnik:thunderforest_access_token"
|
||||||
value={metadata['maputnik:thunderforest_access_token']}
|
value={metadata['maputnik:thunderforest_access_token']}
|
||||||
onChange={onChangeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldArray
|
<FieldArray
|
||||||
label={t("Center")}
|
label={"Center"}
|
||||||
fieldSpec={latest.$root.center}
|
fieldSpec={latest.$root.center}
|
||||||
length={2}
|
length={2}
|
||||||
type="number"
|
type="number"
|
||||||
@@ -167,7 +165,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Zoom")}
|
label={"Zoom"}
|
||||||
fieldSpec={latest.$root.zoom}
|
fieldSpec={latest.$root.zoom}
|
||||||
value={mapStyle.zoom}
|
value={mapStyle.zoom}
|
||||||
default={0}
|
default={0}
|
||||||
@@ -175,7 +173,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Bearing")}
|
label={"Bearing"}
|
||||||
fieldSpec={latest.$root.bearing}
|
fieldSpec={latest.$root.bearing}
|
||||||
value={mapStyle.bearing}
|
value={mapStyle.bearing}
|
||||||
default={latest.$root.bearing.default}
|
default={latest.$root.bearing.default}
|
||||||
@@ -183,7 +181,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Pitch")}
|
label={"Pitch"}
|
||||||
fieldSpec={latest.$root.pitch}
|
fieldSpec={latest.$root.pitch}
|
||||||
value={mapStyle.pitch}
|
value={mapStyle.pitch}
|
||||||
default={latest.$root.pitch.default}
|
default={latest.$root.pitch.default}
|
||||||
@@ -191,7 +189,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldEnum
|
<FieldEnum
|
||||||
label={t("Light anchor")}
|
label={"Light anchor"}
|
||||||
fieldSpec={latest.light.anchor}
|
fieldSpec={latest.light.anchor}
|
||||||
name="light-anchor"
|
name="light-anchor"
|
||||||
value={light.anchor as string}
|
value={light.anchor as string}
|
||||||
@@ -201,7 +199,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldColor
|
<FieldColor
|
||||||
label={t("Light color")}
|
label={"Light color"}
|
||||||
fieldSpec={latest.light.color}
|
fieldSpec={latest.light.color}
|
||||||
value={light.color as string}
|
value={light.color as string}
|
||||||
default={latest.light.color.default}
|
default={latest.light.color.default}
|
||||||
@@ -209,7 +207,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Light intensity")}
|
label={"Light intensity"}
|
||||||
fieldSpec={latest.light.intensity}
|
fieldSpec={latest.light.intensity}
|
||||||
value={light.intensity as number}
|
value={light.intensity as number}
|
||||||
default={latest.light.intensity.default}
|
default={latest.light.intensity.default}
|
||||||
@@ -217,7 +215,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldArray
|
<FieldArray
|
||||||
label={t("Light position")}
|
label={"Light position"}
|
||||||
fieldSpec={latest.light.position}
|
fieldSpec={latest.light.position}
|
||||||
type="number"
|
type="number"
|
||||||
length={latest.light.position.length}
|
length={latest.light.position.length}
|
||||||
@@ -227,7 +225,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldString
|
<FieldString
|
||||||
label={t("Terrain source")}
|
label={"Terrain source"}
|
||||||
fieldSpec={latest.terrain.source}
|
fieldSpec={latest.terrain.source}
|
||||||
data-wd-key="modal:settings.maputnik:terrain_source"
|
data-wd-key="modal:settings.maputnik:terrain_source"
|
||||||
value={terrain.source}
|
value={terrain.source}
|
||||||
@@ -235,7 +233,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Terrain exaggeration")}
|
label={"Terrain exaggeration"}
|
||||||
fieldSpec={latest.terrain.exaggeration}
|
fieldSpec={latest.terrain.exaggeration}
|
||||||
value={terrain.exaggeration}
|
value={terrain.exaggeration}
|
||||||
default={latest.terrain.exaggeration.default}
|
default={latest.terrain.exaggeration.default}
|
||||||
@@ -243,7 +241,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Transition delay")}
|
label={"Transition delay"}
|
||||||
fieldSpec={latest.transition.delay}
|
fieldSpec={latest.transition.delay}
|
||||||
value={transition.delay}
|
value={transition.delay}
|
||||||
default={latest.transition.delay.default}
|
default={latest.transition.delay.default}
|
||||||
@@ -251,7 +249,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Transition duration")}
|
label={"Transition duration"}
|
||||||
fieldSpec={latest.transition.duration}
|
fieldSpec={latest.transition.duration}
|
||||||
value={transition.duration}
|
value={transition.duration}
|
||||||
default={latest.transition.duration.default}
|
default={latest.transition.duration.default}
|
||||||
@@ -259,12 +257,12 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
label={fsa.maputnik.style_renderer.label}
|
label={fieldSpecAdditional.maputnik.style_renderer.label}
|
||||||
fieldSpec={fsa.maputnik.style_renderer}
|
fieldSpec={fieldSpecAdditional.maputnik.style_renderer}
|
||||||
data-wd-key="modal:settings.maputnik:renderer"
|
data-wd-key="modal:settings.maputnik:renderer"
|
||||||
options={[
|
options={[
|
||||||
['mlgljs', 'MapLibreGL JS'],
|
['mlgljs', 'MapLibreGL JS'],
|
||||||
['ol', t('Open Layers (experimental)')],
|
['ol', 'Open Layers (experimental)'],
|
||||||
]}
|
]}
|
||||||
value={metadata['maputnik:renderer'] || 'mlgljs'}
|
value={metadata['maputnik:renderer'] || 'mlgljs'}
|
||||||
onChange={onChangeMetadataProperty.bind(this, 'maputnik:renderer')}
|
onChange={onChangeMetadataProperty.bind(this, 'maputnik:renderer')}
|
||||||
@@ -274,5 +272,3 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalSettings = withTranslation()(ModalSettingsInternal)
|
|
||||||
export default ModalSettings;
|
|
||||||
|
|||||||
@@ -1,50 +1,48 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
|
||||||
type ModalShortcutsInternalProps = {
|
type ModalShortcutsProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onOpenToggle(...args: unknown[]): unknown
|
onOpenToggle(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
|
|
||||||
class ModalShortcutsInternal extends React.Component<ModalShortcutsInternalProps> {
|
export default class ModalShortcuts extends React.Component<ModalShortcutsProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const help = [
|
const help = [
|
||||||
{
|
{
|
||||||
key: <kbd>?</kbd>,
|
key: <kbd>?</kbd>,
|
||||||
text: t("Shortcuts menu")
|
text: "Shortcuts menu"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>o</kbd>,
|
key: <kbd>o</kbd>,
|
||||||
text: t("Open modal")
|
text: "Open modal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>e</kbd>,
|
key: <kbd>e</kbd>,
|
||||||
text: t("Export modal")
|
text: "Export modal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>d</kbd>,
|
key: <kbd>d</kbd>,
|
||||||
text: t("Data Sources modal")
|
text: "Data Sources modal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>s</kbd>,
|
key: <kbd>s</kbd>,
|
||||||
text: t("Style Settings modal")
|
text: "Style Settings modal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>i</kbd>,
|
key: <kbd>i</kbd>,
|
||||||
text: t("Toggle inspect")
|
text: "Toggle inspect"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>m</kbd>,
|
key: <kbd>m</kbd>,
|
||||||
text: t("Focus map")
|
text: "Focus map"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>!</kbd>,
|
key: <kbd>!</kbd>,
|
||||||
text: t("Debug modal")
|
text: "Debug modal"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -52,51 +50,51 @@ class ModalShortcutsInternal extends React.Component<ModalShortcutsInternalProps
|
|||||||
const mapShortcuts = [
|
const mapShortcuts = [
|
||||||
{
|
{
|
||||||
key: <kbd>+</kbd>,
|
key: <kbd>+</kbd>,
|
||||||
text: t("Increase the zoom level by 1.",)
|
text: "Increase the zoom level by 1.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <><kbd>Shift</kbd> + <kbd>+</kbd></>,
|
key: <><kbd>Shift</kbd> + <kbd>+</kbd></>,
|
||||||
text: t("Increase the zoom level by 2.",)
|
text: "Increase the zoom level by 2.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>-</kbd>,
|
key: <kbd>-</kbd>,
|
||||||
text: t("Decrease the zoom level by 1.",)
|
text: "Decrease the zoom level by 1.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <><kbd>Shift</kbd> + <kbd>-</kbd></>,
|
key: <><kbd>Shift</kbd> + <kbd>-</kbd></>,
|
||||||
text: t("Decrease the zoom level by 2.",)
|
text: "Decrease the zoom level by 2.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>Up</kbd>,
|
key: <kbd>Up</kbd>,
|
||||||
text: t("Pan up by 100 pixels.",)
|
text: "Pan up by 100 pixels.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>Down</kbd>,
|
key: <kbd>Down</kbd>,
|
||||||
text: t("Pan down by 100 pixels.",)
|
text: "Pan down by 100 pixels.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>Left</kbd>,
|
key: <kbd>Left</kbd>,
|
||||||
text: t("Pan left by 100 pixels.",)
|
text: "Pan left by 100 pixels.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <kbd>Right</kbd>,
|
key: <kbd>Right</kbd>,
|
||||||
text: t("Pan right by 100 pixels.",)
|
text: "Pan right by 100 pixels.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <><kbd>Shift</kbd> + <kbd>Right</kbd></>,
|
key: <><kbd>Shift</kbd> + <kbd>Right</kbd></>,
|
||||||
text: t("Increase the rotation by 15 degrees.",)
|
text: "Increase the rotation by 15 degrees.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <><kbd>Shift</kbd> + <kbd>Left</kbd></>,
|
key: <><kbd>Shift</kbd> + <kbd>Left</kbd></>,
|
||||||
text: t("Decrease the rotation by 15 degrees.")
|
text: "Decrease the rotation by 15 degrees."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <><kbd>Shift</kbd> + <kbd>Up</kbd></>,
|
key: <><kbd>Shift</kbd> + <kbd>Up</kbd></>,
|
||||||
text: t("Increase the pitch by 10 degrees.")
|
text: "Increase the pitch by 10 degrees."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: <><kbd>Shift</kbd> + <kbd>Down</kbd></>,
|
key: <><kbd>Shift</kbd> + <kbd>Down</kbd></>,
|
||||||
text: t("Decrease the pitch by 10 degrees.")
|
text: "Decrease the pitch by 10 degrees."
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -105,13 +103,11 @@ class ModalShortcutsInternal extends React.Component<ModalShortcutsInternalProps
|
|||||||
data-wd-key="modal:shortcuts"
|
data-wd-key="modal:shortcuts"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Shortcuts')}
|
title={'Shortcuts'}
|
||||||
>
|
>
|
||||||
<section className="maputnik-modal-section maputnik-modal-shortcuts">
|
<section className="maputnik-modal-section maputnik-modal-shortcuts">
|
||||||
<p>
|
<p>
|
||||||
<Trans t={t}>
|
Press <code>ESC</code> to lose focus of any active elements, then press one of:
|
||||||
Press <code>ESC</code> to lose focus of any active elements, then press one of:
|
|
||||||
</Trans>
|
|
||||||
</p>
|
</p>
|
||||||
<dl>
|
<dl>
|
||||||
{help.map((item, idx) => {
|
{help.map((item, idx) => {
|
||||||
@@ -121,7 +117,7 @@ class ModalShortcutsInternal extends React.Component<ModalShortcutsInternalProps
|
|||||||
</div>
|
</div>
|
||||||
})}
|
})}
|
||||||
</dl>
|
</dl>
|
||||||
<p>{t("If the Map is in focused you can use the following shortcuts")}</p>
|
<p>If the Map is in focused you can use the following shortcuts</p>
|
||||||
<ul>
|
<ul>
|
||||||
{mapShortcuts.map((item, idx) => {
|
{mapShortcuts.map((item, idx) => {
|
||||||
return <li key={idx}>
|
return <li key={idx}>
|
||||||
@@ -134,5 +130,3 @@ class ModalShortcutsInternal extends React.Component<ModalShortcutsInternalProps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalShortcuts = withTranslation()(ModalShortcutsInternal);
|
|
||||||
export default ModalShortcuts;
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React from 'react'
|
|||||||
import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
|
import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import type {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, StyleSpecification, VectorSourceSpecification} from 'maplibre-gl'
|
import type {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, StyleSpecification, VectorSourceSpecification} from 'maplibre-gl'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
@@ -75,17 +74,16 @@ type ActiveModalSourcesTypeEditorProps = {
|
|||||||
source: SourceSpecification
|
source: SourceSpecification
|
||||||
onDelete(...args: unknown[]): unknown
|
onDelete(...args: unknown[]): unknown
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class ActiveModalSourcesTypeEditor extends React.Component<ActiveModalSourcesTypeEditorProps> {
|
class ActiveModalSourcesTypeEditor extends React.Component<ActiveModalSourcesTypeEditorProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <div className="maputnik-active-source-type-editor">
|
return <div className="maputnik-active-source-type-editor">
|
||||||
<div className="maputnik-active-source-type-editor-header">
|
<div className="maputnik-active-source-type-editor-header">
|
||||||
<span className="maputnik-active-source-type-editor-header-id">#{this.props.sourceId}</span>
|
<span className="maputnik-active-source-type-editor-header-id">#{this.props.sourceId}</span>
|
||||||
<span className="maputnik-space" />
|
<span className="maputnik-space" />
|
||||||
<InputButton
|
<InputButton
|
||||||
aria-label={t("Remove '{{sourceId}}' source", {sourceId: this.props.sourceId})}
|
aria-label={`Remove '${this.props.sourceId}' source`}
|
||||||
className="maputnik-active-source-type-editor-header-delete"
|
className="maputnik-active-source-type-editor-header-delete"
|
||||||
onClick={()=> this.props.onDelete(this.props.sourceId)}
|
onClick={()=> this.props.onDelete(this.props.sourceId)}
|
||||||
style={{backgroundColor: 'transparent'}}
|
style={{backgroundColor: 'transparent'}}
|
||||||
@@ -106,7 +104,7 @@ class ActiveModalSourcesTypeEditor extends React.Component<ActiveModalSourcesTyp
|
|||||||
|
|
||||||
type AddSourceProps = {
|
type AddSourceProps = {
|
||||||
onAdd(...args: unknown[]): unknown
|
onAdd(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
type AddSourceState = {
|
type AddSourceState = {
|
||||||
mode: EditorMode
|
mode: EditorMode
|
||||||
@@ -204,7 +202,6 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
// Kind of a hack because the type changes, however maputnik has 1..n
|
// Kind of a hack because the type changes, however maputnik has 1..n
|
||||||
// options per type, for example
|
// options per type, for example
|
||||||
//
|
//
|
||||||
@@ -218,25 +215,25 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
|||||||
|
|
||||||
return <div className="maputnik-add-source">
|
return <div className="maputnik-add-source">
|
||||||
<FieldString
|
<FieldString
|
||||||
label={t("Source ID")}
|
label={"Source ID"}
|
||||||
fieldSpec={{doc: t("Unique ID that identifies the source and is used in the layer to reference the source.")}}
|
fieldSpec={{doc: "Unique ID that identifies the source and is used in the layer to reference the source."}}
|
||||||
value={this.state.sourceId}
|
value={this.state.sourceId}
|
||||||
onChange={(v: string) => this.setState({ sourceId: v})}
|
onChange={(v: string) => this.setState({ sourceId: v})}
|
||||||
/>
|
/>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
label={t("Source Type")}
|
label={"Source Type"}
|
||||||
fieldSpec={sourceTypeFieldSpec}
|
fieldSpec={sourceTypeFieldSpec}
|
||||||
options={[
|
options={[
|
||||||
['geojson_json', t('GeoJSON (JSON)')],
|
['geojson_json', 'GeoJSON (JSON)'],
|
||||||
['geojson_url', t('GeoJSON (URL)')],
|
['geojson_url', 'GeoJSON (URL)'],
|
||||||
['tilejson_vector', t('Vector (TileJSON URL)')],
|
['tilejson_vector', 'Vector (TileJSON URL)'],
|
||||||
['tilexyz_vector', t('Vector (XYZ URLs)')],
|
['tilexyz_vector', 'Vector (XYZ URLs)'],
|
||||||
['tilejson_raster', t('Raster (TileJSON URL)')],
|
['tilejson_raster', 'Raster (TileJSON URL)'],
|
||||||
['tilexyz_raster', t('Raster (XYZ URL)')],
|
['tilexyz_raster', 'Raster (XYZ URL)'],
|
||||||
['tilejson_raster-dem', t('Raster DEM (TileJSON URL)')],
|
['tilejson_raster-dem', 'Raster DEM (TileJSON URL)'],
|
||||||
['tilexyz_raster-dem', t('Raster DEM (XYZ URLs)')],
|
['tilexyz_raster-dem', 'Raster DEM (XYZ URLs)'],
|
||||||
['image', t('Image')],
|
['image', 'Image'],
|
||||||
['video', t('Video')],
|
['video', 'Video'],
|
||||||
]}
|
]}
|
||||||
onChange={mode => this.setState({mode: mode as EditorMode, source: this.defaultSource(mode as EditorMode)})}
|
onChange={mode => this.setState({mode: mode as EditorMode, source: this.defaultSource(mode as EditorMode)})}
|
||||||
value={this.state.mode as string}
|
value={this.state.mode as string}
|
||||||
@@ -250,20 +247,20 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
|||||||
className="maputnik-add-source-button"
|
className="maputnik-add-source-button"
|
||||||
onClick={this.onAdd}
|
onClick={this.onAdd}
|
||||||
>
|
>
|
||||||
{t("Add Source")}
|
Add Source
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModalSourcesInternalProps = {
|
type ModalSourcesProps = {
|
||||||
mapStyle: StyleSpecification
|
mapStyle: StyleSpecification
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onOpenToggle(...args: unknown[]): unknown
|
onOpenToggle(...args: unknown[]): unknown
|
||||||
onStyleChanged(...args: unknown[]): unknown
|
onStyleChanged(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
export default class ModalSources extends React.Component<ModalSourcesProps> {
|
||||||
stripTitle(source: SourceSpecification & {title?: string}): SourceSpecification {
|
stripTitle(source: SourceSpecification & {title?: string}): SourceSpecification {
|
||||||
const strippedSource = {...source}
|
const strippedSource = {...source}
|
||||||
delete strippedSource['title']
|
delete strippedSource['title']
|
||||||
@@ -271,8 +268,7 @@ class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {t, mapStyle} = this.props;
|
const mapStyle = this.props.mapStyle
|
||||||
const i18nProps = {t, i18n: this.props.i18n, tReady: this.props.tReady};
|
|
||||||
const activeSources = Object.keys(mapStyle.sources).map(sourceId => {
|
const activeSources = Object.keys(mapStyle.sources).map(sourceId => {
|
||||||
const source = mapStyle.sources[sourceId]
|
const source = mapStyle.sources[sourceId]
|
||||||
return <ActiveModalSourcesTypeEditor
|
return <ActiveModalSourcesTypeEditor
|
||||||
@@ -281,7 +277,6 @@ class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
|||||||
source={source}
|
source={source}
|
||||||
onChange={(src: SourceSpecification) => this.props.onStyleChanged(changeSource(mapStyle, sourceId, src))}
|
onChange={(src: SourceSpecification) => this.props.onStyleChanged(changeSource(mapStyle, sourceId, src))}
|
||||||
onDelete={() => this.props.onStyleChanged(deleteSource(mapStyle, sourceId))}
|
onDelete={() => this.props.onStyleChanged(deleteSource(mapStyle, sourceId))}
|
||||||
{...i18nProps}
|
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -300,17 +295,17 @@ class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
|||||||
data-wd-key="modal:sources"
|
data-wd-key="modal:sources"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Sources')}
|
title={'Sources'}
|
||||||
>
|
>
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h1>{t("Active Sources")}</h1>
|
<h1>Active Sources</h1>
|
||||||
{activeSources}
|
{activeSources}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h1>{t("Choose Public Source")}</h1>
|
<h1>Choose Public Source</h1>
|
||||||
<p>
|
<p>
|
||||||
{t("Add one of the publicly available sources to your style.")}
|
Add one of the publicly available sources to your style.
|
||||||
</p>
|
</p>
|
||||||
<div className="maputnik-public-sources" style={{maxWidth: 500}}>
|
<div className="maputnik-public-sources" style={{maxWidth: 500}}>
|
||||||
{tilesetOptions}
|
{tilesetOptions}
|
||||||
@@ -318,16 +313,13 @@ class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h1>{t("Add New Source")}</h1>
|
<h1>Add New Source</h1>
|
||||||
<p>{t("Add a new source to your style. You can only choose the source type and id at creation time!")}</p>
|
<p>Add a new source to your style. You can only choose the source type and id at creation time!</p>
|
||||||
<AddSource
|
<AddSource
|
||||||
onAdd={(sourceId: string, source: SourceSpecification) => this.props.onStyleChanged(addSource(mapStyle, sourceId, source))}
|
onAdd={(sourceId: string, source: SourceSpecification) => this.props.onStyleChanged(addSource(mapStyle, sourceId, source))}
|
||||||
{...i18nProps}
|
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalSources = withTranslation()(ModalSourcesInternal);
|
|
||||||
export default ModalSources;
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import FieldDynamicArray from './FieldDynamicArray'
|
|||||||
import FieldArray from './FieldArray'
|
import FieldArray from './FieldArray'
|
||||||
import FieldJson from './FieldJson'
|
import FieldJson from './FieldJson'
|
||||||
import FieldCheckbox from './FieldCheckbox'
|
import FieldCheckbox from './FieldCheckbox'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
import { TFunction } from 'i18next'
|
|
||||||
|
|
||||||
export type EditorMode = "video" | "image" | "tilejson_vector" | "tilexyz_raster" | "tilejson_raster" | "tilexyz_raster-dem" | "tilejson_raster-dem" | "tilexyz_vector" | "geojson_url" | "geojson_json" | null;
|
export type EditorMode = "video" | "image" | "tilejson_vector" | "tilexyz_raster" | "tilejson_raster" | "tilexyz_raster-dem" | "tilejson_raster-dem" | "tilexyz_vector" | "geojson_url" | "geojson_json" | null;
|
||||||
|
|
||||||
@@ -19,15 +17,14 @@ type TileJSONSourceEditorProps = {
|
|||||||
}
|
}
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
|
|
||||||
class TileJSONSourceEditor extends React.Component<TileJSONSourceEditorProps> {
|
class TileJSONSourceEditor extends React.Component<TileJSONSourceEditorProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <div>
|
return <div>
|
||||||
<FieldUrl
|
<FieldUrl
|
||||||
label={t("TileJSON URL")}
|
label={"TileJSON URL"}
|
||||||
fieldSpec={latest.source_vector.url}
|
fieldSpec={latest.source_vector.url}
|
||||||
value={this.props.source.url}
|
value={this.props.source.url}
|
||||||
onChange={url => this.props.onChange({
|
onChange={url => this.props.onChange({
|
||||||
@@ -48,7 +45,7 @@ type TileURLSourceEditorProps = {
|
|||||||
}
|
}
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
|
class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
|
||||||
changeTileUrls(tiles: string[]) {
|
changeTileUrls(tiles: string[]) {
|
||||||
@@ -61,7 +58,7 @@ class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
|
|||||||
renderTileUrls() {
|
renderTileUrls() {
|
||||||
const tiles = this.props.source.tiles || [];
|
const tiles = this.props.source.tiles || [];
|
||||||
return <FieldDynamicArray
|
return <FieldDynamicArray
|
||||||
label={this.props.t("Tile URL")}
|
label={"Tile URL"}
|
||||||
fieldSpec={latest.source_vector.tiles}
|
fieldSpec={latest.source_vector.tiles}
|
||||||
type="url"
|
type="url"
|
||||||
value={tiles}
|
value={tiles}
|
||||||
@@ -70,11 +67,10 @@ class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <div>
|
return <div>
|
||||||
{this.renderTileUrls()}
|
{this.renderTileUrls()}
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Min Zoom")}
|
label={"Min Zoom"}
|
||||||
fieldSpec={latest.source_vector.minzoom}
|
fieldSpec={latest.source_vector.minzoom}
|
||||||
value={this.props.source.minzoom || 0}
|
value={this.props.source.minzoom || 0}
|
||||||
onChange={minzoom => this.props.onChange({
|
onChange={minzoom => this.props.onChange({
|
||||||
@@ -83,7 +79,7 @@ class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Max Zoom")}
|
label={"Max Zoom"}
|
||||||
fieldSpec={latest.source_vector.maxzoom}
|
fieldSpec={latest.source_vector.maxzoom}
|
||||||
value={this.props.source.maxzoom || 22}
|
value={this.props.source.maxzoom || 22}
|
||||||
onChange={maxzoom => this.props.onChange({
|
onChange={maxzoom => this.props.onChange({
|
||||||
@@ -97,24 +93,16 @@ class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createCornerLabels: (t: TFunction) => { label: string, key: string }[] = (t) => ([
|
|
||||||
{ label: t("Coord top left"), key: "top left" },
|
|
||||||
{ label: t("Coord top right"), key: "top right" },
|
|
||||||
{ label: t("Coord bottom right"), key: "bottom right" },
|
|
||||||
{ label: t("Coord bottom left"), key: "bottom left" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
type ImageSourceEditorProps = {
|
type ImageSourceEditorProps = {
|
||||||
source: {
|
source: {
|
||||||
coordinates: [number, number][]
|
coordinates: [number, number][]
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class ImageSourceEditor extends React.Component<ImageSourceEditorProps> {
|
class ImageSourceEditor extends React.Component<ImageSourceEditorProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const changeCoord = (idx: number, val: [number, number]) => {
|
const changeCoord = (idx: number, val: [number, number]) => {
|
||||||
const coordinates = this.props.source.coordinates.slice(0);
|
const coordinates = this.props.source.coordinates.slice(0);
|
||||||
coordinates[idx] = val;
|
coordinates[idx] = val;
|
||||||
@@ -127,7 +115,7 @@ class ImageSourceEditor extends React.Component<ImageSourceEditorProps> {
|
|||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<FieldUrl
|
<FieldUrl
|
||||||
label={t("Image URL")}
|
label={"Image URL"}
|
||||||
fieldSpec={latest.source_image.url}
|
fieldSpec={latest.source_image.url}
|
||||||
value={this.props.source.url}
|
value={this.props.source.url}
|
||||||
onChange={url => this.props.onChange({
|
onChange={url => this.props.onChange({
|
||||||
@@ -135,11 +123,11 @@ class ImageSourceEditor extends React.Component<ImageSourceEditorProps> {
|
|||||||
url,
|
url,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{createCornerLabels(t).map(({label, key}, idx) => {
|
{["top left", "top right", "bottom right", "bottom left"].map((label, idx) => {
|
||||||
return (
|
return (
|
||||||
<FieldArray
|
<FieldArray
|
||||||
label={label}
|
label={`Coord ${label}`}
|
||||||
key={key}
|
key={label}
|
||||||
length={2}
|
length={2}
|
||||||
type="number"
|
type="number"
|
||||||
value={this.props.source.coordinates[idx]}
|
value={this.props.source.coordinates[idx]}
|
||||||
@@ -158,11 +146,10 @@ type VideoSourceEditorProps = {
|
|||||||
urls: string[]
|
urls: string[]
|
||||||
}
|
}
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class VideoSourceEditor extends React.Component<VideoSourceEditorProps> {
|
class VideoSourceEditor extends React.Component<VideoSourceEditorProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const changeCoord = (idx: number, val: [number, number]) => {
|
const changeCoord = (idx: number, val: [number, number]) => {
|
||||||
const coordinates = this.props.source.coordinates.slice(0);
|
const coordinates = this.props.source.coordinates.slice(0);
|
||||||
coordinates[idx] = val;
|
coordinates[idx] = val;
|
||||||
@@ -182,18 +169,18 @@ class VideoSourceEditor extends React.Component<VideoSourceEditorProps> {
|
|||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<FieldDynamicArray
|
<FieldDynamicArray
|
||||||
label={t("Video URL")}
|
label={"Video URL"}
|
||||||
fieldSpec={latest.source_video.urls}
|
fieldSpec={latest.source_video.urls}
|
||||||
type="string"
|
type="string"
|
||||||
value={this.props.source.urls}
|
value={this.props.source.urls}
|
||||||
default={[]}
|
default={[]}
|
||||||
onChange={changeUrls}
|
onChange={changeUrls}
|
||||||
/>
|
/>
|
||||||
{createCornerLabels(t).map(({label, key}, idx) => {
|
{["top left", "top right", "bottom right", "bottom left"].map((label, idx) => {
|
||||||
return (
|
return (
|
||||||
<FieldArray
|
<FieldArray
|
||||||
label={label}
|
label={`Coord ${label}`}
|
||||||
key={key}
|
key={label}
|
||||||
length={2}
|
length={2}
|
||||||
type="number"
|
type="number"
|
||||||
value={this.props.source.coordinates[idx]}
|
value={this.props.source.coordinates[idx]}
|
||||||
@@ -211,13 +198,12 @@ type GeoJSONSourceUrlEditorProps = {
|
|||||||
data: string
|
data: string
|
||||||
}
|
}
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class GeoJSONSourceUrlEditor extends React.Component<GeoJSONSourceUrlEditorProps> {
|
class GeoJSONSourceUrlEditor extends React.Component<GeoJSONSourceUrlEditorProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <FieldUrl
|
return <FieldUrl
|
||||||
label={t("GeoJSON URL")}
|
label={"GeoJSON URL"}
|
||||||
fieldSpec={latest.source_geojson.data}
|
fieldSpec={latest.source_geojson.data}
|
||||||
value={this.props.source.data}
|
value={this.props.source.data}
|
||||||
onChange={data => this.props.onChange({
|
onChange={data => this.props.onChange({
|
||||||
@@ -234,13 +220,12 @@ type GeoJSONSourceFieldJsonEditorProps = {
|
|||||||
cluster: boolean
|
cluster: boolean
|
||||||
}
|
}
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class GeoJSONSourceFieldJsonEditor extends React.Component<GeoJSONSourceFieldJsonEditorProps> {
|
class GeoJSONSourceFieldJsonEditor extends React.Component<GeoJSONSourceFieldJsonEditorProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <div>
|
return <div>
|
||||||
<Block label={t("GeoJSON")} fieldSpec={latest.source_geojson.data}>
|
<Block label={"GeoJSON"} fieldSpec={latest.source_geojson.data}>
|
||||||
<FieldJson
|
<FieldJson
|
||||||
layer={this.props.source.data}
|
layer={this.props.source.data}
|
||||||
maxHeight={200}
|
maxHeight={200}
|
||||||
@@ -258,7 +243,7 @@ class GeoJSONSourceFieldJsonEditor extends React.Component<GeoJSONSourceFieldJso
|
|||||||
/>
|
/>
|
||||||
</Block>
|
</Block>
|
||||||
<FieldCheckbox
|
<FieldCheckbox
|
||||||
label={t('Cluster')}
|
label={'Cluster'}
|
||||||
value={this.props.source.cluster}
|
value={this.props.source.cluster}
|
||||||
onChange={cluster => {
|
onChange={cluster => {
|
||||||
this.props.onChange({
|
this.props.onChange({
|
||||||
@@ -271,22 +256,18 @@ class GeoJSONSourceFieldJsonEditor extends React.Component<GeoJSONSourceFieldJso
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModalSourcesTypeEditorInternalProps = {
|
type ModalSourcesTypeEditorProps = {
|
||||||
mode: EditorMode
|
mode: EditorMode
|
||||||
source: any
|
source: any
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class ModalSourcesTypeEditorInternal extends React.Component<ModalSourcesTypeEditorInternalProps> {
|
export default class ModalSourcesTypeEditor extends React.Component<ModalSourcesTypeEditorProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
source: this.props.source,
|
source: this.props.source,
|
||||||
onChange: this.props.onChange,
|
onChange: this.props.onChange,
|
||||||
t: this.props.t,
|
}
|
||||||
i18n: this.props.i18n,
|
|
||||||
tReady: this.props.tReady,
|
|
||||||
};
|
|
||||||
switch(this.props.mode) {
|
switch(this.props.mode) {
|
||||||
case 'geojson_url': return <GeoJSONSourceUrlEditor {...commonProps} />
|
case 'geojson_url': return <GeoJSONSourceUrlEditor {...commonProps} />
|
||||||
case 'geojson_json': return <GeoJSONSourceFieldJsonEditor {...commonProps} />
|
case 'geojson_json': return <GeoJSONSourceFieldJsonEditor {...commonProps} />
|
||||||
@@ -297,7 +278,7 @@ class ModalSourcesTypeEditorInternal extends React.Component<ModalSourcesTypeEdi
|
|||||||
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
|
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
|
||||||
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps}>
|
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps}>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
label={t("Encoding")}
|
label={"Encoding"}
|
||||||
fieldSpec={latest.source_raster_dem.encoding}
|
fieldSpec={latest.source_raster_dem.encoding}
|
||||||
options={Object.keys(latest.source_raster_dem.encoding.values)}
|
options={Object.keys(latest.source_raster_dem.encoding.values)}
|
||||||
onChange={encoding => this.props.onChange({
|
onChange={encoding => this.props.onChange({
|
||||||
@@ -314,5 +295,3 @@ class ModalSourcesTypeEditorInternal extends React.Component<ModalSourcesTypeEdi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalSourcesTypeEditor = withTranslation()(ModalSourcesTypeEditorInternal);
|
|
||||||
export default ModalSourcesTypeEditor;
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import InputButton from './InputButton'
|
||||||
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import logoImage from 'maputnik-design/logos/logo-color.svg'
|
||||||
|
|
||||||
|
type ModalSurveyProps = {
|
||||||
|
isOpen: boolean
|
||||||
|
onOpenToggle(...args: unknown[]): unknown
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ModalSurvey extends React.Component<ModalSurveyProps> {
|
||||||
|
onClick = () => {
|
||||||
|
window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank');
|
||||||
|
|
||||||
|
this.props.onOpenToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <Modal
|
||||||
|
data-wd-key="modal:survey"
|
||||||
|
isOpen={this.props.isOpen}
|
||||||
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
|
title="Maputnik Survey"
|
||||||
|
>
|
||||||
|
<div className="maputnik-modal-survey">
|
||||||
|
<img src={logoImage} className="maputnik-modal-survey__logo" />
|
||||||
|
<h1>You + Maputnik = Maputnik better for you</h1>
|
||||||
|
<p className="maputnik-modal-survey__description">We don’t track you, so we don’t know how you use Maputnik. Help us make Maputnik better for you by completing a 7–minute survey carried out by our contributing designer.</p>
|
||||||
|
<InputButton onClick={this.onClick} className="maputnik-big-button maputnik-white-button maputnik-wide-button">Take the Maputnik Survey</InputButton>
|
||||||
|
<p className="maputnik-modal-survey__footnote">It takes 7 minutes, tops! Every question is optional.</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,22 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
import './SmallError.scss';
|
import './SmallError.scss';
|
||||||
|
|
||||||
|
|
||||||
type SmallErrorInternalProps = {
|
type SmallErrorProps = {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class SmallErrorInternal extends React.Component<SmallErrorInternalProps> {
|
|
||||||
|
export default class SmallError extends React.Component<SmallErrorProps> {
|
||||||
render () {
|
render () {
|
||||||
const t = this.props.t;
|
|
||||||
return (
|
return (
|
||||||
<div className="SmallError">
|
<div className="SmallError">
|
||||||
{t("Error:")} {this.props.children}
|
Error: {this.props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SmallError = withTranslation()(SmallErrorInternal);
|
|
||||||
export default SmallError;
|
|
||||||
@@ -11,14 +11,13 @@ import Block from './Block'
|
|||||||
import docUid from '../libs/document-uid'
|
import docUid from '../libs/document-uid'
|
||||||
import sortNumerically from '../libs/sort-numerically'
|
import sortNumerically from '../libs/sort-numerically'
|
||||||
import {findDefaultFromSpec} from '../libs/spec-helper';
|
import {findDefaultFromSpec} from '../libs/spec-helper';
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import labelFromFieldName from '../libs/label-from-field-name'
|
import labelFromFieldName from '../libs/label-from-field-name'
|
||||||
import DeleteStopButton from './_DeleteStopButton'
|
import DeleteStopButton from './_DeleteStopButton'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function setStopRefs(props: DataPropertyInternalProps, state: DataPropertyState) {
|
function setStopRefs(props: DataPropertyProps, state: DataPropertyState) {
|
||||||
// This is initialsed below only if required to improved performance.
|
// This is initialsed below only if required to improved performance.
|
||||||
let newRefs: {[key: number]: string} | undefined;
|
let newRefs: {[key: number]: string} | undefined;
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ function setStopRefs(props: DataPropertyInternalProps, state: DataPropertyState)
|
|||||||
return newRefs;
|
return newRefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataPropertyInternalProps = {
|
type DataPropertyProps = {
|
||||||
onChange?(fieldName: string, value: any): unknown
|
onChange?(fieldName: string, value: any): unknown
|
||||||
onDeleteStop?(...args: unknown[]): unknown
|
onDeleteStop?(...args: unknown[]): unknown
|
||||||
onAddStop?(...args: unknown[]): unknown
|
onAddStop?(...args: unknown[]): unknown
|
||||||
@@ -47,7 +46,7 @@ type DataPropertyInternalProps = {
|
|||||||
fieldSpec?: object
|
fieldSpec?: object
|
||||||
value?: DataPropertyValue
|
value?: DataPropertyValue
|
||||||
errors?: object
|
errors?: object
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
type DataPropertyState = {
|
type DataPropertyState = {
|
||||||
refs: {[key: number]: string}
|
refs: {[key: number]: string}
|
||||||
@@ -66,7 +65,7 @@ export type Stop = [{
|
|||||||
value: number
|
value: number
|
||||||
}, number]
|
}, number]
|
||||||
|
|
||||||
class DataPropertyInternal extends React.Component<DataPropertyInternalProps, DataPropertyState> {
|
export default class DataProperty extends React.Component<DataPropertyProps, DataPropertyState> {
|
||||||
state = {
|
state = {
|
||||||
refs: {} as {[key: number]: string}
|
refs: {} as {[key: number]: string}
|
||||||
}
|
}
|
||||||
@@ -81,7 +80,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<DataPropertyInternalProps>, state: DataPropertyState) {
|
static getDerivedStateFromProps(props: DataPropertyProps, state: DataPropertyState) {
|
||||||
const newRefs = setStopRefs(props, state);
|
const newRefs = setStopRefs(props, state);
|
||||||
if(newRefs) {
|
if(newRefs) {
|
||||||
return {
|
return {
|
||||||
@@ -214,8 +213,6 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
|
|
||||||
if (typeof this.props.value?.type === "undefined") {
|
if (typeof this.props.value?.type === "undefined") {
|
||||||
this.props.value!.type = this.getFieldFunctionType(this.props.fieldSpec)
|
this.props.value!.type = this.getFieldFunctionType(this.props.fieldSpec)
|
||||||
}
|
}
|
||||||
@@ -230,8 +227,8 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />
|
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />
|
||||||
|
|
||||||
const dataProps = {
|
const dataProps = {
|
||||||
'aria-label': t("Input value"),
|
'aria-label': "Input value",
|
||||||
label: t("Data value"),
|
label: "Data value",
|
||||||
value: dataLevel as any,
|
value: dataLevel as any,
|
||||||
onChange: (newData: string | number | undefined) => this.changeStop(idx, { zoom: zoomLevel, value: newData as number }, value)
|
onChange: (newData: string | number | undefined) => this.changeStop(idx, { zoom: zoomLevel, value: newData as number }, value)
|
||||||
}
|
}
|
||||||
@@ -266,7 +263,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<InputSpec
|
<InputSpec
|
||||||
aria-label={t("Output value")}
|
aria-label="Output value"
|
||||||
fieldName={this.props.fieldName}
|
fieldName={this.props.fieldName}
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
value={value}
|
value={value}
|
||||||
@@ -285,21 +282,21 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
<legend>{labelFromFieldName(this.props.fieldName)}</legend>
|
<legend>{labelFromFieldName(this.props.fieldName)}</legend>
|
||||||
<div className="maputnik-data-fieldset-inner">
|
<div className="maputnik-data-fieldset-inner">
|
||||||
<Block
|
<Block
|
||||||
label={t("Function")}
|
label={"Function"}
|
||||||
key="function"
|
key="function"
|
||||||
>
|
>
|
||||||
<div className="maputnik-data-spec-property-input">
|
<div className="maputnik-data-spec-property-input">
|
||||||
<InputSelect
|
<InputSelect
|
||||||
value={this.props.value!.type}
|
value={this.props.value!.type}
|
||||||
onChange={(propVal: string) => this.changeDataType(propVal)}
|
onChange={(propVal: string) => this.changeDataType(propVal)}
|
||||||
title={t("Select a type of data scale (default is 'categorical').")}
|
title={"Select a type of data scale (default is 'categorical')."}
|
||||||
options={this.getDataFunctionTypes(this.props.fieldSpec)}
|
options={this.getDataFunctionTypes(this.props.fieldSpec)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Block>
|
</Block>
|
||||||
{this.props.value?.type !== "identity" &&
|
{this.props.value?.type !== "identity" &&
|
||||||
<Block
|
<Block
|
||||||
label={t("Base")}
|
label={"Base"}
|
||||||
key="base"
|
key="base"
|
||||||
>
|
>
|
||||||
<div className="maputnik-data-spec-property-input">
|
<div className="maputnik-data-spec-property-input">
|
||||||
@@ -319,14 +316,14 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
<div className="maputnik-data-spec-property-input">
|
<div className="maputnik-data-spec-property-input">
|
||||||
<InputString
|
<InputString
|
||||||
value={this.props.value?.property}
|
value={this.props.value?.property}
|
||||||
title={t("Input a data property to base styles off of.")}
|
title={"Input a data property to base styles off of."}
|
||||||
onChange={propVal => this.changeDataProperty("property", propVal)}
|
onChange={propVal => this.changeDataProperty("property", propVal)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Block>
|
</Block>
|
||||||
{dataFields &&
|
{dataFields &&
|
||||||
<Block
|
<Block
|
||||||
label={t("Default")}
|
label={"Default"}
|
||||||
key="default"
|
key="default"
|
||||||
>
|
>
|
||||||
<InputSpec
|
<InputSpec
|
||||||
@@ -340,12 +337,12 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
{dataFields &&
|
{dataFields &&
|
||||||
<div className="maputnik-function-stop">
|
<div className="maputnik-function-stop">
|
||||||
<table className="maputnik-function-stop-table">
|
<table className="maputnik-function-stop-table">
|
||||||
<caption>{t("Stops")}</caption>
|
<caption>Stops</caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{t("Zoom")}</th>
|
<th>Zoom</th>
|
||||||
<th>{t("Input value")}</th>
|
<th>Input value</th>
|
||||||
<th rowSpan={2}>{t("Output value")}</th>
|
<th rowSpan={2}>Output value</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -362,7 +359,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
>
|
>
|
||||||
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d={mdiTableRowPlusAfter} />
|
<path fill="currentColor" d={mdiTableRowPlusAfter} />
|
||||||
</svg> {t("Add stop")}
|
</svg> Add stop
|
||||||
</InputButton>
|
</InputButton>
|
||||||
}
|
}
|
||||||
<InputButton
|
<InputButton
|
||||||
@@ -371,7 +368,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
>
|
>
|
||||||
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d={mdiFunctionVariant} />
|
<path fill="currentColor" d={mdiFunctionVariant} />
|
||||||
</svg> {t("Convert to expression")}
|
</svg> Convert to expression
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -379,6 +376,3 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DataProperty = withTranslation()(DataPropertyInternal);
|
|
||||||
export default DataProperty;
|
|
||||||
|
|||||||
@@ -2,26 +2,21 @@ import React from 'react'
|
|||||||
|
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import {MdDelete} from 'react-icons/md'
|
import {MdDelete} from 'react-icons/md'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
|
|
||||||
type DeleteStopButtonInternalProps = {
|
type DeleteStopButtonProps = {
|
||||||
onClick?(...args: unknown[]): unknown
|
onClick?(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
|
|
||||||
class DeleteStopButtonInternal extends React.Component<DeleteStopButtonInternalProps> {
|
export default class DeleteStopButton extends React.Component<DeleteStopButtonProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
return <InputButton
|
return <InputButton
|
||||||
className="maputnik-delete-stop"
|
className="maputnik-delete-stop"
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
title={t("Remove zoom level from stop")}
|
title={"Remove zoom level from stop"}
|
||||||
>
|
>
|
||||||
<MdDelete />
|
<MdDelete />
|
||||||
</InputButton>
|
</InputButton>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeleteStopButton = withTranslation()(DeleteStopButtonInternal);
|
|
||||||
export default DeleteStopButton;
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {MdDelete, MdUndo} from 'react-icons/md'
|
import {MdDelete, MdUndo} from 'react-icons/md'
|
||||||
import stringifyPretty from 'json-stringify-pretty-compact'
|
import stringifyPretty from 'json-stringify-pretty-compact'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
@@ -9,7 +8,7 @@ import labelFromFieldName from '../libs/label-from-field-name'
|
|||||||
import FieldJson from './FieldJson'
|
import FieldJson from './FieldJson'
|
||||||
|
|
||||||
|
|
||||||
type ExpressionPropertyInternalProps = {
|
type ExpressionPropertyProps = {
|
||||||
onDelete?(...args: unknown[]): unknown
|
onDelete?(...args: unknown[]): unknown
|
||||||
fieldName: string
|
fieldName: string
|
||||||
fieldType?: string
|
fieldType?: string
|
||||||
@@ -21,20 +20,20 @@ type ExpressionPropertyInternalProps = {
|
|||||||
canUndo?(...args: unknown[]): unknown
|
canUndo?(...args: unknown[]): unknown
|
||||||
onFocus?(...args: unknown[]): unknown
|
onFocus?(...args: unknown[]): unknown
|
||||||
onBlur?(...args: unknown[]): unknown
|
onBlur?(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
type ExpressionPropertyState = {
|
type ExpressionPropertyState = {
|
||||||
jsonError: boolean
|
jsonError: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInternalProps, ExpressionPropertyState> {
|
export default class ExpressionProperty extends React.Component<ExpressionPropertyProps, ExpressionPropertyState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
errors: {},
|
errors: {},
|
||||||
onFocus: () => {},
|
onFocus: () => {},
|
||||||
onBlur: () => {},
|
onBlur: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: ExpressionPropertyInternalProps) {
|
constructor (props:ExpressionPropertyProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
jsonError: false,
|
jsonError: false,
|
||||||
@@ -54,7 +53,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {t, errors, fieldName, fieldType, value, canUndo} = this.props;
|
const {errors, fieldName, fieldType, value, canUndo} = this.props;
|
||||||
const {jsonError} = this.state;
|
const {jsonError} = this.state;
|
||||||
const undoDisabled = canUndo ? !canUndo() : true;
|
const undoDisabled = canUndo ? !canUndo() : true;
|
||||||
|
|
||||||
@@ -66,7 +65,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
onClick={this.props.onUndo}
|
onClick={this.props.onUndo}
|
||||||
disabled={undoDisabled}
|
disabled={undoDisabled}
|
||||||
className="maputnik-delete-stop"
|
className="maputnik-delete-stop"
|
||||||
title={t("Revert from expression")}
|
title="Revert from expression"
|
||||||
>
|
>
|
||||||
<MdUndo />
|
<MdUndo />
|
||||||
</InputButton>
|
</InputButton>
|
||||||
@@ -75,7 +74,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
key="delete_action"
|
key="delete_action"
|
||||||
onClick={this.props.onDelete}
|
onClick={this.props.onDelete}
|
||||||
className="maputnik-delete-stop"
|
className="maputnik-delete-stop"
|
||||||
title={t("Delete expression")}
|
title="Delete expression"
|
||||||
>
|
>
|
||||||
<MdDelete />
|
<MdDelete />
|
||||||
</InputButton>
|
</InputButton>
|
||||||
@@ -113,7 +112,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
// this feels like an incorrect type...? `foundErrors` is an array of objects, not a single object
|
// this feels like an incorrect type...? `foundErrors` is an array of objects, not a single object
|
||||||
error={foundErrors as any}
|
error={foundErrors as any}
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
label={t(labelFromFieldName(this.props.fieldName))}
|
label={labelFromFieldName(this.props.fieldName)}
|
||||||
action={deleteStopBtn}
|
action={deleteStopBtn}
|
||||||
wideMode={true}
|
wideMode={true}
|
||||||
>
|
>
|
||||||
@@ -138,6 +137,3 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
</Block>
|
</Block>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpressionProperty = withTranslation()(ExpressionPropertyInternal);
|
|
||||||
export default ExpressionProperty;
|
|
||||||
|
|||||||
@@ -3,18 +3,16 @@ import React from 'react'
|
|||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import {MdFunctions, MdInsertChart} from 'react-icons/md'
|
import {MdFunctions, MdInsertChart} from 'react-icons/md'
|
||||||
import {mdiFunctionVariant} from '@mdi/js';
|
import {mdiFunctionVariant} from '@mdi/js';
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type FunctionInputButtonsInternalProps = {
|
type FunctionInputButtonsProps = {
|
||||||
fieldSpec?: any
|
fieldSpec?: any
|
||||||
onZoomClick?(...args: unknown[]): unknown
|
onZoomClick?(...args: unknown[]): unknown
|
||||||
onDataClick?(...args: unknown[]): unknown
|
onDataClick?(...args: unknown[]): unknown
|
||||||
onExpressionClick?(...args: unknown[]): unknown
|
onExpressionClick?(...args: unknown[]): unknown
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
class FunctionInputButtonsInternal extends React.Component<FunctionInputButtonsInternalProps> {
|
export default class FunctionInputButtons extends React.Component<FunctionInputButtonsProps> {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
let makeZoomInputButton, makeDataInputButton, expressionInputButton;
|
let makeZoomInputButton, makeDataInputButton, expressionInputButton;
|
||||||
|
|
||||||
if (this.props.fieldSpec.expression.parameters.includes('zoom')) {
|
if (this.props.fieldSpec.expression.parameters.includes('zoom')) {
|
||||||
@@ -22,7 +20,7 @@ class FunctionInputButtonsInternal extends React.Component<FunctionInputButtonsI
|
|||||||
<InputButton
|
<InputButton
|
||||||
className="maputnik-make-zoom-function"
|
className="maputnik-make-zoom-function"
|
||||||
onClick={this.props.onExpressionClick}
|
onClick={this.props.onExpressionClick}
|
||||||
title={t("Convert to expression")}
|
title="Convert to expression"
|
||||||
>
|
>
|
||||||
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d={mdiFunctionVariant} />
|
<path fill="currentColor" d={mdiFunctionVariant} />
|
||||||
@@ -33,7 +31,7 @@ class FunctionInputButtonsInternal extends React.Component<FunctionInputButtonsI
|
|||||||
makeZoomInputButton = <InputButton
|
makeZoomInputButton = <InputButton
|
||||||
className="maputnik-make-zoom-function"
|
className="maputnik-make-zoom-function"
|
||||||
onClick={this.props.onZoomClick}
|
onClick={this.props.onZoomClick}
|
||||||
title={t("Convert property into a zoom function")}
|
title="Convert property into a zoom function"
|
||||||
>
|
>
|
||||||
<MdFunctions />
|
<MdFunctions />
|
||||||
</InputButton>
|
</InputButton>
|
||||||
@@ -42,7 +40,7 @@ class FunctionInputButtonsInternal extends React.Component<FunctionInputButtonsI
|
|||||||
makeDataInputButton = <InputButton
|
makeDataInputButton = <InputButton
|
||||||
className="maputnik-make-data-function"
|
className="maputnik-make-data-function"
|
||||||
onClick={this.props.onDataClick}
|
onClick={this.props.onDataClick}
|
||||||
title={t("Convert property to data function")}
|
title="Convert property to data function"
|
||||||
>
|
>
|
||||||
<MdInsertChart />
|
<MdInsertChart />
|
||||||
</InputButton>
|
</InputButton>
|
||||||
@@ -58,6 +56,3 @@ class FunctionInputButtonsInternal extends React.Component<FunctionInputButtonsI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FunctionInputButtons = withTranslation()(FunctionInputButtonsInternal);
|
|
||||||
export default FunctionInputButtons;
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
|
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import InputSpec from './InputSpec'
|
import InputSpec from './InputSpec'
|
||||||
@@ -21,7 +20,7 @@ import sortNumerically from '../libs/sort-numerically'
|
|||||||
*
|
*
|
||||||
* 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.
|
* 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) {
|
function setStopRefs(props: ZoomPropertyProps, state: ZoomPropertyState) {
|
||||||
// This is initialsed below only if required to improved performance.
|
// This is initialsed below only if required to improved performance.
|
||||||
let newRefs: {[key: number]: string} = {};
|
let newRefs: {[key: number]: string} = {};
|
||||||
|
|
||||||
@@ -46,7 +45,7 @@ type ZoomWithStops = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type ZoomPropertyInternalProps = {
|
type ZoomPropertyProps = {
|
||||||
onChange?(...args: unknown[]): unknown
|
onChange?(...args: unknown[]): unknown
|
||||||
onChangeToDataFunction?(...args: unknown[]): unknown
|
onChangeToDataFunction?(...args: unknown[]): unknown
|
||||||
onDeleteStop?(...args: unknown[]): unknown
|
onDeleteStop?(...args: unknown[]): unknown
|
||||||
@@ -60,13 +59,13 @@ type ZoomPropertyInternalProps = {
|
|||||||
}
|
}
|
||||||
errors?: object
|
errors?: object
|
||||||
value?: ZoomWithStops
|
value?: ZoomWithStops
|
||||||
} & WithTranslation;
|
};
|
||||||
|
|
||||||
type ZoomPropertyState = {
|
type ZoomPropertyState = {
|
||||||
refs: {[key: number]: string}
|
refs: {[key: number]: string}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, ZoomPropertyState> {
|
export default class ZoomProperty extends React.Component<ZoomPropertyProps, ZoomPropertyState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
errors: {},
|
errors: {},
|
||||||
}
|
}
|
||||||
@@ -85,7 +84,7 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<ZoomPropertyInternalProps>, state: ZoomPropertyState) {
|
static getDerivedStateFromProps(props: ZoomPropertyProps, state: ZoomPropertyState) {
|
||||||
const newRefs = setStopRefs(props, state);
|
const newRefs = setStopRefs(props, state);
|
||||||
if(newRefs) {
|
if(newRefs) {
|
||||||
return {
|
return {
|
||||||
@@ -153,17 +152,17 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
|
||||||
const zoomFields = this.props.value?.stops.map((stop, idx) => {
|
const zoomFields = this.props.value?.stops.map((stop, idx) => {
|
||||||
const zoomLevel = stop[0]
|
const zoomLevel = stop[0]
|
||||||
|
const key = this.state.refs[idx];
|
||||||
const value = stop[1]
|
const value = stop[1]
|
||||||
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />
|
const deleteStopBtn= <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />
|
||||||
return <tr
|
return <tr
|
||||||
key={`${stop[0]}-${stop[1]}`}
|
key={key}
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
aria-label={t("Zoom")}
|
aria-label="Zoom"
|
||||||
value={zoomLevel}
|
value={zoomLevel}
|
||||||
onChange={changedStop => this.changeZoomStop(idx, changedStop, value)}
|
onChange={changedStop => this.changeZoomStop(idx, changedStop, value)}
|
||||||
min={0}
|
min={0}
|
||||||
@@ -172,7 +171,7 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<InputSpec
|
<InputSpec
|
||||||
aria-label={t("Output value")}
|
aria-label="Output value"
|
||||||
fieldName={this.props.fieldName}
|
fieldName={this.props.fieldName}
|
||||||
fieldSpec={this.props.fieldSpec as any}
|
fieldSpec={this.props.fieldSpec as any}
|
||||||
value={value}
|
value={value}
|
||||||
@@ -191,19 +190,19 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
<legend>{labelFromFieldName(this.props.fieldName)}</legend>
|
<legend>{labelFromFieldName(this.props.fieldName)}</legend>
|
||||||
<div className="maputnik-data-fieldset-inner">
|
<div className="maputnik-data-fieldset-inner">
|
||||||
<Block
|
<Block
|
||||||
label={t("Function")}
|
label={"Function"}
|
||||||
>
|
>
|
||||||
<div className="maputnik-data-spec-property-input">
|
<div className="maputnik-data-spec-property-input">
|
||||||
<InputSelect
|
<InputSelect
|
||||||
value={"interpolate"}
|
value={"interpolate"}
|
||||||
onChange={(propVal: string) => this.changeDataType(propVal)}
|
onChange={(propVal: string) => this.changeDataType(propVal)}
|
||||||
title={t("Select a type of data scale (default is 'categorical').")}
|
title={"Select a type of data scale (default is 'categorical')."}
|
||||||
options={this.getDataFunctionTypes(this.props.fieldSpec!)}
|
options={this.getDataFunctionTypes(this.props.fieldSpec!)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Block>
|
</Block>
|
||||||
<Block
|
<Block
|
||||||
label={t("Base")}
|
label={"Base"}
|
||||||
>
|
>
|
||||||
<div className="maputnik-data-spec-property-input">
|
<div className="maputnik-data-spec-property-input">
|
||||||
<InputSpec
|
<InputSpec
|
||||||
@@ -216,11 +215,11 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
</Block>
|
</Block>
|
||||||
<div className="maputnik-function-stop">
|
<div className="maputnik-function-stop">
|
||||||
<table className="maputnik-function-stop-table maputnik-function-stop-table--zoom">
|
<table className="maputnik-function-stop-table maputnik-function-stop-table--zoom">
|
||||||
<caption>{t("Stops")}</caption>
|
<caption>Stops</caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{t("Zoom")}</th>
|
<th>Zoom</th>
|
||||||
<th rowSpan={2}>{t("Output value")}</th>
|
<th rowSpan={2}>Output value</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -235,7 +234,7 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
>
|
>
|
||||||
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d={mdiTableRowPlusAfter} />
|
<path fill="currentColor" d={mdiTableRowPlusAfter} />
|
||||||
</svg> {t("Add stop")}
|
</svg> Add stop
|
||||||
</InputButton>
|
</InputButton>
|
||||||
<InputButton
|
<InputButton
|
||||||
className="maputnik-add-stop"
|
className="maputnik-add-stop"
|
||||||
@@ -243,7 +242,7 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
>
|
>
|
||||||
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d={mdiFunctionVariant} />
|
<path fill="currentColor" d={mdiFunctionVariant} />
|
||||||
</svg> {t("Convert to expression")}
|
</svg> Convert to expression
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -263,6 +262,3 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ZoomProperty = withTranslation()(ZoomPropertyInternal);
|
|
||||||
export default ZoomProperty;
|
|
||||||
|
|||||||
+36
-48
@@ -1,40 +1,40 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "0-empty-style",
|
"id": "osm-liberty",
|
||||||
"title": "Empty Style",
|
"title": "OSM Liberty",
|
||||||
"url": "https://cdn.jsdelivr.net/gh/maputnik/editor@9cf74ca405d2be0608b57db8109cf3a6af5b9f49/src/config/empty-style.json",
|
"url": "https://maputnik.github.io/osm-liberty/style.json",
|
||||||
"thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAQAAAAHDYbIAAAAEUlEQVR42mP8/58BDhiJ4wAA974H/U5Xe1oAAAAASUVORK5CYII="
|
"thumbnail": "https://maputnik.github.io/thumbnails/osm-liberty.png"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dark-matter",
|
|
||||||
"title": "Dark Matter",
|
|
||||||
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/dark-matter-gl-style@v1.9/style.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/dark-matter.png"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "maptiler-basic-gl-style",
|
"id": "maptiler-basic-gl-style",
|
||||||
"title": "Maptiler Basic",
|
"title": "Maptiler Basic",
|
||||||
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/klokantech-basic-gl-style@v1.10/style.json",
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/klokantech-basic-gl-style@v1.9/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/klokantech-basic.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/klokantech-basic.png"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "dark-matter",
|
||||||
|
"title": "Dark Matter",
|
||||||
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/dark-matter-gl-style@v1.8/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/dark-matter.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "positron",
|
||||||
|
"title": "Positron",
|
||||||
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/positron-gl-style@v1.8/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/positron.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "osm-bright",
|
||||||
|
"title": "OSM Bright",
|
||||||
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/osm-bright-gl-style@v1.9/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/osm-bright.png"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "maptiler-toner-gl-style",
|
"id": "maptiler-toner-gl-style",
|
||||||
"title": "Toner",
|
"title": "Toner",
|
||||||
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/toner-gl-style@v1.0/style.json",
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/toner-gl-style@339e5b7/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/toner.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/toner.png"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "os-zoomstack-light",
|
|
||||||
"title": "Zoomstack Light",
|
|
||||||
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/styles/open-zoomstack-light/style.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-light.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "os-zoomstack-night",
|
|
||||||
"title": "Zoomstack Night",
|
|
||||||
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/styles/open-zoomstack-night/style.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-night.png"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "os-zoomstack-outdoor",
|
"id": "os-zoomstack-outdoor",
|
||||||
"title": "Zoomstack Outdoor",
|
"title": "Zoomstack Outdoor",
|
||||||
@@ -48,33 +48,21 @@
|
|||||||
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-road.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-road.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "osm-bright",
|
"id": "os-zoomstack-light",
|
||||||
"title": "OSM Bright",
|
"title": "Zoomstack Light",
|
||||||
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/osm-bright-gl-style@v1.11/style.json",
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/styles/open-zoomstack-light/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/osm-bright.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-light.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "osm-liberty",
|
"id": "os-zoomstack-night",
|
||||||
"title": "OSM Liberty",
|
"title": "Zoomstack Night",
|
||||||
"url": "https://maputnik.github.io/osm-liberty/style.json",
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/styles/open-zoomstack-night/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/osm-liberty.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-night.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "positron",
|
"id": "empty-style",
|
||||||
"title": "Positron",
|
"title": "Empty Style",
|
||||||
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/positron-gl-style@v1.9/style.json",
|
"url": "https://cdn.jsdelivr.net/gh/maputnik/editor@9cf74ca405d2be0608b57db8109cf3a6af5b9f49/src/config/empty-style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/positron.png"
|
"thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAQAAAAHDYbIAAAAEUlEQVR42mP8/58BDhiJ4wAA974H/U5Xe1oAAAAASUVORK5CYII="
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "stadia-outdoors",
|
|
||||||
"title": "Stadia Outdoors",
|
|
||||||
"url": "https://tiles.stadiamaps.com/styles/outdoors.json",
|
|
||||||
"thumbnail": "https://tiles.stadiamaps.com/static/outdoors.png?size=480x320¢er=47.350259,8.49035&zoom=16"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "versatiles-colorful",
|
|
||||||
"title": "Versatiles Colorful",
|
|
||||||
"url": "https://tiles.versatiles.org/assets/styles/colorful.json",
|
|
||||||
"thumbnail": "https://github.com/maplibre/maputnik/assets/649392/6cd69818-c541-46e4-a920-65fb4f654931"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"openmaptiles": {
|
"openmaptiles": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"url": "https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key={key}",
|
"url": "https://api.maptiler.com/tiles/v3/tiles.json?key={key}",
|
||||||
"title": "OpenMapTiles v3"
|
"title": "OpenMapTiles v3"
|
||||||
},
|
},
|
||||||
"thunderforest_transport": {
|
"thunderforest_transport": {
|
||||||
|
|||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
import i18n from "i18next";
|
|
||||||
import detector from "i18next-browser-languagedetector";
|
|
||||||
import resourcesToBackend from "i18next-resources-to-backend";
|
|
||||||
import { initReactI18next } from "react-i18next";
|
|
||||||
|
|
||||||
export const supportedLanguages = {
|
|
||||||
"en": "English",
|
|
||||||
"ja": "日本語",
|
|
||||||
"he": "עברית",
|
|
||||||
"zh": "简体中文"
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
i18n
|
|
||||||
.use(detector) // detect user language from browser settings
|
|
||||||
.use(
|
|
||||||
resourcesToBackend((lang: string, ns: string) => {
|
|
||||||
if (lang === "en") {
|
|
||||||
// English is the default language, so we don't need to load any resources for it.
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return import(`./locales/${lang}/${ns}.json`);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.use(initReactI18next) // required to initialize react-i18next
|
|
||||||
.init({
|
|
||||||
supportedLngs: Object.keys(supportedLanguages),
|
|
||||||
keySeparator: false, // we do not use keys in form messages.welcome
|
|
||||||
nsSeparator: false,
|
|
||||||
interpolation: {
|
|
||||||
escapeValue: false // React already escapes for us
|
|
||||||
},
|
|
||||||
saveMissing: true, // this needs to be set for missingKeyHandler to work
|
|
||||||
fallbackLng: false, // we set the fallback to false so we can get the correct language in the missingKeyHandler
|
|
||||||
missingKeyHandler: (lngs, _ns, key) => {
|
|
||||||
if (lngs[0] === "en") { return; }
|
|
||||||
console.warn(`Missing translation for "${key}" in "${lngs.join(", ")}"`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default i18n;
|
|
||||||
+4
-5
@@ -1,16 +1,15 @@
|
|||||||
import { IconContext } from "react-icons";
|
import { IconContext } from "react-icons";
|
||||||
import { createRoot } from 'react-dom/client';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
import './favicon.ico'
|
import './favicon.ico'
|
||||||
import './styles/index.scss'
|
import './styles/index.scss'
|
||||||
import './i18n';
|
|
||||||
import App from './components/App';
|
import App from './components/App';
|
||||||
|
|
||||||
const root = createRoot(document.querySelector("#app"));
|
ReactDOM.render(
|
||||||
root.render(
|
|
||||||
<IconContext.Provider value={{className: 'react-icons'}}>
|
<IconContext.Provider value={{className: 'react-icons'}}>
|
||||||
<App/>
|
<App/>
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>,
|
||||||
|
document.querySelector("#app")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hide the loader.
|
// Hide the loader.
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import { TFunction } from "i18next";
|
const spec = {
|
||||||
|
|
||||||
const spec = (t: TFunction) => ({
|
|
||||||
maputnik: {
|
maputnik: {
|
||||||
maptiler_access_token: {
|
maptiler_access_token: {
|
||||||
label: t("MapTiler Access Token"),
|
label: "MapTiler Access Token",
|
||||||
doc: t("Public access token for MapTiler Cloud.")
|
doc: "Public access token for MapTiler Cloud."
|
||||||
},
|
},
|
||||||
thunderforest_access_token: {
|
thunderforest_access_token: {
|
||||||
label: t("Thunderforest Access Token"),
|
label: "Thunderforest Access Token",
|
||||||
doc: t("Public access token for Thunderforest services.")
|
doc: "Public access token for Thunderforest services."
|
||||||
},
|
},
|
||||||
style_renderer: {
|
style_renderer: {
|
||||||
label: t("Style Renderer"),
|
label: "Style Renderer",
|
||||||
doc: t("Choose the default Maputnik renderer for this style."),
|
doc: "Choose the default Maputnik renderer for this style.",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
export default spec;
|
export default spec;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import stylegen from '@maplibre/maplibre-gl-inspect/lib/stylegen'
|
// @ts-ignore
|
||||||
import colors from '@maplibre/maplibre-gl-inspect/lib/colors'
|
import stylegen from 'mapbox-gl-inspect/lib/stylegen'
|
||||||
|
// @ts-ignore
|
||||||
|
import colors from 'mapbox-gl-inspect/lib/colors'
|
||||||
import type {FilterSpecification,LayerSpecification } from 'maplibre-gl'
|
import type {FilterSpecification,LayerSpecification } from 'maplibre-gl'
|
||||||
|
|
||||||
export type HighlightedLayer = LayerSpecification & {filter?: FilterSpecification};
|
export type HighlightedLayer = LayerSpecification & {filter?: FilterSpecification};
|
||||||
|
|||||||
@@ -5,15 +5,13 @@ export default class ZoomControl {
|
|||||||
_container: HTMLDivElement | undefined = undefined;
|
_container: HTMLDivElement | undefined = undefined;
|
||||||
_textEl: HTMLSpanElement | null = null;
|
_textEl: HTMLSpanElement | null = null;
|
||||||
|
|
||||||
constructor(public label: string) {}
|
|
||||||
|
|
||||||
onAdd(map: Map) {
|
onAdd(map: Map) {
|
||||||
this._map = map;
|
this._map = map;
|
||||||
this._container = document.createElement('div');
|
this._container = document.createElement('div');
|
||||||
this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-zoom';
|
this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-zoom';
|
||||||
this._container.setAttribute("data-wd-key", "maplibre:ctrl-zoom");
|
this._container.setAttribute("data-wd-key", "maplibre:ctrl-zoom");
|
||||||
this._container.innerHTML = `
|
this._container.innerHTML = `
|
||||||
${this.label} <span></span>
|
Zoom: <span></span>
|
||||||
`;
|
`;
|
||||||
this._textEl = this._container.querySelector("span");
|
this._textEl = this._container.querySelector("span");
|
||||||
|
|
||||||
|
|||||||
@@ -1,184 +0,0 @@
|
|||||||
{
|
|
||||||
"Input value": "ערך קבלה",
|
|
||||||
"Data value": "ערך מידע",
|
|
||||||
"Output value": "ערך החזרה",
|
|
||||||
"Function": "פונקציה",
|
|
||||||
"Select a type of data scale (default is 'categorical').": "בחרו סוג מידע לטובת הגדלה (ברירת המחדל היא קטגוריה)",
|
|
||||||
"Base": "בסיס",
|
|
||||||
"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": "המרה לפונקציית מידע",
|
|
||||||
"Layer <1>{formatLayerId(layerId)}</1>: {parsed.data.message}": "שכבה <1>{formatLayerId(layerId)}</1>: {parsed.data.message}",
|
|
||||||
"switch to layer": "שינוי לשכבה",
|
|
||||||
"Map": "מפה",
|
|
||||||
"Inspect": "בדיקה",
|
|
||||||
"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": "פתיחה",
|
|
||||||
"Export": "ייצוא",
|
|
||||||
"Data Sources": "מקורות מידע",
|
|
||||||
"Style Settings": "הגדרות הסטייל",
|
|
||||||
"View": "תצוגה",
|
|
||||||
"Color accessibility": "נגישות צבעים",
|
|
||||||
"Language": "שפה",
|
|
||||||
"Help": "עזרה",
|
|
||||||
"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": "הסרת איבר מערך",
|
|
||||||
"Press <1>ESC</1> to lose focus": "לחצו על <1>ESC</1> על מנת לאבד פוקוס",
|
|
||||||
"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",
|
|
||||||
"Layer": "שכבה",
|
|
||||||
"JSON Editor": "עורך JSON",
|
|
||||||
"Delete": "מחיקה",
|
|
||||||
"Duplicate": "שכפול",
|
|
||||||
"Show": "הצגה",
|
|
||||||
"Hide": "הסתרה",
|
|
||||||
"Move layer up": "העלה שכבה",
|
|
||||||
"Move layer down": "הורד שכבה",
|
|
||||||
"Layer: {{layerId}}": "שכבה: {{layerId}}",
|
|
||||||
"Layers": "שכבות",
|
|
||||||
"Collapse": "הקטנה",
|
|
||||||
"Expand": "הגדלה",
|
|
||||||
"Add Layer": "הוספת שכבה",
|
|
||||||
"Zoom:": "זום:",
|
|
||||||
"Search": "חיפוש",
|
|
||||||
"Close popup": "סגירת החלון",
|
|
||||||
"cursor:": "סמן",
|
|
||||||
"center:": "מרכז:",
|
|
||||||
"rotation:": "סיבוב:",
|
|
||||||
"Close modal": "סגירת חלונית",
|
|
||||||
"Debug": "דיבאג",
|
|
||||||
"Options": "אפשרויות",
|
|
||||||
"<0>Open in OSM</0> — Opens the current view on openstreetmap.org": "<0>פתיחה ב-OSM</0> - פתיחה של התצוגה הנוכחית ב- openstreetmap.org",
|
|
||||||
"Export Style": "ייצוא של הסטייל",
|
|
||||||
"Download Style": "הורדה של הסטייל",
|
|
||||||
"Download a JSON style to your computer.": "הורדה של הסטייל למחשב",
|
|
||||||
"Download HTML": "הורדה כ-HTML",
|
|
||||||
"Cancel": "ביטול",
|
|
||||||
"Open Style": "פתיחת סטייל",
|
|
||||||
"Upload Style": "העלאה של סטייל",
|
|
||||||
"Upload a JSON style from your computer.": "העלאה של סטייל מהמחשב",
|
|
||||||
"Style file": "קובץ סטייל",
|
|
||||||
"Upload": "העלאה",
|
|
||||||
"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": "מיקום האור",
|
|
||||||
"Terrain source": "מקור terrain",
|
|
||||||
"Terrain exaggeration": "כמה \"להגזים\"",
|
|
||||||
"Transition delay": "זמן לפני מעבר",
|
|
||||||
"Transition duration": "זמן מעבר",
|
|
||||||
"Open Layers (experimental)": "Open Layers (experimental)",
|
|
||||||
"Shortcuts menu": "תפריט קיצורי דרך",
|
|
||||||
"Open modal": "פתיחת חלונית",
|
|
||||||
"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 פיקסלים",
|
|
||||||
"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> על מנת לאבד פוקוס מכל פקד פעיל, ואז ליחצו על אחד מאלו:",
|
|
||||||
"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 (XYZ URLs)": "Vector (XYZ URLs)",
|
|
||||||
"Raster (TileJSON URL)": "Raster (TileJSON URL)",
|
|
||||||
"Raster (XYZ URL)": "Raster (XYZ URL)",
|
|
||||||
"Raster DEM (TileJSON URL)": "Raster DEM (TileJSON URL)",
|
|
||||||
"Raster DEM (XYZ URLs)": "Raster DEM (XYZ URLs)",
|
|
||||||
"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": "כתובת אריחים",
|
|
||||||
"Coord top left": "מיקום שמאלי עליון",
|
|
||||||
"Coord top right": "מיקום ימני עליון",
|
|
||||||
"Coord bottom right": "מיקום ימני תחתון",
|
|
||||||
"Coord bottom left": "מיקום שמאלי תחתון",
|
|
||||||
"Image URL": "כתובת תמונה",
|
|
||||||
"Video URL": "כתובת וידאו",
|
|
||||||
"GeoJSON URL": "כתובת GeoJSON",
|
|
||||||
"GeoJSON": "GeoJSON",
|
|
||||||
"Cluster": "קיבוץ",
|
|
||||||
"Encoding": "קידוד",
|
|
||||||
"Error:": "שגיאה",
|
|
||||||
"MapTiler Access Token": "MapTiler Access Token",
|
|
||||||
"Public access token for MapTiler Cloud.": "Public access token for MapTiler Cloud.",
|
|
||||||
"Thunderforest Access Token": "Thunderforest Access Token",
|
|
||||||
"Public access token for Thunderforest services.": "Public access token for Thunderforest services.",
|
|
||||||
"Style Renderer": "צייר הסטייל",
|
|
||||||
"Choose the default Maputnik renderer for this style.": "בחירת צייר ברירת המחדל של מפוטניק עבור הסטייל הזה"
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
{
|
|
||||||
"Input value": "入力値",
|
|
||||||
"Data value": "データ値",
|
|
||||||
"Output value": "値",
|
|
||||||
"Function": "関数",
|
|
||||||
"Select a type of data scale (default is 'categorical').": "データスケールの種類を選択してください(デフォルトは「categorical」です)。",
|
|
||||||
"Base": "ベース",
|
|
||||||
"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": "プロパティをデータ関数に変換する",
|
|
||||||
"Layer <1>{formatLayerId(layerId)}</1>: {parsed.data.message}": "レイヤ<1>{formatLayerId(layerId)}</1>: {parsed.data.message}",
|
|
||||||
"switch to layer": "レイヤへ切替",
|
|
||||||
"Map": "地図",
|
|
||||||
"Inspect": "検査",
|
|
||||||
"Deuteranopia filter": "緑色盲フィルタ",
|
|
||||||
"Protanopia filter": "赤色盲フィルタ",
|
|
||||||
"Tritanopia filter": "青色盲フィルタ",
|
|
||||||
"Achromatopsia filter": "全色盲フィルタ",
|
|
||||||
"Layers list": "レイヤー一覧",
|
|
||||||
"Layer editor": "レイヤーエディタ",
|
|
||||||
"Map view": "地図画面",
|
|
||||||
"Maputnik on GitHub": "GitHubのMaputnik",
|
|
||||||
"Open": "開く",
|
|
||||||
"Export": "エクスポート",
|
|
||||||
"Data Sources": "データソース",
|
|
||||||
"Style Settings": "スタイル設定",
|
|
||||||
"View": "表示",
|
|
||||||
"Color accessibility": "色のアクセシビリティ",
|
|
||||||
"Language": "言語",
|
|
||||||
"Help": "ヘルプ",
|
|
||||||
"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": "配列の項目を削除",
|
|
||||||
"Press <1>ESC</1> to lose focus": "<1>ESC</1>を押すとフォーカスが外れます",
|
|
||||||
"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> ドメインを使用してください。",
|
|
||||||
"Layer": "レイヤー",
|
|
||||||
"JSON Editor": "JSONエディタ",
|
|
||||||
"Delete": "削除",
|
|
||||||
"Duplicate": "複製",
|
|
||||||
"Show": "表示",
|
|
||||||
"Hide": "非表示",
|
|
||||||
"Move layer up": "上に移動",
|
|
||||||
"Move layer down": "下に移動",
|
|
||||||
"Layer: {{layerId}}": "レイヤー {{layerId}}",
|
|
||||||
"Layers": "レイヤー",
|
|
||||||
"Collapse": "畳む",
|
|
||||||
"Expand": "展開",
|
|
||||||
"Add Layer": "レイヤー追加",
|
|
||||||
"Zoom:": "ズーム:",
|
|
||||||
"Search": "検索",
|
|
||||||
"Close popup": "ポップアップを閉じる",
|
|
||||||
"cursor:": "カーソル",
|
|
||||||
"center:": "中央:",
|
|
||||||
"rotation:": "回転角度:",
|
|
||||||
"Close modal": "モーダルを閉じる",
|
|
||||||
"Debug": "デバッグ",
|
|
||||||
"Options": "設定",
|
|
||||||
"<0>Open in OSM</0> — Opens the current view on openstreetmap.org": "現在のビューを <0>openstreetmap.org で開く</0>",
|
|
||||||
"Export Style": "スタイルをエクスポート",
|
|
||||||
"Download Style": "スタイルをダウンロード",
|
|
||||||
"Download a JSON style to your computer.": "パソコンにJSONスタイルをダウンロードします。",
|
|
||||||
"Download HTML": "HTMLをダウンロード",
|
|
||||||
"Cancel": "キャンセル",
|
|
||||||
"Open Style": "スタイルを開く",
|
|
||||||
"Upload Style": "スタイルをアップロードする",
|
|
||||||
"Upload a JSON style from your computer.": "JSONスタイルをパソコンからアップロードする",
|
|
||||||
"Style file": "スタイルファイル",
|
|
||||||
"Upload": "アップロード",
|
|
||||||
"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": "ライト位置",
|
|
||||||
"Terrain source": "地形ソース",
|
|
||||||
"Terrain exaggeration": "地形の誇張",
|
|
||||||
"Transition delay": "遷移遅延",
|
|
||||||
"Transition duration": "遷移期間",
|
|
||||||
"Open Layers (experimental)": "Open Layers (実験的)",
|
|
||||||
"Shortcuts menu": "ショートカットメニュー",
|
|
||||||
"Open modal": "モーダルを開く",
|
|
||||||
"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ピクセル右に移動",
|
|
||||||
"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> を押してアクティブな要素からフォーカスを外し、次に以下のいずれかを押してください:",
|
|
||||||
"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 (XYZ URLs)": "ベクトル (XYZ URL)",
|
|
||||||
"Raster (TileJSON URL)": "ラスタ (TileJSON URL)",
|
|
||||||
"Raster (XYZ URL)": "ラスタ (XYZ URL)",
|
|
||||||
"Raster DEM (TileJSON URL)": "ラスタ DEM (TileJSON URL)",
|
|
||||||
"Raster DEM (XYZ URLs)": "ラスタ DEM (XYZ 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",
|
|
||||||
"Coord top left": "左上座標",
|
|
||||||
"Coord top right": "右上座標",
|
|
||||||
"Coord bottom right": "右下座標",
|
|
||||||
"Coord bottom left": "左下座標",
|
|
||||||
"Image URL": "画像URL",
|
|
||||||
"Video URL": "動画URL",
|
|
||||||
"GeoJSON URL": "GeoJSON URL",
|
|
||||||
"GeoJSON": "GeoJSON",
|
|
||||||
"Cluster": "クラスタ",
|
|
||||||
"Encoding": "エンコーディング",
|
|
||||||
"Error:": "エラー:",
|
|
||||||
"MapTiler Access Token": "MapTiler アクセストークン",
|
|
||||||
"Public access token for MapTiler Cloud.": "MapTiler Cloud の公開用アクセストークン",
|
|
||||||
"Thunderforest Access Token": "Thunderforest アクセストークン",
|
|
||||||
"Public access token for Thunderforest services.": "Thunderforest サービスの公開用アクセストークン",
|
|
||||||
"Style Renderer": "スタイルレンダラ",
|
|
||||||
"Choose the default Maputnik renderer for this style.": "このスタイルのデフォルトの Maputnik レンダラを選択してください",
|
|
||||||
"Layer options": "レイヤー設定",
|
|
||||||
"Paint properties": "ペイントプロパティ",
|
|
||||||
"Layout properties": "レイアウトプロパティ",
|
|
||||||
"General layout properties": "一般レイアウトプロパティ",
|
|
||||||
"Text layout properties": "文字レイアウトプロパティ",
|
|
||||||
"Icon layout properties": "アイコンレイアウトプロパティ",
|
|
||||||
"Text paint properties": "文字ペイントプロパティ",
|
|
||||||
"Icon paint properties": "アイコンペイントプロパティ"
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
{
|
|
||||||
"Input value": "输入值",
|
|
||||||
"Data value": "数据值",
|
|
||||||
"Output value": "输出值",
|
|
||||||
"Function": "函数",
|
|
||||||
"Select a type of data scale (default is 'categorical').": "选择数据尺度的类型(默认为“分类”)。",
|
|
||||||
"Base": "基础",
|
|
||||||
"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": "将属性转换为数据函数",
|
|
||||||
"Layer <1>{formatLayerId(layerId)}</1>: {parsed.data.message}": "图层<1>{formatLayerId(layerId)}</1>: {parsed.data.message}",
|
|
||||||
"switch to layer": "切换到图层",
|
|
||||||
"Map": "地图",
|
|
||||||
"Inspect": "检查",
|
|
||||||
"Deuteranopia filter": "绿色盲滤镜",
|
|
||||||
"Protanopia filter": "红色盲滤镜",
|
|
||||||
"Tritanopia filter": "蓝色盲滤镜",
|
|
||||||
"Achromatopsia filter": "全色盲滤镜",
|
|
||||||
"Layers list": "图层列表",
|
|
||||||
"Layer editor": "图层编辑器",
|
|
||||||
"Map view": "地图视图",
|
|
||||||
"Maputnik on GitHub": "GitHub上的Maputnik",
|
|
||||||
"Open": "打开",
|
|
||||||
"Export": "导出",
|
|
||||||
"Data Sources": "数据源",
|
|
||||||
"Style Settings": "样式设置",
|
|
||||||
"View": "视图",
|
|
||||||
"Color accessibility": "颜色可访问性",
|
|
||||||
"Language": "语言",
|
|
||||||
"Help": "帮助",
|
|
||||||
"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": "移除数组项目",
|
|
||||||
"Press <1>ESC</1> to lose focus": "按 <1>ESC</1> 键退出焦点",
|
|
||||||
"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> 域名。",
|
|
||||||
"Layer": "图层",
|
|
||||||
"JSON Editor": "JSON编辑器",
|
|
||||||
"Delete": "删除",
|
|
||||||
"Duplicate": "复制",
|
|
||||||
"Show": "显示",
|
|
||||||
"Hide": "隐藏",
|
|
||||||
"Move layer up": "向上移动图层",
|
|
||||||
"Move layer down": "向下移动图层",
|
|
||||||
"Layer: {{layerId}}": "图层: {{layerId}}",
|
|
||||||
"Layers": "图层",
|
|
||||||
"Collapse": "折叠",
|
|
||||||
"Expand": "展开",
|
|
||||||
"Add Layer": "添加图层",
|
|
||||||
"Zoom:": "缩放:",
|
|
||||||
"Search": "搜索",
|
|
||||||
"Close popup": "关闭弹出窗口",
|
|
||||||
"cursor:": "光标:",
|
|
||||||
"center:": "中心:",
|
|
||||||
"rotation:": "旋转:",
|
|
||||||
"Close modal": "关闭模态框",
|
|
||||||
"Debug": "调试",
|
|
||||||
"Options": "选项",
|
|
||||||
"<0>Open in OSM</0> — Opens the current view on openstreetmap.org": "在 openstreetmap.org 打开当前视图",
|
|
||||||
"Export Style": "导出样式",
|
|
||||||
"Download Style": "下载样式",
|
|
||||||
"Download a JSON style to your computer.": "将JSON样式下载到您的电脑。",
|
|
||||||
"Download HTML": "下载HTML",
|
|
||||||
"Cancel": "取消",
|
|
||||||
"Open Style": "打开样式",
|
|
||||||
"Upload Style": "上传样式",
|
|
||||||
"Upload a JSON style from your computer.": "从您的电脑上传JSON样式。",
|
|
||||||
"Style file": "样式文件",
|
|
||||||
"Upload": "上传",
|
|
||||||
"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": "光源位置",
|
|
||||||
"Terrain source": "地形源",
|
|
||||||
"Terrain exaggeration": "地形夸张",
|
|
||||||
"Transition delay": "过渡延迟",
|
|
||||||
"Transition duration": "过渡持续时间",
|
|
||||||
"Open Layers (experimental)": "开放图层(实验性)",
|
|
||||||
"Shortcuts menu": "快捷方式菜单",
|
|
||||||
"Open modal": "打开模态框",
|
|
||||||
"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像素。",
|
|
||||||
"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> 退出任何活动元素的焦点,然后按下以下之一:",
|
|
||||||
"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 (XYZ URLs)": "矢量数据 (XYZ URLs)",
|
|
||||||
"Raster (TileJSON URL)": "栅格数据 (TileJSON URL)",
|
|
||||||
"Raster (XYZ URL)": "栅格数据 (XYZ URL)",
|
|
||||||
"Raster DEM (TileJSON URL)": "栅格高程数据 (TileJSON URL)",
|
|
||||||
"Raster DEM (XYZ URLs)": "栅格高程数据 (XYZ URLs)",
|
|
||||||
"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",
|
|
||||||
"Coord top left": "左上角坐标",
|
|
||||||
"Coord top right": "右上角坐标",
|
|
||||||
"Coord bottom right": "右下角坐标",
|
|
||||||
"Coord bottom left": "左下角坐标",
|
|
||||||
"Image URL": "图像URL",
|
|
||||||
"Video URL": "视频URL",
|
|
||||||
"GeoJSON URL": "GeoJSON URL",
|
|
||||||
"GeoJSON": "GeoJSON",
|
|
||||||
"Cluster": "聚合",
|
|
||||||
"Encoding": "编码",
|
|
||||||
"Error:": "错误:",
|
|
||||||
"MapTiler Access Token": "MapTiler 访问令牌",
|
|
||||||
"Public access token for MapTiler Cloud.": "MapTiler Cloud 的公共访问令牌。",
|
|
||||||
"Thunderforest Access Token": "Thunderforest 访问令牌",
|
|
||||||
"Public access token for Thunderforest services.": "Thunderforest 服务的公共访问令牌。",
|
|
||||||
"Style Renderer": "样式渲染器",
|
|
||||||
"Choose the default Maputnik renderer for this style.": "为这种样式选择默认的Maputnik渲染器。",
|
|
||||||
"Layer options": "图层选项",
|
|
||||||
"Paint properties": "绘制属性",
|
|
||||||
"Layout properties": "布局属性",
|
|
||||||
"General layout properties": "常规布局属性",
|
|
||||||
"Text layout properties": "文本布局属性",
|
|
||||||
"Icon layout properties": "图标布局属性",
|
|
||||||
"Text paint properties": "文本绘制属性",
|
|
||||||
"Icon paint properties": "图标绘制属性"
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,11 @@
|
|||||||
.maputnik-map__container {
|
.maputnik-map__container {
|
||||||
background: white;
|
background: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: fixed !important;
|
||||||
|
top: $toolbar-height + $toolbar-offset;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: calc(100% - #{$toolbar-height + $toolbar-offset});
|
||||||
width: $layout-map-width;
|
width: $layout-map-width;
|
||||||
|
|
||||||
&--error {
|
&--error {
|
||||||
|
|||||||
@@ -14,28 +14,26 @@
|
|||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
|
|
||||||
&-main {
|
&-list {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: calc(100% - #{$toolbar-height + $toolbar-offset});
|
height: calc(100% - #{$toolbar-height + $toolbar-offset});
|
||||||
top: $toolbar-height + $toolbar-offset;
|
top: $toolbar-height + $toolbar-offset;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-list {
|
|
||||||
width: 200px;
|
width: 200px;
|
||||||
background-color: $color-black;
|
background-color: $color-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-drawer {
|
&-drawer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
height: calc(100% - #{$toolbar-height + $toolbar-offset});
|
||||||
|
top: $toolbar-height + $toolbar-offset;
|
||||||
|
left: 200px;
|
||||||
|
z-index: 1;
|
||||||
width: 370px;
|
width: 370px;
|
||||||
background-color: $color-black;
|
background-color: $color-black;
|
||||||
// scroll-container is position: absolute
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-bottom {
|
&-bottom {
|
||||||
|
|||||||
@@ -308,6 +308,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-modal-survey {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-modal-survey__logo {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: 128px;
|
||||||
|
width: 128px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-modal-survey__description {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-modal-survey__footnote {
|
||||||
|
color: $color-green;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-settings {
|
.modal-settings {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-popup-table {
|
.maputnik-popup-feature-left {
|
||||||
width: 100%;
|
margin-left: $margin-2;
|
||||||
}
|
color: $color-lowgray;
|
||||||
|
flex: 1;
|
||||||
.maputnik-popup-table-cell {
|
}
|
||||||
|
.maputnik-popup-feature-right {
|
||||||
|
flex: 1;
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
padding-left: $margin-2;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user