Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/sandcastle/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sandcastle Reborn</title>
<title>Sandcastle | CesiumJS</title>
<style>
/* Load fonts for itwin-ui */
@font-face {
Expand Down
82 changes: 72 additions & 10 deletions packages/sandcastle/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const defaultHtmlCode = `<style>
`;

const GALLERY_BASE = __GALLERY_BASE_URL__;
const cesiumVersion = __CESIUM_VERSION__;
const versionString = __COMMIT_SHA__
? `Commit: ${__COMMIT_SHA__.substring(0, 7)} - ${cesiumVersion}`
: cesiumVersion;

type RightSideRef = {
toggleExpanded: () => void;
Expand Down Expand Up @@ -164,6 +168,7 @@ function AppBarButton({

export type SandcastleAction =
| { type: "reset" }
| { type: "resetDirty" }
| { type: "setCode"; code: string }
| { type: "setHtml"; html: string }
| { type: "runSandcastle" }
Expand All @@ -178,9 +183,6 @@ function App() {
const consoleCollapsedHeight = 26;
const [consoleExpanded, setConsoleExpanded] = useState(false);

const cesiumVersion = __CESIUM_VERSION__;
const versionString = __COMMIT_SHA__ ? `Commit: ${__COMMIT_SHA__}` : "";

const startOnEditor = !!(window.location.search || window.location.hash);
const [leftPanel, setLeftPanel] = useState<"editor" | "gallery">(
startOnEditor ? "editor" : "gallery",
Expand All @@ -196,6 +198,7 @@ function App() {
committedCode: string;
committedHtml: string;
runNumber: number;
dirty: boolean;
};

const initialState: CodeState = {
Expand All @@ -204,6 +207,7 @@ function App() {
committedCode: defaultJsCode,
committedHtml: defaultHtmlCode,
runNumber: 0,
dirty: false,
};

const [codeState, dispatch] = useReducer(function reducer(
Expand All @@ -218,12 +222,14 @@ function App() {
return {
...state,
code: action.code,
dirty: true,
};
}
case "setHtml": {
return {
...state,
html: action.html,
dirty: true,
};
}
case "runSandcastle": {
Expand All @@ -241,11 +247,46 @@ function App() {
committedCode: action.code ?? state.code,
committedHtml: action.html ?? state.html,
runNumber: state.runNumber + 1,
dirty: false,
};
}
case "resetDirty": {
return {
...state,
dirty: false,
};
}
}
}, initialState);

useEffect(() => {
const host = window.location.host;
let envString = "";
if (host.includes("localhost") && host !== "localhost:8080") {
// this helps differentiate tabs for local sandcastle development or other testing
envString = `${host.replace("localhost:", "")} `;
}

const dirtyIndicator = codeState.dirty ? "*" : "";
if (title === "" || title === "New Sandcastle") {
// No need to clutter the window/tab with a name if not viewing a named gallery demo
document.title = `${envString}Sandcastle${dirtyIndicator} | CesiumJS`;
} else {
document.title = `${envString}${title}${dirtyIndicator} | Sandcastle | CesiumJS`;
}
}, [title, codeState.dirty]);

const confirmLeave = useCallback(() => {
if (!codeState.dirty) {
return true;
}

/* eslint-disable-next-line no-alert */
return window.confirm(
"You have unsaved changes. Are you sure you want to navigate away from this demo?",
);
Comment on lines +285 to +287
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is totally up-to-par with what we had before, but what do you think of propagating the current sandcastle to local storage or similar?

The goal would to be to persist state, like what happens when you reload a GitHub tab with an in-progress comment. Or maybe a slightly different workflow, like what happens if you leave and come back to https://geojson.io/.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think of propagating the current sandcastle to local storage or similar?

This (or similar) has been requested often and I do really want to find some solution to try and prevent lost work. However I think it should be a separate effort, this PR just tries to match the functionality of the existing sandcastle. This is already on the running task list but further down for lower priority.happy to re-evaluate if you think it's a "must have" before release.

My current thought is not to make it truly persist every page load but offer a "saved state" that we can suggest the user reload like jsfiddle does

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem with adjusting this as a subsequent PR.

We should probably link or move this conversation to the issue, which I'll let you organize as you see fit.

But for the sake of keeping it going:

My current thought is not to make it truly persist every page load but offer a "saved state" that we can suggest the user reload like jsfiddle does

Cool, that sounds appropriate to me. That seems very similar to what https://geojson.io/ does, as I linked above and shown below. But let me know if I misunderstood.

image

}, [codeState.dirty]);

const [legacyIdMap, setLegacyIdMap] = useState<Record<string, string>>({});
const [galleryItems, setGalleryItems] = useState<GalleryItem[]>([]);
const [galleryLoaded, setGalleryLoaded] = useState(false);
Expand Down Expand Up @@ -277,6 +318,9 @@ function App() {
}

function resetSandcastle() {
if (!confirmLeave()) {
return;
}
dispatch({ type: "reset" });

window.history.pushState({}, "", getBaseUrl());
Expand All @@ -292,6 +336,7 @@ function App() {

const shareUrl = `${getBaseUrl()}#c=${base64String}`;
window.history.replaceState({}, "", shareUrl);
dispatch({ type: "resetDirty" });
}

function openStandalone() {
Expand Down Expand Up @@ -407,12 +452,27 @@ function App() {
useEffect(() => {
// Listen to browser forward/back navigation and try to load from URL
// this is necessary because of the pushState used for faster gallery loading
function pushStateListener() {
loadFromUrl();
function popStateListener() {
if (confirmLeave()) {
loadFromUrl();
}
}
window.addEventListener("popstate", pushStateListener);
return () => window.removeEventListener("popstate", pushStateListener);
}, [loadFromUrl]);
window.addEventListener("popstate", popStateListener);
return () => window.removeEventListener("popstate", popStateListener);
}, [loadFromUrl, confirmLeave]);

useEffect(() => {
// if the code has been edited listen for navigation away and warn
if (codeState.dirty) {
function beforeUnloadListener(e: BeforeUnloadEvent) {
e.preventDefault();
return ""; // modern browsers ignore the contents of this string
}
window.addEventListener("beforeunload", beforeUnloadListener);
return () =>
window.removeEventListener("beforeunload", beforeUnloadListener);
}
}, [codeState.dirty]);

return (
<Root
Expand Down Expand Up @@ -443,8 +503,7 @@ function App() {
</Button>
<div className="flex-spacer"></div>
<div className="version">
{versionString && <pre>{versionString.substring(0, 7)} - </pre>}
<pre>{cesiumVersion}</pre>
{versionString && <pre>{versionString}</pre>}
</div>
</header>
<div className="application-bar">
Expand Down Expand Up @@ -505,6 +564,9 @@ function App() {
hidden={leftPanel !== "gallery"}
galleryItems={galleryItems}
loadDemo={(item, switchToCode) => {
if (!confirmLeave()) {
return;
}
// Load the gallery item every time it's clicked
loadGalleryItem(item.id);

Expand Down
7 changes: 0 additions & 7 deletions packages/sandcastle/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@ import { createRoot } from "react-dom/client";
import "./reset.css"; // TODO: this may not be needed with itwin-ui
import App from "./App.tsx";

const host = window.location.host;
if (host.includes("localhost")) {
document.title = `${host.replace("localhost:", "")} ${document.title}`;
} else if (host.includes("ci")) {
document.title = `CI ${document.title}`;
}

createRoot(document.getElementById("app-container")!).render(
<StrictMode>
<App />
Expand Down
2 changes: 2 additions & 0 deletions packages/sandcastle/vite.config.ci.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineConfig, UserConfig } from "vite";
import { viteStaticCopy } from "vite-plugin-static-copy";
import { env } from "process";

import baseConfig, { cesiumPathReplace } from "./vite.config.ts";

Expand All @@ -22,6 +23,7 @@ export default defineConfig(() => {
...config.define,
__PAGE_BASE_URL__: JSON.stringify(process.env.BASE_URL),
__GALLERY_BASE_URL__: JSON.stringify(`${config.base}/gallery`),
__COMMIT_SHA__: JSON.stringify(env.GITHUB_SHA),
};

const copyPlugin = viteStaticCopy({
Expand Down