From 4c72a01777963bc7b4f94e6227f51b53caf56ee1 Mon Sep 17 00:00:00 2001 From: OlegWock Date: Thu, 20 Apr 2023 19:09:41 +0200 Subject: [PATCH] Add support for custom icons uploaded by user #21 --- declarations.d.ts | 17 ++ generate-icons-assets.ts | 9 +- package.json | 9 +- src/background.ts | 4 +- src/components/Button.scss | 5 +- src/components/Button.tsx | 26 ++- src/components/Hint.tsx | 3 +- src/components/Icon.scss | 3 + src/components/Icon.tsx | 42 +++- src/components/IconPicker.scss | 10 + src/components/IconPicker.tsx | 18 +- src/components/WhatsNew.scss | 40 +++- src/components/WhatsNew.tsx | 27 ++- src/components/icons/all-sets.ts | 9 +- src/pages/newtab/components/FolderContent.tsx | 1 - .../newtab/components/NewWidgetWizard.tsx | 1 + src/pages/newtab/components/Settings.scss | 65 ++++++ src/pages/newtab/components/Settings.tsx | 212 ++++++++++++++---- src/pages/newtab/start.tsx | 4 +- src/plugins/weather/weather-plugin.tsx | 3 - src/utils/custom-icons.ts | 137 +++++++++++ src/utils/files.ts | 29 +++ src/utils/misc.ts | 8 + tsconfig.json | 3 +- yarn.lock | 165 +++++++++----- 25 files changed, 702 insertions(+), 148 deletions(-) create mode 100644 src/components/Icon.scss create mode 100644 src/utils/custom-icons.ts create mode 100644 src/utils/files.ts diff --git a/declarations.d.ts b/declarations.d.ts index 1d5d76b5..471b1805 100644 --- a/declarations.d.ts +++ b/declarations.d.ts @@ -19,3 +19,20 @@ declare module 'webextension-polyfill' { } } } + + +declare global { + type FileSystemDirectoryNamesIterator = AsyncIterable; + type FileSystemDirectoryHandlesIterator = AsyncIterable; + type FileSystemDirectoryEntriesIterator = AsyncIterable<[string, FileSystemDirectoryHandle | FileSystemFileHandle]>; + + interface FileSystemDirectoryHandle { + keys(): FileSystemDirectoryNamesIterator, + values(): FileSystemDirectoryHandlesIterator, + entries(): FileSystemDirectoryEntriesIterator, + } + + interface FileSystemFileHandle { + createWritable(): Promise, + } +} \ No newline at end of file diff --git a/generate-icons-assets.ts b/generate-icons-assets.ts index 9700e412..db5698bc 100644 --- a/generate-icons-assets.ts +++ b/generate-icons-assets.ts @@ -50,11 +50,18 @@ sets.forEach((set, i, arr) => { const allSetsTs = ` -export const allSets = [\n${sets.map(s => ` ${JSON.stringify(s.name)}, // https://icon-sets.iconify.design/${s.name}/`).join('\n')}\n]; +import { CUSTOM_ICONS_AVAILABLE } from '@utils/custom-icons'; +export const allSets = [\n${sets.map(s => ` ${JSON.stringify(s.name)}, // https://icon-sets.iconify.design/${s.name}/`).join('\n')},\n]; export const iconSetPrettyNames: Record = { ${sets.map(s => ` '${s.name}': ${JSON.stringify(s.data.info.name)},`).join('\n')} } as const; + +if (CUSTOM_ICONS_AVAILABLE) { + // Icons uploaded by user + allSets.push('custom'); + iconSetPrettyNames['custom'] = 'Custom icons'; +} ` const iconsBySet: Record = {}; diff --git a/package.json b/package.json index d3cf3a31..cd096f55 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Anori", "description": "Customizable new tab: compose your own page from widgets", - "version": "1.4.1", + "version": "1.5.0", "repository": "git@github.com:OlegWock/anori.git", "author": "OlegWock ", "license": "MIT", @@ -40,8 +40,8 @@ "@types/react-grid-layout": "^1.3.2", "@types/react-window": "^1.8.5", "@types/webextension-polyfill": "^0.10.0", - "@typescript-eslint/eslint-plugin": "^5.50.0", - "@typescript-eslint/parser": "^5.50.0", + "@typescript-eslint/eslint-plugin": "^5.59.0", + "@typescript-eslint/parser": "^5.59.0", "babel-loader": "^8.2.5", "clean-webpack-plugin": "^4.0.0", "clsx": "^1.2.1", @@ -55,6 +55,7 @@ "inject-react-anywhere": "^1.0.0", "jotai": "^2.0.0", "jotai-optics": "^0.3.0", + "jszip": "^3.10.1", "moment-timezone": "^0.5.40", "moment-timezone-data-webpack-plugin": "^1.5.1", "optics-ts": "^2.4.0", @@ -73,7 +74,7 @@ "to-string-loader": "^1.2.0", "ts-loader": "^9.4.2", "ts-node": "^10.9.1", - "typescript": "^4.9.5", + "typescript": "^5.0.4", "walk-sync": "^3.0.0", "webextension-polyfill": "^0.10.0", "webpack": "^5.75.0", diff --git a/src/background.ts b/src/background.ts index 5bd714bd..e2bc0141 100644 --- a/src/background.ts +++ b/src/background.ts @@ -4,7 +4,7 @@ import browser from 'webextension-polyfill'; console.log('Background init'); -const VERSIONS_WITH_CHANGES = ['1.1.0', '1.2.0']; +const VERSIONS_WITH_CHANGES = ['1.1.0', '1.2.0', '1.5.0']; const compareVersions = (v1: string, v2: string): -1 | 0 | 1 => { // v1 is newer than v2 => -1 @@ -54,8 +54,6 @@ browser.runtime.onInstalled.addListener((details) => { } }); -console.log('Planted onInstalled handler'); - const runScheduledCallbacks = async () => { const { scheduledCallbacksInfo } = await browser.storage.session.get({ 'scheduledCallbacksInfo': {}, diff --git a/src/components/Button.scss b/src/components/Button.scss index 4b55c74e..7e34251a 100644 --- a/src/components/Button.scss +++ b/src/components/Button.scss @@ -20,7 +20,7 @@ padding: 6px 18px; border-radius: 24px; - &:hover { + &:hover:not(:disabled):not([aria-disabled="true"]) { background: rgba(255, 255, 255, 0.1); } @@ -29,8 +29,9 @@ } } - &:disabled { + &:disabled, &[aria-disabled="true"] { cursor: not-allowed; + color: var(--text-disabled); } diff --git a/src/components/Button.tsx b/src/components/Button.tsx index c6571169..2028e9f5 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -10,18 +10,26 @@ export interface ButtonProps extends Omit(({ size = "normal", withoutBorder = false, active, block = false, ...props}, ref) => { - return +export const Button = React.forwardRef(({ size = "normal", disabled, visuallyDisabled, withoutBorder = false, active, block = false, onClick, ...props }, ref) => { + return }); export const LinkButton = React.forwardRef>((props, ref) => { - return ); }; +type DraftCustomIcon = { + id: string, + name: string, + extension: string, + content: ArrayBuffer, + preview: string +}; + export const Settings = () => { const exportSettings = async () => { + const zip = new JSZip(); const storage = await browser.storage.local.get(null); - const aElement = document.createElement('a'); - const datetime = moment().format('DD-MM-yyyy_HH-mm'); - aElement.setAttribute('download', `anori-backup-${datetime}.json`); + zip.file('storage.json', JSON.stringify(storage, null, 4), { compression: 'DEFLATE' }); - const blob = new Blob([JSON.stringify(storage, null, 4)], { - type: 'text/plain' - }); - const href = URL.createObjectURL(blob); - aElement.href = href; - aElement.setAttribute('target', '_blank'); - aElement.click(); - URL.revokeObjectURL(href); + const customIconFiles = await getAllCustomIconFiles(); + customIconFiles.forEach(handle => zip.file(`opfs/${handle.name}`, handle.getFile(), { compression: 'DEFLATE' })) + const blob = await zip.generateAsync({ type: "blob" }); + const datetime = moment().format('DD-MM-yyyy_HH-mm'); + downloadBlob(`anori-backup-${datetime}.zip`, blob); }; const importSettings = async () => { - const input = document.createElement('input'); - input.type = 'file'; - input.addEventListener('change', (e) => { - const file = (e.target as HTMLInputElement).files![0]; - const reader = new FileReader(); - reader.readAsText(file, 'UTF-8'); - reader.addEventListener('load', async (e) => { - const text = e.target!.result as string; - const json = JSON.parse(text); - - // TODO: will be a good idea to validate before importing - await browser.storage.local.set(json); - window.location.reload(); - }); + const files = await showOpenFilePicker(false, '.zip'); + const file = files[0]; + const zip = await JSZip.loadAsync(file); + const storageJson = await zip.file('storage.json')!.async('string'); + const parsedStorage = JSON.parse(storageJson); + await browser.storage.local.set(parsedStorage); + + await removeAllCustomIcons(); + const promises: Promise[] = []; + zip.folder('opfs')!.forEach((path, file) => { + console.log('Importing', { file, path }) + promises.push( + file.async('arraybuffer').then(ab => { + return addNewCustomIcon(path, ab); + }) + ); }); - input.click(); + await Promise.all(promises); + window.location.reload(); + }; + + const importCustomIcons = async () => { + const files = await showOpenFilePicker(true, '.jpg,.jpeg,.png,.gif,.svg'); + let hasErrors = false; + const importedFiles: DraftCustomIcon[] = await Promise.all(files.map(async (file) => { + const id = guid(); + const arrayBuffer = await file.arrayBuffer(); + const preview = URL.createObjectURL(file); + const tokens = file.name.split('.'); + const extension = tokens[tokens.length - 1]; + const name = tokens.slice(0, tokens.length - 1).join('.'); + if (!name || !extension || !['png', 'jpg', 'jpeg', 'svg'].includes(extension.toLowerCase())) { + hasErrors = true; + } + + return { + id, + content: arrayBuffer, + name, + extension, + preview, + }; + })); + + if (hasErrors) { + // TODO: replace with toast + alert('Please provide valid file(s). Anori supports .png, .jpg, .gif, .jpeg and .svg'); + return; + } + setDraftCustomIcons(p => [...p, ...importedFiles]); + }; + + const saveDraftCustomIcons = async () => { + await Promise.all(draftCustomIcons.map(draftCustomIcon => addNewCustomIcon(`${draftCustomIcon.name}.${draftCustomIcon.extension}`, draftCustomIcon.content, draftCustomIcon.preview))); + setDraftCustomIcons([]); }; + const { folders, setFolders, createFolder, updateFolder, removeFolder } = useFolders(); const [currentTheme, setTheme] = useBrowserStorageValue('theme', defaultTheme); const [stealFocus, setStealFocus] = useBrowserStorageValue('stealFocus', false); @@ -139,13 +189,17 @@ export const Settings = () => { const [manualCompactMode, setManualCompactMode] = useBrowserStorageValue('compactMode', false); const [showLoadAnimation, setShowLoadAnimation] = useBrowserStorageValue('showLoadAnimation', false); + const { customIcons, addNewCustomIcon, removeCustomIcon } = useCustomIcons(); + const [draftCustomIcons, setDraftCustomIcons] = useState([]); + const hasDraftIconsWithInvalidName = draftCustomIcons.some(i => !isValidCustomIconName(i.name)); + return (
The Settings Menu is a powerful tool for customizing your user experience. Here, you can tweak everything from the default color scheme to the order of folders. With the Settings Menu, you have total control over the look and feel of your new tab. -
+

