mobile: fix numerous issues with tabs

This commit is contained in:
Ammar Ahmed
2023-12-25 10:00:02 +05:00
committed by Abdullah Atta
parent 6b6ee76c39
commit ad338aeefc
5 changed files with 153 additions and 130 deletions

View File

@@ -56,6 +56,7 @@ import { useUserStore } from "../../../stores/use-user-store";
import { import {
eClearEditor, eClearEditor,
eCloseFullscreenEditor, eCloseFullscreenEditor,
eEditorTabFocused,
eOnLoadNote, eOnLoadNote,
eOpenFullscreenEditor, eOpenFullscreenEditor,
eOpenLoginDialog, eOpenLoginDialog,
@@ -377,7 +378,7 @@ export const useEditorEvents = (
logger.info("[WEBVIEW LOG]", editorMessage.value); logger.info("[WEBVIEW LOG]", editorMessage.value);
break; break;
case EventTypes.contentchange: case EventTypes.contentchange:
editor.onContentChanged(); editor.onContentChanged(editorMessage.noteId);
break; break;
case EventTypes.selection: case EventTypes.selection:
break; break;
@@ -531,22 +532,25 @@ export const useEditorEvents = (
} }
case EventTypes.tabFocused: { case EventTypes.tabFocused: {
// Reload the note // Reload the note
console.log("Focused tab", editorMessage.tabId); console.log(
eSendEvent("tabsFocused", editorMessage.tabId); "Focused tab",
editorMessage.tabId,
editorMessage.noteId,
"Content:",
editorMessage.value
);
// const note = await db.notes.note(editorMessage.noteId); eSendEvent(eEditorTabFocused, editorMessage.tabId);
// if (note) { if (!editorMessage.value && editorMessage.noteId) {
// eSendEvent(eOnLoadNote, { const note = await db.notes.note(editorMessage.noteId);
// item: note, if (note) {
// forced: true eSendEvent(eOnLoadNote, {
// }); item: note,
// } forced: true,
// TODO tabId: editorMessage.tabId
// Handle any updates that occured in an note while the tab was not focused. });
// If editor has no content, reload the note, because it might be an app reload }
// or the tab was destroyed in background... }
// Maybe cache changes, something like pendingUpdates list.
// Do proper cleanup when a tab is destroyed though.
break; break;
} }

View File

@@ -49,7 +49,11 @@ import SettingsService from "../../../services/settings";
import { TipManager } from "../../../services/tip-manager"; import { TipManager } from "../../../services/tip-manager";
import { useSettingStore } from "../../../stores/use-setting-store"; import { useSettingStore } from "../../../stores/use-setting-store";
import { useTagStore } from "../../../stores/use-tag-store"; import { useTagStore } from "../../../stores/use-tag-store";
import { eClearEditor, eOnLoadNote } from "../../../utils/events"; import {
eClearEditor,
eEditorTabFocused,
eOnLoadNote
} from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs"; import { tabBarRef } from "../../../utils/global-refs";
import { onNoteCreated } from "../../notes/common"; import { onNoteCreated } from "../../notes/common";
import Commands from "./commands"; import Commands from "./commands";
@@ -109,12 +113,11 @@ export const useEditor = (
const commands = useMemo(() => new Commands(editorRef), [editorRef]); const commands = useMemo(() => new Commands(editorRef), [editorRef]);
const editorSessionHistory = useMemo(() => new SessionHistory(), []); const editorSessionHistory = useMemo(() => new SessionHistory(), []);
const state = useRef<Partial<EditorState>>(defaultState); const state = useRef<Partial<EditorState>>(defaultState);
const placeholderTip = useRef(TipManager.placeholderTip());
const tags = useTagStore((state) => state.items); const tags = useTagStore((state) => state.items);
const insets = useGlobalSafeAreaInsets(); const insets = useGlobalSafeAreaInsets();
const isDefaultEditor = editorId === ""; const isDefaultEditor = editorId === "";
const saveCount = useRef(0); const saveCount = useRef(0);
const lastContentChangeTime = useRef<number>(0); const lastContentChangeTime = useRef<Record<string, number>>({});
const lock = useRef(false); const lock = useRef(false);
const currentLoadingNoteId = useRef<string>(); const currentLoadingNoteId = useRef<string>();
const loadingState = useRef<string>(); const loadingState = useRef<string>();
@@ -149,7 +152,7 @@ export const useEditor = (
}, [commands, tags]); }, [commands, tags]);
useEffect(() => { useEffect(() => {
const event = eSubscribeEvent("tabsFocused", (tabId) => { const event = eSubscribeEvent(eEditorTabFocused, (tabId) => {
lastTabFocused.current = tabId as number; lastTabFocused.current = tabId as number;
console.log(tabId); console.log(tabId);
}); });
@@ -192,6 +195,7 @@ export const useEditor = (
currentNotes.current[noteId] = null; currentNotes.current[noteId] = null;
currentContents.current[noteId] = null; currentContents.current[noteId] = null;
editorSessionHistory.clearSession(noteId); editorSessionHistory.clearSession(noteId);
lastContentChangeTime.current[noteId] = 0;
} }
clearTimeout(timers.current["loading-images" + noteId]); clearTimeout(timers.current["loading-images" + noteId]);
@@ -200,13 +204,12 @@ export const useEditor = (
loadingState.current = undefined; loadingState.current = undefined;
lock.current = false; lock.current = false;
resetContent && postMessage(EditorEvents.title, "", tabId); resetContent && postMessage(EditorEvents.title, "", tabId);
lastContentChangeTime.current = 0;
resetContent && (await commands.clearContent(tabId)); resetContent && (await commands.clearContent(tabId));
resetContent && (await commands.clearTags(tabId)); resetContent && (await commands.clearTags(tabId));
// TODO ? useTabStore.getState().updateTab(tabId, {
// useTabStore.getState().updateTab(tabId, { noteId: undefined
// noteId: undefined });
// });
}, },
[commands, editorSessionHistory, postMessage] [commands, editorSessionHistory, postMessage]
); );
@@ -269,16 +272,15 @@ export const useEditor = (
if (!locked) { if (!locked) {
id = await db.notes?.add(noteData); id = await db.notes?.add(noteData);
if (!note && id) { if (!note && id) {
useTabStore.getState().updateTab(tabId, {
noteId: id
});
editorSessionHistory.newSession(id); editorSessionHistory.newSession(id);
if (id) { if (id) {
currentNotes.current[id] = await db.notes?.note(id); currentNotes.current[id] = await db.notes?.note(id);
} }
useTabStore.getState().updateTab(tabId, {
noteId: id
});
const defaultNotebook = db.settings.getDefaultNotebook(); const defaultNotebook = db.settings.getDefaultNotebook();
if (!state.current.onNoteCreated && defaultNotebook) { if (!state.current.onNoteCreated && defaultNotebook) {
onNoteCreated(id, { onNoteCreated(id, {
@@ -290,9 +292,10 @@ export const useEditor = (
} }
if (!noteData.title) { if (!noteData.title) {
console.log("posting title to tab", tabId);
postMessage( postMessage(
EditorEvents.title, EditorEvents.title,
currentNotes.current?.title, currentNotes.current[id]?.title,
tabId tabId
); );
} }
@@ -315,7 +318,7 @@ export const useEditor = (
tabId tabId
); );
lastContentChangeTime.current = note.dateEdited; lastContentChangeTime.current[id] = note.dateEdited;
if ( if (
saveCount.current < 2 || saveCount.current < 2 ||
@@ -369,7 +372,12 @@ export const useEditor = (
); );
const loadNote = useCallback( const loadNote = useCallback(
async (event: { item?: Note; forced?: boolean; newNote?: boolean }) => { async (event: {
item?: Note;
forced?: boolean;
newNote?: boolean;
tabId?: number;
}) => {
state.current.currentlyEditing = true; state.current.currentlyEditing = true;
if ( if (
!state.current.ready && !state.current.ready &&
@@ -383,56 +391,54 @@ export const useEditor = (
} }
if (event.newNote) { if (event.newNote) {
console.log("Create new note");
useTabStore.getState().focusEmptyTab(); useTabStore.getState().focusEmptyTab();
const tabId = useTabStore.getState().currentTab; const tabId = useTabStore.getState().currentTab;
console.log("empty tab", tabId);
currentNotes.current && (await reset(tabId)); currentNotes.current && (await reset(tabId));
setTimeout(() => { setTimeout(() => {
if (state.current?.ready) commands.focus(tabId); if (state.current?.ready) commands.focus(tabId);
lastContentChangeTime.current = 0;
}); });
} else { } else {
if (!event.item) return; if (!event.item) return;
const item = event.item; const item = event.item;
console.log("load note called again...", event.forced, event.item.id); // If note was already opened in a tab, focus that tab.
// If note was already opened in a tab, focus that tab and return. Once the tab is focused if (typeof event.tabId !== "number") {
// the note will load. if (useTabStore.getState().hasTabForNote(event.item.id)) {
if (useTabStore.getState().hasTabForNote(event.item.id)) { const tabId = useTabStore.getState().getTabForNote(event.item.id);
const tabId = useTabStore.getState().getTabForNote(event.item.id); if (typeof tabId === "number") {
if (typeof tabId === "number") { useTabStore.getState().updateTab(tabId, {
useTabStore.getState().updateTab(tabId, { readonly: event.item.readonly
readonly: event.item.readonly });
useTabStore.getState().focusTab(tabId);
}
console.log("Note already loaded, focusing the tab");
} else {
console.log("Opening note in preview tab");
// Otherwise we focus the preview tab or create one to open the note in.
useTabStore.getState().focusPreviewTab(event.item.id, {
readonly: event.item.readonly,
locked: false
}); });
useTabStore.getState().focusTab(tabId);
} }
} else { } else {
console.log("opening note in preview tab"); if (lastTabFocused.current !== event.tabId) {
// Otherwise we focus the preview tab or create one to open the note in. console.log("Focused tab");
useTabStore.getState().focusPreviewTab(event.item.id, { useTabStore.getState().focusTab(event.tabId);
readonly: event.item.readonly,
locked: false
});
}
const tabId = useTabStore.getState().currentTab;
console.log(lastTabFocused.current, tabId);
if (lastTabFocused.current !== tabId) {
if ((await waitForEvent("tabsFocused", 1000)) !== tabId) {
console.log("tab id did not match after focus in 1000ms");
return;
} }
} }
// If note is already loaded and forced reload is not requested, return. const tabId = event.tabId || useTabStore.getState().currentTab;
if (!event.forced && currentNotes.current[item.id]) return; if (lastTabFocused.current !== tabId) {
// if ((await waitForEvent(eEditorTabFocused, 1000)) !== tabId) {
// console.log("tab id did not match after focus in 1000ms");
// return;
// }
console.log("Waiting for tab to focus");
return;
}
state.current.movedAway = false; state.current.movedAway = false;
state.current.currentlyEditing = true; state.current.currentlyEditing = true;
if (!currentNotes.current[item.id]) {
// Reset current tab if note isn't already loaded.
currentNotes.current && (await reset(tabId, false, false));
}
await loadContent(item); await loadContent(item);
if ( if (
@@ -441,6 +447,7 @@ export const useEditor = (
currentContents.current[item.id]?.data && currentContents.current[item.id]?.data &&
loadingState.current === currentContents.current[item.id]?.data loadingState.current === currentContents.current[item.id]?.data
) { ) {
// If note is already loading, return.
return; return;
} }
@@ -449,7 +456,7 @@ export const useEditor = (
return; return;
} }
lastContentChangeTime.current = item.dateEdited; lastContentChangeTime.current[item.id] = item.dateEdited;
currentLoadingNoteId.current = item.id; currentLoadingNoteId.current = item.id;
currentNotes.current[item.id] = item; currentNotes.current[item.id] = item;
@@ -466,6 +473,7 @@ export const useEditor = (
await postMessage(EditorEvents.title, item.title, tabId); await postMessage(EditorEvents.title, item.title, tabId);
loadingState.current = currentContents.current[item.id]?.data; loadingState.current = currentContents.current[item.id]?.data;
if (currentContents.current[item.id]?.data) { if (currentContents.current[item.id]?.data) {
console.log("loading content for note...");
await postMessage( await postMessage(
EditorEvents.html, EditorEvents.html,
currentContents.current[item.id]?.data, currentContents.current[item.id]?.data,
@@ -512,7 +520,8 @@ export const useEditor = (
const note = await db.notes?.note(noteId); const note = await db.notes?.note(noteId);
if (lastContentChangeTime.current >= (data as Note).dateEdited) return; if (lastContentChangeTime.current[noteId] >= (data as Note).dateEdited)
return;
lock.current = true; lock.current = true;
@@ -538,7 +547,7 @@ export const useEditor = (
} else { } else {
const _nextContent = data.data; const _nextContent = data.data;
if (_nextContent === currentContents.current?.data) return; if (_nextContent === currentContents.current?.data) return;
lastContentChangeTime.current = note.dateEdited; lastContentChangeTime.current[note.id] = note.dateEdited;
await postMessage(EditorEvents.updatehtml, _nextContent, tabId); await postMessage(EditorEvents.updatehtml, _nextContent, tabId);
if (!isEncryptedContent(data)) { if (!isEncryptedContent(data)) {
currentContents.current[note.id] = data as UnencryptedContentItem; currentContents.current[note.id] = data as UnencryptedContentItem;
@@ -596,7 +605,9 @@ export const useEditor = (
) )
return; return;
lastContentChangeTime.current = Date.now(); if (noteId) {
lastContentChangeTime.current[noteId] = Date.now();
}
if (type === EditorEvents.content && noteId) { if (type === EditorEvents.content && noteId) {
currentContents.current[noteId as string] = { currentContents.current[noteId as string] = {
@@ -655,14 +666,12 @@ export const useEditor = (
}; };
}, [editorId, loadNote, restoreEditorState, isDefaultEditor]); }, [editorId, loadNote, restoreEditorState, isDefaultEditor]);
const onContentChanged = () => { const onContentChanged = (noteId?: string) => {
lastContentChangeTime.current = Date.now(); if (noteId) {
lastContentChangeTime.current[noteId] = Date.now();
}
}; };
useEffect(() => {
state.current.saveCount = 0;
}, [loading]);
const onReady = useCallback(async () => { const onReady = useCallback(async () => {
if ( if (
!(await isEditorLoaded( !(await isEditorLoaded(
@@ -671,15 +680,9 @@ export const useEditor = (
useTabStore.getState().currentTab useTabStore.getState().currentTab
)) ))
) { ) {
console.log(
"ready failed....",
sessionIdRef.current,
useTabStore.getState().currentTab
);
eSendEvent("webview_reset", "onReady"); eSendEvent("webview_reset", "onReady");
return false; return false;
} else { } else {
console.log("onReady", "sync tabs");
syncTabs(); syncTabs();
isDefaultEditor && restoreEditorState(); isDefaultEditor && restoreEditorState();
return true; return true;
@@ -691,55 +694,43 @@ export const useEditor = (
clearTimeout(timers.current["editor:loaded"]); clearTimeout(timers.current["editor:loaded"]);
timers.current["editor:loaded"] = setTimeout(async () => { timers.current["editor:loaded"] = setTimeout(async () => {
postMessage(EditorEvents.theme, theme); postMessage(EditorEvents.theme, theme);
commands.setInsets( commands.setInsets(
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 } isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
); );
await commands.setSettings(); await commands.setSettings();
timers.current["editor:loaded"] = setTimeout(async () => {
if (!state.current.ready && (await onReady())) {
state.current.ready = true;
}
overlay(false);
// TODO: Improve handling this on app launch from a link etc. if (!state.current.ready && (await onReady())) {
const noteId = useTabStore.getState().getCurrentNoteId(); state.current.ready = true;
}
overlay(false);
if (noteId) { const noteId = useTabStore.getState().getCurrentNoteId();
if (useSettingStore.getState().isAppLoading) { async function restoreTabNote() {
const unsub = useSettingStore.subscribe(async (s) => { if (!noteId) return;
if (!s.isAppLoading) { const note = await db.notes.note(noteId);
try { if (note) {
const note = await db.notes.note(noteId); loadNote({ item: note, forced: true });
if (note) { } else {
loadNote({ item: note, forced: true }); console.log("Editor loaded with blank note");
} else { loadNote({ newNote: true });
console.log("new note after app load"); if (tabBarRef.current?.page === 1) {
loadNote({ newNote: true }); state.current.currentlyEditing = false;
if (tabBarRef.current?.page === 1) {
state.current.currentlyEditing = false;
}
}
unsub();
} catch (e) {
console.log(e);
}
}
});
} else {
const note = await db.notes.note(noteId);
if (note) {
loadNote({ item: note, forced: true });
} else {
console.log("new note");
loadNote({ newNote: true });
if (tabBarRef.current?.page === 1) {
state.current.currentlyEditing = false;
}
}
} }
} }
}); }
if (noteId) {
if (useSettingStore.getState().isAppLoading) {
const unsub = useSettingStore.subscribe(async (state) => {
if (!state.isAppLoading) {
restoreTabNote();
unsub();
}
});
} else {
restoreTabNote();
}
}
}); });
}, [ }, [
onReady, onReady,

View File

@@ -166,3 +166,4 @@ export const eOnRefreshSearch = "612";
export const eOpenAppLockPasswordDialog = "613"; export const eOpenAppLockPasswordDialog = "613";
export const eCloseAppLocKPasswordDailog = "614"; export const eCloseAppLocKPasswordDailog = "614";
export const eEditorTabFocused = "613";

View File

@@ -2655,6 +2655,8 @@
"mdi-react": "9.1.0", "mdi-react": "9.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-freeze": "^1.0.3",
"tinycolor2": "1.6.0",
"zustand": "^4.4.7" "zustand": "^4.4.7"
}, },
"devDependencies": { "devDependencies": {
@@ -43516,6 +43518,28 @@
} }
} }
}, },
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
},
"node_modules/react-dom/node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/react-freeze": { "node_modules/react-freeze": {
"version": "1.0.3", "version": "1.0.3",
"license": "MIT", "license": "MIT",
@@ -61595,7 +61619,9 @@
"mdi-react": "9.1.0", "mdi-react": "9.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-freeze": "^1.0.3",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"tinycolor2": "1.6.0",
"zustand": "^4.4.7" "zustand": "^4.4.7"
}, },
"dependencies": { "dependencies": {

View File

@@ -149,7 +149,7 @@ export function useEditorController(update: () => void): EditorController {
const contentChange = useCallback( const contentChange = useCallback(
(editor: Editor, ignoreEdit?: boolean) => { (editor: Editor, ignoreEdit?: boolean) => {
const currentSessionId = globalThis.sessionId; const currentSessionId = globalThis.sessionId;
post(EventTypes.contentchange); post(EventTypes.contentchange, undefined, tab.id, tab.noteId);
if (!editor) return; if (!editor) return;
if (typeof timers.current.change === "number") { if (typeof timers.current.change === "number") {
clearTimeout(timers.current?.change); clearTimeout(timers.current?.change);
@@ -198,14 +198,15 @@ export function useEditorController(update: () => void): EditorController {
return; return;
} }
logger( if (tab.id === message.tabId) {
"info", logger(
"webview message for tab", "info",
message.type, message.type,
tab.id, tab.noteId,
message.tabId, "Focused:",
useTabStore.getState().currentTab tab.id === useTabStore.getState().currentTab
); );
}
const editor = editors[tab.id]; const editor = editors[tab.id];
switch (type) { switch (type) {