From 33e8f2188d4ef8234ce77f882fb75c3a8f991267 Mon Sep 17 00:00:00 2001 From: nikec <43032218+niikeec@users.noreply.github.com> Date: Fri, 7 Jul 2023 12:57:25 +0200 Subject: [PATCH 01/18] [ENG-889] Display options based on selected view (#1079) * Update slider color * Update tooltip color * Display options based on view --- .../app/$libraryId/Explorer/OptionsPanel.tsx | 197 +++++++++--------- packages/ui/src/CheckBox.tsx | 5 +- packages/ui/src/Slider.tsx | 2 +- packages/ui/src/Tooltip.tsx | 4 +- packages/ui/style/colors.scss | 3 + packages/ui/style/tailwind.js | 3 +- 6 files changed, 113 insertions(+), 101 deletions(-) diff --git a/interface/app/$libraryId/Explorer/OptionsPanel.tsx b/interface/app/$libraryId/Explorer/OptionsPanel.tsx index 4d35225d40a3..c68df64d769d 100644 --- a/interface/app/$libraryId/Explorer/OptionsPanel.tsx +++ b/interface/app/$libraryId/Explorer/OptionsPanel.tsx @@ -3,7 +3,6 @@ import { type SortOrder, SortOrderSchema } from '~/app/route-schemas'; import { getExplorerConfigStore, useExplorerConfigStore } from './config'; import { FilePathSearchOrderingKeys, getExplorerStore, useExplorerStore } from './store'; -const Heading = tw.div`text-ink-dull text-xs font-semibold`; const Subheading = tw.div`text-ink-dull mb-1 text-xs font-medium`; export const sortOptions: Record = { @@ -21,106 +20,114 @@ export default () => { const explorerConfig = useExplorerConfigStore(); return ( -
- Item size - {explorerStore.layoutMode === 'media' ? ( - { - if (val !== undefined) { - getExplorerStore().mediaColumns = 10 - val; +
+ {(explorerStore.layoutMode === 'grid' || explorerStore.layoutMode === 'media') && ( +
+ Item size + {explorerStore.layoutMode === 'grid' ? ( + { + getExplorerStore().gridItemSize = value[0] || 100; + }} + defaultValue={[explorerStore.gridItemSize]} + max={200} + step={10} + min={60} + /> + ) : ( + { + if (val !== undefined) { + getExplorerStore().mediaColumns = 10 - val; + } + }} + /> + )} +
+ )} + {(explorerStore.layoutMode === 'grid' || explorerStore.layoutMode === 'media') && ( +
+
+ Sort by + +
+ +
+ Direction + +
+
+ )} + + {explorerStore.layoutMode === 'grid' && ( + { + if (typeof value === 'boolean') { + getExplorerStore().showBytesInGridView = value; } }} - /> - ) : ( - { - getExplorerStore().gridItemSize = value[0] || 100; - }} - defaultValue={[explorerStore.gridItemSize]} - max={200} - step={10} - min={60} + className="mt-1" /> )} -
-
- Sort by - -
-
- Direction - -
-
- -
- {explorerStore.layoutMode === 'media' ? ( - { - if (typeof value === 'boolean') { - getExplorerStore().mediaAspectSquare = value; - } - }} - /> - ) : ( - { - if (typeof value === 'boolean') { - getExplorerStore().showBytesInGridView = value; - } - }} - /> - )} -
- Double click action - -
+ }} + className="mt-1" + /> + )} +
+ Double click action +
); diff --git a/packages/ui/src/CheckBox.tsx b/packages/ui/src/CheckBox.tsx index 0e6051a29a91..afad9b046cae 100644 --- a/packages/ui/src/CheckBox.tsx +++ b/packages/ui/src/CheckBox.tsx @@ -1,5 +1,6 @@ import * as Checkbox from '@radix-ui/react-checkbox'; import { VariantProps, cva } from 'class-variance-authority'; +import clsx from 'clsx'; import { Check } from 'phosphor-react'; import { ComponentProps, forwardRef } from 'react'; @@ -24,8 +25,8 @@ export interface RadixCheckboxProps extends ComponentProps } // TODO: Replace above with this, requires refactor of usage -export const RadixCheckbox = (props: RadixCheckboxProps) => ( -
+export const RadixCheckbox = ({ className, ...props }: RadixCheckboxProps) => ( +
( {...props} className={clsx('relative flex h-6 w-full select-none items-center', props.className)} > - + { - + {props.label} )} diff --git a/packages/ui/style/colors.scss b/packages/ui/style/colors.scss index 476234b56f4e..3fbbf21e6c3f 100644 --- a/packages/ui/style/colors.scss +++ b/packages/ui/style/colors.scss @@ -41,6 +41,7 @@ --color-app-active: var(--dark-hue), 15%, 30%; --color-app-shade: var(--dark-hue), 15%, 0%; --color-app-frame: var(--dark-hue), 15%, 25%; + --color-app-slider: var(--dark-hue), 15%, 20%; // menu --color-menu: var(--dark-hue), 25%, 5%; --color-menu-line: var(--dark-hue), 15%, 11%; @@ -80,6 +81,7 @@ --color-app-dark-box: var(--light-hue), 5%, 97%; --color-app-light-box: var(--light-hue), 5%, 100%; --color-app-overlay: var(--light-hue), 5%, 100%; + --color-app-overlay-shade: var(--light-hue), 15%, 95%; --color-app-input: var(--light-hue), 5%, 100%; --color-app-focus: var(--light-hue), 5%, 98%; --color-app-line: var(--light-hue), 5%, 90%; @@ -91,6 +93,7 @@ --color-app-active: var(--light-hue), 5%, 87%; --color-app-shade: var(--light-hue), 15%, 50%; --color-app-frame: 0, 0%, 100%; + --color-app-slider: var(--light-hue), 5%, 95%; // menu --color-menu: var(--light-hue), 5%, 100%; --color-menu-line: var(--light-hue), 5%, 95%; diff --git a/packages/ui/style/tailwind.js b/packages/ui/style/tailwind.js index 23393a958341..37a8fef68cf2 100644 --- a/packages/ui/style/tailwind.js +++ b/packages/ui/style/tailwind.js @@ -77,7 +77,8 @@ module.exports = function (app, options) { hover: alpha('--color-app-hover'), active: alpha('--color-app-active'), shade: alpha('--color-app-shade'), - frame: alpha('--color-app-frame') + frame: alpha('--color-app-frame'), + slider: alpha('--color-app-slider') }, menu: { DEFAULT: alpha('--color-menu'), From 5df1d9a091fec792c72fff45ef9acc5afd4a6a2b Mon Sep 17 00:00:00 2001 From: nikec <43032218+niikeec@users.noreply.github.com> Date: Fri, 7 Jul 2023 17:48:08 +0200 Subject: [PATCH 02/18] [ENG-893] Fix categories offset (#1086) Fix categories offset --- .../app/$libraryId/overview/Categories.tsx | 135 ++++++++++-------- .../app/$libraryId/overview/Statistics.tsx | 2 +- interface/package.json | 1 + pnpm-lock.yaml | 26 +++- 4 files changed, 94 insertions(+), 70 deletions(-) diff --git a/interface/app/$libraryId/overview/Categories.tsx b/interface/app/$libraryId/overview/Categories.tsx index c65cd885d181..ace0bce696d2 100644 --- a/interface/app/$libraryId/overview/Categories.tsx +++ b/interface/app/$libraryId/overview/Categories.tsx @@ -3,10 +3,13 @@ import clsx from 'clsx'; import { motion } from 'framer-motion'; import { ArrowLeft, ArrowRight } from 'phosphor-react'; import { RefObject, useEffect, useRef, useState } from 'react'; +import Sticky from 'react-sticky-el'; import { useDraggable } from 'react-use-draggable-scroll'; import { Category, useLibraryQuery } from '@sd/client'; +import { tw } from '@sd/ui'; import { useIsDark } from '~/hooks'; import { useLayoutContext } from '../Layout/Context'; +import { usePageLayoutContext } from '../PageLayout/Context'; import CategoryButton from './CategoryButton'; import { IconForCategory } from './data'; @@ -30,9 +33,13 @@ const CategoryList = [ 'Trash' ] as Category[]; +const ArrowButton = tw.div`absolute top-1/2 z-40 flex h-8 w-8 shrink-0 -translate-y-1/2 items-center p-2 cursor-pointer justify-center rounded-full border border-app-line bg-app/50 hover:opacity-95 backdrop-blur-md transition-all duration-200`; + export const Categories = (props: { selected: Category; onSelectedChanged(c: Category): void }) => { const isDark = useIsDark(); + const { ref: pageRef } = usePageLayoutContext(); + const ref = useRef(null); const { events } = useDraggable(ref as React.MutableRefObject); @@ -56,70 +63,74 @@ export const Categories = (props: { selected: Category; onSelectedChanged(c: Cat index === CategoryList.length - 1 && setLastCategoryVisible((prev) => !prev); }; + const maskImage = `linear-gradient(90deg, transparent 0.1%, rgba(0, 0, 0, 1) ${ + scroll > 0 ? '10%' : '0%' + }, rgba(0, 0, 0, 1) ${lastCategoryVisible ? '95%' : '85%'}, transparent 99%)`; + return ( -
-
handleArrowOnClick('right')} - className={clsx( - scroll > 0 - ? 'cursor-pointer bg-app/50 opacity-100 hover:opacity-95' - : 'pointer-events-none', - 'sticky left-[15px] z-40 -ml-4 mt-4 flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-app-line bg-app p-2 opacity-0 backdrop-blur-md transition-all duration-200' - )} - > - -
-
0 ? '10%' : '0%' - }, rgba(0, 0, 0, 1) ${lastCategoryVisible ? '95%' : '85%'}, transparent 99%)` - }} - > - {categories.data && - CategoryList.map((category, index) => { - const iconString = IconForCategory[category] || 'Document'; - return ( - lastCategoryVisibleHandler(index)} - onViewportLeave={() => lastCategoryVisibleHandler(index)} - viewport={{ - root: ref, - // WARNING: Edge breaks if the values are not postfixed with px or % - margin: '0% -120px 0% 0%' - }} - className={clsx( - 'min-w-fit', - mouseState !== 'dragging' && '!cursor-default' - )} - key={category} - > - props.onSelectedChanged(category)} - /> - - ); - })} -
-
handleArrowOnClick('left')} - className={clsx( - lastCategoryVisible - ? 'pointer-events-none opacity-0 hover:opacity-0' - : 'hover:opacity-95', - 'sticky right-[15px] z-40 mt-4 flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-full border border-app-line bg-app/50 p-2 backdrop-blur-md transition-all duration-200' - )} - > - + +
+ handleArrowOnClick('right')} + className={clsx('left-3', scroll === 0 && 'pointer-events-none opacity-0')} + > + + + +
+ {categories.data && + CategoryList.map((category, index) => { + const iconString = IconForCategory[category] || 'Document'; + return ( + lastCategoryVisibleHandler(index)} + onViewportLeave={() => lastCategoryVisibleHandler(index)} + viewport={{ + root: ref, + // WARNING: Edge breaks if the values are not postfixed with px or % + margin: '0% -120px 0% 0%' + }} + className={clsx( + 'min-w-fit', + mouseState !== 'dragging' && '!cursor-default' + )} + key={category} + > + props.onSelectedChanged(category)} + /> + + ); + })} +
+ + handleArrowOnClick('left')} + className={clsx( + 'right-3', + lastCategoryVisible && 'pointer-events-none opacity-0' + )} + > + +
-
+ ); }; diff --git a/interface/app/$libraryId/overview/Statistics.tsx b/interface/app/$libraryId/overview/Statistics.tsx index f3936f42d04a..48c88eaa32e7 100644 --- a/interface/app/$libraryId/overview/Statistics.tsx +++ b/interface/app/$libraryId/overview/Statistics.tsx @@ -106,7 +106,7 @@ export default () => { }); mounted = true; return ( -
+
{/* STAT CONTAINER */}
{Object.entries(stats?.data || []).map(([key, value]) => { diff --git a/interface/package.json b/interface/package.json index 23c0a0dd4d3e..6ed9841c47f7 100644 --- a/interface/package.json +++ b/interface/package.json @@ -58,6 +58,7 @@ "react-router-dom": "6.9.0", "react-scroll-sync": "^0.11.0", "react-selecto": "^1.22.3", + "react-sticky-el": "^2.1.0", "react-use-draggable-scroll": "^0.4.7", "remix-params-helper": "^0.4.10", "rooks": "^5.14.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ec00c3eb274..f1935a70039d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,7 +49,7 @@ importers: version: 5.0.4 vite: specifier: ^4.3.9 - version: 4.3.9(@types/node@18.15.1) + version: 4.3.9(less@4.1.3) apps/desktop: dependencies: @@ -563,7 +563,7 @@ importers: version: 5.0.4 vite: specifier: ^4.0.4 - version: 4.3.9(@types/node@18.15.1) + version: 4.3.9(less@4.1.3) vite-plugin-html: specifier: ^3.2.0 version: 3.2.0(vite@4.3.9) @@ -603,7 +603,7 @@ importers: version: 4.8.2 vite: specifier: ^4.0.4 - version: 4.3.9(@types/node@18.15.1) + version: 4.3.9(less@4.1.3) interface: dependencies: @@ -730,6 +730,9 @@ importers: react-selecto: specifier: ^1.22.3 version: 1.22.3 + react-sticky-el: + specifier: ^2.1.0 + version: 2.1.0(react-dom@18.2.0)(react@18.2.0) react-use-draggable-scroll: specifier: ^0.4.7 version: 0.4.7(react@18.2.0) @@ -10600,7 +10603,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.22.1) magic-string: 0.26.7 react-refresh: 0.14.0 - vite: 4.3.9(sass@1.55.0) + vite: 4.3.9(@types/node@18.15.1) transitivePeerDependencies: - supports-color @@ -20946,6 +20949,16 @@ packages: react: 18.2.0 react-is: 18.2.0 + /react-sticky-el@2.1.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-oo+a2GedF4QMfCfm20e9gD+RuuQp/ngvwGMUXAXpST+h4WnmKhuv7x6MQ4X/e3AHiLYgE0zDyJo1Pzo8m51KpA==} + peerDependencies: + react: '>=16.3.0' + react-dom: '>=16.3.0' + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-style-singleton@2.2.1(@types/react@18.0.38)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -24143,7 +24156,7 @@ packages: dependencies: '@rollup/pluginutils': 4.2.1 '@svgr/core': 6.5.1 - vite: 4.3.9(sass@1.55.0) + vite: 4.3.9(@types/node@18.15.1) transitivePeerDependencies: - supports-color dev: true @@ -24157,7 +24170,7 @@ packages: globrex: 0.1.2 recrawl-sync: 2.2.3 tsconfig-paths: 4.2.0 - vite: 4.3.9(@types/node@18.15.1) + vite: 4.3.9(less@4.1.3) transitivePeerDependencies: - supports-color dev: true @@ -24240,7 +24253,6 @@ packages: rollup: 3.26.0 optionalDependencies: fsevents: 2.3.2 - dev: true /vite@4.3.9(less@4.1.3): resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} From 121b5b4bfee6a1e5ab87870c7296e7675d1184d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Tue, 11 Jul 2023 04:20:38 -0300 Subject: [PATCH 03/18] Fix clippy not running for some rust changes (#1088) --- .github/workflows/ci.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c902d85c2284..c66affb171b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,8 +55,16 @@ jobs: with: filters: | changes: + - 'apps/cli/*/**' + - 'apps/desktop/crates/*/**' + - 'apps/desktop/src-tauri/*/**' + - 'apps/mobile/crates/*/**' + - 'apps/server/*/**' - 'core/**' - 'crates/*/**' + - 'extensions/*/**' + - 'Cargo.toml' + - 'Cargo.lock' - name: Setup Rust and Prisma if: steps.filter.outputs.changes == 'true' @@ -81,8 +89,16 @@ jobs: with: filters: | changes: + - 'apps/cli/*/**' + - 'apps/desktop/crates/*/**' + - 'apps/desktop/src-tauri/*/**' + - 'apps/mobile/crates/*/**' + - 'apps/server/*/**' - 'core/**' - 'crates/*/**' + - 'extensions/*/**' + - 'Cargo.toml' + - 'Cargo.lock' - name: Setup System and Rust if: steps.filter.outputs.changes == 'true' From 7cd00dab49ef405fb5679bd8bb472ff4abdb53cb Mon Sep 17 00:00:00 2001 From: "Ericson \"Fogo\" Soares" Date: Tue, 11 Jul 2023 12:14:11 -0300 Subject: [PATCH 04/18] [ENG-884] Update file_path metadata (#1077) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Partitioned paths to be updated on walker * Updating file_paths in indexer * Properly tracking modified date on renames --------- Co-authored-by: Vítor Vasconcellos --- core/src/api/locations.rs | 27 ++-- .../isolated_file_path_data.rs | 5 +- core/src/location/file_path_helper/mod.rs | 16 ++- core/src/location/indexer/indexer_job.rs | 108 ++++++++++++-- core/src/location/indexer/mod.rs | 108 +++++++++++++- core/src/location/indexer/shallow.rs | 33 +++-- core/src/location/indexer/walk.rs | 135 ++++++++++++++---- core/src/location/manager/mod.rs | 5 - core/src/location/manager/watcher/linux.rs | 13 +- core/src/location/manager/watcher/macos.rs | 13 +- core/src/location/manager/watcher/utils.rs | 41 +++--- core/src/location/manager/watcher/windows.rs | 33 ++++- core/src/util/db.rs | 20 +++ 13 files changed, 440 insertions(+), 117 deletions(-) diff --git a/core/src/api/locations.rs b/core/src/api/locations.rs index 47da8048c9f9..e1b73978b4f7 100644 --- a/core/src/api/locations.rs +++ b/core/src/api/locations.rs @@ -14,7 +14,6 @@ use std::path::PathBuf; use rspc::{self, alpha::AlphaRouter, ErrorCode}; use serde::{Deserialize, Serialize}; use specta::Type; -use tracing::info; use super::{utils::library, Ctx, R}; @@ -141,28 +140,20 @@ pub(crate) fn mount() -> AlphaRouter { reidentify_objects, }| async move { if reidentify_objects { - let object_ids = library + library .db .file_path() - .find_many(vec![ - file_path::location_id::equals(Some(location_id)), - file_path::object_id::not(None), - ]) - .select(file_path::select!({ object_id })) - .exec() - .await? - .into_iter() - .filter_map(|file_path| file_path.object_id) - .collect::>(); - - let count = library - .db - .object() - .delete_many(vec![object::id::in_vec(object_ids)]) + .update_many( + vec![ + file_path::location_id::equals(Some(location_id)), + file_path::object_id::not(None), + ], + vec![file_path::object::disconnect()], + ) .exec() .await?; - info!("Deleted {count} objects, to be reidentified"); + library.orphan_remover.invoke().await; } // rescan location diff --git a/core/src/location/file_path_helper/isolated_file_path_data.rs b/core/src/location/file_path_helper/isolated_file_path_data.rs index 95b0141a6112..b14c39e9cf06 100644 --- a/core/src/location/file_path_helper/isolated_file_path_data.rs +++ b/core/src/location/file_path_helper/isolated_file_path_data.rs @@ -16,12 +16,12 @@ use serde::{Deserialize, Serialize}; use super::{ file_path_for_file_identifier, file_path_for_object_validator, file_path_for_thumbnailer, file_path_to_full_path, file_path_to_handle_custom_uri, file_path_to_isolate, - file_path_to_isolate_with_id, file_path_with_object, FilePathError, + file_path_to_isolate_with_id, file_path_walker, file_path_with_object, FilePathError, }; static FORBIDDEN_FILE_NAMES: OnceLock = OnceLock::new(); -#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq)] #[non_exhaustive] pub struct IsolatedFilePathData<'a> { // WARN! These fields MUST NOT be changed outside the location module, that's why they have this visibility @@ -446,6 +446,7 @@ mod macros { impl_from_db!( file_path, file_path_to_isolate, + file_path_walker, file_path_to_isolate_with_id, file_path_with_object ); diff --git a/core/src/location/file_path_helper/mod.rs b/core/src/location/file_path_helper/mod.rs index a59b420ae1db..376f7e871b10 100644 --- a/core/src/location/file_path_helper/mod.rs +++ b/core/src/location/file_path_helper/mod.rs @@ -68,6 +68,17 @@ file_path::select!(file_path_to_isolate_with_id { name extension }); +file_path::select!(file_path_walker { + pub_id + location_id + materialized_path + is_dir + name + extension + date_modified + inode + device +}); file_path::select!(file_path_to_handle_custom_uri { materialized_path is_dir @@ -170,6 +181,7 @@ pub async fn create_file_path( cas_id: Option, metadata: FilePathMetadata, ) -> Result { + use crate::util::db::{device_to_db, inode_to_db}; use crate::{sync, util::db::uuid_to_bytes}; use sd_prisma::prisma; @@ -228,8 +240,8 @@ pub async fn create_file_path( materialized_path::set(Some(materialized_path.into_owned())), name::set(Some(name.into_owned())), extension::set(Some(extension.into_owned())), - inode::set(Some(metadata.inode.to_le_bytes().into())), - device::set(Some(metadata.device.to_le_bytes().into())), + inode::set(Some(inode_to_db(metadata.inode))), + device::set(Some(device_to_db(metadata.device))), cas_id::set(cas_id), is_dir::set(Some(is_dir)), size_in_bytes_bytes::set(Some(metadata.size_in_bytes.to_be_bytes().to_vec())), diff --git a/core/src/location/indexer/indexer_job.rs b/core/src/location/indexer/indexer_job.rs index 30717e47de85..308e01781034 100644 --- a/core/src/location/indexer/indexer_job.rs +++ b/core/src/location/indexer/indexer_job.rs @@ -29,10 +29,11 @@ use tokio::time::Instant; use tracing::info; use super::{ - execute_indexer_save_step, iso_file_path_factory, remove_non_existing_file_paths, + execute_indexer_save_step, execute_indexer_update_step, iso_file_path_factory, + remove_non_existing_file_paths, rules::IndexerRule, walk::{keep_walking, walk, ToWalkEntry, WalkResult}, - IndexerError, IndexerJobSaveStep, + IndexerError, IndexerJobSaveStep, IndexerJobUpdateStep, }; /// BATCH_SIZE is the number of files to index at each step, writing the chunk of files metadata in the database. @@ -69,8 +70,11 @@ pub struct IndexerJobRunMetadata { db_write_time: Duration, scan_read_time: Duration, total_paths: u64, + total_updated_paths: u64, total_save_steps: u64, + total_update_steps: u64, indexed_count: u64, + updated_count: u64, removed_count: u64, } @@ -79,7 +83,9 @@ impl JobRunMetadata for IndexerJobRunMetadata { self.db_write_time += new_data.db_write_time; self.scan_read_time += new_data.scan_read_time; self.total_paths += new_data.total_paths; + self.total_updated_paths += new_data.total_updated_paths; self.total_save_steps += new_data.total_save_steps; + self.total_update_steps += new_data.total_update_steps; self.indexed_count += new_data.indexed_count; self.removed_count += new_data.removed_count; } @@ -89,6 +95,7 @@ impl JobRunMetadata for IndexerJobRunMetadata { pub enum ScanProgress { ChunkCount(usize), SavedChunks(usize), + UpdatedChunks(usize), Message(String), } @@ -99,7 +106,9 @@ impl IndexerJobData { .into_iter() .map(|p| match p { ScanProgress::ChunkCount(c) => JobReportUpdate::TaskCount(c), - ScanProgress::SavedChunks(p) => JobReportUpdate::CompletedTaskCount(p), + ScanProgress::SavedChunks(p) | ScanProgress::UpdatedChunks(p) => { + JobReportUpdate::CompletedTaskCount(p) + } ScanProgress::Message(m) => JobReportUpdate::Message(m), }) .collect(), @@ -110,9 +119,9 @@ impl IndexerJobData { /// `IndexerJobStepInput` defines the action that should be executed in the current step #[derive(Serialize, Deserialize, Debug)] pub enum IndexerJobStepInput { - /// `IndexerJobStepEntry`. The size of this vector is given by the [`BATCH_SIZE`] constant. Save(IndexerJobSaveStep), Walk(ToWalkEntry), + Update(IndexerJobUpdateStep), } /// A `IndexerJob` is a stateful job that walks a directory and indexes all files. @@ -172,6 +181,7 @@ impl StatefulJob for IndexerJobInit { let scan_start = Instant::now(); let WalkResult { walked, + to_update, to_walk, to_remove, errors, @@ -192,8 +202,11 @@ impl StatefulJob for IndexerJobInit { let removed_count = remove_non_existing_file_paths(to_remove, &db).await?; let db_delete_time = db_delete_start.elapsed(); - let total_paths = &mut 0; + let total_new_paths = &mut 0; + let total_updated_paths = &mut 0; let to_walk_count = to_walk.len(); + let to_save_chunks = &mut 0; + let to_update_chunks = &mut 0; let steps = walked .chunks(BATCH_SIZE) @@ -202,22 +215,41 @@ impl StatefulJob for IndexerJobInit { .map(|(i, chunk)| { let chunk_steps = chunk.collect::>(); - *total_paths += chunk_steps.len() as u64; + *total_new_paths += chunk_steps.len() as u64; + *to_save_chunks += 1; IndexerJobStepInput::Save(IndexerJobSaveStep { chunk_idx: i, walked: chunk_steps, }) }) + .chain( + to_update + .chunks(BATCH_SIZE) + .into_iter() + .enumerate() + .map(|(i, chunk)| { + let chunk_updates = chunk.collect::>(); + + *total_updated_paths += chunk_updates.len() as u64; + *to_update_chunks += 1; + + IndexerJobStepInput::Update(IndexerJobUpdateStep { + chunk_idx: i, + to_update: chunk_updates, + }) + }), + ) .chain(to_walk.into_iter().map(IndexerJobStepInput::Walk)) .collect::>(); IndexerJobData::on_scan_progress( ctx, vec![ - ScanProgress::ChunkCount(steps.len() - to_walk_count), + ScanProgress::ChunkCount(*to_save_chunks + *to_update_chunks), ScanProgress::Message(format!( - "Starting saving {total_paths} files or directories, \ + "Starting saving {total_new_paths} files or directories, \ + {total_updated_paths} files or directories to update, \ there still {to_walk_count} directories to index", )), ], @@ -232,10 +264,13 @@ impl StatefulJob for IndexerJobInit { IndexerJobRunMetadata { db_write_time: db_delete_time, scan_read_time, - total_paths: *total_paths, + total_paths: *total_new_paths, + total_updated_paths: *total_updated_paths, indexed_count: 0, + updated_count: 0, removed_count, - total_save_steps: steps.len() as u64 - to_walk_count as u64, + total_save_steps: *to_save_chunks as u64, + total_update_steps: *to_update_chunks as u64, }, steps, errors @@ -272,14 +307,34 @@ impl StatefulJob for IndexerJobInit { ], ); - let count = - execute_indexer_save_step(&init.location, step, &ctx.library.clone()).await?; + let count = execute_indexer_save_step(&init.location, step, &ctx.library).await?; new_metadata.indexed_count = count as u64; new_metadata.db_write_time = start_time.elapsed(); Ok(new_metadata.into()) } + IndexerJobStepInput::Update(to_update) => { + let start_time = Instant::now(); + IndexerJobData::on_scan_progress( + ctx, + vec![ + ScanProgress::UpdatedChunks(to_update.chunk_idx + 1), + ScanProgress::Message(format!( + "Updating chunk {} of {} to database", + to_update.chunk_idx, run_metadata.total_save_steps + )), + ], + ); + + let count = execute_indexer_update_step(to_update, &ctx.library).await?; + + new_metadata.updated_count = count as u64; + new_metadata.db_write_time = start_time.elapsed(); + + Ok(new_metadata.into()) + } + IndexerJobStepInput::Walk(to_walk_entry) => { let location_id = init.location.id; let location_path = @@ -291,6 +346,7 @@ impl StatefulJob for IndexerJobInit { let WalkResult { walked, + to_update, to_walk, to_remove, errors, @@ -320,12 +376,25 @@ impl StatefulJob for IndexerJobInit { .map(|(i, chunk)| { let chunk_steps = chunk.collect::>(); new_metadata.total_paths += chunk_steps.len() as u64; + new_metadata.total_save_steps += 1; IndexerJobStepInput::Save(IndexerJobSaveStep { chunk_idx: i, walked: chunk_steps, }) }) + .chain(to_update.chunks(BATCH_SIZE).into_iter().enumerate().map( + |(i, chunk)| { + let chunk_updates = chunk.collect::>(); + new_metadata.total_updated_paths += chunk_updates.len() as u64; + new_metadata.total_update_steps += 1; + + IndexerJobStepInput::Update(IndexerJobUpdateStep { + chunk_idx: i, + to_update: chunk_updates, + }) + }, + )) .chain(to_walk.into_iter().map(IndexerJobStepInput::Walk)) .collect::>(); @@ -334,8 +403,11 @@ impl StatefulJob for IndexerJobInit { vec![ ScanProgress::ChunkCount(more_steps.len() - to_walk_count), ScanProgress::Message(format!( - "Scanned more {} files or directories; {} more directories to scan", - new_metadata.total_paths, to_walk_count + "Scanned more {} files or directories; \ + {} more directories to scan and more {} entries to update", + new_metadata.total_paths, + to_walk_count, + new_metadata.total_updated_paths )), ], ); @@ -363,11 +435,12 @@ impl StatefulJob for IndexerJobInit { let init = self; info!( "Scan of {} completed in {:?}. {} new files found, \ - indexed {} files in db. db write completed in {:?}", + indexed {} files in db, updated {} entries. db write completed in {:?}", maybe_missing(&init.location.path, "location.path")?, run_metadata.scan_read_time, run_metadata.total_paths, run_metadata.indexed_count, + run_metadata.total_updated_paths, run_metadata.db_write_time, ); @@ -375,6 +448,11 @@ impl StatefulJob for IndexerJobInit { invalidate_query!(ctx.library, "search.paths"); } + if run_metadata.total_updated_paths > 0 { + // Invoking orphan remover here as we probably have some orphans objects due to updates + ctx.library.orphan_remover.invoke().await; + } + Ok(Some(json!({"init: ": init, "run_metadata": run_metadata}))) } } diff --git a/core/src/location/indexer/mod.rs b/core/src/location/indexer/mod.rs index 588b0ba465b4..c41225c46133 100644 --- a/core/src/location/indexer/mod.rs +++ b/core/src/location/indexer/mod.rs @@ -2,7 +2,10 @@ use crate::{ library::Library, prisma::{file_path, location, PrismaClient}, sync, - util::{db::uuid_to_bytes, error::FileIOError}, + util::{ + db::{device_to_db, inode_to_db, uuid_to_bytes}, + error::FileIOError, + }, }; use std::path::Path; @@ -37,6 +40,12 @@ pub struct IndexerJobSaveStep { walked: Vec, } +#[derive(Serialize, Deserialize, Debug)] +pub struct IndexerJobUpdateStep { + chunk_idx: usize, + to_update: Vec, +} + /// Error type for the indexer module #[derive(Error, Debug)] pub enum IndexerError { @@ -127,11 +136,11 @@ async fn execute_indexer_save_step( ), ( (inode::NAME, json!(entry.metadata.inode.to_le_bytes())), - inode::set(Some(entry.metadata.inode.to_le_bytes().into())), + inode::set(Some(inode_to_db(entry.metadata.inode))), ), ( (device::NAME, json!(entry.metadata.device.to_le_bytes())), - device::set(Some(entry.metadata.device.to_le_bytes().into())), + device::set(Some(device_to_db(entry.metadata.device))), ), ( (date_created::NAME, json!(entry.metadata.created_at)), @@ -176,6 +185,91 @@ async fn execute_indexer_save_step( Ok(count) } +async fn execute_indexer_update_step( + update_step: &IndexerJobUpdateStep, + library: &Library, +) -> Result { + let Library { sync, db, .. } = &library; + + let (sync_stuff, paths_to_update): (Vec<_>, Vec<_>) = update_step + .to_update + .iter() + .map(|entry| { + let IsolatedFilePathData { is_dir, .. } = &entry.iso_file_path; + + use file_path::*; + + let pub_id = uuid_to_bytes(entry.pub_id); + + let (sync_params, db_params): (Vec<_>, Vec<_>) = [ + // As this file was updated while Spacedrive was offline, we mark the object_id as null + // So this file_path will be updated at file identifier job + ( + (object_id::NAME, serde_json::Value::Null), + object::disconnect(), + ), + ((is_dir::NAME, json!(*is_dir)), is_dir::set(Some(*is_dir))), + ( + ( + size_in_bytes_bytes::NAME, + json!(entry.metadata.size_in_bytes.to_be_bytes().to_vec()), + ), + size_in_bytes_bytes::set(Some( + entry.metadata.size_in_bytes.to_be_bytes().to_vec(), + )), + ), + ( + (inode::NAME, json!(entry.metadata.inode.to_le_bytes())), + inode::set(Some(inode_to_db(entry.metadata.inode))), + ), + ( + (device::NAME, json!(entry.metadata.device.to_le_bytes())), + device::set(Some(device_to_db(entry.metadata.device))), + ), + ( + (date_created::NAME, json!(entry.metadata.created_at)), + date_created::set(Some(entry.metadata.created_at.into())), + ), + ( + (date_modified::NAME, json!(entry.metadata.modified_at)), + date_modified::set(Some(entry.metadata.modified_at.into())), + ), + ] + .into_iter() + .unzip(); + + ( + sync_params + .into_iter() + .map(|(field, value)| { + sync.shared_update( + sync::file_path::SyncId { + pub_id: pub_id.clone(), + }, + field, + value, + ) + }) + .collect::>(), + db.file_path() + .update(file_path::pub_id::equals(pub_id), db_params) + .select(file_path::select!({ id })), + ) + }) + .unzip(); + + let updated = sync + .write_ops( + db, + (sync_stuff.into_iter().flatten().collect(), paths_to_update), + ) + .await?; + + trace!("Updated {updated:?} records"); + + Ok(updated.len() as i64) +} + fn iso_file_path_factory( location_id: location::id::Type, location_path: &Path, @@ -200,7 +294,7 @@ async fn remove_non_existing_file_paths( } // TODO: Change this macro to a fn when we're able to return -// `impl Fn(Vec) -> impl Future, IndexerError>>` +// `impl Fn(Vec) -> impl Future, IndexerError>>` // Maybe when TAITs arrive #[macro_export] macro_rules! file_paths_db_fetcher_fn { @@ -216,8 +310,10 @@ macro_rules! file_paths_db_fetcher_fn { .into_iter() .map(|founds| { $db.file_path() - .find_many(founds.collect::>()) - .select($crate::location::file_path_helper::file_path_to_isolate::select()) + .find_many(vec![::prisma_client_rust::operator::or( + founds.collect::>(), + )]) + .select($crate::location::file_path_helper::file_path_walker::select()) }) .collect::>(); diff --git a/core/src/location/indexer/shallow.rs b/core/src/location/indexer/shallow.rs index df3e86537f6b..98ecbb5600e1 100644 --- a/core/src/location/indexer/shallow.rs +++ b/core/src/location/indexer/shallow.rs @@ -7,6 +7,7 @@ use crate::{ check_file_path_exists, ensure_sub_path_is_directory, ensure_sub_path_is_in_location, IsolatedFilePathData, }, + indexer::{execute_indexer_update_step, IndexerJobUpdateStep}, LocationError, }, to_remove_db_fetcher_fn, @@ -66,7 +67,7 @@ pub async fn shallow( (false, location_path.to_path_buf()) }; - let (walked, to_remove, errors) = { + let (walked, to_update, to_remove, errors) = { walk_single_dir( &to_walk_path, &indexer_rules, @@ -84,26 +85,32 @@ pub async fn shallow( // TODO pass these uuids to sync system remove_non_existing_file_paths(to_remove, &db).await?; - let total_paths = &mut 0; - - let steps = walked + let save_steps = walked .chunks(BATCH_SIZE) .into_iter() .enumerate() - .map(|(i, chunk)| { - let chunk_steps = chunk.collect::>(); + .map(|(i, chunk)| IndexerJobSaveStep { + chunk_idx: i, + walked: chunk.collect::>(), + }) + .collect::>(); - *total_paths += chunk_steps.len() as u64; + for step in save_steps { + execute_indexer_save_step(location, &step, library).await?; + } - IndexerJobSaveStep { - chunk_idx: i, - walked: chunk_steps, - } + let update_steps = to_update + .chunks(BATCH_SIZE) + .into_iter() + .enumerate() + .map(|(i, chunk)| IndexerJobUpdateStep { + chunk_idx: i, + to_update: chunk.collect::>(), }) .collect::>(); - for step in steps { - execute_indexer_save_step(location, &step, library).await?; + for step in update_steps { + execute_indexer_update_step(&step, library).await?; } invalidate_query!(library, "search.paths"); diff --git a/core/src/location/indexer/walk.rs b/core/src/location/indexer/walk.rs index 753cb73a6231..cd64a98c9797 100644 --- a/core/src/location/indexer/walk.rs +++ b/core/src/location/indexer/walk.rs @@ -1,10 +1,13 @@ use crate::{ location::file_path_helper::{ - file_path_just_pub_id, file_path_to_isolate, FilePathMetadata, IsolatedFilePathData, + file_path_just_pub_id, file_path_walker, FilePathMetadata, IsolatedFilePathData, MetadataExt, }, prisma::file_path, - util::error::FileIOError, + util::{ + db::{device_from_db, from_bytes_to_uuid, inode_from_db}, + error::FileIOError, + }, }; #[cfg(target_family = "unix")] @@ -14,12 +17,13 @@ use crate::location::file_path_helper::get_inode_and_device; use crate::location::file_path_helper::get_inode_and_device_from_path; use std::{ - collections::{HashSet, VecDeque}, + collections::{HashMap, HashSet, VecDeque}, future::Future, hash::{Hash, Hasher}, path::{Path, PathBuf}, }; +use chrono::{DateTime, Duration, FixedOffset}; use serde::{Deserialize, Serialize}; use tokio::fs; use tracing::trace; @@ -49,11 +53,44 @@ pub struct ToWalkEntry { parent_dir_accepted_by_its_children: Option, } +#[derive(Debug)] struct WalkingEntry { iso_file_path: IsolatedFilePathData<'static>, maybe_metadata: Option, } +impl From for WalkedEntry { + fn from(walking_entry: WalkingEntry) -> Self { + let WalkingEntry { + iso_file_path, + maybe_metadata, + } = walking_entry; + + Self { + pub_id: Uuid::new_v4(), + iso_file_path, + metadata: maybe_metadata + .expect("we always use Some in `the inner_walk_single_dir` function"), + } + } +} + +impl From<(Uuid, WalkingEntry)> for WalkedEntry { + fn from((pub_id, walking_entry): (Uuid, WalkingEntry)) -> Self { + let WalkingEntry { + iso_file_path, + maybe_metadata, + } = walking_entry; + + Self { + pub_id, + iso_file_path, + metadata: maybe_metadata + .expect("we always use Some in `the inner_walk_single_dir` function"), + } + } +} + impl PartialEq for WalkingEntry { fn eq(&self, other: &Self) -> bool { self.iso_file_path == other.iso_file_path @@ -68,12 +105,14 @@ impl Hash for WalkingEntry { } } -pub struct WalkResult +pub struct WalkResult where Walked: Iterator, + ToUpdate: Iterator, ToRemove: Iterator, { pub walked: Walked, + pub to_update: ToUpdate, pub to_walk: VecDeque, pub to_remove: ToRemove, pub errors: Vec, @@ -95,13 +134,14 @@ pub(super) async fn walk( limit: u64, ) -> Result< WalkResult< + impl Iterator, impl Iterator, impl Iterator, >, IndexerError, > where - FilePathDBFetcherFut: Future, IndexerError>>, + FilePathDBFetcherFut: Future, IndexerError>>, ToRemoveDbFetcherFut: Future, IndexerError>>, { let root = root.as_ref(); @@ -139,8 +179,11 @@ where } } + let (walked, to_update) = filter_existing_paths(indexed_paths, file_paths_db_fetcher).await?; + Ok(WalkResult { - walked: filter_existing_paths(indexed_paths, file_paths_db_fetcher).await?, + walked, + to_update, to_walk, to_remove: to_remove.into_iter().flatten(), errors, @@ -159,13 +202,14 @@ pub(super) async fn keep_walking( iso_file_path_factory: impl Fn(&Path, bool) -> Result, IndexerError>, ) -> Result< WalkResult< + impl Iterator, impl Iterator, impl Iterator, >, IndexerError, > where - FilePathDBFetcherFut: Future, IndexerError>>, + FilePathDBFetcherFut: Future, IndexerError>>, ToRemoveDbFetcherFut: Future, IndexerError>>, { let mut to_keep_walking = VecDeque::with_capacity(TO_WALK_QUEUE_INITIAL_CAPACITY); @@ -189,8 +233,11 @@ where ) .await; + let (walked, to_update) = filter_existing_paths(indexed_paths, file_paths_db_fetcher).await?; + Ok(WalkResult { - walked: filter_existing_paths(indexed_paths, file_paths_db_fetcher).await?, + walked, + to_update, to_walk: to_keep_walking, to_remove: to_remove.into_iter(), errors, @@ -210,6 +257,7 @@ pub(super) async fn walk_single_dir( add_root: bool, ) -> Result< ( + impl Iterator, impl Iterator, Vec, Vec, @@ -217,7 +265,7 @@ pub(super) async fn walk_single_dir( IndexerError, > where - FilePathDBFetcherFut: Future, IndexerError>>, + FilePathDBFetcherFut: Future, IndexerError>>, ToRemoveDbFetcherFut: Future, IndexerError>>, { let root = root.as_ref(); @@ -275,19 +323,23 @@ where ) .await; - Ok(( - filter_existing_paths(indexed_paths, file_paths_db_fetcher).await?, - to_remove, - errors, - )) + let (walked, to_update) = filter_existing_paths(indexed_paths, file_paths_db_fetcher).await?; + + Ok((walked, to_update, to_remove, errors)) } async fn filter_existing_paths( indexed_paths: HashSet, file_paths_db_fetcher: impl Fn(Vec) -> F, -) -> Result, IndexerError> +) -> Result< + ( + impl Iterator, + impl Iterator, + ), + IndexerError, +> where - F: Future, IndexerError>>, + F: Future, IndexerError>>, { if !indexed_paths.is_empty() { file_paths_db_fetcher( @@ -304,18 +356,47 @@ where .map(move |file_paths| { let isolated_paths_already_in_db = file_paths .into_iter() - .flat_map(IsolatedFilePathData::try_from) - .collect::>(); - - indexed_paths.into_iter().filter_map(move |entry| { - (!isolated_paths_already_in_db.contains(&entry.iso_file_path)).then(|| WalkedEntry { - pub_id: Uuid::new_v4(), - iso_file_path: entry.iso_file_path, - metadata: entry - .maybe_metadata - .expect("we always use Some in `the inner_walk_single_dir` function"), + .flat_map(|file_path| { + IsolatedFilePathData::try_from(file_path.clone()) + .map(|iso_file_path| (iso_file_path, file_path)) + }) + .collect::>(); + + let mut to_update = vec![]; + + let to_create = indexed_paths + .into_iter() + .filter_map(|entry| { + if let Some(file_path) = isolated_paths_already_in_db.get(&entry.iso_file_path) { + if let (Some(metadata), Some(inode), Some(device), Some(date_modified)) = ( + &entry.maybe_metadata, + &file_path.inode, + &file_path.device, + &file_path.date_modified, + ) { + let (inode, device) = + (inode_from_db(&inode[0..8]), device_from_db(&device[0..8])); + + // Datetimes stored in DB loses a bit of precision, so we need to check against a delta + // instead of using != operator + if inode != metadata.inode + || device != metadata.device || DateTime::::from( + metadata.modified_at, + ) - *date_modified + > Duration::milliseconds(1) + { + to_update.push((from_bytes_to_uuid(&file_path.pub_id), entry).into()); + } + } + + None + } else { + Some(entry.into()) + } }) - }) + .collect::>(); + + (to_create.into_iter(), to_update.into_iter()) }) } diff --git a/core/src/location/manager/mod.rs b/core/src/location/manager/mod.rs index 2ace800e7db3..e0927368dfe7 100644 --- a/core/src/location/manager/mod.rs +++ b/core/src/location/manager/mod.rs @@ -110,11 +110,6 @@ pub enum LocationManagerError { #[error("missing-field")] MissingField(#[from] MissingFieldError), - #[error("invalid inode")] - InvalidInode, - #[error("invalid device")] - InvalidDevice, - #[error(transparent)] FileIO(#[from] FileIOError), } diff --git a/core/src/location/manager/watcher/linux.rs b/core/src/location/manager/watcher/linux.rs index 8d69c56b895c..2ffa438e254e 100644 --- a/core/src/location/manager/watcher/linux.rs +++ b/core/src/location/manager/watcher/linux.rs @@ -106,8 +106,19 @@ impl<'lib> EventHandler<'lib> for LinuxEventHandler<'lib> { EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => { let from_path = &paths[0]; + let to_path = &paths[1]; + self.rename_from.remove(from_path); - rename(self.location_id, &paths[1], from_path, self.library).await?; + rename( + self.location_id, + to_path, + from_path, + fs::metadata(to_path) + .await + .map_err(|e| FileIOError::from((to_path, e)))?, + self.library, + ) + .await?; self.recently_renamed_from .insert(paths.swap_remove(0), Instant::now()); } diff --git a/core/src/location/manager/watcher/macos.rs b/core/src/location/manager/watcher/macos.rs index d19ccdd474c3..a1ef6f41bb24 100644 --- a/core/src/location/manager/watcher/macos.rs +++ b/core/src/location/manager/watcher/macos.rs @@ -268,7 +268,7 @@ impl MacOsEventHandler<'_> { ); // We found a new path for this old path, so we can rename it - rename(self.location_id, &path, &old_path, self.library).await?; + rename(self.location_id, &path, &old_path, meta, self.library).await?; } else { trace!("No match for new path yet: {}", path.display()); self.new_paths_map @@ -299,7 +299,16 @@ impl MacOsEventHandler<'_> { ); // We found a new path for this old path, so we can rename it - rename(self.location_id, &new_path, &path, self.library).await?; + rename( + self.location_id, + &new_path, + &path, + fs::metadata(&new_path) + .await + .map_err(|e| FileIOError::from((&new_path, e)))?, + self.library, + ) + .await?; } else { trace!("No match for old path yet: {}", path.display()); // We didn't find a new path for this old path, so we store ir for later diff --git a/core/src/location/manager/watcher/utils.rs b/core/src/location/manager/watcher/utils.rs index 952b1d4af186..a1a0bf355a4c 100644 --- a/core/src/location/manager/watcher/utils.rs +++ b/core/src/location/manager/watcher/utils.rs @@ -21,7 +21,10 @@ use crate::{ }, prisma::{file_path, location, object}, sync, - util::{db::maybe_missing, error::FileIOError}, + util::{ + db::{device_from_db, device_to_db, inode_from_db, inode_to_db, maybe_missing}, + error::FileIOError, + }, }; #[cfg(target_family = "unix")] @@ -39,7 +42,7 @@ use std::{ use sd_file_ext::extensions::ImageExtension; -use chrono::{DateTime, Local}; +use chrono::{DateTime, Local, Utc}; use notify::{Event, EventKind}; use prisma_client_rust::{raw, PrismaValue}; use serde_json::json; @@ -382,16 +385,8 @@ async fn inner_update_file( let location_path = location_path.as_ref(); let (current_inode, current_device) = ( - u64::from_le_bytes( - maybe_missing(file_path.inode.as_ref(), "file_path.inode")?[0..8] - .try_into() - .map_err(|_| LocationManagerError::InvalidInode)?, - ), - u64::from_le_bytes( - maybe_missing(file_path.device.as_ref(), "file_path.device")?[0..8] - .try_into() - .map_err(|_| LocationManagerError::InvalidDevice)?, - ), + inode_from_db(&maybe_missing(file_path.inode.as_ref(), "file_path.inode")?[0..8]), + device_from_db(&maybe_missing(file_path.device.as_ref(), "file_path.device")?[0..8]), ); trace!( @@ -451,7 +446,7 @@ async fn inner_update_file( ))), ), { - let date = DateTime::::from(fs_metadata.modified_or_now()).into(); + let date = DateTime::::from(fs_metadata.modified_or_now()).into(); ( (date_modified::NAME, json!(date)), @@ -480,7 +475,7 @@ async fn inner_update_file( if let Some(new_inode) = maybe_new_inode { ( (inode::NAME, json!(new_inode)), - Some(inode::set(Some(new_inode.to_le_bytes().to_vec()))), + Some(inode::set(Some(inode_to_db(new_inode)))), ) } else { ((inode::NAME, serde_json::Value::Null), None) @@ -490,7 +485,7 @@ async fn inner_update_file( if let Some(new_device) = maybe_new_device { ( (device::NAME, json!(new_device)), - Some(device::set(Some(new_device.to_le_bytes().to_vec()))), + Some(device::set(Some(device_to_db(new_device)))), ) } else { ((device::NAME, serde_json::Value::Null), None) @@ -574,6 +569,7 @@ pub(super) async fn rename( location_id: location::id::Type, new_path: impl AsRef, old_path: impl AsRef, + new_path_metadata: Metadata, library: &Library, ) -> Result<(), LocationManagerError> { let location_path = extract_location_path(location_id, library).await?; @@ -642,6 +638,9 @@ pub(super) async fn rename( file_path::materialized_path::set(Some(new_path_materialized_str)), file_path::name::set(Some(new.name.to_string())), file_path::extension::set(Some(new.extension.to_string())), + file_path::date_modified::set(Some( + DateTime::::from(new_path_metadata.modified_or_now()).into(), + )), ], ) .exec() @@ -801,15 +800,11 @@ pub(super) async fn extract_inode_and_device_from_path( Err(FilePathError::NotFound(path.into()).into()), |file_path| { Ok(( - u64::from_le_bytes( - maybe_missing(file_path.inode, "file_path.inode")?[0..8] - .try_into() - .map_err(|_| LocationManagerError::InvalidInode)?, + inode_from_db( + &maybe_missing(file_path.inode.as_ref(), "file_path.inode")?[0..8], ), - u64::from_le_bytes( - maybe_missing(file_path.device, "file_path.device")?[0..8] - .try_into() - .map_err(|_| LocationManagerError::InvalidDevice)?, + device_from_db( + &maybe_missing(file_path.device.as_ref(), "file_path.device")?[0..8], ), )) }, diff --git a/core/src/location/manager/watcher/windows.rs b/core/src/location/manager/watcher/windows.rs index bcf982c76b44..7c5c2fd43ee0 100644 --- a/core/src/location/manager/watcher/windows.rs +++ b/core/src/location/manager/watcher/windows.rs @@ -88,7 +88,16 @@ impl<'lib> EventHandler<'lib> for WindowsEventHandler<'lib> { ); // We found a new path for this old path, so we can rename it instead of removing and creating it - rename(self.location_id, &paths[0], &old_path, self.library).await?; + rename( + self.location_id, + &paths[0], + &old_path, + fs::metadata(&paths[0]) + .await + .map_err(|e| FileIOError::from((&paths[0], e)))?, + self.library, + ) + .await?; } else { let metadata = create_dir_or_file(self.location_id, &paths[0], self.library).await?; @@ -120,7 +129,16 @@ impl<'lib> EventHandler<'lib> for WindowsEventHandler<'lib> { if let Some((_, new_path)) = self.rename_to_map.remove(&inode_and_device) { // We found a new path for this old path, so we can rename it - rename(self.location_id, &new_path, &path, self.library).await?; + rename( + self.location_id, + &new_path, + &path, + fs::metadata(&new_path) + .await + .map_err(|e| FileIOError::from((&new_path, e)))?, + self.library, + ) + .await?; } else { self.rename_from_map .insert(inode_and_device, (Instant::now(), path)); @@ -135,7 +153,16 @@ impl<'lib> EventHandler<'lib> for WindowsEventHandler<'lib> { if let Some((_, old_path)) = self.rename_to_map.remove(&inode_and_device) { // We found a old path for this new path, so we can rename it - rename(self.location_id, &path, &old_path, self.library).await?; + rename( + self.location_id, + &path, + &old_path, + fs::metadata(&path) + .await + .map_err(|e| FileIOError::from((&path, e)))?, + self.library, + ) + .await?; } else { self.rename_from_map .insert(inode_and_device, (Instant::now(), path)); diff --git a/core/src/util/db.rs b/core/src/util/db.rs index ab5889a7dc64..df43a24e04f1 100644 --- a/core/src/util/db.rs +++ b/core/src/util/db.rs @@ -76,6 +76,26 @@ pub fn uuid_to_bytes(uuid: Uuid) -> Vec { uuid.as_bytes().to_vec() } +pub fn from_bytes_to_uuid(bytes: &[u8]) -> Uuid { + Uuid::from_slice(bytes).expect("corrupted uuid in database") +} + +pub fn inode_from_db(db_inode: &[u8]) -> u64 { + u64::from_le_bytes(db_inode.try_into().expect("corrupted inode in database")) +} + +pub fn device_from_db(db_device: &[u8]) -> u64 { + u64::from_le_bytes(db_device.try_into().expect("corrupted device in database")) +} + +pub fn inode_to_db(inode: u64) -> Vec { + inode.to_le_bytes().to_vec() +} + +pub fn device_to_db(device: u64) -> Vec { + device.to_le_bytes().to_vec() +} + #[derive(Error, Debug)] #[error("Missing field {0}")] pub struct MissingFieldError(&'static str); From 456a642a1f852eab4dcc848b8bf9fd996847e700 Mon Sep 17 00:00:00 2001 From: Omar Hamad Date: Tue, 11 Jul 2023 22:55:33 +0300 Subject: [PATCH 05/18] Check winget version and require update if necessary (#1087) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a check for the winget version to ensure it meets the minimum required version. If the winget version is outdated, the script now exits with an appropriate error message. Co-authored-by: Vítor Vasconcellos --- .github/scripts/setup-system.ps1 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/scripts/setup-system.ps1 b/.github/scripts/setup-system.ps1 index 19da0d48207f..7d2fc3dbb94f 100644 --- a/.github/scripts/setup-system.ps1 +++ b/.github/scripts/setup-system.ps1 @@ -162,7 +162,13 @@ https://learn.microsoft.com/windows/package-manager/winget/ '@ } - # TODO: Check system winget version is greater or equal to v1.4.10052 + # Check system winget version is greater or equal to v1.4.10052 + $wingetVersion = [Version]((winget --version) -replace '.*?(\d+)\.(\d+)\.(\d+).*', '$1.$2.$3') + $requiredVersion = [Version]'1.4.10052' + if ($wingetVersion.CompareTo($requiredVersion) -lt 0) { + $errorMessage = "You need to update your winget to version $requiredVersion or higher." + Exit-WithError $errorMessage + } # Check connectivity to GitHub $ProgressPreference = 'SilentlyContinue' From 07c00a8b7f3c2b7714db45a200560b824fa7b292 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Wed, 12 Jul 2023 11:32:53 +0700 Subject: [PATCH 06/18] [ENG-846] Preferences (#1047) * preferences table + trait * cleaner implementation * router + ts type * preference test * per-column list view preferences * preference read + write * add some docs * migration + docs * remove ts expect error --- Cargo.lock | 11 + .../authjs-adapter-drizzle-mysql/index.ts | 1 - core/Cargo.toml | 1 + .../20230711114013_preferences/migration.sql | 5 + core/prisma/schema.prisma | 8 + core/src/api/mod.rs | 3 + core/src/api/preferences.rs | 21 + core/src/lib.rs | 1 + core/src/preferences/kv.rs | 159 ++ core/src/preferences/mod.rs | 151 ++ core/src/sync/manager.rs | 1 + interface/package.json | 2 + interface/util/uuid.ts | 1 + packages/client/src/core.ts | 14 + pnpm-lock.yaml | 1693 +++++++++-------- 15 files changed, 1229 insertions(+), 843 deletions(-) create mode 100644 core/prisma/migrations/20230711114013_preferences/migration.sql create mode 100644 core/src/api/preferences.rs create mode 100644 core/src/preferences/kv.rs create mode 100644 core/src/preferences/mod.rs create mode 100644 interface/util/uuid.ts diff --git a/Cargo.lock b/Cargo.lock index 10d75055968f..445fd550ce09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6742,6 +6742,16 @@ dependencies = [ "serde", ] +[[package]] +name = "rmpv" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de8813b3a2f95c5138fe5925bfb8784175d88d6bff059ba8ce090aa891319754" +dependencies = [ + "num-traits", + "rmp", +] + [[package]] name = "rspc" version = "0.1.4" @@ -7070,6 +7080,7 @@ dependencies = [ "regex", "rmp", "rmp-serde", + "rmpv", "rspc", "sd-crypto", "sd-ffmpeg", diff --git a/apps/landing/src/app/api/[...auth]/authjs-adapter-drizzle-mysql/index.ts b/apps/landing/src/app/api/[...auth]/authjs-adapter-drizzle-mysql/index.ts index 2673edadf4b4..0d1543bcb2a9 100644 --- a/apps/landing/src/app/api/[...auth]/authjs-adapter-drizzle-mysql/index.ts +++ b/apps/landing/src/app/api/[...auth]/authjs-adapter-drizzle-mysql/index.ts @@ -17,7 +17,6 @@ */ import type { Adapter } from '@auth/core/adapters'; import { and, eq } from 'drizzle-orm'; -// @ts-expect-error import { v4 as uuid } from 'uuid'; import type { DbClient, Schema } from './schema'; diff --git a/core/Cargo.toml b/core/Cargo.toml index 5c7804177791..e39d8618aa23 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -58,6 +58,7 @@ serde_json = "1.0" futures = "0.3" rmp = "^0.8.11" rmp-serde = "^1.1.1" +rmpv = "^1.0.0" blake3 = "1.3.3" hostname = "0.3.1" uuid = { version = "1.3.3", features = ["v4", "serde"] } diff --git a/core/prisma/migrations/20230711114013_preferences/migration.sql b/core/prisma/migrations/20230711114013_preferences/migration.sql new file mode 100644 index 000000000000..2552b1fe32c1 --- /dev/null +++ b/core/prisma/migrations/20230711114013_preferences/migration.sql @@ -0,0 +1,5 @@ +-- CreateTable +CREATE TABLE "preference" ( + "key" TEXT NOT NULL PRIMARY KEY, + "value" BLOB +); diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma index 01273b7fb4ec..1745b19ed221 100644 --- a/core/prisma/schema.prisma +++ b/core/prisma/schema.prisma @@ -458,3 +458,11 @@ model IndexerRulesInLocation { @@id([location_id, indexer_rule_id]) @@map("indexer_rule_in_location") } + +/// @shared(id: key) +model Preference { + key String @id + value Bytes? + + @@map("preference") +} diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs index 6b2b3fec3da2..3bc083bd8af5 100644 --- a/core/src/api/mod.rs +++ b/core/src/api/mod.rs @@ -28,6 +28,7 @@ mod libraries; mod locations; mod nodes; mod p2p; +mod preferences; mod search; mod sync; mod tags; @@ -82,6 +83,7 @@ pub(crate) fn mount() -> Arc { .merge("p2p.", p2p::mount()) .merge("nodes.", nodes::mount()) .merge("sync.", sync::mount()) + .merge("preferences.", preferences::mount()) .merge("invalidation.", utils::mount_invalidate()) .build( #[allow(clippy::let_and_return)] @@ -98,6 +100,7 @@ pub(crate) fn mount() -> Arc { }, ) .arced(); + InvalidRequests::validate(r.clone()); // This validates all invalidation calls. r diff --git a/core/src/api/preferences.rs b/core/src/api/preferences.rs new file mode 100644 index 000000000000..ac455604c95a --- /dev/null +++ b/core/src/api/preferences.rs @@ -0,0 +1,21 @@ +use rspc::alpha::AlphaRouter; + +use super::{utils::library, Ctx, R}; +use crate::preferences::LibraryPreferences; + +pub(crate) fn mount() -> AlphaRouter { + R.router() + .procedure("update", { + R.with2(library()) + .mutation(|(_, library), args: LibraryPreferences| async move { + args.write(&library.db).await?; + + Ok(()) + }) + }) + .procedure("get", { + R.with2(library()).query(|(_, library), _: ()| async move { + Ok(LibraryPreferences::read(&library.db).await?) + }) + }) +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 4885040b2576..7aaf7e5f35f1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -33,6 +33,7 @@ pub(crate) mod location; pub(crate) mod node; pub(crate) mod object; pub(crate) mod p2p; +pub(crate) mod preferences; pub(crate) mod sync; pub(crate) mod util; pub(crate) mod volume; diff --git a/core/src/preferences/kv.rs b/core/src/preferences/kv.rs new file mode 100644 index 000000000000..f7927a239633 --- /dev/null +++ b/core/src/preferences/kv.rs @@ -0,0 +1,159 @@ +use std::collections::BTreeMap; + +use crate::prisma::{preference, PrismaClient}; +use itertools::Itertools; +use rmpv::Value; +use serde::{de::DeserializeOwned, Serialize}; + +use super::Preferences; + +#[derive(Debug)] +pub struct PreferenceKey(Vec); + +impl PreferenceKey { + pub fn new(value: impl Into) -> Self { + Self( + value + .into() + .split('.') + .map(ToString::to_string) + .collect_vec(), + ) + } + + pub fn prepend_path(&mut self, prefix: &str) { + self.0 = [prefix.to_string()] + .into_iter() + .chain(self.0.drain(..)) + .collect_vec(); + } +} + +impl std::fmt::Display for PreferenceKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.join(".")) + } +} + +#[derive(Debug)] +pub struct PreferenceValue(Vec); + +impl PreferenceValue { + pub fn new(value: impl Serialize) -> Self { + let mut bytes = vec![]; + + rmp_serde::encode::write_named(&mut bytes, &value).unwrap(); + + // let value = rmpv::decode::read_value(&mut bytes.as_slice()).unwrap(); + + Self(bytes) + } + + pub fn from_value(value: Value) -> Self { + let mut bytes = vec![]; + + rmpv::encode::write_value(&mut bytes, &value).unwrap(); + + Self(bytes) + } +} + +#[derive(Debug)] +pub struct PreferenceKVs(Vec<(PreferenceKey, PreferenceValue)>); + +impl IntoIterator for PreferenceKVs { + type Item = (PreferenceKey, PreferenceValue); + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +#[derive(Debug)] +pub enum Entry { + Value(Vec), + Nested(Entries), +} + +impl Entry { + pub fn expect_value(self) -> T { + match self { + Self::Value(value) => rmp_serde::decode::from_read(value.as_slice()).unwrap(), + _ => panic!("Expected value"), + } + } + + pub fn expect_nested(self) -> Entries { + match self { + Self::Nested(entries) => entries, + _ => panic!("Expected nested entry"), + } + } +} + +pub type Entries = BTreeMap; + +impl PreferenceKVs { + pub fn new(values: Vec<(PreferenceKey, PreferenceValue)>) -> Self { + Self(values) + } + + pub fn with_prefix(mut self, prefix: &str) -> Self { + for (key, _) in &mut self.0 { + key.prepend_path(prefix); + } + + self + } + + pub fn to_upserts(self, db: &PrismaClient) -> Vec { + self.0 + .into_iter() + .map(|(key, value)| { + let value = vec![preference::value::set(Some(value.0))]; + + db.preference().upsert( + preference::key::equals(key.to_string()), + preference::create(key.to_string(), value.clone()), + value, + ) + }) + .collect() + } + + pub fn parse(self) -> T { + let entries = self + .0 + .into_iter() + .fold(BTreeMap::new(), |mut acc, (key, value)| { + let key_parts = key.0; + let key_parts_len = key_parts.len(); + + { + let mut curr_map: &mut BTreeMap = &mut acc; + + for (i, part) in key_parts.into_iter().enumerate() { + if i >= key_parts_len - 1 { + curr_map.insert(part, Entry::Value(value.0)); + break; + } else { + curr_map = match curr_map + .entry(part) + .or_insert(Entry::Nested(BTreeMap::new())) + { + Entry::Nested(map) => map, + _ => unreachable!(), + }; + } + } + } + + acc + }); + + dbg!(&entries); + + T::from_entries(entries) + } +} diff --git a/core/src/preferences/mod.rs b/core/src/preferences/mod.rs new file mode 100644 index 000000000000..65521a4f164a --- /dev/null +++ b/core/src/preferences/mod.rs @@ -0,0 +1,151 @@ +mod kv; + +pub use kv::*; +use specta::Type; + +use crate::prisma::PrismaClient; +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +// Preferences are a set of types that are serialized as a list of key-value pairs, +// where nested type keys are serialized as a dot-separated path. +// They are serailized as a list because this allows preferences to be a synchronisation boundary, +// whereas their values (referred to as settings) will be overwritten. + +#[derive(Clone, Serialize, Deserialize, Type, Debug)] +pub struct LibraryPreferences { + #[serde(default)] + #[specta(optional)] + location: HashMap, +} + +impl LibraryPreferences { + pub async fn write(self, db: &PrismaClient) -> prisma_client_rust::Result<()> { + let kvs = self.to_kvs(); + + db._batch(kvs.to_upserts(&db)).await?; + + Ok(()) + } + + pub async fn read(db: &PrismaClient) -> prisma_client_rust::Result { + let kvs = db.preference().find_many(vec![]).exec().await?; + + let prefs = PreferenceKVs::new( + kvs.into_iter() + .filter_map(|data| { + let a = rmpv::decode::read_value(&mut data.value?.as_slice()).unwrap(); + + Some((PreferenceKey::new(data.key), PreferenceValue::from_value(a))) + }) + .collect(), + ); + + Ok(prefs.parse()) + } +} + +#[derive(Clone, Serialize, Deserialize, Type, Debug)] +pub struct LocationPreferences { + /// View settings for the location - all writes are overwrites! + #[specta(optional)] + view: Option, +} + +#[derive(Clone, Serialize, Deserialize, Type, Debug)] +pub struct LocationViewSettings { + layout: ExplorerLayout, + list: ListViewSettings, +} + +#[derive(Clone, Serialize, Deserialize, Type, Default, Debug)] +pub struct ListViewSettings { + columns: HashMap, + sort_col: Option, +} + +#[derive(Clone, Serialize, Deserialize, Type, Default, Debug)] +pub struct ListViewColumnSettings { + hide: bool, + size: Option, +} + +#[derive(Clone, Serialize, Deserialize, Type, Debug)] +pub enum ExplorerLayout { + Grid, + List, + Media, +} + +impl Preferences for HashMap +where + V: Preferences, +{ + fn to_kvs(self) -> PreferenceKVs { + PreferenceKVs::new( + self.into_iter() + .flat_map(|(id, value)| { + let mut buf = Uuid::encode_buffer(); + + let id = id.as_simple().encode_lower(&mut buf); + + value.to_kvs().with_prefix(id) + }) + .collect(), + ) + } + + fn from_entries(entries: Entries) -> Self { + entries + .into_iter() + .map(|(key, value)| { + let id = Uuid::parse_str(&key).unwrap(); + + (id, V::from_entries(value.expect_nested())) + }) + .collect() + } +} + +impl Preferences for LibraryPreferences { + fn to_kvs(self) -> PreferenceKVs { + let Self { location } = self; + + location.to_kvs().with_prefix("location") + } + + fn from_entries(mut entries: Entries) -> Self { + Self { + location: entries + .remove("location") + .map(|value| HashMap::from_entries(value.expect_nested())) + .unwrap_or_default(), + } + } +} + +impl Preferences for LocationPreferences { + fn to_kvs(self) -> PreferenceKVs { + let Self { view } = self; + + PreferenceKVs::new( + [view.map(|view| (PreferenceKey::new("view"), PreferenceValue::new(view)))] + .into_iter() + .flatten() + .collect(), + ) + } + + fn from_entries(mut entries: Entries) -> Self { + Self { + view: entries.remove("view").map(|view| view.expect_value()), + } + } +} + +pub trait Preferences { + fn to_kvs(self) -> PreferenceKVs; + fn from_entries(entries: Entries) -> Self; +} diff --git a/core/src/sync/manager.rs b/core/src/sync/manager.rs index 0331fab5e0f2..db9d49ba63e0 100644 --- a/core/src/sync/manager.rs +++ b/core/src/sync/manager.rs @@ -303,6 +303,7 @@ impl SyncManager { .await?; } }, + ModelSyncData::Preference(_, _) => todo!(), } if let CRDTOperationType::Shared(shared_op) = op.typ { diff --git a/interface/package.json b/interface/package.json index 6ed9841c47f7..effea049fa5f 100644 --- a/interface/package.json +++ b/interface/package.json @@ -37,6 +37,7 @@ "@tanstack/react-table": "^8.8.5", "@tanstack/react-virtual": "3.0.0-beta.54", "@types/react-scroll-sync": "^0.8.4", + "@types/uuid": "^9.0.2", "@vitejs/plugin-react": "^2.1.0", "autoprefixer": "^10.4.12", "class-variance-authority": "^0.5.3", @@ -67,6 +68,7 @@ "use-count-up": "^3.0.1", "use-debounce": "^8.0.4", "use-resize-observer": "^9.1.0", + "uuid": "^9.0.0", "valtio": "^1.7.4" }, "devDependencies": { diff --git a/interface/util/uuid.ts b/interface/util/uuid.ts new file mode 100644 index 000000000000..a56a942789b2 --- /dev/null +++ b/interface/util/uuid.ts @@ -0,0 +1 @@ +export { stringify } from 'uuid'; diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 4fe053d7888d..cb2893fe0a20 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -19,6 +19,7 @@ export type Procedures = { { key: "locations.list", input: LibraryArgs, result: { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; node_id: number | null; node: Node | null }[] } | { key: "nodeState", input: never, result: NodeState } | { key: "nodes.listLocations", input: LibraryArgs, result: ExplorerItem[] } | + { key: "preferences.get", input: LibraryArgs, result: LibraryPreferences } | { key: "search.objects", input: LibraryArgs, result: SearchData } | { key: "search.paths", input: LibraryArgs, result: SearchData } | { key: "sync.messages", input: LibraryArgs, result: CRDTOperation[] } | @@ -61,6 +62,7 @@ export type Procedures = { { key: "p2p.acceptSpacedrop", input: [string, string | null], result: null } | { key: "p2p.pair", input: LibraryArgs, result: number } | { key: "p2p.spacedrop", input: SpacedropArgs, result: string | null } | + { key: "preferences.update", input: LibraryArgs, result: null } | { key: "tags.assign", input: LibraryArgs, result: null } | { key: "tags.create", input: LibraryArgs, result: Tag } | { key: "tags.delete", input: LibraryArgs, result: null } | @@ -97,6 +99,8 @@ export type EditLibraryArgs = { id: string; name: LibraryName | null; descriptio export type ExplorerItem = { type: "Path"; has_local_thumbnail: boolean; thumbnail_key: string[] | null; item: FilePathWithObject } | { type: "Object"; has_local_thumbnail: boolean; thumbnail_key: string[] | null; item: ObjectWithFilePaths } | { type: "Location"; has_local_thumbnail: boolean; thumbnail_key: string[] | null; item: Location } +export type ExplorerLayout = "Grid" | "List" | "Media" + export type FileCopierJobInit = { source_location_id: number; target_location_id: number; sources_file_path_ids: number[]; target_location_relative_directory_path: string; target_file_name_suffix: string | null } export type FileCutterJobInit = { source_location_id: number; target_location_id: number; sources_file_path_ids: number[]; target_location_relative_directory_path: string } @@ -158,8 +162,14 @@ export type LibraryConfigWrapped = { uuid: string; config: SanitisedLibraryConfi export type LibraryName = string +export type LibraryPreferences = { location?: { [key: string]: LocationPreferences } } + export type LightScanArgs = { location_id: number; sub_path: string } +export type ListViewColumnSettings = { hide: boolean; size: number | null } + +export type ListViewSettings = { columns: { [key: string]: ListViewColumnSettings }; sort_col: string | null } + export type Location = { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; node_id: number | null } /** @@ -169,6 +179,8 @@ export type Location = { id: number; pub_id: number[]; name: string | null; path */ export type LocationCreateArgs = { path: string; dry_run: boolean; indexer_rules_ids: number[] } +export type LocationPreferences = { view?: LocationViewSettings | null } + /** * `LocationUpdateArgs` is the argument received from the client using `rspc` to update a location. * It contains the id of the location to be updated, possible a name to change the current location's name @@ -179,6 +191,8 @@ export type LocationCreateArgs = { path: string; dry_run: boolean; indexer_rules */ export type LocationUpdateArgs = { id: number; name: string | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; indexer_rules_ids: number[] } +export type LocationViewSettings = { layout: ExplorerLayout; list: ListViewSettings } + export type LocationWithIndexerRules = { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; node_id: number | null; indexer_rules: { indexer_rule: IndexerRule }[] } export type MaybeNot = T | { not: T } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1935a70039d..65a0758a91ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -49,7 +49,7 @@ importers: version: 5.0.4 vite: specifier: ^4.3.9 - version: 4.3.9(less@4.1.3) + version: 4.3.9(@types/node@18.15.1) apps/desktop: dependencies: @@ -167,7 +167,7 @@ importers: version: 1.2.1 contentlayer: specifier: ^0.3.2 - version: 0.3.2(esbuild@0.18.10) + version: 0.3.2(esbuild@0.18.0) drizzle-orm: specifier: ^0.26.0 version: 0.26.0(@planetscale/database@1.7.0) @@ -182,7 +182,7 @@ importers: version: 13.4.3(@babel/core@7.22.1)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) next-contentlayer: specifier: ^0.3.2 - version: 0.3.2(esbuild@0.18.10)(next@13.4.3)(react-dom@18.2.0)(react@18.2.0) + version: 0.3.2(esbuild@0.18.0)(next@13.4.3)(react-dom@18.2.0)(react@18.2.0) phosphor-react: specifier: ^1.4.1 version: 1.4.1(react@18.2.0) @@ -412,7 +412,7 @@ importers: version: 7.22.1 '@rnx-kit/metro-config': specifier: ^1.3.6 - version: 1.3.6(@babel/core@7.22.1)(metro-config@0.76.7)(metro-react-native-babel-preset@0.76.7)(react-native@0.71.3)(react@18.2.0) + version: 1.3.6(@babel/core@7.22.1)(metro-config@0.76.6)(metro-react-native-babel-preset@0.76.6)(react-native@0.71.3)(react@18.2.0) '@sd/config': specifier: workspace:* version: link:../../packages/config @@ -447,7 +447,7 @@ importers: version: 7.0.5(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-styling': specifier: ^1.0.6 - version: 1.0.6(less@4.1.3)(postcss@8.4.23)(react-dom@18.2.0)(react@18.2.0)(webpack@5.88.1) + version: 1.0.6(less@4.1.3)(postcss@8.4.23)(react-dom@18.2.0)(react@18.2.0)(webpack@5.86.0) '@storybook/blocks': specifier: ^7.0.5 version: 7.0.5(react-dom@18.2.0)(react@18.2.0) @@ -456,7 +456,7 @@ importers: version: 7.0.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4) '@storybook/react-vite': specifier: ^7.0.5 - version: 7.0.20(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.9) + version: 7.0.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.9) '@storybook/testing-library': specifier: ^0.1.0 version: 0.1.0 @@ -563,7 +563,7 @@ importers: version: 5.0.4 vite: specifier: ^4.0.4 - version: 4.3.9(less@4.1.3) + version: 4.3.9(@types/node@18.15.1) vite-plugin-html: specifier: ^3.2.0 version: 3.2.0(vite@4.3.9) @@ -603,7 +603,7 @@ importers: version: 4.8.2 vite: specifier: ^4.0.4 - version: 4.3.9(less@4.1.3) + version: 4.3.9(@types/node@18.15.1) interface: dependencies: @@ -667,6 +667,9 @@ importers: '@types/react-scroll-sync': specifier: ^0.8.4 version: 0.8.4 + '@types/uuid': + specifier: ^9.0.2 + version: 9.0.2 '@vitejs/plugin-react': specifier: ^2.1.0 version: 2.1.0(vite@4.3.9) @@ -757,6 +760,9 @@ importers: use-resize-observer: specifier: ^9.1.0 version: 9.1.0(react-dom@18.2.0)(react@18.2.0) + uuid: + specifier: ^9.0.0 + version: 9.0.0 valtio: specifier: ^1.7.4 version: 1.10.4(react@18.2.0) @@ -995,16 +1001,16 @@ importers: version: 10.4.14(postcss@8.4.23) babel-loader: specifier: ^8.2.5 - version: 8.2.5(@babel/core@7.22.1)(webpack@5.88.1) + version: 8.2.5(@babel/core@7.22.1)(webpack@5.86.0) sass: specifier: ^1.55.0 version: 1.55.0 sass-loader: specifier: ^13.0.2 - version: 13.0.2(sass@1.55.0)(webpack@5.88.1) + version: 13.0.2(sass@1.55.0)(webpack@5.86.0) style-loader: specifier: ^3.3.1 - version: 3.3.1(webpack@5.88.1) + version: 3.3.1(webpack@5.86.0) tailwindcss: specifier: ^3.3.2 version: 3.3.2 @@ -1017,11 +1023,6 @@ importers: packages: - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true - /@alloc/quick-lru@5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -1102,7 +1103,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/client-ses@3.337.0: @@ -1143,10 +1144,10 @@ packages: '@aws-sdk/util-user-agent-node': 3.337.0 '@aws-sdk/util-utf8': 3.310.0 '@aws-sdk/util-waiter': 3.337.0 - '@smithy/protocol-http': 1.1.0 - '@smithy/types': 1.1.0 + '@smithy/protocol-http': 1.0.1 + '@smithy/types': 1.0.0 fast-xml-parser: 4.1.2 - tslib: 2.6.0 + tslib: 2.5.3 transitivePeerDependencies: - aws-crt dev: false @@ -1185,9 +1186,9 @@ packages: '@aws-sdk/util-user-agent-browser': 3.337.0 '@aws-sdk/util-user-agent-node': 3.337.0 '@aws-sdk/util-utf8': 3.310.0 - '@smithy/protocol-http': 1.1.0 - '@smithy/types': 1.1.0 - tslib: 2.6.0 + '@smithy/protocol-http': 1.0.1 + '@smithy/types': 1.0.0 + tslib: 2.5.3 transitivePeerDependencies: - aws-crt dev: false @@ -1226,9 +1227,9 @@ packages: '@aws-sdk/util-user-agent-browser': 3.337.0 '@aws-sdk/util-user-agent-node': 3.337.0 '@aws-sdk/util-utf8': 3.310.0 - '@smithy/protocol-http': 1.1.0 - '@smithy/types': 1.1.0 - tslib: 2.6.0 + '@smithy/protocol-http': 1.0.1 + '@smithy/types': 1.0.0 + tslib: 2.5.3 transitivePeerDependencies: - aws-crt dev: false @@ -1270,10 +1271,10 @@ packages: '@aws-sdk/util-user-agent-browser': 3.337.0 '@aws-sdk/util-user-agent-node': 3.337.0 '@aws-sdk/util-utf8': 3.310.0 - '@smithy/protocol-http': 1.1.0 - '@smithy/types': 1.1.0 + '@smithy/protocol-http': 1.0.1 + '@smithy/types': 1.0.0 fast-xml-parser: 4.1.2 - tslib: 2.6.0 + tslib: 2.5.3 transitivePeerDependencies: - aws-crt dev: false @@ -1285,7 +1286,7 @@ packages: '@aws-sdk/types': 3.337.0 '@aws-sdk/util-config-provider': 3.310.0 '@aws-sdk/util-middleware': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/credential-provider-env@3.337.0: @@ -1294,7 +1295,7 @@ packages: dependencies: '@aws-sdk/property-provider': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/credential-provider-imds@3.337.0: @@ -1305,7 +1306,7 @@ packages: '@aws-sdk/property-provider': 3.337.0 '@aws-sdk/types': 3.337.0 '@aws-sdk/url-parser': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/credential-provider-ini@3.337.0: @@ -1320,7 +1321,7 @@ packages: '@aws-sdk/property-provider': 3.337.0 '@aws-sdk/shared-ini-file-loader': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 transitivePeerDependencies: - aws-crt dev: false @@ -1338,7 +1339,7 @@ packages: '@aws-sdk/property-provider': 3.337.0 '@aws-sdk/shared-ini-file-loader': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 transitivePeerDependencies: - aws-crt dev: false @@ -1350,7 +1351,7 @@ packages: '@aws-sdk/property-provider': 3.337.0 '@aws-sdk/shared-ini-file-loader': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/credential-provider-sso@3.337.0: @@ -1362,7 +1363,7 @@ packages: '@aws-sdk/shared-ini-file-loader': 3.337.0 '@aws-sdk/token-providers': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 transitivePeerDependencies: - aws-crt dev: false @@ -1373,7 +1374,7 @@ packages: dependencies: '@aws-sdk/property-provider': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/fetch-http-handler@3.337.0: @@ -1383,7 +1384,7 @@ packages: '@aws-sdk/querystring-builder': 3.337.0 '@aws-sdk/types': 3.337.0 '@aws-sdk/util-base64': 3.310.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/hash-node@3.337.0: @@ -1393,21 +1394,21 @@ packages: '@aws-sdk/types': 3.337.0 '@aws-sdk/util-buffer-from': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/invalid-dependency@3.337.0: resolution: {integrity: sha512-QjVKlAeFpTbiQiUzVHDoNYmDQzIzAwgDqBs6ExFtVrLZRvl7NicwZgOARYSfQOvsPuWcm3Dv30wgJMD5FK3rwg==} dependencies: '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/is-array-buffer@3.310.0: resolution: {integrity: sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/middleware-content-length@3.337.0: @@ -1416,7 +1417,7 @@ packages: dependencies: '@aws-sdk/protocol-http': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/middleware-endpoint@3.337.0: @@ -1427,7 +1428,7 @@ packages: '@aws-sdk/types': 3.337.0 '@aws-sdk/url-parser': 3.337.0 '@aws-sdk/util-middleware': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/middleware-host-header@3.337.0: @@ -1436,7 +1437,7 @@ packages: dependencies: '@aws-sdk/protocol-http': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/middleware-logger@3.337.0: @@ -1444,7 +1445,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/middleware-recursion-detection@3.337.0: @@ -1453,7 +1454,7 @@ packages: dependencies: '@aws-sdk/protocol-http': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/middleware-retry@3.337.0: @@ -1465,7 +1466,7 @@ packages: '@aws-sdk/types': 3.337.0 '@aws-sdk/util-middleware': 3.337.0 '@aws-sdk/util-retry': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 uuid: 8.3.2 dev: false @@ -1475,7 +1476,7 @@ packages: dependencies: '@aws-sdk/middleware-signing': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/middleware-serde@3.337.0: @@ -1483,7 +1484,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/middleware-signing@3.337.0: @@ -1495,14 +1496,14 @@ packages: '@aws-sdk/signature-v4': 3.337.0 '@aws-sdk/types': 3.337.0 '@aws-sdk/util-middleware': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/middleware-stack@3.337.0: resolution: {integrity: sha512-NPaf9b90lmM8vITfD4ZZros14mDVaul2yA3+0CeVl+sqJ/k/sEl0/f0kEsnVunPj6garTbBIhEyG6nlz5dJIbQ==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/middleware-user-agent@3.337.0: @@ -1512,7 +1513,7 @@ packages: '@aws-sdk/protocol-http': 3.337.0 '@aws-sdk/types': 3.337.0 '@aws-sdk/util-endpoints': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/node-config-provider@3.337.0: @@ -1522,7 +1523,7 @@ packages: '@aws-sdk/property-provider': 3.337.0 '@aws-sdk/shared-ini-file-loader': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/node-http-handler@3.337.0: @@ -1533,7 +1534,7 @@ packages: '@aws-sdk/protocol-http': 3.337.0 '@aws-sdk/querystring-builder': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/property-provider@3.337.0: @@ -1541,7 +1542,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/protocol-http@3.337.0: @@ -1549,7 +1550,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/querystring-builder@3.337.0: @@ -1558,7 +1559,7 @@ packages: dependencies: '@aws-sdk/types': 3.337.0 '@aws-sdk/util-uri-escape': 3.310.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/querystring-parser@3.337.0: @@ -1566,7 +1567,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/service-error-classification@3.337.0: @@ -1579,7 +1580,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/signature-v4@3.337.0: @@ -1592,7 +1593,7 @@ packages: '@aws-sdk/util-middleware': 3.337.0 '@aws-sdk/util-uri-escape': 3.310.0 '@aws-sdk/util-utf8': 3.310.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/smithy-client@3.337.0: @@ -1601,7 +1602,7 @@ packages: dependencies: '@aws-sdk/middleware-stack': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/token-providers@3.337.0: @@ -1612,7 +1613,7 @@ packages: '@aws-sdk/property-provider': 3.337.0 '@aws-sdk/shared-ini-file-loader': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 transitivePeerDependencies: - aws-crt dev: false @@ -1621,7 +1622,7 @@ packages: resolution: {integrity: sha512-yR7e9iWMabUfNWkpQs05QXXBXGwp5cunkzVNeDvcAKgaxVVD2n8wY9Iocl324GvXMplJcnND9l0DvizkTak3yQ==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/url-parser@3.337.0: @@ -1629,7 +1630,7 @@ packages: dependencies: '@aws-sdk/querystring-parser': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-base64@3.310.0: @@ -1637,20 +1638,20 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/util-buffer-from': 3.310.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-body-length-browser@3.310.0: resolution: {integrity: sha512-sxsC3lPBGfpHtNTUoGXMQXLwjmR0zVpx0rSvzTPAuoVILVsp5AU/w5FphNPxD5OVIjNbZv9KsKTuvNTiZjDp9g==} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-body-length-node@3.310.0: resolution: {integrity: sha512-2tqGXdyKhyA6w4zz7UPoS8Ip+7sayOg9BwHNidiGm2ikbDxm1YrCfYXvCBdwaJxa4hJfRVz+aL9e+d3GqPI9pQ==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-buffer-from@3.310.0: @@ -1658,14 +1659,14 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/is-array-buffer': 3.310.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-config-provider@3.310.0: resolution: {integrity: sha512-xIBaYo8dwiojCw8vnUcIL4Z5tyfb1v3yjqyJKJWV/dqKUFOOS0U591plmXbM+M/QkXyML3ypon1f8+BoaDExrg==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-defaults-mode-browser@3.337.0: @@ -1675,7 +1676,7 @@ packages: '@aws-sdk/property-provider': 3.337.0 '@aws-sdk/types': 3.337.0 bowser: 2.11.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-defaults-mode-node@3.337.0: @@ -1687,7 +1688,7 @@ packages: '@aws-sdk/node-config-provider': 3.337.0 '@aws-sdk/property-provider': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-endpoints@3.337.0: @@ -1695,28 +1696,28 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-hex-encoding@3.310.0: resolution: {integrity: sha512-sVN7mcCCDSJ67pI1ZMtk84SKGqyix6/0A1Ab163YKn+lFBQRMKexleZzpYzNGxYzmQS6VanP/cfU7NiLQOaSfA==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-locate-window@3.310.0: resolution: {integrity: sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-middleware@3.337.0: resolution: {integrity: sha512-DKQf0SB7kMy5JFLHZq9w0DiupYfanEXCot8S/WzXuyPdUrsfhHT3TwrO44Alkcm8YFTSeXIxdd2jSEHEN6L50Q==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-retry@3.337.0: @@ -1724,14 +1725,14 @@ packages: engines: {node: '>= 14.0.0'} dependencies: '@aws-sdk/service-error-classification': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-uri-escape@3.310.0: resolution: {integrity: sha512-drzt+aB2qo2LgtDoiy/3sVG8w63cgLkqFIa2NFlGpUgHFWTXkqtbgf4L5QdjRGKWhmZsnqkbtL7vkSWEcYDJ4Q==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-user-agent-browser@3.337.0: @@ -1739,7 +1740,7 @@ packages: dependencies: '@aws-sdk/types': 3.337.0 bowser: 2.11.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-user-agent-node@3.337.0: @@ -1753,13 +1754,13 @@ packages: dependencies: '@aws-sdk/node-config-provider': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-utf8-browser@3.259.0: resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-utf8@3.310.0: @@ -1767,7 +1768,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@aws-sdk/util-buffer-from': 3.310.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@aws-sdk/util-waiter@3.337.0: @@ -1776,7 +1777,7 @@ packages: dependencies: '@aws-sdk/abort-controller': 3.337.0 '@aws-sdk/types': 3.337.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@babel/code-frame@7.10.4: @@ -1844,7 +1845,7 @@ packages: resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.5 + '@babel/types': 7.17.0 jsesc: 2.5.2 source-map: 0.5.7 dev: true @@ -1888,7 +1889,7 @@ packages: '@babel/compat-data': 7.22.5 '@babel/core': 7.21.8 '@babel/helper-validator-option': 7.22.5 - browserslist: 4.21.9 + browserslist: 4.21.7 lru-cache: 5.1.1 semver: 6.3.0 dev: true @@ -1902,7 +1903,7 @@ packages: '@babel/compat-data': 7.22.5 '@babel/core': 7.22.1 '@babel/helper-validator-option': 7.22.5 - browserslist: 4.21.9 + browserslist: 4.21.7 lru-cache: 5.1.1 semver: 6.3.0 @@ -3946,7 +3947,7 @@ packages: babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.21.8) babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.21.8) babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.21.8) - core-js-compat: 3.31.0 + core-js-compat: 3.30.2 semver: 6.3.0 transitivePeerDependencies: - supports-color @@ -4037,7 +4038,7 @@ packages: babel-plugin-polyfill-corejs2: 0.4.3(@babel/core@7.22.1) babel-plugin-polyfill-corejs3: 0.8.1(@babel/core@7.22.1) babel-plugin-polyfill-regenerator: 0.5.0(@babel/core@7.22.1) - core-js-compat: 3.31.0 + core-js-compat: 3.30.2 semver: 6.3.0 transitivePeerDependencies: - supports-color @@ -4118,7 +4119,7 @@ packages: clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 - pirates: 4.0.6 + pirates: 4.0.5 source-map-support: 0.5.21 /@babel/regjsgen@0.8.0: @@ -4149,7 +4150,7 @@ packages: '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.5 '@babel/parser': 7.22.5 - '@babel/types': 7.22.5 + '@babel/types': 7.17.0 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: @@ -4256,10 +4257,10 @@ packages: dev: true optional: true - /@contentlayer/cli@0.3.2(esbuild@0.18.10): + /@contentlayer/cli@0.3.2(esbuild@0.18.0): resolution: {integrity: sha512-KLzB2z3Klbl4bU7VTJ8EaY1d17GCBFtwgvtNAVLOqUJ4LRw46+jT+qBMk8gyy7R1xDNF2H1a/yGYs8t8rlFVmg==} dependencies: - '@contentlayer/core': 0.3.2(esbuild@0.18.10) + '@contentlayer/core': 0.3.2(esbuild@0.18.0) '@contentlayer/utils': 0.3.2 clipanion: 3.2.1(typanion@3.12.1) typanion: 3.12.1 @@ -4270,10 +4271,10 @@ packages: - supports-color dev: false - /@contentlayer/client@0.3.2(esbuild@0.18.10): + /@contentlayer/client@0.3.2(esbuild@0.18.0): resolution: {integrity: sha512-5m7IFd0Z8qRBAOnAYwWcf/SFe1SmtHmeV1kO4pldEuD8J/5sxKeefdGHLNnH3sxlGfeJhEdDnymJtppg8v0D8w==} dependencies: - '@contentlayer/core': 0.3.2(esbuild@0.18.10) + '@contentlayer/core': 0.3.2(esbuild@0.18.0) transitivePeerDependencies: - '@effect-ts/otel-node' - esbuild @@ -4281,7 +4282,7 @@ packages: - supports-color dev: false - /@contentlayer/core@0.3.2(esbuild@0.18.10): + /@contentlayer/core@0.3.2(esbuild@0.18.0): resolution: {integrity: sha512-5ZLzS3s4Lp5Tlw+U4kUUK9frYmi8sc970spJSvLSxtOTDHDE7xemGT9HSj0V4DcmIkY9TT7pCmMFRfpEv7IC6Q==} peerDependencies: esbuild: 0.17.x @@ -4295,25 +4296,25 @@ packages: '@contentlayer/utils': 0.3.2 camel-case: 4.1.2 comment-json: 4.2.3 - esbuild: 0.18.10 + esbuild: 0.18.0 gray-matter: 4.0.3 - mdx-bundler: 9.2.1(esbuild@0.18.10) + mdx-bundler: 9.2.1(esbuild@0.18.0) rehype-stringify: 9.0.3 remark-frontmatter: 4.0.1 remark-parse: 10.0.2 remark-rehype: 10.1.0 source-map-support: 0.5.21 - type-fest: 3.12.0 + type-fest: 3.11.1 unified: 10.1.2 transitivePeerDependencies: - '@effect-ts/otel-node' - supports-color dev: false - /@contentlayer/source-files@0.3.2(esbuild@0.18.10): + /@contentlayer/source-files@0.3.2(esbuild@0.18.0): resolution: {integrity: sha512-VYUaUbT3Hg3fSEEKpjDdfGEkw4bl4BaLHJWf5sulrkBtjdyNJ3RwUdnsqN3i+bibhcYF4ZvnFme4xtHBuEChmw==} dependencies: - '@contentlayer/core': 0.3.2(esbuild@0.18.10) + '@contentlayer/core': 0.3.2(esbuild@0.18.0) '@contentlayer/utils': 0.3.2 chokidar: 3.5.3 fast-glob: 3.2.12 @@ -4331,11 +4332,11 @@ packages: - supports-color dev: false - /@contentlayer/source-remote-files@0.3.2(esbuild@0.18.10): + /@contentlayer/source-remote-files@0.3.2(esbuild@0.18.0): resolution: {integrity: sha512-BuABBHemn/UzhARsQh2XH13VUeb5HoRI3NkJeCGEMSnstzI72Dcc6krELwG3cTFYmgb95TV8NuIZKcrz8IsX6A==} dependencies: - '@contentlayer/core': 0.3.2(esbuild@0.18.10) - '@contentlayer/source-files': 0.3.2(esbuild@0.18.10) + '@contentlayer/core': 0.3.2(esbuild@0.18.0) + '@contentlayer/source-files': 0.3.2(esbuild@0.18.0) '@contentlayer/utils': 0.3.2 transitivePeerDependencies: - '@effect-ts/otel-node' @@ -4372,9 +4373,9 @@ packages: hash-wasm: 4.9.0 inflection: 2.0.1 memfs: 3.5.3 - oo-ascii-tree: 1.84.0 + oo-ascii-tree: 1.83.0 ts-pattern: 4.3.0 - type-fest: 3.12.0 + type-fest: 3.11.1 dev: false /@cspell/cspell-bundled-dicts@6.31.1: @@ -4396,7 +4397,7 @@ packages: '@cspell/dict-elixir': 4.0.3 '@cspell/dict-en-common-misspellings': 1.0.2 '@cspell/dict-en-gb': 1.1.33 - '@cspell/dict-en_us': 4.3.4 + '@cspell/dict-en_us': 4.3.3 '@cspell/dict-filetypes': 3.0.0 '@cspell/dict-fonts': 3.0.2 '@cspell/dict-fullstack': 3.1.5 @@ -4416,12 +4417,12 @@ packages: '@cspell/dict-php': 4.0.1 '@cspell/dict-powershell': 5.0.1 '@cspell/dict-public-licenses': 2.0.2 - '@cspell/dict-python': 4.1.1 + '@cspell/dict-python': 4.1.0 '@cspell/dict-r': 2.0.1 '@cspell/dict-ruby': 5.0.0 '@cspell/dict-rust': 4.0.1 '@cspell/dict-scala': 5.0.0 - '@cspell/dict-software-terms': 3.1.17 + '@cspell/dict-software-terms': 3.1.15 '@cspell/dict-sql': 2.1.0 '@cspell/dict-svelte': 1.0.2 '@cspell/dict-swift': 2.0.1 @@ -4480,8 +4481,8 @@ packages: resolution: {integrity: sha512-jigcODm7Z4IFZ4vParwwP3IT0fIgRq/9VoxkXfrxBMsLBGGM2QltHBj7pl+joX+c4cOHxfyZktGJK1B1wFtR4Q==} dev: true - /@cspell/dict-data-science@1.0.4: - resolution: {integrity: sha512-yuS6Z8Kk9cUxkXnXHiH8rDsmLxiBrUB9msnWh3xHM0WFlUQp3uMI6KYq/kxN7IS02lgWPy1Vt7GF+h3Q+Jt31w==} + /@cspell/dict-data-science@1.0.2: + resolution: {integrity: sha512-ZyOumj/4HKXc8q0u8aa0nvGE/nBTCbiU3BA+etqs5ghh421uUwKcXN1bgJM/L/MwOihdivvTbSWmK+134BCpUw==} dev: true /@cspell/dict-django@4.0.2: @@ -4508,8 +4509,8 @@ packages: resolution: {integrity: sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==} dev: true - /@cspell/dict-en_us@4.3.4: - resolution: {integrity: sha512-mR2yqWmFip1zTKja2SqyVMbzuqEThqkEJk9M32bMDziPJpEyOIPvLA0UPmj3cyRKJkRuVF0bhDCE33O+at38hw==} + /@cspell/dict-en_us@4.3.3: + resolution: {integrity: sha512-Csjm8zWo1YzLrQSdVZsRMfwHXoqqKR41pA8RpRGy2ODPjFeSteslyTW7jv1+R5V/E/IUI97Cxu+Nobm8MBy4MA==} dev: true /@cspell/dict-filetypes@3.0.0: @@ -4588,10 +4589,10 @@ packages: resolution: {integrity: sha512-baKkbs/WGEV2lCWZoL0KBPh3uiPcul5GSDwmXEBAsR5McEW52LF94/b7xWM0EmSAc/y8ODc5LnPYC7RDRLi6LQ==} dev: true - /@cspell/dict-python@4.1.1: - resolution: {integrity: sha512-gllGfnunCC3HhNuiF/e8OHq+EZG3bErydmnE1TlvSSngChLo/0O46hD7U+chHyTakZ6z4ancH5O+0cQ78e8XDA==} + /@cspell/dict-python@4.1.0: + resolution: {integrity: sha512-H4g3c25axmm0rvcZ/Dy+r+nKbhIeVdhe0OxlOGH8rolDiiP12ulh4Asnz07kKvYZ55sGCtXqzJ3YTzExwIR6Tw==} dependencies: - '@cspell/dict-data-science': 1.0.4 + '@cspell/dict-data-science': 1.0.2 dev: true /@cspell/dict-r@2.0.1: @@ -4614,8 +4615,8 @@ packages: resolution: {integrity: sha512-ph0twaRoV+ylui022clEO1dZ35QbeEQaKTaV2sPOsdwIokABPIiK09oWwGK9qg7jRGQwVaRPEq0Vp+IG1GpqSQ==} dev: true - /@cspell/dict-software-terms@3.1.17: - resolution: {integrity: sha512-LYEoHdwO8Wa7qCWn3CB50HJ9D++c33y5Y6wLSooERkbhak/ZVv8RNO/eZJLO25cAQxrAA1AbcAzGIpnFnZ2EjQ==} + /@cspell/dict-software-terms@3.1.15: + resolution: {integrity: sha512-EHnLozXWeJr3mUzLz8uH58EvnQTPZ7Y4wHDDxbQTF+QIbu41mLbPjgN/fU1pHAEsV4BzDc08E0EzAZlHxJfEKQ==} dev: true /@cspell/dict-sql@2.1.0: @@ -4785,14 +4786,14 @@ packages: react: 18.2.0 dev: false - /@esbuild-plugins/node-resolve@0.1.4(esbuild@0.18.10): + /@esbuild-plugins/node-resolve@0.1.4(esbuild@0.18.0): resolution: {integrity: sha512-haFQ0qhxEpqtWWY0kx1Y5oE3sMyO1PcoSiWEPrAw6tm/ZOOLXjSs6Q+v1v9eyuVF0nNt50YEvrcrvENmyoMv5g==} peerDependencies: esbuild: '*' dependencies: '@types/resolve': 1.20.2 debug: 4.3.4 - esbuild: 0.18.10 + esbuild: 0.18.0 escape-string-regexp: 4.0.0 resolve: 1.22.2 transitivePeerDependencies: @@ -4807,8 +4808,8 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm64@0.18.10: - resolution: {integrity: sha512-ynm4naLbNbK0ajf9LUWtQB+6Vfg1Z/AplArqr4tGebC00Z6m9Y91OVIcjDa461wGcZwcaHYaZAab4yJxfhisTQ==} + /@esbuild/android-arm64@0.18.0: + resolution: {integrity: sha512-nAwRCs5+jxi3gBMVkOqmRvsITB/UtfpvkbMwAwJUIbp66NnPbV2KGCFnjNn7IEqabJQXfBLe/QLdjCGpHU+yEw==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -4833,8 +4834,8 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm@0.18.10: - resolution: {integrity: sha512-3KClmVNd+Fku82uZJz5C4Rx8m1PPmWUFz5Zkw8jkpZPOmsq+EG1TTOtw1OXkHuX3WczOFQigrtf60B1ijKwNsg==} + /@esbuild/android-arm@0.18.0: + resolution: {integrity: sha512-+uLHSiWK3qOeyDYCf/nuvIgCnQsYjXWNa3TlGYLW1pPG7OYMawllU+VyBgHQPjF2aIUVFpfrvz5aAfxGk/0qNg==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -4850,8 +4851,8 @@ packages: requiresBuild: true optional: true - /@esbuild/android-x64@0.18.10: - resolution: {integrity: sha512-vFfXj8P9Yfjh54yqUDEHKzqzYuEfPyAOl3z7R9hjkwt+NCvbn9VMxX+IILnAfdImRBfYVItgSUsqGKhJFnBwZw==} + /@esbuild/android-x64@0.18.0: + resolution: {integrity: sha512-TiOJmHQ8bXCGlYLpBd3Qy7N8dxi4n6q+nOmTzPr5Hb/bUr+PKuP4e5lWaOlpkaKc1Q9wsFt+sHfQpFCrM7SMow==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -4867,8 +4868,8 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-arm64@0.18.10: - resolution: {integrity: sha512-k2OJQ7ZxE6sVc91+MQeZH9gFeDAH2uIYALPAwTjTCvcPy9Dzrf7V7gFUQPYkn09zloWhQ+nvxWHia2x2ZLR0sQ==} + /@esbuild/darwin-arm64@0.18.0: + resolution: {integrity: sha512-5GsFovtGyjMIXJrcCzmI1hX3TneCrmFncFIlo0WrRvWcVU6H094P854ZaP8qoLgevXhggO2dhlEGYY0Zv6/S9Q==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -4884,8 +4885,8 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-x64@0.18.10: - resolution: {integrity: sha512-tnz/mdZk1L1Z3WpGjin/L2bKTe8/AKZpI8fcCLtH+gq8WXWsCNJSxlesAObV4qbtTl6pG5vmqFXfWUQ5hV8PAQ==} + /@esbuild/darwin-x64@0.18.0: + resolution: {integrity: sha512-4K/QCksQ8F58rvC1D62Xi4q4E7YWpiyc3zy2H/n1W7y0hjQpOBBxciLn0qycMskP/m/I5h9HNbRlu1aK821sHg==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -4901,8 +4902,8 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-arm64@0.18.10: - resolution: {integrity: sha512-QJluV0LwBrbHnYYwSKC+K8RGz0g/EyhpQH1IxdoFT0nM7PfgjE+aS8wxq/KFEsU0JkL7U/EEKd3O8xVBxXb2aA==} + /@esbuild/freebsd-arm64@0.18.0: + resolution: {integrity: sha512-DMazN0UGzipD0Fi1O9pRX0xfp+JC3gSnFWxTWq88Dr/odWhZzm8Jqy44LN2veYeipb1fBMxhoEp7eCr902SWqg==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -4918,8 +4919,8 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-x64@0.18.10: - resolution: {integrity: sha512-Hi/ycUkS6KTw+U9G5PK5NoK7CZboicaKUSVs0FSiPNtuCTzK6HNM4DIgniH7hFaeuszDS9T4dhAHWiLSt/Y5Ng==} + /@esbuild/freebsd-x64@0.18.0: + resolution: {integrity: sha512-GdkJAB3ZBiYnie9iFO9v/CM4ko0dm5SYkUs97lBKNLHw9mo4H9IXwGNKtUztisEsmUP0IWfEi4YTWOJF3DIO4w==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -4935,8 +4936,8 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm64@0.18.10: - resolution: {integrity: sha512-Nz6XcfRBOO7jSrVpKAyEyFOPGhySPNlgumSDhWAspdQQ11ub/7/NZDMhWDFReE9QH/SsCOCLQbdj0atAk/HMOQ==} + /@esbuild/linux-arm64@0.18.0: + resolution: {integrity: sha512-Mb3yCN9PXA6G5qf84UF0IEuXP22eyNlquF17Zs2F1vVBM0CtyWLYosC5JaxBxfK6EzWwB2IkPBIjMeK3ek+ItA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -4952,8 +4953,8 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm@0.18.10: - resolution: {integrity: sha512-HfFoxY172tVHPIvJy+FHxzB4l8xU7e5cxmNS11cQ2jt4JWAukn/7LXaPdZid41UyTweqa4P/1zs201gRGCTwHw==} + /@esbuild/linux-arm@0.18.0: + resolution: {integrity: sha512-A3Ue/oZdb43znNpeY71FrAjZF20MtnBKCGb1vXLIVg5qg8rRM1gRgn6X2ixYwATiw5dE04JnP+aV4OBf8c5ZvQ==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -4969,8 +4970,8 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ia32@0.18.10: - resolution: {integrity: sha512-otMdmSmkMe+pmiP/bZBjfphyAsTsngyT9RCYwoFzqrveAbux9nYitDTpdgToG0Z0U55+PnH654gCH2GQ1aB6Yw==} + /@esbuild/linux-ia32@0.18.0: + resolution: {integrity: sha512-WNDXgJdfDhN6ZxHU7HgR2BRDVx9iGN8SpmebUUGdENg4MZJndGcaQuf2kCJjMwoK0+es1g61TeJzAMxfgDcmcA==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -4995,8 +4996,8 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-loong64@0.18.10: - resolution: {integrity: sha512-t8tjFuON1koxskzQ4VFoh0T5UDUMiLYjwf9Wktd0tx8AoK6xgU+5ubKOpWpcnhEQ2tESS5u0v6QuN8PX/ftwcQ==} + /@esbuild/linux-loong64@0.18.0: + resolution: {integrity: sha512-PBr8Lf+L8amvheTGFVNK/0qionszkOKMq2WyfFlVz8D41v0+uSth6fYYHwtASkMk4xf+oh0vW8NYuav3/3RHuQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -5012,8 +5013,8 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-mips64el@0.18.10: - resolution: {integrity: sha512-+dUkcVzcfEJHz3HEnVpIJu8z8Wdn2n/nWMWdl6FVPFGJAVySO4g3+XPzNKFytVFwf8hPVDwYXzVcu8GMFqsqZw==} + /@esbuild/linux-mips64el@0.18.0: + resolution: {integrity: sha512-Lg4ygah5bwfDDCOMFsBJjSVbD1UzNwWt4f7DhpaSIFOrJqoECX1VTByKw3iSDAVRlwl1cljlfy7wlysrRZcdiQ==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -5029,8 +5030,8 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ppc64@0.18.10: - resolution: {integrity: sha512-sO3PjjxEGy+PY2qkGe2gwJbXdZN9wAYpVBZWFD0AwAoKuXRkWK0/zaMQ5ekUFJDRDCRm8x5U0Axaub7ynH/wVg==} + /@esbuild/linux-ppc64@0.18.0: + resolution: {integrity: sha512-obz/firdtou244DIjHzdKmJChwGseqA3tWGa6xPMfuq54Ca4Pp1a4ANMrqy2IZ67rfpRHcJTlb2h3rSfW6tvAA==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -5046,8 +5047,8 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-riscv64@0.18.10: - resolution: {integrity: sha512-JDtdbJg3yjDeXLv4lZYE1kiTnxv73/8cbPHY9T/dUKi8rYOM/k5b3W4UJLMUksuQ6nTm5c89W1nADsql6FW75A==} + /@esbuild/linux-riscv64@0.18.0: + resolution: {integrity: sha512-UkuBdxQsxi39wWrRLMOkJl//82/hpQw79TD+OBLw3IBYyVQ4Wfvpe56RfEGK/j439sIm79ccnD5RUNQceHvZdQ==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -5063,8 +5064,8 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-s390x@0.18.10: - resolution: {integrity: sha512-NLuSKcp8WckjD2a7z5kzLiCywFwBTMlIxDNuud1AUGVuwBBJSkuubp6cNjJ0p5c6CZaA3QqUGwjHJBiG1SoOFw==} + /@esbuild/linux-s390x@0.18.0: + resolution: {integrity: sha512-MgyuC30oYB465hyAqsb3EH6Y4zTeqqgixRAOpsDNMCelyDiW9ZDPXvMPfBgCZGJlDZFGKDm2I9ou8E3VI+v7pg==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -5080,8 +5081,8 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-x64@0.18.10: - resolution: {integrity: sha512-wj2KRsCsFusli+6yFgNO/zmmLslislAWryJnodteRmGej7ZzinIbMdsyp13rVGde88zxJd5vercNYK9kuvlZaQ==} + /@esbuild/linux-x64@0.18.0: + resolution: {integrity: sha512-oLLKU3F4pKWAsNmfi7Rd4qkj0qvg1S923ZjlcISA2IMgHsODA9xzwerqWayI5nOhLGgKXviDofn9exTeA4EUQQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -5097,8 +5098,8 @@ packages: requiresBuild: true optional: true - /@esbuild/netbsd-x64@0.18.10: - resolution: {integrity: sha512-pQ9QqxEPI3cVRZyUtCoZxhZK3If+7RzR8L2yz2+TDzdygofIPOJFaAPkEJ5rYIbUO101RaiYxfdOBahYexLk5A==} + /@esbuild/netbsd-x64@0.18.0: + resolution: {integrity: sha512-BEfJrZsZ/gMtpS2vC+2YoFGxmfLKiYQvj8lZrBfjKzQrwyMpH53CzQJj9ypOx9ldjM/MVxf9i9wi/rS4BWV7WA==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -5114,8 +5115,8 @@ packages: requiresBuild: true optional: true - /@esbuild/openbsd-x64@0.18.10: - resolution: {integrity: sha512-k8GTIIW9I8pEEfoOUm32TpPMgSg06JhL5DO+ql66aLTkOQUs0TxCA67Wi7pv6z8iF8STCGcNbm3UWFHLuci+ag==} + /@esbuild/openbsd-x64@0.18.0: + resolution: {integrity: sha512-eDolHeG3REnEIgwl7Lw2S0znUMY4PFVtCAzLKqdRO0HD+iPKJR8n2MEJJyhPdUjcobo8SEQ2AG6gtYfft9VFHg==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -5131,8 +5132,8 @@ packages: requiresBuild: true optional: true - /@esbuild/sunos-x64@0.18.10: - resolution: {integrity: sha512-vIGYJIdEI6d4JBucAx8py792G8J0GP40qSH+EvSt80A4zvGd6jph+5t1g+eEXcS2aRpgZw6CrssNCFZxTdEsxw==} + /@esbuild/sunos-x64@0.18.0: + resolution: {integrity: sha512-kl7vONem2wmRQke015rSrknmc6TYXKVNs2quiVTdvkSufscrjegpNqKyP7v6EHqXtvkzrB92ySjpfzazKG627g==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -5148,8 +5149,8 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-arm64@0.18.10: - resolution: {integrity: sha512-kRhNcMZFGMW+ZHCarAM1ypr8OZs0k688ViUCetVCef9p3enFxzWeBg9h/575Y0nsFu0ZItluCVF5gMR2pwOEpA==} + /@esbuild/win32-arm64@0.18.0: + resolution: {integrity: sha512-WohArFQ3HStBu9MAsx3JUk2wfC2v8QoadnMoNfx3Y26ac54tD/wQhPzw4QOzQbSqOFqzIMLKWbxindTsko+9OA==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -5165,8 +5166,8 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-ia32@0.18.10: - resolution: {integrity: sha512-AR9PX1whYaYh9p0EOaKna0h48F/A101Mt/ag72+kMkkBZXPQ7cjbz2syXI/HI3OlBdUytSdHneljfjvUoqwqiQ==} + /@esbuild/win32-ia32@0.18.0: + resolution: {integrity: sha512-SdnpSOxpeoewYCurmfLVepLuhOAphWkGTxWHifFjp37DaUHwF1fpGzyxhZoXMt5MKGuAO5aE3c5668YYtno+9Q==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -5182,8 +5183,8 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-x64@0.18.10: - resolution: {integrity: sha512-5sTkYhAGHNRr6bVf4RM0PsscqVr6/DBYdrlMh168oph3usid3lKHcHEEHmr34iZ9GHeeg2juFOxtpl6XyC3tpw==} + /@esbuild/win32-x64@0.18.0: + resolution: {integrity: sha512-WJxImv0Pehpbo+pgg7Xrn88/b6ZzSweNHTw/2LW95JjeQUIS6ToJeQmjAdud9H3yiHJmhLOmEAOvUdNLhptD0w==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -5282,7 +5283,7 @@ packages: md5-file: 3.2.3 md5hex: 1.0.0 minipass: 3.1.6 - node-fetch: 2.6.12 + node-fetch: 2.6.11 node-forge: 1.3.1 npm-package-arg: 7.0.0 ora: 3.4.0 @@ -5391,7 +5392,7 @@ packages: fs-extra: 9.0.0 is-docker: 2.2.1 is-wsl: 2.2.0 - node-fetch: 2.6.12 + node-fetch: 2.6.11 open: 8.4.2 resolve-from: 5.0.0 semver: 7.3.2 @@ -5417,7 +5418,7 @@ packages: rimraf: 2.7.1 sudo-prompt: 8.2.5 tmp: 0.0.33 - tslib: 2.6.0 + tslib: 2.5.3 transitivePeerDependencies: - supports-color dev: false @@ -5431,7 +5432,7 @@ packages: getenv: 1.0.0 jimp-compact: 0.16.1 mime: 2.6.0 - node-fetch: 2.6.12 + node-fetch: 2.6.11 parse-png: 2.1.0 resolve-from: 5.0.0 semver: 7.3.2 @@ -5523,7 +5524,7 @@ packages: '@segment/loosely-validate-event': 2.0.0 fetch-retry: 4.1.1 md5: 2.3.0 - node-fetch: 2.6.12 + node-fetch: 2.6.11 remove-trailing-slash: 0.1.1 uuid: 8.3.2 transitivePeerDependencies: @@ -5562,8 +5563,8 @@ packages: resolution: {integrity: sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==} dev: false - /@floating-ui/core@1.3.1: - resolution: {integrity: sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==} + /@floating-ui/core@1.2.6: + resolution: {integrity: sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==} dev: false /@floating-ui/dom@0.5.4: @@ -5572,10 +5573,10 @@ packages: '@floating-ui/core': 0.7.3 dev: false - /@floating-ui/dom@1.4.3: - resolution: {integrity: sha512-nB/68NyaQlcdY22L+Fgd1HERQ7UGv7XFN+tPxwrEfQL4nKtAP/jIZnZtpUlXbtV+VEGHh6W/63Gy2C5biWI3sA==} + /@floating-ui/dom@1.2.9: + resolution: {integrity: sha512-sosQxsqgxMNkV3C+3UqTS6LxP7isRLwX8WMepp843Rb3/b0Wz8+MdUkxJksByip3C2WwLugLHN1b4ibn//zKwQ==} dependencies: - '@floating-ui/core': 1.3.1 + '@floating-ui/core': 1.2.6 dev: false /@floating-ui/react-dom@0.7.2(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0): @@ -5592,13 +5593,13 @@ packages: - '@types/react' dev: false - /@floating-ui/react-dom@2.0.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==} + /@floating-ui/react-dom@2.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Ke0oU3SeuABC2C4OFu2mSAwHIP5WUiV98O9YWoHV4Q5aT6E9k06DV0Khi5uYspR8xmmBk08t8ZDcz3TR3ARkEg==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@floating-ui/dom': 1.4.3 + '@floating-ui/dom': 1.2.9 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -5654,8 +5655,8 @@ packages: graphql: 15.8.0 dev: false - /@grpc/grpc-js@1.8.17: - resolution: {integrity: sha512-DGuSbtMFbaRsyffMf+VEkVu8HkSXEUfO3UyGJNtqxW9ABdtTIA+2UXAJpwbJS+xfQxuwqLUeELmL6FuZkOqPxw==} + /@grpc/grpc-js@1.8.15: + resolution: {integrity: sha512-H2Bu/w6+oQ58DsRbQol66ERBk3V5ZIak/z/MDx0T4EgDnJWps807I6BvTjq0v6UvZtOcLO+ur+Q9wvniqu3OJA==} engines: {node: ^8.13.0 || >=10.10.0} dependencies: '@grpc/proto-loader': 0.7.7 @@ -5682,7 +5683,7 @@ packages: '@types/long': 4.0.2 lodash.camelcase: 4.3.0 long: 4.0.0 - protobufjs: 7.2.4 + protobufjs: 7.2.3 yargs: 17.7.2 dev: false @@ -5789,7 +5790,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@sinonjs/fake-timers': 10.3.0 + '@sinonjs/fake-timers': 10.2.0 '@types/node': 18.15.1 jest-message-util: 29.5.0 jest-mock: 29.5.0 @@ -5817,7 +5818,7 @@ packages: jest-regex-util: 29.4.3 jest-util: 29.5.0 micromatch: 4.0.5 - pirates: 4.0.6 + pirates: 4.0.5 slash: 3.0.0 write-file-atomic: 4.0.2 transitivePeerDependencies: @@ -5887,8 +5888,11 @@ packages: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} - /@jridgewell/source-map@0.3.4: - resolution: {integrity: sha512-KE/SxsDqNs3rrWwFHcRh15ZLVFrI0YoZtgAdIyIq9k5hUNmiWRXXThPomIxHuL20sLdgzbDFyvkUMna14bvtrw==} + /@jridgewell/source-map@0.3.3: + resolution: {integrity: sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.18 /@jridgewell/sourcemap-codec@1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} @@ -5907,20 +5911,20 @@ packages: engines: {node: '>=12'} dependencies: jsbi: 4.3.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@juggle/resize-observer@3.4.0: resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} dev: false - /@mdx-js/esbuild@2.3.0(esbuild@0.18.10): + /@mdx-js/esbuild@2.3.0(esbuild@0.18.0): resolution: {integrity: sha512-r/vsqsM0E+U4Wr0DK+0EfmABE/eg+8ITW4DjvYdh3ve/tK2safaqHArNnaqbOk1DjYGrhxtoXoGaM3BY8fGBTA==} peerDependencies: esbuild: '>=0.11.0' dependencies: '@mdx-js/mdx': 2.3.0 - esbuild: 0.18.10 + esbuild: 0.18.0 node-fetch: 3.3.1 vfile: 5.3.7 transitivePeerDependencies: @@ -5971,7 +5975,7 @@ packages: '@motionone/easing': 10.15.1 '@motionone/types': 10.15.1 '@motionone/utils': 10.15.1 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@motionone/dom@10.12.0: @@ -5982,14 +5986,14 @@ packages: '@motionone/types': 10.15.1 '@motionone/utils': 10.15.1 hey-listen: 1.0.8 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@motionone/easing@10.15.1: resolution: {integrity: sha512-6hIHBSV+ZVehf9dcKZLT7p5PEKHGhDwky2k8RKkmOvUoYP3S+dXsKupyZpqx5apjd9f+php4vXk4LuS+ADsrWw==} dependencies: '@motionone/utils': 10.15.1 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@motionone/generators@10.15.1: @@ -5997,7 +6001,7 @@ packages: dependencies: '@motionone/types': 10.15.1 '@motionone/utils': 10.15.1 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@motionone/types@10.15.1: @@ -6009,7 +6013,7 @@ packages: dependencies: '@motionone/types': 10.15.1 hey-listen: 1.0.8 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@ndelangen/get-tarball@3.0.9: @@ -6193,7 +6197,7 @@ packages: peerDependencies: '@opentelemetry/api': ^1.0.0 dependencies: - '@grpc/grpc-js': 1.8.17 + '@grpc/grpc-js': 1.8.15 '@grpc/proto-loader': 0.6.13 '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.5.0(@opentelemetry/api@1.4.1) @@ -6219,7 +6223,7 @@ packages: peerDependencies: '@opentelemetry/api': ^1.0.0 dependencies: - '@grpc/grpc-js': 1.8.17 + '@grpc/grpc-js': 1.8.15 '@grpc/proto-loader': 0.6.13 '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.5.0(@opentelemetry/api@1.4.1) @@ -6358,7 +6362,7 @@ packages: is-glob: 4.0.3 open: 9.1.0 picocolors: 1.0.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: true /@planetscale/database@1.7.0: @@ -6897,7 +6901,7 @@ packages: optional: true dependencies: '@babel/runtime': 7.22.5 - '@floating-ui/react-dom': 2.0.1(react-dom@18.2.0)(react@18.2.0) + '@floating-ui/react-dom': 2.0.0(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.38)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.0.38)(react@18.2.0) @@ -7501,15 +7505,15 @@ packages: transitivePeerDependencies: - supports-color - /@react-native-community/cli-doctor@10.2.5: - resolution: {integrity: sha512-1YbzXvsldBmSw1MmBsXB74bKiHXKNCjlb2ByLgkfTiarpSvETYam3g5vex0N+qc0Cdkzkq+8NznE744LFhnUpw==} + /@react-native-community/cli-doctor@10.2.2: + resolution: {integrity: sha512-49Ep2aQOF0PkbAR/TcyMjOm9XwBa8VQr+/Zzf4SJeYwiYLCT1NZRAVAVjYRXl0xqvq5S5mAGZZShS4AQl4WsZw==} dependencies: '@react-native-community/cli-config': 10.1.1 - '@react-native-community/cli-platform-ios': 10.2.5 + '@react-native-community/cli-platform-ios': 10.2.1 '@react-native-community/cli-tools': 10.1.1 chalk: 4.1.2 command-exists: 1.2.9 - envinfo: 7.10.0 + envinfo: 7.8.1 execa: 1.0.0 hermes-profile-transformer: 0.0.6 ip: 1.1.8 @@ -7567,31 +7571,31 @@ packages: transitivePeerDependencies: - encoding - /@react-native-community/cli-platform-ios@10.2.5: - resolution: {integrity: sha512-hq+FZZuSBK9z82GLQfzdNDl8vbFx5UlwCLFCuTtNCROgBoapFtVZQKRP2QBftYNrQZ0dLAb01gkwxagHsQCFyg==} + /@react-native-community/cli-platform-ios@10.2.1: + resolution: {integrity: sha512-hz4zu4Y6eyj7D0lnZx8Mf2c2si8y+zh/zUTgCTaPPLzQD8jSZNNBtUUiA1cARm2razpe8marCZ1QbTMAGbf3mg==} dependencies: '@react-native-community/cli-tools': 10.1.1 chalk: 4.1.2 execa: 1.0.0 - fast-xml-parser: 4.2.5 + fast-xml-parser: 4.2.4 glob: 7.2.3 ora: 5.4.1 transitivePeerDependencies: - encoding - /@react-native-community/cli-plugin-metro@10.2.3(@babel/core@7.22.1): - resolution: {integrity: sha512-jHi2oDuTePmW4NEyVT8JEGNlIYcnFXCSV2ZMp4rnDrUk4TzzyvS3IMvDlESEmG8Kry8rvP0KSUx/hTpy37Sbkw==} + /@react-native-community/cli-plugin-metro@10.2.2(@babel/core@7.22.1): + resolution: {integrity: sha512-sTGjZlD3OGqbF9v1ajwUIXhGmjw9NyJ/14Lo0sg7xH8Pv4qUd5ZvQ6+DWYrQn3IKFUMfGFWYyL81ovLuPylrpw==} dependencies: '@react-native-community/cli-server-api': 10.1.1 '@react-native-community/cli-tools': 10.1.1 chalk: 4.1.2 execa: 1.0.0 - metro: 0.73.10 - metro-config: 0.73.10 - metro-core: 0.73.10 - metro-react-native-babel-transformer: 0.73.10(@babel/core@7.22.1) - metro-resolver: 0.73.10 - metro-runtime: 0.73.10 + metro: 0.73.9 + metro-config: 0.73.9 + metro-core: 0.73.9 + metro-react-native-babel-transformer: 0.73.9(@babel/core@7.22.1) + metro-resolver: 0.73.9 + metro-runtime: 0.73.9 readline: 1.3.0 transitivePeerDependencies: - '@babel/core' @@ -7625,7 +7629,7 @@ packages: chalk: 4.1.2 find-up: 5.0.0 mime: 2.6.0 - node-fetch: 2.6.12 + node-fetch: 2.6.11 open: 6.4.0 ora: 5.4.1 semver: 6.3.0 @@ -7646,9 +7650,9 @@ packages: '@react-native-community/cli-clean': 10.1.1 '@react-native-community/cli-config': 10.1.1 '@react-native-community/cli-debugger-ui': 10.0.0 - '@react-native-community/cli-doctor': 10.2.5 + '@react-native-community/cli-doctor': 10.2.2 '@react-native-community/cli-hermes': 10.2.0 - '@react-native-community/cli-plugin-metro': 10.2.3(@babel/core@7.22.1) + '@react-native-community/cli-plugin-metro': 10.2.2(@babel/core@7.22.1) '@react-native-community/cli-server-api': 10.1.1 '@react-native-community/cli-tools': 10.1.1 '@react-native-community/cli-types': 10.0.0 @@ -7695,7 +7699,7 @@ packages: react-native-safe-area-context: '>= 3.0.0' react-native-screens: '>= 3.0.0' dependencies: - '@react-navigation/elements': 1.3.18(@react-navigation/native@6.1.6)(react-native-safe-area-context@4.5.1)(react-native@0.71.3)(react@18.2.0) + '@react-navigation/elements': 1.3.17(@react-navigation/native@6.1.6)(react-native-safe-area-context@4.5.1)(react-native@0.71.3)(react@18.2.0) '@react-navigation/native': 6.1.6(react-native@0.71.3)(react@18.2.0) color: 4.2.3 react: 18.2.0 @@ -7705,12 +7709,12 @@ packages: warn-once: 0.1.1 dev: false - /@react-navigation/core@6.4.9(react@18.2.0): - resolution: {integrity: sha512-G9GH7bP9x0qqupxZnkSftnkn4JoXancElTvFc8FVGfEvxnxP+gBo3wqcknyBi7M5Vad4qecsYjCOa9wqsftv9g==} + /@react-navigation/core@6.4.8(react@18.2.0): + resolution: {integrity: sha512-klZ9Mcf/P2j+5cHMoGyIeurEzyBM2Uq9+NoSFrF6sdV5iCWHLFhrCXuhbBiQ5wVLCKf4lavlkd/DDs47PXs9RQ==} peerDependencies: react: '*' dependencies: - '@react-navigation/routers': 6.1.9 + '@react-navigation/routers': 6.1.8 escape-string-regexp: 4.0.0 nanoid: 3.3.6 query-string: 7.1.3 @@ -7730,7 +7734,7 @@ packages: react-native-safe-area-context: '>= 3.0.0' react-native-screens: '>= 3.0.0' dependencies: - '@react-navigation/elements': 1.3.18(@react-navigation/native@6.1.6)(react-native-safe-area-context@4.5.1)(react-native@0.71.3)(react@18.2.0) + '@react-navigation/elements': 1.3.17(@react-navigation/native@6.1.6)(react-native-safe-area-context@4.5.1)(react-native@0.71.3)(react@18.2.0) '@react-navigation/native': 6.1.6(react-native@0.71.3)(react@18.2.0) color: 4.2.3 react: 18.2.0 @@ -7742,8 +7746,8 @@ packages: warn-once: 0.1.1 dev: false - /@react-navigation/elements@1.3.18(@react-navigation/native@6.1.6)(react-native-safe-area-context@4.5.1)(react-native@0.71.3)(react@18.2.0): - resolution: {integrity: sha512-/0hwnJkrr415yP0Hf4PjUKgGyfshrvNUKFXN85Mrt1gY49hy9IwxZgrrxlh0THXkPeq8q4VWw44eHDfAcQf20Q==} + /@react-navigation/elements@1.3.17(@react-navigation/native@6.1.6)(react-native-safe-area-context@4.5.1)(react-native@0.71.3)(react@18.2.0): + resolution: {integrity: sha512-sui8AzHm6TxeEvWT/NEXlz3egYvCUog4tlXA4Xlb2Vxvy3purVXDq/XsM56lJl344U5Aj/jDzkVanOTMWyk4UA==} peerDependencies: '@react-navigation/native': ^6.0.0 react: '*' @@ -7762,7 +7766,7 @@ packages: react: '*' react-native: '*' dependencies: - '@react-navigation/core': 6.4.9(react@18.2.0) + '@react-navigation/core': 6.4.8(react@18.2.0) escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 nanoid: 3.3.6 @@ -7770,8 +7774,8 @@ packages: react-native: 0.71.3(@babel/core@7.22.1)(@babel/preset-env@7.22.5)(react@18.2.0) dev: false - /@react-navigation/routers@6.1.9: - resolution: {integrity: sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==} + /@react-navigation/routers@6.1.8: + resolution: {integrity: sha512-CEge+ZLhb1HBrSvv4RwOol7EKLW1QoqVIQlE9TN5MpxS/+VoQvP+cLbuz0Op53/iJfYhtXRFd1ZAd3RTRqto9w==} dependencies: nanoid: 3.3.6 dev: false @@ -7786,7 +7790,7 @@ packages: react-native-safe-area-context: '>= 3.0.0' react-native-screens: '>= 3.0.0' dependencies: - '@react-navigation/elements': 1.3.18(@react-navigation/native@6.1.6)(react-native-safe-area-context@4.5.1)(react-native@0.71.3)(react@18.2.0) + '@react-navigation/elements': 1.3.17(@react-navigation/native@6.1.6)(react-native-safe-area-context@4.5.1)(react-native@0.71.3)(react@18.2.0) '@react-navigation/native': 6.1.6(react-native@0.71.3)(react@18.2.0) color: 4.2.3 react: 18.2.0 @@ -7883,8 +7887,8 @@ packages: '@react-spring/three': 9.6.1(@react-three/fiber@8.13.4)(react@18.2.0)(three@0.154.0) '@react-three/fiber': 8.13.4(react-dom@18.2.0)(react@18.2.0)(three@0.154.0) '@use-gesture/react': 10.2.27(react@18.2.0) - camera-controls: 2.7.0(three@0.154.0) - detect-gpu: 5.0.30 + camera-controls: 2.6.0(three@0.154.0) + detect-gpu: 5.0.29 glsl-noise: 0.0.0 lodash.clamp: 4.0.3 lodash.omit: 4.5.0 @@ -7947,7 +7951,7 @@ packages: engines: {node: '>=14'} dev: false - /@rnx-kit/babel-preset-metro-react-native@1.1.4(@babel/core@7.22.1)(metro-react-native-babel-preset@0.76.7): + /@rnx-kit/babel-preset-metro-react-native@1.1.4(@babel/core@7.22.1)(metro-react-native-babel-preset@0.76.6): resolution: {integrity: sha512-ev82sa8Q5Z4a7kQ9pfCKYvpPpPesn0bgOFX8mNx5Gb3uZENb1i1oqySsmw1Qrrf/1KCMi4DKXOI7KezUl8Kf4g==} peerDependencies: '@babel/core': ^7.0.0 @@ -7959,7 +7963,7 @@ packages: dependencies: '@babel/core': 7.22.1 babel-plugin-const-enum: 1.2.0(@babel/core@7.22.1) - metro-react-native-babel-preset: 0.76.7(@babel/core@7.22.1) + metro-react-native-babel-preset: 0.76.6(@babel/core@7.22.1) transitivePeerDependencies: - supports-color dev: true @@ -7970,7 +7974,7 @@ packages: chalk: 4.1.2 dev: true - /@rnx-kit/metro-config@1.3.6(@babel/core@7.22.1)(metro-config@0.76.7)(metro-react-native-babel-preset@0.76.7)(react-native@0.71.3)(react@18.2.0): + /@rnx-kit/metro-config@1.3.6(@babel/core@7.22.1)(metro-config@0.76.6)(metro-react-native-babel-preset@0.76.6)(react-native@0.71.3)(react@18.2.0): resolution: {integrity: sha512-gvcEwFkhviioaWfDKNtABc+qRvZbMxcM5RGvFn6riOx7auKVypD5J3j+mvPjXNLJpdKnerpGfIxzjm8ROoaiqg==} peerDependencies: '@react-native/metro-config': '*' @@ -7982,12 +7986,12 @@ packages: '@react-native/metro-config': optional: true dependencies: - '@rnx-kit/babel-preset-metro-react-native': 1.1.4(@babel/core@7.22.1)(metro-react-native-babel-preset@0.76.7) + '@rnx-kit/babel-preset-metro-react-native': 1.1.4(@babel/core@7.22.1)(metro-react-native-babel-preset@0.76.6) '@rnx-kit/console': 1.0.11 '@rnx-kit/tools-node': 2.0.0 '@rnx-kit/tools-workspaces': 0.1.3 - metro-config: 0.76.7 - metro-react-native-babel-preset: 0.76.7(@babel/core@7.22.1) + metro-config: 0.76.6 + metro-react-native-babel-preset: 0.76.6(@babel/core@7.22.1) react: 18.2.0 react-native: 0.71.3(@babel/core@7.22.1)(@babel/preset-env@7.22.5)(react@18.2.0) transitivePeerDependencies: @@ -8051,8 +8055,8 @@ packages: '@tauri-apps/api': 1.3.0 dev: false - /@rushstack/eslint-patch@1.3.2: - resolution: {integrity: sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==} + /@rushstack/eslint-patch@1.3.1: + resolution: {integrity: sha512-RkmuBcqiNioeeBKbgzMlOdreUkJfYaSjwgx9XDgGGpjvWgyaxWvDmZVSN9CS6LjEASadhgPv2BcFp+SeouWXXA==} dev: true /@scena/dragscroll@1.4.0: @@ -8151,24 +8155,24 @@ packages: dependencies: type-detect: 4.0.8 - /@sinonjs/fake-timers@10.3.0: - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + /@sinonjs/fake-timers@10.2.0: + resolution: {integrity: sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg==} dependencies: '@sinonjs/commons': 3.0.0 - /@smithy/protocol-http@1.1.0: - resolution: {integrity: sha512-H5y/kZOqfJSqRkwtcAoVbqONmhdXwSgYNJ1Glk5Ry8qlhVVy5qUzD9EklaCH8/XLnoCsLO/F/Giee8MIvaBRkg==} + /@smithy/protocol-http@1.0.1: + resolution: {integrity: sha512-9OrEn0WfOVtBNYJUjUAn9AOiJ4lzERCJJ/JeZs8E6yajTGxBaFRxUnNBHiNqoDJVg076hY36UmEnPx7xXrvUSg==} engines: {node: '>=14.0.0'} dependencies: - '@smithy/types': 1.1.0 - tslib: 2.6.0 + '@smithy/types': 1.0.0 + tslib: 2.5.3 dev: false - /@smithy/types@1.1.0: - resolution: {integrity: sha512-KzmvisMmuwD2jZXuC9e65JrgsZM97y5NpDU7g347oB+Q+xQLU6hQZ5zFNNbEfwwOJHoOvEVTna+dk1h/lW7alw==} + /@smithy/types@1.0.0: + resolution: {integrity: sha512-kc1m5wPBHQCTixwuaOh9vnak/iJm21DrSf9UK6yDE5S3mQQ4u11pqAUiKWnlrZnYkeLfAI9UEHj9OaMT1v5Umg==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@splinetool/react-spline@2.2.3(@splinetool/runtime@0.9.128)(react-dom@18.2.0)(react@18.2.0): @@ -8441,7 +8445,7 @@ packages: ts-dedent: 2.2.0 dev: false - /@storybook/addon-styling@1.0.6(less@4.1.3)(postcss@8.4.23)(react-dom@18.2.0)(react@18.2.0)(webpack@5.88.1): + /@storybook/addon-styling@1.0.6(less@4.1.3)(postcss@8.4.23)(react-dom@18.2.0)(react@18.2.0)(webpack@5.86.0): resolution: {integrity: sha512-OHs6Yj04TjyFQ+1NQrMBxf+5tCEMDWGvkztB1XQf0+hNqNDRsoRvDkeXeS462RHCt3ffSYUi5leUvotmDYno6g==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -8452,22 +8456,22 @@ packages: react-dom: optional: true dependencies: - '@storybook/api': 7.0.24(react-dom@18.2.0)(react@18.2.0) - '@storybook/components': 7.0.24(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.24 - '@storybook/manager-api': 7.0.24(react-dom@18.2.0)(react@18.2.0) - '@storybook/node-logger': 7.0.24 - '@storybook/preview-api': 7.0.24 - '@storybook/theming': 7.0.24(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.24 - css-loader: 6.8.1(webpack@5.88.1) - less-loader: 11.1.3(less@4.1.3)(webpack@5.88.1) - postcss-loader: 7.3.3(postcss@8.4.23)(webpack@5.88.1) + '@storybook/api': 7.0.20(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.0.20(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.20 + '@storybook/manager-api': 7.0.20(react-dom@18.2.0)(react@18.2.0) + '@storybook/node-logger': 7.0.20 + '@storybook/preview-api': 7.0.20 + '@storybook/theming': 7.0.20(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.20 + css-loader: 6.8.1(webpack@5.86.0) + less-loader: 11.1.3(less@4.1.3)(webpack@5.86.0) + postcss-loader: 7.3.3(postcss@8.4.23)(webpack@5.86.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) resolve-url-loader: 5.0.0 - sass-loader: 13.3.2(webpack@5.88.1) - style-loader: 3.3.3(webpack@5.88.1) + sass-loader: 13.3.2(webpack@5.86.0) + style-loader: 3.3.3(webpack@5.86.0) transitivePeerDependencies: - fibers - less @@ -8522,8 +8526,8 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@storybook/api@7.0.24(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-rjWZgBbt43Ma5Vg2RwK9FtiF9ZkLRT+vOfDFtRL1PQkOIUlYlm33dOdPTh+HrW5QMO9cj/cchqmzU2AtgEZCyw==} + /@storybook/api@7.0.20(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-yHX3WcWdWqrJBuN85bvSijh/kYGuKXYWNDLmW++XPs0WGWBk/1UfMFEJShfccnSKSlbaTIU8e4dNH8x9Nk190w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -8533,8 +8537,8 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.24 - '@storybook/manager-api': 7.0.24(react-dom@18.2.0)(react@18.2.0) + '@storybook/client-logger': 7.0.20 + '@storybook/manager-api': 7.0.20(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -8626,15 +8630,57 @@ packages: express: 4.18.2 fs-extra: 11.1.1 glob: 8.1.0 - glob-promise: 6.0.3(glob@8.1.0) + glob-promise: 6.0.2(glob@8.1.0) magic-string: 0.27.0 remark-external-links: 8.0.0 remark-slug: 6.1.0 - rollup: 3.26.0 + rollup: 3.24.1 + typescript: 5.0.4 + vite: 4.3.9(@types/node@18.15.1) + transitivePeerDependencies: + - supports-color + dev: true + + /@storybook/builder-vite@7.0.5(typescript@5.0.4)(vite@4.3.9): + resolution: {integrity: sha512-jQUpqmTiCZpVCLTVuMu3bSv1Iw4TJJhKYyrsozlfSbAzdM1S8IDEVhpKo+XoUrYLrGURSP8918zaOrl7ht8pvw==} + peerDependencies: + '@preact/preset-vite': '*' + typescript: '>= 4.3.x' + vite: ^3.0.0 || ^4.0.0 + vite-plugin-glimmerx: '*' + peerDependenciesMeta: + '@preact/preset-vite': + optional: true + typescript: + optional: true + vite-plugin-glimmerx: + optional: true + dependencies: + '@storybook/channel-postmessage': 7.0.5 + '@storybook/channel-websocket': 7.0.5 + '@storybook/client-logger': 7.0.5 + '@storybook/core-common': 7.0.5 + '@storybook/csf-plugin': 7.0.5 + '@storybook/mdx2-csf': 1.1.0 + '@storybook/node-logger': 7.0.5 + '@storybook/preview': 7.0.5 + '@storybook/preview-api': 7.0.5 + '@storybook/types': 7.0.5 + browser-assert: 1.2.1 + es-module-lexer: 0.9.3 + express: 4.18.2 + fs-extra: 11.1.1 + glob: 8.1.0 + glob-promise: 6.0.2(glob@8.1.0) + magic-string: 0.27.0 + remark-external-links: 8.0.0 + remark-slug: 6.1.0 + rollup: 3.24.1 typescript: 5.0.4 vite: 4.3.9(less@4.1.3) transitivePeerDependencies: - supports-color + dev: false /@storybook/channel-postmessage@7.0.20: resolution: {integrity: sha512-GhVI40gbCnq20+Wjk/f8RD/T4gruLNKCjuwTnCAoKIQpMOVAB6ddx0469f9lF5tAha6alZn0MLk5CXPK8LAn5w==} @@ -8646,17 +8692,6 @@ packages: qs: 6.11.2 telejson: 7.1.0 - /@storybook/channel-postmessage@7.0.24: - resolution: {integrity: sha512-QLtLXjEeTEwBN/7pB888mBaykmRU9Jy2BitvZuLJWyHHygTYm3vYZOaGR37DT+q/6Ob5GaZ0tURZmCSNDe8IIA==} - dependencies: - '@storybook/channels': 7.0.24 - '@storybook/client-logger': 7.0.24 - '@storybook/core-events': 7.0.24 - '@storybook/global': 5.0.0 - qs: 6.11.2 - telejson: 7.1.0 - dev: false - /@storybook/channel-postmessage@7.0.5: resolution: {integrity: sha512-Ri0188tHfvg2asdNOVUeLU1w1G/V485y/vatZ/vC3My9cG8P39t8ZKAJdA3hukc+7RZKZU+snqCz7de89/CF7Q==} dependencies: @@ -8674,12 +8709,23 @@ packages: '@storybook/client-logger': 7.0.20 '@storybook/global': 5.0.0 telejson: 7.1.0 + dev: true + + /@storybook/channel-websocket@7.0.5: + resolution: {integrity: sha512-QgvxAZjEdRzPZveUibErJbaqqe97DLscPeK5YHA1/xDCPqMKo0HaQKTyT0YSsSkeE3oKXbdz9IXFXEaPmIpjzw==} + dependencies: + '@storybook/channels': 7.0.5 + '@storybook/client-logger': 7.0.5 + '@storybook/global': 5.0.0 + telejson: 7.1.0 + dev: false /@storybook/channels@7.0.20: resolution: {integrity: sha512-AL5GGSQ8WTDUoh3gitKEzo3fu7Vq5okXq2pAknAZlQA2Oio+HHO5nMeXvOfGdvo/tzbpNE3n5utmCJz006xrCA==} /@storybook/channels@7.0.24: resolution: {integrity: sha512-NZVLwMhtzy6cZrNRjshFvMAD9mQTmJDNwhohodSkM/YFCDVFhmxQk9tgizVGh9MwY3CYGJ1SI96RUejGosb49Q==} + dev: true /@storybook/channels@7.0.5: resolution: {integrity: sha512-WiSPXgOK63jAlDDmbTs1sVXoYe3r/4VjpfwhEcxSPU544YQVARF1ePtiGjlp8HVFhZh1Q7afbVGJ9w96++u98A==} @@ -8704,7 +8750,7 @@ packages: commander: 6.2.1 cross-spawn: 7.0.3 detect-indent: 6.1.0 - envinfo: 7.10.0 + envinfo: 7.8.1 execa: 5.1.1 express: 4.18.2 find-up: 5.0.0 @@ -8738,12 +8784,6 @@ packages: dependencies: '@storybook/global': 5.0.0 - /@storybook/client-logger@7.0.24: - resolution: {integrity: sha512-4zRTb+QQ1hWaRqad/UufZNRfi2d/cf5a40My72Ct97VwjhJFE6aQ3K+hl1Xt6hh8dncDL2JK3cgziw6ElqjT0w==} - dependencies: - '@storybook/global': 5.0.0 - dev: false - /@storybook/client-logger@7.0.5: resolution: {integrity: sha512-p8Vtb5G/l3gePNDbNjqgGsikthRqDfsPAqFEsAvBWJVZ3vq/ZSU4IsCWSLO/kdkyJyhTXMqQZnOpQ0pDXlOPcQ==} dependencies: @@ -8769,17 +8809,17 @@ packages: - supports-color dev: true - /@storybook/components@7.0.24(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Pu7zGurCyWyiuFl2Pb5gybHA0f4blmHuVqccbMqnUw4Ew80BRu8AqfhNqN2hNdxFCx0mmy0baRGVftx76rNZ0w==} + /@storybook/components@7.0.20(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-eoEtby/yVkvUKpXfktibxPOhR5UBsWnKRWQUNSxN0vYTG4iBBh3HdjgxFJYfSXV13J+6OfvpBPLlPC+enXrbrQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/client-logger': 7.0.24 + '@storybook/client-logger': 7.0.20 '@storybook/csf': 0.1.1 '@storybook/global': 5.0.0 - '@storybook/theming': 7.0.24(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.24 + '@storybook/theming': 7.0.20(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.20 memoizerific: 1.11.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -8810,6 +8850,7 @@ packages: dependencies: '@storybook/client-logger': 7.0.20 '@storybook/preview-api': 7.0.20 + dev: true /@storybook/core-client@7.0.5: resolution: {integrity: sha512-vN3jK0H4IRjdn/VP7E5dtY0MjytTFSosreSzschmSDTs/K9w52Zm+PkmDzQaBtrDo/VNjJCHnxDLDJZ1ewkoEw==} @@ -8823,16 +8864,16 @@ packages: dependencies: '@storybook/node-logger': 7.0.20 '@storybook/types': 7.0.20 - '@types/node': 16.18.37 + '@types/node': 16.18.35 '@types/pretty-hrtime': 1.0.1 chalk: 4.1.2 esbuild: 0.17.19 esbuild-register: 3.4.2(esbuild@0.17.19) - file-system-cache: 2.4.1 + file-system-cache: 2.3.0 find-up: 5.0.0 fs-extra: 11.1.1 glob: 8.1.0 - glob-promise: 6.0.3(glob@8.1.0) + glob-promise: 6.0.2(glob@8.1.0) handlebars: 4.7.7 lazy-universal-dotenv: 4.0.0 picomatch: 2.3.1 @@ -8842,22 +8883,23 @@ packages: ts-dedent: 2.2.0 transitivePeerDependencies: - supports-color + dev: true /@storybook/core-common@7.0.5: resolution: {integrity: sha512-MIvWwu2ntKK3A0FDWRhKcegIAKyJTyzTf5K4PiVgCT2X9Mj0r0GZ10L/OlyTrlnGHqgxNc4oS2rcN3uWjlwXaA==} dependencies: '@storybook/node-logger': 7.0.5 '@storybook/types': 7.0.5 - '@types/node': 16.18.37 + '@types/node': 16.18.35 '@types/pretty-hrtime': 1.0.1 chalk: 4.1.2 esbuild: 0.17.19 esbuild-register: 3.4.2(esbuild@0.17.19) - file-system-cache: 2.4.1 + file-system-cache: 2.3.0 find-up: 5.0.0 fs-extra: 11.1.1 glob: 8.1.0 - glob-promise: 6.0.3(glob@8.1.0) + glob-promise: 6.0.2(glob@8.1.0) handlebars: 4.7.7 lazy-universal-dotenv: 4.0.0 picomatch: 2.3.1 @@ -8871,10 +8913,6 @@ packages: /@storybook/core-events@7.0.20: resolution: {integrity: sha512-gUBQsbcDmRufmg8LdH7D57c/9BQ+cPi2vBcXdudmxeJFafGwDmLRu1mlv9rxlW4kicn/LZWJjKXtq4XXzF4OGg==} - /@storybook/core-events@7.0.24: - resolution: {integrity: sha512-xkf/rihCkhqMeh5EA8lVp90/mzbb2gcg6I3oeFWw2hognVcTnPXg6llhWdU4Spqd0cals7GEFmQugIILCmH8GA==} - dev: false - /@storybook/core-events@7.0.5: resolution: {integrity: sha512-bYQFZlJR3n5gFk5GVIemuL3m6aYPF6DVnzj6n9UcMZDlHcOZ2B2WbTmAUrGy0bmtj/Fd6ZJKDpBhh3cRRsYkbA==} @@ -8896,7 +8934,7 @@ packages: '@storybook/telemetry': 7.0.5 '@storybook/types': 7.0.5 '@types/detect-port': 1.3.3 - '@types/node': 16.18.37 + '@types/node': 16.18.35 '@types/node-fetch': 2.6.4 '@types/pretty-hrtime': 1.0.1 '@types/semver': 7.5.0 @@ -8911,7 +8949,7 @@ packages: globby: 11.1.0 ip: 2.0.0 lodash: 4.17.21 - node-fetch: 2.6.12 + node-fetch: 2.6.11 open: 8.4.2 pretty-hrtime: 1.0.3 prompts: 2.4.2 @@ -8937,6 +8975,7 @@ packages: unplugin: 0.10.2 transitivePeerDependencies: - supports-color + dev: true /@storybook/csf-plugin@7.0.5: resolution: {integrity: sha512-TTM6l1i73ZGUSCJpAXitsd/KHWQbiuPsFSHKaikowK+pJ2hz4kfNG5JrajXKR5OltBAAbUudK25oJWsvo8FGpQ==} @@ -8961,6 +9000,7 @@ packages: ts-dedent: 2.2.0 transitivePeerDependencies: - supports-color + dev: true /@storybook/csf-tools@7.0.5: resolution: {integrity: sha512-W83OAlYUyzbx3SuDGgsPunw8BeT5gkYJGqenC6wJH0B1Nc+MjYxjhffaMtnT2X8RgMKKgIIf7sB3QN22y+kN/Q==} @@ -8998,6 +9038,7 @@ packages: lodash: 4.17.21 transitivePeerDependencies: - supports-color + dev: true /@storybook/docs-tools@7.0.5: resolution: {integrity: sha512-8e/9EIA9+1AhekJ8g81FgnjhJKWq8fNZK3AWYoDiPCjBFY3bLzisTLMAnxQILUG9DRbbX4aH2FZ3sMqvO9f3EQ==} @@ -9016,14 +9057,14 @@ packages: /@storybook/global@5.0.0: resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} - /@storybook/instrumenter@7.0.24: - resolution: {integrity: sha512-XQ4Whq0rqW9eFMTtRpLxcl6bCf+KO3UZYcm+H63EDn9TstDyopmqv1fDg2tmJOpqLo143F8qLVC89rI7M/lO6w==} + /@storybook/instrumenter@7.0.20: + resolution: {integrity: sha512-TQW/4LJOV2Rok8HH0/AiD9TRDdGaCcCDI34r394frNL61tprrSkT7+ASul68U3c2yuddL9mfrbacr7AzVuf2rA==} dependencies: - '@storybook/channels': 7.0.24 - '@storybook/client-logger': 7.0.24 - '@storybook/core-events': 7.0.24 + '@storybook/channels': 7.0.20 + '@storybook/client-logger': 7.0.20 + '@storybook/core-events': 7.0.20 '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.24 + '@storybook/preview-api': 7.0.20 dev: false /@storybook/instrumenter@7.0.5: @@ -9036,20 +9077,20 @@ packages: '@storybook/preview-api': 7.0.5 dev: false - /@storybook/manager-api@7.0.24(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-cBpgDWq8reFgyrv4fBZlZJQyWYb9cDW0LDe476rWn/29uXNvYMNsHRwveLNgSA8Oy1NdyQCgf4ZgcYvY3wpvMA==} + /@storybook/manager-api@7.0.20(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-/f4L63SWcj4OCck8hdKItnlq/QDZAF6fn4QDLdqXNhPsoi+G6YUMVBX23bW0ygyTM0nrOoAPLVP934H33Xb9Bg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/channels': 7.0.24 - '@storybook/client-logger': 7.0.24 - '@storybook/core-events': 7.0.24 + '@storybook/channels': 7.0.20 + '@storybook/client-logger': 7.0.20 + '@storybook/core-events': 7.0.20 '@storybook/csf': 0.1.1 '@storybook/global': 5.0.0 - '@storybook/router': 7.0.24(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 7.0.24(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.24 + '@storybook/router': 7.0.20(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 7.0.20(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.20 dequal: 2.0.3 lodash: 4.17.21 memoizerific: 1.11.3 @@ -9101,15 +9142,6 @@ packages: npmlog: 5.0.1 pretty-hrtime: 1.0.3 - /@storybook/node-logger@7.0.24: - resolution: {integrity: sha512-gjcYnreYBBtZVF6p/cHMas4FEafPddjsLMrAfB+0lLGoRdUwWVto46BZTHQ9seY5gPW0JQydAdDGHko8/kEOXA==} - dependencies: - '@types/npmlog': 4.1.4 - chalk: 4.1.2 - npmlog: 5.0.1 - pretty-hrtime: 1.0.3 - dev: false - /@storybook/node-logger@7.0.5: resolution: {integrity: sha512-REBIMItpBVn9tpo2JXP3eyHg9lsYSt1JqWFaEncdKEiXWArv5c8pN6/od7MB3sU3NdHwEDKwLel2fZaDbg3jBQ==} dependencies: @@ -9141,26 +9173,6 @@ packages: ts-dedent: 2.2.0 util-deprecate: 1.0.2 - /@storybook/preview-api@7.0.24: - resolution: {integrity: sha512-psycU07tuB5nyJvfAJiDN/9e8cjOdJ+5lrCSYC3vPzH86LxADDIN0/8xFb1CaQWcXZsADEFJGpHKWbRhjym5ew==} - dependencies: - '@storybook/channel-postmessage': 7.0.24 - '@storybook/channels': 7.0.24 - '@storybook/client-logger': 7.0.24 - '@storybook/core-events': 7.0.24 - '@storybook/csf': 0.1.1 - '@storybook/global': 5.0.0 - '@storybook/types': 7.0.24 - '@types/qs': 6.9.7 - dequal: 2.0.3 - lodash: 4.17.21 - memoizerific: 1.11.3 - qs: 6.11.2 - synchronous-promise: 2.0.17 - ts-dedent: 2.2.0 - util-deprecate: 1.0.2 - dev: false - /@storybook/preview-api@7.0.5: resolution: {integrity: sha512-mZruATt5JXfLuXJfOo30WCXILXjK+hs0HwtUDGRVW/J4Ql8CdNPB+WF56ZgeWUnMAYRf392bN3uNwmZx4v4Fog==} dependencies: @@ -9182,6 +9194,11 @@ packages: /@storybook/preview@7.0.20: resolution: {integrity: sha512-ayC7Aud0WM91ki+UM/CInd3GbGPmkUaeT6fqs9zvH8H4QQGznr9E8sI9IUQN0dbpGWayZn0m7Ma89EHwpWOwiw==} + dev: true + + /@storybook/preview@7.0.5: + resolution: {integrity: sha512-N1IDKzmqnF+XAdACGnaWw22dmSUQHuHKyyQ/vV9upMf0hA+4gk9pc5RFEHOQO/sTbxblgfKm9Q1fIYkxgPVFxg==} + dev: false /@storybook/react-dom-shim@7.0.20(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-/TpK3WZFQ/wV3Z1sCYf5PN+u2XdncozE+wHdoXO0FYr3BY3w0BOeMLg6DauX9Nlbs8nh0RiIvck/sm/eBZH+qA==} @@ -9191,6 +9208,7 @@ packages: dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + dev: true /@storybook/react-dom-shim@7.0.5(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-iSdP73Af/d8RdNfa4rDHI3JuAakDqPl8Z1LT0cFcfzg29kihdmXIVaLvMcMqTrnqELU6VmzSiE86U+T1XOX95w==} @@ -9220,12 +9238,39 @@ packages: react: 18.2.0 react-docgen: 6.0.0-alpha.3 react-dom: 18.2.0(react@18.2.0) + vite: 4.3.9(@types/node@18.15.1) + transitivePeerDependencies: + - '@preact/preset-vite' + - supports-color + - typescript + - vite-plugin-glimmerx + dev: true + + /@storybook/react-vite@7.0.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.9): + resolution: {integrity: sha512-jBwRrfC1ue/ZPMrey+VBPsjt89hBx21ZVMtIpLOGws6B2y6vYKskNqCh5iiYZrw9VRKYh6UL5qXiMeNM52o48A==} + engines: {node: '>=16'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + vite: ^3.0.0 || ^4.0.0 + dependencies: + '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.0.4)(vite@4.3.9) + '@rollup/pluginutils': 4.2.1 + '@storybook/builder-vite': 7.0.5(typescript@5.0.4)(vite@4.3.9) + '@storybook/react': 7.0.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4) + '@vitejs/plugin-react': 3.1.0(vite@4.3.9) + ast-types: 0.14.2 + magic-string: 0.27.0 + react: 18.2.0 + react-docgen: 6.0.0-alpha.3 + react-dom: 18.2.0(react@18.2.0) vite: 4.3.9(less@4.1.3) transitivePeerDependencies: - '@preact/preset-vite' - supports-color - typescript - vite-plugin-glimmerx + dev: false /@storybook/react@7.0.20(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4): resolution: {integrity: sha512-5F7ENxlAgUMzYu8W4OThn01P5zMPg/4Th/ekeSGJvAzR8OwwNNzHG9tKmu29cz8unmQqCSxkwaC63N1nm4YaBQ==} @@ -9247,11 +9292,11 @@ packages: '@storybook/types': 7.0.20 '@types/escodegen': 0.0.6 '@types/estree': 0.0.51 - '@types/node': 16.18.37 + '@types/node': 16.18.35 acorn: 7.4.1 acorn-jsx: 5.3.2(acorn@7.4.1) acorn-walk: 7.2.0 - escodegen: 2.1.0 + escodegen: 2.0.0 html-tags: 3.3.1 lodash: 4.17.21 prop-types: 15.8.1 @@ -9264,6 +9309,7 @@ packages: util-deprecate: 1.0.2 transitivePeerDependencies: - supports-color + dev: true /@storybook/react@7.0.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4): resolution: {integrity: sha512-VXLi/oZnYLXe61Bvfan1YY6cANbFgDb5MmCpu8COaYOGjT53o4gTh3zQoDubaN8wzTQfE0TyP9E+m4//KvZxow==} @@ -9285,11 +9331,11 @@ packages: '@storybook/types': 7.0.5 '@types/escodegen': 0.0.6 '@types/estree': 0.0.51 - '@types/node': 16.18.37 + '@types/node': 16.18.35 acorn: 7.4.1 acorn-jsx: 5.3.2(acorn@7.4.1) acorn-walk: 7.2.0 - escodegen: 2.1.0 + escodegen: 2.0.0 html-tags: 3.3.1 lodash: 4.17.21 prop-types: 15.8.1 @@ -9304,13 +9350,13 @@ packages: - supports-color dev: false - /@storybook/router@7.0.24(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-SRCV+srCZUbko/V0phVN8jY8ilrxQWWAY/gegwNlIYaNqLJSyYqIj739VDmX+deXl6rOEpFLZreClVXWiDU9+w==} + /@storybook/router@7.0.20(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Nzyy62hlP4QR3Dub2/PBqi2E7NjKUd1HBEMXFg2ggWF7ak2h9M1iPI0gGk6sUuC5NBVzYP20eF9wrz3Fe9eq8Q==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/client-logger': 7.0.24 + '@storybook/client-logger': 7.0.20 memoizerific: 1.11.3 qs: 6.11.2 react: 18.2.0 @@ -9350,21 +9396,21 @@ packages: /@storybook/testing-library@0.1.0: resolution: {integrity: sha512-g947f4LJZw3IluBhysMKLJXByAFiSxnGuooENqU+ZPt/GTrz1I9GDBlhmoTJahuFkVbwHvziAl/8riY2Re921g==} dependencies: - '@storybook/client-logger': 7.0.24 - '@storybook/instrumenter': 7.0.24 - '@testing-library/dom': 8.20.1 - '@testing-library/user-event': 13.5.0(@testing-library/dom@8.20.1) + '@storybook/client-logger': 7.0.20 + '@storybook/instrumenter': 7.0.20 + '@testing-library/dom': 8.20.0 + '@testing-library/user-event': 13.5.0(@testing-library/dom@8.20.0) ts-dedent: 2.2.0 dev: false - /@storybook/theming@7.0.24(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-CMeCCfqffJ/D5rBl1HpAM/e5Vw0h7ucT+CLzP0ALtLrguz9ZzOiIZYgMj17KpfvWqje7HT+DwEtNkSrnJ01FNQ==} + /@storybook/theming@7.0.20(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-qmo/RKygt7W+NoHCfszChhSOFKe7eNeGzax4YR7yeX3brTzUQqGnb0onGv7MPtoCPhMFpbktK80u4biZtC7XhQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) - '@storybook/client-logger': 7.0.24 + '@storybook/client-logger': 7.0.20 '@storybook/global': 5.0.0 memoizerific: 1.11.3 react: 18.2.0 @@ -9391,7 +9437,7 @@ packages: '@storybook/channels': 7.0.20 '@types/babel__core': 7.20.1 '@types/express': 4.17.17 - file-system-cache: 2.4.1 + file-system-cache: 2.3.0 /@storybook/types@7.0.24: resolution: {integrity: sha512-SZh/XBHP1TT5bmEk0W52nT0v6fUnYwmZVls3da5noutdgOAiwL7TANtl41XrNjG+UDr8x0OE3PVVJi+LhwUaNA==} @@ -9400,6 +9446,7 @@ packages: '@types/babel__core': 7.20.1 '@types/express': 4.17.17 file-system-cache: 2.3.0 + dev: true /@storybook/types@7.0.5: resolution: {integrity: sha512-By+tF3B30QiCnzEJ+Z73M2usSCqBWEmX4OGT1KbiEzWekkrsfCfpZwfzeMw1WwdQGlB1gLKTzB8wZ1zZB8oPtQ==} @@ -9407,7 +9454,7 @@ packages: '@storybook/channels': 7.0.5 '@types/babel__core': 7.20.1 '@types/express': 4.17.17 - file-system-cache: 2.4.1 + file-system-cache: 2.3.0 /@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.22.1): resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==} @@ -9684,7 +9731,7 @@ packages: /@swc/helpers@0.5.1: resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /@szmarczak/http-timer@1.1.2: @@ -9757,7 +9804,7 @@ packages: '@tanstack/react-query': 4.29.1(react-dom@18.2.0)(react-native@0.71.3)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - superjson: 1.12.4 + superjson: 1.12.3 use-sync-external-store: 1.2.0(react@18.2.0) dev: false @@ -9911,8 +9958,8 @@ packages: '@tauri-apps/cli-win32-x64-msvc': 1.3.1 dev: true - /@testing-library/dom@8.20.1: - resolution: {integrity: sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==} + /@testing-library/dom@8.20.0: + resolution: {integrity: sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==} engines: {node: '>=12'} dependencies: '@babel/code-frame': 7.22.5 @@ -9925,14 +9972,14 @@ packages: pretty-format: 27.5.1 dev: false - /@testing-library/user-event@13.5.0(@testing-library/dom@8.20.1): + /@testing-library/user-event@13.5.0(@testing-library/dom@8.20.0): resolution: {integrity: sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==} engines: {node: '>=10', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' dependencies: '@babel/runtime': 7.22.5 - '@testing-library/dom': 8.20.1 + '@testing-library/dom': 8.20.0 dev: false /@trivago/prettier-plugin-sort-imports@4.1.1(prettier@2.8.8): @@ -10075,11 +10122,11 @@ packages: /@types/eslint-scope@3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: - '@types/eslint': 8.40.2 + '@types/eslint': 8.40.1 '@types/estree': 1.0.1 - /@types/eslint@8.40.2: - resolution: {integrity: sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ==} + /@types/eslint@8.40.1: + resolution: {integrity: sha512-vRb792M4mF1FBT+eoLecmkpLXwxsBHvWWRGJjzbYANBM6DtiJc6yETyv4rqDA6QNjF1pkj1U7LMA6dGb3VYlHw==} dependencies: '@types/estree': 1.0.1 '@types/json-schema': 7.0.12 @@ -10110,7 +10157,7 @@ packages: '@types/body-parser': 1.19.2 '@types/express-serve-static-core': 4.17.35 '@types/qs': 6.9.7 - '@types/serve-static': 1.15.2 + '@types/serve-static': 1.15.1 /@types/find-cache-dir@3.2.1: resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==} @@ -10148,9 +10195,6 @@ packages: resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} dev: true - /@types/http-errors@2.0.1: - resolution: {integrity: sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==} - /@types/istanbul-lib-coverage@2.0.4: resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} @@ -10237,8 +10281,8 @@ packages: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true - /@types/node@16.18.37: - resolution: {integrity: sha512-ql+4dw4PlPFBP495k8JzUX/oMNRI2Ei4PrMHgj8oT4VhGlYUzF4EYr0qk2fW+XBVGIrq8Zzk13m4cvyXZuv4pA==} + /@types/node@16.18.35: + resolution: {integrity: sha512-yqU2Rf94HFZqgHf6Tuyc/IqVD0l3U91KjvypSr1GtJKyrnl6L/kfnxVqN4QOwcF5Zx9tO/HKK+fozGr5AtqA+g==} /@types/node@18.15.1: resolution: {integrity: sha512-U2TWca8AeHSmbpi314QBESRk7oPjSZjDsR+c+H4ECC1l+kFgpZf8Ydhv3SJpPy51VyZHHqxlb6mTTqYNNRVAIw==} @@ -10370,10 +10414,9 @@ packages: '@types/mime': 1.3.2 '@types/node': 18.15.1 - /@types/serve-static@1.15.2: - resolution: {integrity: sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==} + /@types/serve-static@1.15.1: + resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} dependencies: - '@types/http-errors': 2.0.1 '@types/mime': 3.0.1 '@types/node': 18.15.1 @@ -10395,6 +10438,10 @@ packages: /@types/unist@2.0.6: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} + /@types/uuid@9.0.2: + resolution: {integrity: sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==} + dev: false + /@types/webxr@0.5.2: resolution: {integrity: sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==} @@ -10603,7 +10650,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.22.1) magic-string: 0.26.7 react-refresh: 0.14.0 - vite: 4.3.9(@types/node@18.15.1) + vite: 4.3.9(sass@1.55.0) transitivePeerDependencies: - supports-color @@ -10731,7 +10778,7 @@ packages: esbuild: '>=0.10.0' dependencies: esbuild: 0.17.19 - tslib: 2.6.0 + tslib: 2.5.3 dev: true /@zxcvbn-ts/core@2.1.0: @@ -10762,12 +10809,12 @@ packages: mime-types: 2.1.35 negotiator: 0.6.3 - /acorn-import-assertions@1.9.0(acorn@8.9.0): + /acorn-import-assertions@1.9.0(acorn@8.8.2): resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} peerDependencies: acorn: ^8 dependencies: - acorn: 8.9.0 + acorn: 8.8.2 /acorn-jsx@5.3.2(acorn@7.4.1): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -10776,12 +10823,12 @@ packages: dependencies: acorn: 7.4.1 - /acorn-jsx@5.3.2(acorn@8.9.0): + /acorn-jsx@5.3.2(acorn@8.8.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.9.0 + acorn: 8.8.2 /acorn-walk@7.2.0: resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} @@ -10792,8 +10839,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - /acorn@8.9.0: - resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} + /acorn@8.8.2: + resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} hasBin: true @@ -10963,20 +11010,13 @@ packages: resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} engines: {node: '>=10'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /aria-query@5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} dependencies: deep-equal: 2.2.1 - dev: false - - /aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - dependencies: - dequal: 2.0.3 - dev: true /arr-diff@4.0.0: resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} @@ -11082,20 +11122,20 @@ packages: resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==} engines: {node: '>=4'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 /ast-types@0.15.2: resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} engines: {node: '>=4'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: true /ast-types@0.16.1: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 /ast-types@0.7.8: resolution: {integrity: sha512-RIOpVnVlltB6PcBJ5BMLx+H+6JJ/zjDGU0t7f0L6c2M1dqcK92VQopLBlPQ9R80AVXelfqYgjcPLtHtDbNFg0Q==} @@ -11137,8 +11177,8 @@ packages: peerDependencies: postcss: ^8.1.0 dependencies: - browserslist: 4.21.9 - caniuse-lite: 1.0.30001509 + browserslist: 4.21.7 + caniuse-lite: 1.0.30001498 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -11154,10 +11194,10 @@ packages: engines: {node: '>=4'} dev: true - /axobject-query@3.2.1: - resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} + /axobject-query@3.1.1: + resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} dependencies: - dequal: 2.0.3 + deep-equal: 2.2.1 dev: true /babel-core@7.0.0-bridge.0(@babel/core@7.22.1): @@ -11167,7 +11207,7 @@ packages: dependencies: '@babel/core': 7.22.1 - /babel-loader@8.2.5(@babel/core@7.22.1)(webpack@5.88.1): + /babel-loader@8.2.5(@babel/core@7.22.1)(webpack@5.86.0): resolution: {integrity: sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==} engines: {node: '>= 8.9'} peerDependencies: @@ -11179,7 +11219,7 @@ packages: loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.88.1(esbuild@0.17.19) + webpack: 5.86.0(esbuild@0.17.19) dev: true /babel-plugin-const-enum@1.2.0(@babel/core@7.22.1): @@ -11262,7 +11302,7 @@ packages: dependencies: '@babel/core': 7.21.8 '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.21.8) - core-js-compat: 3.31.0 + core-js-compat: 3.30.2 transitivePeerDependencies: - supports-color dev: true @@ -11274,7 +11314,7 @@ packages: dependencies: '@babel/core': 7.22.1 '@babel/helper-define-polyfill-provider': 0.4.0(@babel/core@7.22.1) - core-js-compat: 3.31.0 + core-js-compat: 3.30.2 transitivePeerDependencies: - supports-color @@ -11599,15 +11639,15 @@ packages: pako: 0.2.9 dev: true - /browserslist@4.21.9: - resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==} + /browserslist@4.21.7: + resolution: {integrity: sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001509 - electron-to-chromium: 1.4.446 + caniuse-lite: 1.0.30001498 + electron-to-chromium: 1.4.427 node-releases: 2.0.12 - update-browserslist-db: 1.0.11(browserslist@4.21.9) + update-browserslist-db: 1.0.11(browserslist@4.21.7) /bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -11769,7 +11809,7 @@ packages: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} dependencies: pascal-case: 3.1.2 - tslib: 2.6.0 + tslib: 2.5.3 /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} @@ -11793,16 +11833,16 @@ packages: engines: {node: '>=14.16'} dev: true - /camera-controls@2.7.0(three@0.154.0): - resolution: {integrity: sha512-HONMoMYHieOCQOoweS639bdWHP/P/fvVGR08imnECGVUp04mqGfsX/zp1ZufLeiAA5hA6i1JhP6SrnOwh01C0w==} + /camera-controls@2.6.0(three@0.154.0): + resolution: {integrity: sha512-65vkZ+FQfRLtq5LHrNuDFeOALN8+gfoRFuIOwYwgwzVY7bjBxP+j3joj6RTgc5Ot+dTJupFWwfcq7ds4Iq4DGg==} peerDependencies: three: '>=0.126.1' dependencies: three: 0.154.0 dev: false - /caniuse-lite@1.0.30001509: - resolution: {integrity: sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA==} + /caniuse-lite@1.0.30001498: + resolution: {integrity: sha512-LFInN2zAwx3ANrGCDZ5AKKJroHqNKyjXitdV5zRIVIaQlXKj3GmxUKagoKsjqUfckpAObPCEWnk5EeMlyMWcgw==} /capture-stack-trace@1.0.2: resolution: {integrity: sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==} @@ -11828,8 +11868,8 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 - /chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + /chalk@5.2.0: + resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: true @@ -12281,17 +12321,17 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} - /contentlayer@0.3.2(esbuild@0.18.10): + /contentlayer@0.3.2(esbuild@0.18.0): resolution: {integrity: sha512-fQN3l/KvUW+nIvXiaShpOCvXX4alNbvfo56vnVxHVm6vKP10bb/IRhjMXPXZzr+5hmCaeep9wMpCAvOKB6NJHA==} engines: {node: '>=14.18'} hasBin: true requiresBuild: true dependencies: - '@contentlayer/cli': 0.3.2(esbuild@0.18.10) - '@contentlayer/client': 0.3.2(esbuild@0.18.10) - '@contentlayer/core': 0.3.2(esbuild@0.18.10) - '@contentlayer/source-files': 0.3.2(esbuild@0.18.10) - '@contentlayer/source-remote-files': 0.3.2(esbuild@0.18.10) + '@contentlayer/cli': 0.3.2(esbuild@0.18.0) + '@contentlayer/client': 0.3.2(esbuild@0.18.0) + '@contentlayer/core': 0.3.2(esbuild@0.18.0) + '@contentlayer/source-files': 0.3.2(esbuild@0.18.0) + '@contentlayer/source-remote-files': 0.3.2(esbuild@0.18.0) '@contentlayer/utils': 0.3.2 transitivePeerDependencies: - '@effect-ts/otel-node' @@ -12330,10 +12370,10 @@ packages: resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} engines: {node: '>=0.10.0'} - /core-js-compat@3.31.0: - resolution: {integrity: sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==} + /core-js-compat@3.30.2: + resolution: {integrity: sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==} dependencies: - browserslist: 4.21.9 + browserslist: 4.21.7 /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -12387,7 +12427,7 @@ packages: /cross-fetch@3.1.6: resolution: {integrity: sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==} dependencies: - node-fetch: 2.6.12 + node-fetch: 2.6.11 transitivePeerDependencies: - encoding dev: false @@ -12478,7 +12518,7 @@ packages: engines: {node: '>=14'} dependencies: '@cspell/cspell-service-bus': 6.31.1 - node-fetch: 2.6.12 + node-fetch: 2.6.11 transitivePeerDependencies: - encoding dev: true @@ -12546,7 +12586,7 @@ packages: - encoding dev: true - /css-loader@6.8.1(webpack@5.88.1): + /css-loader@6.8.1(webpack@5.86.0): resolution: {integrity: sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==} engines: {node: '>= 12.13.0'} peerDependencies: @@ -12560,7 +12600,7 @@ packages: postcss-modules-values: 4.0.0(postcss@8.4.23) postcss-value-parser: 4.2.0 semver: 7.5.0 - webpack: 5.88.1(esbuild@0.17.19) + webpack: 5.86.0(esbuild@0.17.19) dev: false /css-select@4.3.0: @@ -12587,8 +12627,8 @@ packages: '@daybrush/utils': 1.13.0 dev: false - /css-to-mat@1.1.1: - resolution: {integrity: sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==} + /css-to-mat@1.0.3: + resolution: {integrity: sha512-HADRhVqPc8wFqEp6ClK+uuPYg+FMBinNo2ReLyI/KQCncmHPJ60o5zldyJG7NjsTqXWbdfGJO51jnoxfMvWJiA==} dependencies: '@daybrush/utils': 1.13.0 '@scena/matrix': 1.1.1 @@ -12761,7 +12801,6 @@ packages: which-boxed-primitive: 1.0.2 which-collection: 1.0.1 which-typed-array: 1.1.9 - dev: false /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} @@ -12769,7 +12808,6 @@ packages: /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true /deepmerge@3.3.0: resolution: {integrity: sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==} @@ -12896,8 +12934,8 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - /detect-gpu@5.0.30: - resolution: {integrity: sha512-TfoGbAsy3uRz82OklH456eYS5eO+9BoqzKRZNpwFcW73ZHN8EvNmlSSrEU2XhLqIuBE+U9GtErl74kG4qTSEYg==} + /detect-gpu@5.0.29: + resolution: {integrity: sha512-DEqWxHXAKaoIHxF0rtFPDdWWINGoketQ6fk64KbahK3IC0I0LiZR6NbWZo4pwf7UH8khBLY8w4wS+1MHi81LSQ==} dependencies: webgl-constants: 1.1.1 dev: false @@ -13025,7 +13063,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.6.0 + tslib: 2.5.3 /dot-prop@4.2.1: resolution: {integrity: sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==} @@ -13049,8 +13087,8 @@ packages: resolution: {integrity: sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==} engines: {node: '>=12'} - /dotenv@16.3.1: - resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} + /dotenv@16.1.4: + resolution: {integrity: sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==} engines: {node: '>=12'} /draco3d@1.5.6: @@ -13073,7 +13111,7 @@ packages: hasBin: true dependencies: camelcase: 7.0.1 - chalk: 5.3.0 + chalk: 5.2.0 commander: 9.5.0 esbuild: 0.15.18 esbuild-register: 3.4.2(esbuild@0.15.18) @@ -13171,8 +13209,8 @@ packages: dependencies: jake: 10.8.7 - /electron-to-chromium@1.4.446: - resolution: {integrity: sha512-4Gnw7ztEQ/E0eOt5JWfPn9jjeupfUlKoeW5ETKP9nLdWj+4spFoS3Stj19fqlKIaX28UQs0fNX+uKEyoLCBnkw==} + /electron-to-chromium@1.4.427: + resolution: {integrity: sha512-HK3r9l+Jm8dYAm1ctXEWIC+hV60zfcjS9UA5BDlYvnI5S7PU/yytjpvSrTNrSSRRkuu3tDyZhdkwIczh+0DWaw==} /electron@11.5.0: resolution: {integrity: sha512-WjNDd6lGpxyiNjE3LhnFCAk/D9GIj1rU3GSDealVShhkkkPR3Vh4q8ErXGDl1OAO/faomVa10KoFPUN/pLbNxg==} @@ -13207,8 +13245,8 @@ packages: dependencies: once: 1.4.0 - /enhanced-resolve@5.15.0: - resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} + /enhanced-resolve@5.14.1: + resolution: {integrity: sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==} engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.11 @@ -13231,8 +13269,8 @@ packages: engines: {node: '>=6'} dev: true - /envinfo@7.10.0: - resolution: {integrity: sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==} + /envinfo@7.8.1: + resolution: {integrity: sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==} engines: {node: '>=4'} hasBin: true @@ -13317,13 +13355,12 @@ packages: is-string: 1.0.7 isarray: 2.0.5 stop-iteration-iterator: 1.0.0 - dev: false /es-module-lexer@0.9.3: resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} - /es-module-lexer@1.3.0: - resolution: {integrity: sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==} + /es-module-lexer@1.2.1: + resolution: {integrity: sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==} /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} @@ -13655,34 +13692,34 @@ packages: '@esbuild/win32-ia32': 0.17.19 '@esbuild/win32-x64': 0.17.19 - /esbuild@0.18.10: - resolution: {integrity: sha512-33WKo67auOXzZHBY/9DTJRo7kIvfU12S+D4sp2wIz39N88MDIaCGyCwbW01RR70pK6Iya0I74lHEpyLfFqOHPA==} + /esbuild@0.18.0: + resolution: {integrity: sha512-/2sQaWHNX2jkglLu85EjmEAR2ANpKOa1kp2rAE3wjKcuYjEHFlB+D60tn6W9BRgHiAQEKYtl4hEygKWothfDEA==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.18.10 - '@esbuild/android-arm64': 0.18.10 - '@esbuild/android-x64': 0.18.10 - '@esbuild/darwin-arm64': 0.18.10 - '@esbuild/darwin-x64': 0.18.10 - '@esbuild/freebsd-arm64': 0.18.10 - '@esbuild/freebsd-x64': 0.18.10 - '@esbuild/linux-arm': 0.18.10 - '@esbuild/linux-arm64': 0.18.10 - '@esbuild/linux-ia32': 0.18.10 - '@esbuild/linux-loong64': 0.18.10 - '@esbuild/linux-mips64el': 0.18.10 - '@esbuild/linux-ppc64': 0.18.10 - '@esbuild/linux-riscv64': 0.18.10 - '@esbuild/linux-s390x': 0.18.10 - '@esbuild/linux-x64': 0.18.10 - '@esbuild/netbsd-x64': 0.18.10 - '@esbuild/openbsd-x64': 0.18.10 - '@esbuild/sunos-x64': 0.18.10 - '@esbuild/win32-arm64': 0.18.10 - '@esbuild/win32-ia32': 0.18.10 - '@esbuild/win32-x64': 0.18.10 + '@esbuild/android-arm': 0.18.0 + '@esbuild/android-arm64': 0.18.0 + '@esbuild/android-x64': 0.18.0 + '@esbuild/darwin-arm64': 0.18.0 + '@esbuild/darwin-x64': 0.18.0 + '@esbuild/freebsd-arm64': 0.18.0 + '@esbuild/freebsd-x64': 0.18.0 + '@esbuild/linux-arm': 0.18.0 + '@esbuild/linux-arm64': 0.18.0 + '@esbuild/linux-ia32': 0.18.0 + '@esbuild/linux-loong64': 0.18.0 + '@esbuild/linux-mips64el': 0.18.0 + '@esbuild/linux-ppc64': 0.18.0 + '@esbuild/linux-riscv64': 0.18.0 + '@esbuild/linux-s390x': 0.18.0 + '@esbuild/linux-x64': 0.18.0 + '@esbuild/netbsd-x64': 0.18.0 + '@esbuild/openbsd-x64': 0.18.0 + '@esbuild/sunos-x64': 0.18.0 + '@esbuild/win32-arm64': 0.18.0 + '@esbuild/win32-ia32': 0.18.0 + '@esbuild/win32-x64': 0.18.0 dev: false /escalade@3.1.1: @@ -13721,14 +13758,15 @@ packages: source-map: 0.1.43 dev: false - /escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + /escodegen@2.0.0: + resolution: {integrity: sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==} engines: {node: '>=6.0'} hasBin: true dependencies: esprima: 4.0.1 estraverse: 5.3.0 esutils: 2.0.3 + optionator: 0.8.3 optionalDependencies: source-map: 0.6.1 @@ -13742,7 +13780,7 @@ packages: optional: true dependencies: '@next/eslint-plugin-next': 13.3.0 - '@rushstack/eslint-patch': 1.3.2 + '@rushstack/eslint-patch': 1.3.1 '@typescript-eslint/parser': 5.59.6(eslint@8.41.0)(typescript@5.1.3) eslint: 8.41.0 eslint-import-resolver-node: 0.3.7 @@ -13793,12 +13831,12 @@ packages: eslint-plugin-import: '*' dependencies: debug: 4.3.4 - enhanced-resolve: 5.15.0 + enhanced-resolve: 5.14.1 eslint: 8.41.0 eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.41.0) eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.41.0) - get-tsconfig: 4.6.2 - globby: 13.2.0 + get-tsconfig: 4.6.0 + globby: 13.1.4 is-core-module: 2.12.1 is-glob: 4.0.3 synckit: 0.8.5 @@ -13879,17 +13917,17 @@ packages: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 dependencies: '@babel/runtime': 7.22.5 - aria-query: 5.3.0 + aria-query: 5.1.3 array-includes: 3.1.6 array.prototype.flatmap: 1.3.1 ast-types-flow: 0.0.7 axe-core: 4.7.2 - axobject-query: 3.2.1 + axobject-query: 3.1.1 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 eslint: 8.41.0 has: 1.0.3 - jsx-ast-utils: 3.3.4 + jsx-ast-utils: 3.3.3 language-tags: 1.0.5 minimatch: 3.1.2 object.entries: 1.1.6 @@ -13951,7 +13989,7 @@ packages: doctrine: 2.1.0 eslint: 8.41.0 estraverse: 5.3.0 - jsx-ast-utils: 3.3.4 + jsx-ast-utils: 3.3.3 minimatch: 3.1.2 object.entries: 1.1.6 object.fromentries: 2.0.6 @@ -14057,7 +14095,7 @@ packages: lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 - optionator: 0.9.3 + optionator: 0.9.1 strip-ansi: 6.0.1 strip-json-comments: 3.1.1 text-table: 0.2.0 @@ -14069,8 +14107,8 @@ packages: resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.9.0 - acorn-jsx: 5.3.2(acorn@8.9.0) + acorn: 8.8.2 + acorn-jsx: 5.3.2(acorn@8.8.2) eslint-visitor-keys: 3.4.1 dev: true @@ -14425,7 +14463,7 @@ packages: getenv: 1.0.0 invariant: 2.2.4 md5-file: 3.2.3 - node-fetch: 2.6.12 + node-fetch: 2.6.11 pretty-format: 26.6.2 uuid: 3.4.0 transitivePeerDependencies: @@ -14550,7 +14588,6 @@ packages: /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true /fast-xml-parser@4.1.2: resolution: {integrity: sha512-CDYeykkle1LiA/uqQyNwYpFbyF6Axec6YapmpUP+/RHWIoR1zKjocdvNaTsxCxZzQ6v9MLXaSYm9Qq0thv0DHg==} @@ -14559,8 +14596,8 @@ packages: strnum: 1.0.5 dev: false - /fast-xml-parser@4.2.5: - resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} + /fast-xml-parser@4.2.4: + resolution: {integrity: sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==} hasBin: true dependencies: strnum: 1.0.5 @@ -14645,12 +14682,6 @@ packages: fs-extra: 11.1.1 ramda: 0.29.0 - /file-system-cache@2.4.1: - resolution: {integrity: sha512-mzEiUdjzqhxwppIJVSBq8C9evWM1j0v/lCg7gFMDiQDQPlQSm8kRfXSPFScT1p/Fxy0N2LSIps6g28e8itumlg==} - dependencies: - fs-extra: 11.1.1 - ramda: 0.29.0 - /filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} dependencies: @@ -14779,8 +14810,8 @@ packages: resolution: {integrity: sha512-2hJ5ACYeJCzNtiVULov6pljKOLygy0zddoqSI1fFetM+XRPpRshFdGEijtqlamA1XwyZ+7rhryI6FQFzvtLWUQ==} engines: {node: '>=0.4.0'} - /flow-parser@0.210.2: - resolution: {integrity: sha512-kQiVau1WnXMCxJziuOF9wk4EoE/sPTU5H7dWOJN+7lsh+tmUh6LXz1dcLE44D+ouVIg8RRnfRZQymZqzKfh5fA==} + /flow-parser@0.208.0: + resolution: {integrity: sha512-nuoC/kw8BH0gw7ykHNlKJVvtQWh/j5+CE3P/54RBMy63IoGlj9ScTQOC1ntLzwnnfzm9gT5OOukWG0TKrgKyug==} engines: {node: '>=0.4.0'} dev: true @@ -14862,7 +14893,7 @@ packages: dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - tslib: 2.6.0 + tslib: 2.5.3 optionalDependencies: '@emotion/is-prop-valid': 0.8.8 dev: false @@ -14880,7 +14911,7 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) style-value-types: 5.0.0 - tslib: 2.6.0 + tslib: 2.5.3 optionalDependencies: '@emotion/is-prop-valid': 0.8.8 dev: false @@ -14888,7 +14919,7 @@ packages: /framesync@6.0.1: resolution: {integrity: sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 dev: false /framework-utils@1.1.0: @@ -15090,8 +15121,8 @@ packages: get-intrinsic: 1.2.1 dev: true - /get-tsconfig@4.6.2: - resolution: {integrity: sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==} + /get-tsconfig@4.6.0: + resolution: {integrity: sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg==} dependencies: resolve-pkg-maps: 1.0.0 dev: true @@ -15159,8 +15190,8 @@ packages: '@types/glob': 7.2.0 glob: 7.2.3 - /glob-promise@6.0.3(glob@8.1.0): - resolution: {integrity: sha512-m+kxywR5j/2Z2V9zvHKfwwL5Gp7gIFEBX+deTB9w2lJB+wSuw9kcS43VfvTAMk8TXL5JCl/cCjsR+tgNVspGyA==} + /glob-promise@6.0.2(glob@8.1.0): + resolution: {integrity: sha512-Ni2aDyD1ekD6x8/+K4hDriRDbzzfuK4yKpqSymJ4P7IxbtARiOOuU+k40kbHM0sLIlbf1Qh0qdMkAHMZYE6XJQ==} engines: {node: '>=16'} peerDependencies: glob: ^8.0.3 @@ -15242,7 +15273,7 @@ packages: fs.realpath: 1.0.0 minimatch: 8.0.4 minipass: 4.2.8 - path-scurry: 1.10.0 + path-scurry: 1.9.2 dev: true /global-agent@3.0.0: @@ -15307,8 +15338,8 @@ packages: merge2: 1.4.1 slash: 3.0.0 - /globby@13.2.0: - resolution: {integrity: sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==} + /globby@13.1.4: + resolution: {integrity: sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: dir-glob: 3.0.1 @@ -15393,7 +15424,7 @@ packages: graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 dependencies: graphql: 15.8.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /graphql@15.8.0: @@ -15738,7 +15769,7 @@ packages: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.18.2 + terser: 5.17.7 /html-tags@3.3.1: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} @@ -16190,7 +16221,6 @@ packages: /is-map@2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} - dev: false /is-nan@1.3.2: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} @@ -16309,7 +16339,6 @@ packages: /is-set@2.0.2: resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} - dev: false /is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} @@ -16368,7 +16397,6 @@ packages: /is-weakmap@2.0.1: resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} - dev: false /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} @@ -16381,7 +16409,6 @@ packages: dependencies: call-bind: 1.0.2 get-intrinsic: 1.2.1 - dev: false /is-what@3.14.1: resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} @@ -16410,7 +16437,6 @@ packages: /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: false /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -16428,7 +16454,7 @@ packages: /isomorphic-unfetch@3.1.0: resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==} dependencies: - node-fetch: 2.6.12 + node-fetch: 2.6.11 unfetch: 4.2.0 transitivePeerDependencies: - encoding @@ -16687,6 +16713,7 @@ packages: /jsc-safe-url@0.2.4: resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} + dev: true /jscodeshift@0.13.1(@babel/preset-env@7.22.5): resolution: {integrity: sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==} @@ -16735,7 +16762,7 @@ packages: '@babel/register': 7.22.5(@babel/core@7.22.1) babel-core: 7.0.0-bridge.0(@babel/core@7.22.1) chalk: 4.1.2 - flow-parser: 0.210.2 + flow-parser: 0.208.0 graceful-fs: 4.2.11 micromatch: 4.0.5 neo-async: 2.6.2 @@ -16765,7 +16792,7 @@ packages: '@babel/register': 7.22.5(@babel/core@7.22.1) babel-core: 7.0.0-bridge.0(@babel/core@7.22.1) chalk: 4.1.2 - flow-parser: 0.210.2 + flow-parser: 0.208.0 graceful-fs: 4.2.11 micromatch: 4.0.5 neo-async: 2.6.2 @@ -16860,18 +16887,16 @@ packages: optionalDependencies: graceful-fs: 4.2.11 - /jsx-ast-utils@3.3.4: - resolution: {integrity: sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==} + /jsx-ast-utils@3.3.3: + resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} engines: {node: '>=4.0'} dependencies: array-includes: 3.1.6 - array.prototype.flat: 1.3.1 object.assign: 4.1.4 - object.values: 1.1.6 dev: true - /katex@0.16.8: - resolution: {integrity: sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==} + /katex@0.16.7: + resolution: {integrity: sha512-Xk9C6oGKRwJTfqfIbtr0Kes9OSv6IFsuhFGc7tW4urlpMJtuh+7YhzU6YEG9n8gmWKcMAFzkp7nr+r69kV0zrA==} hasBin: true dependencies: commander: 8.3.0 @@ -16956,10 +16981,10 @@ packages: engines: {node: '>=14.0.0'} dependencies: app-root-dir: 1.0.2 - dotenv: 16.3.1 + dotenv: 16.1.4 dotenv-expand: 10.0.0 - /less-loader@11.1.3(less@4.1.3)(webpack@5.88.1): + /less-loader@11.1.3(less@4.1.3)(webpack@5.86.0): resolution: {integrity: sha512-A5b7O8dH9xpxvkosNrP0dFp2i/dISOJa9WwGF3WJflfqIERE2ybxh1BFDj5CovC2+jCE4M354mk90hN6ziXlVw==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -16967,7 +16992,7 @@ packages: webpack: ^5.0.0 dependencies: less: 4.1.3 - webpack: 5.88.1(esbuild@0.17.19) + webpack: 5.86.0(esbuild@0.17.19) dev: false /less@4.1.3: @@ -16977,7 +17002,7 @@ packages: dependencies: copy-anything: 2.0.6 parse-node-version: 1.0.1 - tslib: 2.6.0 + tslib: 2.5.3 optionalDependencies: errno: 0.1.8 graceful-fs: 4.2.11 @@ -16993,6 +17018,13 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + /levn@0.3.0: + resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + type-check: 0.3.2 + /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -17160,7 +17192,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.6.0 + tslib: 2.5.3 /lowercase-keys@1.0.1: resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} @@ -17172,11 +17204,6 @@ packages: engines: {node: '>=8'} dev: true - /lru-cache@10.0.0: - resolution: {integrity: sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==} - engines: {node: 14 || >=16.14} - dev: true - /lru-cache@2.7.3: resolution: {integrity: sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ==} dev: true @@ -17199,6 +17226,11 @@ packages: dependencies: yallist: 4.0.0 + /lru-cache@9.1.2: + resolution: {integrity: sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==} + engines: {node: 14 || >=16.14} + dev: true + /lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} dependencies: @@ -17545,17 +17577,17 @@ packages: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} dev: true - /mdx-bundler@9.2.1(esbuild@0.18.10): + /mdx-bundler@9.2.1(esbuild@0.18.0): resolution: {integrity: sha512-hWEEip1KU9MCNqeH2rqwzAZ1pdqPPbfkx9OTJjADqGPQz4t9BO85fhI7AP9gVYrpmfArf9/xJZUN0yBErg/G/Q==} engines: {node: '>=14', npm: '>=6'} peerDependencies: esbuild: 0.* dependencies: '@babel/runtime': 7.22.5 - '@esbuild-plugins/node-resolve': 0.1.4(esbuild@0.18.10) + '@esbuild-plugins/node-resolve': 0.1.4(esbuild@0.18.0) '@fal-works/esbuild-plugin-global-externals': 2.1.2 - '@mdx-js/esbuild': 2.3.0(esbuild@0.18.10) - esbuild: 0.18.10 + '@mdx-js/esbuild': 2.3.0(esbuild@0.18.0) + esbuild: 0.18.0 gray-matter: 4.0.3 remark-frontmatter: 4.0.1 remark-mdx-frontmatter: 1.1.1 @@ -17630,85 +17662,86 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} - /metro-babel-transformer@0.73.10: - resolution: {integrity: sha512-Yv2myTSnpzt/lTyurLvqYbBkytvUJcLHN8XD3t7W6rGiLTQPzmf1zypHQLphvcAXtCWBOXFtH7KLOSi2/qMg+A==} + /metro-babel-transformer@0.73.7: + resolution: {integrity: sha512-s7UVkwovGTEXYEQrv5hcmSBbFJ9s9lhCRNMScn4Itgj3UMdqRr9lU8DXKEFlJ7osgRxN6n5+eXqcvhE4B1H1VQ==} dependencies: '@babel/core': 7.22.1 hermes-parser: 0.8.0 - metro-source-map: 0.73.10 + metro-source-map: 0.73.7 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - /metro-babel-transformer@0.73.7: - resolution: {integrity: sha512-s7UVkwovGTEXYEQrv5hcmSBbFJ9s9lhCRNMScn4Itgj3UMdqRr9lU8DXKEFlJ7osgRxN6n5+eXqcvhE4B1H1VQ==} + /metro-babel-transformer@0.73.9: + resolution: {integrity: sha512-DlYwg9wwYIZTHtic7dyD4BP0SDftoltZ3clma76nHu43blMWsCnrImHeHsAVne3XsQ+RJaSRxhN5nkG2VyVHwA==} dependencies: '@babel/core': 7.22.1 hermes-parser: 0.8.0 - metro-source-map: 0.73.7 + metro-source-map: 0.73.9 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - /metro-babel-transformer@0.76.7: - resolution: {integrity: sha512-bgr2OFn0J4r0qoZcHrwEvccF7g9k3wdgTOgk6gmGHrtlZ1Jn3oCpklW/DfZ9PzHfjY2mQammKTc19g/EFGyOJw==} + /metro-babel-transformer@0.76.6: + resolution: {integrity: sha512-9Y04wJufg1TVAnexWc/iICN6TetUPXLRV/kXcUdtMwQcz7NMaENe1twtzaxkGD3D6MRXcMh9OhcUD/4UJyoyrw==} engines: {node: '>=16'} dependencies: '@babel/core': 7.22.1 hermes-parser: 0.12.0 + metro-source-map: 0.76.6 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color dev: true - /metro-cache-key@0.73.10: - resolution: {integrity: sha512-JMVDl/EREDiUW//cIcUzRjKSwE2AFxVWk47cFBer+KA4ohXIG2CQPEquT56hOw1Y1s6gKNxxs1OlAOEsubrFjw==} + /metro-cache-key@0.73.9: + resolution: {integrity: sha512-uJg+6Al7UoGIuGfoxqPBy6y1Ewq7Y8/YapGYIDh6sohInwt/kYKnPZgLDYHIPvY2deORnQ/2CYo4tOeBTnhCXQ==} - /metro-cache-key@0.76.7: - resolution: {integrity: sha512-0pecoIzwsD/Whn/Qfa+SDMX2YyasV0ndbcgUFx7w1Ct2sLHClujdhQ4ik6mvQmsaOcnGkIyN0zcceMDjC2+BFQ==} + /metro-cache-key@0.76.6: + resolution: {integrity: sha512-npH9gAlO1cOBd7q0LIu6t+8A7Xq7hplweOt4VJVcLGhEYsa1NmIND0S73/Z7aYbID7bWsY7i8wOFFfgUxh8W0A==} engines: {node: '>=16'} dev: true - /metro-cache@0.73.10: - resolution: {integrity: sha512-wPGlQZpdVlM404m7MxJqJ+hTReDr5epvfPbt2LerUAHY9RN99w61FeeAe25BMZBwgUgDtAsfGlJ51MBHg8MAqw==} + /metro-cache@0.73.9: + resolution: {integrity: sha512-upiRxY8rrQkUWj7ieACD6tna7xXuXdu2ZqrheksT79ePI0aN/t0memf6WcyUtJUMHZetke3j+ppELNvlmp3tOw==} dependencies: - metro-core: 0.73.10 + metro-core: 0.73.9 rimraf: 3.0.2 - /metro-cache@0.76.7: - resolution: {integrity: sha512-nWBMztrs5RuSxZRI7hgFgob5PhYDmxICh9FF8anm9/ito0u0vpPvRxt7sRu8fyeD2AHdXqE7kX32rWY0LiXgeg==} + /metro-cache@0.76.6: + resolution: {integrity: sha512-zrsNEiPJWQYq69IiR06gfiuYMmAxYt21CxLsrZLJdfU/jB1rvqZa43MuUWG129uxN8ZtjWKnUVl6LRZj+85XFw==} engines: {node: '>=16'} dependencies: - metro-core: 0.76.7 + metro-core: 0.76.6 rimraf: 3.0.2 dev: true - /metro-config@0.73.10: - resolution: {integrity: sha512-wIlybd1Z9I8K2KcStTiJxTB7OK529dxFgogNpKCTU/3DxkgAASqSkgXnZP6kVyqjh5EOWAKFe5U6IPic7kXDdQ==} + /metro-config@0.73.9: + resolution: {integrity: sha512-NiWl1nkYtjqecDmw77tbRbXnzIAwdO6DXGZTuKSkH+H/c1NKq1eizO8Fe+NQyFtwR9YLqn8Q0WN1nmkwM1j8CA==} dependencies: cosmiconfig: 5.2.1 jest-validate: 26.6.2 - metro: 0.73.10 - metro-cache: 0.73.10 - metro-core: 0.73.10 - metro-runtime: 0.73.10 + metro: 0.73.9 + metro-cache: 0.73.9 + metro-core: 0.73.9 + metro-runtime: 0.73.9 transitivePeerDependencies: - bufferutil - encoding - supports-color - utf-8-validate - /metro-config@0.76.7: - resolution: {integrity: sha512-CFDyNb9bqxZemiChC/gNdXZ7OQkIwmXzkrEXivcXGbgzlt/b2juCv555GWJHyZSlorwnwJfY3uzAFu4A9iRVfg==} + /metro-config@0.76.6: + resolution: {integrity: sha512-fhlYLbIwrkNAJKBhGg7b4AeZHLKqXDBD8pN7K+BD/wHajhyUap7Vi97io/wMBuX41dIUvQgGCmF/y063IlXMJQ==} engines: {node: '>=16'} dependencies: connect: 3.7.0 cosmiconfig: 5.2.1 jest-validate: 29.5.0 - metro: 0.76.7 - metro-cache: 0.76.7 - metro-core: 0.76.7 - metro-runtime: 0.76.7 + metro: 0.76.6 + metro-cache: 0.76.6 + metro-core: 0.76.6 + metro-runtime: 0.76.6 transitivePeerDependencies: - bufferutil - encoding @@ -17716,22 +17749,22 @@ packages: - utf-8-validate dev: true - /metro-core@0.73.10: - resolution: {integrity: sha512-5uYkajIxKyL6W45iz/ftNnYPe1l92CvF2QJeon1CHsMXkEiOJxEjo41l+iSnO/YodBGrmMCyupSO4wOQGUc0lw==} + /metro-core@0.73.9: + resolution: {integrity: sha512-1NTs0IErlKcFTfYyRT3ljdgrISWpl1nys+gaHkXapzTSpvtX9F1NQNn5cgAuE+XIuTJhbsCdfIJiM2JXbrJQaQ==} dependencies: lodash.throttle: 4.1.1 - metro-resolver: 0.73.10 + metro-resolver: 0.73.9 - /metro-core@0.76.7: - resolution: {integrity: sha512-0b8KfrwPmwCMW+1V7ZQPkTy2tsEKZjYG9Pu1PTsu463Z9fxX7WaR0fcHFshv+J1CnQSUTwIGGjbNvj1teKe+pw==} + /metro-core@0.76.6: + resolution: {integrity: sha512-T6gcggmNXWsd6wBABIVcFE7bXUh8oWlRk0sg521olUXK4RptoL4G5qg4lwJJqkVPBAYGiTBgyZnTjxAZsOGbpw==} engines: {node: '>=16'} dependencies: lodash.throttle: 4.1.1 - metro-resolver: 0.76.7 + metro-resolver: 0.76.6 dev: true - /metro-file-map@0.73.10: - resolution: {integrity: sha512-XOMWAybeaXyD6zmVZPnoCCL2oO3rp4ta76oUlqWP0skBzhFxVtkE/UtDwApEMUY361JeBBago647gnKiARs+1g==} + /metro-file-map@0.73.9: + resolution: {integrity: sha512-R/Wg3HYeQhYY3ehWtfedw8V0ne4lpufG7a21L3GWer8tafnC9pmjoCKEbJz9XZkVj9i1FtxE7UTbrtZNeIILxQ==} dependencies: abort-controller: 3.0.0 anymatch: 3.1.3 @@ -17751,8 +17784,8 @@ packages: transitivePeerDependencies: - supports-color - /metro-file-map@0.76.7: - resolution: {integrity: sha512-s+zEkTcJ4mOJTgEE2ht4jIo1DZfeWreQR3tpT3gDV/Y/0UQ8aJBTv62dE775z0GLsWZApiblAYZsj7ZE8P06nw==} + /metro-file-map@0.76.6: + resolution: {integrity: sha512-XzfQOQ86nf9QgoZ+Ox9zE2hq8ief7F9jzqmzNslKPYs59cyCL9BSwrhj1knY7CSCsk0+SaBn2LAV2eGmNlbfVw==} engines: {node: '>=16'} dependencies: anymatch: 3.1.3 @@ -17773,11 +17806,11 @@ packages: - supports-color dev: true - /metro-hermes-compiler@0.73.10: - resolution: {integrity: sha512-rTRWEzkVrwtQLiYkOXhSdsKkIObnL+Jqo+IXHI7VEK2aSLWRAbtGNqECBs44kbOUypDYTFFE+WLtoqvUWqYkWg==} + /metro-hermes-compiler@0.73.9: + resolution: {integrity: sha512-5B3vXIwQkZMSh3DQQY23XpTCpX9kPLqZbA3rDuAcbGW0tzC3f8dCenkyBb0GcCzyTDncJeot/A7oVCVK6zapwg==} - /metro-inspector-proxy@0.73.10: - resolution: {integrity: sha512-CEEvocYc5xCCZBtGSIggMCiRiXTrnBbh8pmjKQqm9TtJZALeOGyt5pXUaEkKGnhrXETrexsg6yIbsQHhEvVfvQ==} + /metro-inspector-proxy@0.73.9: + resolution: {integrity: sha512-B3WrWZnlYhtTrv0IaX3aUAhi2qVILPAZQzb5paO1e+xrz4YZHk9c7dXv7qe7B/IQ132e3w46y3AL7rFo90qVjA==} hasBin: true dependencies: connect: 3.7.0 @@ -17789,14 +17822,14 @@ packages: - supports-color - utf-8-validate - /metro-inspector-proxy@0.76.7: - resolution: {integrity: sha512-rNZ/6edTl/1qUekAhAbaFjczMphM50/UjtxiKulo6vqvgn/Mjd9hVqDvVYfAMZXqPvlusD88n38UjVYPkruLSg==} + /metro-inspector-proxy@0.76.6: + resolution: {integrity: sha512-8jbbTYUoFne9vmOOW9gWfAk08rIlYe8ZBvIUg3mS1jYBblSSEby3tJcVwB+j6rqPKHBLtakYmPb4BoYqIvO91g==} engines: {node: '>=16'} hasBin: true dependencies: connect: 3.7.0 debug: 2.6.9 - node-fetch: 2.6.12 + node-fetch: 2.6.11 ws: 7.5.9 yargs: 17.7.2 transitivePeerDependencies: @@ -17806,76 +17839,30 @@ packages: - utf-8-validate dev: true - /metro-minify-terser@0.73.10: - resolution: {integrity: sha512-uG7TSKQ/i0p9kM1qXrwbmY3v+6BrMItsOcEXcSP8Z+68bb+t9HeVK0T/hIfUu1v1PEnonhkhfzVsaP8QyTd5lQ==} + /metro-minify-terser@0.73.9: + resolution: {integrity: sha512-MTGPu2qV5qtzPJ2SqH6s58awHDtZ4jd7lmmLR+7TXDwtZDjIBA0YVfI0Zak2Haby2SqoNKrhhUns/b4dPAQAVg==} dependencies: - terser: 5.18.2 + terser: 5.17.7 - /metro-minify-terser@0.76.7: - resolution: {integrity: sha512-FQiZGhIxCzhDwK4LxyPMLlq0Tsmla10X7BfNGlYFK0A5IsaVKNJbETyTzhpIwc+YFRT4GkFFwgo0V2N5vxO5HA==} + /metro-minify-terser@0.76.6: + resolution: {integrity: sha512-CFwq3thxrNjhiYNeoTAB+XGmjLcUz7XdD03j+19zd+3W9FFGWd4GXePacVX+o2GeuKPerO5431F+W98lWV11pA==} engines: {node: '>=16'} dependencies: - terser: 5.18.2 + terser: 5.17.7 dev: true - /metro-minify-uglify@0.73.10: - resolution: {integrity: sha512-eocnSeJKnLz/UoYntVFhCJffED7SLSgbCHgNvI6ju6hFb6EFHGJT9OLbkJWeXaWBWD3Zw5mYLS8GGqGn/CHZPA==} + /metro-minify-uglify@0.73.9: + resolution: {integrity: sha512-gzxD/7WjYcnCNGiFJaA26z34rjOp+c/Ft++194Wg91lYep3TeWQ0CnH8t2HRS7AYDHU81SGWgvD3U7WV0g4LGA==} dependencies: uglify-es: 3.3.9 - /metro-minify-uglify@0.76.7: - resolution: {integrity: sha512-FuXIU3j2uNcSvQtPrAJjYWHruPiQ+EpE++J9Z+VznQKEHcIxMMoQZAfIF2IpZSrZYfLOjVFyGMvj41jQMxV1Vw==} + /metro-minify-uglify@0.76.6: + resolution: {integrity: sha512-/ydKCoJKuNKB5SHadSekOn6X00vglDtFEWYMrE3EJZPovyYT92ZBpQPOLPcGdgwsxUACssBwRSWoakQuooHEgw==} engines: {node: '>=16'} dependencies: uglify-es: 3.3.9 dev: true - /metro-react-native-babel-preset@0.73.10(@babel/core@7.22.1): - resolution: {integrity: sha512-1/dnH4EHwFb2RKEKx34vVDpUS3urt2WEeR8FYim+ogqALg4sTpG7yeQPxWpbgKATezt4rNfqAANpIyH19MS4BQ==} - peerDependencies: - '@babel/core': '*' - dependencies: - '@babel/core': 7.22.1 - '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.22.1) - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.22.1) - '@babel/plugin-proposal-export-default-from': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.22.1) - '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.22.1) - '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.22.1) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.22.1) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.22.1) - '@babel/plugin-syntax-export-default-from': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.1) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.1) - '@babel/plugin-transform-arrow-functions': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-async-to-generator': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-block-scoping': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-classes': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-computed-properties': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-destructuring': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-flow-strip-types': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-function-name': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-literals': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-modules-commonjs': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-parameters': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-react-display-name': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-runtime': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-shorthand-properties': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-spread': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-sticky-regex': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-template-literals': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-typescript': 7.22.5(@babel/core@7.22.1) - '@babel/plugin-transform-unicode-regex': 7.22.5(@babel/core@7.22.1) - '@babel/template': 7.22.5 - react-refresh: 0.4.3 - transitivePeerDependencies: - - supports-color - /metro-react-native-babel-preset@0.73.7(@babel/core@7.22.1): resolution: {integrity: sha512-RKcmRZREjJCzHKP+JhC9QTCohkeb3xa/DtqHU14U5KWzJHdC0mMrkTZYNXhV0cryxsaVKVEw5873KhbZyZHMVw==} peerDependencies: @@ -17967,10 +17954,9 @@ packages: react-refresh: 0.4.3 transitivePeerDependencies: - supports-color - dev: false - /metro-react-native-babel-preset@0.76.7(@babel/core@7.22.1): - resolution: {integrity: sha512-R25wq+VOSorAK3hc07NW0SmN8z9S/IR0Us0oGAsBcMZnsgkbOxu77Mduqf+f4is/wnWHc5+9bfiqdLnaMngiVw==} + /metro-react-native-babel-preset@0.76.6(@babel/core@7.22.1): + resolution: {integrity: sha512-WswDzpHAW7QuD+u8ICTY1D7mGGQUVeZWr/e9bxfCCsQurH9l1eO0uxGzciMmNr9CzI5DTMtGeaA6Ol+1AH8F2A==} engines: {node: '>=16'} peerDependencies: '@babel/core': '*' @@ -18018,117 +18004,117 @@ packages: - supports-color dev: true - /metro-react-native-babel-transformer@0.73.10(@babel/core@7.22.1): - resolution: {integrity: sha512-4G/upwqKdmKEjmsNa92/NEgsOxUWOygBVs+FXWfXWKgybrmcjh3NoqdRYrROo9ZRA/sB9Y/ZXKVkWOGKHtGzgg==} + /metro-react-native-babel-transformer@0.73.7(@babel/core@7.22.1): + resolution: {integrity: sha512-73HW8betjX+VPm3iqsMBe8F/F2Tt+hONO6YJwcF7FonTqQYW1oTz0dOp0dClZGfHUXxpJBz6Vuo7J6TpdzDD+w==} peerDependencies: '@babel/core': '*' dependencies: '@babel/core': 7.22.1 babel-preset-fbjs: 3.4.0(@babel/core@7.22.1) hermes-parser: 0.8.0 - metro-babel-transformer: 0.73.10 - metro-react-native-babel-preset: 0.73.10(@babel/core@7.22.1) - metro-source-map: 0.73.10 + metro-babel-transformer: 0.73.7 + metro-react-native-babel-preset: 0.73.7(@babel/core@7.22.1) + metro-source-map: 0.73.7 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - /metro-react-native-babel-transformer@0.73.7(@babel/core@7.22.1): - resolution: {integrity: sha512-73HW8betjX+VPm3iqsMBe8F/F2Tt+hONO6YJwcF7FonTqQYW1oTz0dOp0dClZGfHUXxpJBz6Vuo7J6TpdzDD+w==} + /metro-react-native-babel-transformer@0.73.9(@babel/core@7.22.1): + resolution: {integrity: sha512-DSdrEHuQ22ixY7DyipyKkIcqhOJrt5s6h6X7BYJCP9AMUfXOwLe2biY3BcgJz5GOXv8/Akry4vTCvQscVS1otQ==} peerDependencies: '@babel/core': '*' dependencies: '@babel/core': 7.22.1 babel-preset-fbjs: 3.4.0(@babel/core@7.22.1) hermes-parser: 0.8.0 - metro-babel-transformer: 0.73.7 - metro-react-native-babel-preset: 0.73.7(@babel/core@7.22.1) - metro-source-map: 0.73.7 + metro-babel-transformer: 0.73.9 + metro-react-native-babel-preset: 0.73.9(@babel/core@7.22.1) + metro-source-map: 0.73.9 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - /metro-resolver@0.73.10: - resolution: {integrity: sha512-HeXbs+0wjakaaVQ5BI7eT7uqxlZTc9rnyw6cdBWWMgUWB++KpoI0Ge7Hi6eQAOoVAzXC3m26mPFYLejpzTWjng==} + /metro-resolver@0.73.9: + resolution: {integrity: sha512-Ej3wAPOeNRPDnJmkK0zk7vJ33iU07n+oPhpcf5L0NFkWneMmSM2bflMPibI86UjzZGmRfn0AhGhs8yGeBwQ/Xg==} dependencies: absolute-path: 0.0.0 - /metro-resolver@0.76.7: - resolution: {integrity: sha512-pC0Wgq29HHIHrwz23xxiNgylhI8Rq1V01kQaJ9Kz11zWrIdlrH0ZdnJ7GC6qA0ErROG+cXmJ0rJb8/SW1Zp2IA==} + /metro-resolver@0.76.6: + resolution: {integrity: sha512-npZbsYTauhBmWj8AsWCU/RZe2qUJ5mAS93QoqYPIGNqnXlVq7maga3z17jXvP9o5BgsfdxnEmBVHVzYHdSegGA==} engines: {node: '>=16'} dev: true - /metro-runtime@0.73.10: - resolution: {integrity: sha512-EpVKm4eN0Fgx2PEWpJ5NiMArV8zVoOin866jIIvzFLpmkZz1UEqgjf2JAfUJnjgv3fjSV3JqeGG2vZCaGQBTow==} + /metro-runtime@0.73.7: + resolution: {integrity: sha512-2fxRGrF8FyrwwHY0TCitdUljzutfW6CWEpdvPilfrs8p0PI5X8xOWg8ficeYtw+DldHtHIAL2phT59PqzHTyVA==} dependencies: '@babel/runtime': 7.22.5 react-refresh: 0.4.3 - /metro-runtime@0.73.7: - resolution: {integrity: sha512-2fxRGrF8FyrwwHY0TCitdUljzutfW6CWEpdvPilfrs8p0PI5X8xOWg8ficeYtw+DldHtHIAL2phT59PqzHTyVA==} + /metro-runtime@0.73.9: + resolution: {integrity: sha512-d5Hs83FpKB9r8q8Vb95+fa6ESpwysmPr4lL1I2rM2qXAFiO7OAPT9Bc23WmXgidkBtD0uUFdB2lG+H1ATz8rZg==} dependencies: '@babel/runtime': 7.22.5 react-refresh: 0.4.3 - /metro-runtime@0.76.7: - resolution: {integrity: sha512-MuWHubQHymUWBpZLwuKZQgA/qbb35WnDAKPo83rk7JRLIFPvzXSvFaC18voPuzJBt1V98lKQIonh6MiC9gd8Ug==} + /metro-runtime@0.76.6: + resolution: {integrity: sha512-KWE1AhHrh69w/28/b9ebvE47je/K74XB0oVdAQeQLKUXb6iGhMBgoqpHwt3XO6dgYOWv901DKPp/aRahzDM4qA==} engines: {node: '>=16'} dependencies: '@babel/runtime': 7.22.5 react-refresh: 0.4.3 dev: true - /metro-source-map@0.73.10: - resolution: {integrity: sha512-NAGv14701p/YaFZ76KzyPkacBw/QlEJF1f8elfs23N1tC33YyKLDKvPAzFJiYqjdcFvuuuDCA8JCXd2TgLxNPw==} + /metro-source-map@0.73.7: + resolution: {integrity: sha512-gbC/lfUN52TtQhEsTTA+987MaFUpQlufuCI05blLGLosDcFCsARikHsxa65Gtslm/rG2MqvFLiPA5hviONNv9g==} dependencies: '@babel/traverse': 7.22.5 '@babel/types': 7.22.5 invariant: 2.2.4 - metro-symbolicate: 0.73.10 + metro-symbolicate: 0.73.7 nullthrows: 1.1.1 - ob1: 0.73.10 + ob1: 0.73.7 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - /metro-source-map@0.73.7: - resolution: {integrity: sha512-gbC/lfUN52TtQhEsTTA+987MaFUpQlufuCI05blLGLosDcFCsARikHsxa65Gtslm/rG2MqvFLiPA5hviONNv9g==} + /metro-source-map@0.73.9: + resolution: {integrity: sha512-l4VZKzdqafipriETYR6lsrwtavCF1+CMhCOY9XbyWeTrpGSNgJQgdeJpttzEZTHQQTLR0csQo0nD1ef3zEP6IQ==} dependencies: '@babel/traverse': 7.22.5 '@babel/types': 7.22.5 invariant: 2.2.4 - metro-symbolicate: 0.73.7 + metro-symbolicate: 0.73.9 nullthrows: 1.1.1 - ob1: 0.73.7 + ob1: 0.73.9 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - /metro-source-map@0.76.7: - resolution: {integrity: sha512-Prhx7PeRV1LuogT0Kn5VjCuFu9fVD68eefntdWabrksmNY6mXK8pRqzvNJOhTojh6nek+RxBzZeD6MIOOyXS6w==} + /metro-source-map@0.76.6: + resolution: {integrity: sha512-If5SgizVsWrDYuTGNs2zZIjNY2nuF5nUgpjjRW7QC+46vcetdaxWEFdJa6BGBAe55vZzRlB2KaxQAeR1W+JJ2w==} engines: {node: '>=16'} dependencies: '@babel/traverse': 7.22.5 '@babel/types': 7.22.5 invariant: 2.2.4 - metro-symbolicate: 0.76.7 + metro-symbolicate: 0.76.6 nullthrows: 1.1.1 - ob1: 0.76.7 + ob1: 0.76.6 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color dev: true - /metro-symbolicate@0.73.10: - resolution: {integrity: sha512-PmCe3TOe1c/NVwMlB+B17me951kfkB3Wve5RqJn+ErPAj93od1nxicp6OJe7JT4QBRnpUP8p9tw2sHKqceIzkA==} + /metro-symbolicate@0.73.7: + resolution: {integrity: sha512-571ThWmX5o8yGNzoXjlcdhmXqpByHU/bSZtWKhtgV2TyIAzYCYt4hawJAS5+/qDazUvjHdm8BbdqFUheM0EKNQ==} engines: {node: '>=8.3'} hasBin: true dependencies: invariant: 2.2.4 - metro-source-map: 0.73.10 + metro-source-map: 0.73.7 nullthrows: 1.1.1 source-map: 0.5.7 through2: 2.0.5 @@ -18136,13 +18122,13 @@ packages: transitivePeerDependencies: - supports-color - /metro-symbolicate@0.73.7: - resolution: {integrity: sha512-571ThWmX5o8yGNzoXjlcdhmXqpByHU/bSZtWKhtgV2TyIAzYCYt4hawJAS5+/qDazUvjHdm8BbdqFUheM0EKNQ==} + /metro-symbolicate@0.73.9: + resolution: {integrity: sha512-4TUOwxRHHqbEHxRqRJ3wZY5TA8xq7AHMtXrXcjegMH9FscgYztsrIG9aNBUBS+VLB6g1qc6BYbfIgoAnLjCDyw==} engines: {node: '>=8.3'} hasBin: true dependencies: invariant: 2.2.4 - metro-source-map: 0.73.7 + metro-source-map: 0.73.9 nullthrows: 1.1.1 source-map: 0.5.7 through2: 2.0.5 @@ -18150,13 +18136,13 @@ packages: transitivePeerDependencies: - supports-color - /metro-symbolicate@0.76.7: - resolution: {integrity: sha512-p0zWEME5qLSL1bJb93iq+zt5fz3sfVn9xFYzca1TJIpY5MommEaS64Va87lp56O0sfEIvh4307Oaf/ZzRjuLiQ==} + /metro-symbolicate@0.76.6: + resolution: {integrity: sha512-6o+OIQa5brLaknxN8xsCasXHxFuqfqhboWjf89klWKqqZnUzL/k94bJhhJrtlxswmOV6SSI3X9IREvdpy0wZqw==} engines: {node: '>=16'} hasBin: true dependencies: invariant: 2.2.4 - metro-source-map: 0.76.7 + metro-source-map: 0.76.6 nullthrows: 1.1.1 source-map: 0.5.7 through2: 2.0.5 @@ -18165,8 +18151,8 @@ packages: - supports-color dev: true - /metro-transform-plugins@0.73.10: - resolution: {integrity: sha512-D4AgD3Vsrac+4YksaPmxs/0ocT67bvwTkFSIgWWeDvWwIG0U1iHzTS9f8Bvb4PITnXryDoFtjI6OWF7uOpGxpA==} + /metro-transform-plugins@0.73.9: + resolution: {integrity: sha512-r9NeiqMngmooX2VOKLJVQrMuV7PAydbqst5bFhdVBPcFpZkxxqyzjzo+kzrszGy2UpSQBZr2P1L6OMjLHwQwfQ==} dependencies: '@babel/core': 7.22.1 '@babel/generator': 7.22.5 @@ -18176,8 +18162,8 @@ packages: transitivePeerDependencies: - supports-color - /metro-transform-plugins@0.76.7: - resolution: {integrity: sha512-iSmnjVApbdivjuzb88Orb0JHvcEt5veVyFAzxiS5h0QB+zV79w6JCSqZlHCrbNOkOKBED//LqtKbFVakxllnNg==} + /metro-transform-plugins@0.76.6: + resolution: {integrity: sha512-7uAmmFZjm5PxFjIRTbzLfqTZOyRh1kF1QU1z3sS4UHz451/khc1vg6qCWUQh8ELeHnV9/LVCGPUd60gHC4B5VA==} engines: {node: '>=16'} dependencies: '@babel/core': 7.22.1 @@ -18189,21 +18175,21 @@ packages: - supports-color dev: true - /metro-transform-worker@0.73.10: - resolution: {integrity: sha512-IySvVubudFxahxOljWtP0QIMMpgUrCP0bW16cz2Enof0PdumwmR7uU3dTbNq6S+XTzuMHR+076aIe4VhPAWsIQ==} + /metro-transform-worker@0.73.9: + resolution: {integrity: sha512-Rq4b489sIaTUENA+WCvtu9yvlT/C6zFMWhU4sq+97W29Zj0mPBjdk+qGT5n1ZBgtBIJzZWt1KxeYuc17f4aYtQ==} dependencies: '@babel/core': 7.22.1 '@babel/generator': 7.22.5 '@babel/parser': 7.22.5 '@babel/types': 7.22.5 babel-preset-fbjs: 3.4.0(@babel/core@7.22.1) - metro: 0.73.10 - metro-babel-transformer: 0.73.10 - metro-cache: 0.73.10 - metro-cache-key: 0.73.10 - metro-hermes-compiler: 0.73.10 - metro-source-map: 0.73.10 - metro-transform-plugins: 0.73.10 + metro: 0.73.9 + metro-babel-transformer: 0.73.9 + metro-cache: 0.73.9 + metro-cache-key: 0.73.9 + metro-hermes-compiler: 0.73.9 + metro-source-map: 0.73.9 + metro-transform-plugins: 0.73.9 nullthrows: 1.1.1 transitivePeerDependencies: - bufferutil @@ -18211,8 +18197,8 @@ packages: - supports-color - utf-8-validate - /metro-transform-worker@0.76.7: - resolution: {integrity: sha512-cGvELqFMVk9XTC15CMVzrCzcO6sO1lURfcbgjuuPdzaWuD11eEyocvkTX0DPiRjsvgAmicz4XYxVzgYl3MykDw==} + /metro-transform-worker@0.76.6: + resolution: {integrity: sha512-dCe6yqsrzH9NUIre2LjUsXs1gtgLii6TWGrmD9YySpN7ivPs3RA2cdA/e5mNi5sIx4i5d8vDwvePJ+ZXYj61/g==} engines: {node: '>=16'} dependencies: '@babel/core': 7.22.1 @@ -18220,12 +18206,12 @@ packages: '@babel/parser': 7.22.5 '@babel/types': 7.22.5 babel-preset-fbjs: 3.4.0(@babel/core@7.22.1) - metro: 0.76.7 - metro-babel-transformer: 0.76.7 - metro-cache: 0.76.7 - metro-cache-key: 0.76.7 - metro-source-map: 0.76.7 - metro-transform-plugins: 0.76.7 + metro: 0.76.6 + metro-babel-transformer: 0.76.6 + metro-cache: 0.76.6 + metro-cache-key: 0.76.6 + metro-source-map: 0.76.6 + metro-transform-plugins: 0.76.6 nullthrows: 1.1.1 transitivePeerDependencies: - bufferutil @@ -18234,8 +18220,8 @@ packages: - utf-8-validate dev: true - /metro@0.73.10: - resolution: {integrity: sha512-J2gBhNHFtc/Z48ysF0B/bfTwUwaRDLjNv7egfhQCc+934dpXcjJG2KZFeuybF+CvA9vo4QUi56G2U+RSAJ5tsA==} + /metro@0.73.9: + resolution: {integrity: sha512-BlYbPmTF60hpetyNdKhdvi57dSqutb+/oK0u3ni4emIh78PiI0axGo7RfdsZ/mn3saASXc94tDbpC5yn7+NpEg==} hasBin: true dependencies: '@babel/code-frame': 7.22.5 @@ -18259,27 +18245,26 @@ packages: image-size: 0.6.3 invariant: 2.2.4 jest-worker: 27.5.1 - jsc-safe-url: 0.2.4 lodash.throttle: 4.1.1 - metro-babel-transformer: 0.73.10 - metro-cache: 0.73.10 - metro-cache-key: 0.73.10 - metro-config: 0.73.10 - metro-core: 0.73.10 - metro-file-map: 0.73.10 - metro-hermes-compiler: 0.73.10 - metro-inspector-proxy: 0.73.10 - metro-minify-terser: 0.73.10 - metro-minify-uglify: 0.73.10 - metro-react-native-babel-preset: 0.73.10(@babel/core@7.22.1) - metro-resolver: 0.73.10 - metro-runtime: 0.73.10 - metro-source-map: 0.73.10 - metro-symbolicate: 0.73.10 - metro-transform-plugins: 0.73.10 - metro-transform-worker: 0.73.10 + metro-babel-transformer: 0.73.9 + metro-cache: 0.73.9 + metro-cache-key: 0.73.9 + metro-config: 0.73.9 + metro-core: 0.73.9 + metro-file-map: 0.73.9 + metro-hermes-compiler: 0.73.9 + metro-inspector-proxy: 0.73.9 + metro-minify-terser: 0.73.9 + metro-minify-uglify: 0.73.9 + metro-react-native-babel-preset: 0.73.9(@babel/core@7.22.1) + metro-resolver: 0.73.9 + metro-runtime: 0.73.9 + metro-source-map: 0.73.9 + metro-symbolicate: 0.73.9 + metro-transform-plugins: 0.73.9 + metro-transform-worker: 0.73.9 mime-types: 2.1.35 - node-fetch: 2.6.12 + node-fetch: 2.6.11 nullthrows: 1.1.1 rimraf: 3.0.2 serialize-error: 2.1.0 @@ -18295,8 +18280,8 @@ packages: - supports-color - utf-8-validate - /metro@0.76.7: - resolution: {integrity: sha512-67ZGwDeumEPnrHI+pEDSKH2cx+C81Gx8Mn5qOtmGUPm/Up9Y4I1H2dJZ5n17MWzejNo0XAvPh0QL0CrlJEODVQ==} + /metro@0.76.6: + resolution: {integrity: sha512-aHjD5GJaPT4B3goqgi5/UBbFJpvj7ZygLUPDkbKbkE3lMOf3fKo2b+o+3N1fCCENFWiNZLYHEA6Twrr+6140MA==} engines: {node: '>=16'} hasBin: true dependencies: @@ -18322,24 +18307,24 @@ packages: jest-worker: 27.5.1 jsc-safe-url: 0.2.4 lodash.throttle: 4.1.1 - metro-babel-transformer: 0.76.7 - metro-cache: 0.76.7 - metro-cache-key: 0.76.7 - metro-config: 0.76.7 - metro-core: 0.76.7 - metro-file-map: 0.76.7 - metro-inspector-proxy: 0.76.7 - metro-minify-terser: 0.76.7 - metro-minify-uglify: 0.76.7 - metro-react-native-babel-preset: 0.76.7(@babel/core@7.22.1) - metro-resolver: 0.76.7 - metro-runtime: 0.76.7 - metro-source-map: 0.76.7 - metro-symbolicate: 0.76.7 - metro-transform-plugins: 0.76.7 - metro-transform-worker: 0.76.7 + metro-babel-transformer: 0.76.6 + metro-cache: 0.76.6 + metro-cache-key: 0.76.6 + metro-config: 0.76.6 + metro-core: 0.76.6 + metro-file-map: 0.76.6 + metro-inspector-proxy: 0.76.6 + metro-minify-terser: 0.76.6 + metro-minify-uglify: 0.76.6 + metro-react-native-babel-preset: 0.76.6(@babel/core@7.22.1) + metro-resolver: 0.76.6 + metro-runtime: 0.76.6 + metro-source-map: 0.76.6 + metro-symbolicate: 0.76.6 + metro-transform-plugins: 0.76.6 + metro-transform-worker: 0.76.6 mime-types: 2.1.35 - node-fetch: 2.6.12 + node-fetch: 2.6.11 nullthrows: 1.1.1 rimraf: 3.0.2 serialize-error: 2.1.0 @@ -18461,7 +18446,7 @@ packages: resolution: {integrity: sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==} dependencies: '@types/katex': 0.16.0 - katex: 0.16.8 + katex: 0.16.7 micromark-factory-space: 1.1.0 micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 @@ -18520,8 +18505,8 @@ packages: /micromark-extension-mdxjs@1.0.1: resolution: {integrity: sha512-7YA7hF6i5eKOfFUzZ+0z6avRG52GpWR8DL+kN47y3f2KhxbBZMhmxe7auOeaTBrW2DenbbZTf1ea9tA2hDpC2Q==} dependencies: - acorn: 8.9.0 - acorn-jsx: 5.3.2(acorn@8.9.0) + acorn: 8.8.2 + acorn-jsx: 5.3.2(acorn@8.8.2) micromark-extension-mdx-expression: 1.0.8 micromark-extension-mdx-jsx: 1.0.5 micromark-extension-mdx-md: 1.0.1 @@ -19011,14 +18996,14 @@ packages: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} dev: false - /next-contentlayer@0.3.2(esbuild@0.18.10)(next@13.4.3)(react-dom@18.2.0)(react@18.2.0): + /next-contentlayer@0.3.2(esbuild@0.18.0)(next@13.4.3)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-pihb/VtBq30eV+WpaWakWVtA1DWKzfXeaL7l/vR4MvrTO8UtZaX9H6wY0oSOqrmy674BRjXiQ03PbEOE5D6/iA==} peerDependencies: next: ^12 || ^13 react: '*' react-dom: '*' dependencies: - '@contentlayer/core': 0.3.2(esbuild@0.18.10) + '@contentlayer/core': 0.3.2(esbuild@0.18.0) '@contentlayer/utils': 0.3.2 next: 13.4.3(@babel/core@7.22.1)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 @@ -19059,7 +19044,7 @@ packages: '@opentelemetry/api': 1.4.1 '@swc/helpers': 0.5.1 busboy: 1.6.0 - caniuse-lite: 1.0.30001509 + caniuse-lite: 1.0.30001498 postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -19087,7 +19072,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.6.0 + tslib: 2.5.3 /nocache@3.0.4: resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==} @@ -19123,8 +19108,8 @@ packages: resolution: {integrity: sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ==} dev: true - /node-fetch@2.6.12: - resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} + /node-fetch@2.6.11: + resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==} engines: {node: 4.x || >=6.0.0} peerDependencies: encoding: ^0.1.0 @@ -19244,14 +19229,14 @@ packages: resolution: {integrity: sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==} dev: false - /ob1@0.73.10: - resolution: {integrity: sha512-aO6EYC+QRRCkZxVJhCWhLKgVjhNuD6Gu1riGjxrIm89CqLsmKgxzYDDEsktmKsoDeRdWGQM5EdMzXDl5xcVfsw==} - /ob1@0.73.7: resolution: {integrity: sha512-DfelfvR843KADhSUATGGhuepVMRcf5VQX+6MQLy5AW0BKDLlO7Usj6YZeAAZP7P86QwsoTxB0RXCFiA7t6S1IQ==} - /ob1@0.76.7: - resolution: {integrity: sha512-BQdRtxxoUNfSoZxqeBGOyuT9nEYSn18xZHwGMb0mMVpn2NBcYbnyKY4BK2LIHRgw33CBGlUmE+KMaNvyTpLLtQ==} + /ob1@0.73.9: + resolution: {integrity: sha512-kHOzCOFXmAM26fy7V/YuXNKne2TyRiXbFAvPBIbuedJCZZWQZHLdPzMeXJI4Egt6IcfDttRzN3jQ90wOwq1iNw==} + + /ob1@0.76.6: + resolution: {integrity: sha512-yeLyF3Vao7K3TsXNUhIneyPei42Oe9PESzC7/udzKqnx+M6jmKZQ1nPXH5/zCMqMjqZ9to11it9q5uHUNexNUw==} engines: {node: '>=16'} dev: true @@ -19386,9 +19371,9 @@ packages: mimic-fn: 4.0.0 dev: true - /oo-ascii-tree@1.84.0: - resolution: {integrity: sha512-8bvsAKFAQ7HwU3lAEDwsKYDkTqsDTsRTkr3J0gvH1U805d2no9rUNYptWzg3oYku5h5mr9Bko+BIh1pjSD8qrg==} - engines: {node: '>= 14.17.0'} + /oo-ascii-tree@1.83.0: + resolution: {integrity: sha512-Fib3Py1moaeRkIRCZyKmqHvuWGf1x3SXE5vjFc5L13EMgycO6edNgkLFa/0zSy+oZHzkNneLY3Ozt7UZFy0k6g==} + engines: {node: '>= 14.6.0'} dev: false /open@6.4.0: @@ -19432,16 +19417,27 @@ packages: tiny-inflate: 1.0.3 dev: false - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + /optionator@0.8.3: + resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.3.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 + word-wrap: 1.2.3 + + /optionator@0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} dependencies: - '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 + word-wrap: 1.2.3 dev: true /ora@3.4.0: @@ -19559,7 +19555,7 @@ packages: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} dependencies: dot-case: 3.0.4 - tslib: 2.6.0 + tslib: 2.5.3 /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -19636,7 +19632,7 @@ packages: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: no-case: 3.0.4 - tslib: 2.6.0 + tslib: 2.5.3 /pascalcase@0.1.1: resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} @@ -19689,11 +19685,11 @@ packages: /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - /path-scurry@1.10.0: - resolution: {integrity: sha512-tZFEaRQbMLjwrsmidsGJ6wDMv0iazJWk6SfIKnY4Xru8auXgmJkOBa5DUbYFcFD2Rzk2+KDlIiF0GVXNCbgC7g==} + /path-scurry@1.9.2: + resolution: {integrity: sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==} engines: {node: '>=16 || 14 >=14.17'} dependencies: - lru-cache: 10.0.0 + lru-cache: 9.1.2 minipass: 6.0.2 dev: true @@ -19742,7 +19738,7 @@ packages: react-native: '*' react-native-svg: '*' dependencies: - caniuse-lite: 1.0.30001509 + caniuse-lite: 1.0.30001498 react: 18.2.0 react-native: 0.71.3(@babel/core@7.22.1)(@babel/preset-env@7.22.5)(react@18.2.0) react-native-svg: 13.4.0(react-native@0.71.3)(react@18.2.0) @@ -19777,8 +19773,8 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - /pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + /pirates@4.0.5: + resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} /pkg-dir@3.0.0: @@ -19848,7 +19844,7 @@ packages: framesync: 6.0.1 hey-listen: 1.0.8 style-value-types: 5.0.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /posix-character-classes@0.1.1: @@ -19891,7 +19887,7 @@ packages: postcss: 8.4.23 yaml: 2.3.1 - /postcss-loader@7.3.3(postcss@8.4.23)(webpack@5.88.1): + /postcss-loader@7.3.3(postcss@8.4.23)(webpack@5.86.0): resolution: {integrity: sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -19902,7 +19898,7 @@ packages: jiti: 1.18.2 postcss: 8.4.23 semver: 7.5.0 - webpack: 5.88.1(esbuild@0.17.19) + webpack: 5.86.0(esbuild@0.17.19) dev: false /postcss-modules-extract-imports@3.0.0(postcss@8.4.23): @@ -20026,6 +20022,10 @@ packages: tunnel-agent: 0.6.0 dev: false + /prelude-ls@1.1.2: + resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} + engines: {node: '>= 0.8.0'} + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -20221,8 +20221,8 @@ packages: long: 4.0.0 dev: false - /protobufjs@7.2.4: - resolution: {integrity: sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==} + /protobufjs@7.2.3: + resolution: {integrity: sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==} engines: {node: '>=12.0.0'} requiresBuild: true dependencies: @@ -20586,7 +20586,7 @@ packages: react-base16-styling: 0.6.0 react-dom: 18.2.0(react@18.2.0) react-lifecycles-compat: 3.0.4 - react-textarea-autosize: 8.5.0(@types/react@18.0.38)(react@18.2.0) + react-textarea-autosize: 8.4.1(@types/react@18.0.38)(react@18.2.0) transitivePeerDependencies: - '@types/react' - encoding @@ -20859,7 +20859,7 @@ packages: '@types/react': 18.0.38 react: 18.2.0 react-style-singleton: 2.2.1(@types/react@18.0.38)(react@18.2.0) - tslib: 2.6.0 + tslib: 2.5.3 dev: false /react-remove-scroll@2.5.4(@types/react@18.0.38)(react@18.2.0): @@ -20876,7 +20876,7 @@ packages: react: 18.2.0 react-remove-scroll-bar: 2.3.4(@types/react@18.0.38)(react@18.2.0) react-style-singleton: 2.2.1(@types/react@18.0.38)(react@18.2.0) - tslib: 2.6.0 + tslib: 2.5.3 use-callback-ref: 1.3.0(@types/react@18.0.38)(react@18.2.0) use-sidecar: 1.1.2(@types/react@18.0.38)(react@18.2.0) dev: false @@ -20895,7 +20895,7 @@ packages: react: 18.2.0 react-remove-scroll-bar: 2.3.4(@types/react@18.0.38)(react@18.2.0) react-style-singleton: 2.2.1(@types/react@18.0.38)(react@18.2.0) - tslib: 2.6.0 + tslib: 2.5.3 use-callback-ref: 1.3.0(@types/react@18.0.38)(react@18.2.0) use-sidecar: 1.1.2(@types/react@18.0.38)(react@18.2.0) dev: false @@ -20973,11 +20973,11 @@ packages: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.2.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false - /react-textarea-autosize@8.5.0(@types/react@18.0.38)(react@18.2.0): - resolution: {integrity: sha512-cp488su3U9RygmHmGpJp0KEt0i/+57KCK33XVPH+50swVRBhIZYh0fGduz2YLKXwl9vSKBZ9HUXcg9PQXUXqIw==} + /react-textarea-autosize@8.4.1(@types/react@18.0.38)(react@18.2.0): + resolution: {integrity: sha512-aD2C+qK6QypknC+lCMzteOdIjoMbNlgSFmJjCV+DrfTPwp59i/it9mMNf2HDzvRjQgKAyBDPyLJhcrzElf2U4Q==} engines: {node: '>=10'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -21098,7 +21098,7 @@ packages: ast-types: 0.14.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.6.0 + tslib: 2.5.3 /recast@0.21.5: resolution: {integrity: sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==} @@ -21107,7 +21107,7 @@ packages: ast-types: 0.15.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.6.0 + tslib: 2.5.3 dev: true /recast@0.23.2: @@ -21118,7 +21118,7 @@ packages: ast-types: 0.16.1 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.6.0 + tslib: 2.5.3 /rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} @@ -21266,7 +21266,7 @@ packages: '@types/katex': 0.14.0 hast-util-from-html-isomorphic: 1.0.0 hast-util-to-text: 3.1.2 - katex: 0.16.8 + katex: 0.16.7 unist-util-visit: 4.1.2 dev: false @@ -21618,8 +21618,8 @@ packages: fsevents: 2.3.2 dev: true - /rollup@3.26.0: - resolution: {integrity: sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==} + /rollup@3.24.1: + resolution: {integrity: sha512-REHe5dx30ERBRFS0iENPHy+t6wtSEYkjrhwNsLyh3qpRaZ1+aylvMUdMBUHWUD/RjjLmLzEvY8Z9XRlpcdIkHA==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -21689,7 +21689,7 @@ packages: /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /sass-loader@13.0.2(sass@1.55.0)(webpack@5.88.1): + /sass-loader@13.0.2(sass@1.55.0)(webpack@5.86.0): resolution: {integrity: sha512-BbiqbVmbfJaWVeOOAu2o7DhYWtcNmTfvroVgFXa6k2hHheMxNAeDHLNoDy/Q5aoaVlz0LH+MbMktKwm9vN/j8Q==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -21711,10 +21711,10 @@ packages: klona: 2.0.6 neo-async: 2.6.2 sass: 1.55.0 - webpack: 5.88.1(esbuild@0.17.19) + webpack: 5.86.0(esbuild@0.17.19) dev: true - /sass-loader@13.3.2(webpack@5.88.1): + /sass-loader@13.3.2(webpack@5.86.0): resolution: {integrity: sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -21734,7 +21734,7 @@ packages: optional: true dependencies: neo-async: 2.6.2 - webpack: 5.88.1(esbuild@0.17.19) + webpack: 5.86.0(esbuild@0.17.19) dev: false /sass@1.55.0: @@ -21769,8 +21769,8 @@ packages: ajv-keywords: 3.5.2(ajv@6.12.6) dev: true - /schema-utils@3.3.0: - resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + /schema-utils@3.2.0: + resolution: {integrity: sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==} engines: {node: '>= 10.13.0'} dependencies: '@types/json-schema': 7.0.12 @@ -21798,7 +21798,7 @@ packages: '@scena/dragscroll': 1.4.0 '@scena/event-emitter': 1.0.5 css-styled: 1.0.8 - css-to-mat: 1.1.1 + css-to-mat: 1.0.3 framework-utils: 1.1.0 gesto: 1.19.1 keycon: 1.4.0 @@ -22058,7 +22058,7 @@ packages: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: dot-case: 3.0.4 - tslib: 2.6.0 + tslib: 2.5.3 dev: true /snapdragon-node@2.1.1: @@ -22265,7 +22265,6 @@ packages: engines: {node: '>= 0.4'} dependencies: internal-slot: 1.0.5 - dev: false /store2@2.14.2: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} @@ -22451,22 +22450,22 @@ packages: resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} dev: false - /style-loader@3.3.1(webpack@5.88.1): + /style-loader@3.3.1(webpack@5.86.0): resolution: {integrity: sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 dependencies: - webpack: 5.88.1(esbuild@0.17.19) + webpack: 5.86.0(esbuild@0.17.19) dev: true - /style-loader@3.3.3(webpack@5.88.1): + /style-loader@3.3.3(webpack@5.86.0): resolution: {integrity: sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 dependencies: - webpack: 5.88.1(esbuild@0.17.19) + webpack: 5.86.0(esbuild@0.17.19) dev: false /style-to-object@0.4.1: @@ -22479,7 +22478,7 @@ packages: resolution: {integrity: sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==} dependencies: hey-listen: 1.0.8 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /styled-jsx@5.1.1(@babel/core@7.22.1)(react@18.2.0): @@ -22510,7 +22509,7 @@ packages: glob: 7.1.6 lines-and-columns: 1.2.4 mz: 2.7.0 - pirates: 4.0.6 + pirates: 4.0.5 ts-interface-checker: 0.1.13 /sudo-prompt@8.2.5: @@ -22533,8 +22532,8 @@ packages: - supports-color dev: true - /superjson@1.12.4: - resolution: {integrity: sha512-vkpPQAxdCg9SLfPv5GPC5fnGrui/WryktoN9O5+Zif/14QIMjw+RITf/5LbBh+9QpBFb3KNvJth+puz2H8o6GQ==} + /superjson@1.12.3: + resolution: {integrity: sha512-0j+U70KUtP8+roVPbwfqkyQI7lBt7ETnuA7KXbTDX3mCKiD/4fXs2ldKSMdt0MCfpTwiMxo20yFU3vu6ewETpQ==} engines: {node: '>=10'} dependencies: copy-anything: 3.0.5 @@ -22617,7 +22616,7 @@ packages: engines: {node: ^14.18.0 || >=16.0.0} dependencies: '@pkgr/utils': 2.4.1 - tslib: 2.6.0 + tslib: 2.5.3 dev: true /tailwindcss-animate@1.0.5(tailwindcss@3.3.2): @@ -22769,7 +22768,7 @@ packages: supports-hyperlinks: 2.3.0 dev: false - /terser-webpack-plugin@5.3.9(esbuild@0.17.19)(webpack@5.88.1): + /terser-webpack-plugin@5.3.9(esbuild@0.17.19)(webpack@5.86.0): resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -22788,18 +22787,18 @@ packages: '@jridgewell/trace-mapping': 0.3.18 esbuild: 0.17.19 jest-worker: 27.5.1 - schema-utils: 3.3.0 + schema-utils: 3.2.0 serialize-javascript: 6.0.1 - terser: 5.18.2 - webpack: 5.88.1(esbuild@0.17.19) + terser: 5.17.7 + webpack: 5.86.0(esbuild@0.17.19) - /terser@5.18.2: - resolution: {integrity: sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==} + /terser@5.17.7: + resolution: {integrity: sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==} engines: {node: '>=10'} hasBin: true dependencies: - '@jridgewell/source-map': 0.3.4 - acorn: 8.9.0 + '@jridgewell/source-map': 0.3.3 + acorn: 8.8.2 commander: 2.20.3 source-map-support: 0.5.21 @@ -23049,8 +23048,8 @@ packages: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} dev: false - /tslib@2.6.0: - resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} + /tslib@2.5.3: + resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} /tsparticles-engine@2.10.1: resolution: {integrity: sha512-DV2gYsbChyiXYIZYgnXtKHSAZdvnNMJpVf9Cw0gO7vjQ6pcgLAeyboRtvsaTfwKZNzzA7BeSf1lVhgGxorL4CQ==} @@ -23446,6 +23445,12 @@ packages: resolution: {integrity: sha512-3SJF/czpzqq6G3lprGFLa6ps12yb1uQ1EmitNnep2fDMNh1aO/Zbq9sWY+3lem0zYb2oHJnQWyabTGUZ+L1ScQ==} dev: false + /type-check@0.3.2: + resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -23505,8 +23510,8 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - /type-fest@3.12.0: - resolution: {integrity: sha512-qj9wWsnFvVEMUDbESiilKeXeHL7FwwiFcogfhfyjmvT968RXSvnl23f1JOClTHYItsi7o501C/7qVllscUP3oA==} + /type-fest@3.11.1: + resolution: {integrity: sha512-aCuRNRERRVh33lgQaJRlUxZqzfhzwTrsE98Mc3o3VXqmiaQdHacgUtJ0esp+7MvZ92qhtzKPeusaX6vIEcoreA==} engines: {node: '>=14.16'} dev: false @@ -23757,7 +23762,7 @@ packages: /unplugin@0.10.2: resolution: {integrity: sha512-6rk7GUa4ICYjae5PrAllvcDeuT8pA9+j5J5EkxbMFaV+SalHhxZ7X2dohMzu6C3XzsMT+6jwR/+pwPNR3uK9MA==} dependencies: - acorn: 8.9.0 + acorn: 8.8.2 chokidar: 3.5.3 webpack-sources: 3.2.3 webpack-virtual-modules: 0.4.6 @@ -23779,13 +23784,13 @@ packages: engines: {node: '>=4'} dev: true - /update-browserslist-db@1.0.11(browserslist@4.21.9): + /update-browserslist-db@1.0.11(browserslist@4.21.7): resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.9 + browserslist: 4.21.7 escalade: 3.1.1 picocolors: 1.0.0 @@ -23851,7 +23856,7 @@ packages: dependencies: '@types/react': 18.0.38 react: 18.2.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /use-composed-ref@1.3.0(react@18.2.0): @@ -23956,7 +23961,7 @@ packages: '@types/react': 18.0.38 detect-node-es: 1.1.0 react: 18.2.0 - tslib: 2.6.0 + tslib: 2.5.3 dev: false /use-sync-external-store@1.2.0(react@18.2.0): @@ -24107,7 +24112,7 @@ packages: colorette: 2.0.20 connect-history-api-fallback: 1.6.0 consola: 2.15.3 - dotenv: 16.3.1 + dotenv: 16.1.4 dotenv-expand: 8.0.3 ejs: 3.1.9 fast-glob: 3.2.12 @@ -24127,7 +24132,7 @@ packages: colorette: 2.0.20 connect-history-api-fallback: 1.6.0 consola: 2.15.3 - dotenv: 16.3.1 + dotenv: 16.1.4 dotenv-expand: 8.0.3 ejs: 3.1.9 fast-glob: 3.2.12 @@ -24156,7 +24161,7 @@ packages: dependencies: '@rollup/pluginutils': 4.2.1 '@svgr/core': 6.5.1 - vite: 4.3.9(@types/node@18.15.1) + vite: 4.3.9(sass@1.55.0) transitivePeerDependencies: - supports-color dev: true @@ -24170,7 +24175,7 @@ packages: globrex: 0.1.2 recrawl-sync: 2.2.3 tsconfig-paths: 4.2.0 - vite: 4.3.9(less@4.1.3) + vite: 4.3.9(@types/node@18.15.1) transitivePeerDependencies: - supports-color dev: true @@ -24250,9 +24255,10 @@ packages: '@types/node': 18.15.1 esbuild: 0.17.19 postcss: 8.4.23 - rollup: 3.26.0 + rollup: 3.24.1 optionalDependencies: fsevents: 2.3.2 + dev: true /vite@4.3.9(less@4.1.3): resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} @@ -24282,7 +24288,7 @@ packages: esbuild: 0.17.19 less: 4.1.3 postcss: 8.4.23 - rollup: 3.26.0 + rollup: 3.24.1 optionalDependencies: fsevents: 2.3.2 @@ -24313,7 +24319,7 @@ packages: dependencies: esbuild: 0.17.19 postcss: 8.4.23 - rollup: 3.26.0 + rollup: 3.24.1 sass: 1.55.0 optionalDependencies: fsevents: 2.3.2 @@ -24377,8 +24383,8 @@ packages: /webpack-virtual-modules@0.4.6: resolution: {integrity: sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==} - /webpack@5.88.1(esbuild@0.17.19): - resolution: {integrity: sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==} + /webpack@5.86.0(esbuild@0.17.19): + resolution: {integrity: sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -24392,12 +24398,12 @@ packages: '@webassemblyjs/ast': 1.11.6 '@webassemblyjs/wasm-edit': 1.11.6 '@webassemblyjs/wasm-parser': 1.11.6 - acorn: 8.9.0 - acorn-import-assertions: 1.9.0(acorn@8.9.0) - browserslist: 4.21.9 + acorn: 8.8.2 + acorn-import-assertions: 1.9.0(acorn@8.8.2) + browserslist: 4.21.7 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.15.0 - es-module-lexer: 1.3.0 + enhanced-resolve: 5.14.1 + es-module-lexer: 1.2.1 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -24406,9 +24412,9 @@ packages: loader-runner: 4.3.0 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 3.3.0 + schema-utils: 3.2.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(esbuild@0.17.19)(webpack@5.88.1) + terser-webpack-plugin: 5.3.9(esbuild@0.17.19)(webpack@5.86.0) watchpack: 2.4.0 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -24441,7 +24447,6 @@ packages: is-set: 2.0.2 is-weakmap: 2.0.1 is-weakset: 2.0.2 - dev: false /which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} @@ -24493,6 +24498,10 @@ packages: resolution: {integrity: sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==} dev: false + /word-wrap@1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + /wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} From c877c03b63da8542bbb115e031d0c7bcef7e6c8b Mon Sep 17 00:00:00 2001 From: Niklas Wojtkowiak <57798165+Nim1com@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:01:30 +0300 Subject: [PATCH 07/18] Add functionality to some Overview Catrgories (#1090) Co-authored-by: Brendan Allan --- core/src/library/cat.rs | 6 ++++++ crates/file-ext/src/kind.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/library/cat.rs b/core/src/library/cat.rs index 88ec0ddb1d85..84df22e2199b 100644 --- a/core/src/library/cat.rs +++ b/core/src/library/cat.rs @@ -51,6 +51,9 @@ impl Category { Category::Music => ObjectKind::Audio, Category::Books => ObjectKind::Book, Category::Encrypted => ObjectKind::Encrypted, + Category::Databases => ObjectKind::Database, + Category::Archives => ObjectKind::Archive, + Category::Applications => ObjectKind::Executable, _ => unimplemented!("Category::to_object_kind() for {:?}", self), } } @@ -63,6 +66,9 @@ impl Category { | Category::Videos | Category::Music | Category::Encrypted + | Category::Databases + | Category::Archives + | Category::Applications | Category::Books => object::kind::equals(Some(self.to_object_kind() as i32)), _ => object::id::equals(-1), } diff --git a/crates/file-ext/src/kind.rs b/crates/file-ext/src/kind.rs index a2a2e5a6d11c..86f6e84d8aad 100644 --- a/crates/file-ext/src/kind.rs +++ b/crates/file-ext/src/kind.rs @@ -27,7 +27,7 @@ pub enum ObjectKind { Alias = 10, /// Raw bytes encrypted by Spacedrive with self contained metadata Encrypted = 11, - /// A link can open web pages, apps or Spaces + /// A key or certificate file Key = 12, /// A link can open web pages, apps or Spaces Link = 13, From cf39f8dbcca7a41c0411052b9644765f11f90aa2 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Wed, 12 Jul 2023 08:23:30 +0200 Subject: [PATCH 08/18] [ENG-759] P2P Cleanup (#1062) * less stupid name * yeet * awaiting futures is kinda important lol * no-async * more proto stuff * cleanup * move it round * None of my homies like broadcast * minor * do the shuffle * restore after force push * reusing `sysinfo::System` as intended * fix lol * remove `node_id` from `Volume` * Remove `node_local_id` from `Library` * Remove `Job` to `Node` relation * feature flags be like * press save 4head * remove `Location` -> `Node` relation * `volume.rs` to `volume/mod.rs` * yes * add `Instance` model and deprecate `Node` model * pairing is better now * Pretty code * thinking in code * wip * What if Observables but in Rust?! * Observables aren't it + `useP2PEvents` hook * more around some jsx * Trade offer: bad code for working pairing? * Unit test pairing protocol * fix everything up * corrections * Have you got a moment for our lord and saviour Clippy * tsc --fixMyCode * Prisma being wacky * Config files being f'ed up * broken config after migrating * Zed being Zed * Argh * cliipzy * rewind * Fix truncate logic * wip: instances in peer metadata * Rethink instance ids * fix * whoops --------- Co-authored-by: Brendan Allan --- Cargo.lock | 5 +- apps/desktop/src-tauri/src/file.rs | 3 +- .../settings/library/LocationSettings.tsx | 8 +- .../migration.sql | 95 ++++++ core/prisma/schema.prisma | 79 +++-- core/src/api/libraries.rs | 13 +- core/src/api/locations.rs | 1 - core/src/api/nodes.rs | 58 ++-- core/src/api/p2p.rs | 19 +- core/src/job/report.rs | 3 +- core/src/lib.rs | 15 - core/src/library/config.rs | 118 ++++--- core/src/library/library.rs | 10 +- core/src/library/manager.rs | 127 ++++---- core/src/library/name.rs | 6 + core/src/location/manager/helpers.rs | 18 +- core/src/location/manager/mod.rs | 3 +- core/src/location/metadata.rs | 1 + core/src/location/mod.rs | 44 +-- core/src/p2p/mod.rs | 3 + core/src/p2p/p2p_manager.rs | 273 +++++----------- core/src/p2p/pairing/mod.rs | 266 +++++++++++++++ core/src/p2p/pairing/proto.rs | 302 ++++++++++++++++++ core/src/p2p/peer_metadata.rs | 30 +- core/src/p2p/protocol.rs | 258 +++------------ core/src/p2p/trusted_peers.rs | 5 + core/src/preferences/mod.rs | 2 +- core/src/sync/manager.rs | 24 +- core/src/util/debug_initializer.rs | 15 +- core/src/util/migrator.rs | 33 +- core/src/util/mod.rs | 2 + core/src/util/observable.rs | 101 ++++++ core/src/{volume.rs => volume/mod.rs} | 0 crates/p2p/Cargo.toml | 1 + crates/p2p/examples/basic.rs | 39 ++- crates/p2p/src/{ => discovery}/mdns.rs | 0 .../src/{ => discovery}/metadata_manager.rs | 0 crates/p2p/src/discovery/mod.rs | 5 + crates/p2p/src/event.rs | 29 +- crates/p2p/src/lib.rs | 7 +- crates/p2p/src/manager.rs | 1 + crates/p2p/src/proto.rs | 81 +++++ crates/p2p/src/spaceblock/block.rs | 46 +++ crates/p2p/src/spaceblock/block_size.rs | 22 ++ crates/p2p/src/spaceblock/mod.rs | 146 +-------- crates/p2p/src/spaceblock/sb_request.rs | 59 ++++ crates/p2p/src/spacetime/proto_inbound.rs | 54 +++- crates/p2p/src/spacetime/proto_outbound.rs | 3 +- crates/p2p/src/spacetime/stream.rs | 69 +--- crates/p2p/src/spacetunnel/identity.rs | 7 + crates/p2p/src/spacetunnel/mod.rs | 2 +- crates/sync/src/crdt.rs | 4 +- interface/ErrorFallback.tsx | 2 +- .../settings/library/locations/ListItem.tsx | 9 +- .../app/$libraryId/settings/library/nodes.tsx | 23 +- interface/app/$libraryId/spacedrop.tsx | 77 ----- interface/app/{ => p2p}/Spacedrop.tsx | 38 +-- interface/app/p2p/index.tsx | 23 ++ interface/app/p2p/pairing.tsx | 145 +++++++++ interface/index.tsx | 4 +- packages/client/src/core.ts | 30 +- .../{useFeatureFlag.ts => useFeatureFlag.tsx} | 27 +- packages/client/src/hooks/useP2PEvents.tsx | 57 +++- packages/ui/package.json | 1 + pnpm-lock.yaml | 7 + 65 files changed, 1875 insertions(+), 1083 deletions(-) create mode 100644 core/prisma/migrations/20230712050046_library_instance/migration.sql create mode 100644 core/src/p2p/pairing/mod.rs create mode 100644 core/src/p2p/pairing/proto.rs create mode 100644 core/src/p2p/trusted_peers.rs create mode 100644 core/src/util/observable.rs rename core/src/{volume.rs => volume/mod.rs} (100%) rename crates/p2p/src/{ => discovery}/mdns.rs (100%) rename crates/p2p/src/{ => discovery}/metadata_manager.rs (100%) create mode 100644 crates/p2p/src/discovery/mod.rs create mode 100644 crates/p2p/src/proto.rs create mode 100644 crates/p2p/src/spaceblock/block.rs create mode 100644 crates/p2p/src/spaceblock/block_size.rs create mode 100644 crates/p2p/src/spaceblock/sb_request.rs rename interface/app/{ => p2p}/Spacedrop.tsx (76%) create mode 100644 interface/app/p2p/index.tsx create mode 100644 interface/app/p2p/pairing.tsx rename packages/client/src/hooks/{useFeatureFlag.ts => useFeatureFlag.tsx} (54%) diff --git a/Cargo.lock b/Cargo.lock index 445fd550ce09..34ed4beb9975 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7268,6 +7268,7 @@ dependencies = [ "tokio-util", "tracing 0.1.37", "tracing-subscriber 0.3.17", + "uuid", ] [[package]] @@ -9324,9 +9325,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" dependencies = [ "getrandom 0.2.9", "serde", diff --git a/apps/desktop/src-tauri/src/file.rs b/apps/desktop/src-tauri/src/file.rs index f96c79e4ec0a..91528d59e8fa 100644 --- a/apps/desktop/src-tauri/src/file.rs +++ b/apps/desktop/src-tauri/src/file.rs @@ -330,7 +330,8 @@ pub async fn reveal_items( .db .location() .find_many(vec![ - location::node_id::equals(Some(library.node_local_id)), + // TODO(N): This will fall apart with removable media and is making an invalid assumption that the `Node` is fixed for an `Instance`. + location::instance_id::equals(Some(library.config.instance_id)), location::id::in_vec(locations), ]) .select(location::select!({ path })) diff --git a/apps/mobile/src/screens/settings/library/LocationSettings.tsx b/apps/mobile/src/screens/settings/library/LocationSettings.tsx index 2ffca45d81b1..b7e24a585585 100644 --- a/apps/mobile/src/screens/settings/library/LocationSettings.tsx +++ b/apps/mobile/src/screens/settings/library/LocationSettings.tsx @@ -4,7 +4,6 @@ import { Animated, FlatList, Pressable, Text, View } from 'react-native'; import { Swipeable } from 'react-native-gesture-handler'; import { Location, - Node, arraysEqual, useLibraryMutation, useLibraryQuery, @@ -19,7 +18,7 @@ import { tw, twStyle } from '~/lib/tailwind'; import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator'; type LocationItemProps = { - location: Location & { node: Node | null }; + location: Location; index: number; navigation: SettingsStackScreenProps<'LocationSettings'>['navigation']; }; @@ -109,13 +108,14 @@ function LocationItem({ location, index, navigation }: LocationItemProps) { {location.name} - {location.node && ( + {/* // TODO: This is ephemeral so it should not come from the DB. Eg. a external USB can move between nodes */} + {/* {location.node && ( {location.node.name} - )} + )} */}
)} diff --git a/interface/app/$libraryId/settings/library/locations/ListItem.tsx b/interface/app/$libraryId/settings/library/locations/ListItem.tsx index 043a2059b2e9..dd2f2b748094 100644 --- a/interface/app/$libraryId/settings/library/locations/ListItem.tsx +++ b/interface/app/$libraryId/settings/library/locations/ListItem.tsx @@ -2,14 +2,14 @@ import clsx from 'clsx'; import { Repeat, Trash } from 'phosphor-react'; import { useState } from 'react'; import { useNavigate } from 'react-router'; -import { Location, Node, arraysEqual, useLibraryMutation, useOnlineLocations } from '@sd/client'; +import { Location, arraysEqual, useLibraryMutation, useOnlineLocations } from '@sd/client'; import { Button, Card, Tooltip, dialogManager } from '@sd/ui'; import { Folder } from '~/components'; import { useIsDark } from '~/hooks'; import DeleteDialog from './DeleteDialog'; interface Props { - location: Location & { node: Node | null }; + location: Location; } export default ({ location }: Props) => { @@ -36,11 +36,12 @@ export default ({ location }: Props) => {

{location.name}

- {location.node && ( + {/* // TODO: This is ephemeral so it should not come from the DB. Eg. a external USB can move between nodes */} + {/* {location.node && ( {location.node.name} - )} + )} */} {location.path}

diff --git a/interface/app/$libraryId/settings/library/nodes.tsx b/interface/app/$libraryId/settings/library/nodes.tsx index c3e4e84c903c..74da6a66ac79 100644 --- a/interface/app/$libraryId/settings/library/nodes.tsx +++ b/interface/app/$libraryId/settings/library/nodes.tsx @@ -1,5 +1,6 @@ -import { useDiscoveredPeers, useFeatureFlag, useLibraryMutation } from '@sd/client'; +import { useBridgeMutation, useDiscoveredPeers, useFeatureFlag } from '@sd/client'; import { Button } from '@sd/ui'; +import { startPairing } from '~/app/p2p/pairing'; import { Heading } from '../Layout'; export const Component = () => { @@ -11,9 +12,9 @@ export const Component = () => { title="Nodes" description="Manage the nodes connected to this library. A node is an instance of Spacedrive's backend, running on a device or server. Each node carries a copy of the database and synchronizes via peer-to-peer connections in realtime." /> - {/* TODO: Show paired nodes + unpair button */} + {/* TODO: Replace with modal */} {isPairingEnabled && } ); @@ -22,14 +23,12 @@ export const Component = () => { // TODO: This entire component shows a UI which is pairing by node but that is just not how it works. function IncorrectP2PPairingPane() { const onlineNodes = useDiscoveredPeers(); - const p2pPair = useLibraryMutation('p2p.pair', { + const p2pPair = useBridgeMutation('p2p.pair', { onSuccess(data) { console.log(data); } }); - console.log(onlineNodes); - return ( <>

Pairing

@@ -37,7 +36,19 @@ function IncorrectP2PPairingPane() {

{node.name}

- +
))} diff --git a/interface/app/$libraryId/spacedrop.tsx b/interface/app/$libraryId/spacedrop.tsx index 960aba43d291..6c1e7cd63512 100644 --- a/interface/app/$libraryId/spacedrop.tsx +++ b/interface/app/$libraryId/spacedrop.tsx @@ -83,86 +83,9 @@ function DropItem(props: DropItemProps) { ); } -// const schema = z.object({ -// target_peer: z.string(), -// file_path: z.string() -// }); - -// // TODO: This will be removed and properly hooked up to the UI in the future -// function TemporarySpacedropDemo() { -// const [[discoveredPeers], setDiscoveredPeer] = useState([new Map()]); -// const doSpacedrop = useBridgeMutation('p2p.spacedrop'); - -// const form = useZodForm({ -// schema -// }); - -// useBridgeSubscription(['p2p.events'], { -// onData(data) { -// if (data.type === 'DiscoveredPeer') { -// setDiscoveredPeer([discoveredPeers.set(data.peer_id, data.metadata)]); -// // if (!form.getValues().target_peer) form.setValue('target_peer', data.peer_id); -// } -// } -// }); - -// const onSubmit = form.handleSubmit((data) => { -// doSpacedrop.mutate({ -// peer_id: data.target_peer, -// file_path: data.file_path -// }); -// }); - -// // TODO: Input select -// return ( -//
-// Spacedrop Demo -//

-// Note: Right now the file must be less than 255 bytes long and only contain UTF-8 -// chars. Create a txt file in Vscode to test (note macOS TextEdit cause that is rtf by -// default) -//

-//
-// - -// - -// - -// -//
-//
-// ); -// } - export const Component = () => { return ( <> - {/* */}
diff --git a/interface/app/Spacedrop.tsx b/interface/app/p2p/Spacedrop.tsx similarity index 76% rename from interface/app/Spacedrop.tsx rename to interface/app/p2p/Spacedrop.tsx index bfb20d8417f0..42b327cb74a7 100644 --- a/interface/app/Spacedrop.tsx +++ b/interface/app/p2p/Spacedrop.tsx @@ -3,7 +3,9 @@ import { useBridgeMutation, useBridgeSubscription, useDiscoveredPeers, - useFeatureFlag + useFeatureFlag, + useP2PEvents, + withFeatureFlag } from '@sd/client'; import { Dialog, @@ -16,37 +18,25 @@ import { useZodForm, z } from '@sd/ui'; -import { getSpacedropState, subscribeSpacedropState } from '../hooks/useSpacedropState'; +import { getSpacedropState, subscribeSpacedropState } from '../../hooks/useSpacedropState'; export function SpacedropUI() { - const isSpacedropEnabled = useFeatureFlag('spacedrop'); - if (!isSpacedropEnabled) { - return null; - } - - return ; -} - -function SpacedropUIInner() { useEffect(() => subscribeSpacedropState(() => { dialogManager.create((dp) => ); }) ); - // TODO: In a perfect world, this would not exist as it means we have two open subscriptions for the same data (the other one being in `useP2PEvents.tsx` in `@sd/client`). It's just hard so we will eat the overhead for now. - useBridgeSubscription(['p2p.events'], { - onData(data) { - if (data.type === 'SpacedropRequest') { - dialogManager.create((dp) => ( - - )); - } + useP2PEvents((data) => { + if (data.type === 'SpacedropRequest') { + dialogManager.create((dp) => ( + + )); } }); diff --git a/interface/app/p2p/index.tsx b/interface/app/p2p/index.tsx new file mode 100644 index 000000000000..cf3153e21c30 --- /dev/null +++ b/interface/app/p2p/index.tsx @@ -0,0 +1,23 @@ +import { useOnFeatureFlagsChange, useP2PEvents, withFeatureFlag } from '@sd/client'; +import { SpacedropUI } from './Spacedrop'; +import { startPairing } from './pairing'; + +export const SpacedropUI2 = withFeatureFlag('spacedrop', SpacedropUI); + +// Entrypoint of P2P UI +export function P2P() { + useP2PEvents((data) => { + if (data.type === 'PairingRequest') { + startPairing(data.id, { + name: data.name, + os: data.os + }); + } + }); + + return ( + <> + + + ); +} diff --git a/interface/app/p2p/pairing.tsx b/interface/app/p2p/pairing.tsx new file mode 100644 index 000000000000..c32572b07af9 --- /dev/null +++ b/interface/app/p2p/pairing.tsx @@ -0,0 +1,145 @@ +import { useState } from 'react'; +import { P, match } from 'ts-pattern'; +import { + OperatingSystem, + useBridgeMutation, + useCachedLibraries, + usePairingStatus +} from '@sd/client'; +import { + Button, + Dialog, + Loader, + Select, + SelectOption, + UseDialogProps, + dialogManager, + useDialog, + useZodForm, + z +} from '@sd/ui'; + +type Node = { + name: string; + os: OperatingSystem | null; +}; + +export function startPairing(pairing_id: number, node: Node) { + dialogManager.create((dp) => ); +} + +function OriginatorDialog({ + pairingId, + node, + ...props +}: { pairingId: number; node: Node } & UseDialogProps) { + const pairingStatus = usePairingStatus(pairingId); + + // TODO: If dialog closes before finished, cancel pairing + + return ( + { + alert('TODO'); + // TODO: Change into the new library + }} + // onCancelled={() => acceptSpacedrop.mutate([props.dropId, null])} + > +
+ {match(pairingStatus) + .with({ type: 'EstablishingConnection' }, () => ( + + )) + .with({ type: 'PairingRequested' }, () => ( + + )) + .with({ type: 'PairingDecisionRequest' }, () => ( + + )) + .with({ type: 'PairingInProgress', data: P.select() }, (data) => ( + + )) + .with({ type: 'InitialSyncProgress', data: P.select() }, (data) => ( + + )) + .with({ type: 'PairingComplete' }, () => ) + .with({ type: 'PairingRejected' }, () => ) + .with(undefined, () => <>) + .exhaustive()} +
+
+ ); +} + +function PairingResponder({ pairingId }: { pairingId: number }) { + const libraries = useCachedLibraries(); + const [selectedLibrary, setSelectedLibrary] = useState( + libraries.data?.[0]?.uuid + ); + const pairingResponse = useBridgeMutation('p2p.pairingResponse'); + + return ( + <> + {selectedLibrary ? ( + + ) : ( +

No libraries. Uh oh!

+ )} +
+ + +
+ + ); +} + +function PairingLoading({ msg }: { msg?: string }) { + return ( +
+ + {msg &&

{msg}

} +
+ ); +} + +function CompletePairing() { + return ( +
+

Pairing Complete!

+
+ ); +} + +function PairingRejected() { + return ( +
+

Pairing Rejected By Remote!

+
+ ); +} diff --git a/interface/index.tsx b/interface/index.tsx index 66c177a97ad9..c02b0e1fb95a 100644 --- a/interface/index.tsx +++ b/interface/index.tsx @@ -10,7 +10,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import { RouterProvider, RouterProviderProps } from 'react-router-dom'; import { P2PContextProvider, useDebugState } from '@sd/client'; import ErrorFallback from './ErrorFallback'; -import { SpacedropUI } from './app/Spacedrop'; +import { P2P } from './app/p2p'; export { ErrorPage } from './ErrorFallback'; export * from './app'; @@ -49,8 +49,8 @@ export const SpacedriveInterface = (props: { router: RouterProviderProps['router return ( + - diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index cb2893fe0a20..a702475b765e 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -16,7 +16,7 @@ export type Procedures = { { key: "locations.indexer_rules.get", input: LibraryArgs, result: IndexerRule } | { key: "locations.indexer_rules.list", input: LibraryArgs, result: IndexerRule[] } | { key: "locations.indexer_rules.listForLocation", input: LibraryArgs, result: IndexerRule[] } | - { key: "locations.list", input: LibraryArgs, result: { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; node_id: number | null; node: Node | null }[] } | + { key: "locations.list", input: LibraryArgs, result: Location[] } | { key: "nodeState", input: never, result: NodeState } | { key: "nodes.listLocations", input: LibraryArgs, result: ExplorerItem[] } | { key: "preferences.get", input: LibraryArgs, result: LibraryPreferences } | @@ -60,7 +60,8 @@ export type Procedures = { { key: "locations.update", input: LibraryArgs, result: null } | { key: "nodes.edit", input: ChangeNodeNameArgs, result: null } | { key: "p2p.acceptSpacedrop", input: [string, string | null], result: null } | - { key: "p2p.pair", input: LibraryArgs, result: number } | + { key: "p2p.pair", input: PeerId, result: number } | + { key: "p2p.pairingResponse", input: [number, PairingDecision], result: null } | { key: "p2p.spacedrop", input: SpacedropArgs, result: string | null } | { key: "preferences.update", input: LibraryArgs, result: null } | { key: "tags.assign", input: LibraryArgs, result: null } | @@ -80,7 +81,7 @@ export type Procedures = { export type BuildInfo = { version: string; commit: string } -export type CRDTOperation = { node: string; timestamp: number; id: string; typ: CRDTOperationType } +export type CRDTOperation = { instance: string; timestamp: number; id: string; typ: CRDTOperationType } export type CRDTOperationType = SharedOperation | RelationOperation @@ -158,7 +159,12 @@ export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Faile */ export type LibraryArgs = { library_id: string; arg: T } -export type LibraryConfigWrapped = { uuid: string; config: SanitisedLibraryConfig } +/** + * LibraryConfig holds the configuration for a specific library. This is stored as a '{uuid}.sdlibrary' file. + */ +export type LibraryConfig = { name: LibraryName; description: string | null; instance_id: number } + +export type LibraryConfigWrapped = { uuid: string; config: LibraryConfig } export type LibraryName = string @@ -170,7 +176,7 @@ export type ListViewColumnSettings = { hide: boolean; size: number | null } export type ListViewSettings = { columns: { [key: string]: ListViewColumnSettings }; sort_col: string | null } -export type Location = { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; node_id: number | null } +export type Location = { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; instance_id: number | null } /** * `LocationCreateArgs` is the argument received from the client using `rspc` to create a new location. @@ -193,7 +199,7 @@ export type LocationUpdateArgs = { id: number; name: string | null; generate_pre export type LocationViewSettings = { layout: ExplorerLayout; list: ListViewSettings } -export type LocationWithIndexerRules = { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; node_id: number | null; indexer_rules: { indexer_rule: IndexerRule }[] } +export type LocationWithIndexerRules = { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; instance_id: number | null; indexer_rules: { indexer_rule: IndexerRule }[] } export type MaybeNot = T | { not: T } @@ -201,8 +207,6 @@ export type MaybeUndefined = null | null | T export type MediaData = { id: number; pixel_width: number | null; pixel_height: number | null; longitude: number | null; latitude: number | null; fps: number | null; capture_device_make: string | null; capture_device_model: string | null; capture_device_software: string | null; duration_seconds: number | null; codecs: string | null; streams: number | null } -export type Node = { id: number; pub_id: number[]; name: string; platform: number; date_created: string; identity: number[] | null; node_peer_id: string | null } - export type NodeState = ({ id: string; name: string; p2p_port: number | null; p2p_email: string | null; p2p_img_url: string | null }) & { data_path: string } export type Object = { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null } @@ -230,11 +234,15 @@ export type OptionalRange = { from: T | null; to: T | null } /** * TODO: P2P event for the frontend */ -export type P2PEvent = { type: "DiscoveredPeer"; peer_id: PeerId; metadata: PeerMetadata } | { type: "SpacedropRequest"; id: string; peer_id: PeerId; name: string } +export type P2PEvent = { type: "DiscoveredPeer"; peer_id: PeerId; metadata: PeerMetadata } | { type: "SpacedropRequest"; id: string; peer_id: PeerId; name: string } | { type: "PairingRequest"; id: number; name: string; os: OperatingSystem } | { type: "PairingProgress"; id: number; status: PairingStatus } + +export type PairingDecision = { decision: "accept"; libraryId: string } | { decision: "reject" } + +export type PairingStatus = { type: "EstablishingConnection" } | { type: "PairingRequested" } | { type: "PairingDecisionRequest" } | { type: "PairingInProgress"; data: { library_name: string; library_description: string | null } } | { type: "InitialSyncProgress"; data: number } | { type: "PairingComplete"; data: string } | { type: "PairingRejected" } export type PeerId = string -export type PeerMetadata = { name: string; operating_system: OperatingSystem | null; version: string | null; email: string | null; img_url: string | null } +export type PeerMetadata = { name: string; operating_system: OperatingSystem | null; version: string | null; email: string | null; img_url: string | null; instances: string[] } export type RelationOperation = { relation_item: string; relation_group: string; relation: string; data: RelationOperationData } @@ -250,8 +258,6 @@ export type RenameOne = { from_file_path_id: number; to: string } export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent" -export type SanitisedLibraryConfig = { name: LibraryName; description: string | null; node_id: string } - export type SanitisedNodeConfig = { id: string; name: string; p2p_port: number | null; p2p_email: string | null; p2p_img_url: string | null } export type SearchData = { cursor: number[] | null; items: T[] } diff --git a/packages/client/src/hooks/useFeatureFlag.ts b/packages/client/src/hooks/useFeatureFlag.tsx similarity index 54% rename from packages/client/src/hooks/useFeatureFlag.ts rename to packages/client/src/hooks/useFeatureFlag.tsx index eef416f1cd2c..ec48c100c8b4 100644 --- a/packages/client/src/hooks/useFeatureFlag.ts +++ b/packages/client/src/hooks/useFeatureFlag.tsx @@ -1,4 +1,5 @@ -import { useSnapshot } from 'valtio'; +import { useEffect } from 'react'; +import { subscribe, useSnapshot } from 'valtio'; import { valtioPersist } from '../lib/valito'; export const features = ['spacedrop', 'p2pPairing'] as const; @@ -18,6 +19,10 @@ export function useFeatureFlag(flag: FeatureFlag | FeatureFlag[]) { return Array.isArray(flag) ? flag.every((f) => isEnabled(f)) : isEnabled(flag); } +export function useOnFeatureFlagsChange(callback: (flags: FeatureFlag[]) => void) { + useEffect(() => subscribe(featureFlagState, () => callback(featureFlagState.enabled))); +} + export const isEnabled = (flag: FeatureFlag) => featureFlagState.enabled.find((ff) => flag === ff); export function toggleFeatureFlag(flags: FeatureFlag | FeatureFlag[]) { @@ -26,9 +31,29 @@ export function toggleFeatureFlag(flags: FeatureFlag | FeatureFlag[]) { } flags.forEach((f) => { if (!featureFlagState.enabled.find((ff) => f === ff)) { + if (f === 'p2pPairing') { + alert( + 'Pairing will render your database broken and it WILL need to be reset! Use at your own risk!' + ); + } + featureFlagState.enabled.push(f); } else { featureFlagState.enabled = featureFlagState.enabled.filter((ff) => f !== ff); } }); } + +// Render component only when feature flag is enabled +export function withFeatureFlag( + flag: FeatureFlag | FeatureFlag[], + Component: React.FunctionComponent, + fallback: React.ReactNode = null +): React.FunctionComponent { + // @ts-expect-error + return (props) => { + const enabled = useFeatureFlag(flag); + // eslint-disable-next-line react-hooks/rules-of-hooks + return enabled ? : fallback; + }; +} diff --git a/packages/client/src/hooks/useP2PEvents.tsx b/packages/client/src/hooks/useP2PEvents.tsx index f72e11fd4121..6e3142db0262 100644 --- a/packages/client/src/hooks/useP2PEvents.tsx +++ b/packages/client/src/hooks/useP2PEvents.tsx @@ -1,23 +1,70 @@ -import { PropsWithChildren, createContext, useContext, useState } from 'react'; -import { PeerMetadata } from '../core'; +import { + MutableRefObject, + PropsWithChildren, + createContext, + useContext, + useEffect, + useRef, + useState +} from 'react'; +import { P2PEvent, PairingStatus, PeerMetadata } from '../core'; import { useBridgeSubscription } from '../rspc'; -const Context = createContext>(null as any); +type Context = { + discoveredPeers: Map; + pairingStatus: Map; + events: MutableRefObject; +}; + +const Context = createContext(null as any); export function P2PContextProvider({ children }: PropsWithChildren) { + const events = useRef(new EventTarget()); const [[discoveredPeers], setDiscoveredPeer] = useState([new Map()]); + const [[pairingStatus], setPairingStatus] = useState([new Map()]); useBridgeSubscription(['p2p.events'], { onData(data) { + events.current.dispatchEvent(new CustomEvent('p2p-event', { detail: data })); + if (data.type === 'DiscoveredPeer') { setDiscoveredPeer([discoveredPeers.set(data.peer_id, data.metadata)]); + } else if (data.type === 'PairingProgress') { + setPairingStatus([pairingStatus.set(data.id, data.status)]); } } }); - return {children}; + return ( + + {children} + + ); } export function useDiscoveredPeers() { - return useContext(Context); + return useContext(Context).discoveredPeers; +} + +export function usePairingStatus(pairing_id: number) { + return useContext(Context).pairingStatus.get(pairing_id); +} + +export function useP2PEvents(fn: (event: P2PEvent) => void) { + const ctx = useContext(Context); + + useEffect(() => { + const handler = (e: Event) => { + fn((e as any).detail); + }; + + ctx.events.current.addEventListener('p2p-event', handler); + return () => ctx.events.current.removeEventListener('p2p-event', handler); + }); } diff --git a/packages/ui/package.json b/packages/ui/package.json index 51536e94712d..8428f06f4cee 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -43,6 +43,7 @@ "react-loading-icons": "^1.1.0", "react-router-dom": "6.9.0", "tailwindcss-radix": "^2.6.0", + "ts-pattern": "^5.0.1", "use-debounce": "^9.0.4", "zod": "^3.21.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65a0758a91ec..6e0030ae1a2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -968,6 +968,9 @@ importers: tailwindcss-radix: specifier: ^2.6.0 version: 2.6.0 + ts-pattern: + specifier: ^5.0.1 + version: 5.0.1 use-debounce: specifier: ^9.0.4 version: 9.0.4(react@18.2.0) @@ -23003,6 +23006,10 @@ packages: resolution: {integrity: sha512-pefrkcd4lmIVR0LA49Imjf9DYLK8vtWhqBPA3Ya1ir8xCW0O2yjL9dsCVvI7pCodLC5q7smNpEtDR2yVulQxOg==} dev: false + /ts-pattern@5.0.1: + resolution: {integrity: sha512-ZyNm28Lsg34Co5DS3e9DVyjlX2Y+2exkI4jqTKyU+9/OL6Y2fKOOuL8i+7no71o74C6mVS+UFoP3ekM3iCT1HQ==} + dev: false + /tsconfck@2.1.1(typescript@5.0.4): resolution: {integrity: sha512-ZPCkJBKASZBmBUNqGHmRhdhM8pJYDdOXp4nRgj/O0JwUwsMq50lCDRQP/M5GBNAA0elPrq4gAeu4dkaVCuKWww==} engines: {node: ^14.13.1 || ^16 || >=18} From e31b03c248dd38975b467adafc11f898fde09d30 Mon Sep 17 00:00:00 2001 From: Utku <74243531+utkubakir@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:38:59 +0300 Subject: [PATCH 09/18] [MOB-19] Search (#1085) * search wip * hook up search --- .../src/components/settings/SettingsItem.tsx | 6 +- apps/mobile/src/screens/Search.tsx | 56 +++++++++++++++---- apps/mobile/src/screens/Tag.tsx | 6 -- apps/mobile/src/stores/explorerStore.ts | 16 +++++- 4 files changed, 62 insertions(+), 22 deletions(-) diff --git a/apps/mobile/src/components/settings/SettingsItem.tsx b/apps/mobile/src/components/settings/SettingsItem.tsx index e1044b5485c9..a7bfebbe82d1 100644 --- a/apps/mobile/src/components/settings/SettingsItem.tsx +++ b/apps/mobile/src/components/settings/SettingsItem.tsx @@ -13,15 +13,15 @@ export function SettingsItem(props: SettingsItemProps) { return ( - + {props.leftIcon && props.leftIcon({ size: 20, color: tw.color('ink'), style: tw`mr-3` })} - {props.title} + {props.title} {props.rightArea ? ( props.rightArea ) : ( - + )} diff --git a/apps/mobile/src/screens/Search.tsx b/apps/mobile/src/screens/Search.tsx index 04f7393ae740..17f761cbcfc2 100644 --- a/apps/mobile/src/screens/Search.tsx +++ b/apps/mobile/src/screens/Search.tsx @@ -1,17 +1,51 @@ import { MagnifyingGlass } from 'phosphor-react-native'; -import { useState } from 'react'; +import { Suspense, useDeferredValue, useMemo, useState } from 'react'; import { ActivityIndicator, Pressable, Text, TextInput, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Button } from '~/components/primitive/Button'; +import { getExplorerItemData, useLibraryQuery } from '@sd/client'; +import Explorer from '~/components/explorer/Explorer'; import { tw, twStyle } from '~/lib/tailwind'; import { RootStackScreenProps } from '~/navigation'; +import { getExplorerStore } from '~/stores/explorerStore'; + +// TODO: Animations! const SearchScreen = ({ navigation }: RootStackScreenProps<'Search'>) => { const { top } = useSafeAreaInsets(); const [loading, setLoading] = useState(false); - // TODO: Animations! + const [search, setSearch] = useState(''); + const deferredSearch = useDeferredValue(search); + + const query = useLibraryQuery( + [ + 'search.paths', + { + // ...args, + filter: { + search: deferredSearch + } + } + ], + { + suspense: true, + enabled: !!deferredSearch, + onSuccess: () => getExplorerStore().resetNewThumbnails() + } + ); + + const items = useMemo(() => { + const items = query.data?.items; + + // Mobile does not thave media layout + // if (explorerStore.layoutMode !== 'media') return items; + + return items?.filter((item) => { + const { kind } = getExplorerItemData(item); + return kind === 'Video' || kind === 'Image'; + }); + }, [query.data]); return ( @@ -32,12 +66,14 @@ const SearchScreen = ({ navigation }: RootStackScreenProps<'Search'>) => { )} setSearch(t)} + style={tw`flex-1 text-sm font-medium text-ink`} + placeholder="Search" clearButtonMode="never" // can't change the color?? underlineColorAndroid="transparent" placeholderTextColor={tw.color('ink-dull')} - style={tw`flex-1 text-sm font-medium text-ink`} - textContentType={'none'} + textContentType="none" autoFocus autoCapitalize="none" autoCorrect={false} @@ -50,10 +86,10 @@ const SearchScreen = ({ navigation }: RootStackScreenProps<'Search'>) => { {/* Content */} - - + + }> + + ); diff --git a/apps/mobile/src/screens/Tag.tsx b/apps/mobile/src/screens/Tag.tsx index c585288a522d..fbe60d79cb01 100644 --- a/apps/mobile/src/screens/Tag.tsx +++ b/apps/mobile/src/screens/Tag.tsx @@ -24,11 +24,5 @@ export default function TagScreen({ navigation, route }: SharedScreenProps<'Tag' }); }, [tag.data?.name, navigation]); - useEffect(() => { - // no location, this is tags! - // getExplorerStore().locationId = id; - // getExplorerStore().path = path; - }, [id]); - return ; } diff --git a/apps/mobile/src/stores/explorerStore.ts b/apps/mobile/src/stores/explorerStore.ts index f6772109fc72..6e26ddfc15cd 100644 --- a/apps/mobile/src/stores/explorerStore.ts +++ b/apps/mobile/src/stores/explorerStore.ts @@ -1,4 +1,5 @@ import { proxy, useSnapshot } from 'valtio'; +import { proxySet } from 'valtio/utils'; import { resetStore } from '@sd/client'; // TODO: Add "media" @@ -13,14 +14,23 @@ const state = { // Using gridNumColumns instead of fixed size. We dynamically calculate the item size. gridNumColumns: 3, listItemSize: 65, - newThumbnails: {} as Record + newThumbnails: proxySet() as Set }; +function flattenThumbnailKey(thumbKey: string[]) { + return thumbKey.join('/'); +} + const explorerStore = proxy({ ...state, reset: () => resetStore(explorerStore, state), - addNewThumbnail: (cas_id: string) => { - explorerStore.newThumbnails[cas_id] = true; + addNewThumbnail: (thumbKey: string[]) => { + explorerStore.newThumbnails.add(flattenThumbnailKey(thumbKey)); + }, + // this should be done when the explorer query is refreshed + // prevents memory leak + resetNewThumbnails: () => { + explorerStore.newThumbnails.clear(); } }); From 1a5d2898c250828531a3ef896fc6cb6d5e261479 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Wed, 12 Jul 2023 09:43:27 +0200 Subject: [PATCH 10/18] [ENG-537] Notification system (#805) * `Notification` struct * Manage dem notifications * a dash of rspc * `read` field * `handle_notification_callback` * some Prisma * TS-centric notifications backend * `useNotifications` hook * Notification test buttons --- .../migration.sql | 7 + core/prisma/schema.prisma | 10 + core/src/api/mod.rs | 4 +- core/src/api/notifications.rs | 179 ++++++++++++++++++ core/src/lib.rs | 55 +++++- core/src/library/library.rs | 52 ++++- core/src/library/manager.rs | 4 + core/src/node/config.rs | 15 +- .../Layout/Sidebar/DebugPopover.tsx | 14 ++ interface/index.tsx | 10 +- packages/client/src/core.ts | 19 ++ packages/client/src/hooks/index.ts | 1 + .../client/src/hooks/useNotifications.tsx | 31 +++ 13 files changed, 388 insertions(+), 13 deletions(-) create mode 100644 core/prisma/migrations/20230712063345_notifications/migration.sql create mode 100644 core/src/api/notifications.rs create mode 100644 packages/client/src/hooks/useNotifications.tsx diff --git a/core/prisma/migrations/20230712063345_notifications/migration.sql b/core/prisma/migrations/20230712063345_notifications/migration.sql new file mode 100644 index 000000000000..3d250b5d3d6d --- /dev/null +++ b/core/prisma/migrations/20230712063345_notifications/migration.sql @@ -0,0 +1,7 @@ +-- CreateTable +CREATE TABLE "notification" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "read" BOOLEAN NOT NULL DEFAULT false, + "data" BLOB NOT NULL, + "expires_at" DATETIME +); diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma index d77fe56c62b5..7a0de58faf2e 100644 --- a/core/prisma/schema.prisma +++ b/core/prisma/schema.prisma @@ -483,3 +483,13 @@ model Preference { @@map("preference") } + +model Notification { + id Int @id @default(autoincrement()) + read Boolean @default(false) + // Enum: crate::api::notifications::NotificationData + data Bytes + expires_at DateTime? + + @@map("notification") +} diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs index 3bc083bd8af5..f96920460bfd 100644 --- a/core/src/api/mod.rs +++ b/core/src/api/mod.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use utils::{InvalidRequests, InvalidateOperationEvent}; #[allow(non_upper_case_globals)] -pub(self) const R: Rspc = Rspc::new(); +pub(crate) const R: Rspc = Rspc::new(); pub type Ctx = Arc; pub type Router = rspc::Router; @@ -27,6 +27,7 @@ mod keys; mod libraries; mod locations; mod nodes; +pub mod notifications; mod p2p; mod preferences; mod search; @@ -84,6 +85,7 @@ pub(crate) fn mount() -> Arc { .merge("nodes.", nodes::mount()) .merge("sync.", sync::mount()) .merge("preferences.", preferences::mount()) + .merge("notifications.", notifications::mount()) .merge("invalidation.", utils::mount_invalidate()) .build( #[allow(clippy::let_and_return)] diff --git a/core/src/api/notifications.rs b/core/src/api/notifications.rs new file mode 100644 index 000000000000..3f38c05865de --- /dev/null +++ b/core/src/api/notifications.rs @@ -0,0 +1,179 @@ +use async_stream::stream; +use chrono::{DateTime, Utc}; +use futures::future::join_all; +use rspc::{alpha::AlphaRouter, ErrorCode}; +use sd_prisma::prisma::notification; +use serde::{Deserialize, Serialize}; +use specta::Type; +use uuid::Uuid; + +use crate::api::{Ctx, R}; + +use super::utils::library; + +/// Represents a single notification. +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct Notification { + #[serde(flatten)] + pub id: NotificationId, + pub data: NotificationData, + pub read: bool, + pub expires: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Type, PartialEq, Eq)] +#[serde(tag = "type", content = "id", rename_all = "camelCase")] +pub enum NotificationId { + Library(Uuid, u32), + Node(u32), +} + +/// Represents the data of a single notification. +/// This data is used by the frontend to properly display the notification. +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub enum NotificationData { + PairingRequest { id: Uuid, pairing_id: u16 }, + Test, +} + +pub(crate) fn mount() -> AlphaRouter { + R.router() + .procedure("get", { + R.query(|ctx, _: ()| async move { + let mut notifications = ctx.config.get().await.notifications; + for lib_notifications in join_all( + ctx.library_manager + .get_all_libraries() + .await + .into_iter() + .map(|library| async move { + library + .db + .notification() + .find_many(vec![]) + .exec() + .await + .map_err(|err| { + rspc::Error::new( + ErrorCode::InternalServerError, + format!( + "Failed to get notifications for library '{}': {}", + library.id, err + ), + ) + })? + .into_iter() + .map(|n| { + Ok(Notification { + id: NotificationId::Library(library.id, n.id as u32), + data: rmp_serde::from_slice(&n.data).map_err(|err| { + rspc::Error::new( + ErrorCode::InternalServerError, + format!( + "Failed to get notifications for library '{}': {}", + library.id, err + ), + ) + })?, + read: false, + expires: n.expires_at.map(Into::into), + }) + }) + .collect::, rspc::Error>>() + }), + ) + .await + { + notifications.extend(lib_notifications?); + } + + Ok(notifications) + }) + }) + .procedure("dismiss", { + R.query(|ctx, id: NotificationId| async move { + match id { + NotificationId::Library(library_id, id) => { + ctx.library_manager + .get_library(library_id) + .await + .ok_or_else(|| { + rspc::Error::new(ErrorCode::NotFound, "Library not found".into()) + })? + .db + .notification() + .delete_many(vec![notification::id::equals(id as i32)]) + .exec() + .await + .map_err(|err| { + rspc::Error::new(ErrorCode::InternalServerError, err.to_string()) + })?; + } + NotificationId::Node(id) => { + ctx.config + .write(|mut cfg| { + cfg.notifications + .retain(|n| n.id != NotificationId::Node(id)); + }) + .await + .map_err(|err| { + rspc::Error::new(ErrorCode::InternalServerError, err.to_string()) + })?; + } + } + + Ok(()) + }) + }) + .procedure("dismissAll", { + R.query(|ctx, _: ()| async move { + ctx.config + .write(|mut cfg| { + cfg.notifications = vec![]; + }) + .await + .map_err(|err| { + rspc::Error::new(ErrorCode::InternalServerError, err.to_string()) + })?; + + join_all( + ctx.library_manager + .get_all_libraries() + .await + .into_iter() + .map(|library| async move { + library.db.notification().delete_many(vec![]).exec().await + }), + ) + .await + .into_iter() + .collect::, _>>()?; + + Ok(()) + }) + }) + .procedure("listen", { + R.subscription(|ctx, _: ()| async move { + let mut sub = ctx.notifications.subscribe(); + + stream! { + while let Ok(notification) = sub.recv().await { + yield notification; + } + } + }) + }) + .procedure("test", { + R.mutation(|ctx, _: ()| async move { + ctx.emit_notification(NotificationData::Test, None).await; + }) + }) + .procedure("testLibrary", { + R.with2(library()) + .mutation(|(_, library), _: ()| async move { + library + .emit_notification(NotificationData::Test, None) + .await; + }) + }) +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 597842bf0c57..02b98dcfbb44 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,11 +9,16 @@ use crate::{ p2p::P2PManager, }; +use api::notifications::{Notification, NotificationData, NotificationId}; +use chrono::{DateTime, Utc}; pub use sd_prisma::*; use std::{ path::{Path, PathBuf}, - sync::Arc, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, + }, }; use thiserror::Error; @@ -44,6 +49,7 @@ pub struct NodeContext { pub job_manager: Arc, pub location_manager: Arc, pub event_bus_tx: broadcast::Sender, + pub notifications: Arc, } pub struct Node { @@ -54,7 +60,7 @@ pub struct Node { job_manager: Arc, p2p: Arc, event_bus: (broadcast::Sender, broadcast::Receiver), - // peer_request: tokio::sync::Mutex>, + notifications: Arc, } impl Node { @@ -79,6 +85,8 @@ impl Node { debug!("Initialised 'JobManager'..."); + let notifications = NotificationManager::new(); + let location_manager = LocationManager::new(); debug!("Initialised 'LocationManager'..."); let library_manager = LibraryManager::new( @@ -89,6 +97,7 @@ impl Node { location_manager: location_manager.clone(), // p2p: p2p.clone(), event_bus_tx: event_bus.0.clone(), + notifications: notifications.clone(), }, ) .await?; @@ -112,7 +121,7 @@ impl Node { job_manager, p2p, event_bus, - // peer_request: tokio::sync::Mutex::new(None), + notifications, }; info!("Spacedrive online."); @@ -200,6 +209,46 @@ impl Node { self.p2p.shutdown().await; info!("Spacedrive Core shutdown successful!"); } + + pub async fn emit_notification(&self, data: NotificationData, expires: Option>) { + let notification = Notification { + id: NotificationId::Node(self.notifications.1.fetch_add(1, Ordering::SeqCst)), + data, + read: false, + expires, + }; + + match self + .config + .write(|mut cfg| cfg.notifications.push(notification.clone())) + .await + { + Ok(_) => { + self.notifications.0.send(notification).ok(); + } + Err(err) => { + error!("Error saving notification to config: {:?}", err); + } + } + } +} + +pub struct NotificationManager( + // Keep this private and use `Node::emit_notification` or `Library::emit_notification` instead. + broadcast::Sender, + // Counter for `NotificationId::Node(_)`. NotificationId::Library(_, _)` is autogenerated by the DB. + AtomicU32, +); + +impl NotificationManager { + pub fn new() -> Arc { + let (tx, _) = broadcast::channel(30); + Arc::new(Self(tx, AtomicU32::new(0))) + } + + pub fn subscribe(&self) -> broadcast::Receiver { + self.0.subscribe() + } } /// Error type for Node related errors. diff --git a/core/src/library/library.rs b/core/src/library/library.rs index 68fe467dd801..e56adfec5ba2 100644 --- a/core/src/library/library.rs +++ b/core/src/library/library.rs @@ -1,5 +1,8 @@ use crate::{ - api::CoreEvent, + api::{ + notifications::{Notification, NotificationData, NotificationId}, + CoreEvent, + }, location::{ file_path_helper::{file_path_to_full_path, IsolatedFilePathData}, LocationManager, @@ -19,7 +22,9 @@ use std::{ sync::Arc, }; +use chrono::{DateTime, Utc}; use sd_p2p::spacetunnel::Identity; +use sd_prisma::prisma::notification; use tokio::{fs, io}; use tracing::warn; use uuid::Uuid; @@ -126,4 +131,49 @@ impl Library { Ok(out) } + + /// Create a new notification which will be stored into the DB and emitted to the UI. + pub async fn emit_notification(&self, data: NotificationData, expires: Option>) { + let result = match self + .db + .notification() + .create( + match rmp_serde::to_vec(&data).map_err(|err| err.to_string()) { + Ok(data) => data, + Err(err) => { + warn!( + "Failed to serialize notification data for library '{}': {}", + self.id, err + ); + return; + } + }, + expires + .map(|e| vec![notification::expires_at::set(Some(e.fixed_offset()))]) + .unwrap_or_else(Vec::new), + ) + .exec() + .await + { + Ok(result) => result, + Err(err) => { + warn!( + "Failed to create notification in library '{}': {}", + self.id, err + ); + return; + } + }; + + self.node_context + .notifications + .0 + .send(Notification { + id: NotificationId::Library(self.id, result.id as u32), + data, + read: false, + expires, + }) + .ok(); + } } diff --git a/core/src/library/manager.rs b/core/src/library/manager.rs index ae854e0b36f3..868db9ccca58 100644 --- a/core/src/library/manager.rs +++ b/core/src/library/manager.rs @@ -271,6 +271,10 @@ impl LibraryManager { Ok(LibraryConfigWrapped { uuid: id, config }) } + pub(crate) async fn get_all_libraries(&self) -> Vec { + self.libraries.read().await.clone() + } + pub(crate) async fn get_all_libraries_config(&self) -> Vec { self.libraries .read() diff --git a/core/src/node/config.rs b/core/src/node/config.rs index 7fe27831b6d3..f25cf706b0b8 100644 --- a/core/src/node/config.rs +++ b/core/src/node/config.rs @@ -9,20 +9,26 @@ use std::{ use tokio::sync::{RwLock, RwLockWriteGuard}; use uuid::Uuid; -use crate::util::migrator::{Migrate, MigratorError}; +use crate::{ + api::notifications::Notification, + util::migrator::{Migrate, MigratorError}, +}; /// NODE_STATE_CONFIG_NAME is the name of the file which stores the NodeState pub const NODE_STATE_CONFIG_NAME: &str = "node_state.sdconfig"; /// NodeConfig is the configuration for a node. This is shared between all libraries and is stored in a JSON file on disk. -#[derive(Debug, Serialize, Deserialize, Clone)] // If you are adding `specta::Type` on this your probably about to leak the P2P private key +#[derive(Debug, Clone, Serialize, Deserialize)] // If you are adding `specta::Type` on this your probably about to leak the P2P private key pub struct NodeConfig { /// id is a unique identifier for the current node. Each node has a public identifier (this one) and is given a local id for each library (done within the library code). pub id: Uuid, /// name is the display name of the current node. This is set by the user and is shown in the UI. // TODO: Length validation so it can fit in DNS record pub name: String, - // the port this node uses for peer to peer communication. By default a random free port will be chosen each time the application is started. + /// the port this node uses for peer to peer communication. By default a random free port will be chosen each time the application is started. pub p2p_port: Option, + /// core level notifications + #[serde(default)] + pub notifications: Vec, /// The p2p identity keypair for this node. This is used to identify the node on the network. /// This keypair does effectively nothing except for provide libp2p with a stable peer_id. pub keypair: Keypair, @@ -78,6 +84,7 @@ impl Migrate for NodeConfig { keypair: Keypair::generate(), p2p_email: None, p2p_img_url: None, + notifications: vec![], }) } @@ -109,6 +116,7 @@ impl Default for NodeConfig { keypair: Keypair::generate(), p2p_email: None, p2p_img_url: None, + notifications: Vec::new(), } } } @@ -139,7 +147,6 @@ impl NodeConfigManager { } /// write allows the user to update the configuration. This is done in a closure while a Mutex lock is held so that the user can't cause a race condition if the config were to be updated in multiple parts of the app at the same time. - #[allow(unused)] pub(crate) async fn write)>( &self, mutation_fn: F, diff --git a/interface/app/$libraryId/Layout/Sidebar/DebugPopover.tsx b/interface/app/$libraryId/Layout/Sidebar/DebugPopover.tsx index 474d64018be9..13181dbefef7 100644 --- a/interface/app/$libraryId/Layout/Sidebar/DebugPopover.tsx +++ b/interface/app/$libraryId/Layout/Sidebar/DebugPopover.tsx @@ -4,6 +4,7 @@ import { getDebugState, isEnabled, toggleFeatureFlag, + useBridgeMutation, useBridgeQuery, useDebugState, useFeatureFlags, @@ -109,6 +110,7 @@ export default () => { + {/* {platform.showDevtools && ( ); } + +function TestNotifications() { + const coreNotif = useBridgeMutation(['notifications.test']); + const libraryNotif = useLibraryMutation(['notifications.testLibrary']); + + return ( + + + + + ); +} diff --git a/interface/index.tsx b/interface/index.tsx index c02b0e1fb95a..5367d220ead0 100644 --- a/interface/index.tsx +++ b/interface/index.tsx @@ -8,7 +8,7 @@ import duration from 'dayjs/plugin/duration'; import relativeTime from 'dayjs/plugin/relativeTime'; import { ErrorBoundary } from 'react-error-boundary'; import { RouterProvider, RouterProviderProps } from 'react-router-dom'; -import { P2PContextProvider, useDebugState } from '@sd/client'; +import { NotificationContextProvider, P2PContextProvider, useDebugState } from '@sd/client'; import ErrorFallback from './ErrorFallback'; import { P2P } from './app/p2p'; @@ -49,9 +49,11 @@ export const SpacedriveInterface = (props: { router: RouterProviderProps['router return ( - - - + + + + + ); diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index a702475b765e..bb6c65394a9a 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -19,6 +19,9 @@ export type Procedures = { { key: "locations.list", input: LibraryArgs, result: Location[] } | { key: "nodeState", input: never, result: NodeState } | { key: "nodes.listLocations", input: LibraryArgs, result: ExplorerItem[] } | + { key: "notifications.dismiss", input: NotificationId, result: null } | + { key: "notifications.dismissAll", input: never, result: null } | + { key: "notifications.get", input: never, result: Notification[] } | { key: "preferences.get", input: LibraryArgs, result: LibraryPreferences } | { key: "search.objects", input: LibraryArgs, result: SearchData } | { key: "search.paths", input: LibraryArgs, result: SearchData } | @@ -59,6 +62,8 @@ export type Procedures = { { key: "locations.relink", input: LibraryArgs, result: null } | { key: "locations.update", input: LibraryArgs, result: null } | { key: "nodes.edit", input: ChangeNodeNameArgs, result: null } | + { key: "notifications.test", input: never, result: null } | + { key: "notifications.testLibrary", input: LibraryArgs, result: null } | { key: "p2p.acceptSpacedrop", input: [string, string | null], result: null } | { key: "p2p.pair", input: PeerId, result: number } | { key: "p2p.pairingResponse", input: [number, PairingDecision], result: null } | @@ -74,6 +79,7 @@ export type Procedures = { { key: "jobs.progress", input: LibraryArgs, result: JobProgressEvent } | { key: "locations.online", input: never, result: number[][] } | { key: "locations.quickRescan", input: LibraryArgs, result: null } | + { key: "notifications.listen", input: never, result: Notification } | { key: "p2p.events", input: never, result: P2PEvent } | { key: "p2p.spacedropProgress", input: string, result: number } | { key: "sync.newMessage", input: LibraryArgs, result: CRDTOperation } @@ -209,6 +215,19 @@ export type MediaData = { id: number; pixel_width: number | null; pixel_height: export type NodeState = ({ id: string; name: string; p2p_port: number | null; p2p_email: string | null; p2p_img_url: string | null }) & { data_path: string } +/** + * Represents a single notification. + */ +export type Notification = ({ type: "library"; id: [string, number] } | { type: "node"; id: number }) & { data: NotificationData; read: boolean; expires: string | null } + +/** + * Represents the data of a single notification. + * This data is used by the frontend to properly display the notification. + */ +export type NotificationData = { PairingRequest: { id: string; pairing_id: number } } | "Test" + +export type NotificationId = { type: "library"; id: [string, number] } | { type: "node"; id: number } + export type Object = { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null } export type ObjectFilterArgs = { favorite?: boolean | null; hidden?: ObjectHiddenFilter; dateAccessed?: MaybeNot | null; kind?: number[]; tags?: number[]; category?: Category | null } diff --git a/packages/client/src/hooks/index.ts b/packages/client/src/hooks/index.ts index c89d512c49d8..49588facbe3d 100644 --- a/packages/client/src/hooks/index.ts +++ b/packages/client/src/hooks/index.ts @@ -8,3 +8,4 @@ export * from './useP2PEvents'; export * from './usePlausible'; export * from './useTelemetryState'; export * from './useThemeStore'; +export * from './useNotifications'; diff --git a/packages/client/src/hooks/useNotifications.tsx b/packages/client/src/hooks/useNotifications.tsx new file mode 100644 index 000000000000..252b62ae5574 --- /dev/null +++ b/packages/client/src/hooks/useNotifications.tsx @@ -0,0 +1,31 @@ +import { PropsWithChildren, createContext, useContext, useState } from 'react'; +import { Notification } from '../core'; +import { useBridgeSubscription } from '../rspc'; + +type Context = { + notifications: Set; +}; + +const Context = createContext(null as any); + +export function NotificationContextProvider({ children }: PropsWithChildren) { + const [[notifications], setNotifications] = useState([new Set()]); + + useBridgeSubscription(['notifications.listen'], { + onData(data) { + setNotifications([notifications.add(data)]); + } + }); + + console.log(notifications); // TODO + + return ( + + {children} + + ); +} From 1b966b72c405c7cefa47137d4cb0f674e857343f Mon Sep 17 00:00:00 2001 From: Willem-Jaap <67187467+Willem-Jaap@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:16:31 +0200 Subject: [PATCH 11/18] fix(landing): correct aspect ratio on images of teammembers which makes sure they are not stretched (#1094) Co-authored-by: Utku <74243531+utkubakir@users.noreply.github.com> --- apps/landing/src/components/TeamMember.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/landing/src/components/TeamMember.tsx b/apps/landing/src/components/TeamMember.tsx index 31eccde40d4e..2b94540aa394 100644 --- a/apps/landing/src/components/TeamMember.tsx +++ b/apps/landing/src/components/TeamMember.tsx @@ -54,7 +54,7 @@ export function TeamMember(props: TeamMemberProps) { alt={`Portrait of ${props.name}`} width={size} height={size} - className={clsx('m-0 inline-flex rounded-md', { + className={clsx('m-0 inline-flex rounded-md object-cover', { '!xs:w-36 !xs:h-36 !sm:w-40 !sm:h-40 h-32 w-32': !props.investmentRound, 'lg:h-28 lg:w-28': props.investmentRound })} From 4642808bd2601f98c641099dffc7a77930ea23f2 Mon Sep 17 00:00:00 2001 From: nikec <43032218+niikeec@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:34:52 +0200 Subject: [PATCH 12/18] [ENG-913] Fix quick preview showing wrong file (#1096) Fix quick preview --- .../$libraryId/Explorer/FilePath/Thumb.tsx | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/interface/app/$libraryId/Explorer/FilePath/Thumb.tsx b/interface/app/$libraryId/Explorer/FilePath/Thumb.tsx index b53811055b9d..effb191fefe2 100644 --- a/interface/app/$libraryId/Explorer/FilePath/Thumb.tsx +++ b/interface/app/$libraryId/Explorer/FilePath/Thumb.tsx @@ -1,7 +1,7 @@ import { getIcon, iconNames } from '@sd/assets/util'; import clsx from 'clsx'; import { ImgHTMLAttributes, memo, useEffect, useLayoutEffect, useRef, useState } from 'react'; -import { ExplorerItem, getItemLocation, useLibraryContext } from '@sd/client'; +import { ExplorerItem, getItemFilePath, getItemLocation, useLibraryContext } from '@sd/client'; import { PDFViewer } from '~/components'; import { useCallbackToWatchResize, useIsDark } from '~/hooks'; import { usePlatform } from '~/util/Platform'; @@ -116,6 +116,7 @@ function FileThumb({ size, cover, ...props }: ThumbProps) { const platform = usePlatform(); const itemData = useExplorerItemData(props.data); const locationData = getItemLocation(props.data); + const filePath = getItemFilePath(props.data); const { library } = useLibraryContext(); const [src, setSrc] = useState(null); const [loaded, setLoaded] = useState(false); @@ -129,12 +130,12 @@ function FileThumb({ size, cover, ...props }: ThumbProps) { setSrc(null); setLoaded(false); - if (props.loadOriginal) { + if (locationData) { + setThumbType(ThumbType.Location); + } else if (props.loadOriginal) { setThumbType(ThumbType.Original); } else if (itemData.hasLocalThumbnail) { setThumbType(ThumbType.Thumbnail); - } else if (locationData) { - setThumbType(ThumbType.Location); } else { setThumbType(ThumbType.Icon); } @@ -159,7 +160,7 @@ function FileThumb({ size, cover, ...props }: ThumbProps) { platform.getFileUrl( library.uuid, locationId, - props.data.item.id, + filePath?.id || props.data.item.id, // Workaround Linux webview not supporting playing video and audio through custom protocol urls kind == 'Video' || kind == 'Audio' ) @@ -182,7 +183,16 @@ function FileThumb({ size, cover, ...props }: ThumbProps) { if (isDir !== null) setSrc(getIcon(kind, isDark, extension, isDir)); break; } - }, [props.data.item.id, isDark, library.uuid, itemData, platform, thumbType, parent]); + }, [ + props.data.item.id, + filePath?.id, + isDark, + library.uuid, + itemData, + platform, + thumbType, + parent + ]); const onLoad = () => setLoaded(true); From 1ebe91acba9a5e7b06875e2aaea2192b88870dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Thu, 13 Jul 2023 23:35:54 -0300 Subject: [PATCH 13/18] Fix server CI (#1091) * Fix typo making server CI fail * Use redhat buildah and push action to handle the server docker image creating and publishing * Dumb typo * Update buildah * Forgot sudo * Replace env with github action output --- .github/workflows/server.yml | 76 ++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index ad0f315a7021..d0eb68c87d5c 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -14,49 +14,49 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - with: - install: true - platforms: linux/amd64 - driver-opts: | - image=moby/buildkit:master - network=host + - name: Update buildah + shell: bash + run: | + wget -O- 'https://github.com/nicholasdille/buildah-static/releases/download/v1.30.0/buildah-amd64.tar.gz' \ + | sudo tar -xzf- -C /usr/ - name: Determine image name & tag + id: image_info shell: bash run: | if [ "$GITHUB_EVENT_NAME" == "release" ]; then - export IMAGE_TAG=${GITHUB_REF##*/} + IMAGE_TAG=${GITHUB_REF##*/} else - export IMAGE_TAG=$(git rev-parse --short "$GITHUB_SHA") + IMAGE_TAG=$(git rev-parse --short "$GITHUB_SHA") fi - export GITHUB_REPOSITORY_LOWER=$(echo $GITHUB_REPOSITORY | awk '{print tolower($0)}') - export IMAGE_NAME="ghcr.io/$GITHUB_REPOSITORY_LOWER/server" - echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV - echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV - echo "Building $IMAGE_NAME:$IMAGE_TAG" - - - name: Build Docker image - shell: bash - run: | - docker build ./apps/server/docker --tag $IMAGE_NAME:$IMAGE_TAG --build-arg="REPO=${GITHUB_REPOSITORY}" --build-arg="REPO_REF=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" - - - name: Push Docker image - shell: bash - run: | - docker push $IMAGE_NAME:$IMAGE_TAG + GITHUB_REPOSITORY_LOWER=$(echo $GITHUB_REPOSITORY | awk '{print tolower($0)}') + IMAGE_NAME="ghcr.io/$GITHUB_REPOSITORY_LOWER/server" - - name: Tag & push image as latest staging image - if: ${{ github.event_name != 'release' }} - shell: bash - run: | - docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:staging - docker push $IMAGE_NAME:staging - - - name: Tag & push image as latest production image - if: ${{ github.event_name == 'release' }} - shell: bash - run: | - docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:production - docker push $IMAGE_NAME:production + echo "Building $IMAGE_NAME:$IMAGE_TAG" + echo "tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT + echo "name=${IMAGE_NAME}" >> $GITHUB_OUTPUT + echo "repo=${GITHUB_REPOSITORY}" >> $GITHUB_OUTPUT + echo "repo_ref=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + + - name: Build image + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + tags: ${{ steps.image_info.outputs.tag }} ${{ github.event_name == 'release' && 'production' || 'staging' }} + archs: amd64 + image: ${{ steps.image_info.outputs.name }} + context: ./apps/server/docker + build-args: | + REPO=${{ steps.image_info.outputs.repo }} + REPO_REF=${{ steps.image_info.outputs.repo_ref }} + containerfiles: | + ./apps/server/docker/Dockerfile + + - name: Push image to ghcr.io + uses: redhat-actions/push-to-registry@v2 + with: + tags: ${{ steps.build-image.outputs.tags }} + image: ${{ steps.build-image.outputs.image }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} From f9b32f5a4a9b731b2d09687686dee5cb5b9d337e Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Sat, 15 Jul 2023 10:56:00 +0700 Subject: [PATCH 14/18] SQLite connection limit (#1111) connection_limit=1 --- core/src/library/manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/library/manager.rs b/core/src/library/manager.rs index 868db9ccca58..9927f9878961 100644 --- a/core/src/library/manager.rs +++ b/core/src/library/manager.rs @@ -401,7 +401,7 @@ impl LibraryManager { ) -> Result { let db_path = db_path.as_ref(); let db_url = format!( - "file:{}?socket_timeout=15", + "file:{}?socket_timeout=15&connection_limit=1", db_path.as_os_str().to_str().ok_or_else(|| { LibraryManagerError::NonUtf8Path(NonUtf8PathError(db_path.into())) })? From 0a1d721e58fb3ab0984f3cfc561b59258e8053b1 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Sat, 15 Jul 2023 11:22:04 +0700 Subject: [PATCH 15/18] Use codegen for ModelSyncType exec (#1098) * use codegen for ModelSyncType exec * folder client format * hande create operation properly --- .gitignore | 1 - Cargo.lock | 28 +- Cargo.toml | 6 +- core/prisma/schema.prisma | 8 +- core/src/api/tags.rs | 2 +- core/src/location/file_path_helper/mod.rs | 49 ++-- core/src/location/indexer/mod.rs | 8 +- core/src/location/manager/watcher/utils.rs | 2 +- core/src/location/mod.rs | 64 ++-- core/src/object/file_identifier/mod.rs | 11 +- core/src/object/tag/mod.rs | 39 +-- core/src/object/validation/validator_job.rs | 2 +- core/src/sync/manager.rs | 194 +------------ core/src/sync/mod.rs | 1 + crates/prisma/.gitignore | 1 + crates/sync-generator/src/lib.rs | 305 +++++--------------- crates/sync-generator/src/model.rs | 163 +++++++++++ crates/sync-generator/src/sync_data.rs | 223 ++++++++++++++ crates/sync/src/crdt.rs | 11 +- crates/sync/src/factory.rs | 126 ++++++++ crates/sync/src/lib.rs | 30 +- crates/sync/src/model_traits.rs | 25 ++ interface/app/$libraryId/sync.tsx | 10 +- packages/client/src/core.ts | 6 +- 24 files changed, 768 insertions(+), 547 deletions(-) create mode 100644 crates/prisma/.gitignore create mode 100644 crates/sync-generator/src/model.rs create mode 100644 crates/sync-generator/src/sync_data.rs create mode 100644 crates/sync/src/factory.rs create mode 100644 crates/sync/src/model_traits.rs diff --git a/.gitignore b/.gitignore index 2af23553f7f4..c60db51ed4b5 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,6 @@ yalc.lock todos.md examples/*/*.lock /target -prisma*.rs playwright-report diff --git a/Cargo.lock b/Cargo.lock index 34ed4beb9975..8fdfceae35c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5871,7 +5871,7 @@ dependencies = [ [[package]] name = "prisma-client-rust" version = "0.6.8" -source = "git+https://github.com/Brendonovich/prisma-client-rust?rev=e0af63224dcdb00a14c0aeade878894be8304cfc#e0af63224dcdb00a14c0aeade878894be8304cfc" +source = "git+https://github.com/Brendonovich/prisma-client-rust?rev=047c1102284a61ee400788baa7529d54ae745790#047c1102284a61ee400788baa7529d54ae745790" dependencies = [ "base64 0.13.1", "bigdecimal", @@ -5904,7 +5904,27 @@ dependencies = [ [[package]] name = "prisma-client-rust-cli" version = "0.6.8" -source = "git+https://github.com/Brendonovich/prisma-client-rust?rev=e0af63224dcdb00a14c0aeade878894be8304cfc#e0af63224dcdb00a14c0aeade878894be8304cfc" +source = "git+https://github.com/Brendonovich/prisma-client-rust?rev=047c1102284a61ee400788baa7529d54ae745790#047c1102284a61ee400788baa7529d54ae745790" +dependencies = [ + "directories", + "flate2", + "http", + "prisma-client-rust-generator", + "proc-macro2", + "quote", + "regex", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "syn 1.0.109", + "thiserror", +] + +[[package]] +name = "prisma-client-rust-generator" +version = "0.6.8" +source = "git+https://github.com/Brendonovich/prisma-client-rust?rev=047c1102284a61ee400788baa7529d54ae745790#047c1102284a61ee400788baa7529d54ae745790" dependencies = [ "directories", "flate2", @@ -5924,7 +5944,7 @@ dependencies = [ [[package]] name = "prisma-client-rust-macros" version = "0.6.8" -source = "git+https://github.com/Brendonovich/prisma-client-rust?rev=e0af63224dcdb00a14c0aeade878894be8304cfc#e0af63224dcdb00a14c0aeade878894be8304cfc" +source = "git+https://github.com/Brendonovich/prisma-client-rust?rev=047c1102284a61ee400788baa7529d54ae745790#047c1102284a61ee400788baa7529d54ae745790" dependencies = [ "convert_case 0.6.0", "proc-macro2", @@ -5936,7 +5956,7 @@ dependencies = [ [[package]] name = "prisma-client-rust-sdk" version = "0.6.8" -source = "git+https://github.com/Brendonovich/prisma-client-rust?rev=e0af63224dcdb00a14c0aeade878894be8304cfc#e0af63224dcdb00a14c0aeade878894be8304cfc" +source = "git+https://github.com/Brendonovich/prisma-client-rust?rev=047c1102284a61ee400788baa7529d54ae745790#047c1102284a61ee400788baa7529d54ae745790" dependencies = [ "convert_case 0.5.0", "dmmf", diff --git a/Cargo.toml b/Cargo.toml index 2b9e14f7c928..eb2fb8b16608 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,19 +18,19 @@ edition = "2021" repository = "https://github.com/spacedriveapp/spacedrive" [workspace.dependencies] -prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust", rev = "e0af63224dcdb00a14c0aeade878894be8304cfc", features = [ +prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust", rev = "047c1102284a61ee400788baa7529d54ae745790", features = [ "rspc", "sqlite-create-many", "migrations", "sqlite", ], default-features = false } -prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", rev = "e0af63224dcdb00a14c0aeade878894be8304cfc", features = [ +prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", rev = "047c1102284a61ee400788baa7529d54ae745790", features = [ "rspc", "sqlite-create-many", "migrations", "sqlite", ], default-features = false } -prisma-client-rust-sdk = { git = "https://github.com/Brendonovich/prisma-client-rust", rev = "e0af63224dcdb00a14c0aeade878894be8304cfc", features = [ +prisma-client-rust-sdk = { git = "https://github.com/Brendonovich/prisma-client-rust", rev = "047c1102284a61ee400788baa7529d54ae745790", features = [ "sqlite", ], default-features = false } diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma index 7a0de58faf2e..afcdaea14e71 100644 --- a/core/prisma/schema.prisma +++ b/core/prisma/schema.prisma @@ -5,13 +5,15 @@ datasource db { generator client { provider = "cargo prisma" - output = "../../crates/prisma/src/prisma.rs" + output = "../../crates/prisma/src/prisma" module_path = "sd_prisma::prisma" + client_format = "folder" } generator sync { provider = "cargo prisma-sync" - output = "../../crates/prisma/src/prisma_sync.rs" + output = "../../crates/prisma/src/prisma_sync" + client_format = "folder" } //// Sync //// @@ -48,7 +50,7 @@ model Node { @@map("node") } -/// @local +/// @local(id: pub_id) // represents a single `.db` file (SQLite DB) that is paired to the current library. // A `LibraryInstance` is always owned by a single `Node` but it's possible for that node to change (or two to be owned by a single node). model Instance { diff --git a/core/src/api/tags.rs b/core/src/api/tags.rs index 6d3d594da04f..8a1a031728c0 100644 --- a/core/src/api/tags.rs +++ b/core/src/api/tags.rs @@ -10,7 +10,7 @@ use crate::{ library::Library, object::tag::TagCreateArgs, prisma::{tag, tag_on_object}, - sync, + sync::{self, OperationFactory}, }; use super::{utils::library, Ctx, R}; diff --git a/core/src/location/file_path_helper/mod.rs b/core/src/location/file_path_helper/mod.rs index 376f7e871b10..868b3f4eca6d 100644 --- a/core/src/location/file_path_helper/mod.rs +++ b/core/src/location/file_path_helper/mod.rs @@ -185,6 +185,7 @@ pub async fn create_file_path( use crate::{sync, util::db::uuid_to_bytes}; use sd_prisma::prisma; + use sd_sync::OperationFactory; use serde_json::json; use uuid::Uuid; @@ -225,30 +226,34 @@ pub async fn create_file_path( let pub_id = uuid_to_bytes(Uuid::new_v4()); let created_path = sync - .write_op( + .write_ops( db, - sync.unique_shared_create( - sync::file_path::SyncId { - pub_id: pub_id.clone(), - }, - params, + ( + sync.shared_create( + sync::file_path::SyncId { + pub_id: pub_id.clone(), + }, + params, + ), + db.file_path().create(pub_id, { + use file_path::*; + vec![ + location::connect(prisma::location::id::equals(location.id)), + materialized_path::set(Some(materialized_path.into_owned())), + name::set(Some(name.into_owned())), + extension::set(Some(extension.into_owned())), + inode::set(Some(inode_to_db(metadata.inode))), + device::set(Some(device_to_db(metadata.device))), + cas_id::set(cas_id), + is_dir::set(Some(is_dir)), + size_in_bytes_bytes::set(Some( + metadata.size_in_bytes.to_be_bytes().to_vec(), + )), + date_created::set(Some(metadata.created_at.into())), + date_modified::set(Some(metadata.modified_at.into())), + ] + }), ), - db.file_path().create(pub_id, { - use file_path::*; - vec![ - location::connect(prisma::location::id::equals(location.id)), - materialized_path::set(Some(materialized_path.into_owned())), - name::set(Some(name.into_owned())), - extension::set(Some(extension.into_owned())), - inode::set(Some(inode_to_db(metadata.inode))), - device::set(Some(device_to_db(metadata.device))), - cas_id::set(cas_id), - is_dir::set(Some(is_dir)), - size_in_bytes_bytes::set(Some(metadata.size_in_bytes.to_be_bytes().to_vec())), - date_created::set(Some(metadata.created_at.into())), - date_modified::set(Some(metadata.modified_at.into())), - ] - }), ) .await?; diff --git a/core/src/location/indexer/mod.rs b/core/src/location/indexer/mod.rs index c41225c46133..6ecdf80b4b55 100644 --- a/core/src/location/indexer/mod.rs +++ b/core/src/location/indexer/mod.rs @@ -12,7 +12,7 @@ use std::path::Path; use chrono::Utc; use rspc::ErrorCode; -use sd_prisma::prisma_sync; +use sd_sync::*; use serde::{Deserialize, Serialize}; use serde_json::json; use thiserror::Error; @@ -109,7 +109,7 @@ async fn execute_indexer_save_step( ( ( location::NAME, - json!(prisma_sync::location::SyncId { + json!(sync::location::SyncId { pub_id: pub_id.clone() }), ), @@ -159,7 +159,7 @@ async fn execute_indexer_save_step( .unzip(); ( - sync.unique_shared_create( + sync.shared_create( sync::file_path::SyncId { pub_id: uuid_to_bytes(entry.pub_id), }, @@ -174,7 +174,7 @@ async fn execute_indexer_save_step( .write_ops( db, ( - sync_stuff, + sync_stuff.into_iter().flatten().collect(), db.file_path().create_many(paths).skip_duplicates(), ), ) diff --git a/core/src/location/manager/watcher/utils.rs b/core/src/location/manager/watcher/utils.rs index a1a0bf355a4c..bca1648ba5fe 100644 --- a/core/src/location/manager/watcher/utils.rs +++ b/core/src/location/manager/watcher/utils.rs @@ -20,7 +20,7 @@ use crate::{ validation::hash::file_checksum, }, prisma::{file_path, location, object}, - sync, + sync::{self, OperationFactory}, util::{ db::{device_from_db, device_to_db, inode_from_db, inode_to_db, maybe_missing}, error::FileIOError, diff --git a/core/src/location/mod.rs b/core/src/location/mod.rs index cd9c3761c339..2bf8181880ff 100644 --- a/core/src/location/mod.rs +++ b/core/src/location/mod.rs @@ -21,6 +21,7 @@ use chrono::Utc; use futures::future::TryFutureExt; use normpath::PathExt; use prisma_client_rust::{operator::and, or, QueryError}; +use sd_sync::*; use serde::Deserialize; use serde_json::json; use specta::Type; @@ -579,38 +580,41 @@ async fn create_location( let date_created = Utc::now(); let location = sync - .write_op( + .write_ops( db, - sync.unique_shared_create( - sync::location::SyncId { - pub_id: location_pub_id.as_bytes().to_vec(), - }, - [ - (location::name::NAME, json!(&name)), - (location::path::NAME, json!(&location_path)), - (location::date_created::NAME, json!(date_created)), - ( - location::instance_id::NAME, - json!(sync::instance::SyncId { - id: library.config.instance_id, - }), - ), - ], - ), - db.location() - .create( - location_pub_id.as_bytes().to_vec(), - vec![ - location::name::set(Some(name.clone())), - location::path::set(Some(location_path)), - location::date_created::set(Some(date_created.into())), - location::instance_id::set(Some(library.config.instance_id)), - // location::instance::connect(instance::id::equals( - // library.config.instance_id.as_bytes().to_vec(), - // )), + ( + sync.shared_create( + sync::location::SyncId { + pub_id: location_pub_id.as_bytes().to_vec(), + }, + [ + (location::name::NAME, json!(&name)), + (location::path::NAME, json!(&location_path)), + (location::date_created::NAME, json!(date_created)), + ( + location::instance_id::NAME, + json!(sync::instance::SyncId { + pub_id: vec![], + // id: library.config.instance_id, + }), + ), ], - ) - .include(location_with_indexer_rules::include()), + ), + db.location() + .create( + location_pub_id.as_bytes().to_vec(), + vec![ + location::name::set(Some(name.clone())), + location::path::set(Some(location_path)), + location::date_created::set(Some(date_created.into())), + location::instance_id::set(Some(library.config.instance_id)), + // location::instance::connect(instance::id::equals( + // library.config.instance_id.as_bytes().to_vec(), + // )), + ], + ) + .include(location_with_indexer_rules::include()), + ), ) .await?; diff --git a/core/src/object/file_identifier/mod.rs b/core/src/object/file_identifier/mod.rs index 2ea078b2f240..6934599c76e3 100644 --- a/core/src/object/file_identifier/mod.rs +++ b/core/src/object/file_identifier/mod.rs @@ -6,8 +6,7 @@ use crate::{ }, object::{cas::generate_cas_id, object_for_file_identifier}, prisma::{file_path, location, object, PrismaClient}, - sync, - sync::SyncManager, + sync::{self, CRDTOperation, OperationFactory, SyncManager}, util::{ db::{maybe_missing, uuid_to_bytes}, error::FileIOError, @@ -15,7 +14,6 @@ use crate::{ }; use sd_file_ext::{extensions::Extension, kind::ObjectKind}; -use sd_sync::CRDTOperation; use std::{ collections::{HashMap, HashSet}, @@ -252,7 +250,7 @@ async fn identifier_job_step( .unzip(); let object_creation_args = ( - sync.unique_shared_create(sync_id(), sync_params), + sync.shared_create(sync_id(), sync_params), object::create_unchecked(uuid_to_bytes(object_pub_id), db_params), ); @@ -274,7 +272,10 @@ async fn identifier_job_step( .write_ops(db, { let (sync, db_params): (Vec<_>, Vec<_>) = object_create_args.into_iter().unzip(); - (sync, db.object().create_many(db_params)) + ( + sync.into_iter().flatten().collect(), + db.object().create_many(db_params), + ) }) .await .unwrap_or_else(|e| { diff --git a/core/src/object/tag/mod.rs b/core/src/object/tag/mod.rs index 0047bfcc727a..70dce817485a 100644 --- a/core/src/object/tag/mod.rs +++ b/core/src/object/tag/mod.rs @@ -1,6 +1,7 @@ pub mod seed; use chrono::{DateTime, FixedOffset, Utc}; +use sd_sync::*; use serde::Deserialize; use serde_json::json; use specta::Type; @@ -23,25 +24,27 @@ impl TagCreateArgs { let pub_id = Uuid::new_v4().as_bytes().to_vec(); let date_created: DateTime = Utc::now().into(); - sync.write_op( + sync.write_ops( db, - sync.unique_shared_create( - sync::tag::SyncId { - pub_id: pub_id.clone(), - }, - [ - (tag::name::NAME, json!(&self.name)), - (tag::color::NAME, json!(&self.color)), - (tag::date_created::NAME, json!(&date_created.to_rfc3339())), - ], - ), - db.tag().create( - pub_id, - vec![ - tag::name::set(Some(self.name)), - tag::color::set(Some(self.color)), - tag::date_created::set(Some(date_created)), - ], + ( + sync.shared_create( + sync::tag::SyncId { + pub_id: pub_id.clone(), + }, + [ + (tag::name::NAME, json!(&self.name)), + (tag::color::NAME, json!(&self.color)), + (tag::date_created::NAME, json!(&date_created.to_rfc3339())), + ], + ), + db.tag().create( + pub_id, + vec![ + tag::name::set(Some(self.name)), + tag::color::set(Some(self.color)), + tag::date_created::set(Some(date_created)), + ], + ), ), ) .await diff --git a/core/src/object/validation/validator_job.rs b/core/src/object/validation/validator_job.rs index d0ced3ec88e0..da9188f821b0 100644 --- a/core/src/object/validation/validator_job.rs +++ b/core/src/object/validation/validator_job.rs @@ -8,7 +8,7 @@ use crate::{ file_path_for_object_validator, IsolatedFilePathData, }, prisma::{file_path, location}, - sync, + sync::{self, OperationFactory}, util::{ db::{chain_optional_iter, maybe_missing}, error::FileIOError, diff --git a/core/src/sync/manager.rs b/core/src/sync/manager.rs index 3f700565cb92..76039a864f7d 100644 --- a/core/src/sync/manager.rs +++ b/core/src/sync/manager.rs @@ -1,12 +1,11 @@ #![allow(clippy::unwrap_used, clippy::panic)] // TODO: Brendan remove this once you've got error handling here use crate::prisma::*; +use sd_sync::*; use std::{collections::HashMap, sync::Arc}; -use sd_sync::*; - -use serde_json::{json, to_vec, Value}; +use serde_json::to_vec; use tokio::sync::broadcast::{self, Receiver, Sender}; use uhlc::{HLCBuilder, HLC, NTP64}; use uuid::Uuid; @@ -55,7 +54,7 @@ impl SyncManager { .filter_map(|op| match &op.typ { CRDTOperationType::Shared(shared_op) => { let kind = match &shared_op.data { - SharedOperationData::Create(_) => "c", + SharedOperationData::Create => "c", SharedOperationData::Update { .. } => "u", SharedOperationData::Delete => "d", }; @@ -101,7 +100,7 @@ impl SyncManager { let ret = match &op.typ { CRDTOperationType::Shared(shared_op) => { let kind = match &shared_op.data { - SharedOperationData::Create(_) => "c", + SharedOperationData::Create => "c", SharedOperationData::Update { .. } => "u", SharedOperationData::Delete => "d", }; @@ -177,138 +176,14 @@ impl SyncManager { let msg = SyncMessage::Ingested(op.clone()); - match ModelSyncData::from_op(op.typ.clone()).unwrap() { - ModelSyncData::FilePath(id, shared_op) => match shared_op { - SharedOperationData::Create(data) => { - let data: Vec<_> = data - .into_iter() - .flat_map(|(k, v)| file_path::SetParam::deserialize(&k, v)) - .collect(); - - db.file_path() - .upsert( - file_path::pub_id::equals(id.pub_id.clone()), - file_path::create(id.pub_id, data.clone()), - data, - ) - .exec() - .await?; - } - SharedOperationData::Update { field, value } => { - let data = vec![file_path::SetParam::deserialize(&field, value).unwrap()]; - - db.file_path() - .upsert( - file_path::pub_id::equals(id.pub_id.clone()), - file_path::create(id.pub_id, data.clone()), - data, - ) - .exec() - .await?; - } - _ => todo!(), - }, - ModelSyncData::Location(id, shared_op) => match shared_op { - SharedOperationData::Create(data) => { - let data: Vec<_> = data - .into_iter() - .flat_map(|(k, v)| location::SetParam::deserialize(&k, v)) - .collect(); - - db.location() - .upsert( - location::pub_id::equals(id.pub_id.clone()), - location::create(id.pub_id, data.clone()), - data, - ) - .exec() - .await?; - } - SharedOperationData::Update { field, value } => { - let data = vec![location::SetParam::deserialize(&field, value).unwrap()]; - - db.location() - .upsert( - location::pub_id::equals(id.pub_id.clone()), - location::create(id.pub_id, data.clone()), - data, - ) - .exec() - .await?; - } - _ => todo!(), - }, - ModelSyncData::Object(id, shared_op) => match shared_op { - SharedOperationData::Create(data) => { - let data: Vec<_> = data - .into_iter() - .flat_map(|(k, v)| object::SetParam::deserialize(&k, v)) - .collect(); - - db.object() - .upsert( - object::pub_id::equals(id.pub_id.clone()), - object::create(id.pub_id, vec![]), - data, - ) - .exec() - .await?; - } - SharedOperationData::Update { field, value } => { - let data = vec![object::SetParam::deserialize(&field, value).unwrap()]; - - db.object() - .upsert( - object::pub_id::equals(id.pub_id.clone()), - object::create(id.pub_id, data.clone()), - data, - ) - .exec() - .await?; - } - _ => todo!(), - }, - ModelSyncData::Tag(id, shared_op) => match shared_op { - SharedOperationData::Create(data) => { - let data: Vec<_> = data - .into_iter() - .flat_map(|(field, value)| tag::SetParam::deserialize(&field, value)) - .collect(); - - db.tag() - .upsert( - tag::pub_id::equals(id.pub_id.clone()), - tag::create(id.pub_id, data.clone()), - data, - ) - .exec() - .await?; - } - SharedOperationData::Update { field, value } => { - let data = vec![tag::SetParam::deserialize(&field, value).unwrap()]; - - db.tag() - .upsert( - tag::pub_id::equals(id.pub_id.clone()), - tag::create(id.pub_id, data.clone()), - data, - ) - .exec() - .await?; - } - SharedOperationData::Delete => { - db.tag() - .delete(tag::pub_id::equals(id.pub_id)) - .exec() - .await?; - } - }, - ModelSyncData::Preference(_, _) => todo!(), - } + ModelSyncData::from_op(op.typ.clone()) + .unwrap() + .exec(db) + .await?; if let CRDTOperationType::Shared(shared_op) = op.typ { let kind = match &shared_op.data { - SharedOperationData::Create(_) => "c", + SharedOperationData::Create => "c", SharedOperationData::Update { .. } => "u", SharedOperationData::Delete => "d", }; @@ -332,53 +207,14 @@ impl SyncManager { Ok(()) } +} - fn new_op(&self, typ: CRDTOperationType) -> CRDTOperation { - let timestamp = self.clock.new_timestamp(); - - CRDTOperation { - instance: self.instance, - timestamp: *timestamp.get_time(), - id: Uuid::new_v4(), - typ, - } +impl OperationFactory for SyncManager { + fn get_clock(&self) -> &HLC { + &self.clock } - pub fn unique_shared_create< - TSyncId: SyncId, - TModel: SyncType, - >( - &self, - id: TSyncId, - values: impl IntoIterator + 'static, - ) -> CRDTOperation { - self.new_op(CRDTOperationType::Shared(SharedOperation { - model: TModel::MODEL.to_string(), - record_id: json!(id), - data: SharedOperationData::Create( - values - .into_iter() - .map(|(name, value)| (name.to_string(), value)) - .collect(), - ), - })) - } - pub fn shared_update< - TSyncId: SyncId, - TModel: SyncType, - >( - &self, - id: TSyncId, - field: &str, - value: Value, - ) -> CRDTOperation { - self.new_op(CRDTOperationType::Shared(SharedOperation { - model: TModel::MODEL.to_string(), - record_id: json!(id), - data: SharedOperationData::Update { - field: field.to_string(), - value, - }, - })) + fn get_instance(&self) -> Uuid { + self.instance } } diff --git a/core/src/sync/mod.rs b/core/src/sync/mod.rs index 285954b44669..ca23770219db 100644 --- a/core/src/sync/mod.rs +++ b/core/src/sync/mod.rs @@ -2,3 +2,4 @@ mod manager; pub use crate::prisma_sync::*; pub use manager::*; +pub use sd_sync::*; diff --git a/crates/prisma/.gitignore b/crates/prisma/.gitignore new file mode 100644 index 000000000000..4d20cf2e7d2c --- /dev/null +++ b/crates/prisma/.gitignore @@ -0,0 +1 @@ +src/*/ diff --git a/crates/sync-generator/src/lib.rs b/crates/sync-generator/src/lib.rs index 00e49922403f..2c3a3f0e5693 100644 --- a/crates/sync-generator/src/lib.rs +++ b/crates/sync-generator/src/lib.rs @@ -1,10 +1,14 @@ mod attribute; +mod model; +mod sync_data; use attribute::*; use prisma_client_rust_sdk::{ prelude::*, - prisma::prisma_models::walkers::{FieldWalker, ModelWalker, RefinedFieldWalker}, + prisma::prisma_models::walkers::{ + FieldWalker, ModelWalker, RefinedFieldWalker, RelationFieldWalker, + }, }; #[derive(Debug, serde::Serialize, thiserror::Error)] @@ -13,51 +17,70 @@ enum Error {} #[derive(serde::Deserialize)] struct SDSyncGenerator {} -type FieldVec<'a> = Vec>; - #[allow(unused)] #[derive(Clone)] -enum ModelSyncType<'a> { +pub enum ModelSyncType<'a> { Local { - id: FieldVec<'a>, + id: FieldWalker<'a>, }, // Owned { // id: FieldVec<'a>, // }, Shared { - id: FieldVec<'a>, + id: FieldWalker<'a>, }, Relation { - group: FieldVec<'a>, - item: FieldVec<'a>, + group: RelationFieldWalker<'a>, + item: RelationFieldWalker<'a>, }, } impl<'a> ModelSyncType<'a> { fn from_attribute(attr: Attribute, model: ModelWalker<'a>) -> Option { - let id = attr - .field("id") - .map(|field| match field { - AttributeFieldValue::Single(s) => vec![*s], - AttributeFieldValue::List(l) => l.clone(), - }) - .unwrap_or_else(|| { - model - .primary_key() - .as_ref() - .unwrap() - .fields() - .map(|f| f.name()) - .collect() - }) - .into_iter() - .flat_map(|name| model.fields().find(|f| f.name() == name)) - .collect(); - Some(match attr.name { - "local" => Self::Local { id }, + "local" | "shared" => { + let id = attr + .field("id") + .and_then(|field| match field { + AttributeFieldValue::Single(s) => Some(s), + AttributeFieldValue::List(l) => None, + }) + .and_then(|name| model.fields().find(|f| f.name() == *name))?; + + match attr.name { + "local" => Self::Local { id }, + "shared" => Self::Shared { id }, + _ => return None, + } + } + "relation" => { + let get_field = |name| { + attr.field(name) + .and_then(|field| match field { + AttributeFieldValue::Single(s) => Some(*s), + AttributeFieldValue::List(l) => None, + }) + .and_then(|name| { + match model + .fields() + .find(|f| f.name() == name) + .expect(&format!("'{name}' field not found")) + .refine() + { + RefinedFieldWalker::Relation(r) => Some(r), + _ => None, + } + }) + .expect(&format!("'{name}' must be a relation field")) + }; + + Self::Relation { + item: get_field("item"), + group: get_field("group"), + } + } + // "owned" => Self::Owned { id }, - "shared" => Self::Shared { id }, _ => return None, }) } @@ -65,8 +88,9 @@ impl<'a> ModelSyncType<'a> { fn sync_id(&self) -> Vec { match self { // Self::Owned { id } => id.clone(), - Self::Local { id } => id.clone(), - Self::Shared { id } => id.clone(), + Self::Local { id } => vec![id.clone()], + Self::Shared { id } => vec![id.clone()], + Self::Relation { group, item } => vec![(*group).into(), (*item).into()], _ => vec![], } } @@ -85,13 +109,15 @@ impl ToTokens for ModelSyncType<'_> { } } +pub type ModelWithSyncType<'a> = (ModelWalker<'a>, Option>); + impl PrismaGenerator for SDSyncGenerator { const NAME: &'static str = "SD Sync Generator"; const DEFAULT_OUTPUT: &'static str = "prisma-sync.rs"; type Error = Error; - fn generate(self, args: GenerateArgs) -> Result { + fn generate(self, args: GenerateArgs) -> Result { let db = &args.schema.db; let models_with_sync_types = db @@ -106,213 +132,22 @@ impl PrismaGenerator for SDSyncGenerator { }) .collect::>(); - let model_modules = models_with_sync_types.clone().into_iter().map(|(model, sync_type)| { - let model_name_snake = snake_ident(model.name()); - - let sync_id = sync_type.as_ref() - .map(|sync_type| { - let fields = sync_type.sync_id(); - let fields = fields.iter().flat_map(|field| { - let name_snake = snake_ident(field.name()); - - let typ = match field.refine() { - RefinedFieldWalker::Scalar(_) => { - field.type_tokens("e!(self)) - }, - RefinedFieldWalker::Relation(relation)=> { - let relation_model_name_snake = snake_ident(relation.related_model().name()); - Some(quote!(super::#relation_model_name_snake::SyncId)) - }, - }; - - Some(quote!(pub #name_snake: #typ)) - }); - - quote! { - #[derive(serde::Serialize, serde::Deserialize)] - pub struct SyncId { - #(#fields),* - } - - impl sd_sync::SyncId for SyncId { - type ModelTypes = #model_name_snake::Types; - } - - impl sd_sync::SyncType for #model_name_snake::Types { - type SyncId = SyncId; - type Marker = sd_sync::#sync_type; - } - } - }); - - let set_param_impl = { - let field_matches = model.fields().filter_map(|field| { - let field_name_snake = snake_ident(field.name()); - - match field.refine() { - RefinedFieldWalker::Scalar(scalar_field) => { - (!scalar_field.is_in_required_relation()).then(|| quote! { - #model_name_snake::#field_name_snake::set(::serde_json::from_value(val).unwrap()), - }) - }, - RefinedFieldWalker::Relation(relation_field) => { - let relation_model_name_snake = snake_ident(relation_field.related_model().name()); - - match relation_field.referenced_fields() { - Some(i) => { - if i.count() == 1 { - Some(quote! {{ - let val: std::collections::HashMap = ::serde_json::from_value(val).unwrap(); - let val = val.into_iter().next().unwrap(); - - #model_name_snake::#field_name_snake::connect( - #relation_model_name_snake::UniqueWhereParam::deserialize(&val.0, val.1).unwrap() - ) - }}) - } else { None } - }, - _ => None - } - }, - }.map(|body| quote!(#model_name_snake::#field_name_snake::NAME => #body)) - }); - - match field_matches.clone().count() { - 0 => quote!(), - _ => quote! { - impl #model_name_snake::SetParam { - pub fn deserialize(field: &str, val: ::serde_json::Value) -> Option { - Some(match field { - #(#field_matches)* - _ => return None - }) - } - } - } - } - }; - - let unique_param_impl = { - let field_matches = model - .unique_criterias() - .flat_map(|criteria| match &criteria.fields().next() { - Some(field) if criteria.fields().len() == 1 => { - let field_name_snake = snake_ident(field.name()); - - Some(quote!(#model_name_snake::#field_name_snake::NAME => - #model_name_snake::#field_name_snake::equals( - ::serde_json::from_value(val).unwrap() - ), - )) - } - _ => None, - }) - .collect::>(); - - match field_matches.len() { - 0 => quote!(), - _ => quote! { - impl #model_name_snake::UniqueWhereParam { - pub fn deserialize(field: &str, val: ::serde_json::Value) -> Option { - Some(match field { - #(#field_matches)* - _ => return None - }) - } - } - }, - } - }; - - quote! { - pub mod #model_name_snake { - use super::prisma::*; - - #sync_id - - #set_param_impl - - #unique_param_impl - } - } - }); - - let model_sync_data = { - let (variants, matches): (Vec<_>, Vec<_>) = models_with_sync_types - .into_iter() - .filter_map(|(model, sync_type)| { - let model_name_snake = snake_ident(model.name()); - let model_name_pascal = pascal_ident(model.name()); - - sync_type.and_then(|a| { - let data_type = match a { - // ModelSyncType::Owned { .. } => quote!(OwnedOperationData), - ModelSyncType::Shared { .. } => quote!(SharedOperationData), - ModelSyncType::Relation { .. } => { - quote!(RelationOperationData) - } - _ => return None, - }; - - let variant = quote! { - #model_name_pascal(#model_name_snake::SyncId, sd_sync::#data_type) - }; - - let op_type_enum = quote!(sd_sync::CRDTOperationType); - - let cond = quote!(if op.model == prisma::#model_name_snake::NAME); - - let match_case = match a { - // ModelSyncType::Owned { .. } => { - // quote! { - // #op_type_enum::Owned(op) #cond => - // Self::#model_name_pascal(serde_json::from_value(op.record_id).ok()?, op.data) - // } - // } - ModelSyncType::Shared { .. } => { - quote! { - #op_type_enum::Shared(op) #cond => - Self::#model_name_pascal(serde_json::from_value(op.record_id).ok()?, op.data) - } - } - // ModelSyncType::Relation { .. } => { - // quote! { - // (#model_name_str, sd_sync::CRDTOperation::Relation(op)) => - // Self::#model_name_pascal() - // } - // } - _ => return None, - }; - - Some((variant, match_case)) - }) - }) - .unzip(); + let model_sync_data = sync_data::r#enum(models_with_sync_types.clone()); + let mut module = Module::new( + "root", quote! { - pub enum ModelSyncData { - #(#variants),* - } + use crate::prisma; - impl ModelSyncData { - pub fn from_op(op: sd_sync::CRDTOperationType) -> Option { - Some(match op { - #(#matches),*, - _ => return None - }) - } - } - } - }; - - Ok(quote! { - use crate::prisma; - - #model_sync_data + #model_sync_data + }, + ); + models_with_sync_types + .into_iter() + .map(model::module) + .for_each(|model| module.add_submodule(model)); - #(#model_modules)* - } - .to_string()) + Ok(module) } } diff --git a/crates/sync-generator/src/model.rs b/crates/sync-generator/src/model.rs new file mode 100644 index 000000000000..58e8616f0b40 --- /dev/null +++ b/crates/sync-generator/src/model.rs @@ -0,0 +1,163 @@ +use prisma_client_rust_sdk::{prelude::*, prisma::prisma_models::walkers::RefinedFieldWalker}; + +use crate::{ModelSyncType, ModelWithSyncType}; + +pub fn module((model, sync_type): ModelWithSyncType) -> Module { + let model_name_snake = snake_ident(model.name()); + + let sync_id = sync_type.as_ref().map(|sync_type| { + let fields = sync_type.sync_id(); + let fields = fields.iter().flat_map(|field| { + let name_snake = snake_ident(field.name()); + + let typ = match field.refine() { + RefinedFieldWalker::Scalar(_) => field.type_tokens("e!(self)), + RefinedFieldWalker::Relation(relation) => { + let relation_model_name_snake = snake_ident(relation.related_model().name()); + Some(quote!(super::#relation_model_name_snake::SyncId)) + } + }; + + Some(quote!(pub #name_snake: #typ)) + }); + + let model_stuff = match sync_type { + ModelSyncType::Relation { item, group } => { + let item_name_snake = snake_ident(item.name()); + let item_model_name_snake = snake_ident(item.related_model().name()); + + let group_name_snake = snake_ident(group.name()); + let group_model_name_snake = snake_ident(group.related_model().name()); + + Some(quote! { + impl sd_sync::RelationSyncId for SyncId { + type ItemSyncId = super::#item_model_name_snake::SyncId; + type GroupSyncId = super::#group_model_name_snake::SyncId; + + fn split(&self) -> (&Self::ItemSyncId, &Self::GroupSyncId) { + ( + &self.#item_name_snake, + &self.#group_name_snake + ) + } + } + + impl sd_sync::RelationSyncModel for #model_name_snake::Types { + type SyncId = SyncId; + } + }) + } + ModelSyncType::Shared { .. } => Some(quote! { + impl sd_sync::SharedSyncModel for #model_name_snake::Types { + type SyncId = SyncId; + } + }), + _ => None, + }; + + quote! { + #[derive(serde::Serialize, serde::Deserialize, Clone)] + pub struct SyncId { + #(#fields),* + } + + impl sd_sync::SyncId for SyncId { + type Model = #model_name_snake::Types; + } + + #model_stuff + } + }); + + let set_param_impl = { + let field_matches = model.fields().filter_map(|field| { + let field_name_snake = snake_ident(field.name()); + + match field.refine() { + RefinedFieldWalker::Scalar(scalar_field) => { + (!scalar_field.is_in_required_relation()).then(|| quote! { + #model_name_snake::#field_name_snake::set(::serde_json::from_value(val).unwrap()), + }) + }, + RefinedFieldWalker::Relation(relation_field) => { + let relation_model_name_snake = snake_ident(relation_field.related_model().name()); + + match relation_field.referenced_fields() { + Some(i) => { + if i.count() == 1 { + Some(quote! {{ + let val: std::collections::HashMap = ::serde_json::from_value(val).unwrap(); + let val = val.into_iter().next().unwrap(); + + #model_name_snake::#field_name_snake::connect( + #relation_model_name_snake::UniqueWhereParam::deserialize(&val.0, val.1).unwrap() + ) + }}) + } else { None } + }, + _ => None + } + }, + }.map(|body| quote!(#model_name_snake::#field_name_snake::NAME => #body)) + }); + + match field_matches.clone().count() { + 0 => quote!(), + _ => quote! { + impl #model_name_snake::SetParam { + pub fn deserialize(field: &str, val: ::serde_json::Value) -> Option { + Some(match field { + #(#field_matches)* + _ => return None + }) + } + } + }, + } + }; + + let unique_param_impl = { + let field_matches = model + .unique_criterias() + .flat_map(|criteria| match &criteria.fields().next() { + Some(field) if criteria.fields().len() == 1 => { + let field_name_snake = snake_ident(field.name()); + + Some(quote!(#model_name_snake::#field_name_snake::NAME => + #model_name_snake::#field_name_snake::equals( + ::serde_json::from_value(val).unwrap() + ), + )) + } + _ => None, + }) + .collect::>(); + + match field_matches.len() { + 0 => quote!(), + _ => quote! { + impl #model_name_snake::UniqueWhereParam { + pub fn deserialize(field: &str, val: ::serde_json::Value) -> Option { + Some(match field { + #(#field_matches)* + _ => return None + }) + } + } + }, + } + }; + + Module::new( + model.name(), + quote! { + use super::prisma::*; + + #sync_id + + #set_param_impl + + #unique_param_impl + }, + ) +} diff --git a/crates/sync-generator/src/sync_data.rs b/crates/sync-generator/src/sync_data.rs new file mode 100644 index 000000000000..b108c30fded2 --- /dev/null +++ b/crates/sync-generator/src/sync_data.rs @@ -0,0 +1,223 @@ +use prisma_client_rust_sdk::{prelude::*, prisma::prisma_models::walkers::RelationFieldWalker}; + +use crate::{ModelSyncType, ModelWithSyncType}; + +pub fn r#enum(models: Vec) -> TokenStream { + let (variants, matches): (Vec<_>, Vec<_>) = models + .iter() + .filter_map(|(model, sync_type)| { + let model_name_snake = snake_ident(model.name()); + let model_name_pascal = pascal_ident(model.name()); + + sync_type.as_ref().and_then(|a| { + let data_type = match a { + // ModelSyncType::Owned { .. } => quote!(OwnedOperationData), + ModelSyncType::Shared { .. } => quote!(SharedOperationData), + ModelSyncType::Relation { .. } => { + quote!(RelationOperationData) + } + _ => return None, + }; + + let variant = quote! { + #model_name_pascal(#model_name_snake::SyncId, sd_sync::#data_type) + }; + + let op_type_enum = quote!(sd_sync::CRDTOperationType); + + let match_case = match a { + // ModelSyncType::Owned { .. } => { + // quote! { + // #op_type_enum::Owned(op) #cond => + // Self::#model_name_pascal(serde_json::from_value(op.record_id).ok()?, op.data) + // } + // } + ModelSyncType::Shared { .. } => { + quote! { + #op_type_enum::Shared(op) if op.model == prisma::#model_name_snake::NAME => + Self::#model_name_pascal(serde_json::from_value(op.record_id).ok()?, op.data) + } + } + ModelSyncType::Relation { item, group } => { + let item_name_snake = snake_ident(item.name()); + let group_name_snake = snake_ident(group.name()); + + quote! { + #op_type_enum::Relation(op) if op.relation == prisma::#model_name_snake::NAME => + Self::#model_name_pascal( + #model_name_snake::SyncId { + #item_name_snake: serde_json::from_value(op.relation_item).ok()?, + #group_name_snake: serde_json::from_value(op.relation_group).ok()?, + }, + op.data + ) + } + } + _ => return None, + }; + + Some((variant, match_case)) + }) + }) + .unzip(); + + let exec_matches = models.iter().filter_map(|(model, sync_type)| { + let model_name_pascal = pascal_ident(model.name()); + let model_name_snake = snake_ident(model.name()); + + let match_arms = match sync_type.as_ref()? { + ModelSyncType::Shared { id } => { + let id_name_snake = snake_ident(id.name()); + + quote! { + match data { + sd_sync::SharedOperationData::Create => { + db.#model_name_snake() + .upsert( + prisma::#model_name_snake::#id_name_snake::equals(id.#id_name_snake.clone()), + prisma::#model_name_snake::create(id.#id_name_snake, vec![]), + vec![] + ) + .exec() + .await?; + }, + sd_sync::SharedOperationData::Update { field, value } => { + let data = vec![ + prisma::#model_name_snake::SetParam::deserialize(&field, value).unwrap() + ]; + + db.#model_name_snake() + .upsert( + prisma::#model_name_snake::#id_name_snake::equals(id.#id_name_snake.clone()), + prisma::#model_name_snake::create(id.#id_name_snake, data.clone()), + data, + ) + .exec() + .await?; + }, + sd_sync::SharedOperationData::Delete => { + db.#model_name_snake() + .delete(prisma::#model_name_snake::#id_name_snake::equals(id.#id_name_snake)) + .exec() + .await?; + }, + } + } + } + ModelSyncType::Relation { item, group } => { + let compound_id = format_ident!( + "{}", + item.fields() + .unwrap() + .chain(group.fields().unwrap()) + .map(|f| f.name()) + .collect::>() + .join("_") + ); + + let db_batch_items = { + let batch_item = |item: &RelationFieldWalker| { + let item_model_name_snake = snake_ident(item.related_model().name()); + let item_field_name_snake = snake_ident(item.name()); + + quote!(db.#item_model_name_snake().find_unique( + prisma::#item_model_name_snake::pub_id::equals(id.#item_field_name_snake.pub_id.clone()) + )) + }; + + [batch_item(item), batch_item(group)] + }; + + let create_items = { + let create_item = |item: &RelationFieldWalker, var: TokenStream| { + let item_model_name_snake = snake_ident(item.related_model().name()); + + quote!( + prisma::#item_model_name_snake::id::equals(#var.id) + ) + }; + + [ + create_item(item, quote!(item)), + create_item(group, quote!(group)), + ] + }; + + quote! { + let (Some(item), Some(group)) = + db._batch((#(#db_batch_items),*)).await? else { + panic!("item and group not found!"); + }; + + let id = prisma::tag_on_object::#compound_id(item.id, group.id); + + match data { + sd_sync::RelationOperationData::Create => { + db.#model_name_snake() + .create( + #(#create_items),*, + vec![], + ) + .exec() + .await + .ok(); + }, + sd_sync::RelationOperationData::Update { field, value } => { + let data = vec![prisma::#model_name_snake::SetParam::deserialize(&field, value).unwrap()]; + + db.#model_name_snake() + .upsert( + id, + prisma::#model_name_snake::create( + #(#create_items),*, + data.clone(), + ), + data, + ) + .exec() + .await + .ok(); + }, + sd_sync::RelationOperationData::Delete => { + db.#model_name_snake() + .delete(id) + .exec() + .await + .ok(); + }, + } + } + } + _ => return None, + }; + + Some(quote! { + Self::#model_name_pascal(id, data) => { + #match_arms + } + }) + }); + + quote! { + pub enum ModelSyncData { + #(#variants),* + } + + impl ModelSyncData { + pub fn from_op(op: sd_sync::CRDTOperationType) -> Option { + Some(match op { + #(#matches),*, + _ => return None + }) + } + + pub async fn exec(self, db: &prisma::PrismaClient) -> prisma_client_rust::Result<()> { + match self { + #(#exec_matches),* + } + + Ok(()) + } + } + } +} diff --git a/crates/sync/src/crdt.rs b/crates/sync/src/crdt.rs index 79e016625409..e985b3cc0571 100644 --- a/crates/sync/src/crdt.rs +++ b/crates/sync/src/crdt.rs @@ -1,22 +1,25 @@ use std::fmt::Debug; use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; +use serde_json::Value; use specta::Type; use uhlc::NTP64; use uuid::Uuid; #[derive(Serialize, Deserialize, Clone, Debug, Type)] pub enum RelationOperationData { + #[serde(rename = "c")] Create, + #[serde(rename = "u")] Update { field: String, value: Value }, + #[serde(rename = "d")] Delete, } #[derive(Serialize, Deserialize, Clone, Debug, Type)] pub struct RelationOperation { - pub relation_item: Uuid, - pub relation_group: Uuid, + pub relation_item: Value, + pub relation_group: Value, pub relation: String, pub data: RelationOperationData, } @@ -24,7 +27,7 @@ pub struct RelationOperation { #[derive(Serialize, Deserialize, Clone, Debug, Type)] pub enum SharedOperationData { #[serde(rename = "c")] - Create(Map), + Create, #[serde(rename = "u")] Update { field: String, value: Value }, #[serde(rename = "d")] diff --git a/crates/sync/src/factory.rs b/crates/sync/src/factory.rs new file mode 100644 index 000000000000..11151aeb22ed --- /dev/null +++ b/crates/sync/src/factory.rs @@ -0,0 +1,126 @@ +use serde_json::{json, Value}; +use uhlc::HLC; +use uuid::Uuid; + +use crate::*; + +pub trait OperationFactory { + fn get_clock(&self) -> &HLC; + fn get_instance(&self) -> Uuid; + + fn new_op(&self, typ: CRDTOperationType) -> CRDTOperation { + let timestamp = self.get_clock().new_timestamp(); + + CRDTOperation { + instance: self.get_instance(), + timestamp: *timestamp.get_time(), + id: Uuid::new_v4(), + typ, + } + } + + fn shared_op, TModel: SharedSyncModel>( + &self, + id: &TSyncId, + data: SharedOperationData, + ) -> CRDTOperation { + self.new_op(CRDTOperationType::Shared(SharedOperation { + model: TModel::MODEL.to_string(), + record_id: json!(id), + data, + })) + } + + fn shared_create, TModel: SharedSyncModel>( + &self, + id: TSyncId, + values: impl IntoIterator + 'static, + ) -> Vec { + [self.shared_op(&id, SharedOperationData::Create)] + .into_iter() + .chain(values.into_iter().map(|(name, value)| { + self.shared_op( + &id, + SharedOperationData::Update { + field: name.to_string(), + value, + }, + ) + })) + .collect() + } + fn shared_update, TModel: SharedSyncModel>( + &self, + id: TSyncId, + field: impl Into, + value: Value, + ) -> CRDTOperation { + self.shared_op( + &id, + SharedOperationData::Update { + field: field.into(), + value, + }, + ) + } + fn shared_delete, TModel: SharedSyncModel>( + &self, + id: TSyncId, + ) -> CRDTOperation { + self.shared_op(&id, SharedOperationData::Delete) + } + + fn relation_op, TModel: RelationSyncModel>( + &self, + id: &TSyncId, + data: RelationOperationData, + ) -> CRDTOperation { + let (item_id, group_id) = id.split(); + + self.new_op(CRDTOperationType::Relation(RelationOperation { + relation_item: json!(item_id), + relation_group: json!(group_id), + relation: TModel::MODEL.to_string(), + data, + })) + } + + fn relation_create, TModel: RelationSyncModel>( + &self, + id: TSyncId, + values: impl IntoIterator + 'static, + ) -> Vec { + [self.relation_op(&id, RelationOperationData::Create)] + .into_iter() + .chain(values.into_iter().map(|(name, value)| { + self.relation_op( + &id, + RelationOperationData::Update { + field: name.to_string(), + value, + }, + ) + })) + .collect() + } + fn relation_update, TModel: RelationSyncModel>( + &self, + id: TSyncId, + field: impl Into, + value: Value, + ) -> CRDTOperation { + self.relation_op( + &id, + RelationOperationData::Update { + field: field.into(), + value, + }, + ) + } + fn relation_delete, TModel: RelationSyncModel>( + &self, + id: TSyncId, + ) -> CRDTOperation { + self.relation_op(&id, RelationOperationData::Delete) + } +} diff --git a/crates/sync/src/lib.rs b/crates/sync/src/lib.rs index b14ea9764bb3..603ccb12764d 100644 --- a/crates/sync/src/lib.rs +++ b/crates/sync/src/lib.rs @@ -1,29 +1,7 @@ mod crdt; +mod factory; +mod model_traits; pub use crdt::*; - -use prisma_client_rust::ModelTypes; -use serde::{de::DeserializeOwned, Serialize}; - -pub trait SyncId: Serialize + DeserializeOwned { - type ModelTypes: SyncType; -} - -pub trait SyncType: ModelTypes { - type SyncId: SyncId; - type Marker: SyncTypeMarker; -} - -pub trait SyncTypeMarker {} - -pub struct LocalSyncType; -impl SyncTypeMarker for LocalSyncType {} - -pub struct OwnedSyncType; -impl SyncTypeMarker for OwnedSyncType {} - -pub struct SharedSyncType; -impl SyncTypeMarker for SharedSyncType {} - -pub struct RelationSyncType; -impl SyncTypeMarker for RelationSyncType {} +pub use factory::*; +pub use model_traits::*; diff --git a/crates/sync/src/model_traits.rs b/crates/sync/src/model_traits.rs new file mode 100644 index 000000000000..62f313f6abba --- /dev/null +++ b/crates/sync/src/model_traits.rs @@ -0,0 +1,25 @@ +use prisma_client_rust::ModelTypes; +use serde::{de::DeserializeOwned, Serialize}; + +pub trait SyncId: Serialize + DeserializeOwned { + type Model: ModelTypes; +} + +pub trait LocalSyncModel: ModelTypes { + type SyncId: SyncId; +} + +pub trait SharedSyncModel: ModelTypes { + type SyncId: SyncId; +} + +pub trait RelationSyncId: SyncId { + type ItemSyncId: SyncId; + type GroupSyncId: SyncId; + + fn split(&self) -> (&Self::ItemSyncId, &Self::GroupSyncId); +} + +pub trait RelationSyncModel: ModelTypes { + type SyncId: RelationSyncId; +} diff --git a/interface/app/$libraryId/sync.tsx b/interface/app/$libraryId/sync.tsx index 0d99a8a78327..f3ab60987753 100644 --- a/interface/app/$libraryId/sync.tsx +++ b/interface/app/$libraryId/sync.tsx @@ -11,13 +11,9 @@ const OperationItem = ({ op }: { op: CRDTOperation }) => { if ('model' in op.typ) { let subContents = null; - if (op.typ.data === 'd') { - subContents = 'Delete'; - } else if ('c' in op.typ.data) { - subContents = 'Create'; - } else { - subContents = `Update - ${op.typ.data.u.field}`; - } + if (op.typ.data === 'd') subContents = 'Delete'; + else if (op.typ.data === 'c') subContents = 'Create'; + else subContents = `Update - ${op.typ.data.u.field}`; contents = ( <> diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index bb6c65394a9a..de284e4c4153 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -263,9 +263,9 @@ export type PeerId = string export type PeerMetadata = { name: string; operating_system: OperatingSystem | null; version: string | null; email: string | null; img_url: string | null; instances: string[] } -export type RelationOperation = { relation_item: string; relation_group: string; relation: string; data: RelationOperationData } +export type RelationOperation = { relation_item: any; relation_group: any; relation: string; data: RelationOperationData } -export type RelationOperationData = "Create" | { Update: { field: string; value: any } } | "Delete" +export type RelationOperationData = "c" | { u: { field: string; value: any } } | "d" export type RenameFileArgs = { location_id: number; kind: RenameKind } @@ -287,7 +287,7 @@ export type SetNoteArgs = { id: number; note: string | null } export type SharedOperation = { record_id: any; model: string; data: SharedOperationData } -export type SharedOperationData = { c: { [key: string]: any } } | { u: { field: string; value: any } } | "d" +export type SharedOperationData = "c" | { u: { field: string; value: any } } | "d" export type SortOrder = "Asc" | "Desc" From 20eae57e7530472186804b760f7da8cd2fd666c4 Mon Sep 17 00:00:00 2001 From: Utku <74243531+utkubakir@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:47:57 +0300 Subject: [PATCH 16/18] [MOB-34] Update thumb logic (#1100) update thumb logic --- .../src/components/explorer/FileThumb.tsx | 193 +++++++++++------- .../settings/client/GeneralSettings.tsx | 16 +- apps/mobile/src/stores/explorerStore.ts | 2 +- packages/client/src/hooks/useThemeStore.ts | 4 + 4 files changed, 138 insertions(+), 77 deletions(-) diff --git a/apps/mobile/src/components/explorer/FileThumb.tsx b/apps/mobile/src/components/explorer/FileThumb.tsx index 0a5845e791c7..3a6b92a1720c 100644 --- a/apps/mobile/src/components/explorer/FileThumb.tsx +++ b/apps/mobile/src/components/explorer/FileThumb.tsx @@ -1,10 +1,53 @@ -import * as icons from '@sd/assets/icons'; -import { PropsWithChildren } from 'react'; +import { getIcon } from '@sd/assets/util'; +import { PropsWithChildren, useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { Image, View } from 'react-native'; import { DocumentDirectoryPath } from 'react-native-fs'; -import { ExplorerItem, ObjectKind, getItemFilePath, getItemObject, isPath } from '@sd/client'; +import { + ExplorerItem, + getExplorerItemData, + getItemFilePath, + getItemLocation, + isDarkTheme +} from '@sd/client'; +import { flattenThumbnailKey, useExplorerStore } from '~/stores/explorerStore'; import { tw } from '../../lib/tailwind'; -import FolderIcon from '../icons/FolderIcon'; + +export const getThumbnailUrlByThumbKey = (thumbKey: string[]) => + `${DocumentDirectoryPath}/thumbnails/${thumbKey + .map((i) => encodeURIComponent(i)) + .join('/')}.webp`; + +const FileThumbWrapper = ({ children, size = 1 }: PropsWithChildren<{ size: number }>) => ( + + {children} + +); + +function useExplorerItemData(explorerItem: ExplorerItem) { + const explorerStore = useExplorerStore(); + + const newThumbnail = !!( + explorerItem.thumbnail_key && + explorerStore.newThumbnails.has(flattenThumbnailKey(explorerItem.thumbnail_key)) + ); + + return useMemo(() => { + const itemData = getExplorerItemData(explorerItem); + + if (!itemData.hasLocalThumbnail) { + itemData.hasLocalThumbnail = newThumbnail; + } + + return itemData; + }, [explorerItem, newThumbnail]); +} + +enum ThumbType { + Icon, + // Original, + Thumbnail, + Location +} type FileThumbProps = { data: ExplorerItem; @@ -13,87 +56,87 @@ type FileThumbProps = { * default: `1` */ size?: number; + // loadOriginal?: boolean; }; -export const getThumbnailUrlById = (keyParts: string[]) => - `${DocumentDirectoryPath}/thumbnails/${keyParts - .map((i) => encodeURIComponent(i)) - .join('/')}.webp`; +export default function FileThumb({ size = 1, ...props }: FileThumbProps) { + const itemData = useExplorerItemData(props.data); + const locationData = getItemLocation(props.data); + const filePath = getItemFilePath(props.data); -type KindType = keyof typeof icons | 'Unknown'; + const [src, setSrc] = useState(null); + const [thumbType, setThumbType] = useState(ThumbType.Icon); + // const [loaded, setLoaded] = useState(false); -function getExplorerItemData(data: ExplorerItem) { - const objectData = getItemObject(data); - const filePath = getItemFilePath(data); + useLayoutEffect(() => { + // Reset src when item changes, to allow detection of yet not updated src + setSrc(null); + // setLoaded(false); - return { - casId: filePath?.cas_id || null, - isDir: isPath(data) && data.item.is_dir, - kind: ObjectKind[objectData?.kind || 0] as KindType, - hasLocalThumbnail: data.has_local_thumbnail, // this will be overwritten if new thumbnail is generated - thumbnailKey: data.thumbnail_key, - extension: filePath?.extension - }; -} + if (locationData) { + setThumbType(ThumbType.Location); + // } else if (props.loadOriginal) { + // setThumbType(ThumbType.Original); + } else if (itemData.hasLocalThumbnail) { + setThumbType(ThumbType.Thumbnail); + } else { + setThumbType(ThumbType.Icon); + } + }, [locationData, itemData]); -const FileThumbWrapper = ({ children, size = 1 }: PropsWithChildren<{ size: number }>) => ( - - {children} - -); + // This sets the src to the thumbnail url + useEffect(() => { + const { casId, kind, isDir, extension, locationId, thumbnailKey } = itemData; + + // ??? + // const locationId = + // itemLocationId ?? (parent?.type === 'Location' ? parent.location.id : null); -export default function FileThumb({ data, size = 1 }: FileThumbProps) { - const { casId, isDir, kind, hasLocalThumbnail, extension, thumbnailKey } = - getExplorerItemData(data); - - if (isPath(data) && data.item.is_dir) { - return ( - - - - ); - } - - if (hasLocalThumbnail && thumbnailKey) { - // TODO: Handle Image checkers bg? - return ( - - - - ); - } - - // Default icon - let icon = icons['Document']; - - if (isDir) { - icon = icons['Folder']; - } else if ( - kind && - extension && - icons[`${kind}_${extension.toLowerCase()}` as keyof typeof icons] - ) { - // e.g. Document_pdf - icon = icons[`${kind}_${extension.toLowerCase()}` as keyof typeof icons]; - } else if (kind !== 'Unknown' && kind && icons[kind]) { - icon = icons[kind]; - } - - // TODO: Handle video thumbnails (do we have ffmpeg on mobile?) - - // // 10 percent of the size - // const videoBarsHeight = Math.floor(size / 10); - - // // calculate 16:9 ratio for height from size - // const videoHeight = Math.floor((size * 9) / 16) + videoBarsHeight * 2; + switch (thumbType) { + // case ThumbType.Original: + // if (locationId) { + // setSrc( + // platform.getFileUrl( + // library.uuid, + // locationId, + // filePath?.id || props.data.item.id, + // // Workaround Linux webview not supporting playing video and audio through custom protocol urls + // kind == 'Video' || kind == 'Audio' + // ) + // ); + // } else { + // setThumbType(ThumbType.Thumbnail); + // } + // break; + case ThumbType.Thumbnail: + if (casId && thumbnailKey) { + setSrc(getThumbnailUrlByThumbKey(thumbnailKey)); + } else { + setThumbType(ThumbType.Icon); + } + break; + case ThumbType.Location: + setSrc(getIcon('Folder', isDarkTheme(), extension, true)); + break; + default: + if (isDir !== null) setSrc(getIcon(kind, isDarkTheme(), extension, isDir)); + break; + } + }, [filePath?.id, itemData, props.data.item.id, thumbType]); return ( - + {(() => { + if (src == null) return null; + let source = null; + // getIcon returns number for some magic reason + if (typeof src === 'number') { + source = src; + } else { + source = { uri: src }; + } + return ; + })()} ); } diff --git a/apps/mobile/src/screens/settings/client/GeneralSettings.tsx b/apps/mobile/src/screens/settings/client/GeneralSettings.tsx index 759d319d28f0..fe1cc95d8585 100644 --- a/apps/mobile/src/screens/settings/client/GeneralSettings.tsx +++ b/apps/mobile/src/screens/settings/client/GeneralSettings.tsx @@ -1,5 +1,5 @@ import { Text, View } from 'react-native'; -import { useBridgeQuery } from '@sd/client'; +import { useBridgeQuery, useDebugState } from '@sd/client'; import { Input } from '~/components/form/Input'; import Card from '~/components/layout/Card'; import { Divider } from '~/components/primitive/Divider'; @@ -10,6 +10,8 @@ import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator'; const GeneralSettingsScreen = ({ navigation }: SettingsStackScreenProps<'GeneralSettings'>) => { const { data: node } = useBridgeQuery(['nodeState']); + const debugState = useDebugState(); + if (!node) return null; return ( @@ -37,6 +39,18 @@ const GeneralSettingsScreen = ({ navigation }: SettingsStackScreenProps<'General Node Port + {/* TODO: Move this to Debug screen */} + {debugState.enabled && ( + + {/* Card Header */} + Debug + {/* Divider */} + + Data Folder + {/* Useful for simulator, not so for real devices. */} + + + )} ); }; diff --git a/apps/mobile/src/stores/explorerStore.ts b/apps/mobile/src/stores/explorerStore.ts index 6e26ddfc15cd..e2ac60f63814 100644 --- a/apps/mobile/src/stores/explorerStore.ts +++ b/apps/mobile/src/stores/explorerStore.ts @@ -17,7 +17,7 @@ const state = { newThumbnails: proxySet() as Set }; -function flattenThumbnailKey(thumbKey: string[]) { +export function flattenThumbnailKey(thumbKey: string[]) { return thumbKey.join('/'); } diff --git a/packages/client/src/hooks/useThemeStore.ts b/packages/client/src/hooks/useThemeStore.ts index 07ff71311079..dc8fbabd4f4b 100644 --- a/packages/client/src/hooks/useThemeStore.ts +++ b/packages/client/src/hooks/useThemeStore.ts @@ -16,3 +16,7 @@ export function useThemeStore() { export function getThemeStore() { return themeStore; } + +export function isDarkTheme() { + return themeStore.theme === 'dark'; +} From 53ab3178c2c93d7ffd0bc5e87fde569968781b02 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 17 Jul 2023 19:53:25 +0800 Subject: [PATCH 17/18] [ENG-906] Initial library sync (#1095) * ffs * typo * yeet library data over p2p * fix a bunch of edge cases * report complete status on responder * better log * fix types * mobile debug screen * mobile + P2P is a mess * feature flag mobile p2p pairing * wrong one --------- Co-authored-by: Brendan Allan --- apps/mobile/package.json | 1 + apps/mobile/src/App.tsx | 10 +- apps/mobile/src/main.tsx | 12 + .../src/navigation/SettingsNavigator.tsx | 7 + apps/mobile/src/screens/p2p/index.tsx | 26 ++ apps/mobile/src/screens/settings/Settings.tsx | 43 +- .../src/screens/settings/info/Debug.tsx | 35 ++ .../settings/library/NodesSettings.tsx | 36 +- core/src/library/config.rs | 19 +- core/src/library/manager.rs | 21 +- core/src/object/orphan_remover.rs | 70 ++-- core/src/p2p/pairing/initial_sync.rs | 368 ++++++++++++++++++ core/src/p2p/pairing/mod.rs | 88 ++++- core/src/p2p/pairing/proto.rs | 86 +++- core/src/util/debug_initializer.rs | 2 +- .../app/$libraryId/settings/library/nodes.tsx | 2 +- .../$libraryId/settings/resources/about.tsx | 18 +- interface/app/p2p/index.tsx | 5 +- interface/app/p2p/pairing.tsx | 3 + packages/client/src/core.ts | 2 +- packages/client/src/hooks/useDebugState.ts | 17 + packages/client/src/hooks/useP2PEvents.tsx | 2 +- pnpm-lock.yaml | 7 + 23 files changed, 787 insertions(+), 93 deletions(-) create mode 100644 apps/mobile/src/screens/p2p/index.tsx create mode 100644 apps/mobile/src/screens/settings/info/Debug.tsx create mode 100644 core/src/p2p/pairing/initial_sync.rs diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 1177ad9c2970..de1762bb428e 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -32,6 +32,7 @@ "@tanstack/react-query": "^4.29.1", "class-variance-authority": "^0.5.3", "dayjs": "^1.11.8", + "event-target-polyfill": "^0.0.3", "expo": "~48.0.19", "expo-linking": "~4.0.1", "expo-media-library": "~15.2.3", diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index 5e3aaab99652..b64cd29d4459 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -19,6 +19,8 @@ import { useSnapshot } from 'valtio'; import { ClientContextProvider, LibraryContextProvider, + NotificationContextProvider, + P2PContextProvider, RspcProvider, initPlausible, useClientContext, @@ -30,6 +32,7 @@ import { useTheme } from './hooks/useTheme'; import { changeTwTheme, tw } from './lib/tailwind'; import RootNavigator from './navigation'; import OnboardingNavigator from './navigation/OnboardingNavigator'; +import { P2P } from './screens/p2p'; import { currentLibraryStore } from './utils/nav'; dayjs.extend(advancedFormat); @@ -111,7 +114,12 @@ function AppContainer() { - + + + + + + diff --git a/apps/mobile/src/main.tsx b/apps/mobile/src/main.tsx index 6d0139bcc1ff..fe2960fbc8f4 100644 --- a/apps/mobile/src/main.tsx +++ b/apps/mobile/src/main.tsx @@ -1,4 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; +import 'event-target-polyfill'; import * as SplashScreen from 'expo-splash-screen'; import { Suspense, lazy } from 'react'; import { Platform } from 'react-native'; @@ -7,6 +8,17 @@ import { reactNativeLink } from './lib/rspcReactNativeTransport'; // Enable the splash screen SplashScreen.preventAutoHideAsync(); +// The worlds worse pollyfill for "CustomEvent". I tried "custom-event-pollyfill" from npm but it uses `document` :( +if (typeof globalThis.CustomEvent !== 'function') { + // @ts-expect-error + globalThis.CustomEvent = (event, params) => { + const evt = new Event(event, params); + // @ts-expect-error + evt.detail = params.detail; + return evt; + }; +} + const _localStorage = new Map(); // We patch stuff onto `globalThis` so that `@sd/client` can use it. This is super hacky but as far as I can tell, there's no better way to do this. diff --git a/apps/mobile/src/navigation/SettingsNavigator.tsx b/apps/mobile/src/navigation/SettingsNavigator.tsx index 116ec478a820..dc93c6644e25 100644 --- a/apps/mobile/src/navigation/SettingsNavigator.tsx +++ b/apps/mobile/src/navigation/SettingsNavigator.tsx @@ -7,6 +7,7 @@ import GeneralSettingsScreen from '~/screens/settings/client/GeneralSettings'; import LibrarySettingsScreen from '~/screens/settings/client/LibrarySettings'; import PrivacySettingsScreen from '~/screens/settings/client/PrivacySettings'; import AboutScreen from '~/screens/settings/info/About'; +import DebugScreen from '~/screens/settings/info/Debug'; import SupportScreen from '~/screens/settings/info/Support'; import EditLocationSettingsScreen from '~/screens/settings/library/EditLocationSettings'; // import KeysSettingsScreen from '~/screens/settings/library/KeysSettings'; @@ -104,6 +105,11 @@ export default function SettingsNavigator() { component={SupportScreen} options={{ headerTitle: 'Support' }} /> + ); } @@ -130,6 +136,7 @@ export type SettingsStackParamList = { // Info About: undefined; Support: undefined; + Debug: undefined; }; export type SettingsStackScreenProps = diff --git a/apps/mobile/src/screens/p2p/index.tsx b/apps/mobile/src/screens/p2p/index.tsx new file mode 100644 index 000000000000..cf3980bbdd9e --- /dev/null +++ b/apps/mobile/src/screens/p2p/index.tsx @@ -0,0 +1,26 @@ +import { useBridgeMutation, useFeatureFlag, useLibraryContext, useP2PEvents } from '@sd/client'; + +export function P2P() { + // const pairingResponse = useBridgeMutation('p2p.pairingResponse'); + // const activeLibrary = useLibraryContext(); + + const pairingEnabled = useFeatureFlag('p2pPairing'); + useP2PEvents((data) => { + if (data.type === 'PairingRequest' && pairingEnabled) { + console.log('Pairing incoming from', data.name); + + // TODO: open pairing screen and guide user through the process. For now we auto-accept + // pairingResponse.mutate([ + // data.id, + // { decision: 'accept', libraryId: activeLibrary.library.uuid } + // ]); + } + + // TODO: For now until UI is implemented + if (data.type === 'PairingProgress') { + console.log('Pairing progress', data); + } + }); + + return null; +} diff --git a/apps/mobile/src/screens/settings/Settings.tsx b/apps/mobile/src/screens/settings/Settings.tsx index 2f67a0f78fb5..d4eb74de8699 100644 --- a/apps/mobile/src/screens/settings/Settings.tsx +++ b/apps/mobile/src/screens/settings/Settings.tsx @@ -1,6 +1,7 @@ import { Books, FlyingSaucer, + Gear, GearSix, HardDrive, Heart, @@ -12,7 +13,8 @@ import { TagSimple } from 'phosphor-react-native'; import React from 'react'; -import { SectionList, Text, View } from 'react-native'; +import { SectionList, Text, TouchableWithoutFeedback, View } from 'react-native'; +import { DebugState, useDebugState, useDebugStateEnabler } from '@sd/client'; import { SettingsItem, SettingsItemDivider } from '~/components/settings/SettingsItem'; import { tw, twStyle } from '~/lib/tailwind'; import { SettingsStackParamList, SettingsStackScreenProps } from '~/navigation/SettingsNavigator'; @@ -26,7 +28,7 @@ type SectionType = { }[]; }; -const sections: SectionType[] = [ +const sections: (debugState: DebugState) => SectionType[] = (debugState) => [ { title: 'Client', data: [ @@ -99,7 +101,16 @@ const sections: SectionType[] = [ icon: Heart, navigateTo: 'Support', title: 'Support' - } + }, + ...(debugState.enabled + ? ([ + { + icon: Gear, + navigateTo: 'Debug', + title: 'Debug' + } + ] as const) + : []) ] } ]; @@ -118,10 +129,12 @@ function renderSectionHeader({ section }: { section: { title: string } }) { } export default function SettingsScreen({ navigation }: SettingsStackScreenProps<'Home'>) { + const debugState = useDebugState(); + return ( ( @@ -132,13 +145,7 @@ export default function SettingsScreen({ navigation }: SettingsStackScreenProps< /> )} renderSectionHeader={renderSectionHeader} - ListFooterComponent={ - - Spacedrive - {/* TODO: Get this automatically (expo-device have this?) */} - v0.1.0 - - } + ListFooterComponent={} showsVerticalScrollIndicator={false} stickySectionHeadersEnabled={false} initialNumToRender={50} @@ -146,3 +153,17 @@ export default function SettingsScreen({ navigation }: SettingsStackScreenProps< ); } + +function FooterComponent() { + const onClick = useDebugStateEnabler(); + + return ( + + + Spacedrive + + {/* TODO: Get this automatically (expo-device have this?) */} + v0.1.0 + + ); +} diff --git a/apps/mobile/src/screens/settings/info/Debug.tsx b/apps/mobile/src/screens/settings/info/Debug.tsx new file mode 100644 index 000000000000..72df1e263f8e --- /dev/null +++ b/apps/mobile/src/screens/settings/info/Debug.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Text, View } from 'react-native'; +import { getDebugState, toggleFeatureFlag, useDebugState, useFeatureFlags } from '@sd/client'; +import { Button } from '~/components/primitive/Button'; +import { tw } from '~/lib/tailwind'; +import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator'; + +const DebugScreen = ({ navigation }: SettingsStackScreenProps<'Debug'>) => { + const debugState = useDebugState(); + const featureFlags = useFeatureFlags(); + return ( + + Debug + + + {JSON.stringify(featureFlags)} + {JSON.stringify(debugState)} + + + ); +}; + +export default DebugScreen; diff --git a/apps/mobile/src/screens/settings/library/NodesSettings.tsx b/apps/mobile/src/screens/settings/library/NodesSettings.tsx index e54180a523e2..a214e91d2ed6 100644 --- a/apps/mobile/src/screens/settings/library/NodesSettings.tsx +++ b/apps/mobile/src/screens/settings/library/NodesSettings.tsx @@ -1,12 +1,46 @@ import React from 'react'; import { Text, View } from 'react-native'; +import { isEnabled, useBridgeMutation, useDiscoveredPeers } from '@sd/client'; +import { Button } from '~/components/primitive/Button'; import { tw } from '~/lib/tailwind'; import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator'; const NodesSettingsScreen = ({ navigation }: SettingsStackScreenProps<'NodesSettings'>) => { + const onlineNodes = useDiscoveredPeers(); + const p2pPair = useBridgeMutation('p2p.pair', { + onSuccess(data) { + console.log(data); + } + }); + return ( - TODO + Pairing + + {[...onlineNodes.entries()].map(([id, node]) => ( + + {node.name} + + + + ))} ); }; diff --git a/core/src/library/config.rs b/core/src/library/config.rs index c367ccee7a18..04e02092972f 100644 --- a/core/src/library/config.rs +++ b/core/src/library/config.rs @@ -35,7 +35,7 @@ pub struct LibraryConfig { #[async_trait::async_trait] impl Migrate for LibraryConfig { - const CURRENT_VERSION: u32 = 7; + const CURRENT_VERSION: u32 = 8; type Ctx = (NodeConfig, Arc); @@ -200,7 +200,7 @@ impl Migrate for LibraryConfig { if instances.len() > 1 { return Err(MigratorError::Custom( - "7 - More than one node found in the DB... This can't be automatically reconciled!" + "7 - More than one instance found in the DB... This can't be automatically reconciled!" .into(), )); } @@ -210,6 +210,9 @@ impl Migrate for LibraryConfig { )); }; + config.remove("instance_id"); + config.insert("instance_id".into(), Value::Number(instance.id.into())); + // We are relinking all locations to the current instance. // If you have more than one node in your database and your not @Oscar, something went horribly wrong so this is fine. db.location() @@ -217,6 +220,18 @@ impl Migrate for LibraryConfig { .exec() .await?; } + 8 => { + let instances = db.instance().find_many(vec![]).exec().await?; + let Some(instance) = instances.first() else { + return Err(MigratorError::Custom( + "8 - No nodes found... How did you even get this far?!".into(), + )); + }; + + // This should be in 7 but it's added to ensure to hell it runs. + config.remove("instance_id"); + config.insert("instance_id".into(), Value::Number(instance.id.into())); + } v => unreachable!("Missing migration for library version {}", v), } diff --git a/core/src/library/manager.rs b/core/src/library/manager.rs index 9927f9878961..02f5fadb235a 100644 --- a/core/src/library/manager.rs +++ b/core/src/library/manager.rs @@ -169,6 +169,7 @@ impl LibraryManager { node_context.clone(), &subscribers, None, + true, ) .await?, ); @@ -202,7 +203,7 @@ impl LibraryManager { description: Option, node_cfg: NodeConfig, ) -> Result { - self.create_with_uuid(Uuid::new_v4(), name, description, node_cfg) + self.create_with_uuid(Uuid::new_v4(), name, description, node_cfg, true) .await } @@ -212,6 +213,7 @@ impl LibraryManager { name: LibraryName, description: Option, node_cfg: NodeConfig, + should_seed: bool, ) -> Result { if name.as_ref().is_empty() || name.as_ref().chars().all(|x| x.is_whitespace()) { return Err(LibraryManagerError::InvalidConfig( @@ -251,16 +253,17 @@ impl LibraryManager { date_created: now, _params: vec![instance::id::set(config.instance_id)], }), + should_seed, ) .await?; debug!("Loaded library '{id:?}'"); - // Run seeders - tag::seed::new_library(&library).await?; - indexer::rules::seed::new_or_existing_library(&library).await?; - - debug!("Seeded library '{id:?}'"); + if should_seed { + tag::seed::new_library(&library).await?; + indexer::rules::seed::new_or_existing_library(&library).await?; + debug!("Seeded library '{id:?}'"); + } invalidate_query!(library, "library.list"); @@ -398,6 +401,7 @@ impl LibraryManager { node_context: NodeContext, subscribers: &RwLock>>, create: Option, + should_seed: bool, ) -> Result { let db_path = db_path.as_ref(); let db_url = format!( @@ -476,7 +480,10 @@ impl LibraryManager { identity, }; - indexer::rules::seed::new_or_existing_library(&library).await?; + if should_seed { + library.orphan_remover.invoke().await; + indexer::rules::seed::new_or_existing_library(&library).await?; + } for location in library .db diff --git a/core/src/object/orphan_remover.rs b/core/src/object/orphan_remover.rs index 789c3264b3fd..71df0eefec6d 100644 --- a/core/src/object/orphan_remover.rs +++ b/core/src/object/orphan_remover.rs @@ -14,50 +14,44 @@ impl OrphanRemoverActor { pub fn spawn(db: Arc) -> Self { let (tx, mut rx) = channel(4); - tokio::spawn({ - let tx = tx.clone(); - async move { - tx.send(()).await.ok(); + tokio::spawn(async move { + while let Some(()) = rx.recv().await { + // prevents timeouts + tokio::time::sleep(Duration::from_millis(10)).await; - while let Some(()) = rx.recv().await { - // prevents timeouts - tokio::time::sleep(Duration::from_millis(10)).await; - - loop { - let objs = match db - .object() - .find_many(vec![object::file_paths::none(vec![])]) - .take(512) - .select(object::select!({ id pub_id })) - .exec() - .await - { - Ok(objs) => objs, - Err(e) => { - error!("Failed to fetch orphaned objects: {e}"); - break; - } - }; - - if objs.is_empty() { + loop { + let objs = match db + .object() + .find_many(vec![object::file_paths::none(vec![])]) + .take(512) + .select(object::select!({ id pub_id })) + .exec() + .await + { + Ok(objs) => objs, + Err(e) => { + error!("Failed to fetch orphaned objects: {e}"); break; } + }; - debug!("Removing {} orphaned objects", objs.len()); + if objs.is_empty() { + break; + } - let ids: Vec<_> = objs.iter().map(|o| o.id).collect(); + debug!("Removing {} orphaned objects", objs.len()); - if let Err(e) = db - ._batch(( - db.tag_on_object().delete_many(vec![ - tag_on_object::object_id::in_vec(ids.clone()), - ]), - db.object().delete_many(vec![object::id::in_vec(ids)]), - )) - .await - { - error!("Failed to remove orphaned objects: {e}"); - } + let ids: Vec<_> = objs.iter().map(|o| o.id).collect(); + + if let Err(e) = db + ._batch(( + db.tag_on_object() + .delete_many(vec![tag_on_object::object_id::in_vec(ids.clone())]), + db.object().delete_many(vec![object::id::in_vec(ids)]), + )) + .await + { + error!("Failed to remove orphaned objects: {e}"); } } } diff --git a/core/src/p2p/pairing/initial_sync.rs b/core/src/p2p/pairing/initial_sync.rs new file mode 100644 index 000000000000..bea6bf448cb8 --- /dev/null +++ b/core/src/p2p/pairing/initial_sync.rs @@ -0,0 +1,368 @@ +use sd_prisma::prisma::*; + +// TODO: Turn this entire file into a Prisma generator cause it could be way more maintainable + +// Pairing will fail if the two clients aren't on versions with identical DB models so it's safe to send them and ignore migrations. + +const ITEMS_PER_BATCH: i64 = 1000; + +macro_rules! impl_for_models { + ($($variant:ident($model:ident)),* $(,)+) => { + /// Represents any DB model to be ingested into the database as part of the initial sync + #[derive(Debug, serde::Serialize, serde::Deserialize)] + pub enum ModelData { + $( + $variant(Vec<$model::Data>), + )* + } + + impl ModelData { + /// Length of data + pub fn len(&self) -> usize { + match self { + $( + Self::$variant(data) => data.len(), + )* + } + } + + /// Get count of all of the rows in the database + pub async fn total_count(db: &PrismaClient) -> Result { + let mut total_count = 0; + + let ($( $model ),*) = tokio::join!( + $( + db.$model().count(vec![]).exec(), + )* + ); + + $(total_count += $model?;)* + Ok(total_count) + } + + /// Insert the data into the database + pub async fn insert(self, db: &PrismaClient) -> Result<(), prisma_client_rust::QueryError> { + match self { + $( + Self::$variant(data) => { + // TODO: Prisma Client Rust is broke + // db.$model().create_many(data.into_iter().map(|v| FromData(v).into()).collect()).exec().await?; + + for i in data { + $model::CreateUnchecked::from(FromData(i)).to_query(db).exec().await?; + } + } + )* + } + + Ok(()) + } + } + + /// This exists to determine the next model to sync. + /// It emulates `.window()` functionality but for a `macro_rules` + // TODO: When replacing with a generator this can be removed and done at compile time + #[derive(Debug)] + enum ModelSyncCursorIterator { + Done = 0, + $( + $variant, + )* + } + + impl<'a> From<&'a ModelSyncCursor> for ModelSyncCursorIterator { + fn from(cursor: &'a ModelSyncCursor) -> Self { + match cursor { + $( + ModelSyncCursor::$variant(_) => Self::$variant, + )* + ModelSyncCursor::Done => Self::Done, + } + } + } + + impl ModelSyncCursorIterator { + pub fn next(self) -> ModelSyncCursor { + let i = self as i32; + match i + 1 { + $( + v if v == Self::$variant as i32 => ModelSyncCursor::$variant(0), + )* + _ => ModelSyncCursor::Done, + } + } + } + + /// Represent where we ar eup to with the sync + #[derive(Debug, serde::Serialize, serde::Deserialize)] + pub enum ModelSyncCursor { + $( + $variant(i64), + )* + Done, + } + + impl ModelSyncCursor { + pub fn new() -> Self { + new_impl!($( $variant ),*) + } + + pub async fn next(&mut self, db: &PrismaClient) -> Option> { + match self { + $( + Self::$variant(cursor) => { + match db.$model() + .find_many(vec![]) + .skip(*cursor) + .take(ITEMS_PER_BATCH + 1) + .exec() + .await { + Ok(data) => { + if data.len() <= ITEMS_PER_BATCH as usize { + *self = ModelSyncCursorIterator::from(&*self).next(); + } else { + *self = Self::$variant(*cursor + ITEMS_PER_BATCH); + } + + Some(Ok(ModelData::$variant(data))) + }, + Err(e) => return Some(Err(e)), + } + }, + )* + Self::Done => None + } + } + } + }; +} + +macro_rules! new_impl { + ($x:ident, $($y:ident),+) => { + Self::$x(0) + }; +} + +impl PartialEq for ModelData { + // Crude EQ impl based only on ID's not struct content. + // It's super annoying PCR does have this impl but it kinda makes sense with relation fetching. + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (ModelData::SharedOperation(a), ModelData::SharedOperation(b)) => a + .iter() + .map(|x| x.id.clone()) + .eq(b.iter().map(|x| x.id.clone())), + (ModelData::Volume(a), ModelData::Volume(b)) => { + a.iter().map(|x| x.id).eq(b.iter().map(|x| x.id)) + } + (ModelData::Location(a), ModelData::Location(b)) => { + a.iter().map(|x| x.id).eq(b.iter().map(|x| x.id)) + } + (ModelData::FilePath(a), ModelData::FilePath(b)) => { + a.iter().map(|x| x.id).eq(b.iter().map(|x| x.id)) + } + (ModelData::Object(a), ModelData::Object(b)) => { + a.iter().map(|x| x.id).eq(b.iter().map(|x| x.id)) + } + (ModelData::Tag(a), ModelData::Tag(b)) => { + a.iter().map(|x| x.id).eq(b.iter().map(|x| x.id)) + } + (ModelData::TagOnObject(a), ModelData::TagOnObject(b)) => a + .iter() + .map(|x| (x.tag_id, x.object_id)) + .eq(b.iter().map(|x| (x.tag_id, x.object_id))), + (ModelData::IndexerRule(a), ModelData::IndexerRule(b)) => { + a.iter().map(|x| x.id).eq(b.iter().map(|x| x.id)) + } + (ModelData::IndexerRulesInLocation(a), ModelData::IndexerRulesInLocation(b)) => a + .iter() + .map(|x| (x.location_id, x.indexer_rule_id)) + .eq(b.iter().map(|x| (x.location_id, x.indexer_rule_id))), + (ModelData::Preference(a), ModelData::Preference(b)) => a + .iter() + .map(|x| (x.key.clone(), x.value.clone())) + .eq(b.iter().map(|x| (x.key.clone(), x.value.clone()))), + _ => false, + } + } +} + +/// Meaningless wrapper to avoid Rust's orphan rule +struct FromData(T); + +impl From> for shared_operation::CreateUnchecked { + fn from(FromData(data): FromData) -> Self { + Self { + id: data.id, + timestamp: data.timestamp, + model: data.model, + record_id: data.record_id, + kind: data.kind, + data: data.data, + instance_id: data.instance_id, + _params: vec![], + } + } +} + +impl From> for volume::CreateUnchecked { + fn from(FromData(data): FromData) -> Self { + Self { + name: data.name, + mount_point: data.mount_point, + _params: vec![ + volume::id::set(data.id), + volume::total_bytes_capacity::set(data.total_bytes_capacity), + volume::total_bytes_available::set(data.total_bytes_available), + volume::disk_type::set(data.disk_type), + volume::filesystem::set(data.filesystem), + volume::is_system::set(data.is_system), + volume::date_modified::set(data.date_modified), + ], + } + } +} + +impl From> for location::CreateUnchecked { + fn from(FromData(data): FromData) -> Self { + Self { + pub_id: data.pub_id, + _params: vec![ + location::id::set(data.id), + location::name::set(data.name), + location::path::set(data.path), + location::total_capacity::set(data.total_capacity), + location::available_capacity::set(data.available_capacity), + location::is_archived::set(data.is_archived), + location::generate_preview_media::set(data.generate_preview_media), + location::sync_preview_media::set(data.sync_preview_media), + location::hidden::set(data.hidden), + location::date_created::set(data.date_created), + location::instance_id::set(data.instance_id), + ], + } + } +} + +impl From> for file_path::CreateUnchecked { + fn from(FromData(data): FromData) -> Self { + Self { + pub_id: data.pub_id, + _params: vec![ + file_path::id::set(data.id), + file_path::is_dir::set(data.is_dir), + file_path::cas_id::set(data.cas_id), + file_path::integrity_checksum::set(data.integrity_checksum), + file_path::location_id::set(data.location_id), + file_path::materialized_path::set(data.materialized_path), + file_path::name::set(data.name), + file_path::extension::set(data.extension), + file_path::size_in_bytes::set(data.size_in_bytes), + file_path::size_in_bytes_bytes::set(data.size_in_bytes_bytes), + file_path::inode::set(data.inode), + file_path::device::set(data.device), + file_path::object_id::set(data.object_id), + file_path::key_id::set(data.key_id), + file_path::date_created::set(data.date_created), + file_path::date_modified::set(data.date_modified), + file_path::date_indexed::set(data.date_indexed), + ], + } + } +} + +impl From> for object::CreateUnchecked { + fn from(FromData(data): FromData) -> Self { + Self { + pub_id: data.pub_id, + _params: vec![ + object::id::set(data.id), + object::kind::set(data.kind), + object::key_id::set(data.key_id), + object::hidden::set(data.hidden), + object::favorite::set(data.favorite), + object::important::set(data.important), + object::note::set(data.note), + object::date_created::set(data.date_created), + object::date_accessed::set(data.date_accessed), + ], + } + } +} + +impl From> for tag::CreateUnchecked { + fn from(FromData(data): FromData) -> Self { + Self { + pub_id: data.pub_id, + _params: vec![ + tag::id::set(data.id), + tag::name::set(data.name), + tag::color::set(data.color), + tag::redundancy_goal::set(data.redundancy_goal), + tag::date_created::set(data.date_created), + tag::date_modified::set(data.date_modified), + ], + } + } +} + +impl From> for tag_on_object::CreateUnchecked { + fn from(FromData(data): FromData) -> Self { + Self { + tag_id: data.tag_id, + object_id: data.object_id, + _params: vec![], + } + } +} + +impl From> for indexer_rule::CreateUnchecked { + fn from(FromData(data): FromData) -> Self { + Self { + pub_id: data.pub_id, + _params: vec![ + indexer_rule::id::set(data.id), + indexer_rule::name::set(data.name), + indexer_rule::default::set(data.default), + indexer_rule::rules_per_kind::set(data.rules_per_kind), + indexer_rule::date_created::set(data.date_created), + indexer_rule::date_modified::set(data.date_modified), + ], + } + } +} + +impl From> + for indexer_rules_in_location::CreateUnchecked +{ + fn from(FromData(data): FromData) -> Self { + Self { + location_id: data.location_id, + indexer_rule_id: data.indexer_rule_id, + _params: vec![], + } + } +} + +impl From> for preference::CreateUnchecked { + fn from(FromData(data): FromData) -> Self { + Self { + key: data.key, + _params: vec![preference::value::set(data.value)], + } + } +} + +// Ensure you order the models to Foreign Keys are created before the models that reference them. +impl_for_models! { + Object(object), + SharedOperation(shared_operation), + Volume(volume), + Location(location), + FilePath(file_path), + Tag(tag), + TagOnObject(tag_on_object), + IndexerRule(indexer_rule), + IndexerRulesInLocation(indexer_rules_in_location), + Preference(preference), +} diff --git a/core/src/p2p/pairing/mod.rs b/core/src/p2p/pairing/mod.rs index 5df0c3736713..5bcaca8b87ed 100644 --- a/core/src/p2p/pairing/mod.rs +++ b/core/src/p2p/pairing/mod.rs @@ -16,11 +16,13 @@ use tokio::{ io::{AsyncRead, AsyncWrite, AsyncWriteExt}, sync::broadcast, }; -use tracing::info; +use tracing::{debug, info}; use uuid::Uuid; +mod initial_sync; mod proto; +pub use initial_sync::*; use proto::*; use crate::{ @@ -117,6 +119,21 @@ impl PairingManager { // TODO: Future - Library in pairing state // TODO: Create library + if self + .library_manager + .get_all_libraries() + .await + .into_iter() + .find(|i| i.id == library_id) + .is_some() + { + self.emit_progress(pairing_id, PairingStatus::LibraryAlreadyExists); + + // TODO: Properly handle this at a protocol level so the error is on both sides + + return; + } + let library_config = self .library_manager .create_with_uuid( @@ -124,6 +141,7 @@ impl PairingManager { LibraryName::new(library_name).unwrap(), library_description, node_config, + false, // We will sync everything which will conflict with the seeded stuff ) .await .unwrap(); @@ -144,12 +162,36 @@ impl PairingManager { // 3. // TODO: Either rollback or update library out of pairing state - // TODO: Fake initial sync + // TODO: This should timeout if taking too long so it can't be used as a DOS style thing??? + let mut total = 0; + let mut synced = 0; + loop { + match SyncData::from_stream(&mut stream).await.unwrap() { + SyncData::Data { total_models, data } => { + if let Some(total_models) = total_models { + total = total_models; + } + synced += data.len(); + + data.insert(&library.db).await.unwrap(); + + // Prevent divide by zero + if total != 0 { + self.emit_progress( + pairing_id, + PairingStatus::InitialSyncProgress( + ((synced as f32 / total as f32) * 100.0) as u8, + ), + ); + } + } + SyncData::Finished => break, + } + } // TODO: Done message to frontend self.emit_progress(pairing_id, PairingStatus::PairingComplete(library_id)); - - tokio::time::sleep(std::time::Duration::from_secs(30)).await; // TODO + stream.flush().await.unwrap(); } PairingResponse::Rejected => { info!("Pairing '{pairing_id}' rejected by remote"); @@ -232,12 +274,41 @@ impl PairingManager { // TODO: Pairing confirmation + rollback - stream.flush().await.unwrap(); - tokio::time::sleep(std::time::Duration::from_secs(30)).await; // TODO + let total = ModelData::total_count(&library.db).await.unwrap(); + let mut synced = 0; + info!("Starting sync of {} rows", total); + + let mut cursor = ModelSyncCursor::new(); + while let Some(data) = cursor.next(&library.db).await { + let data = data.unwrap(); + let total_models = match synced { + 0 => Some(total), + _ => None, + }; + synced += data.len(); + self.emit_progress( + pairing_id, + PairingStatus::InitialSyncProgress((synced as f32 / total as f32 * 100.0) as u8), // SAFETY: It's a percentage + ); + debug!( + "Initial library sync cursor={:?} items={}", + cursor, + data.len() + ); + + stream + .write_all(&SyncData::Data { total_models, data }.to_bytes().unwrap()) + .await + .unwrap(); + } - // }; + stream + .write_all(&SyncData::Finished.to_bytes().unwrap()) + .await + .unwrap(); - // inner().await.unwrap(); + self.emit_progress(pairing_id, PairingStatus::PairingComplete(library_id)); + stream.flush().await.unwrap(); } } @@ -253,6 +324,7 @@ pub enum PairingDecision { pub enum PairingStatus { EstablishingConnection, PairingRequested, + LibraryAlreadyExists, PairingDecisionRequest, PairingInProgress { library_name: String, diff --git a/core/src/p2p/pairing/proto.rs b/core/src/p2p/pairing/proto.rs index c86930a7ede2..2019e6092fb0 100644 --- a/core/src/p2p/pairing/proto.rs +++ b/core/src/p2p/pairing/proto.rs @@ -5,12 +5,14 @@ use sd_p2p::{ proto::{decode, encode}, spacetunnel::Identity, }; -use sd_prisma::prisma::instance; +use sd_prisma::prisma::*; use tokio::io::{AsyncRead, AsyncReadExt}; use uuid::Uuid; use crate::node::Platform; +use super::ModelData; + /// Terminology: /// Instance - DB model which represents a single `.db` file. /// Originator - begins the pairing process and is asking to join a library that will be selected by the responder. @@ -76,6 +78,19 @@ pub enum PairingConfirmation { Error, } +/// 4. Sync the data in the database with the originator. +/// Sent `Responder` -> `Originator`. +#[derive(Debug, PartialEq)] +pub enum SyncData { + Data { + /// Only included in first request and is an **estimate** of how many models will be sent. + /// It will likely be wrong so should be constrained to being used for UI purposes only. + total_models: Option, + data: ModelData, + }, + Finished, +} + impl Instance { pub async fn from_stream( stream: &mut (impl AsyncRead + Unpin), @@ -211,9 +226,7 @@ impl PairingConfirmation { match stream.read_u8().await.unwrap() { 0 => Ok(Self::Ok), 1 => Ok(Self::Error), - _ => { - todo!(); - } + _ => todo!(), // TODO: Error handling } } @@ -225,6 +238,52 @@ impl PairingConfirmation { } } +impl SyncData { + pub async fn from_stream( + stream: &mut (impl AsyncRead + Unpin), + ) -> Result { + let discriminator = stream + .read_u8() + .await + .map_err(|e| ("discriminator", e.into()))?; + + match discriminator { + 0 => Ok(Self::Data { + total_models: match stream + .read_i64_le() + .await + .map_err(|e| ("total_models", e.into()))? + { + 0 => None, + n => Some(n), + }, + data: rmp_serde::from_slice( + &decode::buf(stream).await.map_err(|e| ("data", e.into()))?, + ) + .unwrap(), // TODO: Error handling + }), + 1 => Ok(Self::Finished), + _ => todo!(), // TODO: Error handling + } + } + + pub fn to_bytes(&self) -> Result, rmp_serde::encode::Error> { + let mut buf = Vec::new(); + match self { + Self::Data { total_models, data } => { + buf.push(0); + buf.extend((total_models.unwrap_or(0) as i64).to_le_bytes()); + encode::buf(&mut buf, &rmp_serde::to_vec_named(data)?); + } + Self::Finished => { + buf.push(1); + } + } + + Ok(buf) + } +} + #[cfg(test)] mod tests { use super::*; @@ -298,5 +357,24 @@ mod tests { let result = PairingConfirmation::from_stream(&mut cursor).await.unwrap(); assert_eq!(original, result); } + + { + let original = SyncData::Data { + total_models: Some(123), + data: ModelData::Location(vec![]), + }; + + let mut cursor = std::io::Cursor::new(original.to_bytes().unwrap()); + let result = SyncData::from_stream(&mut cursor).await.unwrap(); + assert_eq!(original, result); + } + + { + let original = SyncData::Finished; + + let mut cursor = std::io::Cursor::new(original.to_bytes().unwrap()); + let result = SyncData::from_stream(&mut cursor).await.unwrap(); + assert_eq!(original, result); + } } } diff --git a/core/src/util/debug_initializer.rs b/core/src/util/debug_initializer.rs index 337d27aae99b..378e44da17c2 100644 --- a/core/src/util/debug_initializer.rs +++ b/core/src/util/debug_initializer.rs @@ -114,7 +114,7 @@ impl InitConfig { Some(lib) => lib, None => { let library = library_manager - .create_with_uuid(lib.id, lib.name, lib.description, node_cfg.clone()) + .create_with_uuid(lib.id, lib.name, lib.description, node_cfg.clone(), true) .await?; match library_manager.get_library(library.uuid).await { diff --git a/interface/app/$libraryId/settings/library/nodes.tsx b/interface/app/$libraryId/settings/library/nodes.tsx index 74da6a66ac79..afb37e58324d 100644 --- a/interface/app/$libraryId/settings/library/nodes.tsx +++ b/interface/app/$libraryId/settings/library/nodes.tsx @@ -1,4 +1,4 @@ -import { useBridgeMutation, useDiscoveredPeers, useFeatureFlag } from '@sd/client'; +import { isEnabled, useBridgeMutation, useDiscoveredPeers, useFeatureFlag } from '@sd/client'; import { Button } from '@sd/ui'; import { startPairing } from '~/app/p2p/pairing'; import { Heading } from '../Layout'; diff --git a/interface/app/$libraryId/settings/resources/about.tsx b/interface/app/$libraryId/settings/resources/about.tsx index f469ad50ca9f..f9b90ec5d559 100644 --- a/interface/app/$libraryId/settings/resources/about.tsx +++ b/interface/app/$libraryId/settings/resources/about.tsx @@ -1,8 +1,7 @@ import { AppLogo } from '@sd/assets/images'; import { Discord, Github } from '@sd/assets/svgs/brands'; import { Globe } from 'phosphor-react'; -import { useEffect, useState } from 'react'; -import { getDebugState, useBridgeQuery } from '@sd/client'; +import { useBridgeQuery, useDebugStateEnabler } from '@sd/client'; import { Button, Divider } from '@sd/ui'; import { useOperatingSystem } from '~/hooks/useOperatingSystem'; import { usePlatform } from '~/util/Platform'; @@ -13,18 +12,7 @@ export const Component = () => { const os = useOperatingSystem(); const currentPlatformNiceName = os === 'browser' ? 'Web' : os == 'macOS' ? os : os.charAt(0).toUpperCase() + os.slice(1); - - const [clicked, setClicked] = useState(0); - - useEffect(() => { - if (clicked >= 5) { - getDebugState().enabled = true; - } - - const timeout = setTimeout(() => setClicked(0), 1000); - - return () => clearTimeout(timeout); - }, [clicked]); + const onClick = useDebugStateEnabler(); return (
@@ -33,7 +21,7 @@ export const Component = () => { src={AppLogo} className="mr-8 h-[88px] w-[88px]" draggable="false" - onClick={() => setClicked((clicked) => clicked + 1)} + onClick={onClick} />

diff --git a/interface/app/p2p/index.tsx b/interface/app/p2p/index.tsx index cf3153e21c30..ea4a0ae49621 100644 --- a/interface/app/p2p/index.tsx +++ b/interface/app/p2p/index.tsx @@ -1,4 +1,4 @@ -import { useOnFeatureFlagsChange, useP2PEvents, withFeatureFlag } from '@sd/client'; +import { useFeatureFlag, useP2PEvents, withFeatureFlag } from '@sd/client'; import { SpacedropUI } from './Spacedrop'; import { startPairing } from './pairing'; @@ -6,8 +6,9 @@ export const SpacedropUI2 = withFeatureFlag('spacedrop', SpacedropUI); // Entrypoint of P2P UI export function P2P() { + const pairingEnabled = useFeatureFlag('p2pPairing'); useP2PEvents((data) => { - if (data.type === 'PairingRequest') { + if (data.type === 'PairingRequest' && pairingEnabled) { startPairing(data.id, { name: data.name, os: data.os diff --git a/interface/app/p2p/pairing.tsx b/interface/app/p2p/pairing.tsx index c32572b07af9..d890a82529ca 100644 --- a/interface/app/p2p/pairing.tsx +++ b/interface/app/p2p/pairing.tsx @@ -60,6 +60,9 @@ function OriginatorDialog({ .with({ type: 'PairingRequested' }, () => ( )) + .with({ type: 'LibraryAlreadyExists' }, () => ( + + )) .with({ type: 'PairingDecisionRequest' }, () => ( )) diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index de284e4c4153..f4b80f72c23e 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -257,7 +257,7 @@ export type P2PEvent = { type: "DiscoveredPeer"; peer_id: PeerId; metadata: Peer export type PairingDecision = { decision: "accept"; libraryId: string } | { decision: "reject" } -export type PairingStatus = { type: "EstablishingConnection" } | { type: "PairingRequested" } | { type: "PairingDecisionRequest" } | { type: "PairingInProgress"; data: { library_name: string; library_description: string | null } } | { type: "InitialSyncProgress"; data: number } | { type: "PairingComplete"; data: string } | { type: "PairingRejected" } +export type PairingStatus = { type: "EstablishingConnection" } | { type: "PairingRequested" } | { type: "LibraryAlreadyExists" } | { type: "PairingDecisionRequest" } | { type: "PairingInProgress"; data: { library_name: string; library_description: string | null } } | { type: "InitialSyncProgress"; data: number } | { type: "PairingComplete"; data: string } | { type: "PairingRejected" } export type PeerId = string diff --git a/packages/client/src/hooks/useDebugState.ts b/packages/client/src/hooks/useDebugState.ts index 6d3eb4586a51..ae473b4a5259 100644 --- a/packages/client/src/hooks/useDebugState.ts +++ b/packages/client/src/hooks/useDebugState.ts @@ -1,3 +1,4 @@ +import { useEffect, useState } from 'react'; import { useSnapshot } from 'valtio'; import { valtioPersist } from '../lib/valito'; @@ -24,3 +25,19 @@ export function useDebugState() { export function getDebugState() { return debugState; } + +export function useDebugStateEnabler(): () => void { + const [clicked, setClicked] = useState(0); + + useEffect(() => { + if (clicked >= 5) { + getDebugState().enabled = true; + } + + const timeout = setTimeout(() => setClicked(0), 1000); + + return () => clearTimeout(timeout); + }, [clicked]); + + return () => setClicked((c) => c + 1); +} diff --git a/packages/client/src/hooks/useP2PEvents.tsx b/packages/client/src/hooks/useP2PEvents.tsx index 6e3142db0262..9be18c3820bc 100644 --- a/packages/client/src/hooks/useP2PEvents.tsx +++ b/packages/client/src/hooks/useP2PEvents.tsx @@ -25,7 +25,7 @@ export function P2PContextProvider({ children }: PropsWithChildren) { useBridgeSubscription(['p2p.events'], { onData(data) { - events.current.dispatchEvent(new CustomEvent('p2p-event', { detail: data })); + events.current.dispatchEvent(new CustomEvent('p2p-event', { detail: data })); if (data.type === 'DiscoveredPeer') { setDiscoveredPeer([discoveredPeers.set(data.peer_id, data.metadata)]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e0030ae1a2e..6dd09caee6cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -328,6 +328,9 @@ importers: dayjs: specifier: ^1.11.8 version: 1.11.8 + event-target-polyfill: + specifier: ^0.0.3 + version: 0.0.3 expo: specifier: ~48.0.19 version: 48.0.19(@babel/core@7.22.1) @@ -14239,6 +14242,10 @@ packages: es5-ext: 0.10.62 dev: true + /event-target-polyfill@0.0.3: + resolution: {integrity: sha512-ZMc6UuvmbinrCk4RzGyVmRyIsAyxMRlp4CqSrcQRO8Dy0A9ldbiRy5kdtBj4OtP7EClGdqGfIqo9JmOClMsGLQ==} + dev: false + /event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} From 8d6a0603431a6eafcff6645dc6663907e796ed18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Mon, 17 Jul 2023 14:08:09 -0300 Subject: [PATCH 18/18] [ENG-914] Fix compilation in Linux arm64 (#1104) Remove unecessary casts in movie_decoder that were breaking compilation in linux arm64 Co-authored-by: Utku <74243531+utkubakir@users.noreply.github.com> --- crates/ffmpeg/src/movie_decoder.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/ffmpeg/src/movie_decoder.rs b/crates/ffmpeg/src/movie_decoder.rs index 2b741eee9a07..20521a65610d 100644 --- a/crates/ffmpeg/src/movie_decoder.rs +++ b/crates/ffmpeg/src/movie_decoder.rs @@ -331,7 +331,7 @@ impl MovieDecoder { tag = unsafe { av_dict_get( (*stream).metadata, - empty_cstring.as_ptr() as *const i8, + empty_cstring.as_ptr(), tag, AV_DICT_IGNORE_SUFFIX, ) @@ -711,9 +711,9 @@ fn setup_filter( unsafe { avfilter_graph_create_filter( filter_ctx, - avfilter_get_by_name(filter_name_cstr.as_ptr() as *const i8), - filter_setup_name_cstr.as_ptr() as *const i8, - args_cstr.as_ptr() as *const i8, + avfilter_get_by_name(filter_name_cstr.as_ptr()), + filter_setup_name_cstr.as_ptr(), + args_cstr.as_ptr(), std::ptr::null_mut(), graph_ctx, ) @@ -736,8 +736,8 @@ fn setup_filter_without_args( unsafe { avfilter_graph_create_filter( filter_ctx, - avfilter_get_by_name(filter_name_cstr.as_ptr() as *const i8), - filter_setup_name_cstr.as_ptr() as *const i8, + avfilter_get_by_name(filter_name_cstr.as_ptr()), + filter_setup_name_cstr.as_ptr(), std::ptr::null_mut(), std::ptr::null_mut(), graph_ctx,