Options

{/* Focus stealer works only in Chrome and Safari */} @@ -164,18 +218,71 @@ export const Settings = () => {
-
-
-

Import and export

-
Here you can backup your settings or restore older backup.
-
- - -
-
-
+ + + {CUSTOM_ICONS_AVAILABLE && +

Custom icons

+ + {customIcons.length === 0 &&
+ You don't have any custom icons yet. +
} + + + + + {customIcons.map(icon => { + return ( + {icon.name} +
{icon.name}
+ +
) + })} +
+
+
+ + {draftCustomIcons.length !== 0 && + {draftCustomIcons.map((draftCustomIcon) => { + const validName = isValidCustomIconName(draftCustomIcon.name) || draftCustomIcon.name.length === 0; + return ( + {draftCustomIcon.name} +
+ setDraftCustomIcons(p => p.map(i => i.id === draftCustomIcon.id ? { ...i, name } : i))} /> + {!validName &&
Name contains invalid characters, only letters, digits, - and _ are accepted.
} +
+ +
); + })} + + {hasDraftIconsWithInvalidName && + + } + {!hasDraftIconsWithInvalidName && } +
} + + {draftCustomIcons.length === 0 && + + } +
} + +

Folders

-
+ {folders.map((f, index) => { @@ -190,14 +297,14 @@ export const Settings = () => { />) })} -
+ -
+ -
+

