Skip to content

Commit

Permalink
Tour export
Browse files Browse the repository at this point in the history
  • Loading branch information
invpt committed Mar 11, 2024
1 parent 0dd405e commit 8420c6c
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 3 deletions.
87 changes: 87 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@turf/circle": "^6.5.0",
"hash-wasm": "^4.11.0",
"idb": "^8.0.0",
"jszip": "^3.10.1",
"maplibre-gl": "^4.1.0",
"solid-icons": "^1.1.0",
"solid-js": "^1.8.15",
Expand Down
55 changes: 55 additions & 0 deletions src/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import JSZip from "jszip";

import { ProjectModel } from "./data";
import { DB, DbProject } from "./db";

class ExportError extends Error {
constructor(message: string) {
super("export error: " + message);
}
}

export const exportProject = async (db: DB, project: string, options: { includeAssets: boolean } = { includeAssets: true }) => {
const projectContent: ProjectModel & { id: unknown, source: unknown } | undefined = await db.loadProject(project);
if (projectContent === undefined) {
throw new ExportError("failed to load project for export");
}

// Manually get rid of the database-specific properties.
delete projectContent.id;
delete projectContent.source;

const assetBlobs: { [assetHash: string]: Blob } = {};
if (options.includeAssets) {
for (const assetInfo of Object.values(projectContent.assets)) {
const blob = await db.loadAsset(assetInfo.hash);
if (blob === undefined) {
console.warn("export warning: ignoring missing blob for asset with hash '" + assetInfo.hash + "'");
} else {
assetBlobs[assetInfo.hash] = blob;
}
}
}

const projectContentString = JSON.stringify(projectContent);

// build the zip file
const zip = new JSZip();
// TODO: add an index.html file linking to the tour builder
zip.file("tourforge.json", projectContentString);
for (const [assetHash, assetBlob] of Object.entries(assetBlobs)) {
// TODO: maybe give the asset an extension here based on the blob's media type
zip.file(assetHash, assetBlob);
}
const zipBlob = await zip.generateAsync({ type: "blob" });

// download the zip file
const zipUrl = URL.createObjectURL(zipBlob);
const a = document.createElement("a");
a.href = zipUrl;
a.download = projectContent.title + ".tourforge.zip";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(zipUrl);
};
15 changes: 14 additions & 1 deletion src/pages/project/ProjectEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { useParams } from "@solidjs/router";
import { type Component, JSX } from "solid-js";
import { type Component, JSX, onCleanup } from "solid-js";

import { ProjectEditorPanel } from "./ProjectEditorPanel";

import styles from "./ProjectEditor.module.css";
import { ProjectProvider } from "../../hooks/Project";
import { useDB } from "../../db";
import { exportProject } from "../../export";

export const ProjectEditor: Component<{children?: JSX.Element}> = (props) => {
const params = useParams();
const db = useDB();

const listener = async (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
await exportProject(db, params.pid);
}
};

document.addEventListener('keydown', listener);
onCleanup(() => document.removeEventListener('keydown', listener));

return (
<div class={styles.ProjectEditor}>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/project/ProjectEditorPanel.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@

.BottomButtons {
display: grid;
grid-template-columns: 1fr;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
13 changes: 12 additions & 1 deletion src/pages/project/ProjectEditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { v4 as uuidv4 } from "uuid";

import styles from "./ProjectEditorPanel.module.css";
import { useProject } from "../../hooks/Project";
import { exportProject } from "../../export";
import { useDB } from "../../db";

export const ProjectEditorPanel: Component = () => {
const params = useParams();
const db = useDB();
const [project, setProject] = useProject();

const handleCreateTourClick = async () => {
Expand All @@ -27,6 +30,13 @@ export const ProjectEditorPanel: Component = () => {
]
}));
};
const handleSaveClick = async () => {
if (!project()) {
return;
}

await exportProject(db, project()!.id);
};

return (
<Show when={project()}>
Expand All @@ -51,7 +61,8 @@ export const ProjectEditorPanel: Component = () => {
<div style="flex:1"></div>

<div class={styles.BottomButtons}>
<A class="secondary" href={`/projects/${params.pid}/manage`}>Manage Project</A>
<button class="primary" onClick={handleSaveClick}>Save (Ctrl+S)</button>
<A class="secondary" href={`/projects/${params.pid}/manage`}>Settings</A>
</div>
</div>
</Show>
Expand Down

0 comments on commit 8420c6c

Please sign in to comment.