Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
[IR-3547] studio: show unsaved changes dialog when switching between …
Browse files Browse the repository at this point in the history
…scenes (#10921)

* show unsaved changes dialog when switching between scenes

* replace beforeunload event

* rename function to `confirmSceneSaveIfModified`

* use web standard alert for saving changes

---------

Co-authored-by: aditya-mitra <[email protected]>
  • Loading branch information
dtlehrer and aditya-mitra authored Aug 13, 2024
1 parent 47ca0b8 commit 3e648cd
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 54 deletions.
5 changes: 4 additions & 1 deletion packages/client-core/i18n/en/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,10 @@
"lbl-thumbnail": "Generate thumbnail & envmap",
"lbl-confirm": "Save Scene",
"info-confirm": "Are you sure you want to save the scene?",
"info-question": "Do you want to save the current scene?"
"info-question": "Do you want to save the current scene?",
"unsavedChanges": {
"title": "Unsaved Changes"
}
},
"saveNewScene": {
"title": "Save As",
Expand Down
10 changes: 10 additions & 0 deletions packages/editor/src/components/EditorContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ const EditorContainer = () => {
}
}, [errorState])

useEffect(() => {
const handleBeforeUnload = async (event: BeforeUnloadEvent) => {
if (EditorState.isModified()) {
event.preventDefault()
}
}
window.addEventListener('beforeunload', handleBeforeUnload)
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
}, [])

return (
<main className="pointer-events-auto">
<div
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/components/dialogs/SaveSceneDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const SaveSceneDialog = (props: { isExiting?: boolean; onConfirm?: () =>

return (
<ConfirmDialog
title={props.isExiting ? t('editor:dialog.saveScene.unsavedChanges.title') : t('editor:dialog.saveScene.title')}
onSubmit={handleSubmit}
onClose={() => {
PopoverState.hidePopupover()
Expand Down
39 changes: 9 additions & 30 deletions packages/editor/src/components/toolbar/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,25 +60,21 @@ const onImportAsset = async () => {
}
}

const onClickNewScene = async () => {
export const confirmSceneSaveIfModified = async () => {
const isModified = EditorState.isModified()

if (isModified) {
const confirm = await new Promise((resolve) => {
return new Promise((resolve) => {
PopoverState.showPopupover(
<SaveSceneDialog
isExiting
onConfirm={() => {
resolve(true)
}}
onCancel={() => {
resolve(false)
}}
/>
<SaveSceneDialog isExiting onConfirm={() => resolve(true)} onCancel={() => resolve(false)} />
)
})
if (!confirm) return
}
return true
}

const onClickNewScene = async () => {
if (!(await confirmSceneSaveIfModified())) return

const newSceneUIAddons = getState(EditorState).uiAddons.newScene
if (Object.keys(newSceneUIAddons).length > 0) {
Expand All @@ -89,24 +85,7 @@ const onClickNewScene = async () => {
}

const onCloseProject = async () => {
const isModified = EditorState.isModified()

if (isModified) {
const confirm = await new Promise((resolve) => {
PopoverState.showPopupover(
<SaveSceneDialog
isExiting
onConfirm={() => {
resolve(true)
}}
onCancel={() => {
resolve(false)
}}
/>
)
})
if (!confirm) return
}
if (!(await confirmSceneSaveIfModified())) return

const editorState = getMutableState(EditorState)
getMutableState(GLTFModifiedState).set({})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { SceneItem } from '@etherealengine/client-core/src/admin/components/scen
import { PopoverState } from '@etherealengine/client-core/src/common/services/PopoverState'
import { StaticResourceType, fileBrowserPath, staticResourcePath } from '@etherealengine/common/src/schema.type.module'
import CreateSceneDialog from '@etherealengine/editor/src/components/dialogs/CreateScenePanelDialog'
import { confirmSceneSaveIfModified } from '@etherealengine/editor/src/components/toolbar/Toolbar'
import { onNewScene } from '@etherealengine/editor/src/functions/sceneFunctions'
import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
import { getMutableState, useHookstate, useMutableState } from '@etherealengine/hyperflux'
Expand All @@ -47,7 +48,9 @@ export default function ScenesPanel() {

const scenesLoading = scenesQuery.status === 'pending'

const onClickScene = (scene: StaticResourceType) => {
const onClickScene = async (scene: StaticResourceType) => {
if (!(await confirmSceneSaveIfModified())) return

getMutableState(EditorState).merge({
scenePath: scene.key
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,19 @@ import { uploadToFeathersService } from '@etherealengine/client-core/src/util/up
import { FeatureFlags } from '@etherealengine/common/src/constants/FeatureFlags'
import { clientSettingPath, fileBrowserUploadPath } from '@etherealengine/common/src/schema.type.module'
import { processFileName } from '@etherealengine/common/src/utils/processFileName'
import { getComponent, useComponent, useQuery } from '@etherealengine/ecs'
import { useComponent, useQuery } from '@etherealengine/ecs'
import { ItemTypes, SupportedFileTypes } from '@etherealengine/editor/src/constants/AssetTypes'
import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
import { addMediaNode } from '@etherealengine/editor/src/functions/addMediaNode'
import { getCursorSpawnPosition } from '@etherealengine/editor/src/functions/screenSpaceFunctions'
import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
import { GLTFComponent } from '@etherealengine/engine/src/gltf/GLTFComponent'
import { GLTFModifiedState } from '@etherealengine/engine/src/gltf/GLTFDocumentState'
import { ResourcePendingComponent } from '@etherealengine/engine/src/gltf/ResourcePendingComponent'
import { SourceComponent } from '@etherealengine/engine/src/scene/components/SourceComponent'
import useFeatureFlags from '@etherealengine/engine/src/useFeatureFlags'
import { getMutableState, useHookstate, useMutableState } from '@etherealengine/hyperflux'
import { useMutableState } from '@etherealengine/hyperflux'
import { TransformComponent } from '@etherealengine/spatial'
import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks'
import React, { useEffect } from 'react'
import React from 'react'
import { useDrop } from 'react-dnd'
import { useTranslation } from 'react-i18next'
import { twMerge } from 'tailwind-merge'
Expand Down Expand Up @@ -125,23 +123,6 @@ const SceneLoadingProgress = ({ rootEntity }) => {
const progress = useComponent(rootEntity, GLTFComponent).progress.value
const loaded = GLTFComponent.useSceneLoaded(rootEntity)
const resourcePendingQuery = useQuery([ResourcePendingComponent])
const root = getComponent(rootEntity, SourceComponent)
const sceneModified = useHookstate(getMutableState(GLTFModifiedState)[root]).value

useEffect(() => {
if (!sceneModified) return
const onBeforeUnload = (e: BeforeUnloadEvent) => {
alert('You have unsaved changes. Please save before leaving.')
e.preventDefault()
e.returnValue = ''
}

window.addEventListener('beforeunload', onBeforeUnload)

return () => {
window.removeEventListener('beforeunload', onBeforeUnload)
}
}, [sceneModified])

if (loaded) return null

Expand Down

0 comments on commit 3e648cd

Please sign in to comment.