diff --git a/CHANGELOG.md b/CHANGELOG.md index 2421aab6..9e2a1934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Add scheme type options for vector/raster tile - Add `tileSize` field for raster and raster-dem tile sources - Update Protomaps Light gallery style to v4 -- Add support to edit local files on the file system +- Add support to edit local files on the file system if supported by the browser - _...Add new stuff here..._ ### 🐞 Bug fixes diff --git a/src/components/ModalExport.tsx b/src/components/ModalExport.tsx index 684774c4..2750afba 100644 --- a/src/components/ModalExport.tsx +++ b/src/components/ModalExport.tsx @@ -5,7 +5,7 @@ import {version} from 'maplibre-gl/package.json' import {format} from '@maplibre/maplibre-gl-style-spec' import type {StyleSpecification} from 'maplibre-gl' import {MdMap, MdSave} from 'react-icons/md' -import { WithTranslation, withTranslation } from 'react-i18next'; +import {WithTranslation, withTranslation} from 'react-i18next'; import FieldString from './FieldString' import InputButton from './InputButton' @@ -15,6 +15,7 @@ import fieldSpecAdditional from '../libs/field-spec-additional' const MAPLIBRE_GL_VERSION = version; +const showSaveFilePickerAvailable = typeof window.showSaveFilePicker === "function"; type ModalExportInternalProps = { @@ -29,7 +30,7 @@ type ModalExportInternalProps = { class ModalExportInternal extends React.Component { - tokenizedStyle () { + tokenizedStyle() { return format( style.stripAccessTokens( style.replaceAccessTokens(this.props.mapStyle) @@ -37,8 +38,8 @@ class ModalExportInternal extends React.Component { ); } - exportName () { - if(this.props.mapStyle.name) { + exportName() { + if (this.props.mapStyle.name) { return Slugify(this.props.mapStyle.name, { replacement: '_', remove: /[*\-+~.()'"!:]/g, @@ -86,6 +87,15 @@ class ModalExportInternal extends React.Component { async saveStyle() { const tokenStyle = this.tokenizedStyle(); + // it is not guaranteed that the File System Access API is available on all + // browsers. If the function is not available, a fallback behavior is used. + if (!showSaveFilePickerAvailable) { + const blob = new Blob([tokenStyle], {type: "application/json;charset=utf-8"}); + const exportName = this.exportName(); + saveAs(blob, exportName + ".json"); + return; + } + let fileHandle = this.props.fileHandle; if (fileHandle == null) { fileHandle = await this.createFileHandle(); @@ -112,12 +122,12 @@ class ModalExportInternal extends React.Component { this.props.onOpenToggle(); } - async createFileHandle() : Promise { + async createFileHandle(): Promise { const pickerOpts: SaveFilePickerOptions = { types: [ { description: "json", - accept: { "application/json": [".json"] }, + accept: {"application/json": [".json"]}, }, ], suggestedName: this.exportName(), @@ -179,23 +189,19 @@ class ModalExportInternal extends React.Component {
- - + + {t("Save")} - - - {t("Save as")} - + {showSaveFilePickerAvailable && ( + + + {t("Save as")} + + )} - - + + {t("Create HTML")}
diff --git a/src/components/ModalOpen.tsx b/src/components/ModalOpen.tsx index 2761d14e..11c84e21 100644 --- a/src/components/ModalOpen.tsx +++ b/src/components/ModalOpen.tsx @@ -1,6 +1,7 @@ import React, { FormEvent } from 'react' import {MdFileUpload} from 'react-icons/md' import {MdAddCircleOutline} from 'react-icons/md' +import FileReaderInput, { Result } from 'react-file-reader-input' import { Trans, WithTranslation, withTranslation } from 'react-i18next'; import ModalLoading from './ModalLoading' @@ -168,6 +169,32 @@ class ModalOpenInternal extends React.Component { + const [, file] = files[0]; + const reader = new FileReader(); + this.clearError(); + + reader.readAsText(file, "UTF-8"); + reader.onload = e => { + let mapStyle; + try { + mapStyle = JSON.parse(e.target?.result as string) + } + catch(err) { + this.setState({ + error: (err as Error).toString() + }); + return; + } + mapStyle = style.ensureStyleValidity(mapStyle) + this.props.onStyleOpen(mapStyle); + this.onOpenToggle(); + } + reader.onerror = e => console.log(e.target); + } + onOpenToggle() { this.setState({ styleUrl: "" @@ -217,10 +244,16 @@ class ModalOpenInternal extends React.Component{t("Open local Style")}

{t("Open a local JSON style from your computer.")}

- {t("Open Style")} - + {typeof window.showOpenFilePicker === "function" ? ( + {t("Open Style")} + + ) : ( + + {t("Open Style")} + + )}