Add basic Notes UI

This commit is contained in:
Bruno Bernardino
2024-04-26 14:31:25 +01:00
parent 2920de90b6
commit 3f5422f8eb
14 changed files with 849 additions and 104 deletions

109
islands/notes/Note.tsx Normal file
View File

@@ -0,0 +1,109 @@
import { useSignal, useSignalEffect } from '@preact/signals';
import { useEffect } from 'preact/hooks';
import { RequestBody, ResponseBody } from '/routes/api/notes/save.tsx';
import FilesBreadcrumb from '/components/files/FilesBreadcrumb.tsx';
interface NoteProps {
fileName: string;
currentPath: string;
contents: string;
}
export default function Note({ fileName, currentPath, contents }: NoteProps) {
const saveTimeout = useSignal<ReturnType<typeof setTimeout>>(0);
const hasSavedTimeout = useSignal<ReturnType<typeof setTimeout>>(0);
const isSaving = useSignal<boolean>(false);
const hasSaved = useSignal<boolean>(false);
function saveNote(newNotes: string) {
if (saveTimeout.value) {
clearTimeout(saveTimeout.value);
}
saveTimeout.value = setTimeout(async () => {
hasSaved.value = false;
isSaving.value = true;
try {
const requestBody: RequestBody = { fileName, currentPath, contents: newNotes };
const response = await fetch(`/api/notes/save`, {
method: 'POST',
body: JSON.stringify(requestBody),
});
const result = await response.json() as ResponseBody;
if (!result.success) {
throw new Error('Failed to save note!');
}
} catch (error) {
console.error(error);
}
isSaving.value = false;
hasSaved.value = true;
}, 1000);
}
useSignalEffect(() => {
if (hasSaved.value && !hasSavedTimeout.value) {
hasSavedTimeout.value = setTimeout(() => {
hasSaved.value = false;
}, 3000);
}
});
useEffect(() => {
return () => {
if (saveTimeout.value) {
clearTimeout(saveTimeout.value);
}
if (hasSavedTimeout.value) {
clearTimeout(hasSavedTimeout.value);
}
};
}, []);
return (
<section class='flex flex-col'>
<section class='mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8 w-full flex flex-row items-center justify-start'>
<FilesBreadcrumb path={currentPath} isShowingNotes={true} />
<h3 class='text-base text-white font-semibold'>
<span class='mr-2 text-xs'>/</span>
{decodeURIComponent(fileName)}
</h3>
</section>
<textarea
class='my-2 input-field text-sm font-mono mx-auto max-w-7xl'
onInput={(event) => saveNote(event.currentTarget.value)}
rows={20}
>
{contents}
</textarea>
<span
class={`flex justify-end items-center text-sm mt-1 mx-2 ${
hasSaved.value ? 'text-green-600' : 'text-slate-100'
}`}
>
{isSaving.value
? (
<>
<img src='/images/loading.svg' class='white mr-2' width={18} height={18} />Saving...
</>
)
: null}
{hasSaved.value
? (
<>
<img src='/images/check.svg' class='green mr-2' width={18} height={18} />Saved!
</>
)
: null}
{!isSaving.value && !hasSaved.value ? <>&nbsp;</> : null}
</span>
</section>
);
}