-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New sandcastle title and dirty state tracking #12794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: sandcastle-v2
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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; | ||||||||||||||
|
@@ -164,6 +168,7 @@ function AppBarButton({ | |||||||||||||
|
||||||||||||||
export type SandcastleAction = | ||||||||||||||
| { type: "reset" } | ||||||||||||||
| { type: "resetDirty" } | ||||||||||||||
| { type: "setCode"; code: string } | ||||||||||||||
| { type: "setHtml"; html: string } | ||||||||||||||
| { type: "runSandcastle" } | ||||||||||||||
|
@@ -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", | ||||||||||||||
|
@@ -196,6 +198,7 @@ function App() { | |||||||||||||
committedCode: string; | ||||||||||||||
committedHtml: string; | ||||||||||||||
runNumber: number; | ||||||||||||||
dirty: boolean; | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
const initialState: CodeState = { | ||||||||||||||
|
@@ -204,6 +207,7 @@ function App() { | |||||||||||||
committedCode: defaultJsCode, | ||||||||||||||
committedHtml: defaultHtmlCode, | ||||||||||||||
runNumber: 0, | ||||||||||||||
dirty: false, | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
const [codeState, dispatch] = useReducer(function reducer( | ||||||||||||||
|
@@ -218,12 +222,14 @@ function App() { | |||||||||||||
return { | ||||||||||||||
...state, | ||||||||||||||
code: action.code, | ||||||||||||||
dirty: true, | ||||||||||||||
}; | ||||||||||||||
} | ||||||||||||||
case "setHtml": { | ||||||||||||||
return { | ||||||||||||||
...state, | ||||||||||||||
html: action.html, | ||||||||||||||
dirty: true, | ||||||||||||||
}; | ||||||||||||||
} | ||||||||||||||
case "runSandcastle": { | ||||||||||||||
|
@@ -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 baseTitle = "Cesium Sandcastle"; | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
or possibly even just the following due to all the other info?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want this to match whatever title we pick for the base page so the specific sandcastle title is just prepended. I'm open to dropping down to just "Sandcastle" but I wasn't sure about dropping the branding |
||||||||||||||
const dirtyIndicator = codeState.dirty ? "*" : ""; | ||||||||||||||
if (title !== "") { | ||||||||||||||
document.title = `${envString}${title}${dirtyIndicator} | ${baseTitle}`; | ||||||||||||||
} else { | ||||||||||||||
document.title = `${envString}${baseTitle}`; | ||||||||||||||
} | ||||||||||||||
Comment on lines
+272
to
+276
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check makes sure that we only add the title and the |
||||||||||||||
}, [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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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/. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 ![]() |
||||||||||||||
}, [codeState.dirty]); | ||||||||||||||
|
||||||||||||||
const [legacyIdMap, setLegacyIdMap] = useState<Record<string, string>>({}); | ||||||||||||||
const [galleryItems, setGalleryItems] = useState<GalleryItem[]>([]); | ||||||||||||||
const [galleryLoaded, setGalleryLoaded] = useState(false); | ||||||||||||||
|
@@ -277,6 +318,9 @@ function App() { | |||||||||||||
} | ||||||||||||||
|
||||||||||||||
function resetSandcastle() { | ||||||||||||||
if (!confirmLeave()) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
dispatch({ type: "reset" }); | ||||||||||||||
|
||||||||||||||
window.history.pushState({}, "", getBaseUrl()); | ||||||||||||||
|
@@ -292,6 +336,7 @@ function App() { | |||||||||||||
|
||||||||||||||
const shareUrl = `${getBaseUrl()}#c=${base64String}`; | ||||||||||||||
window.history.replaceState({}, "", shareUrl); | ||||||||||||||
dispatch({ type: "resetDirty" }); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
function openStandalone() { | ||||||||||||||
|
@@ -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 | ||||||||||||||
|
@@ -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"> | ||||||||||||||
|
@@ -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); | ||||||||||||||
|
||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, technically speaking, this should be the branding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly? I was just matching what the current Sandcastle uses