만약, 웹사이트에서 폼으로 문서를 작성하거나 캔버스 같은 것을 통해 작업을 하다가 실수로 창 닫기 버튼, 즐겨찾기, 뒤로가기 버튼 등을 누른 경우를 생각해보자.
작업한 내역이 있어도 사용자의 아무런 의사를 묻지 않고 현재 페이지를 벗어나거나 브라우저가 닫혀 작업한 내역이 전부 사라지면 사용자 경험은 매우 좋지 않을 것이다.
이를 방지하기 위해 페이지를 벗어나는 이벤트를 막고 사용자의 의사를 묻는 방식을 사용해보자
먼저 변경 사항이 있는 지 여부를 확인해야 한다.
import { create } from 'zustand';
type TworkspaceChangeStatus = {
isBlockChanged: boolean; // 블록 관련 변경 사항 여부
isCssChanged: boolean; // css 속성 관련 변경 사항 여부
setIsBlockChanged: (isBlockChanged: boolean) => void;
setIsCssChanged: (isCssChanged: boolean) => void;
};
export const useWorkspaceChangeStatusStore = create<TworkspaceChangeStatus>((set) => ({
isBlockChanged: false,
isCssChanged: false,
setIsBlockChanged: (isBlockChanged) => set({ isBlockChanged }),
setIsCssChanged: (isCssChanged) => set({ isCssChanged }),
}));
워크스페이스 변경 사항에 관련된 전역상태를 추가한다.
블록 관련 변경 사항은 WorkspaceContent에서 확인할 수 있다.
블록 관련 이벤트 발생 시 isBlockChanged
를 true
로 설정한다.
const handleAutoConversion = (event: Blockly.Events.Abstract) => {
if (
event.type === Blockly.Events.BLOCK_CREATE ||
event.type === Blockly.Events.BLOCK_MOVE ||
event.type === Blockly.Events.BLOCK_CHANGE ||
event.type === Blockly.Events.BLOCK_DELETE
) {
// data-block-id 포함된 코드 (내부 처리용)
const codeWithId = generateFullCodeWithBlockId(newWorkspace);
// data-block-id 제거된 코드 (사용자에게 보여지는 코드)
const codeWithNoId = removeBlockIdFromCode(codeWithId);
setHtmlCode(codeWithNoId);
}
if (
event.type === Blockly.Events.VIEWPORT_CHANGE ||
event.type === Blockly.Events.BLOCK_DRAG ||
event.type === Blockly.Events.BLOCK_FIELD_INTERMEDIATE_CHANGE ||
(event.type === Blockly.Events.BLOCK_MOVE && isBlockLoadingFinish.current) ||
(event.type === Blockly.Events.BLOCK_DELETE && isBlockLoadingFinish.current)
) {
setIsBlockChanged(true);
}
if (event.type === Blockly.Events.FINISHED_LOADING) {
isBlockLoadingFinish.current = true;
}
};
css 속성 관련 변경 사항은 css 속성 관련 전역 변수에서 한번이라도 속성 관련 상태를 변경 시 isCssChanged
를 true
로 바꾼다.
setCheckedCssPropertyObj: (className, label, value) =>
set((state) => {
const { setIsCssChanged } = useWorkspaceChangeStatusStore();
setIsCssChanged(true);
const updatedObj = state.totalCssPropertyObj[className] || {
checkedCssPropertyObj: {},
cssOptionObh: {},
};
updatedObj.checkedCssPropertyObj[label] = value;
return {
totalCssPropertyObj: {
...state.totalCssPropertyObj,
[className]: updatedObj,
},
};
}),
setCssOptionObj: (className, label, value) =>
set((state) => {
const { setIsCssChanged } = useWorkspaceChangeStatusStore();
setIsCssChanged(true);
const updatedObj = state.totalCssPropertyObj[className] || {
checkedCssPropertyObj: {},
cssOptionObj: {},
};
updatedObj.cssOptionObj[label] = value;
return {
totalCssPropertyObj: {
...state.totalCssPropertyObj,
[className]: updatedObj,
},
};
}),
// 이외 css 블록 추가 시
useCssPropsStore.getState().addNewCssClass(createClassType);
useWorkspaceChangeStatusStore.getState().setIsBlockChanged(true);
// 기존 블록들이 있는 cssStyleToolboxConfig.ts에 새 블록 추가
// css 블록 삭제 시
useCssPropsStore.getState().addNewCssClass(createClassType);
useWorkspaceChangeStatusStore.getState().setIsBlockChanged(true);
// 기존 블록들이 있는 cssStyleToolboxConfig.ts에 새 블록 추가
// reset css 적용 시
resetCssCheckboxElement.checked = useResetCssStore.getState().isResetCssChecked;
resetCssCheckboxElement.addEventListener('change', () => {
useResetCssStore.getState().toggleResetCss();
useWorkspaceChangeStatusStore.getState().setIsCssChanged(true);
});
beforeunload
이벤트는 window, 문서 및 해당 리소스가 언로드 (닫기, 새로고침) 되려고 할 때 시작한다.