diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ae95733..325aadb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ ### 🐞 Bug fixes +- Improved the local style open modal and local file upload. - Fixed the Expression editor (for long expressions) being able to be float under other components further down - Fixed an issue when clicking on a popup and then clicking on the map again - Fix modal close button position diff --git a/cypress/e2e/maputnik-cypress-helper.ts b/cypress/e2e/maputnik-cypress-helper.ts index 40ff36cc..90467828 100644 --- a/cypress/e2e/maputnik-cypress-helper.ts +++ b/cypress/e2e/maputnik-cypress-helper.ts @@ -43,6 +43,12 @@ export default class MaputnikCypressHelper { } }); }, + dropFileByFixture: (fixture: string, dropzoneTestId: string) => { + this.helper.get.elementByTestId(dropzoneTestId).selectFile("cypress/fixtures/" + fixture, { + action: "drag-drop", + force: true, + }); + }, ...this.helper.when, }; diff --git a/cypress/e2e/maputnik-driver.ts b/cypress/e2e/maputnik-driver.ts index 853de436..692b038d 100644 --- a/cypress/e2e/maputnik-driver.ts +++ b/cypress/e2e/maputnik-driver.ts @@ -143,7 +143,12 @@ export class MaputnikDriver { }, chooseExampleFile: () => { this.helper.given.fixture("example-style.json", "example-style.json"); - this.helper.when.openFileByFixture("example-style.json", "modal:open.file.button", "modal:open.file.input"); + this.helper.when.openFileByFixture("example-style.json", "modal:open.dropzone", "modal:open.file.input"); + this.helper.when.wait(200); + }, + dropExampleFile: () => { + this.helper.given.fixture("example-style.json", "example-style.json"); + this.helper.when.dropFileByFixture("example-style.json", "modal:open.dropzone"); this.helper.when.wait(200); }, setStyle: ( diff --git a/cypress/e2e/modals.cy.ts b/cypress/e2e/modals.cy.ts index db8b7a6b..89b01b8d 100644 --- a/cypress/e2e/modals.cy.ts +++ b/cypress/e2e/modals.cy.ts @@ -23,6 +23,11 @@ describe("modals", () => { then(get.fixture("example-style.json")).shouldEqualToStoredStyle(); }); + it("upload via drag and drop", () => { + when.dropExampleFile(); + then(get.fixture("example-style.json")).shouldEqualToStoredStyle(); + }); + describe("when click open url", () => { beforeEach(() => { const styleFileUrl = get.exampleFileUrl(); diff --git a/src/components/modals/ModalOpen.tsx b/src/components/modals/ModalOpen.tsx index 085f9a3f..06a07dd2 100644 --- a/src/components/modals/ModalOpen.tsx +++ b/src/components/modals/ModalOpen.tsx @@ -1,4 +1,4 @@ -import React, { type FormEvent } from "react"; +import React, { type DragEvent, type FormEvent } from "react"; import { MdFileUpload } from "react-icons/md"; import { MdAddCircleOutline } from "react-icons/md"; import { Trans, type WithTranslation, withTranslation } from "react-i18next"; @@ -51,16 +51,20 @@ type ModalOpenInternalProps = { type ModalOpenState = { styleUrl: string + isDragOver: boolean error?: string | null activeRequest?: any activeRequestUrl?: string | null }; class ModalOpenInternal extends React.Component { + private fileInputRef = React.createRef(); + constructor(props: ModalOpenInternalProps) { super(props); this.state = { - styleUrl: "" + styleUrl: "", + isDragOver: false, }; } @@ -198,12 +202,45 @@ class ModalOpenInternal extends React.Component { + if (typeof window.showOpenFilePicker === "function") { + await this.onOpenFile(); + return; + } + + this.fileInputRef.current?.click(); + }; + + onFileDragOver = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + + if (!this.state.isDragOver) { + this.setState({ isDragOver: true }); + } + }; + + onFileDragLeave = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + this.setState({ isDragOver: false }); + }; + + onFileDrop = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + + this.setState({ isDragOver: false }); + this.onFileChanged(e.dataTransfer.files); + }; + onChangeUrl = (url: string) => { this.setState({ styleUrl: url, @@ -244,19 +281,35 @@ class ModalOpenInternal extends React.Component

{t("Open local Style")}

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

