diff --git a/frontend/src/Editor.svelte b/frontend/src/Editor.svelte index 46f0b79..a02e762 100644 --- a/frontend/src/Editor.svelte +++ b/frontend/src/Editor.svelte @@ -2,9 +2,9 @@ import { getContext } from "svelte"; import Graph from "./Graph.svelte"; - import { nonNull } from "./helpers"; + import { nonNull, saveToLocalDisk } from "./helpers"; import type { TaskDescriptor, TasksFile } from "./tasks"; - import { saveTasks, getCategories } from "./tasks"; + import { saveTasks, getCategories, tasksToString } from "./tasks"; import TaskDisplay from "./TaskDisplay.svelte"; import TaskDetailEditor from "./TaskDetailEditor.svelte"; import { forceSimulation } from "./force-simulation"; @@ -62,6 +62,10 @@ await saveTasks(tasks); } + function saveLocally() { + saveToLocalDisk("tasks.json", tasksToString(tasks)); + } + function openTaskDetailEditorButton(e: CustomEvent) { openTaskDetailEditor(e.detail); } @@ -260,6 +264,7 @@

Toolbox

+
diff --git a/frontend/src/helpers.ts b/frontend/src/helpers.ts index 3918326..614ea73 100644 --- a/frontend/src/helpers.ts +++ b/frontend/src/helpers.ts @@ -5,3 +5,16 @@ export function copyFieldsThatExist(dest: any, source: any) { if (attr in source) dest[attr] = source[attr] } } + +export function saveToLocalDisk(filename: string, text: string) { + var element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +} diff --git a/frontend/src/tasks.ts b/frontend/src/tasks.ts index f4b24a5..4c66c3e 100644 --- a/frontend/src/tasks.ts +++ b/frontend/src/tasks.ts @@ -57,13 +57,16 @@ function normalizeTasks(tasks: TasksFile) { tasks.tasks.sort((t1, t2) => t1.id.localeCompare(t2.id)) } -export async function saveTasks(tasks: TasksFile) { +export function tasksToString(tasks: TasksFile): string { normalizeTasks(tasks); + return JSON.stringify(tasks, null, 4) +} +export async function saveTasks(tasks: TasksFile) { // request options const options = { method: 'POST', - body: JSON.stringify(tasks, null, 4), + body: tasksToString(tasks), headers: { 'Content-Type': 'application/json' }