Skip to content

Commit

Permalink
Refactor DiaryEditor.vue: Replace loading with loadingRef, rename… (
Browse files Browse the repository at this point in the history
#61)

* Refactor DiaryEditor.vue: Replace `loading` with `loadingRef`, rename `json` to `noteJsonRef`, and simplify initialization logic using `useQuery`.

* before switch fetchNote and saveNote

* works

* works

* Remove idb dependency and related files

Deleted the idb package and all related files from the node_modules directory. This removes the IndexedDB wrapper library that was previously installed as a dependency. The package.json file was also deleted as it only contained the idb dependency.
  • Loading branch information
swuecho authored Dec 28, 2024
1 parent 6c3d248 commit adae799
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 45 deletions.
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"axios": "^1.7.4",
"element-tiptap": "1.27.1",
"element-ui": "^2.15.13",
"idb": "^8.0.1",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"prosemirror-tables": "^1.1.1",
Expand Down
107 changes: 62 additions & 45 deletions web/src/components/DiaryEditor.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<template>
<div class="content">
<div class="editor">
<el-tiptap :key="'editor-' + date" :content="content" :extensions="extensions" @onUpdate="debouncedOnUpdate" @onInit="onInit"></el-tiptap>
<el-tiptap :key="'editor-' + date" :content="content" :extensions="extensions" @onUpdate="debouncedOnUpdate"
@onInit="onInit"></el-tiptap>
</div>
<Icon v-if="loading" icon="line-md:loading-alt-loop" />
<Icon v-if="isLoading || isSaving" icon="line-md:loading-alt-loop" />
</div>
</template>


<script setup>
import { ref, computed, onMounted } from 'vue';
import { ref, computed, onMounted, watch } from 'vue';
import moment from 'moment';
import { Icon } from '@iconify/vue2';
import tableOfContents from '@iconify/icons-mdi/table-of-contents';
Expand All @@ -19,38 +20,72 @@ import 'codemirror/lib/codemirror.css'; // import base style
import 'codemirror/mode/xml/xml.js'; // language
import 'codemirror/addon/selection/active-line.js'; // require active-line.js
import 'codemirror/addon/edit/closetag.js'; // autoCloseTags
import { useMutation } from '@tanstack/vue-query';
import { useMutation, useQuery } from '@tanstack/vue-query';
import router from '@/router';
import { debounce } from 'lodash';
import axios from '@/axiosConfig.js';
import { saveNote, fetchNote } from '@/services/note.ts';
import { useQueryClient } from '@tanstack/vue-query';
const queryClient = useQueryClient();
// import axios from '@/axiosConfig.js';
// const saveNote = async (noteObj) => {
// const response = await axios.put(`/api/diary/${props.date}`, noteObj);
// // Introduce a 2-second delay
// await new Promise(resolve => setTimeout(resolve, 2000));
// return response.data;
// }
// const fetchNote = async () => {
// const response = await axios.get(`/api/diary/${props.date}`);
// // Introduce a 2-second delay
// await new Promise(resolve => setTimeout(resolve, 2000));
// return response.data;
// }
const props = defineProps({
date: String
});
const loading = ref(true);
const last_note_json = ref(null);
const content = ref(null);
const extensions = createExtensions();
const json = ref(null);
const content = ref(null);
const noteJsonRef = ref(null);
const mutation = useMutation({
mutationFn: async () => {
const response = await axios.put(`/api/diary/${props.date}`, {
noteId: props.date,
note: JSON.stringify(json.value)
});
return response.data;
const { data: noteData, isLoading } = useQuery({
queryKey: ['diaryContent', props.date],
queryFn: () => fetchNote(props.date),
// TODO: fix the onError removed from the useQuery issue
onError: (error) => {
if (error.response && error.response.status === 401) {
// Use the correct router method in the Vue 3 setup
router.push({ name: 'login' });
}
console.error('Error fetching diary:', error);
},
staleTime: 1000 * 60 * 5, // Data is fresh for 5 minutes
});
watch(noteData, (newData) => {
if (newData) {
const noteObj = JSON.parse(newData.note);
content.value = noteObj;
}
});
const onInit = async ({ editor }) => {
editor.setContent(content.value);
};
const { mutate: updateNote, isLoading: isSaving } = useMutation({
mutationFn: saveNote,
onSuccess: (data) => {
console.log(data);
loading.value = false;
// Invalidate the todoContent query
// Invalidate the todoContent query
// Note: queryClient is not defined in this scope.
Expand All @@ -62,10 +97,9 @@ const mutation = useMutation({
//
// Then use it here:
queryClient.invalidateQueries({ queryKey: ['todoContent'] });
queryClient.invalidateQueries({ queryKey: ['MdContent',props.date] });
queryClient.invalidateQueries({ queryKey: ['MdContent', props.date] });
},
onError: (error) => {
loading.value = false;
if (error.response && error.response.status === 401) {
// Use the correct router method in the Vue 3 setup
router.push({ name: 'login' });
Expand All @@ -75,44 +109,27 @@ const mutation = useMutation({
staleTime: 500,
});
const onUpdate = (output, options) => {
const { getJSON } = options;
json.value = getJSON();
mutation.mutate();
noteJsonRef.value = getJSON();
updateNote(
{
noteId: props.date,
note: JSON.stringify(noteJsonRef.value),
});
};
const debouncedOnUpdate = debounce(function (output, options) {
onUpdate(output, options);
}, 500)
const onInit = async ({ editor }) => {
loading.value = true;
try {
const response = await axios.get(`/api/diary/${props.date}`);
const lastNote = response.data;
if (lastNote) {
const lastNoteJson = JSON.parse(lastNote.note);
last_note_json.value = lastNoteJson;
editor.setContent(lastNoteJson);
}
} catch (error) {
if (error.response && error.response.status === 401) {
// Use the correct router method in the Vue 3 setup
router.push({ name: 'login' });
}
console.error('Error updating diary:', error);
console.error('Error fetching diary content:', error);
} finally {
loading.value = false;
}
};
</script>

<style scoped>
pre code {
font-family: "Fira Code", Courier, Monaco, monospace;
}
</style>
132 changes: 132 additions & 0 deletions web/src/services/note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// services/api.ts
import axios from '../axiosConfig.js';

import { openDB, DBSchema, IDBPDatabase } from 'idb';
import type { DiaryEntry, QueuedRequest } from '../types.ts';

interface MyDB extends DBSchema {
notes: {
key: string;
value: DiaryEntry;
indexes: { 'noteId': string }
}
}

let db: IDBPDatabase<MyDB> | null = null;

const DB_NAME = 'logbook-db';

const openDatabase = async () => {
if (db) return db;
db = await openDB<MyDB>(DB_NAME, 1, {
upgrade(db) {
db.createObjectStore('notes', { keyPath: 'noteId' })
}
});
return db;
}

const requestQueue: QueuedRequest[] = [];
let isSyncing = false;

// Function to process request queue
const processQueue = async () => {
if (isSyncing || !navigator.onLine) {
return; // syncing process or offline, wait
}

isSyncing = true;

while (requestQueue.length > 0) {
const request = requestQueue.shift();
if (!request) continue;

try {
const response = await axios({
url: request.url,
method: request.method,
data: request.data
})
if (response.status >= 200 && response.status < 300) {
request.resolve && request.resolve(response.data)
} else {
console.error('request failed with status: ' + response.status)
requestQueue.unshift(request); // push back into the queue
request.reject && request.reject('request failed');
break;
}
}
catch (error) {
console.error("request failed", error)
requestQueue.unshift(request);
request.reject && request.reject('request failed');
break;
}
}

isSyncing = false;
if (requestQueue.length > 0) {
// if there are requests left, retry after a short delay
setTimeout(processQueue, 1000)
}
};


const enqueueRequest = (url: string, method: 'PUT' | 'GET', data: any): Promise<any> => {
return new Promise((resolve, reject) => {
requestQueue.push({ url, method, data, resolve, reject });
processQueue();
});

};

const apiRequest = async (url: string, method: 'PUT' | 'GET', data: any) => {
if (navigator.onLine) {
// if online go straight to the network
try {
const response = await axios({ url, method, data });
return response.data
}
catch (error) {
throw error
}
}
else {
return enqueueRequest(url, method, data);
}
};


const saveNote = async (note: DiaryEntry) => {
const db = await openDatabase()
console.log("saving note", note);
await db.put('notes', note);

// if user online, immediately save to the server
return apiRequest(`/api/diary/${note.noteId}`, 'PUT', note);
};

const fetchNote = async (noteId: string): Promise<DiaryEntry | undefined> => {
console.log("fetching note", noteId);
const db = await openDatabase();
let cachedNote = await db.get('notes', noteId);
if (navigator.onLine) {
try {
const response = await apiRequest(`/api/diary/${noteId}`, 'GET', null);
if (response) {
cachedNote = response;
db.put('notes', response);
}
} catch (error) {
console.log("fetch note failed, using local", error);
}
}
return cachedNote
};

window.addEventListener('online', () => {
processQueue(); //process the queue on network status change
});

export { saveNote, fetchNote };

12 changes: 12 additions & 0 deletions web/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// types.ts
export interface DiaryEntry {
noteId: string;
note: string;
}
export interface QueuedRequest {
url: string;
method: 'PUT' | 'GET';
data: any;
resolve?: (value: any) => void;
reject?: (reason?: any) => void;
}
5 changes: 5 additions & 0 deletions web/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,11 @@ iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"

idb@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/idb/-/idb-8.0.1.tgz#15e8be673413d6caf4beefacf086c8902d785e1e"
integrity sha512-EkBCzUZSdhJV8PxMSbeEV//xguVKZu9hZZulM+2gHXI0t2hGVU3eYE6/XnH77DS6FM2FY8wl17aDcu9vXpvLWQ==

ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


idb@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/idb/-/idb-8.0.1.tgz#15e8be673413d6caf4beefacf086c8902d785e1e"
integrity sha512-EkBCzUZSdhJV8PxMSbeEV//xguVKZu9hZZulM+2gHXI0t2hGVU3eYE6/XnH77DS6FM2FY8wl17aDcu9vXpvLWQ==

0 comments on commit adae799

Please sign in to comment.