Skip to content
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

feat: persisted storage #531

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 2 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
57 changes: 57 additions & 0 deletions packages/website/src/hooks/use-presisted-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect, useRef } from 'react';

type Getter<T> = () => T;
interface Options<T> {
getter: Getter<T>;
}
type PersistedStorage = <T>(
id: string,
options?: Options<T>,
) => {
/**
* 从 storage 中获取的数据,没有则返回 null
*/
data: T | null;
/**
* 存储数据时,将会调用该函数获取存储的数据
*
* 为什么需要这个?
* 在一些场景,我们需要该 hook 提供的初始数据,来初始化其它 hook
* 而 getter 又依赖那些 hook。见 edit_detail 页面
*/
setGetter: (getter: Getter<T>) => void;
/**
* 调用后,代表数据将不会被保存
*/
finish: () => void;
};
export const usePersistedStorage: PersistedStorage = <T>(id: string, options?: Options<T>) => {
const _id = `resume:${id}`;
const item = sessionStorage.getItem(_id);
if (item) {
sessionStorage.removeItem(_id);
Copy link
Contributor

Choose a reason for hiding this comment

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

页面崩了咋办,数据丢了?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

页面崩掉要分几种情况吧。
如果是 throw Error 那种不会导致整个组件崩掉,状态还是会在的,而且如果是这种错误应该我们这边处理一下(比如解析 wiki 错误加个弹窗之类的)
如果是整个页面崩了,就是状态都没了的话是什么情况呢,不太清楚怎么触发这类错误🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

页面崩掉要分几种情况吧。 如果是 throw Error 那种不会导致整个组件崩掉,状态还是会在的,而且如果是这种错误应该我们这边处理一下(比如解析 wiki 错误加个弹窗之类的) 如果是整个页面崩了,就是状态都没了的话是什么情况呢,不太清楚怎么触发这类错误🤔

比如浏览器某个页面卡死了,用户只能用任务管理器强制 kill 掉浏览器?总之是那种非正常的退出?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

这种存储策略,非正常退出应该没办法~就是正常退出,比如用户直接叉掉页面也是没办法(
针对这种的话可能用实时缓存好点?然后加个 throttle 之类的。

}
const getterRef = useRef<null | Getter<T>>(options?.getter ?? null);
const finished = useRef(false);

const setGetter = (getter: Getter<T>) => {
getterRef.current = getter;
};

useEffect(() => {
return () => {
// 组件 unmount 时存储
if (!finished.current) {
getterRef.current && sessionStorage.setItem(_id, JSON.stringify(getterRef.current()));
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return {
data: item ? (JSON.parse(item) as T) : null,
setGetter,
finish: () => {
finished.current = true;
},
};
};
48 changes: 38 additions & 10 deletions packages/website/src/pages/index/subject/[id]/wiki/edit_detail.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import cn from 'classnames';
import dayjs from 'dayjs';
import { flow } from 'lodash';
import { flow, merge } from 'lodash';
import type { editor } from 'monaco-editor/esm/vs/editor/editor.api';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
@@ -21,6 +21,7 @@ import {
import { withErrorBoundary } from '@bangumi/website/components/ErrorBoundary';
import Helmet from '@bangumi/website/components/Helmet';
import WikiEditor from '@bangumi/website/components/WikiEditor/WikiEditor';
import { usePersistedStorage } from '@bangumi/website/hooks/use-presisted-storage';
import { getWikiTemplate, WikiEditTabsItemsByKey } from '@bangumi/website/shared/wiki';

import WikiBeginnerEditor from '../components/WikiBeginnerEditor';
@@ -71,23 +72,51 @@ const formatWikiSyntaxErrorMessage = (error: WikiSyntaxError): string => {
};

function WikiEditDetailDetailPage() {
const { register, handleSubmit, setValue, watch } = useForm<FormData>();
const { subjectEditHistory, subjectWikiInfo, mutateHistory, subjectId } = useWikiContext();

const {
data: initForm,
setGetter,
finish,
} = usePersistedStorage<FormData>(`wiki-edit-detail/${subjectId}`);
// 存储在 storage 中的数据拥有更高的优先级
const defaultForm = merge({ subject: subjectWikiInfo }, initForm);
const { register, handleSubmit, setValue, watch, getValues } = useForm<FormData>({
defaultValues: defaultForm,
});

setGetter(() =>
// 1. 从表单中获取数据
merge(getValues(), {
subject: {
// 2. 更新 infobox
infobox: stringifyWiki({
type: wikiRef.current?.type ?? '',
data: fromWikiElement(wikiElement),
}),
},
}),
);

const prePlatform = watch('subject.platform');

const [editorType, setEditorType] = useLocalstorageState(
'chii_wiki_editor_type',
EditorType.Beginner,
);
const wikiRef = useRef<Wiki>();
const [wikiElement, setWikiElement] = useState<WikiElement[]>([]);

const wikiRef = useRef<Wiki>(
defaultForm?.subject?.infobox ? parseWiki(defaultForm.subject.infobox) : null,
);

const [wikiElement, setWikiElement] = useState<WikiElement[]>(
wikiRef.current ? toWikiElement(wikiRef.current) : [],
);

const monoEditorInstanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const { subjectEditHistory, subjectWikiInfo, mutateHistory, subjectId } = useWikiContext();

useEffect(() => {
wikiRef.current = parseWiki(subjectWikiInfo.infobox);
monoEditorInstanceRef.current?.setValue(subjectWikiInfo.infobox);
setWikiElement(toWikiElement(wikiRef.current));
// https://github.com/bangumi/frontend/pull/312#discussion_r1086401410
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -130,14 +159,15 @@ function WikiEditDetailDetailPage() {
}
})
.then(() => {
finish();
// TODO: jump to other path
console.log('success');
})
.catch(() => {
toast('提交失败,请稍后再试');
});
},
[wikiElement, editorType, mutateHistory, subjectId],
[wikiElement, editorType, mutateHistory, subjectId, finish],
);

const handleSetEditorType = (type: EditorType) => {
@@ -223,7 +253,6 @@ function WikiEditDetailDetailPage() {
<Input
type='text'
wrapperClass={style.formInput}
defaultValue={subjectWikiInfo.name}
{...register('subject.name', { required: true })}
/>
</Form.Item>
@@ -293,7 +322,6 @@ function WikiEditDetailDetailPage() {
<Form.Item label='剧情介绍'>
<textarea
className={style.formTextArea}
defaultValue={subjectWikiInfo.summary}
{...register('subject.summary', { required: true })}
/>
</Form.Item>