From 979137eb9ce7b09e13557153e64453128eecd56f Mon Sep 17 00:00:00 2001 From: prusswan Date: Sun, 19 Jan 2025 03:27:31 +0800 Subject: [PATCH 01/12] added react-dropzone --- package-lock.json | 43 +++++++++++++++++++++++++++++++---- package.json | 1 + src/components/AppToolbar.tsx | 11 +++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d91d630e4..aa7919f58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "react-collapse": "^5.1.1", "react-color": "^2.19.3", "react-dom": "^18.2.0", + "react-dropzone": "^14.3.5", "react-file-reader-input": "^2.0.0", "react-i18next": "^15.4.0", "react-icon-base": "^2.1.2", @@ -3856,6 +3857,14 @@ "node": ">= 4.0.0" } }, + "node_modules/attr-accept": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -6219,6 +6228,17 @@ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" }, + "node_modules/file-selector": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", + "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -10641,6 +10661,22 @@ "react": "^18.3.1" } }, + "node_modules/react-dropzone": { + "version": "14.3.5", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.5.tgz", + "integrity": "sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ==", + "dependencies": { + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-file-reader-input": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-file-reader-input/-/react-file-reader-input-2.0.0.tgz", @@ -12535,10 +12571,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/tunnel-agent": { "version": "0.6.0", diff --git a/package.json b/package.json index 84ccd37ef..dcd1b223e 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "react-collapse": "^5.1.1", "react-color": "^2.19.3", "react-dom": "^18.2.0", + "react-dropzone": "^14.3.5", "react-file-reader-input": "^2.0.0", "react-i18next": "^15.4.0", "react-icon-base": "^2.1.2", diff --git a/src/components/AppToolbar.tsx b/src/components/AppToolbar.tsx index ca12aa50f..2bf8461ab 100644 --- a/src/components/AppToolbar.tsx +++ b/src/components/AppToolbar.tsx @@ -17,6 +17,8 @@ import maputnikLogo from 'maputnik-design/logos/logo-color.svg?inline' import { withTranslation, WithTranslation } from 'react-i18next'; import { supportedLanguages } from '../i18n'; +import Dropzone from 'react-dropzone'; // for class components + // This is required because of , there isn't another way to detect support that I'm aware of. const browser = detect(); const colorAccessibilityFiltersEnabled = ['chrome', 'firefox'].indexOf(browser!.name) > -1; @@ -289,6 +291,15 @@ class AppToolbarInternal extends React.Component { {t("Help")} + + + {({getRootProps, getInputProps}) => ( +
+ + Drop file here +
+ )} +
From 7ee8e44ca1b92bec907510a96bc9ff396f66423a Mon Sep 17 00:00:00 2001 From: prusswan Date: Mon, 20 Jan 2025 23:13:02 +0800 Subject: [PATCH 02/12] parse FileSource from dropped file for protocol.add # Conflicts: # src/components/MapMaplibreGl.tsx --- src/components/App.tsx | 14 +++++++++++++- src/components/MapMaplibreGl.tsx | 9 +++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 31e2cf575..f894978fa 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -8,7 +8,7 @@ import get from 'lodash.get' import {unset} from 'lodash' import {arrayMoveMutable} from 'array-move' import hash from "string-hash"; -import { PMTiles } from "pmtiles"; +import { FileSource, PMTiles } from 'pmtiles'; import {Map, LayerSpecification, StyleSpecification, ValidationError, SourceSpecification} from 'maplibre-gl' import {latest, validateStyleMin} from '@maplibre/maplibre-gl-style-spec' @@ -741,6 +741,7 @@ export default class App extends React.Component { onChange={this.onMapChange} options={this.state.maplibreGlDebugOptions} inspectModeEnabled={this.state.mapState === "inspect"} + file={this.state.file} highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]} onLayerSelect={this.onLayerSelect} /> } @@ -881,6 +882,15 @@ export default class App extends React.Component { }); } + onFileSelected = (e) => { + var file = e[0]; + var pmt = new PMTiles(new FileSource(file)); + console.log("App.onFileSelected", pmt); + this.setState({ + file: pmt + }) + } + render() { const layers = this.state.mapStyle.layers || [] const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : undefined @@ -895,6 +905,8 @@ export default class App extends React.Component { onStyleOpen={this.onStyleChanged} onSetMapState={this.setMapState} onToggleModal={this.toggleModal.bind(this)} + files={this.state.files} + onFileSelected={this.onFileSelected} /> const layerList = { this.forceUpdate(); @@ -134,7 +135,10 @@ class MapMaplibreGlInternal extends React.Component { From 0fbda5dc84c7c0ad79ec143911f522dec43a8040 Mon Sep 17 00:00:00 2001 From: prusswan Date: Tue, 21 Jan 2025 02:35:01 +0800 Subject: [PATCH 03/12] fix for Inspect to work with FileSource --- src/components/MapMaplibreGl.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/MapMaplibreGl.tsx b/src/components/MapMaplibreGl.tsx index dd17b0b43..2e591c55a 100644 --- a/src/components/MapMaplibreGl.tsx +++ b/src/components/MapMaplibreGl.tsx @@ -137,8 +137,19 @@ class MapMaplibreGlInternal extends React.Component { + let layerNames = metadata.vector_layers.map((e) => e.id); + + map.style.sourceCaches["source"]._source.vectorLayerIds = layerNames; + console.log("layerNames for inspect:", layerNames); + }); + } } + } componentDidMount() { From cdfe88ffc01b1ebb19540dec9a69c0d41079577b Mon Sep 17 00:00:00 2001 From: prusswan Date: Fri, 24 Jan 2025 12:18:11 +0800 Subject: [PATCH 04/12] removed unused prop (files in AppToolbar) fixed TypeScript errors --- src/components/App.tsx | 5 +++-- src/components/AppToolbar.tsx | 1 + src/components/MapMaplibreGl.tsx | 12 +++++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index f894978fa..102ce8a79 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -131,6 +131,7 @@ type AppState = { debug: boolean } fileHandle: FileSystemFileHandle | null + file: PMTiles | null } export default class App extends React.Component { @@ -287,6 +288,7 @@ export default class App extends React.Component { debugToolbox: false, }, fileHandle: null, + file: null } this.layerWatcher = new LayerWatcher({ @@ -882,7 +884,7 @@ export default class App extends React.Component { }); } - onFileSelected = (e) => { + onFileSelected = (e: File[]) => { var file = e[0]; var pmt = new PMTiles(new FileSource(file)); console.log("App.onFileSelected", pmt); @@ -905,7 +907,6 @@ export default class App extends React.Component { onStyleOpen={this.onStyleChanged} onSetMapState={this.setMapState} onToggleModal={this.toggleModal.bind(this)} - files={this.state.files} onFileSelected={this.onFileSelected} /> diff --git a/src/components/AppToolbar.tsx b/src/components/AppToolbar.tsx index 2bf8461ab..965b7c93c 100644 --- a/src/components/AppToolbar.tsx +++ b/src/components/AppToolbar.tsx @@ -105,6 +105,7 @@ type AppToolbarInternalProps = { onSetMapState(mapState: MapState): unknown mapState?: MapState renderer?: string + onFileSelected(...args: unknown[]): unknown } & WithTranslation; class AppToolbarInternal extends React.Component { diff --git a/src/components/MapMaplibreGl.tsx b/src/components/MapMaplibreGl.tsx index 2e591c55a..f3345e697 100644 --- a/src/components/MapMaplibreGl.tsx +++ b/src/components/MapMaplibreGl.tsx @@ -15,7 +15,7 @@ import MaplibreGeocoder, { MaplibreGeocoderApi, MaplibreGeocoderApiConfig } from import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css'; import { withTranslation, WithTranslation } from 'react-i18next' import i18next from 'i18next' -import { Protocol } from "pmtiles"; +import { PMTiles, Protocol } from "pmtiles"; function renderPopup(popup: JSX.Element, mountNode: ReactDOM.Container): HTMLElement { ReactDOM.render(popup, mountNode); @@ -66,6 +66,7 @@ type MapMaplibreGlInternalProps = { } replaceAccessTokens(mapStyle: StyleSpecification): StyleSpecification onChange(value: {center: LngLat, zoom: number}): unknown + file: PMTiles | null; } & WithTranslation; type MapMaplibreGlState = { @@ -74,6 +75,7 @@ type MapMaplibreGlState = { geocoder: MaplibreGeocoder | null; zoomControl: ZoomControl | null; zoom?: number; + protocol: Protocol | null; }; class MapMaplibreGlInternal extends React.Component { @@ -138,11 +140,11 @@ class MapMaplibreGlInternal extends React.Component { - let layerNames = metadata.vector_layers.map((e) => e.id); + file.getMetadata().then((metadata: any) => { + let layerNames = metadata.vector_layers.map((e: LayerSpecification) => e.id); map.style.sourceCaches["source"]._source.vectorLayerIds = layerNames; console.log("layerNames for inspect:", layerNames); @@ -165,7 +167,7 @@ class MapMaplibreGlInternal extends React.Component Date: Fri, 24 Jan 2025 12:24:15 +0800 Subject: [PATCH 05/12] fixed lint errors --- src/components/App.tsx | 5 ++--- src/components/AppToolbar.tsx | 8 ++++---- src/components/MapMaplibreGl.tsx | 6 +++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 102ce8a79..0cf2b38bc 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -885,9 +885,8 @@ export default class App extends React.Component { } onFileSelected = (e: File[]) => { - var file = e[0]; - var pmt = new PMTiles(new FileSource(file)); - console.log("App.onFileSelected", pmt); + const file = e[0]; + const pmt = new PMTiles(new FileSource(file)); this.setState({ file: pmt }) diff --git a/src/components/AppToolbar.tsx b/src/components/AppToolbar.tsx index 965b7c93c..b27f493b0 100644 --- a/src/components/AppToolbar.tsx +++ b/src/components/AppToolbar.tsx @@ -295,10 +295,10 @@ class AppToolbarInternal extends React.Component { {({getRootProps, getInputProps}) => ( -
- - Drop file here -
+
+ + Drop file here +
)}
diff --git a/src/components/MapMaplibreGl.tsx b/src/components/MapMaplibreGl.tsx index f3345e697..6fc4b74a0 100644 --- a/src/components/MapMaplibreGl.tsx +++ b/src/components/MapMaplibreGl.tsx @@ -139,12 +139,12 @@ class MapMaplibreGlInternal extends React.Component { - let layerNames = metadata.vector_layers.map((e: LayerSpecification) => e.id); + const layerNames = metadata.vector_layers.map((e: LayerSpecification) => e.id); map.style.sourceCaches["source"]._source.vectorLayerIds = layerNames; console.log("layerNames for inspect:", layerNames); @@ -166,7 +166,7 @@ class MapMaplibreGlInternal extends React.Component Date: Fri, 24 Jan 2025 15:33:21 +0800 Subject: [PATCH 06/12] fixed i18n for PMTiles dropzone --- src/components/AppToolbar.tsx | 2 +- src/locales/de/translation.json | 2 ++ src/locales/fr/translation.json | 2 ++ src/locales/he/translation.json | 2 ++ src/locales/ja/translation.json | 2 ++ src/locales/zh/translation.json | 2 ++ 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/AppToolbar.tsx b/src/components/AppToolbar.tsx index b27f493b0..8e2ea4be0 100644 --- a/src/components/AppToolbar.tsx +++ b/src/components/AppToolbar.tsx @@ -297,7 +297,7 @@ class AppToolbarInternal extends React.Component { {({getRootProps, getInputProps}) => (
- Drop file here + {t("Drop PMTiles file here")}
)} diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index 435d9b04f..a65af8bd0 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -35,6 +35,7 @@ "View": "Ansicht", "Color accessibility": "Farbzugänglichkeit", "Help": "Hilfe", + "Drop PMTiles file here": "__STRING_NOT_TRANSLATED__", "Comments for the current layer. This is non-standard and not in the spec.": "Kommentare zur aktuellen Ebene. Das ist nicht standardmäßig und nicht in der Spezifikation.", "Comments": "Kommentare", "Comment...": "Dein Kommentar...", @@ -81,6 +82,7 @@ "Close modal": "Modale Fenster schließen", "Debug": "Debug", "Options": "Optionen", + "<0>Open in OSM — Opens the current view on openstreetmap.org": "__STRING_NOT_TRANSLATED__", "Save Style": "Stil Speichern", "Save the JSON style to your computer.": "Speichere den JSON Stil auf deinem Computer.", "Save as": "Speichern unter", diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index 3460a8c3d..90e709401 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -35,6 +35,7 @@ "View": "Vue", "Color accessibility": "Accessibilité des couleurs", "Help": "Aide", + "Drop PMTiles file here": "__STRING_NOT_TRANSLATED__", "Comments for the current layer. This is non-standard and not in the spec.": "Commentaires pour le calque actuel. Ceci n'est pas standard et n'est pas dans la spécification.", "Comments": "Commentaires", "Comment...": "Votre commentaire...", @@ -81,6 +82,7 @@ "Close modal": "Fermer la fenêtre modale", "Debug": "Déboguer", "Options": "Options", + "<0>Open in OSM — Opens the current view on openstreetmap.org": "__STRING_NOT_TRANSLATED__", "Save Style": "Enregistrer le style", "Save the JSON style to your computer.": "Enregistrer le style JSON sur votre ordinateur.", "Save as": "Enregistrer sous", diff --git a/src/locales/he/translation.json b/src/locales/he/translation.json index 7be17f2bc..03fa8010e 100644 --- a/src/locales/he/translation.json +++ b/src/locales/he/translation.json @@ -35,6 +35,7 @@ "View": "תצוגה", "Color accessibility": "נגישות צבעים", "Help": "עזרה", + "Drop PMTiles file here": "__STRING_NOT_TRANSLATED__", "Comments for the current layer. This is non-standard and not in the spec.": "הערות על השכבה הנוכחית. זה לא חלק מהספסיפיקציות", "Comments": "הערות", "Comment...": "הערה...", @@ -81,6 +82,7 @@ "Close modal": "סגירת חלונית", "Debug": "דיבאג", "Options": "אפשרויות", + "<0>Open in OSM — Opens the current view on openstreetmap.org": "__STRING_NOT_TRANSLATED__", "Save Style": "שמירת הסטייל", "Save the JSON style to your computer.": "שמירת הסטייל JSON במחשב שלך.", "Save as": "שמירה בשם", diff --git a/src/locales/ja/translation.json b/src/locales/ja/translation.json index df84cacc7..67e46448b 100644 --- a/src/locales/ja/translation.json +++ b/src/locales/ja/translation.json @@ -35,6 +35,7 @@ "View": "表示", "Color accessibility": "色のアクセシビリティ", "Help": "ヘルプ", + "Drop PMTiles file here": "__STRING_NOT_TRANSLATED__", "Comments for the current layer. This is non-standard and not in the spec.": "現在のレイヤーのコメント。注意:この機能は標準ではないため、他のライブラリとの互換性状況はわかりません。", "Comments": "コメント", "Comment...": "コメントを書く", @@ -81,6 +82,7 @@ "Close modal": "モーダルを閉じる", "Debug": "デバッグ", "Options": "設定", + "<0>Open in OSM — Opens the current view on openstreetmap.org": "__STRING_NOT_TRANSLATED__", "Save Style": "スタイルを保存", "Save the JSON style to your computer.": "JSONスタイルをコンピュータに保存します。", "Save as": "名前を付けて保存", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index c892585a9..0e28345ae 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -35,6 +35,7 @@ "View": "视图", "Color accessibility": "颜色可访问性", "Help": "帮助", + "Drop PMTiles file here": "__STRING_NOT_TRANSLATED__", "Comments for the current layer. This is non-standard and not in the spec.": "当前图层的注释。注意:这不是标准功能,可能与其他库不兼容。", "Comments": "注释", "Comment...": "写注释...", @@ -81,6 +82,7 @@ "Close modal": "关闭模态框", "Debug": "调试", "Options": "选项", + "<0>Open in OSM — Opens the current view on openstreetmap.org": "__STRING_NOT_TRANSLATED__", "Save Style": "保存样式", "Save the JSON style to your computer.": "将JSON样式保存到您的计算机。", "Save as": "另存为", From 7693217a922677eb9f88f51d0b0b3a944c4c387e Mon Sep 17 00:00:00 2001 From: prusswan Date: Fri, 24 Jan 2025 15:37:34 +0800 Subject: [PATCH 07/12] cleaned up props name and console logging --- src/components/App.tsx | 8 ++++---- src/components/MapMaplibreGl.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 0cf2b38bc..3f41f19bd 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -131,7 +131,7 @@ type AppState = { debug: boolean } fileHandle: FileSystemFileHandle | null - file: PMTiles | null + localPMTiles: PMTiles | null } export default class App extends React.Component { @@ -288,7 +288,7 @@ export default class App extends React.Component { debugToolbox: false, }, fileHandle: null, - file: null + localPMTiles: null } this.layerWatcher = new LayerWatcher({ @@ -743,7 +743,7 @@ export default class App extends React.Component { onChange={this.onMapChange} options={this.state.maplibreGlDebugOptions} inspectModeEnabled={this.state.mapState === "inspect"} - file={this.state.file} + localPMTiles={this.state.localPMTiles} highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]} onLayerSelect={this.onLayerSelect} /> } @@ -888,7 +888,7 @@ export default class App extends React.Component { const file = e[0]; const pmt = new PMTiles(new FileSource(file)); this.setState({ - file: pmt + localPMTiles: pmt }) } diff --git a/src/components/MapMaplibreGl.tsx b/src/components/MapMaplibreGl.tsx index 6fc4b74a0..10d001fc4 100644 --- a/src/components/MapMaplibreGl.tsx +++ b/src/components/MapMaplibreGl.tsx @@ -66,7 +66,7 @@ type MapMaplibreGlInternalProps = { } replaceAccessTokens(mapStyle: StyleSpecification): StyleSpecification onChange(value: {center: LngLat, zoom: number}): unknown - file: PMTiles | null; + localPMTiles: PMTiles | null; } & WithTranslation; type MapMaplibreGlState = { @@ -138,16 +138,16 @@ class MapMaplibreGlInternal extends React.Component { const layerNames = metadata.vector_layers.map((e: LayerSpecification) => e.id); + // used by maplibre-gl-inspect to pick up inspectable layers map.style.sourceCaches["source"]._source.vectorLayerIds = layerNames; - console.log("layerNames for inspect:", layerNames); }); } } From a2809a96690ed5d662a7b5dbcf13b40aec4a50c2 Mon Sep 17 00:00:00 2001 From: prusswan Date: Fri, 24 Jan 2025 16:24:29 +0800 Subject: [PATCH 08/12] make it clearer that the added protocol is for PMTiles only --- src/components/MapMaplibreGl.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/MapMaplibreGl.tsx b/src/components/MapMaplibreGl.tsx index 10d001fc4..50ede15f5 100644 --- a/src/components/MapMaplibreGl.tsx +++ b/src/components/MapMaplibreGl.tsx @@ -75,7 +75,7 @@ type MapMaplibreGlState = { geocoder: MaplibreGeocoder | null; zoomControl: ZoomControl | null; zoom?: number; - protocol: Protocol | null; + pmtilesProtocol: Protocol | null; }; class MapMaplibreGlInternal extends React.Component { @@ -95,7 +95,7 @@ class MapMaplibreGlInternal extends React.Component { this.forceUpdate(); @@ -140,7 +140,7 @@ class MapMaplibreGlInternal extends React.Component { @@ -166,8 +166,7 @@ class MapMaplibreGlInternal extends React.Component Date: Fri, 24 Jan 2025 16:40:16 +0800 Subject: [PATCH 09/12] removed comment for react-dropzone import --- src/components/AppToolbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AppToolbar.tsx b/src/components/AppToolbar.tsx index 8e2ea4be0..861944e62 100644 --- a/src/components/AppToolbar.tsx +++ b/src/components/AppToolbar.tsx @@ -17,7 +17,7 @@ import maputnikLogo from 'maputnik-design/logos/logo-color.svg?inline' import { withTranslation, WithTranslation } from 'react-i18next'; import { supportedLanguages } from '../i18n'; -import Dropzone from 'react-dropzone'; // for class components +import Dropzone from 'react-dropzone'; // This is required because of , there isn't another way to detect support that I'm aware of. const browser = detect(); From a2572c2f5b5153ec519c855ebba16c278799fac0 Mon Sep 17 00:00:00 2001 From: prusswan Date: Sun, 26 Jan 2025 17:30:23 +0800 Subject: [PATCH 10/12] moved Dropzone's onDrop handler to AppToolbar --- src/components/App.tsx | 8 +++----- src/components/AppToolbar.tsx | 9 +++++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 3f41f19bd..f075624ea 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -884,11 +884,9 @@ export default class App extends React.Component { }); } - onFileSelected = (e: File[]) => { - const file = e[0]; - const pmt = new PMTiles(new FileSource(file)); + onLocalPMTilesSelected = (file: File) => { this.setState({ - localPMTiles: pmt + localPMTiles: new PMTiles(new FileSource(file)) }) } @@ -906,7 +904,7 @@ export default class App extends React.Component { onStyleOpen={this.onStyleChanged} onSetMapState={this.setMapState} onToggleModal={this.toggleModal.bind(this)} - onFileSelected={this.onFileSelected} + onLocalPMTilesSelected={this.onLocalPMTilesSelected} /> const layerList = { @@ -137,6 +137,11 @@ class AppToolbarInternal extends React.Component { } } + onFileSelected = (e: File[]) => { + const file = e[0]; + this.props.onLocalPMTilesSelected(file); + } + render() { const t = this.props.t; const views = [ @@ -293,7 +298,7 @@ class AppToolbarInternal extends React.Component { {t("Help")} - + {({getRootProps, getInputProps}) => (
From d6cde2fc0f83d1e6dcb495fb8e2080be6afc6490 Mon Sep 17 00:00:00 2001 From: prusswan Date: Mon, 27 Jan 2025 21:33:42 +0800 Subject: [PATCH 11/12] limit accepted file types to .pmtiles only, improved error handling --- src/components/AppToolbar.tsx | 16 ++++++++++++++-- src/components/MapMaplibreGl.tsx | 2 ++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/AppToolbar.tsx b/src/components/AppToolbar.tsx index fc690378e..6ac98d21f 100644 --- a/src/components/AppToolbar.tsx +++ b/src/components/AppToolbar.tsx @@ -17,7 +17,7 @@ import maputnikLogo from 'maputnik-design/logos/logo-color.svg?inline' import { withTranslation, WithTranslation } from 'react-i18next'; import { supportedLanguages } from '../i18n'; -import Dropzone from 'react-dropzone'; +import { default as Dropzone, FileRejection } from 'react-dropzone'; // This is required because of , there isn't another way to detect support that I'm aware of. const browser = detect(); @@ -142,6 +142,14 @@ class AppToolbarInternal extends React.Component { this.props.onLocalPMTilesSelected(file); } + onFileRejected = (r: FileRejection[]) => { + const errorMessageLine = r.map(e => { + return e.errors.map(f => f.message).join("\n") + }).join("\n"); + console.error("Dropzone file rejected:", errorMessageLine); + alert(errorMessageLine); + } + render() { const t = this.props.t; const views = [ @@ -182,6 +190,10 @@ class AppToolbarInternal extends React.Component { }, ]; + const acceptedFileTypes = { + 'application/octet-stream': [".pmtiles"] + } + const currentView = views.find((view) => { return view.id === this.props.mapState; }); @@ -298,7 +310,7 @@ class AppToolbarInternal extends React.Component { {t("Help")} - + {({getRootProps, getInputProps}) => (
diff --git a/src/components/MapMaplibreGl.tsx b/src/components/MapMaplibreGl.tsx index 50ede15f5..f466cfc0e 100644 --- a/src/components/MapMaplibreGl.tsx +++ b/src/components/MapMaplibreGl.tsx @@ -148,6 +148,8 @@ class MapMaplibreGlInternal extends React.Component { + console.error(`Error in reading local PMTiles file: ${e}`); }); } } From cbbac9ee70ff500a6ecf2ff2ac8f5f23826fab0e Mon Sep 17 00:00:00 2001 From: prusswan Date: Tue, 28 Jan 2025 23:33:36 +0800 Subject: [PATCH 12/12] added cypress e2e test, updated translation strings --- cypress/e2e/local-file.cy.ts | 44 ++++++++++++++++++++++++++++ cypress/fixtures/polygon-z0.pmtiles | Bin 0 -> 468 bytes src/components/AppToolbar.tsx | 4 ++- src/components/MapMaplibreGl.tsx | 11 +++++-- src/locales/de/translation.json | 2 ++ src/locales/fr/translation.json | 2 ++ src/locales/he/translation.json | 2 ++ src/locales/ja/translation.json | 2 ++ src/locales/zh/translation.json | 2 ++ 9 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 cypress/e2e/local-file.cy.ts create mode 100644 cypress/fixtures/polygon-z0.pmtiles diff --git a/cypress/e2e/local-file.cy.ts b/cypress/e2e/local-file.cy.ts new file mode 100644 index 000000000..369596742 --- /dev/null +++ b/cypress/e2e/local-file.cy.ts @@ -0,0 +1,44 @@ +import { MaputnikDriver } from "./maputnik-driver"; + +describe("local file", () => { + const { when, get } = new MaputnikDriver(); + + beforeEach(() => { + when.setStyle(""); + }); + + describe("PMTiles", () => { + it("valid file loads without error", () => { + const fileName = "polygon-z0.pmtiles"; // a small polygon located at Null Island + + const stub = cy.stub(); + cy.on('window:alert', stub); + + get + .bySelector("file", "type") + .selectFile(`cypress/fixtures/${fileName}`, { force: true }); + when.wait(200); + cy.then(() => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(stub).to.not.have.been.called; + }); + }); + + it("invalid file results in error", () => { + const fileName = "example-style.json"; + + const stub = cy.stub(); + cy.on('window:alert', stub); + + get + .bySelector("file", "type") + .selectFile(`cypress/fixtures/${fileName}`, { force: true }); + when.wait(200); + cy.then(() => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(stub).to.be.called; + expect(stub.getCall(0).args[0]).to.contain('File type is not supported'); + }); + }) + }); +}); diff --git a/cypress/fixtures/polygon-z0.pmtiles b/cypress/fixtures/polygon-z0.pmtiles new file mode 100644 index 0000000000000000000000000000000000000000..c86db1f27bba6d3818bb5da5ac6b78e8f6e3303a GIT binary patch literal 468 zcmWIW4av+&EoQD~fB;D-Jp)RAhtlX&KO;oM6)FMcGN988OiVB#pvwAbGZ-3x7!KsS zIlyct;pCKrl2jH3ANx)QR-h0r{uR(?Y;E0N${r@wV$Rqz?{?h$%CZ~pR-fD;chPxcGvD5#swCfUJ3q9X zyYJfGQaN*z>CwX)(_MHoE_$j>pZmpR;iPE+bNH@ihyD-PGDW#IC`?~Ew^Gu@^U;5~ zMLN|*T=}&jpE@sYjM(y1WHrOxzp@|qI{uNHt+DuR>j{?#b(QndSF&YxKiSJB=o0K; zmUMDvv&>(=%}+VLv)Zh>lkdN)q3RE>2ir|u&wq}OjgFMDdZaDyH+~%MwD)0=m+&ls zb+dE0Za)PE6vEq+FRf2_B&6%9tJmp&dX0vru2-+Q&I#R { return e.errors.map(f => f.message).join("\n") }).join("\n"); console.error("Dropzone file rejected:", errorMessageLine); - alert(errorMessageLine); + + const alertMessage = this.props.t("File type is not supported"); + alert(alertMessage); } render() { diff --git a/src/components/MapMaplibreGl.tsx b/src/components/MapMaplibreGl.tsx index f466cfc0e..3acb3d058 100644 --- a/src/components/MapMaplibreGl.tsx +++ b/src/components/MapMaplibreGl.tsx @@ -78,6 +78,13 @@ type MapMaplibreGlState = { pmtilesProtocol: Protocol | null; }; +interface Metadata { + name?: string; + type?: string; + tilestats?: unknown; + vector_layers: LayerSpecification[]; +} + class MapMaplibreGlInternal extends React.Component { static defaultProps = { onMapLoaded: () => {}, @@ -143,13 +150,13 @@ class MapMaplibreGlInternal extends React.Component { + (file.getMetadata() as Promise).then(metadata => { const layerNames = metadata.vector_layers.map((e: LayerSpecification) => e.id); // used by maplibre-gl-inspect to pick up inspectable layers map.style.sourceCaches["source"]._source.vectorLayerIds = layerNames; }).catch( e => { - console.error(`Error in reading local PMTiles file: ${e}`); + console.error(`${this.props.t('Error in reading local PMTiles file')}: ${e}`); }); } } diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index a65af8bd0..1ca24baa8 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -18,6 +18,7 @@ "Convert property to data function": "Eigenschaft in eine Datenfunktion umwandeln", "Layer <1>{formatLayerId(layerId)}: {parsed.data.message}": "Ebene <1>{formatLayerId(layerId)}: {parsed.data.message}", "switch to layer": "zur Ebene wechseln", + "File type is not supported": "__STRING_NOT_TRANSLATED__", "Map": "Karte", "Inspect": "Untersuchen", "Deuteranopia filter": "Deuteranopie-Filter", @@ -73,6 +74,7 @@ "Collapse": "Einklappen", "Expand": "Ausklappen", "Add Layer": "Ebene hinzufügen", + "Error in reading local PMTiles file": "__STRING_NOT_TRANSLATED__", "Search": "Suche", "Zoom:": "Zoom:", "Close popup": "Popup schließen", diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index 90e709401..8d0d4db8c 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -18,6 +18,7 @@ "Convert property to data function": "Convertir la propriété en fonction de données", "Layer <1>{formatLayerId(layerId)}: {parsed.data.message}": "Calque <1>{formatLayerId(layerId)} : {parsed.data.message}", "switch to layer": "changer de calque", + "File type is not supported": "__STRING_NOT_TRANSLATED__", "Map": "Carte", "Inspect": "Inspecter", "Deuteranopia filter": "Filtre Deutéranopie", @@ -73,6 +74,7 @@ "Collapse": "Réduire", "Expand": "Développer", "Add Layer": "Ajouter un calque", + "Error in reading local PMTiles file": "__STRING_NOT_TRANSLATED__", "Search": "Recherche", "Zoom:": "Zoom :", "Close popup": "Fermer la fenêtre", diff --git a/src/locales/he/translation.json b/src/locales/he/translation.json index 03fa8010e..b97e022bf 100644 --- a/src/locales/he/translation.json +++ b/src/locales/he/translation.json @@ -18,6 +18,7 @@ "Convert property to data function": "המרה לפונקציית מידע", "Layer <1>{formatLayerId(layerId)}: {parsed.data.message}": "שכבה <1>{formatLayerId(layerId)}: {parsed.data.message}", "switch to layer": "שינוי לשכבה", + "File type is not supported": "__STRING_NOT_TRANSLATED__", "Map": "מפה", "Inspect": "בדיקה", "Deuteranopia filter": "Deuteranopia filter", @@ -73,6 +74,7 @@ "Collapse": "הקטנה", "Expand": "הגדלה", "Add Layer": "הוספת שכבה", + "Error in reading local PMTiles file": "__STRING_NOT_TRANSLATED__", "Search": "חיפוש", "Zoom:": "זום:", "Close popup": "סגירת החלון", diff --git a/src/locales/ja/translation.json b/src/locales/ja/translation.json index 67e46448b..c4f3f01cb 100644 --- a/src/locales/ja/translation.json +++ b/src/locales/ja/translation.json @@ -18,6 +18,7 @@ "Convert property to data function": "プロパティをデータ関数に変換する", "Layer <1>{formatLayerId(layerId)}: {parsed.data.message}": "レイヤ<1>{formatLayerId(layerId)}: {parsed.data.message}", "switch to layer": "レイヤへ切替", + "File type is not supported": "__STRING_NOT_TRANSLATED__", "Map": "地図", "Inspect": "検査", "Deuteranopia filter": "緑色盲フィルタ", @@ -73,6 +74,7 @@ "Collapse": "畳む", "Expand": "展開", "Add Layer": "レイヤー追加", + "Error in reading local PMTiles file": "__STRING_NOT_TRANSLATED__", "Search": "検索", "Zoom:": "ズーム:", "Close popup": "ポップアップを閉じる", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 0e28345ae..959ae16e9 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -18,6 +18,7 @@ "Convert property to data function": "将属性转换为数据函数", "Layer <1>{formatLayerId(layerId)}: {parsed.data.message}": "图层<1>{formatLayerId(layerId)}: {parsed.data.message}", "switch to layer": "切换到图层", + "File type is not supported": "__STRING_NOT_TRANSLATED__", "Map": "地图", "Inspect": "检查", "Deuteranopia filter": "绿色盲滤镜", @@ -73,6 +74,7 @@ "Collapse": "折叠", "Expand": "展开", "Add Layer": "添加图层", + "Error in reading local PMTiles file": "__STRING_NOT_TRANSLATED__", "Search": "搜索", "Zoom:": "缩放:", "Close popup": "关闭弹出窗口",