Theme

{themes.map((theme) => { @@ -212,16 +319,25 @@ export const Settings = () => { />) })}
-
+ - {availablePlugins.filter(p => p.configurationScreen !== null).length !== 0 &&
+ {availablePlugins.filter(p => p.configurationScreen !== null).length !== 0 &&

Plugin settings

{availablePlugins.filter(p => p.configurationScreen !== null).map(p => { return (); })} -
} + } + + +

Import and export

+
Here you can backup your settings or restore older backup.
+
+ + +
+
-
+

About Anori

Anori is free and open source extension. Source code can be found on GitHub. @@ -237,7 +353,7 @@ export const Settings = () => {

Follow me on Twitter and support Ukraine.

-
+
) }; \ No newline at end of file diff --git a/src/pages/newtab/start.tsx b/src/pages/newtab/start.tsx index f042f2f0..5ac6cd4e 100644 --- a/src/pages/newtab/start.tsx +++ b/src/pages/newtab/start.tsx @@ -21,6 +21,7 @@ import { ShortcutsHelp } from '@components/ShortcutsHelp'; import { WhatsNew } from '@components/WhatsNew'; import clsx from 'clsx'; import { CompactModeProvider, useSizeSettings } from '@utils/compact'; +import { getAllCustomIcons } from '@utils/custom-icons'; const Start = () => { @@ -120,7 +121,7 @@ const Start = () => { } - {whatsNewVisible && setWhatsNewVisible(false)}> + {whatsNewVisible && setWhatsNewVisible(false)}> } @@ -155,6 +156,7 @@ storage.getOne('showLoadAnimation').then(showLoadAnimation => { // Fequently used in UI, preload to avoid flashes later requestIconsFamily('ion'); requestIconsFamily('fluent'); +getAllCustomIcons(); mountPage( diff --git a/src/plugins/weather/weather-plugin.tsx b/src/plugins/weather/weather-plugin.tsx index 3a41ccde..3eb31025 100644 --- a/src/plugins/weather/weather-plugin.tsx +++ b/src/plugins/weather/weather-plugin.tsx @@ -29,7 +29,6 @@ const formatTemperature = (valueInCelsius: number, to: Temperature, withUnit = t }; const formatSpeed = (speedInKmPerHour: number, to: Speed): string => { - console.log('Convert speed', speedInKmPerHour, 'to', to); if (to === 'km/h') return `${speedInKmPerHour.toFixed(1)} km/h`; if (to === 'm/s') return `${Math.round(speedInKmPerHour * (5 / 18))} m/s`; return `${(speedInKmPerHour * 0.6213).toFixed(1)} mph`; @@ -218,7 +217,6 @@ const useCurrentWeather = (config: PluginWidgetConfigType) => { } try { const weather = await gerCurrentWeather(config.location); - console.log('Got weather', weather); setWeather({ ...weather, lastUpdated: Date.now(), @@ -265,7 +263,6 @@ const useForecastWeather = (config: PluginWidgetConfigType) => { } try { const weather = await getForecast(config.location); - console.log('Got weather', weather); setForecast(weather); setLastUpdated(Date.now()); } catch (err) { diff --git a/src/utils/custom-icons.ts b/src/utils/custom-icons.ts new file mode 100644 index 00000000..da2bc451 --- /dev/null +++ b/src/utils/custom-icons.ts @@ -0,0 +1,137 @@ +import { useEffect } from "react"; +import { asyncIterableToArray } from "./misc"; +import { atom, getDefaultStore, useAtom } from "jotai"; + +const CUSTOM_ICONS_FOLDER_NAME = 'custom-icons'; + +export type CustomIcon = { + name: string, + urlObject: string, +}; + +export const CUSTOM_ICONS_AVAILABLE = (() => { + if (typeof window === 'undefined') return true; + if (navigator.userAgent.includes('Firefox/')) { + const version = parseInt(navigator.userAgent.split('Firefox/')[1].split('.')[0]); + return version >= 111; + } else { + return true; + } +})(); + +const iconsCache: Record = {}; +const iconsAtom = atom([]); + +const getIconsDirHandle = async () => { + const opfsRoot = await navigator.storage.getDirectory(); + const iconsDir = await opfsRoot.getDirectoryHandle(CUSTOM_ICONS_FOLDER_NAME, { create: true }); + return iconsDir; +} + +export const getAllCustomIcons = async (): Promise => { + const files = await getAllCustomIconFiles(); + const icons: CustomIcon[] = await Promise.all( + files.sort((a, b) => a.name.localeCompare(b.name)).map(async (handle) => { + const file = await (handle as FileSystemFileHandle).getFile(); + const urlObject = iconsCache[handle.name] ? iconsCache[handle.name] : URL.createObjectURL(file); + if (!iconsCache[handle.name]) iconsCache[handle.name] = urlObject; + return { + name: handle.name, + urlObject + }; + }) + ); + getDefaultStore().set(iconsAtom, icons); + return icons; +}; + +export const getAllCustomIconFiles = async () => { + const iconsDir = await getIconsDirHandle(); + const files = await asyncIterableToArray(iconsDir.values()); + return files.filter(h => h.kind === 'file') as FileSystemFileHandle[]; +}; + +export const removeAllCustomIcons = async () => { + const opfsRoot = await navigator.storage.getDirectory(); + await opfsRoot.removeEntry(CUSTOM_ICONS_FOLDER_NAME, {recursive: true}); +}; + +export const getCustomIcon = async (name: string): Promise => { + if (iconsCache[name]) { + return { + name, + urlObject: iconsCache[name], + } + } + + const iconsDir = await getIconsDirHandle(); + try { + const fileHandle = await iconsDir.getFileHandle(name); + const file = await fileHandle.getFile(); + const urlObject = URL.createObjectURL(file); + iconsCache[name] = urlObject; + return { + name, + urlObject, + }; + } catch (err) { + return null; + } +}; + +export const getCustomIconFromCache = (name: string): CustomIcon | null => { + if (iconsCache[name]) { + return { + name, + urlObject: iconsCache[name], + } + } + + return null; +}; + +export const useCustomIcon = (name: string) => { + const { customIcons } = useCustomIcons(); + + return customIcons.find(i => i.name === name); +}; + +export const useCustomIcons = () => { + const addNewCustomIcon = async (filename: string, content: ArrayBuffer, urlObj?: string) => { + const iconsDir = await getIconsDirHandle(); + const fileHandle = await iconsDir.getFileHandle(filename, { create: true }); + const writeHandle = await fileHandle.createWritable(); + await writeHandle.write(content); + await writeHandle.close(); + + const urlObjFinal = urlObj || URL.createObjectURL(new Blob([content])); + iconsCache[filename] = urlObjFinal; + setIcons(p => [...p.filter(i => i.name !== filename), { name: filename, urlObject: urlObjFinal }].sort((a, b) => a.name.localeCompare(b.name))); + }; + + const removeCustomIcon = async (filename: string) => { + const iconsDir = await getIconsDirHandle(); + await iconsDir.removeEntry(filename); + setIcons(p => p.filter(icon => icon.name !== filename)); + if (iconsCache[filename]) { + URL.revokeObjectURL(iconsCache[filename]); + delete iconsCache[filename]; + } + } + + const [icons, setIcons] = useAtom(iconsAtom); + + useEffect(() => { + getAllCustomIcons().then(icons => setIcons(icons)); + }, []); + + return { + customIcons: icons, + addNewCustomIcon, + removeCustomIcon, + }; +}; + +export const isValidCustomIconName = (name: string) => { + return (/^[\w\-_]+$/).test(name); +} \ No newline at end of file diff --git a/src/utils/files.ts b/src/utils/files.ts new file mode 100644 index 00000000..e5069625 --- /dev/null +++ b/src/utils/files.ts @@ -0,0 +1,29 @@ +export const showOpenFilePicker = (multiple = false, accept?: string): Promise => { + return new Promise((resolve) => { + const input = document.createElement('input'); + input.type = 'file'; + if (multiple) input.multiple = true; + if (accept) input.accept = accept; + input.addEventListener('change', (e) => { + resolve(Array.from((e.target as HTMLInputElement).files!)); + }); + + input.click(); + }); +}; + +export const downloadTextFile = (name: string, content: string) => { + downloadBlob(name, new Blob([content], { + type: 'text/plain' + })); +}; + +export const downloadBlob = (name: string, blob: Blob) => { + const aElement = document.createElement('a'); + aElement.setAttribute('download', name); + const href = URL.createObjectURL(blob); + aElement.href = href; + aElement.setAttribute('target', '_blank'); + aElement.click(); + URL.revokeObjectURL(href); +}; \ No newline at end of file diff --git a/src/utils/misc.ts b/src/utils/misc.ts index ff540006..2347bdd3 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -19,4 +19,12 @@ export const parseHost = (url: string) => { } catch (err) { return `Couldn't parse hostname` } +}; + +export const asyncIterableToArray = async (iter: AsyncIterable): Promise => { + const res: T[] = []; + for await (const val of iter) { + res.push(val); + } + return res; }; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 3ee4b068..3c98ef75 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,5 @@ { + "include": ["declarations.d.ts", "src/**/*"], "compilerOptions": { "lib": ["es6", "es2019", "dom", "dom.iterable"], "baseUrl": ".", @@ -17,7 +18,7 @@ "jsx": "preserve", // Will be compiled with Babel anyway "allowJs": true, "moduleResolution": "node", - "resolveJsonModule": true + "resolveJsonModule": true, }, "ts-node": { diff --git a/yarn.lock b/yarn.lock index a9e58ceb..a1f8e6f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -981,6 +981,18 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.0.tgz#f6f729b02feee2c749f57e334b7a1b5f40a81724" + integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ== + "@eslint/eslintrc@^1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e" @@ -1749,88 +1761,88 @@ tapable "^2.2.0" webpack "^5" -"@typescript-eslint/eslint-plugin@^5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz#fb48c31cadc853ffc1dc35373f56b5e2a8908fe9" - integrity sha512-vwksQWSFZiUhgq3Kv7o1Jcj0DUNylwnIlGvKvLLYsq8pAWha6/WCnXUeaSoNNha/K7QSf2+jvmkxggC1u3pIwQ== +"@typescript-eslint/eslint-plugin@^5.59.0": + version "5.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.0.tgz#c0e10eeb936debe5d1c3433cf36206a95befefd0" + integrity sha512-p0QgrEyrxAWBecR56gyn3wkG15TJdI//eetInP3zYRewDh0XS+DhB3VUAd3QqvziFsfaQIoIuZMxZRB7vXYaYw== dependencies: - "@typescript-eslint/scope-manager" "5.50.0" - "@typescript-eslint/type-utils" "5.50.0" - "@typescript-eslint/utils" "5.50.0" + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.59.0" + "@typescript-eslint/type-utils" "5.59.0" + "@typescript-eslint/utils" "5.59.0" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" natural-compare-lite "^1.4.0" - regexpp "^3.2.0" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.50.0.tgz#a33f44b2cc83d1b7176ec854fbecd55605b0b032" - integrity sha512-KCcSyNaogUDftK2G9RXfQyOCt51uB5yqC6pkUYqhYh8Kgt+DwR5M0EwEAxGPy/+DH6hnmKeGsNhiZRQxjH71uQ== +"@typescript-eslint/parser@^5.59.0": + version "5.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.0.tgz#0ad7cd019346cc5d150363f64869eca10ca9977c" + integrity sha512-qK9TZ70eJtjojSUMrrEwA9ZDQ4N0e/AuoOIgXuNBorXYcBDk397D2r5MIe1B3cok/oCtdNC5j+lUUpVB+Dpb+w== dependencies: - "@typescript-eslint/scope-manager" "5.50.0" - "@typescript-eslint/types" "5.50.0" - "@typescript-eslint/typescript-estree" "5.50.0" + "@typescript-eslint/scope-manager" "5.59.0" + "@typescript-eslint/types" "5.59.0" + "@typescript-eslint/typescript-estree" "5.59.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz#90b8a3b337ad2c52bbfe4eac38f9164614e40584" - integrity sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg== +"@typescript-eslint/scope-manager@5.59.0": + version "5.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.0.tgz#86501d7a17885710b6716a23be2e93fc54a4fe8c" + integrity sha512-tsoldKaMh7izN6BvkK6zRMINj4Z2d6gGhO2UsI8zGZY3XhLq1DndP3Ycjhi1JwdwPRwtLMW4EFPgpuKhbCGOvQ== dependencies: - "@typescript-eslint/types" "5.50.0" - "@typescript-eslint/visitor-keys" "5.50.0" + "@typescript-eslint/types" "5.59.0" + "@typescript-eslint/visitor-keys" "5.59.0" -"@typescript-eslint/type-utils@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.50.0.tgz#509d5cc9728d520008f7157b116a42c5460e7341" - integrity sha512-dcnXfZ6OGrNCO7E5UY/i0ktHb7Yx1fV6fnQGGrlnfDhilcs6n19eIRcvLBqx6OQkrPaFlDPk3OJ0WlzQfrV0bQ== +"@typescript-eslint/type-utils@5.59.0": + version "5.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.0.tgz#8e8d1420fc2265989fa3a0d897bde37f3851e8c9" + integrity sha512-d/B6VSWnZwu70kcKQSCqjcXpVH+7ABKH8P1KNn4K7j5PXXuycZTPXF44Nui0TEm6rbWGi8kc78xRgOC4n7xFgA== dependencies: - "@typescript-eslint/typescript-estree" "5.50.0" - "@typescript-eslint/utils" "5.50.0" + "@typescript-eslint/typescript-estree" "5.59.0" + "@typescript-eslint/utils" "5.59.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.50.0.tgz#c461d3671a6bec6c2f41f38ed60bd87aa8a30093" - integrity sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w== +"@typescript-eslint/types@5.59.0": + version "5.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.0.tgz#3fcdac7dbf923ec5251545acdd9f1d42d7c4fe32" + integrity sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA== -"@typescript-eslint/typescript-estree@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz#0b9b82975bdfa40db9a81fdabc7f93396867ea97" - integrity sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow== +"@typescript-eslint/typescript-estree@5.59.0": + version "5.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.0.tgz#8869156ee1dcfc5a95be3ed0e2809969ea28e965" + integrity sha512-sUNnktjmI8DyGzPdZ8dRwW741zopGxltGs/SAPgGL/AAgDpiLsCFLcMNSpbfXfmnNeHmK9h3wGmCkGRGAoUZAg== dependencies: - "@typescript-eslint/types" "5.50.0" - "@typescript-eslint/visitor-keys" "5.50.0" + "@typescript-eslint/types" "5.59.0" + "@typescript-eslint/visitor-keys" "5.59.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.50.0.tgz#807105f5ffb860644d30d201eefad7017b020816" - integrity sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw== +"@typescript-eslint/utils@5.59.0": + version "5.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.0.tgz#063d066b3bc4850c18872649ed0da9ee72d833d5" + integrity sha512-GGLFd+86drlHSvPgN/el6dRQNYYGOvRSDVydsUaQluwIW3HvbXuxyuD5JETvBt/9qGYe+lOrDk6gRrWOHb/FvA== dependencies: + "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.50.0" - "@typescript-eslint/types" "5.50.0" - "@typescript-eslint/typescript-estree" "5.50.0" + "@typescript-eslint/scope-manager" "5.59.0" + "@typescript-eslint/types" "5.59.0" + "@typescript-eslint/typescript-estree" "5.59.0" eslint-scope "^5.1.1" - eslint-utils "^3.0.0" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz#b752ffc143841f3d7bc57d6dd01ac5c40f8c4903" - integrity sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg== +"@typescript-eslint/visitor-keys@5.59.0": + version "5.59.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.0.tgz#a59913f2bf0baeb61b5cfcb6135d3926c3854365" + integrity sha512-qZ3iXxQhanchCeaExlKPV3gDQFxMUmU35xfd5eCXB6+kUw1TUAbIy2n7QIrwz9s98DQLzNWyHp61fY0da4ZcbA== dependencies: - "@typescript-eslint/types" "5.50.0" + "@typescript-eslint/types" "5.59.0" eslint-visitor-keys "^3.3.0" "@webassemblyjs/ast@1.11.1": @@ -3216,6 +3228,11 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + immutable@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" @@ -3538,6 +3555,16 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -3563,6 +3590,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -3937,6 +3971,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -4276,6 +4315,19 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readdir-glob@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.2.tgz#b185789b8e6a43491635b6953295c5c5e3fd224c" @@ -4512,6 +4564,11 @@ serialize-javascript@^6.0.0: dependencies: randombytes "^2.1.0" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" @@ -4858,10 +4915,10 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -typescript@^4.9.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" + integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== unbox-primitive@^1.0.2: version "1.0.2"