-
- {typeof window.showOpenFilePicker === "function" ? ( - {t("Open Style")} - - ) : ( - - )} +
void this.onBrowseClick()} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + void this.onBrowseClick(); + } + }} + > +
+
+ this.onFileChanged(e.target.files)} + />
diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index 5545b702..9ffa501e 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -199,5 +199,6 @@ "Video URL": "Video-URL", "View": "Ansicht", "You've entered an old style filter.": "Du hast einen alten Filter-Stil eingegeben.", - "Zoom": "Zoom" + "Zoom": "Zoom", + "Drag and drop a style JSON file here or click to browse": "Ziehen Sie eine Style-JSON-Datei hierher oder klicken Sie, um zu durchsuchen" } diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json new file mode 100644 index 00000000..27b3d1a4 --- /dev/null +++ b/src/locales/en/translation.json @@ -0,0 +1,3 @@ +{ + "Drag and drop a style JSON file here or click to browse": "Drag and drop a style JSON file here or click to browse" +} diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index 2dab1b4a..f48cda4f 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -199,5 +199,6 @@ "Video URL": "URL de la vidéo", "View": "Vue", "You've entered an old style filter.": "Vous avez entré un ancien style de filtre.", - "Zoom": "Zoom" + "Zoom": "Zoom", + "Drag and drop a style JSON file here or click to browse": "Faites glisser un fichier JSON de style ici ou cliquez pour parcourir" } diff --git a/src/locales/he/translation.json b/src/locales/he/translation.json index af7fa8a0..ce2f7c26 100644 --- a/src/locales/he/translation.json +++ b/src/locales/he/translation.json @@ -199,5 +199,6 @@ "Video URL": "כתובת וידאו", "View": "תצוגה", "You've entered an old style filter.": "הכנסתם סינון מסוג ישן,", - "Zoom": "זום" + "Zoom": "זום", + "Drag and drop a style JSON file here or click to browse": "גרור ושחרר כאן קובץ JSON של סגנון או לחץ כדי לעיין" } diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index 073cf055..f2580af0 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -199,5 +199,6 @@ "Video URL": "Indirizzo video", "View": "Vista", "You've entered an old style filter.": "Hai inserito uno stile filtro obsoleto.", - "Zoom": "Zoom" + "Zoom": "Zoom", + "Drag and drop a style JSON file here or click to browse": "Trascina e rilascia qui un file JSON dello stile o fai clic per sfogliare" } diff --git a/src/locales/ja/translation.json b/src/locales/ja/translation.json index edeebeaf..8390a346 100644 --- a/src/locales/ja/translation.json +++ b/src/locales/ja/translation.json @@ -199,5 +199,6 @@ "Video URL": "動画URL", "View": "表示", "You've entered an old style filter.": "旧型フィルタを使用しております。", - "Zoom": "ズーム" + "Zoom": "ズーム", + "Drag and drop a style JSON file here or click to browse": "ここにスタイルのJSONファイルをドラッグ&ドロップするか、クリックして参照してください" } diff --git a/src/locales/ko/translation.json b/src/locales/ko/translation.json index fb414828..62f28388 100644 --- a/src/locales/ko/translation.json +++ b/src/locales/ko/translation.json @@ -199,5 +199,6 @@ "Video URL": "비디오 URL", "View": "보기", "You've entered an old style filter.": "이전 스타일 필터를 입력했습니다.", - "Zoom": "줌" + "Zoom": "줌", + "Drag and drop a style JSON file here or click to browse": "여기에 스타일 JSON 파일을 끌어다 놓거나 클릭하여 찾아보세요" } diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 80498258..0c531bf5 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -199,5 +199,6 @@ "Video URL": "视频URL", "View": "视图", "You've entered an old style filter.": "您输入了一个旧风格的过滤器。", - "Zoom": "缩放" + "Zoom": "缩放", + "Drag and drop a style JSON file here or click to browse": "将样式 JSON 文件拖放到此处或点击以浏览" } diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index 5ca98256..37727a84 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -88,6 +88,47 @@ @extend .maputnik-big-button !optional; /* stylelint-disable-line */ } +.maputnik-upload-dropzone { + border: 2px dashed vars.$color-midgray; + border-radius: 6px; + padding: vars.$margin-4 vars.$margin-3; + text-align: center; + cursor: pointer; + transition: border-color 120ms ease, background-color 120ms ease; + + &:hover, + &:focus-visible { + border-color: vars.$color-lowgray; + background-color: rgba(255, 255, 255, 0.03); + } + + &:focus-visible { + outline: 2px solid vars.$color-lowgray; + outline-offset: 2px; + } +} + +.maputnik-upload-dropzone--active { + border-color: vars.$color-lowgray; + background-color: rgba(255, 255, 255, 0.04); +} + +.maputnik-upload-dropzone-content { + display: flex; + flex-direction: column; + align-items: center; +} + +.maputnik-upload-dropzone-icon { + font-size: 28px; + color: vars.$color-midgray; +} + +.maputnik-upload-dropzone-text { + margin: vars.$margin-2 0 0; + color: vars.$color-lowgray; +} + .maputnik-style-gallery-container { flex-shrink: 1; }