From 802e953a7567bc41e6c1d8aa317ae6b039a31a0d Mon Sep 17 00:00:00 2001 From: Muhammed-Rahif Date: Fri, 27 Dec 2024 22:37:20 +0530 Subject: [PATCH] feat(settings): setup caret style and animation store TODO: functionalities of caret style and animation --- public/click.wav | Bin 0 -> 40492 bytes src/lib/components/EditorTitle.svelte | 2 +- src/lib/components/editor/Editor.svelte | 93 +++++++----------- src/lib/components/editor/FakeCaret.svelte | 58 +++++++++++ .../components/font-dialog/FontDialog.svelte | 2 +- .../font-dialog/FontFamilyCombobox.svelte | 2 +- .../font-dialog/FontSizeCombobox.svelte | 2 +- .../components/menubar/SettingsMenu.svelte | 70 +++++++++++-- src/lib/helpers/github-api.ts | 2 +- src/lib/helpers/menubar/view-options.ts | 2 +- src/lib/helpers/settings.ts | 21 +++- src/lib/helpers/type-sound-player.ts | 75 ++++++++++++++ src/lib/store/storage.ts | 2 +- src/lib/store/store.ts | 2 +- .../types/{EditorTypes.ts => EditorType.ts} | 0 .../{GithubApiTypes.ts => GithubApiType.ts} | 0 .../{SettingsTypes.ts => SettingsType.ts} | 21 ++++ 17 files changed, 276 insertions(+), 78 deletions(-) create mode 100644 public/click.wav create mode 100644 src/lib/components/editor/FakeCaret.svelte create mode 100644 src/lib/helpers/type-sound-player.ts rename src/lib/types/{EditorTypes.ts => EditorType.ts} (100%) rename src/lib/types/{GithubApiTypes.ts => GithubApiType.ts} (100%) rename src/lib/types/{SettingsTypes.ts => SettingsType.ts} (73%) diff --git a/public/click.wav b/public/click.wav new file mode 100644 index 0000000000000000000000000000000000000000..91ba195243f5ae251929d1e6710ff659375114c2 GIT binary patch literal 40492 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 S=mN37@Y6WN*r5X+cJ-!N9ic z=e|(oYqGhBrdW=Cd*EoO(si2cvP;ce^=rf=9zn?rq%OZTrKYI$o^x-Sk&!gSeDZq? zvYyl0K31+bWw8&M!IZiz)v%KNGE3}rT+j+%G{P|rXj-|uyt7_-?;TdgdnBe8kGe5Q zs*T&~ITs?Lw;@3U9$tilN8t4#D1I}cu@^Jb`b0pUgIx%yw8!j|NLCGda#hdSj64jurpYiTz76c$SNKj~igZ zOqem5weSO^8U`zR!I*Y5U^ip^m_GZ8MK#69mXL+Np!-hkttHi4{JxZym;l*_8r2|} zKbHKAVvkN@yRAWsBkntnYFpvUVS4l*m=SUV51`(0n0XZ1Tt=C#&ab9frl8S8&s$I4 z)kel*|VH}W~&9E^o;6FhAV`ft$V)#hrq znL3E}*Yz@MG^5=8q{zL|qjFaFSkXN$Gx`i)`6doT@l`g@U71=W?(oAW|-LJ3YsmMR~;~81Z_9O_m9kI&qUl$J!6@Z->}KPX3>03 zm-pv$b@5pr?pSFQaT$eoes%pieO}L=Tkg3Fc^fA@JFF`_h*u-@-)$Ntl{5drQ@QA# z5XmiS?#mfoy%UD%pLNzW=Kzo{L3e!(wwHu8hUx`>6c$C=ag$2s2)!uwE< zEQ*8wzoyn+i1<4VvKwtfJ(oo+z!25_)x1qM$4kujVspA8VeiOvVn6H^S-I@*J+4{7 z4h}gOf3Qc+&_1`>2WMg57Fz93^BHP++<*<|tQdtiTI^w0?ZQ=uV8(48W^NYMJ$#L; zFesV(ySs8Xs`&^*4No9#*Tr{2!VAnnU_ZBG_?Cf~f#1FV@hW6)cX};FipV9&|2B_ zHV<7MRuXb1w8|wsNVnr@6$xpT1>(b@Uer2#IoEgDpCsrg-aSBC4zME^;PE+h#4-rJ z3ED4&&b#3EdRF2THt%Zkvz?wfAtsTQUj2ve_}%@p$;D)d{~Mg2EWg1!YqZvjWDJ4u zK_cHHQ6KRfRS#{ z_ta3aXb$ur>S-e#TlDY`Qn(h%A2FH}xOuO+y&{T{%6wddxxtG(<@`*P@%}r~a3diN z-?Lc=R!fjV1Xb{M`|1d`NFi(bu2ow&^s^DCa}frBI)0n}5fgKgz#3Xa-ig z{txH2Xm5j2tzz+qxK*V8WAY(cMuH^>(8dscSugT7~VO+MKrdHtTG zS84%}gtsB&+jQ{zQ0EmRY3rI$=cN~Ee2czpY6PLWN^v%HB~O0ZXFVf(Hi1o5jkk&O zulsD6K&AAqNP*{Pamg}r7U<34hb(vS!gaWw5kA)>cTLD!H8>Naw|3&4qtH6kS`1a1 zQjyoA_~$Hi4|Qf!=>H;6D-&5yp>5K@gB%d15M;}cK&=bz300WR(Gyo;>lvIFG~!OU z{HK`>waAVpbY^xK9_KmT_N>%ezu*0j&>bZl6|`O)7ldrC5Zf&7C@0=k9CBxcv5&yW zD!x_o^nmNxNz7I9bsy{x85KqJyDI4kal3-r%ISPbx;qyROM}CLRh-B9%8;@g8dPDo zhbp&K@o7D6)u3C#TL5eFB|>%2$MI=7?UpBrPa0dOADP{~nelLG*EWQ}l}YEL#uRFo zmEa$)t1!OLZbiOom+xAjuf# z=fJJ;kY_xk8O8SO&I)}4zBPqO)mRplpi@pVXulSlIJ|RdiX&8e=>;dcYN3(Vnvwr@ zq6{y%%Pwor2z6g3z^bXN@0kv7Ya_+s9fFH_5_`<}7Bah?U9dyzYv9-lSosfpjq-d# zc2*G>WuFzh<{o@j5M4v&-+B15%WQ`gqjg$1NxH)or(xJ`#|G5gjw8eVn$0+C2Xz12 zJ=fT`!Hb>;A!gvlkR7^!RkIA<*^f^9KPYOQNj}XX7!z^~cIeF^-!4Ph%RXgXLz{!n zhFrgZIj8-7UfkdojM;}n{>5vDaQ1%fh4;nG>s!Pc_nX->Bw;__=O_#1fZ6w! zF_y($qrL3Tm?zxS@=Z^>rIqtI?i_wPi+_U-yN!1rA`SP^v&YT(B_qA0#e)gnDS)r< zXfG=nEed_gk@QL=DZDqf0uJ^b9Y|K0joFk8w}W=|;aXi5ba}EI^5-AJ%XLY?E3Q^! z1`7ShX94(75Mrfb%VwbG@5(ld@T}uJ%&gkE%)2~kYzK_%ADns|7l&$=1+ import { onMount, tick } from 'svelte'; import throttle from 'lodash.throttle'; - import debounce from 'lodash.debounce'; import StatusBar from '@/components/StatusBar.svelte'; import Quill from 'quill'; - import { position as getCaretPosition } from 'caret-pos'; import { Notpad } from '@/helpers/notpad'; import { settings } from '@/store/store'; + import FakeCaret from './FakeCaret.svelte'; + import type { EditorType } from '@/src/lib/types/EditorType'; import 'quill/dist/quill.core.css'; - import type { EditorType } from '@/types/EditorTypes'; - import { RichTextEditor } from '@/helpers/rich-text-editor'; import './Editor.css'; interface Props { @@ -18,23 +16,43 @@ let { editor }: Props = $props(); let editorContainer: HTMLDivElement = $state(null!); - let quill: Quill | undefined = $state(); - let fakeCaret: HTMLSpanElement | null = $state(null); - let caretPosition = $state({ top: 10, left: 8, height: 24 }); let lineNo = $state(0); let caretLineNo = $state(1); let caretColumnNo = $state(1); let characterCount = $state(0); let wordCount = $state(0); + let quill = $derived(editor.quill!); + let fakeCaret: FakeCaret = $state(null!); async function setupQuill() { - const richTextEditor = new RichTextEditor({ - editorContainer - }); - quill = richTextEditor.quill; - - await Notpad.editors.setQuill(editor.id, quill); - quill.setContents(Notpad.editors.getContent(editor.id)!); + const options = { + formats: [ + 'bold', + 'code', + 'italic', + 'link', + 'size', + 'strike', + 'script', + 'underline', + 'blockquote', + 'header', + 'indent', + 'list', + 'align', + 'direction', + 'code-block', + 'formula' + // 'background', + // 'font', + // 'image', + // 'video' + ] + }; + + editor.quill = new Quill(editorContainer, options); + await Notpad.editors.setQuill(editor.id, editor.quill); + editor.quill.setContents(Notpad.editors.getContent(editor.id)!); } /** @@ -58,39 +76,7 @@ .trim() .split(/\s+/) .filter((word) => word).length; // \s+ is RegEx for whitespace characters. - }, 300); - - const resumeFakeCaretBlink = debounce(function () { - if (fakeCaret) fakeCaret.classList.add('animate-caret-blink'); - }, 1000); - - // Using requestAnimationFrame for smooth updates - const updateCaretPosition = throttle(() => { - if (!quill) return; - // console.count('Update caret position'); - - requestAnimationFrame(async () => { - if (fakeCaret) fakeCaret.classList.remove('animate-caret-blink'); - - if (!quill) return; - if (editorContainer) { - try { - const caret = getCaretPosition(quill.root); - - // Adjust for the scroll position - caretPosition = { - top: caret.top - editorContainer.scrollTop, - left: caret.left - editorContainer.scrollLeft, - height: caret.height - }; - } catch (error) { - console.error(error); - } - } - - resumeFakeCaretBlink(); - }); - }); + }, 150); $effect(() => { if ($settings) { @@ -108,7 +94,7 @@ const updateCaretPosAndEditorData = async () => { updateEditorData(); await tick(); - updateCaretPosition(); + fakeCaret.updateCaretPosition(); }; onMount(async () => { @@ -141,16 +127,7 @@ bind:this={editorContainer} > - + diff --git a/src/lib/components/editor/FakeCaret.svelte b/src/lib/components/editor/FakeCaret.svelte new file mode 100644 index 0000000..bd05562 --- /dev/null +++ b/src/lib/components/editor/FakeCaret.svelte @@ -0,0 +1,58 @@ + + + diff --git a/src/lib/components/font-dialog/FontDialog.svelte b/src/lib/components/font-dialog/FontDialog.svelte index 20b9b4e..9eaa0e8 100644 --- a/src/lib/components/font-dialog/FontDialog.svelte +++ b/src/lib/components/font-dialog/FontDialog.svelte @@ -7,7 +7,7 @@ import FontSizeCombobox from './FontSizeCombobox.svelte'; import { Label } from '@/components/ui/label'; import * as Card from '@/components/ui/card'; - import { FontFamily, FontSize } from '@/types/SettingsTypes'; + import { FontFamily, FontSize } from '@/src/lib/types/SettingsType'; import { get } from 'svelte/store'; const currentSettings = get(settings); diff --git a/src/lib/components/font-dialog/FontFamilyCombobox.svelte b/src/lib/components/font-dialog/FontFamilyCombobox.svelte index 65413ce..fd53fd4 100644 --- a/src/lib/components/font-dialog/FontFamilyCombobox.svelte +++ b/src/lib/components/font-dialog/FontFamilyCombobox.svelte @@ -9,7 +9,7 @@ import { get } from 'svelte/store'; import CheckIcon from '@/components/icons/Check.svelte'; import ChevronsUpDownIcon from '@/components/icons/ChevronsUpDown.svelte'; - import { FontFamily } from '@/types/SettingsTypes'; + import { FontFamily } from '@/src/lib/types/SettingsType'; interface Props { value: FontFamily; diff --git a/src/lib/components/font-dialog/FontSizeCombobox.svelte b/src/lib/components/font-dialog/FontSizeCombobox.svelte index 9ea2483..18b62be 100644 --- a/src/lib/components/font-dialog/FontSizeCombobox.svelte +++ b/src/lib/components/font-dialog/FontSizeCombobox.svelte @@ -9,7 +9,7 @@ import { cn } from '@/utils'; import { settings } from '@/store/store'; import { get } from 'svelte/store'; - import { FontSize } from '@/types/SettingsTypes'; + import { FontSize } from '@/src/lib/types/SettingsType'; interface Props { value: FontSize; diff --git a/src/lib/components/menubar/SettingsMenu.svelte b/src/lib/components/menubar/SettingsMenu.svelte index 7d06760..3d95b2f 100644 --- a/src/lib/components/menubar/SettingsMenu.svelte +++ b/src/lib/components/menubar/SettingsMenu.svelte @@ -6,6 +6,8 @@ import GithubOultineIcon from '@/components/icons/GithubOultine.svelte'; import ALargeSmallIcon from '@/components/icons/ALargeSmall.svelte'; import { Notpad } from '@/helpers/notpad'; + import { CaretAnimation, CaretStyle } from '@/types/SettingsType'; + import { settings } from '@/store/store'; @@ -30,21 +32,69 @@ Sound - None - Click - Pop - Typewriter + + Effect + + None + Click + Pop + Typewriter + + + + + Volume + + 100% + 75% + 50% + 25% + + + + + Vibration + + None + Low + Medium + High + Strong + + - Vibration + Caret - None - Low - Medium - High - Strong + + Style + + {#each Object.values(CaretStyle) as caretStyle} + Notpad.settings.updateCaret({ style: caretStyle })} + > + {caretStyle} + + {/each} + + + + + Animation + + {#each Object.values(CaretAnimation) as caretAnimation} + Notpad.settings.updateCaret({ animation: caretAnimation })} + > + {caretAnimation} + + {/each} + + diff --git a/src/lib/helpers/github-api.ts b/src/lib/helpers/github-api.ts index cf4632d..5c6331a 100644 --- a/src/lib/helpers/github-api.ts +++ b/src/lib/helpers/github-api.ts @@ -1,4 +1,4 @@ -import type { ContributorType, ReleasesType } from '@/types/GithubApiTypes'; +import type { ContributorType, ReleasesType } from '@/src/lib/types/GithubApiType'; export class GithubApi { public async getAppLicense(): Promise { diff --git a/src/lib/helpers/menubar/view-options.ts b/src/lib/helpers/menubar/view-options.ts index df1f4b1..fcc019f 100644 --- a/src/lib/helpers/menubar/view-options.ts +++ b/src/lib/helpers/menubar/view-options.ts @@ -1,7 +1,7 @@ import { get } from 'svelte/store'; import { settings } from '@/store/store'; import { Notpad } from '@/helpers/notpad'; -import type { SettingsType } from '@/types/SettingsTypes'; +import type { SettingsType } from '@/src/lib/types/SettingsType'; export class ViewOptions { public toggleStatusBar() { diff --git a/src/lib/helpers/settings.ts b/src/lib/helpers/settings.ts index eb010da..02bb7c7 100644 --- a/src/lib/helpers/settings.ts +++ b/src/lib/helpers/settings.ts @@ -1,5 +1,11 @@ import { settings } from '@/store/store'; -import { FontFamily, FontSize, type SettingsType } from '@/types/SettingsTypes'; +import { + CaretAnimation, + CaretStyle, + FontFamily, + FontSize, + type SettingsType +} from '@/types/SettingsType'; export class Settings { static defaultSettings: SettingsType = { @@ -8,7 +14,11 @@ export class Settings { fontFamily: FontFamily.SUSE, fontSize: FontSize.Size16, lineNumbers: false, - wrapLines: true + wrapLines: true, + caret: { + animation: CaretAnimation.Medium, + style: CaretStyle.VerticalBar + } }; setFontFamily(fontFamily: FontFamily) { @@ -18,4 +28,11 @@ export class Settings { setFontSize(fontSize: FontSize) { settings.update((value) => ({ ...value, fontSize })); } + + updateCaret(caret: Partial) { + settings.update((value) => ({ + ...value, + caret: { ...value.caret, ...caret } + })); + } } diff --git a/src/lib/helpers/type-sound-player.ts b/src/lib/helpers/type-sound-player.ts new file mode 100644 index 0000000..8af992a --- /dev/null +++ b/src/lib/helpers/type-sound-player.ts @@ -0,0 +1,75 @@ +class TypeSoundPlayer { + private audioContext: AudioContext; + private audioUrl: string; + private audioBuffer: AudioBuffer | null = null; + private audioReady: Promise; + + constructor(audioUrl: string) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); + this.audioUrl = audioUrl; + this.audioReady = this.loadAudio(); + } + + private async loadAudio(): Promise { + try { + const response = await fetch(this.audioUrl); + const arrayBuffer = await response.arrayBuffer(); + this.audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer); + console.log('Audio loaded successfully!'); + } catch (error) { + console.error('Error loading audio:', error); + } + } + + private calculatePan(key: string): number { + // Map the keyboard layout to a pan value + const leftKeys = 'qwertasdfgzxcvb'; // Example keys for the left side + const rightKeys = 'yuiophjkl;nm'; // Example keys for the right side + + if (leftKeys.includes(key.toLowerCase())) { + return -1; // Full left + } else if (rightKeys.includes(key.toLowerCase())) { + return 1; // Full right + } else { + return 0; // Center for unsupported keys + } + } + + public async play(key: string): Promise { + await this.audioReady; // Ensure audio is loaded + if (!this.audioBuffer) { + console.warn('Audio not loaded yet!'); + return; + } + + // Create a buffer source for each play + const source = this.audioContext.createBufferSource(); + source.buffer = this.audioBuffer; + + // Create a stereo panner + const panner = this.audioContext.createStereoPanner(); + panner.pan.value = this.calculatePan(key); + + // Connect the source to the panner and the destination + source.connect(panner).connect(this.audioContext.destination); + + // Start the sound + source.start(0); + } + + public resumeAudioContext(): void { + if (this.audioContext.state === 'suspended') { + this.audioContext.resume(); + } + } +} + +// Instantiate TypeSoundPlayer once and use it globally +const soundPlayer = new TypeSoundPlayer('http://localhost:5173/Notpad/click.wav'); + +// Event Listener +window.addEventListener('keydown', (event: KeyboardEvent): void => { + soundPlayer.resumeAudioContext(); + soundPlayer.play(event.key); +}); diff --git a/src/lib/store/storage.ts b/src/lib/store/storage.ts index c40cf98..92eceb7 100644 --- a/src/lib/store/storage.ts +++ b/src/lib/store/storage.ts @@ -1,7 +1,7 @@ import localforage from 'localforage'; import { activeTabId, editors, settings } from '@/store/store'; import type { EditorType } from '@/types/EditorTypes'; -import type { SettingsType } from '@/types/SettingsTypes'; +import type { SettingsType } from '@/src/lib/types/SettingsType'; import { Editors } from '@/helpers/editors'; import { Settings } from '@/helpers/settings'; import merge from 'lodash.merge'; diff --git a/src/lib/store/store.ts b/src/lib/store/store.ts index df92f0e..b5c864e 100644 --- a/src/lib/store/store.ts +++ b/src/lib/store/store.ts @@ -1,6 +1,6 @@ import { get, writable } from 'svelte/store'; import type { EditorType } from '@/types/EditorTypes'; -import { type SettingsType } from '@/types/SettingsTypes'; +import { type SettingsType } from '@/src/lib/types/SettingsType'; import { Settings } from '@/helpers/settings'; import { Editors } from '@/helpers/editors'; diff --git a/src/lib/types/EditorTypes.ts b/src/lib/types/EditorType.ts similarity index 100% rename from src/lib/types/EditorTypes.ts rename to src/lib/types/EditorType.ts diff --git a/src/lib/types/GithubApiTypes.ts b/src/lib/types/GithubApiType.ts similarity index 100% rename from src/lib/types/GithubApiTypes.ts rename to src/lib/types/GithubApiType.ts diff --git a/src/lib/types/SettingsTypes.ts b/src/lib/types/SettingsType.ts similarity index 73% rename from src/lib/types/SettingsTypes.ts rename to src/lib/types/SettingsType.ts index 891d8ad..a6dca72 100644 --- a/src/lib/types/SettingsTypes.ts +++ b/src/lib/types/SettingsType.ts @@ -32,6 +32,20 @@ export enum FontSize { Size72 = 72 } +export enum CaretAnimation { + Slow = 'Slow', + Medium = 'Medium', + Fast = 'Fast', + Off = 'Off' +} + +export enum CaretStyle { + VerticalBar = 'Vertical Bar ( | )', + Block = 'Block ( ▮ )', + HollowBlock = 'Hollow Block ( ▯ )', + Underline = 'Underline ( _ )' +} + /** * Interface representing the settings for the application. */ @@ -60,4 +74,11 @@ export interface SettingsType { * Should wrap long lines. */ wrapLines: boolean; + /** + * Caret's specific configs. Style and Animation. + */ + caret: { + style: CaretStyle; + animation: CaretAnimation; + }; }