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

Commit 3e648cd

Browse files
[IR-3547] studio: show unsaved changes dialog when switching between 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 <55396651+aditya-mitra@users.noreply.github.com>
1 parent 47ca0b8 commit 3e648cd

File tree

6 files changed

+31
-54
lines changed

6 files changed

+31
-54
lines changed

packages/client-core/i18n/en/editor.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1360,7 +1360,10 @@
13601360
"lbl-thumbnail": "Generate thumbnail & envmap",
13611361
"lbl-confirm": "Save Scene",
13621362
"info-confirm": "Are you sure you want to save the scene?",
1363-
"info-question": "Do you want to save the current scene?"
1363+
"info-question": "Do you want to save the current scene?",
1364+
"unsavedChanges": {
1365+
"title": "Unsaved Changes"
1366+
}
13641367
},
13651368
"saveNewScene": {
13661369
"title": "Save As",

packages/editor/src/components/EditorContainer.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,16 @@ const EditorContainer = () => {
200200
}
201201
}, [errorState])
202202

203+
useEffect(() => {
204+
const handleBeforeUnload = async (event: BeforeUnloadEvent) => {
205+
if (EditorState.isModified()) {
206+
event.preventDefault()
207+
}
208+
}
209+
window.addEventListener('beforeunload', handleBeforeUnload)
210+
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
211+
}, [])
212+
203213
return (
204214
<main className="pointer-events-auto">
205215
<div

packages/editor/src/components/dialogs/SaveSceneDialog.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export const SaveSceneDialog = (props: { isExiting?: boolean; onConfirm?: () =>
8181

8282
return (
8383
<ConfirmDialog
84+
title={props.isExiting ? t('editor:dialog.saveScene.unsavedChanges.title') : t('editor:dialog.saveScene.title')}
8485
onSubmit={handleSubmit}
8586
onClose={() => {
8687
PopoverState.hidePopupover()

packages/editor/src/components/toolbar/Toolbar.tsx

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -60,25 +60,21 @@ const onImportAsset = async () => {
6060
}
6161
}
6262

63-
const onClickNewScene = async () => {
63+
export const confirmSceneSaveIfModified = async () => {
6464
const isModified = EditorState.isModified()
6565

6666
if (isModified) {
67-
const confirm = await new Promise((resolve) => {
67+
return new Promise((resolve) => {
6868
PopoverState.showPopupover(
69-
<SaveSceneDialog
70-
isExiting
71-
onConfirm={() => {
72-
resolve(true)
73-
}}
74-
onCancel={() => {
75-
resolve(false)
76-
}}
77-
/>
69+
<SaveSceneDialog isExiting onConfirm={() => resolve(true)} onCancel={() => resolve(false)} />
7870
)
7971
})
80-
if (!confirm) return
8172
}
73+
return true
74+
}
75+
76+
const onClickNewScene = async () => {
77+
if (!(await confirmSceneSaveIfModified())) return
8278

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

9187
const onCloseProject = async () => {
92-
const isModified = EditorState.isModified()
93-
94-
if (isModified) {
95-
const confirm = await new Promise((resolve) => {
96-
PopoverState.showPopupover(
97-
<SaveSceneDialog
98-
isExiting
99-
onConfirm={() => {
100-
resolve(true)
101-
}}
102-
onCancel={() => {
103-
resolve(false)
104-
}}
105-
/>
106-
)
107-
})
108-
if (!confirm) return
109-
}
88+
if (!(await confirmSceneSaveIfModified())) return
11089

11190
const editorState = getMutableState(EditorState)
11291
getMutableState(GLTFModifiedState).set({})

packages/ui/src/components/editor/panels/Scenes/container/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { SceneItem } from '@etherealengine/client-core/src/admin/components/scen
2727
import { PopoverState } from '@etherealengine/client-core/src/common/services/PopoverState'
2828
import { StaticResourceType, fileBrowserPath, staticResourcePath } from '@etherealengine/common/src/schema.type.module'
2929
import CreateSceneDialog from '@etherealengine/editor/src/components/dialogs/CreateScenePanelDialog'
30+
import { confirmSceneSaveIfModified } from '@etherealengine/editor/src/components/toolbar/Toolbar'
3031
import { onNewScene } from '@etherealengine/editor/src/functions/sceneFunctions'
3132
import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
3233
import { getMutableState, useHookstate, useMutableState } from '@etherealengine/hyperflux'
@@ -47,7 +48,9 @@ export default function ScenesPanel() {
4748

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

50-
const onClickScene = (scene: StaticResourceType) => {
51+
const onClickScene = async (scene: StaticResourceType) => {
52+
if (!(await confirmSceneSaveIfModified())) return
53+
5154
getMutableState(EditorState).merge({
5255
scenePath: scene.key
5356
})

packages/ui/src/components/editor/panels/Viewport/container/index.tsx

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,19 @@ import { uploadToFeathersService } from '@etherealengine/client-core/src/util/up
2929
import { FeatureFlags } from '@etherealengine/common/src/constants/FeatureFlags'
3030
import { clientSettingPath, fileBrowserUploadPath } from '@etherealengine/common/src/schema.type.module'
3131
import { processFileName } from '@etherealengine/common/src/utils/processFileName'
32-
import { getComponent, useComponent, useQuery } from '@etherealengine/ecs'
32+
import { useComponent, useQuery } from '@etherealengine/ecs'
3333
import { ItemTypes, SupportedFileTypes } from '@etherealengine/editor/src/constants/AssetTypes'
3434
import { EditorControlFunctions } from '@etherealengine/editor/src/functions/EditorControlFunctions'
3535
import { addMediaNode } from '@etherealengine/editor/src/functions/addMediaNode'
3636
import { getCursorSpawnPosition } from '@etherealengine/editor/src/functions/screenSpaceFunctions'
3737
import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
3838
import { GLTFComponent } from '@etherealengine/engine/src/gltf/GLTFComponent'
39-
import { GLTFModifiedState } from '@etherealengine/engine/src/gltf/GLTFDocumentState'
4039
import { ResourcePendingComponent } from '@etherealengine/engine/src/gltf/ResourcePendingComponent'
41-
import { SourceComponent } from '@etherealengine/engine/src/scene/components/SourceComponent'
4240
import useFeatureFlags from '@etherealengine/engine/src/useFeatureFlags'
43-
import { getMutableState, useHookstate, useMutableState } from '@etherealengine/hyperflux'
41+
import { useMutableState } from '@etherealengine/hyperflux'
4442
import { TransformComponent } from '@etherealengine/spatial'
4543
import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks'
46-
import React, { useEffect } from 'react'
44+
import React from 'react'
4745
import { useDrop } from 'react-dnd'
4846
import { useTranslation } from 'react-i18next'
4947
import { twMerge } from 'tailwind-merge'
@@ -125,23 +123,6 @@ const SceneLoadingProgress = ({ rootEntity }) => {
125123
const progress = useComponent(rootEntity, GLTFComponent).progress.value
126124
const loaded = GLTFComponent.useSceneLoaded(rootEntity)
127125
const resourcePendingQuery = useQuery([ResourcePendingComponent])
128-
const root = getComponent(rootEntity, SourceComponent)
129-
const sceneModified = useHookstate(getMutableState(GLTFModifiedState)[root]).value
130-
131-
useEffect(() => {
132-
if (!sceneModified) return
133-
const onBeforeUnload = (e: BeforeUnloadEvent) => {
134-
alert('You have unsaved changes. Please save before leaving.')
135-
e.preventDefault()
136-
e.returnValue = ''
137-
}
138-
139-
window.addEventListener('beforeunload', onBeforeUnload)
140-
141-
return () => {
142-
window.removeEventListener('beforeunload', onBeforeUnload)
143-
}
144-
}, [sceneModified])
145126

146127
if (loaded) return null
147128

0 commit comments

Comments
 (0)