diff --git a/package.json b/package.json index e3bc0fc..0f6fb35 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "cert": "rm -rf .certs && mkdir -p .certs && mkcert -key-file ./.certs/key.pem -cert-file ./.certs/cert.pem 'localhost' '::1' '192.168.1.2' " + "cert": "rm -rf .certs && mkdir -p .certs && mkcert -key-file ./.certs/key.pem -cert-file ./.certs/cert.pem 'localhost' '::1' '192.168.1.2' ", + "deploy": "scp -r $(pwd)/* nicolas-choquet@ssh-nicolas-choquet.alwaysdata.net:/home/nicolas-choquet/www/portfolio-apple/" }, "dependencies": { "@vitejs/plugin-basic-ssl": "^1.0.2", @@ -25,7 +26,7 @@ "@types/uuid": "^9.0.7", "@vitejs/plugin-vue": "^4.5.0", "sass": "^1.69.5", - "vite": "^5.0.0", + "vite": "^5.0.3", "vite-plugin-pwa": "^0.17.2", "vue-tsc": "^1.8.25" } diff --git a/src/hooks/kernel/clipboard/index.ts b/src/hooks/kernel/clipboard/index.ts new file mode 100644 index 0000000..c2356b2 --- /dev/null +++ b/src/hooks/kernel/clipboard/index.ts @@ -0,0 +1,42 @@ +import {useClipboard as useClipboardFromVueUse} from '@vueuse/core'; +import type {UseClipboardOptions} from '@vueuse/core'; +import {ref} from 'vue'; +import type {Ref} from 'vue'; + +const copiedDuring = ref(1500); +const textIfNotSupported = ref(''); +const copiedIfNotSupported = ref(false); +const copyIfNotSupported = async (text?: string|undefined) => { + textIfNotSupported.value = text ?? ''; + copiedIfNotSupported.value = true; + + setTimeout(() => { + textIfNotSupported.value = ''; + copiedIfNotSupported.value = false; + }, copiedDuring.value) +}; + +export const useClipboard = (options?: Pick>, 'copiedDuring'>) => { + const source = ref(''); + if (options?.copiedDuring) (copiedDuring.value = options.copiedDuring); + + const { + isSupported, copy, + text, copied + } = useClipboardFromVueUse({ + read: true, + source, + legacy: true, + copiedDuring: copiedDuring.value + }); + + return isSupported.value ? { + copy, + text, + copied + } : { + copy: copyIfNotSupported, + text: textIfNotSupported, + copied: copiedIfNotSupported + }; +} \ No newline at end of file diff --git a/src/hooks/kernel/filesystem/functions.ts b/src/hooks/kernel/filesystem/functions.ts new file mode 100644 index 0000000..30ecca1 --- /dev/null +++ b/src/hooks/kernel/filesystem/functions.ts @@ -0,0 +1,21 @@ +import {fsTree} from './index'; +import type {Directory, File} from './types'; + +export const directoryHasChildren = (directory: Directory): boolean => + fsTree.value.filter(i => + i.path.startsWith(directory.path + '/' + directory.name) + ).length >=1; +export const directoryExists = (directory: Directory): boolean => + fsTree.value.filter(i => + i.type === 'directory' && + i.path === directory.path && + i.name === directory.name + ).length === 1; + +export const fileExists = (file: File): boolean => + fsTree.value.filter(i => + i.type === 'file' && + i.path === file.path && + i.name === file.name && + i.extension === file.extension + ).length === 1; \ No newline at end of file diff --git a/src/hooks/kernel/filesystem/index.ts b/src/hooks/kernel/filesystem/index.ts new file mode 100644 index 0000000..97dd16c --- /dev/null +++ b/src/hooks/kernel/filesystem/index.ts @@ -0,0 +1,74 @@ +import {computed, ComputedRef, onMounted, Ref, ref, watch, watchEffect} from 'vue'; +import type { + FSTree, + UseDirectories, UseDirectoriesReturn, + UseFiles, + UseFilesReturn, + UseFileSystem, + UseFolders, + UseFoldersReturn +} from './types'; +import initMocked, * as mockedFS from './mocked'; +import initPersistent, * as persistentFS from './persistent'; + +export const fsTree = ref([]); +const asMock = ref(false); + +watch(fsTree, (fsTree) => { + console.log(fsTree) +}); + +export const useMock = (mock: Ref|T) => { + if (typeof mock === 'object') { + watchEffect(() => { + asMock.value = mock.value + }); + } + else { + asMock.value = mock; + } +}; + +export const useFileSystem: UseFileSystem = + ({ mocked = false } = {}) => { + if (mocked !== asMock.value) { + asMock.value = mocked; + } + + onMounted(() => { + if (mocked) { + initMocked(); + } + else { + initPersistent(); + } + }) + + return mocked ? mockedFS : persistentFS; + }; + +export const useFSTree = (): ComputedRef => computed(() => fsTree.value); +export const useFiles: UseFiles = () => { + const { file } = useFileSystem({mocked: asMock.value}); + + return Array.from(Object.keys(file)).reduce((r, k) => ({ + ...r, + [`${k}File`]: file[k as keyof typeof file] + }), {} as UseFilesReturn); +}; +export const useFolders: UseFolders = () => { + const { directory } = useFileSystem({mocked: asMock.value}); + + return Array.from(Object.keys(directory)).reduce((r, k) => ({ + ...r, + [`${k}Folder`]: directory[k as keyof typeof directory] + }), {} as UseFoldersReturn); +}; +export const useDirectories: UseDirectories = () => { + const folders = useFolders(); + + return Array.from(Object.keys(folders)).reduce((r, k) => ({ + ...r, + [k.replace('Folder', 'Directory')]: folders[k as keyof typeof folders] + }), {} as UseDirectoriesReturn); +}; \ No newline at end of file diff --git a/src/hooks/kernel/filesystem/mimeTypes.ts b/src/hooks/kernel/filesystem/mimeTypes.ts new file mode 100644 index 0000000..bc27553 --- /dev/null +++ b/src/hooks/kernel/filesystem/mimeTypes.ts @@ -0,0 +1,91 @@ +/** + * @author nicolachoqyet06250 + * + * use this lib for uncompress archive in javascript from web worker : + * https://github.com/nika-begiashvili/libarchivejs?ref=hackernoon.com + * + * use this lib for compress archives in javascript : + * https://github.com/Stuk/jszip + * + * use this lib for read word (docx) documents in HTML : + * https://github.com/scuderia-fe/docx-to-html + * + * invert, use this lib : + * https://github.com/caiyexiang/html-docx-js-typescript + * + * for convert excels documents to html and invert, use this lib : + * https://docs.sheetjs.com/ + * + * @source https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + */ + +export default { + // images + png: 'image/png', + apng: 'image/apng', + avif: 'image/avif', + gif: 'image/gif', + ico: 'image/vnd.microsoft.icon', + jpeg: 'image/jpeg', + jpg: 'image/jpeg', + svg: 'image/svg+xml', + webp: 'image/webp', + + // audios + mp3: 'audio/mpeg', + oga: 'audio/ogg', + opus: 'audio/opus', + wav: 'audio/wav', + weba: 'audio/webm', + + // video + mp4: 'video/mp4', + mpeg: 'video/mpeg', + ogv: 'video/ogg', + webm: 'video/webm', + + // audio - video mix + '3gp': [ + 'video/3gpp', + 'audio/3gpp' + ], + '3g2': [ + 'video/3gpp2', + 'audio/3gpp2' + ], + + // archives + '7z': 'application/x-7z-compressed', + bz: 'application/x-bzip', + bz2: 'application/x-bzip2', + gz: 'application/gzip', + rar: 'application/vnd.rar', + tar: 'application/x-tar', + + // applications + ods: 'application/vnd.oasis.opendocument.spreadsheet', + odt: 'application/vnd.oasis.opendocument.text', + arc: 'application/x-freearc', + csh: 'application/x-csh', + sh: 'application/x-sh', + docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + json: 'application/json', + jsonld: 'application/ld+json', + pdf: 'application/pdf', + php: 'application/x-httpd-php', + rtf: 'application/rtf', + xhtml: 'application/xhtml+xml', + xml: 'application/xml', + zip: 'application/zip', + + // text + css: 'text/css', + csv: 'text/csv', + html: 'text/html', + htm: 'text/html', + ics: 'text/calendar', + js: 'text/javascript', + mjs: 'text/javascript', + txt: 'text/plain', +} as const; diff --git a/src/hooks/kernel/filesystem/mocked/directory.ts b/src/hooks/kernel/filesystem/mocked/directory.ts new file mode 100644 index 0000000..f2b8d45 --- /dev/null +++ b/src/hooks/kernel/filesystem/mocked/directory.ts @@ -0,0 +1,136 @@ +import type { + DirectoryItem, + UseFileSystemReturn +} from '../types.ts'; + +import {fsTree} from '../index'; +import {directoryExists, directoryHasChildren} from '@/hooks/kernel/filesystem/functions.ts'; + +export const create: UseFileSystemReturn['directory']['create'] = + (directory, force = false) => { + const pathExists = fsTree.value.filter(i => { + const path = directory.path.split('/'); + const name = path.pop(); + return i.type === 'directory' + && i.path === path.join('/') + && i.name === name; + }).length === 1; + + const directoryExists = fsTree.value.filter(i => + i.type === 'directory' && + i.path === directory.path && + i.name === directory.name + ).length === 1; + + if (!force && (!pathExists || !directoryExists)) { + throw Error(`impossible de créer le répertoire «${directory.path}/${directory.name}»: Aucun fichier ou dossier de ce type`); + } + + if (force && (!pathExists || !directoryExists)) { + let iterationPath = ''; + const rootPath = '/'; + for (let i = 0; i < directory.path.split('/').length; i++) { + iterationPath += '/' + directory.path.split('/')[i]; + iterationPath = (iterationPath.startsWith('//') ? iterationPath.substring(1) : iterationPath); + + const path = iterationPath.split('/'); + path.shift() + const name = path.pop(); + const joinedPath = '/' + path.join('/'); + + const iterationPathExists = fsTree.value.filter(i => + i.type === 'directory' && + ( + ( + iterationPath === rootPath && + i.path === '' && i.name === rootPath + ) || + ( + i.path === joinedPath + && i.name === name + ) + ) + ).length === 1; + + if (!iterationPathExists) { + const path = iterationPath.split('/'); + path.shift() + const name = path.pop(); + + const item: DirectoryItem = { + type: 'directory', + path: '/' + path.join('/'), + name: name! + }; + + fsTree.value = [...fsTree.value, item] + } + } + } + + if (pathExists && !directoryExists) { + const item: DirectoryItem = { + type: 'directory', + ...directory + }; + + fsTree.value = [...fsTree.value, item] + } + } + +export const remove: UseFileSystemReturn['directory']['remove'] = + (directory, force = false) => { + if (!directoryExists(directory) && !force) { + throw new Error(`impossible de supprimer '/${directory.path}/${directory.name}': Aucun fichier ou dossier de ce type`); + } + + const hasChildren = directoryHasChildren(directory); + if (hasChildren) { + if (!force) { + throw new Error(`impossible de supprimer '${directory.path}/${directory.name}/': est un dossier`) + } + + fsTree.value = fsTree.value.filter(i => + !i.path.startsWith(directory.path + '/' + directory.name) + ) + } + + fsTree.value = fsTree.value.filter(i => + i.type === 'file' || + ( + i.path !== directory.path || + i.name !== directory.name + ) + ) + }; + +export const rename: UseFileSystemReturn['directory']['rename'] = + (from, to) => { + if (!directoryExists(from)) { + throw new Error(`impossible d'évaluer '${from.path}/${from.name}': Aucun fichier ou dossier de ce type`) + } + + const toPath = to.path.split('/'); + const toName = toPath.pop(); + const fromPath = from.path.split('/'); + fromPath.pop(); + + if (!directoryExists({ + path: toPath.join('/'), + name: toName! + }) && toPath.length > fromPath.length) { + throw new Error(`impossible de déplacer '${from.path}/${from.name}/' vers '${to.path}/${to.name}': Aucun fichier ou dossier de ce type`) + } + + fsTree.value = fsTree.value.map(i => i.path.startsWith(from.path + '/' + from.name) ? { + ...i, + path: to.path + '/' + to.name + i.path.substring((from.path + '/' + from.name).length) + } : i); + + fsTree.value = fsTree.value.map(i => + i.type === 'directory' && + i.path === from.path && + i.name === from.name ? + { ...i, ...to } : i + ); + }; diff --git a/src/hooks/kernel/filesystem/mocked/file.ts b/src/hooks/kernel/filesystem/mocked/file.ts new file mode 100644 index 0000000..28894ff --- /dev/null +++ b/src/hooks/kernel/filesystem/mocked/file.ts @@ -0,0 +1,98 @@ +import type {FileItem, UseFileSystemReturn} from '../types'; +import {fsTree} from '../index'; +import {create as createDirectory} from './directory'; +import {directoryExists, fileExists} from '../functions'; + +export const create: UseFileSystemReturn['file']['create'] = + (file, force = false) => { + const fileExists = fsTree.value.filter(i => + i.type === 'file' && + i.path === file.path && + i.name === file.name && + i.extension === file.extension + ).length === 1; + + const directoryPath = file.path.split('/'); + const directoryName = directoryPath.pop(); + + createDirectory({ + path: directoryPath.join('/'), + name: directoryName! + }, force); + + const pathExists = fsTree.value.filter(i => { + const path = file.path.split('/'); + const name = path.pop(); + return i.type === 'directory' + && i.path === path.join('/') + && i.name === name; + }).length === 1; + + const item: FileItem = { + type: 'file', + ...file, + content: '' + }; + + if (pathExists && !fileExists) { + fsTree.value = [...fsTree.value, item] + } + }; + +export const remove: UseFileSystemReturn['file']['remove'] = + (file, force = false) => { + if (!fileExists(file) && !force) { + throw new Error(`impossible de supprimer '${file.path}/${file.name}.${file.extension}': Aucun fichier ou dossier de ce type`) + } + + fsTree.value = fsTree.value.filter(i => + i.type === 'directory' || + ( + i.path !== file.path || + i.name !== file.name || + i.extension !== file.extension + ) + ) + }; + +export const rename: UseFileSystemReturn['file']['rename'] = + (from, to) => { + if (!fileExists(from)) { + throw new Error(`impossible d'évaluer '${from.path}/${from.name}.${from.extension}': Aucun fichier ou dossier de ce type`) + } + + const toPath = to.path.split('/'); + const toName = toPath.pop(); + const fromPath = from.path.split('/'); + fromPath.pop(); + + if (!directoryExists({ + path: toPath.join('/'), + name: toName! + })) { + throw new Error(`impossible de déplacer '${from.path}/${from.name}.${from.extension}' vers '${to.path}/${to.name}.${to.extension}': Aucun fichier ou dossier de ce type`) + } + + fsTree.value = fsTree.value.map(i => + i.type === 'file' && + i.path === from.path && + i.name === from.name && + i.extension === from.extension ? + { ...i, ...to } : i + ); + }; + +export const update: UseFileSystemReturn['file']['update'] = + (file, content = '') => { + if (!fileExists(file)) { + create(file); + } + + fsTree.value = fsTree.value.map(i => + i.type === 'file' && + i.path === file.path && + i.name === file.name && + i.extension === file.extension ? + { ...i, content } : i + ) + }; \ No newline at end of file diff --git a/src/hooks/kernel/filesystem/mocked/index.ts b/src/hooks/kernel/filesystem/mocked/index.ts new file mode 100644 index 0000000..32bb94a --- /dev/null +++ b/src/hooks/kernel/filesystem/mocked/index.ts @@ -0,0 +1,24 @@ +export * as file from './file'; +export * as directory from './directory'; +import {fsTree} from '../../filesystem'; + +// mock init +export default () => { + fsTree.value = [ + { + type: 'directory', + path: '', + name: '/' + }, + { + type: 'directory', + path: '/', + name: 'test' + }, + { + type: 'directory', + path: '/test', + name: 'toto' + }, + ]; +} \ No newline at end of file diff --git a/src/hooks/kernel/filesystem/persistent/directory.ts b/src/hooks/kernel/filesystem/persistent/directory.ts new file mode 100644 index 0000000..608d56d --- /dev/null +++ b/src/hooks/kernel/filesystem/persistent/directory.ts @@ -0,0 +1,132 @@ +import type {DirectoryItem, UseFileSystemReturn} from '../types'; +import {fsTree} from '../index'; +import {directoryExists, directoryHasChildren} from '../functions'; + +export const create: UseFileSystemReturn['directory']['create'] = + (directory, force = false) => { + const pathExists = fsTree.value.filter(i => { + const path = directory.path.split('/'); + const name = path.pop(); + return i.type === 'directory' + && i.path === path.join('/') + && i.name === name; + }).length === 1; + + const directoryExists = fsTree.value.filter(i => + i.type === 'directory' && + i.path === directory.path && + i.name === directory.name + ).length === 1; + + if (!force && (!pathExists || !directoryExists)) { + throw Error(`impossible de créer le répertoire «${directory.path}/${directory.name}»: Aucun fichier ou dossier de ce type`); + } + + if (force && (!pathExists || !directoryExists)) { + let iterationPath = ''; + const rootPath = '/'; + for (let i = 0; i < directory.path.split('/').length; i++) { + iterationPath += '/' + directory.path.split('/')[i]; + iterationPath = (iterationPath.startsWith('//') ? iterationPath.substring(1) : iterationPath); + + const path = iterationPath.split('/'); + path.shift() + const name = path.pop(); + const joinedPath = '/' + path.join('/'); + + const iterationPathExists = fsTree.value.filter(i => + i.type === 'directory' && + ( + ( + iterationPath === rootPath && + i.path === '' && i.name === rootPath + ) || + ( + i.path === joinedPath + && i.name === name + ) + ) + ).length === 1; + + if (!iterationPathExists) { + const path = iterationPath.split('/'); + path.shift() + const name = path.pop(); + + const item: DirectoryItem = { + type: 'directory', + path: '/' + path.join('/'), + name: name! + }; + + fsTree.value = [...fsTree.value, item] + } + } + } + + if (pathExists && !directoryExists) { + const item: DirectoryItem = { + type: 'directory', + ...directory + }; + + fsTree.value = [...fsTree.value, item] + } + } + +export const remove: UseFileSystemReturn['directory']['remove'] = + (directory, force = false) => { + if (!directoryExists(directory) && !force) { + throw new Error(`impossible de supprimer '/${directory.path}/${directory.name}': Aucun fichier ou dossier de ce type`); + } + + const hasChildren = directoryHasChildren(directory); + if (hasChildren) { + if (!force) { + throw new Error(`impossible de supprimer '${directory.path}/${directory.name}/': est un dossier`) + } + + fsTree.value = fsTree.value.filter(i => + !i.path.startsWith(directory.path + '/' + directory.name) + ) + } + + fsTree.value = fsTree.value.filter(i => + i.type === 'file' || + ( + i.path !== directory.path || + i.name !== directory.name + ) + ) + }; + +export const rename: UseFileSystemReturn['directory']['rename'] = + (from, to) => { + if (!directoryExists(from)) { + throw new Error(`impossible d'évaluer '${from.path}/${from.name}': Aucun fichier ou dossier de ce type`) + } + + const toPath = to.path.split('/'); + const toName = toPath.pop(); + const fromPath = from.path.split('/'); + fromPath.pop(); + + if (!directoryExists({ + path: toPath.join('/'), + name: toName! + }) && toPath.length > fromPath.length) { + throw new Error(`impossible de déplacer '${from.path}/${from.name}/' vers '${to.path}/${to.name}': Aucun fichier ou dossier de ce type`) + } + + fsTree.value = fsTree.value.map(i => i.path.startsWith(from.path + '/' + from.name) ? { + ...i, + path: to.path + '/' + to.name + i.path.substring((from.path + '/' + from.name).length) + } : i); + + fsTree.value = fsTree.value.map(i => + i.type === 'directory' && + i.path === from.path && + i.name === from.name ? + { ...i, ...to } : i + ); + }; diff --git a/src/hooks/kernel/filesystem/persistent/file.ts b/src/hooks/kernel/filesystem/persistent/file.ts new file mode 100644 index 0000000..5f7c35f --- /dev/null +++ b/src/hooks/kernel/filesystem/persistent/file.ts @@ -0,0 +1,98 @@ +import type {FileItem, UseFileSystemReturn} from '../types.ts'; +import {fsTree} from '../index.ts'; +import {create as createDirectory} from './directory.ts'; +import {directoryExists, fileExists} from '@/hooks/kernel/filesystem/functions.ts'; + +export const create: UseFileSystemReturn['file']['create'] = + (file, force = false) => { + const fileExists = fsTree.value.filter(i => + i.type === 'file' && + i.path === file.path && + i.name === file.name && + i.extension === file.extension + ).length === 1; + + const directoryPath = file.path.split('/'); + const directoryName = directoryPath.pop(); + + createDirectory({ + path: directoryPath.join('/'), + name: directoryName! + }, force); + + const pathExists = fsTree.value.filter(i => { + const path = file.path.split('/'); + const name = path.pop(); + return i.type === 'directory' + && i.path === path.join('/') + && i.name === name; + }).length === 1; + + const item: FileItem = { + type: 'file', + ...file, + content: '' + }; + + if (pathExists && !fileExists) { + fsTree.value = [...fsTree.value, item] + } + }; + +export const remove: UseFileSystemReturn['file']['remove'] = + (file, force = false) => { + if (!fileExists(file) && !force) { + throw new Error(`impossible de supprimer '${file.path}/${file.name}.${file.extension}': Aucun fichier ou dossier de ce type`) + } + + fsTree.value = fsTree.value.filter(i => + i.type === 'directory' || + ( + i.path !== file.path || + i.name !== file.name || + i.extension !== file.extension + ) + ) + }; + +export const rename: UseFileSystemReturn['file']['rename'] = + (from, to) => { + if (!fileExists(from)) { + throw new Error(`impossible d'évaluer '${from.path}/${from.name}.${from.extension}': Aucun fichier ou dossier de ce type`) + } + + const toPath = to.path.split('/'); + const toName = toPath.pop(); + const fromPath = from.path.split('/'); + fromPath.pop(); + + if (!directoryExists({ + path: toPath.join('/'), + name: toName! + })) { + throw new Error(`impossible de déplacer '${from.path}/${from.name}.${from.extension}' vers '${to.path}/${to.name}.${to.extension}': Aucun fichier ou dossier de ce type`) + } + + fsTree.value = fsTree.value.map(i => + i.type === 'file' && + i.path === from.path && + i.name === from.name && + i.extension === from.extension ? + { ...i, ...to } : i + ); + }; + +export const update: UseFileSystemReturn['file']['update'] = + (file, content = '') => { + if (!fileExists(file)) { + create(file); + } + + fsTree.value = fsTree.value.map(i => + i.type === 'file' && + i.path === file.path && + i.name === file.name && + i.extension === file.extension ? + { ...i, content } : i + ) + }; \ No newline at end of file diff --git a/src/hooks/kernel/filesystem/persistent/index.ts b/src/hooks/kernel/filesystem/persistent/index.ts new file mode 100644 index 0000000..abe66e4 --- /dev/null +++ b/src/hooks/kernel/filesystem/persistent/index.ts @@ -0,0 +1,16 @@ +import type {FSTree} from '../../filesystem/types'; + +export * as file from './file'; +export * as directory from './directory'; +import {fsTree} from '../../filesystem'; + +// init persistent +export default () => { + fsTree.value = JSON.parse(localStorage.getItem('os-tree') ?? JSON.stringify([ + { + type: 'directory', + path: '', + name: '/' + }, + ])) as FSTree; +} \ No newline at end of file diff --git a/src/hooks/kernel/filesystem/types.ts b/src/hooks/kernel/filesystem/types.ts new file mode 100644 index 0000000..c3293d4 --- /dev/null +++ b/src/hooks/kernel/filesystem/types.ts @@ -0,0 +1,48 @@ +export type FileSystemOptions = { + mocked?: boolean +}; + +export type FileItem = { + type: 'file', + path: string, + name: string, + extension: string, + content: string|Blob|Array +}; +export type DirectoryItem = { type: 'directory' } & Pick; +export type FSTree = Array; + +export type File = { + path: string, + name: string, + extension: string +}; +export type Directory = Omit; + +export type UseFileSystemReturn = { + file: { + create(file: File, force?: boolean): void, + remove(file: File, force?: boolean): void, + rename(from: File, to: File): void, + update(file: File, content?: string): void, + }, + directory: { + create(directory: Directory, force?: boolean): void, + remove(directory: Directory, force?: boolean): void, + rename(from: Directory, to: Directory): void, + } +}; +export type UseFileSystem = (options?: FileSystemOptions) => UseFileSystemReturn; + +export type UseFilesReturn = { + [K in keyof UseFileSystemReturn['file'] as `${K}File`]: UseFileSystemReturn['file'][K] +}; +export type UseFiles = () => UseFilesReturn; +export type UseFoldersReturn = { + [K in keyof UseFileSystemReturn['directory'] as `${K}Folder`]: UseFileSystemReturn['directory'][K] +}; +export type UseFolders = () => UseFoldersReturn; +export type UseDirectoriesReturn = { + [K in keyof UseFileSystemReturn['directory'] as `${K}Directory`]: UseFileSystemReturn['directory'][K] +}; +export type UseDirectories = () => UseDirectoriesReturn; \ No newline at end of file diff --git a/src/hooks/kernel/geolocation/index.ts b/src/hooks/kernel/geolocation/index.ts new file mode 100644 index 0000000..2b56953 --- /dev/null +++ b/src/hooks/kernel/geolocation/index.ts @@ -0,0 +1,18 @@ +import {ComputedRef} from 'vue'; +import {useGeolocation as useGeolocationFromVueUse} from '@vueuse/core'; +import {toComputed} from '@/macros/vue'; + +export const useGeolocation = () => { + const {coords, error} = useGeolocationFromVueUse({ + immediate: true + }); + + return { + ...Array.from(Object.keys(coords.value)).reduce((r, k) => ({ + ...r, + [k]: toComputed(() => coords.value[k as keyof typeof coords.value]) + }), {} as Record>), + error: toComputed(error), + accuracyUnit: 'm' + }; +}; \ No newline at end of file diff --git a/src/hooks/kernel/index.ts b/src/hooks/kernel/index.ts new file mode 100644 index 0000000..cc2024b --- /dev/null +++ b/src/hooks/kernel/index.ts @@ -0,0 +1,39 @@ +import { useScheduler, useProcess } from './scheduler'; +import type { KernelScheduler } from './scheduler'; + +import keyboard, { useKeyboard } from './keyboard'; +import type { KernelKeyboard } from './keyboard/types'; +export { KeyboardEvent } from './keyboard/enums'; + +import { useNetwork } from './network'; + +import { useClipboard } from '@/hooks/kernel/clipboard'; + +import { useGeolocation } from './geolocation'; + +import { + useFileSystem, useFSTree, + useFiles, useFolders, + useDirectories, useMock +} from './filesystem'; + +export type UseKernel = () => { + keyboard: KernelKeyboard, + scheduler: KernelScheduler +}; + +export const useKernel: UseKernel = () => { + const scheduler = useScheduler(); + + return {keyboard, scheduler} +} + +export default () => ({ + useKernel, useScheduler, + useProcess, useKeyboard, + useNetwork, useClipboard, + useGeolocation, + useFileSystem, useFSTree, + useFiles, useFolders, + useDirectories, useMock +}) \ No newline at end of file diff --git a/src/hooks/kernel/keyboard/enums.ts b/src/hooks/kernel/keyboard/enums.ts new file mode 100644 index 0000000..42c37a9 --- /dev/null +++ b/src/hooks/kernel/keyboard/enums.ts @@ -0,0 +1,17 @@ +export enum KeyboardEvent { + INPUT = 'input', + DELETE = 'delete', + UNDO = 'undo', + SPACE = 'space', + COMBO = 'combo', + MOVE = 'move', + ENTER = 'enter', + NUMBER = 'number', + FUNCTION = 'fn', + PAGE = 'page', + HOME = 'home', + CLEAR = 'clear', + OPERATOR = 'operator', + CUSTOM = 'custom', + CUSTOM_ALL = 'custom_all' +} \ No newline at end of file diff --git a/src/hooks/kernel/keyboard/index.ts b/src/hooks/kernel/keyboard/index.ts new file mode 100644 index 0000000..df98a4e --- /dev/null +++ b/src/hooks/kernel/keyboard/index.ts @@ -0,0 +1,491 @@ +import {ComputedRef, toRef, ToRefs} from 'vue'; +import {UnwrapRefSimple} from '@vue/reactivity' +import {onKeyDown, onKeyStroke, onKeyUp} from '@vueuse/core'; +import {ref} from 'vue'; +import {useKernel} from '@/hooks/kernel'; +import type {Events, LastCharKey, ControlActions, KernelKeyboard} from './types'; +import {KeyboardEvent} from './enums'; +import {toComputed, ToMultiComputed} from '@/macros/vue.ts'; + +const eventsDefault: Events = { + [KeyboardEvent.INPUT]: { + type: KeyboardEvent.INPUT, + key: '' + }, + [KeyboardEvent.DELETE]: { + type: KeyboardEvent.DELETE, + key: '', + side: 'left' + }, + [KeyboardEvent.UNDO]: { + type: KeyboardEvent.UNDO + }, + [KeyboardEvent.SPACE]: { + type: KeyboardEvent.SPACE, + key: '' + }, + [KeyboardEvent.COMBO]: { + type: KeyboardEvent.COMBO, + key: '', + ctrl: false, + shift: false, + alt: false, + altGr: false + }, + [KeyboardEvent.MOVE]: { + type: KeyboardEvent.MOVE, + key: '', + side: 'left' + }, + [KeyboardEvent.ENTER]: { + type: KeyboardEvent.ENTER, + key: 'Enter', + }, + [KeyboardEvent.NUMBER]: { + type: KeyboardEvent.NUMBER, + key: '', + }, + [KeyboardEvent.FUNCTION]: { + type: KeyboardEvent.FUNCTION, + key: '', + }, + [KeyboardEvent.PAGE]: { + type: KeyboardEvent.PAGE, + key: '', + first: false, + last: false, + next: false, + prev: false + }, + [KeyboardEvent.HOME]: { + type: KeyboardEvent.HOME, + key: 'Home' + }, + [KeyboardEvent.CLEAR]: { + type: KeyboardEvent.CLEAR, + key: 'Clear' + }, + [KeyboardEvent.OPERATOR]: { + type: KeyboardEvent.OPERATOR, + key: '' + }, + [KeyboardEvent.CUSTOM]: { + type: KeyboardEvent.CUSTOM, + key: '', + composed: false, + event: null + }, + [KeyboardEvent.CUSTOM_ALL]: { + type: KeyboardEvent.CUSTOM_ALL, + key: '', + composed: false, + event: null + }, +}; + +const isAltGr = ref(false); +onKeyDown('AltGraph', () => { + isAltGr.value = true; +}); +onKeyUp('AltGraph', () => { + isAltGr.value = false; +}); + +export const useKeyboard = < + N extends (typeof KeyboardEvent)[keyof typeof KeyboardEvent]|(keyof typeof KeyboardEvent), + E extends Events[N extends keyof typeof KeyboardEvent ? typeof KeyboardEvent[N] : N], + Trigger extends (params: E) => void = (params: E) => void, + Return = ToMultiComputed & { watch: (name: string, trigger: Trigger) => void } +>(eventName: N): Return => { + const { keyboard } = useKernel(); + const triggers = ref([]); + + const newEventName = Array.from(Object.keys(KeyboardEvent)).includes(eventName) ? KeyboardEvent[eventName as keyof typeof KeyboardEvent] : eventName as KeyboardEvent; + + const d: ToRefs = + Array.from(Object.keys(eventsDefault[newEventName])) + .reduce( + (r, c) => ({ + ...r, + [c]: toRef((eventsDefault[newEventName] as ToRefs)[c as keyof ToRefs]) + }), + {} as ToRefs + ); + + keyboard.listen(newEventName, (e) => { + for (const k in e) { + (d[k as keyof ToRefs].value as E[keyof E]) = (e as E)[k as keyof E]; + } + triggers.value.map(trigger => trigger?.(e as E)); + }); + + return { + ...Array.from(Object.keys(d)) + .reduce<{ [K in keyof E]: ComputedRef }>( + (r, c) => ({ + ...r, + [c]: toComputed((d as ToRefs)[c as keyof ToRefs]) + }), + {} as { [K in keyof E]: ComputedRef } + ), + watch(_n: string, watcher: Trigger) { + triggers.value = [ + ...triggers.value, + watcher as UnwrapRefSimple + ]; + } + } as Return; +} + +const addAltGraphAndGetEvent = < + E extends globalThis.KeyboardEvent & Partial<{altGraphKey: boolean}>, + R extends Required +>(e: E): R => { + e.altGraphKey = isAltGr.value; + + return e as unknown as R; +} + +const lastCharKey: LastCharKey = ref(''); + +const addAccents = (e: globalThis.KeyboardEvent) => { + switch (e.key) { + case 'E': + if (e.shiftKey && ['^', '¨'].includes(lastCharKey.value)) { + if (lastCharKey.value === '^') { + lastCharKey.value = ''; + return 'Ê'; + } + else if (lastCharKey.value === '¨') { + lastCharKey.value = ''; + return 'Ë'; + } + } + break; + case 'e': + if (e.shiftKey && ['^', '¨'].includes(lastCharKey.value)) { + if (lastCharKey.value === '^') { + lastCharKey.value = ''; + return 'ê'; + } + else if (lastCharKey.value === '¨') { + lastCharKey.value = ''; + return 'ë'; + } + } + break; + case 'A': + if (e.shiftKey && ['^', '¨'].includes(lastCharKey.value)) { + if (lastCharKey.value === '^') { + lastCharKey.value = ''; + return 'Â'; + } + else if (lastCharKey.value === '¨') { + lastCharKey.value = ''; + return 'Ä'; + } + } + break; + case 'a': + if (e.shiftKey && ['^', '¨'].includes(lastCharKey.value)) { + if (lastCharKey.value === '^') { + lastCharKey.value = ''; + return 'â'; + } + else if (lastCharKey.value === '¨') { + lastCharKey.value = ''; + return 'ä'; + } + } + break; + case 'I': + if (e.shiftKey && ['^', '¨'].includes(lastCharKey.value)) { + if (lastCharKey.value === '^') { + lastCharKey.value = ''; + return 'Î'; + } + else if (lastCharKey.value === '¨') { + lastCharKey.value = ''; + return 'Ï'; + } + } + break; + case 'i': + if (e.shiftKey && ['^', '¨'].includes(lastCharKey.value)) { + if (lastCharKey.value === '^') { + lastCharKey.value = ''; + return 'î'; + } + else if (lastCharKey.value === '¨') { + lastCharKey.value = ''; + return 'ï'; + } + } + break; + default: return e.key + } +}; + +const controlActions: ControlActions = { + [KeyboardEvent.UNDO]( + e: globalThis.KeyboardEvent, + composed: boolean, + event: (event: Events[KeyboardEvent.UNDO]) => void + ) { + if (composed && e.ctrlKey && ['z', 'Z'].includes(e.key)) { + event({ type: KeyboardEvent.UNDO }) + } + }, + [KeyboardEvent.NUMBER]( + e: globalThis.KeyboardEvent, + composed: boolean, + event: (event: Events[KeyboardEvent.NUMBER]) => void + ) { + if ( + ( + ( + e.code.startsWith('Digit') && + composed && e.shiftKey + ) || + e.code.startsWith('Numpad') + ) && + /^[0-9]+$/g.test(e.key) + ) { + event({ + type: KeyboardEvent.NUMBER, + key: e.key + }) + } + }, + [KeyboardEvent.COMBO]( + e: globalThis.KeyboardEvent, + composed: boolean, + event: (event: Events[KeyboardEvent.COMBO]) => void + ) { + if (composed) { + if (lastCharKey.value === '' && e.key === 'Dead') { + lastCharKey.value = '¨'; + return; + } + + if ( + (e.code.startsWith('Digit') && composed && e.shiftKey) || + (e.ctrlKey && ['z', 'Z'].includes(e.key)) + ) return; + + const key = addAccents(e); + + if (!key || key === '¨') return; + + event({ + type: KeyboardEvent.COMBO, + key, + ctrl: e.ctrlKey, + shift: e.shiftKey, + alt: e.altKey, + altGr: isAltGr.value + }) + } + }, + [KeyboardEvent.SPACE]( + e: globalThis.KeyboardEvent, + _composed: boolean, + event: (event: Events[KeyboardEvent.SPACE]) => void + ) { + if (e.key === ' ') { + event({ + type: KeyboardEvent.SPACE, + key: e.key + }) + } + }, + [KeyboardEvent.DELETE]( + e: globalThis.KeyboardEvent, + composed: boolean, + event: (event: Events[KeyboardEvent.DELETE]) => void + ) { + if (!composed && (e.key === 'Backspace' || e.key === 'Delete')) { + const side = e.key === 'Backspace' ? 'left' : 'right'; + + event({ + type: KeyboardEvent.DELETE, + key: e.key, + side + }) + } + }, + [KeyboardEvent.MOVE]( + e: globalThis.KeyboardEvent, + composed: boolean, + event: (event: Events[KeyboardEvent.MOVE]) => void + ) { + if ( + !composed && + [ + 'ArrowUp', 'ArrowDown', + 'ArrowLeft', 'ArrowRight' + ].includes(e.key) + ) { + event({ + type: KeyboardEvent.MOVE, + key: e.key, + side: e.key.replace('Arrow', '').toLowerCase() as Events[KeyboardEvent.MOVE]['side'] + }) + } + }, + [KeyboardEvent.INPUT]( + e: globalThis.KeyboardEvent, + composed: boolean, + event: (event: Events[KeyboardEvent.INPUT]) => void + ) { + if (!composed) { + if (lastCharKey.value === '' && e.key === 'Dead') { + lastCharKey.value = '^'; + return; + } + + if ( + e.key.startsWith('Arrow') || + ['Backspace', 'Delete', 'Space'].includes(e.key) || + e.code.startsWith('Numpad') || + e.key === ' ' || + (e.keyCode >= 112 && e.keyCode <= 123) + ) return; + + const key = addAccents(e); + + if (!key || key === '^') return; + + event({ + type: KeyboardEvent.INPUT, + key + }) + } + }, + [KeyboardEvent.FUNCTION]( + e: globalThis.KeyboardEvent, + _composed: boolean, + event: (event: Events[KeyboardEvent.FUNCTION]) => void + ) { + if (e.keyCode >= 112 && e.keyCode <= 123) { + event({ + type: KeyboardEvent.FUNCTION, + key: e.key + }); + } + }, + [KeyboardEvent.ENTER]( + e: globalThis.KeyboardEvent, + composed: boolean, + event: (event: Events[KeyboardEvent.ENTER]) => void + ) { + if (!composed && e.key === 'Enter') { + event({ + type: KeyboardEvent.ENTER, + key: e.key + }) + } + }, + [KeyboardEvent.PAGE]( + e: globalThis.KeyboardEvent, + _composed: boolean, + event: (event: Events[KeyboardEvent.PAGE]) => void + ) { + if ( + e.key.startsWith('Page') || + ['Home', 'End'].includes(e.key) + ) { + event({ + type: KeyboardEvent.PAGE, + key: e.key, + ...((side) => ({ + first: side === 'home', + last: side === 'end', + next: side === 'down', + prev: side === 'up' + }))(e.key + .replace('Page', '') + .toLowerCase() + ) + }); + } + }, + [KeyboardEvent.HOME]( + e: globalThis.KeyboardEvent, + _composed: boolean, + event: (event: Events[KeyboardEvent.HOME]) => void + ) { + if (e.key === 'Home') { + event({ + type: KeyboardEvent.HOME, + key: e.key + }) + } + }, + [KeyboardEvent.CLEAR]( + e: globalThis.KeyboardEvent, + _composed: boolean, + event: (event: Events[KeyboardEvent.CLEAR]) => void + ) { + if (e.key === 'Clear') { + event({ + type: KeyboardEvent.CLEAR, + key: e.key + }) + } + }, + [KeyboardEvent.OPERATOR]( + e: globalThis.KeyboardEvent, + _composed: boolean, + event: (event: Events[KeyboardEvent.OPERATOR]) => void + ) { + if (['/', '*', '-', '+', '='].includes(e.key)) { + event({ + type: KeyboardEvent.OPERATOR, + key: e.key + }) + } + }, +}; + +const listen = < + N extends (typeof KeyboardEvent)[keyof typeof KeyboardEvent], + E extends Events[N], + Event extends (event: E) => void = (event: E) => void +>(name: N, event: Event) => { + onKeyStroke(e => { + const composed = (e.ctrlKey || e.shiftKey || e.altKey || isAltGr.value) && e.key !== 'Control' && e.key !== 'Alt' && e.key !== 'AltGraph' && e.key !== 'Shift'; + + const isCompositionChar = + ['Control', 'Shift', 'Alt', 'AltGraph'].includes(e.key) || + e.key.endsWith('Lock'); + const isAllCustom = name === KeyboardEvent.CUSTOM_ALL; + const isCustom = name === KeyboardEvent.CUSTOM; + + if (isAllCustom || (!isCompositionChar && isCustom)) { + event({ + type: isCustom ? KeyboardEvent.CUSTOM : KeyboardEvent.CUSTOM_ALL, + key: e.key, + composed, + event: addAltGraphAndGetEvent(e) + } as E) + + return this; + } + + e.preventDefault(); + + if ( + !['Control', 'Shift', 'Alt', 'AltGraph'].includes(e.key) && + !e.key.endsWith('Lock') && + name in controlActions + ) { + // @ts-expect-error + controlActions[name].call(this, e, composed, event) + } + }); + + return this; +} + +export default { listen } as unknown as KernelKeyboard; \ No newline at end of file diff --git a/src/hooks/kernel/keyboard/types.ts b/src/hooks/kernel/keyboard/types.ts new file mode 100644 index 0000000..8f07d09 --- /dev/null +++ b/src/hooks/kernel/keyboard/types.ts @@ -0,0 +1,100 @@ +import type {Ref} from 'vue'; +import {KeyboardEvent} from './enums'; + +export type Events = { + [KeyboardEvent.INPUT]: { + type: KeyboardEvent.INPUT, + key: string + }, + [KeyboardEvent.DELETE]: { + type: KeyboardEvent.DELETE, + key: string, + side: 'left'|'right' + }, + [KeyboardEvent.UNDO]: { + type: KeyboardEvent.UNDO + }, + [KeyboardEvent.SPACE]: { + type: KeyboardEvent.SPACE, + key: string + }, + [KeyboardEvent.COMBO]: { + type: KeyboardEvent.COMBO, + key: string, + ctrl: boolean, + shift: boolean, + alt: boolean, + altGr: boolean + }, + [KeyboardEvent.MOVE]: { + type: KeyboardEvent.MOVE, + key: string, + side: 'up'|'down'|'left'|'right' + }, + [KeyboardEvent.ENTER]: { + type: KeyboardEvent.ENTER, + key: string + }, + [KeyboardEvent.NUMBER]: { + type: KeyboardEvent.NUMBER, + key: string + }, + [KeyboardEvent.FUNCTION]: { + type: KeyboardEvent.FUNCTION, + key: string + }, + [KeyboardEvent.PAGE]: { + type: KeyboardEvent.PAGE, + key: string, + first: boolean, + last: boolean, + next: boolean, + prev: boolean + }, + [KeyboardEvent.HOME]: { + type: KeyboardEvent.HOME, + key: string + }, + [KeyboardEvent.CLEAR]: { + type: KeyboardEvent.CLEAR, + key: string + }, + [KeyboardEvent.OPERATOR]: { + type: KeyboardEvent.OPERATOR, + key: string + }, + [KeyboardEvent.CUSTOM]: { + type: KeyboardEvent.CUSTOM, + key: string, + composed: boolean, + event: (globalThis.KeyboardEvent & {altGraphKey: boolean})|null + }, + [KeyboardEvent.CUSTOM_ALL]: { + type: KeyboardEvent.CUSTOM_ALL, + key: string, + composed: boolean, + event: [globalThis.KeyboardEvent & {altGraphKey: boolean}]|null + } +} + +export type EventsOmitCustom = keyof Omit< + Events, + KeyboardEvent.CUSTOM | KeyboardEvent.CUSTOM_ALL +>; + +export type ControlActions = { + [K in EventsOmitCustom]: ( + e: globalThis.KeyboardEvent, + composed: boolean, + event: (event: Events[K]) => void + ) => void +} + +export type LastCharKey = Ref; + +export type KernelKeyboard = { + listen< + N extends (typeof KeyboardEvent)[keyof typeof KeyboardEvent], + E extends Events[N] + >(name: N, event: (event: E) => void): KernelKeyboard +}; \ No newline at end of file diff --git a/src/hooks/kernel/network/index.ts b/src/hooks/kernel/network/index.ts new file mode 100644 index 0000000..6f0b2a5 --- /dev/null +++ b/src/hooks/kernel/network/index.ts @@ -0,0 +1,35 @@ +import {useNetwork as useNetworkFromVueUse} from '@vueuse/core'; +import {useMobile} from '@/hooks/device-type'; +import {toComputed} from '@/macros/vue'; + +export const useNetwork = () => { + const isMobile = useMobile(); + + const { + isSupported, + isOnline, + offlineAt, + downlink, + downlinkMax, + // NetworkEffectType: "slow-2g" | "2g" | "3g" | "4g" | undefined + effectiveType, + rtt, + saveData, + // NetworkType: "bluetooth" | "cellular" | "ethernet" | "none" | "wifi" | "wimax" | "other" | "unknown" + type + } = useNetworkFromVueUse(); + + const _downlink = toComputed(() => downlink.value ?? 0); + + return { + isSupported: toComputed(isSupported), + isOnline: toComputed(isOnline), + offlineAt: toComputed(() => offlineAt.value ?? 0), + downlink: toComputed(_downlink), + downlinkMax: toComputed(() => downlinkMax.value ?? _downlink.value), + effectiveType: toComputed(effectiveType), + rtt: toComputed(() => rtt.value ?? 0), + saveData: toComputed(() => saveData.value ?? false), + type: toComputed(() => type.value ?? (isMobile.value ? 'cellular' : 'wifi')) + }; +}; \ No newline at end of file diff --git a/src/hooks/kernel/scheduler/index.ts b/src/hooks/kernel/scheduler/index.ts new file mode 100644 index 0000000..8383612 --- /dev/null +++ b/src/hooks/kernel/scheduler/index.ts @@ -0,0 +1,84 @@ +import {ref, computed} from "vue"; +import type { ComputedRef } from "vue"; +import { useUuid } from '@/hooks/uuid.ts'; + +export type Process> = { + id: string, + time: number, + data: D, + run(data: D): Promise|string|boolean, + deleted?: (data: D) => void +}; + +const runProcess = >(process: Process) => + new Promise( + (resolve, reject) => { + const result = process.run(process.data); + + if (result instanceof Promise) { + result.then(() => resolve(process.data)).catch(reject); + } + else if (typeof result === 'boolean' && result) + resolve(process.data); + else reject(result); + } + ); + +export type KernelScheduler = { + processes: ComputedRef[]>, + + createProcess>(process: Omit, 'id'|'time'>): Promise, + removeProcess>(process: Process): void +}; + +type UseScheduler = () => KernelScheduler; + +const processes = ref[]>([]); + +const removeProcess = >(process: Process) => { + const index = processes.value.indexOf(process); + + if (index !== -1) { + processes.value.splice(index, 1); + } +}; +const createProcess = (id: ComputedRef) => async >(process: Omit, 'id' | 'time'>) => { + const completeProcess: Process = { + ...process, + id: id.value, + time: Date.now() + }; + processes.value.push(completeProcess); + + try { + return await runProcess(completeProcess); + } + finally { + removeProcess(completeProcess); + completeProcess.deleted?.(completeProcess.data); + } +}; + +export const useScheduler: UseScheduler = () => { + const id = useUuid(); + + return { + processes: computed(() => processes.value.sort((a, b) => + a.time - b.time + )), + + createProcess: createProcess(id), + removeProcess + }; +}; + +export type UseProcess = () => [create: KernelScheduler['createProcess'], remove: KernelScheduler['removeProcess']] + +export const useProcess: UseProcess = () => { + const id = useUuid(); + + return [ + createProcess(id), + removeProcess + ]; +} \ No newline at end of file diff --git a/src/macros/vue.ts b/src/macros/vue.ts new file mode 100644 index 0000000..1c1f22c --- /dev/null +++ b/src/macros/vue.ts @@ -0,0 +1,17 @@ +import {computed, ComputedGetter, ComputedRef, isRef, Ref} from 'vue'; + +export const toComputed = < + T extends any, + R extends ComputedRef +>(v: ComputedRef|Ref|ComputedGetter): R => { + if (isRef(v)) return computed(() => (v as Ref).value) as unknown as R; + else if ('value' in v && !isRef(v)) return v as unknown as R; + return computed(v as unknown as ComputedGetter) as unknown as R; +}; + +export type ToMultiComputed> = { + [K in keyof L]: L[K] extends Ref + ? ComputedRef : (L[K] extends ComputedRef + ? ComputedRef : (L[K] extends ComputedGetter + ? ComputedRef : ComputedRef)) +} diff --git a/yarn.lock b/yarn.lock index 3e75bda..0ceeca3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2433,7 +2433,7 @@ muggle-string@^0.3.1: resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.3.1.tgz#e524312eb1728c63dd0b2ac49e3282e6ed85963a" integrity sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg== -nanoid@^3.3.6, nanoid@^3.3.7: +nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== @@ -2508,16 +2508,7 @@ pinia@^2.1.7: "@vue/devtools-api" "^6.5.0" vue-demi ">=0.14.5" -postcss@^8.4.31: - version "8.4.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" - integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== - dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -postcss@^8.4.33: +postcss@^8.4.32, postcss@^8.4.33: version "8.4.33" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== @@ -3043,13 +3034,13 @@ vite-plugin-pwa@^0.17.2: workbox-build "^7.0.0" workbox-window "^7.0.0" -vite@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.2.tgz#3c94627dace83b9bf04b64eaf618038e30fb95c0" - integrity sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g== +vite@^5.0.3: + version "5.0.12" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.12.tgz#8a2ffd4da36c132aec4adafe05d7adde38333c47" + integrity sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w== dependencies: esbuild "^0.19.3" - postcss "^8.4.31" + postcss "^8.4.32" rollup "^4.2.0" optionalDependencies: fsevents "~2.3.3"