From ff304c620c947129b4d3313492db2c71c69ee87f Mon Sep 17 00:00:00 2001 From: Reimar Date: Wed, 15 Oct 2025 09:34:02 +0200 Subject: [PATCH] implement file dropping --- src/index.js | 26 +++++++++++++++++++++++++- src/project_save_handler.js | 31 +++++++++++++++++++------------ style.css | 4 ++++ 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/index.js b/src/index.js index 071161c..decd921 100644 --- a/src/index.js +++ b/src/index.js @@ -62,6 +62,7 @@ const projectName = document.querySelector("#project-name"); const saveButton = document.querySelector("#save-button"); const loadButton = document.querySelector("#load-button"); const exportButton = document.querySelector("#export-button"); +const dropZone = document.querySelector("main"); const projectSaveHandler = new ProjectSaveHandler(editor, assetEditor, assetProvider, projectName); @@ -105,7 +106,7 @@ exportButton.onclick = async () => { }; loadButton.onclick = () => { - projectSaveHandler.loadFromFile(); + projectSaveHandler.showLoadFilePrompt(); }; saveButton.onclick = () => { @@ -120,3 +121,26 @@ addEventListener("keydown", (ev) => { }); toggleAssetEditorButton.addEventListener("click", () => assetEditor.toggleEditor()); + +window.ondragover = (ev) => ev.preventDefault(); +window.ondrop = (ev) => ev.preventDefault(); + +dropZone.onclick = (ev) => ev.preventDefault(); +dropZone.ondragover = (ev) => ev.preventDefault(); +dropZone.ondrop = async (ev) => { + for (const file of ev.dataTransfer.files) { + if (file.name.endsWith(".karlkode")) { + await projectSaveHandler.loadFromFile(file); + continue; + } + + if (file.type.startsWith("image/")) { + await assetEditor.addAsset({ name: file.name, file }); + continue; + } + + alert("Invalid file: " + file.name); + } + + ev.preventDefault(); +}; diff --git a/src/project_save_handler.js b/src/project_save_handler.js index 812ff07..9457dd8 100644 --- a/src/project_save_handler.js +++ b/src/project_save_handler.js @@ -1,6 +1,7 @@ import { promptUpload } from "./prompt_upload.js"; import { downloadFile, slugify } from "./utils.js"; import { HtmlExporter } from "./html_exporter.js"; +import { KarlkoderCodec } from "./karlkoder_codec.js"; export class ProjectSaveHandler { constructor(editor, assetEditor, assetProvider, projectName) { @@ -22,16 +23,7 @@ export class ProjectSaveHandler { }); } - async loadFromFile() { - if ( - !this.isSaved && - !confirm( - "Your project has not been saved. Loading a new project will override your current one. Continue?", - ) - ) { - return; - } - + async showLoadFilePrompt() { const files = await promptUpload(".karlkode", false); if (files.length === 0) { return; @@ -43,8 +35,21 @@ export class ProjectSaveHandler { ); } + this.loadFromFile(files[0]); + } + + async loadFromFile(file) { + if ( + !this.isSaved && + !confirm( + "Your project has not been saved. Loading a new project will override your current one. Continue?", + ) + ) { + return; + } + const items = KarlkoderCodec.de( - await fetch(URL.createObjectURL(files[0])).then((x) => x.bytes()), + await fetch(URL.createObjectURL(file)).then((x) => x.bytes()), ); const assets = items @@ -57,7 +62,7 @@ export class ProjectSaveHandler { const code = items.find((x) => x.tag === "code"); delete code.tag; - assetEditor.importAssets( + this.assetEditor.importAssets( assets.map(({ name, mime, content }) => { const file = new File([content], name, { type: mime }); @@ -68,6 +73,8 @@ export class ProjectSaveHandler { this.projectName.value = code.name; const dec = new TextDecoder(); this.editor.setValue(dec.decode(code.content)); + + this.isSaved = true; } async saveFile() { diff --git a/style.css b/style.css index 7fb4849..4830bab 100644 --- a/style.css +++ b/style.css @@ -24,6 +24,10 @@ main { gap: 2rem; } +#file-drop-input { + display: none; +} + .section-header { background-color: #bdbdbd; border-top-left-radius: 0.5rem;