From 561462760b50d01fa2de566f1da9ad100e788eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Thu, 4 Jul 2024 05:57:43 -0300 Subject: [PATCH] Fix autoformat CI & format codebase (#2581) * Update pnpm version * Fix autoformat * Improve autoformat msg * Attempt to fix autoformat 2 * Fix autoformat * Ignore deleted files in auto-format * Fix diff filter * Autoformat whole codebase * Improve error message for autoformat CI * Test autoformat CI * Revert "Test autoformat CI" This reverts commit 0bf2f46d1aa19bec92d73d4d62c8156473455040. --- .github/workflows/ci.yml | 22 +- CONTRIBUTING.md | 2 +- apps/desktop/src/platform.ts | 4 +- .../app/api/slack/webhook/createRelease.ts | 6 +- .../src/app/api/slack/webhook/utils.ts | 38 +- apps/landing/src/app/team/people.tsx | 4 +- apps/mobile/src/App.tsx | 26 +- .../src/components/browse/BrowseLocations.tsx | 50 +- .../src/components/browse/BrowseTags.tsx | 38 +- .../src/components/drawer/DrawerContent.tsx | 2 +- .../drawer/DrawerLibraryManager.tsx | 2 +- .../src/components/drawer/DrawerLocations.tsx | 11 +- .../src/components/drawer/DrawerTags.tsx | 11 +- .../src/components/explorer/Explorer.tsx | 100 +-- .../src/components/explorer/FileItem.tsx | 35 +- .../src/components/explorer/FileRow.tsx | 68 ++- .../src/components/explorer/FileThumb.tsx | 8 +- .../src/components/explorer/menu/Menu.tsx | 47 +- .../components/explorer/menu/SortByMenu.tsx | 105 ++-- .../explorer/sections/FavoriteButton.tsx | 2 +- .../explorer/sections/InfoTagPills.tsx | 111 ++-- .../src/components/header/DynamicHeader.tsx | 21 +- .../src/components/job/JobContainer.tsx | 24 +- apps/mobile/src/components/job/JobGroup.tsx | 29 +- apps/mobile/src/components/layout/Empty.tsx | 13 +- apps/mobile/src/components/layout/Modal.tsx | 4 +- .../src/components/layout/ScreenContainer.tsx | 5 +- .../src/components/locations/GridLocation.tsx | 8 +- .../src/components/locations/ListLocation.tsx | 7 +- .../src/components/locations/LocationItem.tsx | 4 +- .../src/components/modal/AddTagModal.tsx | 304 +++++---- .../components/modal/ImportLibraryModal.tsx | 4 +- .../src/components/modal/ImportModal.tsx | 6 +- .../confirmModals/DeleteLocationModal.tsx | 2 +- .../modal/confirmModals/DeleteTagModal.tsx | 2 +- .../modal/inspector/ActionsModal.tsx | 59 +- .../modal/inspector/FileInfoModal.tsx | 148 +++-- .../modal/inspector/RenameModal.tsx | 41 +- .../components/modal/job/JobManagerModal.tsx | 13 +- .../modal/search/SaveSearchModal.tsx | 18 +- .../components/modal/tag/CreateTagModal.tsx | 6 +- .../src/components/overview/Locations.tsx | 104 ++-- .../src/components/overview/OverviewStats.tsx | 16 +- .../src/components/primitive/InfoPill.tsx | 2 +- .../mobile/src/components/primitive/Input.tsx | 4 +- apps/mobile/src/components/primitive/Menu.tsx | 21 +- .../mobile/src/components/primitive/Toast.tsx | 38 +- .../components/search/filters/FiltersBar.tsx | 6 +- .../components/search/filters/FiltersList.tsx | 6 +- .../src/components/search/filters/Kind.tsx | 32 +- .../components/search/filters/Locations.tsx | 2 +- .../search/filters/SavedSearches.tsx | 19 +- .../src/components/search/filters/Tags.tsx | 36 +- apps/mobile/src/components/tags/GridTag.tsx | 2 +- apps/mobile/src/components/tags/ListTag.tsx | 8 +- apps/mobile/src/components/tags/TagItem.tsx | 4 +- apps/mobile/src/hooks/useFiltersSearch.ts | 105 ++-- apps/mobile/src/hooks/useSavedSearch.ts | 105 ++-- apps/mobile/src/hooks/useSortBy.ts | 39 +- apps/mobile/src/navigation/SearchStack.tsx | 12 +- apps/mobile/src/navigation/TabNavigator.tsx | 4 +- apps/mobile/src/navigation/index.tsx | 2 +- .../src/navigation/tabs/BrowseStack.tsx | 12 +- apps/mobile/src/screens/BackfillWaiting.tsx | 19 +- apps/mobile/src/screens/browse/Location.tsx | 47 +- apps/mobile/src/screens/browse/Locations.tsx | 76 +-- apps/mobile/src/screens/browse/Tag.tsx | 34 +- apps/mobile/src/screens/browse/Tags.tsx | 66 +- apps/mobile/src/screens/search/Filters.tsx | 2 +- apps/mobile/src/screens/search/Search.tsx | 50 +- apps/mobile/src/screens/settings/Settings.tsx | 6 +- .../settings/client/GeneralSettings.tsx | 2 +- .../settings/client/LibrarySettings.tsx | 42 +- .../src/screens/settings/info/About.tsx | 4 +- .../library/CloudSettings/CloudSettings.tsx | 6 +- .../library/CloudSettings/Instance.tsx | 57 +- .../library/CloudSettings/Library.tsx | 2 +- .../settings/library/CloudSettings/Login.tsx | 8 +- .../library/CloudSettings/ThisInstance.tsx | 52 +- .../screens/settings/library/SyncSettings.tsx | 24 +- apps/mobile/src/stores/auth.ts | 4 +- apps/mobile/src/stores/explorerStore.ts | 2 +- apps/mobile/src/stores/modalStore.ts | 2 +- apps/mobile/src/stores/searchStore.ts | 8 +- crates/fda/README.md | 4 +- docs/developers/architecture/vdfs.mdx | 1 + docs/developers/p2p/discovery.mdx | 19 +- .../p2p/local-network-discovery.mdx | 13 +- docs/developers/p2p/overview.mdx | 22 +- docs/developers/p2p/protocols.mdx | 34 +- docs/developers/p2p/relay.mdx | 23 +- docs/developers/p2p/sd_p2p.mdx | 5 +- docs/developers/p2p/sd_p2p_block.mdx | 9 +- docs/developers/p2p/sd_p2p_proto.mdx | 8 +- docs/developers/p2p/sd_p2p_tunnel.mdx | 7 +- docs/product/resources/privacy.mdx | 1 + .../ContextMenu/AssignTagMenuItems.tsx | 5 +- .../$libraryId/Explorer/ExplorerPathBar.tsx | 14 +- .../$libraryId/Explorer/ParentContextMenu.tsx | 10 +- .../app/$libraryId/Explorer/TopBarOptions.tsx | 6 +- .../$libraryId/Explorer/View/Grid/Item.tsx | 5 +- .../Explorer/View/GridView/Item/index.tsx | 28 +- .../Explorer/View/GridView/index.tsx | 5 +- .../Explorer/View/ListView/Item.tsx | 2 +- .../Explorer/View/ListView/useTable.tsx | 63 +- .../Explorer/View/MediaView/index.tsx | 2 +- interface/app/$libraryId/Explorer/index.tsx | 14 +- interface/app/$libraryId/Explorer/store.ts | 2 +- .../app/$libraryId/Explorer/useExplorer.ts | 8 +- .../Layout/Sidebar/DebugPopover.tsx | 28 +- .../SidebarLayout/LibrariesDropdown.tsx | 8 +- .../app/$libraryId/Layout/Sidebar/index.tsx | 2 +- .../sections/Devices/AddDeviceDialog.tsx | 35 +- .../Layout/Sidebar/sections/Devices/index.tsx | 60 +- .../sections/Locations/ContextMenu.tsx | 1 - interface/app/$libraryId/debug/cloud.tsx | 57 +- interface/app/$libraryId/debug/index.ts | 2 +- interface/app/$libraryId/location/$id.tsx | 4 +- .../app/$libraryId/overview/StatCard.tsx | 12 +- interface/app/$libraryId/peer/$id.tsx | 1 + .../app/$libraryId/peer/StarfieldEffect.tsx | 576 ++++++++++-------- interface/app/$libraryId/recents.tsx | 4 +- interface/app/$libraryId/settings/Setting.tsx | 11 +- .../app/$libraryId/settings/client/index.ts | 2 +- .../locations/IndexerRuleEditor/RulesForm.tsx | 2 +- .../settings/library/locations/PathInput.tsx | 8 +- .../app/$libraryId/settings/library/sync.tsx | 29 +- .../settings/library/tags/CreateDialog.tsx | 2 +- .../settings/node/libraries/JoinDialog.tsx | 9 +- .../$libraryId/settings/resources/index.tsx | 2 +- interface/app/$libraryId/tag/$id.tsx | 2 +- interface/hooks/useRedirectToNewLocation.ts | 2 +- interface/util/Platform.tsx | 2 +- package.json | 2 +- packages/client/src/lib/humanizeSize.ts | 2 +- packages/ui/src/Loader.tsx | 2 +- scripts/autoformat.sh | 13 +- 137 files changed, 2023 insertions(+), 1669 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 689c7202f640..9c4e73b03b1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,8 +25,14 @@ jobs: timeout-minutes: 7 permissions: {} steps: - - name: Checkout repository + - name: 'PR commits + 1' + run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> "${GITHUB_ENV}" + + - name: 'Checkout PR branch and all PR commits' uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: ${{ env.PR_FETCH_DEPTH }} - name: Setup Node.js, pnpm and dependencies uses: ./.github/actions/setup-pnpm @@ -38,10 +44,18 @@ jobs: - name: Perform style check run: |- - set -eux - pnpm autoformat only-frontend - if [ -n "$(git diff --name-only --cached)" ]; then + set -eu + + pnpm autoformat only-frontend >/dev/null + + _files="$(git diff --name-only --cached)" + if [ -n "$_files" ]; then echo "Some files are not correctly formatted. Please run 'pnpm autoformat' and commit the changes." >&2 + + while IFS= read -r _file || [ -n "$_file" ]; do + echo "::error file=${_file},title=Incorrectly formatted file::Please run 'pnpm autoformat' and commit the changes." + done < <(printf '%s' "$_files") + exit 1 fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f18dec6745f..060a4faae4d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -91,7 +91,7 @@ If you encounter any issues, ensure that you are using the following versions of - Rust version: **1.78** - Node version: **18.18** -- Pnpm version: **9.1.1** +- Pnpm version: **9.4.0** After cleaning out your build artifacts using `pnpm clean`, `git clean`, or `cargo clean`, it is necessary to re-run the `setup-system` script. diff --git a/apps/desktop/src/platform.ts b/apps/desktop/src/platform.ts index 288e887df5fa..0e0311a41e55 100644 --- a/apps/desktop/src/platform.ts +++ b/apps/desktop/src/platform.ts @@ -48,8 +48,8 @@ export const platform = { getThumbnailUrlByThumbKey: (thumbKey) => constructServerUrl( `/thumbnail/${encodeURIComponent( - thumbKey.base_directory_str - )}/${encodeURIComponent(thumbKey.shard_hex)}/${encodeURIComponent(thumbKey.cas_id)}.webp` + thumbKey.base_directory_str + )}/${encodeURIComponent(thumbKey.shard_hex)}/${encodeURIComponent(thumbKey.cas_id)}.webp` ), getFileUrl: (libraryId, locationLocalId, filePathId) => constructServerUrl(`/file/${libraryId}/${locationLocalId}/${filePathId}`), diff --git a/apps/landing/src/app/api/slack/webhook/createRelease.ts b/apps/landing/src/app/api/slack/webhook/createRelease.ts index c394e49d3983..a217c9f4131f 100644 --- a/apps/landing/src/app/api/slack/webhook/createRelease.ts +++ b/apps/landing/src/app/api/slack/webhook/createRelease.ts @@ -17,7 +17,11 @@ export const fields = { } as const; export const COMMAND_NAME = '/release' as const; -export const EVENT_SCHEMAS = [createSlashCommand(COMMAND_NAME), createViewSubmission(), createBlockActions()] as const; +export const EVENT_SCHEMAS = [ + createSlashCommand(COMMAND_NAME), + createViewSubmission(), + createBlockActions() +] as const; export async function createModal( trigger_id: string, diff --git a/apps/landing/src/app/api/slack/webhook/utils.ts b/apps/landing/src/app/api/slack/webhook/utils.ts index 9be732291197..d1d37868a24c 100644 --- a/apps/landing/src/app/api/slack/webhook/utils.ts +++ b/apps/landing/src/app/api/slack/webhook/utils.ts @@ -52,7 +52,9 @@ export function createSlashCommand(command: T) { command: z.literal(command), text: z.string().transform((s) => s.split(' ')), api_app_id: z.string(), - is_enterprise_install: z.union([z.literal('false'), z.literal('true')]).transform((v) => v === 'true'), + is_enterprise_install: z + .union([z.literal('false'), z.literal('true')]) + .transform((v) => v === 'true'), response_url: z.string(), trigger_id: z.string() }); @@ -88,23 +90,27 @@ const BLOCK_ACTIONS_INNER = z.object({ z.object({ type: z.string(), block_id: z.string(), - text: z.object({ - type: z.string(), - text: z.string(), - verbatim: z.boolean() - }).optional(), - elements: z.array( - z.object({ + text: z + .object({ type: z.string(), - action_id: z.string(), - text: z.object({ - type: z.string(), - text: z.string(), - emoji: z.boolean() - }), - url: z.string().optional() + text: z.string(), + verbatim: z.boolean() }) - ).optional() + .optional(), + elements: z + .array( + z.object({ + type: z.string(), + action_id: z.string(), + text: z.object({ + type: z.string(), + text: z.string(), + emoji: z.boolean() + }), + url: z.string().optional() + }) + ) + .optional() }) ) }), diff --git a/apps/landing/src/app/team/people.tsx b/apps/landing/src/app/team/people.tsx index 39b716c4c6eb..cba4114c8b42 100644 --- a/apps/landing/src/app/team/people.tsx +++ b/apps/landing/src/app/team/people.tsx @@ -16,7 +16,7 @@ export const teamMembers: Array = [ name: 'Valerie Wong', location: 'Vancouver, Canada', role: 'Director of Operations', - imageUrl: '/images/team/valerie.jpeg', + imageUrl: '/images/team/valerie.jpeg' }, { name: 'Ericson Soares', @@ -43,7 +43,7 @@ export const teamMembers: Array = [ role: 'Rust Engineer', imageUrl: '/images/team/matheus.jpg', socials: { - github: 'https://github.com/matheus-consoli', + github: 'https://github.com/matheus-consoli' } }, { diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index 54e45cb5df3f..a8e022485e88 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -4,19 +4,6 @@ import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'; -import { - ClientContextProvider, - LibraryContextProvider, - P2PContextProvider, - RspcProvider, - initPlausible, - useBridgeQuery, - useClientContext, - useInvalidateQuery, - usePlausibleEvent, - usePlausiblePageViewMonitor, - usePlausiblePingMonitor -} from '@sd/client'; import { QueryClient } from '@tanstack/react-query'; import dayjs from 'dayjs'; import advancedFormat from 'dayjs/plugin/advancedFormat'; @@ -30,6 +17,19 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { MenuProvider } from 'react-native-popup-menu'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { useSnapshot } from 'valtio'; +import { + ClientContextProvider, + initPlausible, + LibraryContextProvider, + P2PContextProvider, + RspcProvider, + useBridgeQuery, + useClientContext, + useInvalidateQuery, + usePlausibleEvent, + usePlausiblePageViewMonitor, + usePlausiblePingMonitor +} from '@sd/client'; import { GlobalModals } from './components/modal/GlobalModals'; import { Toast, toastConfig } from './components/primitive/Toast'; diff --git a/apps/mobile/src/components/browse/BrowseLocations.tsx b/apps/mobile/src/components/browse/BrowseLocations.tsx index 991404cab451..75787d7a4a3a 100644 --- a/apps/mobile/src/components/browse/BrowseLocations.tsx +++ b/apps/mobile/src/components/browse/BrowseLocations.tsx @@ -1,13 +1,13 @@ import { useNavigation } from '@react-navigation/native'; -import { useLibraryQuery } from '@sd/client'; +import { Plus } from 'phosphor-react-native'; import { useRef, useState } from 'react'; import { FlatList, Text, View } from 'react-native'; +import { useLibraryQuery } from '@sd/client'; import { ModalRef } from '~/components/layout/Modal'; import { tw, twStyle } from '~/lib/tailwind'; import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack'; import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack'; -import { Plus } from 'phosphor-react-native'; import Empty from '../layout/Empty'; import Fade from '../layout/Fade'; import { LocationItem } from '../locations/LocationItem'; @@ -32,12 +32,16 @@ const BrowseLocations = () => { - + } + ListEmptyComponent={ + + } numColumns={showAll ? 3 : 1} horizontal={showAll ? false : true} - contentContainerStyle={twStyle(locations?.length === 0 && 'w-full','px-5')} + contentContainerStyle={twStyle(locations?.length === 0 && 'w-full', 'px-5')} key={showAll ? '_locations' : 'alllocationcols'} keyExtractor={(item) => item.id.toString()} scrollEnabled={showAll ? false : true} showsHorizontalScrollIndicator={false} renderItem={({ item }) => { - return ( - - navigation.navigate('SettingsStack', { - screen: 'EditLocationSettings', - params: { id: item.id }, - initial: false - }) - } - onPress={() => navigation.navigate('Location', { id: item.id })} + return ( + + navigation.navigate('SettingsStack', { + screen: 'EditLocationSettings', + params: { id: item.id }, + initial: false + }) + } + onPress={() => navigation.navigate('Location', { id: item.id })} /> - )} - } + ); + }} /> diff --git a/apps/mobile/src/components/browse/BrowseTags.tsx b/apps/mobile/src/components/browse/BrowseTags.tsx index a1360362db13..b823bafb7773 100644 --- a/apps/mobile/src/components/browse/BrowseTags.tsx +++ b/apps/mobile/src/components/browse/BrowseTags.tsx @@ -1,8 +1,8 @@ import { useNavigation } from '@react-navigation/native'; -import { useLibraryQuery } from '@sd/client'; import { Plus } from 'phosphor-react-native'; import React, { useRef, useState } from 'react'; import { FlatList, Text, View } from 'react-native'; +import { useLibraryQuery } from '@sd/client'; import { ModalRef } from '~/components/layout/Modal'; import { tw, twStyle } from '~/lib/tailwind'; import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack'; @@ -28,33 +28,39 @@ const BrowseTags = () => { Tags - + } + ListEmptyComponent={ + + } numColumns={showAll ? 3 : 1} - contentContainerStyle={twStyle(tagData?.length === 0 && 'w-full','px-5')} + contentContainerStyle={twStyle(tagData?.length === 0 && 'w-full', 'px-5')} horizontal={showAll ? false : true} key={showAll ? '_tags' : 'alltagcols'} keyExtractor={(item) => item.id.toString()} @@ -62,13 +68,13 @@ const BrowseTags = () => { showsHorizontalScrollIndicator={false} renderItem={({ item }) => ( - navigation.navigate('Tag', { id: item.id, color: item.color! }) - } - /> + style={twStyle(showAll && 'max-w-[31%] flex-1')} + key={item.id} + tag={item} + onPress={() => + navigation.navigate('Tag', { id: item.id, color: item.color! }) + } + /> )} /> diff --git a/apps/mobile/src/components/drawer/DrawerContent.tsx b/apps/mobile/src/components/drawer/DrawerContent.tsx index b46099351f3b..02817e0809c9 100644 --- a/apps/mobile/src/components/drawer/DrawerContent.tsx +++ b/apps/mobile/src/components/drawer/DrawerContent.tsx @@ -1,11 +1,11 @@ import { DrawerContentScrollView } from '@react-navigation/drawer'; import { DrawerContentComponentProps } from '@react-navigation/drawer/lib/typescript/src/types'; import { AppLogo } from '@sd/assets/images'; -import { JobManagerContextProvider, useLibraryQuery } from '@sd/client'; import { Image } from 'expo-image'; import { CheckCircle } from 'phosphor-react-native'; import { useRef } from 'react'; import { Platform, Pressable, Text, View } from 'react-native'; +import { JobManagerContextProvider, useLibraryQuery } from '@sd/client'; import Layout from '~/constants/Layout'; import { tw, twStyle } from '~/lib/tailwind'; diff --git a/apps/mobile/src/components/drawer/DrawerLibraryManager.tsx b/apps/mobile/src/components/drawer/DrawerLibraryManager.tsx index 112de0b9d8f8..6c9a4b46fa77 100644 --- a/apps/mobile/src/components/drawer/DrawerLibraryManager.tsx +++ b/apps/mobile/src/components/drawer/DrawerLibraryManager.tsx @@ -1,10 +1,10 @@ import { useDrawerStatus } from '@react-navigation/drawer'; import { useNavigation } from '@react-navigation/native'; -import { useClientContext } from '@sd/client'; import { MotiView } from 'moti'; import { CaretRight, CloudArrowDown, Gear, Lock, Plus } from 'phosphor-react-native'; import { useEffect, useRef, useState } from 'react'; import { Alert, Pressable, Text, View } from 'react-native'; +import { useClientContext } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; import { currentLibraryStore } from '~/utils/nav'; diff --git a/apps/mobile/src/components/drawer/DrawerLocations.tsx b/apps/mobile/src/components/drawer/DrawerLocations.tsx index 7dde34e90b4c..61a4efba7fa9 100644 --- a/apps/mobile/src/components/drawer/DrawerLocations.tsx +++ b/apps/mobile/src/components/drawer/DrawerLocations.tsx @@ -1,14 +1,14 @@ import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types'; import { useNavigation } from '@react-navigation/native'; +import { useRef } from 'react'; +import { Pressable, Text, View } from 'react-native'; import { - Location, arraysEqual, humanizeSize, + Location, useLibraryQuery, useOnlineLocations } from '@sd/client'; -import { useRef } from 'react'; -import { Pressable, Text, View } from 'react-native'; import { ModalRef } from '~/components/layout/Modal'; import { tw, twStyle } from '~/lib/tailwind'; @@ -45,7 +45,10 @@ const DrawerLocationItem: React.FC = ({ )} /> - + {location.name ?? ''} diff --git a/apps/mobile/src/components/drawer/DrawerTags.tsx b/apps/mobile/src/components/drawer/DrawerTags.tsx index 69ed080e8489..ea36e69b9f75 100644 --- a/apps/mobile/src/components/drawer/DrawerTags.tsx +++ b/apps/mobile/src/components/drawer/DrawerTags.tsx @@ -1,8 +1,8 @@ import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types'; import { useNavigation } from '@react-navigation/native'; -import { Tag, useLibraryQuery } from '@sd/client'; import { useRef } from 'react'; import { ColorValue, Pressable, Text, View } from 'react-native'; +import { Tag, useLibraryQuery } from '@sd/client'; import { ModalRef } from '~/components/layout/Modal'; import { tw, twStyle } from '~/lib/tailwind'; @@ -90,9 +90,12 @@ interface TagColumnProps { const TagColumn = ({ tags, dataAmount }: TagColumnProps) => { const navigation = useNavigation(); return ( - 2 ? 'w-[49%] flex-col' : 'flex-1 flex-row' - )}> + 2 ? 'w-[49%] flex-col' : 'flex-1 flex-row' + )} + > {tags?.slice(dataAmount[0], dataAmount[1]).map((tag: Tag) => ( ); +type Props = + | ExplorerProps + | ({ + // isEmpty and empty are mutually exclusive + emptyComponent: React.ReactElement; // component to show when FlashList has no data + isEmpty: boolean; // if true - show empty component + } & Omit); const Explorer = (props: Props) => { const navigation = useNavigation['navigation']>(); @@ -42,10 +42,10 @@ const Explorer = (props: Props) => { function handlePress(data: ExplorerItem) { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); if (isPath(data) && data.item.is_dir && data.item.location_id !== null) { - navigation.push('Location', { - id: data.item.location_id, - path: `${data.item.materialized_path}${data.item.name}/` - }); + navigation.push('Location', { + id: data.item.location_id, + path: `${data.item.materialized_path}${data.item.name}/` + }); } else { setData(data); modalRef.current?.present(); @@ -66,42 +66,44 @@ const Explorer = (props: Props) => { {/* Items */} {props.isEmpty ? ( props.emptyComponent - ) : - - item.type === 'NonIndexedPath' - ? item.item.path - : item.type === 'SpacedropPeer' - ? item.item.name - : item.item.id.toString() - } - renderItem={({ item }) => ( - + item.type === 'NonIndexedPath' + ? item.item.path + : item.type === 'SpacedropPeer' + ? item.item.name + : item.item.id.toString() + } + renderItem={({ item }) => ( + handlePress(item)} onLongPress={() => handleLongPress(item)} - > - {store.layoutMode === 'grid' ? ( - - ) : ( - - )} - - )} - contentContainerStyle={tw`px-2 py-5`} - extraData={store.layoutMode} - estimatedItemSize={ - store.layoutMode === 'grid' - ? Layout.window.width / store.gridNumColumns - : store.listItemSize - } - onEndReached={() => props.loadMore?.()} - onEndReachedThreshold={0.6} - ListFooterComponent={props.query.isFetchingNextPage ? : null} - /> - } + > + {store.layoutMode === 'grid' ? ( + + ) : ( + + )} + + )} + contentContainerStyle={tw`px-2 py-5`} + extraData={store.layoutMode} + estimatedItemSize={ + store.layoutMode === 'grid' + ? Layout.window.width / store.gridNumColumns + : store.listItemSize + } + onEndReached={() => props.loadMore?.()} + onEndReachedThreshold={0.6} + ListFooterComponent={ + props.query.isFetchingNextPage ? : null + } + /> + )} ); }; diff --git a/apps/mobile/src/components/explorer/FileItem.tsx b/apps/mobile/src/components/explorer/FileItem.tsx index 62d30d29d6f5..340b85c9349f 100644 --- a/apps/mobile/src/components/explorer/FileItem.tsx +++ b/apps/mobile/src/components/explorer/FileItem.tsx @@ -1,10 +1,10 @@ -import { ExplorerItem, Tag, getItemFilePath, getItemObject } from '@sd/client'; +import { useMemo } from 'react'; import { Text, View } from 'react-native'; +import { ExplorerItem, getItemFilePath, getItemObject, Tag } from '@sd/client'; import Layout from '~/constants/Layout'; import { tw, twStyle } from '~/lib/tailwind'; import { getExplorerStore } from '~/stores/explorerStore'; -import { useMemo } from 'react'; import FileThumb from './FileThumb'; type FileItemProps = { @@ -37,19 +37,24 @@ const FileItem = ({ data }: FileItemProps) => { {filePath?.extension && `.${filePath.extension}`} - - {tags.map(({tag}: {tag: Tag}, idx: number) => { - return ( - - ) + + {tags.map(({ tag }: { tag: Tag }, idx: number) => { + return ( + + ); })} diff --git a/apps/mobile/src/components/explorer/FileRow.tsx b/apps/mobile/src/components/explorer/FileRow.tsx index 25ac79a5f8ad..649ed1f67a18 100644 --- a/apps/mobile/src/components/explorer/FileRow.tsx +++ b/apps/mobile/src/components/explorer/FileRow.tsx @@ -1,6 +1,6 @@ -import { ExplorerItem, Tag, getItemFilePath, getItemObject } from '@sd/client'; import React, { useMemo } from 'react'; import { Text, View } from 'react-native'; +import { ExplorerItem, getItemFilePath, getItemObject, Tag } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; import { getExplorerStore } from '~/stores/explorerStore'; @@ -22,36 +22,46 @@ const FileRow = ({ data }: FileRowProps) => { return ( <> - - - - - - {filePath?.name} - {filePath?.extension && `.${filePath.extension}`} - - - - {tags.map(({tag}: {tag: Tag}, idx: number) => { - return ( - - ) + + + + + + {filePath?.name} + {filePath?.extension && `.${filePath.extension}`} + + + + {tags.map(({ tag }: { tag: Tag }, idx: number) => { + return ( + + ); + })} + + - - ); }; diff --git a/apps/mobile/src/components/explorer/FileThumb.tsx b/apps/mobile/src/components/explorer/FileThumb.tsx index ffc51417aced..eddca14fca8f 100644 --- a/apps/mobile/src/components/explorer/FileThumb.tsx +++ b/apps/mobile/src/components/explorer/FileThumb.tsx @@ -1,15 +1,15 @@ import { DocumentDirectoryPath } from '@dr.pogodin/react-native-fs'; import { getIcon } from '@sd/assets/util'; +import { Image } from 'expo-image'; +import { useEffect, useLayoutEffect, useMemo, useState, type PropsWithChildren } from 'react'; +import { View } from 'react-native'; import { - ThumbKey, getExplorerItemData, getItemLocation, isDarkTheme, + ThumbKey, type ExplorerItem } from '@sd/client'; -import { Image } from 'expo-image'; -import { useEffect, useLayoutEffect, useMemo, useState, type PropsWithChildren } from 'react'; -import { View } from 'react-native'; import { flattenThumbnailKey, useExplorerStore } from '~/stores/explorerStore'; import { tw } from '../../lib/tailwind'; diff --git a/apps/mobile/src/components/explorer/menu/Menu.tsx b/apps/mobile/src/components/explorer/menu/Menu.tsx index 0d73bb8e1459..af77616ee437 100644 --- a/apps/mobile/src/components/explorer/menu/Menu.tsx +++ b/apps/mobile/src/components/explorer/menu/Menu.tsx @@ -11,41 +11,40 @@ const Menu = () => { return ( - {store.toggleMenu && ( - - - - {store.layoutMode === 'grid' ? ( - (getExplorerStore().layoutMode = 'list')}> - - - ) : ( - (getExplorerStore().layoutMode = 'grid')}> + + + {store.layoutMode === 'grid' ? ( + (getExplorerStore().layoutMode = 'list')} + > + + + ) : ( + (getExplorerStore().layoutMode = 'grid')} + > - )} - {/* toast.error('Media view is not available yet...')} // onPress={() => (getExplorerStore().layoutMode = 'media')} > @@ -58,9 +57,9 @@ const Menu = () => { size={23} /> */} - + - )} + )} ); }; diff --git a/apps/mobile/src/components/explorer/menu/SortByMenu.tsx b/apps/mobile/src/components/explorer/menu/SortByMenu.tsx index 8abd4322dd0c..fed2f0cc145a 100644 --- a/apps/mobile/src/components/explorer/menu/SortByMenu.tsx +++ b/apps/mobile/src/components/explorer/menu/SortByMenu.tsx @@ -2,7 +2,7 @@ import { ArrowDown, ArrowUp, CaretDown, Check } from 'phosphor-react-native'; import { Text, View } from 'react-native'; import { Menu, MenuItem } from '~/components/primitive/Menu'; import { tw } from '~/lib/tailwind'; -import { SortOptionsType, getSearchStore, useSearchStore } from '~/stores/searchStore'; +import { getSearchStore, SortOptionsType, useSearchStore } from '~/stores/searchStore'; const sortOptions = { none: 'None', @@ -12,53 +12,63 @@ const sortOptions = { dateCreated: 'Date Created', dateModified: 'Date Modified', dateAccessed: 'Date Accessed', - dateTaken: 'Date Taken', + dateTaken: 'Date Taken' } satisfies Record; const sortOrder = ['Asc', 'Desc'] as SortOptionsType['direction'][]; -const ArrowUpIcon = ; -const ArrowDownIcon = ; +const ArrowUpIcon = ( + +); +const ArrowDownIcon = ( + +); const SortByMenu = () => { const searchStore = useSearchStore(); return ( - } - - > - {(Object.entries(sortOptions) as [[SortOptionsType['by'], string]]).map(([value, text], idx) => ( - - getSearchStore().sort.by = value} - /> - {idx !== Object.keys(sortOptions).length - 1 && } - - ))} - - - } - > - {sortOrder.map((value, idx) => ( - - getSearchStore().sort.direction = value} - /> - {idx !== 1 && } - - ))} - + } + > + {(Object.entries(sortOptions) as [[SortOptionsType['by'], string]]).map( + ([value, text], idx) => ( + + (getSearchStore().sort.by = value)} + /> + {idx !== Object.keys(sortOptions).length - 1 && ( + + )} + + ) + )} + + + } + > + {sortOrder.map((value, idx) => ( + + (getSearchStore().sort.direction = value)} + /> + {idx !== 1 && } + + ))} + ); }; @@ -68,13 +78,22 @@ interface Props { triggerIcon?: React.ReactNode; } -const Trigger = ({activeOption, triggerIcon}: Props) => { +const Trigger = ({ activeOption, triggerIcon }: Props) => { return ( {activeOption} - {triggerIcon ? triggerIcon : } + {triggerIcon ? ( + triggerIcon + ) : ( + + )} - ) -} + ); +}; export default SortByMenu; diff --git a/apps/mobile/src/components/explorer/sections/FavoriteButton.tsx b/apps/mobile/src/components/explorer/sections/FavoriteButton.tsx index 00091020e834..5a09e515c521 100644 --- a/apps/mobile/src/components/explorer/sections/FavoriteButton.tsx +++ b/apps/mobile/src/components/explorer/sections/FavoriteButton.tsx @@ -1,8 +1,8 @@ -import { Object as SDObject, useLibraryMutation } from '@sd/client'; import * as Haptics from 'expo-haptics'; import { Heart } from 'phosphor-react-native'; import { useState } from 'react'; import { Pressable, PressableProps } from 'react-native'; +import { Object as SDObject, useLibraryMutation } from '@sd/client'; type Props = { data: SDObject; diff --git a/apps/mobile/src/components/explorer/sections/InfoTagPills.tsx b/apps/mobile/src/components/explorer/sections/InfoTagPills.tsx index a8782d7c55dc..43627fd433a9 100644 --- a/apps/mobile/src/components/explorer/sections/InfoTagPills.tsx +++ b/apps/mobile/src/components/explorer/sections/InfoTagPills.tsx @@ -1,3 +1,5 @@ +import React, { useRef, useState } from 'react'; +import { FlatList, NativeScrollEvent, Pressable, View, ViewStyle } from 'react-native'; import { ExplorerItem, getExplorerItemData, @@ -6,8 +8,6 @@ import { isPath, useLibraryQuery } from '@sd/client'; -import React, { useRef, useState } from 'react'; -import { FlatList, NativeScrollEvent, Pressable, View, ViewStyle } from 'react-native'; import Fade from '~/components/layout/Fade'; import { ModalRef } from '~/components/layout/Modal'; import AddTagModal from '~/components/modal/AddTagModal'; @@ -22,14 +22,13 @@ type Props = { }; const InfoTagPills = ({ data, style, contentContainerStyle, columnCount = 3 }: Props) => { - const objectData = getItemObject(data); const filePath = getItemFilePath(data); const [startedScrolling, setStartedScrolling] = useState(false); const [reachedBottom, setReachedBottom] = useState(true); // needs to be set to true for initial rendering fade to be correct const tagsQuery = useLibraryQuery(['tags.getForObject', objectData?.id ?? -1], { - enabled: objectData != null, + enabled: objectData != null }); const ref = useRef(null); @@ -43,62 +42,66 @@ const InfoTagPills = ({ data, style, contentContainerStyle, columnCount = 3 }: P const hasReachedBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height; setReachedBottom(hasReachedBottom); - } + }; return ( <> - - - ref.current?.present()}> - - + + + ref.current?.present()}> + + {/* Kind */} - - {/* Extension */} - {filePath?.extension && ( - - )} - - { - if (e.nativeEvent.layout.height >= 80) { - setReachedBottom(false); - } else { - setReachedBottom(true); - } - }} style={twStyle(`relative flex-row flex-wrap gap-1 overflow-hidden`)}> - + {/* Extension */} + {filePath?.extension && } + + { + if (e.nativeEvent.layout.height >= 80) { + setReachedBottom(false); + } else { + setReachedBottom(true); + } + }} + style={twStyle(`relative flex-row flex-wrap gap-1 overflow-hidden`)} > - fadeScroll(e.nativeEvent)} - style={tw`max-h-20 w-full grow-0`} - data={tags} - scrollEventThrottle={1} - showsVerticalScrollIndicator={false} - numColumns={columnCount} - contentContainerStyle={twStyle(`gap-1`, contentContainerStyle)} - columnWrapperStyle={tags && twStyle(tags.length > 0 && `flex-wrap gap-1`)} - key={tags?.length} - keyExtractor={(item) => item.id.toString() + Math.floor(Math.random() * 10)} - renderItem={({ item }) => ( - - )}/> - + + fadeScroll(e.nativeEvent)} + style={tw`max-h-20 w-full grow-0`} + data={tags} + scrollEventThrottle={1} + showsVerticalScrollIndicator={false} + numColumns={columnCount} + contentContainerStyle={twStyle(`gap-1`, contentContainerStyle)} + columnWrapperStyle={ + tags && twStyle(tags.length > 0 && `flex-wrap gap-1`) + } + key={tags?.length} + keyExtractor={(item) => + item.id.toString() + Math.floor(Math.random() * 10) + } + renderItem={({ item }) => ( + + )} + /> + + - - + ); }; diff --git a/apps/mobile/src/components/header/DynamicHeader.tsx b/apps/mobile/src/components/header/DynamicHeader.tsx index 2c27fdea4ae1..626c28978ac8 100644 --- a/apps/mobile/src/components/header/DynamicHeader.tsx +++ b/apps/mobile/src/components/header/DynamicHeader.tsx @@ -6,8 +6,8 @@ import { Platform, Pressable, Text, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { tw, twStyle } from '~/lib/tailwind'; import { getExplorerStore, useExplorerStore } from '~/stores/explorerStore'; - import { FilterItem, TagItem, useSearchStore } from '~/stores/searchStore'; + import { Icon } from '../icons/Icon'; type Props = { @@ -32,7 +32,7 @@ export default function DynamicHeader({ id: number; color: string; name: string; - } + }; //pressing the search icon will add a filter //based on the screen @@ -43,12 +43,11 @@ export default function DynamicHeader({ tags: TagItem; locations: FilterItem; } = { - tags: {id: params.id, color: params.color}, - locations: {id: params.id, name: params.name}, - } - searchStore.searchFrom(key, keys[key]) - } - + tags: { id: params.id, color: params.color }, + locations: { id: params.id, name: params.name } + }; + searchStore.searchFrom(key, keys[key]); + }; return ( - { - searchHandler(kind) + searchHandler(kind); navigation.navigate('SearchStack', { screen: 'Search' }); @@ -97,7 +96,7 @@ export default function DynamicHeader({ > - {Icon && ( - - )} - {item?.text} - {index < filteredItems.length - 1 && } + {Icon && ( + + )} + + {item?.text} + + {index < filteredItems.length - 1 && ( + + )} ); diff --git a/apps/mobile/src/components/job/JobGroup.tsx b/apps/mobile/src/components/job/JobGroup.tsx index c0077add4d41..a7fc344c53a0 100644 --- a/apps/mobile/src/components/job/JobGroup.tsx +++ b/apps/mobile/src/components/job/JobGroup.tsx @@ -134,18 +134,23 @@ export default function ({ group, progress }: JobGroupProps) { {jobs.map((job, i) => ( - - - 1} - job={job} - progress={progress[job.id] ?? null} - /> + + + 1} + job={job} + progress={progress[job.id] ?? null} + /> ))} diff --git a/apps/mobile/src/components/layout/Empty.tsx b/apps/mobile/src/components/layout/Empty.tsx index 2c7ce7ab4048..7556b4f1cfd7 100644 --- a/apps/mobile/src/components/layout/Empty.tsx +++ b/apps/mobile/src/components/layout/Empty.tsx @@ -1,8 +1,8 @@ import { Text, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { ClassInput } from 'twrnc'; import { twStyle } from '~/lib/tailwind'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Icon, IconName } from '../icons/Icon'; interface Props { @@ -14,14 +14,21 @@ interface Props { includeHeaderHeight?: boolean; //Height of the header } -const Empty = ({ description, icon, style, includeHeaderHeight = false, textStyle, iconSize = 38 }: Props) => { +const Empty = ({ + description, + icon, + style, + includeHeaderHeight = false, + textStyle, + iconSize = 38 +}: Props) => { const headerHeight = useSafeAreaInsets().top; return ( diff --git a/apps/mobile/src/components/layout/Modal.tsx b/apps/mobile/src/components/layout/Modal.tsx index ecdc8687284e..6d2b60524c0b 100644 --- a/apps/mobile/src/components/layout/Modal.tsx +++ b/apps/mobile/src/components/layout/Modal.tsx @@ -6,10 +6,10 @@ import { BottomSheetHandleProps, BottomSheetModal, BottomSheetModalProps, - BottomSheetScrollView, + BottomSheetScrollView } from '@gorhom/bottom-sheet'; import { X } from 'phosphor-react-native'; -import { ReactNode, forwardRef } from 'react'; +import { forwardRef, ReactNode } from 'react'; import { Platform, Pressable, Text, View } from 'react-native'; import useForwardedRef from '~/hooks/useForwardedRef'; import { tw, twStyle } from '~/lib/tailwind'; diff --git a/apps/mobile/src/components/layout/ScreenContainer.tsx b/apps/mobile/src/components/layout/ScreenContainer.tsx index 6dea4bfecbcf..ce0f4dabc90e 100644 --- a/apps/mobile/src/components/layout/ScreenContainer.tsx +++ b/apps/mobile/src/components/layout/ScreenContainer.tsx @@ -31,10 +31,7 @@ const ScreenContainer = ({ ref.current?.scrollToEnd({ animated: true }); }} contentContainerStyle={twStyle('justify-between gap-10 py-6', style)} - style={twStyle( - 'bg-black', - tabHeight && { marginBottom: bottomTabBarHeight } - )} + style={twStyle('bg-black', tabHeight && { marginBottom: bottomTabBarHeight })} > {children} diff --git a/apps/mobile/src/components/locations/GridLocation.tsx b/apps/mobile/src/components/locations/GridLocation.tsx index bfc5239ef47d..8d9f99d96689 100644 --- a/apps/mobile/src/components/locations/GridLocation.tsx +++ b/apps/mobile/src/components/locations/GridLocation.tsx @@ -1,6 +1,6 @@ -import { Location, arraysEqual, humanizeSize, useOnlineLocations } from '@sd/client'; import { DotsThreeOutlineVertical } from 'phosphor-react-native'; import { Pressable, Text, View } from 'react-native'; +import { arraysEqual, humanizeSize, Location, useOnlineLocations } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; import FolderIcon from '../icons/FolderIcon'; @@ -47,9 +47,9 @@ const GridLocation: React.FC = ({ location, modalRef }: GridL - - {`${humanizeSize(location.size_in_bytes)}`} - + + {`${humanizeSize(location.size_in_bytes)}`} + ); diff --git a/apps/mobile/src/components/locations/ListLocation.tsx b/apps/mobile/src/components/locations/ListLocation.tsx index 66b242a186a6..cd2527bbcc9d 100644 --- a/apps/mobile/src/components/locations/ListLocation.tsx +++ b/apps/mobile/src/components/locations/ListLocation.tsx @@ -1,9 +1,9 @@ import { useNavigation } from '@react-navigation/native'; -import { Location, arraysEqual, humanizeSize, useOnlineLocations } from '@sd/client'; import { DotsThreeVertical } from 'phosphor-react-native'; import { useRef } from 'react'; import { Pressable, Text, View } from 'react-native'; import { Swipeable } from 'react-native-gesture-handler'; +import { arraysEqual, humanizeSize, Location, useOnlineLocations } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack'; @@ -63,10 +63,7 @@ const ListLocation = ({ location }: ListLocationProps) => { - + {`${humanizeSize(location.size_in_bytes)}`} diff --git a/apps/mobile/src/components/locations/LocationItem.tsx b/apps/mobile/src/components/locations/LocationItem.tsx index a9f47672420f..15eb8f8e4348 100644 --- a/apps/mobile/src/components/locations/LocationItem.tsx +++ b/apps/mobile/src/components/locations/LocationItem.tsx @@ -1,9 +1,9 @@ -import { Location } from '@sd/client'; import { useRef } from 'react'; import { Pressable } from 'react-native'; +import { ClassInput } from 'twrnc'; +import { Location } from '@sd/client'; import { twStyle } from '~/lib/tailwind'; -import { ClassInput } from 'twrnc'; import { ModalRef } from '../layout/Modal'; import { LocationModal } from '../modal/location/LocationModal'; import GridLocation from './GridLocation'; diff --git a/apps/mobile/src/components/modal/AddTagModal.tsx b/apps/mobile/src/components/modal/AddTagModal.tsx index e45d165025c8..929820c4ca63 100644 --- a/apps/mobile/src/components/modal/AddTagModal.tsx +++ b/apps/mobile/src/components/modal/AddTagModal.tsx @@ -1,23 +1,28 @@ -import { Tag, getItemObject, useLibraryMutation, useLibraryQuery, useRspcLibraryContext } from "@sd/client"; -import { CaretLeft, Plus } from "phosphor-react-native"; -import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { FlatList, NativeScrollEvent, Pressable, Text, View } from "react-native"; -import useForwardedRef from "~/hooks/useForwardedRef"; -import { tw, twStyle } from "~/lib/tailwind"; -import { useActionsModalStore } from "~/stores/modalStore"; -import Card from "../layout/Card"; -import Fade from "../layout/Fade"; -import { Modal, ModalRef } from "../layout/Modal"; -import { Button } from "../primitive/Button"; -import CreateTagModal from "./tag/CreateTagModal"; - +import { CaretLeft, Plus } from 'phosphor-react-native'; +import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { FlatList, NativeScrollEvent, Pressable, Text, View } from 'react-native'; +import { + getItemObject, + Tag, + useLibraryMutation, + useLibraryQuery, + useRspcLibraryContext +} from '@sd/client'; +import useForwardedRef from '~/hooks/useForwardedRef'; +import { tw, twStyle } from '~/lib/tailwind'; +import { useActionsModalStore } from '~/stores/modalStore'; + +import Card from '../layout/Card'; +import Fade from '../layout/Fade'; +import { Modal, ModalRef } from '../layout/Modal'; +import { Button } from '../primitive/Button'; +import CreateTagModal from './tag/CreateTagModal'; const AddTagModal = forwardRef((_, ref) => { - - const {data} = useActionsModalStore(); + const { data } = useActionsModalStore(); // Wrapped in memo to ensure that the data is not undefined on initial render - const objectData = data && getItemObject(data); + const objectData = data && getItemObject(data); const modalRef = useForwardedRef(ref); const newTagRef = useRef(null); @@ -30,8 +35,8 @@ const AddTagModal = forwardRef((_, ref) => { const mutation = useLibraryMutation(['tags.assign'], { onSuccess: () => { // this makes sure that the tags are updated in the UI - rspc.queryClient.invalidateQueries(['tags.getForObject']) - rspc.queryClient.invalidateQueries(['search.paths']) + rspc.queryClient.invalidateQueries(['tags.getForObject']); + rspc.queryClient.invalidateQueries(['search.paths']); modalRef.current?.dismiss(); } }); @@ -39,11 +44,13 @@ const AddTagModal = forwardRef((_, ref) => { const tagsData = tagsQuery.data; const tagsObject = tagsObjectQuery.data; - const [selectedTags, setSelectedTags] = useState<{ - id: number; - unassign: boolean; - selected: boolean; - }[]>([]); + const [selectedTags, setSelectedTags] = useState< + { + id: number; + unassign: boolean; + selected: boolean; + }[] + >([]); // get the tags that are already applied to the object const appliedTags = useMemo(() => { @@ -51,133 +58,160 @@ const AddTagModal = forwardRef((_, ref) => { return tagsObject?.map((t) => t.id); }, [tagsObject]); - // set selected tags when tagsOfObject.data is available useEffect(() => { if (!tagsObject) return; //we want to set the selectedTags if there are applied tags //this deals with an edge case of clearing the tags onDismiss of the Modal if (selectedTags.length === 0 && appliedTags.length > 0) { - setSelectedTags((tagsObject ?? []).map((tag) => ({ - id: tag.id, - unassign: false, - selected: true - })))} - }, [tagsObject, appliedTags, selectedTags]) + setSelectedTags( + (tagsObject ?? []).map((tag) => ({ + id: tag.id, + unassign: false, + selected: true + })) + ); + } + }, [tagsObject, appliedTags, selectedTags]); // check if tag is selected - const isSelected = useCallback((id: number) => { - const findTag = selectedTags.find((t) => t.id === id); - return findTag?.selected ?? false; - }, [selectedTags]); - - const selectTag = useCallback((id: number) => { - //check if tag is already selected - const findTag = selectedTags.find((t) => t.id === id); - if (findTag) { - //if tag is already selected, update its selected value - setSelectedTags((prev) => prev.map((t) => t.id === id ? { ...t, selected: !t.selected, unassign: !t.unassign } : t)); - } else { - //if tag is not selected, select it - setSelectedTags((prev) => [...prev, { id, unassign: false, selected: true }]); - } - }, [selectedTags]); + const isSelected = useCallback( + (id: number) => { + const findTag = selectedTags.find((t) => t.id === id); + return findTag?.selected ?? false; + }, + [selectedTags] + ); + + const selectTag = useCallback( + (id: number) => { + //check if tag is already selected + const findTag = selectedTags.find((t) => t.id === id); + if (findTag) { + //if tag is already selected, update its selected value + setSelectedTags((prev) => + prev.map((t) => + t.id === id ? { ...t, selected: !t.selected, unassign: !t.unassign } : t + ) + ); + } else { + //if tag is not selected, select it + setSelectedTags((prev) => [...prev, { id, unassign: false, selected: true }]); + } + }, + [selectedTags] + ); const assignHandler = async () => { - const targets = data && 'id' in data.item && (data.type === 'Object' ? { - Object: data.item.id - } : { - FilePath: data.item.id - }); + const targets = + data && + 'id' in data.item && + (data.type === 'Object' + ? { + Object: data.item.id + } + : { + FilePath: data.item.id + }); // in order to support assigning multiple tags // we need to make multiple mutation calls - if (targets) await Promise.all([...selectedTags.map(async (tag) => await mutation.mutateAsync({ - targets: [targets], - tag_id: tag.id, - unassign: tag.unassign - })), - ] - ); - } - - // Fade the tags when scrolling - const fadeScroll = ({ layoutMeasurement, contentOffset, contentSize }: NativeScrollEvent) => { - const isScrolling = contentOffset.y > 0; - setStartedScrolling(isScrolling); - - const hasReachedBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height; - setReachedBottom(hasReachedBottom); - } + if (targets) + await Promise.all([ + ...selectedTags.map( + async (tag) => + await mutation.mutateAsync({ + targets: [targets], + tag_id: tag.id, + unassign: tag.unassign + }) + ) + ]); + }; + + // Fade the tags when scrolling + const fadeScroll = ({ layoutMeasurement, contentOffset, contentSize }: NativeScrollEvent) => { + const isScrolling = contentOffset.y > 0; + setStartedScrolling(isScrolling); + + const hasReachedBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height; + setReachedBottom(hasReachedBottom); + }; return ( <> - setSelectedTags([])} - enableContentPanningGesture={false} - enablePanDownToClose={false} - snapPoints={['50']} - title="Select Tags" - > - {/* Back Button */} - modalRef.current?.close()} - style={tw`absolute z-10 ml-6 rounded-full bg-app-button p-2`} - > - - - { - if (e.nativeEvent.layout.height >= 80) { - setReachedBottom(false); - } else { - setReachedBottom(true); - } - }} style={twStyle(`relative mt-4 h-[70%]`)}> - setSelectedTags([])} + enableContentPanningGesture={false} + enablePanDownToClose={false} + snapPoints={['50']} + title="Select Tags" + > + {/* Back Button */} + modalRef.current?.close()} + style={tw`absolute z-10 ml-6 rounded-full bg-app-button p-2`} > - fadeScroll(e.nativeEvent)} - extraData={selectedTags} - key={tagsData ? 'tags' : '_'} - keyExtractor={(item) => item.id.toString()} - contentContainerStyle={tw`mx-auto p-4 pb-6`} - ItemSeparatorComponent={() => } - renderItem={({ item }) => ( - isSelected(item.id)} select={() => selectTag(item.id)} tag={item} /> - )} - /> + + + { + if (e.nativeEvent.layout.height >= 80) { + setReachedBottom(false); + } else { + setReachedBottom(true); + } + }} + style={twStyle(`relative mt-4 h-[70%]`)} + > + + fadeScroll(e.nativeEvent)} + extraData={selectedTags} + key={tagsData ? 'tags' : '_'} + keyExtractor={(item) => item.id.toString()} + contentContainerStyle={tw`mx-auto p-4 pb-6`} + ItemSeparatorComponent={() => } + renderItem={({ item }) => ( + isSelected(item.id)} + select={() => selectTag(item.id)} + tag={item} + /> + )} + /> - - - - - - - + + + + + + + - ) + ); }); interface Props { @@ -186,12 +220,12 @@ interface Props { isSelected: () => boolean; } -const TagItem = ({tag, select, isSelected}: Props) => { +const TagItem = ({ tag, select, isSelected }: Props) => { return ( { {tag?.name} - ) -} + ); +}; export default AddTagModal; diff --git a/apps/mobile/src/components/modal/ImportLibraryModal.tsx b/apps/mobile/src/components/modal/ImportLibraryModal.tsx index 29e12367df41..82de9fdd4474 100644 --- a/apps/mobile/src/components/modal/ImportLibraryModal.tsx +++ b/apps/mobile/src/components/modal/ImportLibraryModal.tsx @@ -1,5 +1,7 @@ import { BottomSheetFlatList } from '@gorhom/bottom-sheet'; import { NavigationProp, useNavigation } from '@react-navigation/native'; +import { forwardRef } from 'react'; +import { ActivityIndicator, Text, View } from 'react-native'; import { CloudLibrary, useBridgeMutation, @@ -7,8 +9,6 @@ import { useClientContext, useRspcContext } from '@sd/client'; -import { forwardRef } from 'react'; -import { ActivityIndicator, Text, View } from 'react-native'; import { Modal, ModalRef } from '~/components/layout/Modal'; import { Button } from '~/components/primitive/Button'; import useForwardedRef from '~/hooks/useForwardedRef'; diff --git a/apps/mobile/src/components/modal/ImportModal.tsx b/apps/mobile/src/components/modal/ImportModal.tsx index 4f14da02071f..c551ac9db2b3 100644 --- a/apps/mobile/src/components/modal/ImportModal.tsx +++ b/apps/mobile/src/components/modal/ImportModal.tsx @@ -1,8 +1,8 @@ import * as RNFS from '@dr.pogodin/react-native-fs'; -import { useLibraryMutation, useRspcLibraryContext } from '@sd/client'; import { forwardRef, useCallback } from 'react'; import { Alert, Platform, Text, View } from 'react-native'; import DocumentPicker from 'react-native-document-picker'; +import { useLibraryMutation, useRspcLibraryContext } from '@sd/client'; import { Modal, ModalRef } from '~/components/layout/Modal'; import { Button } from '~/components/primitive/Button'; import useForwardedRef from '~/hooks/useForwardedRef'; @@ -25,9 +25,9 @@ const ImportModal = forwardRef((_, ref) => { onError: (error, variables) => { modalRef.current?.close(); //custom message handling - if (error.message.startsWith("location already exists")) { + if (error.message.startsWith('location already exists')) { return toast.error('This location has already been added'); - } else if (error.message.startsWith("nested location currently")) { + } else if (error.message.startsWith('nested location currently')) { return toast.error('Nested locations are currently not supported'); } switch (error.message) { diff --git a/apps/mobile/src/components/modal/confirmModals/DeleteLocationModal.tsx b/apps/mobile/src/components/modal/confirmModals/DeleteLocationModal.tsx index 8d221305e972..20fbc5b83895 100644 --- a/apps/mobile/src/components/modal/confirmModals/DeleteLocationModal.tsx +++ b/apps/mobile/src/components/modal/confirmModals/DeleteLocationModal.tsx @@ -1,5 +1,5 @@ -import { useLibraryMutation, usePlausibleEvent, useRspcLibraryContext } from '@sd/client'; import { useRef } from 'react'; +import { useLibraryMutation, usePlausibleEvent, useRspcLibraryContext } from '@sd/client'; import { ConfirmModal, ModalRef } from '~/components/layout/Modal'; import { toast } from '~/components/primitive/Toast'; diff --git a/apps/mobile/src/components/modal/confirmModals/DeleteTagModal.tsx b/apps/mobile/src/components/modal/confirmModals/DeleteTagModal.tsx index 8d68272cc358..70157cf38867 100644 --- a/apps/mobile/src/components/modal/confirmModals/DeleteTagModal.tsx +++ b/apps/mobile/src/components/modal/confirmModals/DeleteTagModal.tsx @@ -1,5 +1,5 @@ -import { useLibraryMutation, usePlausibleEvent, useRspcLibraryContext } from '@sd/client'; import { useRef } from 'react'; +import { useLibraryMutation, usePlausibleEvent, useRspcLibraryContext } from '@sd/client'; import { ConfirmModal, ModalRef } from '~/components/layout/Modal'; import { toast } from '~/components/primitive/Toast'; diff --git a/apps/mobile/src/components/modal/inspector/ActionsModal.tsx b/apps/mobile/src/components/modal/inspector/ActionsModal.tsx index 3872dad9a3f8..f388b1ff4dec 100644 --- a/apps/mobile/src/components/modal/inspector/ActionsModal.tsx +++ b/apps/mobile/src/components/modal/inspector/ActionsModal.tsx @@ -1,11 +1,3 @@ -import { - getIndexedItemFilePath, - getItemObject, - humanizeSize, - useLibraryMutation, - useLibraryQuery, - useRspcContext -} from '@sd/client'; import dayjs from 'dayjs'; import { Copy, @@ -21,14 +13,22 @@ import { import { PropsWithChildren, useRef } from 'react'; import { Pressable, Text, View, ViewStyle } from 'react-native'; import FileViewer from 'react-native-file-viewer'; +import { + getIndexedItemFilePath, + getItemObject, + humanizeSize, + useLibraryMutation, + useLibraryQuery, + useRspcContext +} from '@sd/client'; import FileThumb from '~/components/explorer/FileThumb'; import FavoriteButton from '~/components/explorer/sections/FavoriteButton'; import InfoTagPills from '~/components/explorer/sections/InfoTagPills'; import { Modal, ModalRef } from '~/components/layout/Modal'; +import { toast } from '~/components/primitive/Toast'; import { tw, twStyle } from '~/lib/tailwind'; import { useActionsModalStore } from '~/stores/modalStore'; -import { toast } from '~/components/primitive/Toast'; import FileInfoModal from './FileInfoModal'; import RenameModal from './RenameModal'; @@ -88,7 +88,7 @@ export const ActionsModal = () => { const deleteFile = useLibraryMutation('files.deleteFiles', { onSuccess: () => { - rspc.queryClient.invalidateQueries(['search.paths']) + rspc.queryClient.invalidateQueries(['search.paths']); modalRef.current?.dismiss(); } }); @@ -104,9 +104,9 @@ export const ActionsModal = () => { }); filePath && filePath.object_id && - await updateAccessTime.mutateAsync([filePath.object_id]).catch(console.error); + (await updateAccessTime.mutateAsync([filePath.object_id]).catch(console.error)); } catch (error) { - toast.error("Error opening object") + toast.error('Error opening object'); } } @@ -143,7 +143,9 @@ export const ActionsModal = () => { - {objectData && } + {objectData && ( + + )} {/* Actions */} @@ -157,9 +159,13 @@ export const ActionsModal = () => { /> - { - renameRef.current?.present(); - }} icon={Pencil} title="Rename" /> + { + renameRef.current?.present(); + }} + icon={Pencil} + title="Rename" + /> @@ -172,14 +178,19 @@ export const ActionsModal = () => { - { - if (filePath && filePath.location_id) { - await deleteFile.mutateAsync({ - location_id: filePath.location_id, - file_path_ids: [filePath.id] - }); - } - }} /> + { + if (filePath && filePath.location_id) { + await deleteFile.mutateAsync({ + location_id: filePath.location_id, + file_path_ids: [filePath.id] + }); + } + }} + /> )} diff --git a/apps/mobile/src/components/modal/inspector/FileInfoModal.tsx b/apps/mobile/src/components/modal/inspector/FileInfoModal.tsx index 89a9457fdf15..ea46e1902b09 100644 --- a/apps/mobile/src/components/modal/inspector/FileInfoModal.tsx +++ b/apps/mobile/src/components/modal/inspector/FileInfoModal.tsx @@ -1,8 +1,17 @@ -import { getItemFilePath, getItemObject, humanizeSize, type ExplorerItem } from '@sd/client'; import dayjs from 'dayjs'; -import { Barcode, CaretLeft, Clock, Cube, FolderOpen, Icon, SealCheck, Snowflake } from 'phosphor-react-native'; +import { + Barcode, + CaretLeft, + Clock, + Cube, + FolderOpen, + Icon, + SealCheck, + Snowflake +} from 'phosphor-react-native'; import { forwardRef } from 'react'; import { Pressable, Text, View } from 'react-native'; +import { getItemFilePath, getItemObject, humanizeSize, type ExplorerItem } from '@sd/client'; import FileThumb from '~/components/explorer/FileThumb'; import InfoTagPills from '~/components/explorer/sections/InfoTagPills'; import { Modal, ModalScrollView, type ModalRef } from '~/components/layout/Modal'; @@ -51,79 +60,90 @@ const FileInfoModal = forwardRef((props, ref) => { snapPoints={['70']} > - {data && ( - - {/* Back Button */} - modalRef.current?.close()} - style={tw`absolute left-2 z-10 rounded-full bg-app-button p-2`} - > - - - - {/* File Icon / Name */} - - - {filePathData?.name} - - - - {/* Details */} - - <> - {/* Size */} - - {/* Created */} - {data.type !== 'SpacedropPeer' && ( + {data && ( + + {/* Back Button */} + modalRef.current?.close()} + style={tw`absolute left-2 z-10 rounded-full bg-app-button p-2`} + > + + + + {/* File Icon / Name */} + + + {filePathData?.name} + + + + {/* Details */} + + <> + {/* Size */} - )} + {/* Created */} + {data.type !== 'SpacedropPeer' && ( + + )} - {/* Accessed */} + {/* Accessed */} - {/* Modified */} + {/* Modified */} - {filePathData && 'cas_id' in filePathData && ( - <> - {/* Indexed */} - - {/* TODO: Note */} - {filePathData.cas_id && ( - - )} - {/* Checksum */} - {filePathData?.integrity_checksum && ( + {filePathData && 'cas_id' in filePathData && ( + <> + {/* Indexed */} - )} - - )} - - - )} + {/* TODO: Note */} + {filePathData.cas_id && ( + + )} + {/* Checksum */} + {filePathData?.integrity_checksum && ( + + )} + + )} + + + )} ); diff --git a/apps/mobile/src/components/modal/inspector/RenameModal.tsx b/apps/mobile/src/components/modal/inspector/RenameModal.tsx index 03a4f77418e8..581192482a7d 100644 --- a/apps/mobile/src/components/modal/inspector/RenameModal.tsx +++ b/apps/mobile/src/components/modal/inspector/RenameModal.tsx @@ -1,7 +1,7 @@ -import { getIndexedItemFilePath, useLibraryMutation, useRspcLibraryContext } from '@sd/client'; import React, { forwardRef, useEffect, useRef, useState } from 'react'; import { Text, View } from 'react-native'; import { TextInput } from 'react-native-gesture-handler'; +import { getIndexedItemFilePath, useLibraryMutation, useRspcLibraryContext } from '@sd/client'; import { Modal, ModalRef } from '~/components/layout/Modal'; import { Button } from '~/components/primitive/Button'; import { ModalInput } from '~/components/primitive/Input'; @@ -10,7 +10,6 @@ import useForwardedRef from '~/hooks/useForwardedRef'; import { tw } from '~/lib/tailwind'; import { useActionsModalStore } from '~/stores/modalStore'; - const RenameModal = forwardRef((_, ref) => { const modalRef = useForwardedRef(ref); const [newName, setNewName] = useState(''); @@ -40,27 +39,27 @@ const RenameModal = forwardRef((_, ref) => { }, [fileName, combined]); const textRenameHandler = async () => { - switch (data?.type) { - case 'Path': - case 'Object': { - if (!filePathData) throw new Error('Failed to get file path object'); + switch (data?.type) { + case 'Path': + case 'Object': { + if (!filePathData) throw new Error('Failed to get file path object'); - const { id, location_id } = filePathData; + const { id, location_id } = filePathData; - if (!location_id) throw new Error('Missing location id'); + if (!location_id) throw new Error('Missing location id'); - await renameFile.mutateAsync({ - location_id: location_id, - kind: { - One: { - from_file_path_id: id, - to: newName - } + await renameFile.mutateAsync({ + location_id: location_id, + kind: { + One: { + from_file_path_id: id, + to: newName } - }); - break; - } + } + }); + break; } + } }; return ( @@ -80,7 +79,11 @@ const RenameModal = forwardRef((_, ref) => { value={newName} onChangeText={(t) => setNewName(t)} /> - diff --git a/apps/mobile/src/components/modal/job/JobManagerModal.tsx b/apps/mobile/src/components/modal/job/JobManagerModal.tsx index 2c759e2a1a25..66658d75710a 100644 --- a/apps/mobile/src/components/modal/job/JobManagerModal.tsx +++ b/apps/mobile/src/components/modal/job/JobManagerModal.tsx @@ -1,6 +1,6 @@ import { BottomSheetFlatList } from '@gorhom/bottom-sheet'; -import { useJobProgress, useLibraryQuery } from '@sd/client'; import { forwardRef, useEffect } from 'react'; +import { useJobProgress, useLibraryQuery } from '@sd/client'; import JobGroup from '~/components/job/JobGroup'; import Empty from '~/components/layout/Empty'; import { Modal, ModalRef } from '~/components/layout/Modal'; @@ -32,21 +32,14 @@ export const JobManagerModal = forwardRef((_, ref) => { }, [jobGroups, modalRef]); return ( - + i.id} contentContainerStyle={tw`mt-4`} renderItem={({ item }) => } - ListEmptyComponent={ - - } + ListEmptyComponent={} /> ); diff --git a/apps/mobile/src/components/modal/search/SaveSearchModal.tsx b/apps/mobile/src/components/modal/search/SaveSearchModal.tsx index a3f622fc7d79..d39febb99057 100644 --- a/apps/mobile/src/components/modal/search/SaveSearchModal.tsx +++ b/apps/mobile/src/components/modal/search/SaveSearchModal.tsx @@ -1,7 +1,7 @@ import { useNavigation } from '@react-navigation/native'; -import { useLibraryMutation } from '@sd/client'; import { forwardRef, useState } from 'react'; import { Text, View } from 'react-native'; +import { useLibraryMutation } from '@sd/client'; import { Modal, ModalRef } from '~/components/layout/Modal'; import { Button } from '~/components/primitive/Button'; import { ModalInput } from '~/components/primitive/Input'; @@ -34,15 +34,13 @@ const SaveSearchModal = forwardRef((_, ref) => { style={tw`mt-2`} variant="accent" onPress={async () => { - await saveSearch.mutateAsync( - { - name: searchName, - filters: JSON.stringify(searchStore.mergedFilters), - description: null, - icon: null, - search: null - } - ); + await saveSearch.mutateAsync({ + name: searchName, + filters: JSON.stringify(searchStore.mergedFilters), + description: null, + icon: null, + search: null + }); setSearchName(''); }} > diff --git a/apps/mobile/src/components/modal/tag/CreateTagModal.tsx b/apps/mobile/src/components/modal/tag/CreateTagModal.tsx index ff4f9fdd0531..68bbb4bcf6e2 100644 --- a/apps/mobile/src/components/modal/tag/CreateTagModal.tsx +++ b/apps/mobile/src/components/modal/tag/CreateTagModal.tsx @@ -1,12 +1,12 @@ +import { forwardRef, useEffect, useState } from 'react'; +import { Pressable, Text, View } from 'react-native'; +import ColorPicker from 'react-native-wheel-color-picker'; import { ToastDefautlColor, useLibraryMutation, usePlausibleEvent, useRspcLibraryContext } from '@sd/client'; -import { forwardRef, useEffect, useState } from 'react'; -import { Pressable, Text, View } from 'react-native'; -import ColorPicker from 'react-native-wheel-color-picker'; import { FadeInAnimation } from '~/components/animation/layout'; import { Modal, ModalRef } from '~/components/layout/Modal'; import { Button } from '~/components/primitive/Button'; diff --git a/apps/mobile/src/components/overview/Locations.tsx b/apps/mobile/src/components/overview/Locations.tsx index fb24ca639bb0..1eb65a4490e4 100644 --- a/apps/mobile/src/components/overview/Locations.tsx +++ b/apps/mobile/src/components/overview/Locations.tsx @@ -1,8 +1,8 @@ import { useNavigation } from '@react-navigation/native'; -import { useLibraryQuery } from '@sd/client'; import React, { useRef } from 'react'; import { Pressable, Text, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; +import { useLibraryQuery } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; import { OverviewStackScreenProps } from '~/navigation/tabs/OverviewStack'; @@ -25,56 +25,58 @@ const Locations = () => { <> - - location.id.toString()} - ItemSeparatorComponent={() => } - ListEmptyComponent={() => { - return ( - ( - - )} - /> - ); - }} - showsVerticalScrollIndicator={false} - renderItem={({ item }) => ( - - navigation.jumpTo('BrowseStack', { - initial: false, - screen: 'Location', - params: { id: item.id } - }) - } - > - - - )} - /> - + + location.id.toString()} + ItemSeparatorComponent={() => } + ListEmptyComponent={() => { + return ( + ( + + )} + /> + ); + }} + showsVerticalScrollIndicator={false} + renderItem={({ item }) => ( + + navigation.jumpTo('BrowseStack', { + initial: false, + screen: 'Location', + params: { id: item.id } + }) + } + > + + + )} + /> + diff --git a/apps/mobile/src/components/overview/OverviewStats.tsx b/apps/mobile/src/components/overview/OverviewStats.tsx index d72eba4e7225..0a414f8a0213 100644 --- a/apps/mobile/src/components/overview/OverviewStats.tsx +++ b/apps/mobile/src/components/overview/OverviewStats.tsx @@ -1,10 +1,10 @@ import * as RNFS from '@dr.pogodin/react-native-fs'; import { AlphaRSPCError } from '@oscartbeaumont-sd/rspc-client/v2'; -import { Statistics, StatisticsResponse, humanizeSize, useLibraryContext } from '@sd/client'; import { UseQueryResult } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { Platform, Text, View } from 'react-native'; import { ClassInput } from 'twrnc/dist/esm/types'; +import { humanizeSize, Statistics, StatisticsResponse, useLibraryContext } from '@sd/client'; import useCounter from '~/hooks/useCounter'; import { tw, twStyle } from '~/lib/tailwind'; @@ -77,7 +77,11 @@ const OverviewStats = ({ stats }: Props) => { }, []); const renderStatItems = (isTotalStat = true) => { - const keysToFilter = ['total_local_bytes_capacity', 'total_local_bytes_used', 'total_library_bytes']; + const keysToFilter = [ + 'total_local_bytes_capacity', + 'total_local_bytes_used', + 'total_library_bytes' + ]; if (!stats.data?.statistics) return null; return Object.entries(stats.data.statistics).map(([key, bytesRaw]) => { if (!displayableStatItems.includes(key)) return null; @@ -107,12 +111,8 @@ const OverviewStats = ({ stats }: Props) => { Statistics - - {renderStatItems()} - - - {renderStatItems(false)} - + {renderStatItems()} + {renderStatItems(false)} ); diff --git a/apps/mobile/src/components/primitive/InfoPill.tsx b/apps/mobile/src/components/primitive/InfoPill.tsx index bd01f4bb2c49..11ae91a5d83b 100644 --- a/apps/mobile/src/components/primitive/InfoPill.tsx +++ b/apps/mobile/src/components/primitive/InfoPill.tsx @@ -7,7 +7,7 @@ type Props = { text: string; containerStyle?: ViewStyle; textStyle?: TextStyle; - icon?: ReactElement + icon?: ReactElement; }; export const InfoPill = (props: Props) => { diff --git a/apps/mobile/src/components/primitive/Input.tsx b/apps/mobile/src/components/primitive/Input.tsx index abe5b453f753..d45e5c45c846 100644 --- a/apps/mobile/src/components/primitive/Input.tsx +++ b/apps/mobile/src/components/primitive/Input.tsx @@ -34,7 +34,7 @@ export const Input = forwardRef((props, ref) => { {...otherProps} /> ); -}) +}); // To use in modals (for keyboard handling) export const ModalInput = forwardRef((props, ref) => { @@ -48,7 +48,7 @@ export const ModalInput = forwardRef((props, ref) => { {...otherProps} /> ); -}) +}); // Same as Input but configured with password props & show/hide password button diff --git a/apps/mobile/src/components/primitive/Menu.tsx b/apps/mobile/src/components/primitive/Menu.tsx index 7f16d870a8f8..d3abb62a2fda 100644 --- a/apps/mobile/src/components/primitive/Menu.tsx +++ b/apps/mobile/src/components/primitive/Menu.tsx @@ -19,12 +19,17 @@ type MenuProps = { // TODO: Still looks a bit off... export const Menu = (props: MenuProps) => ( - - {props.trigger} - - {props.children} - - + + {props.trigger} + + {props.children} + + ); type MenuItemProps = { @@ -39,9 +44,7 @@ export const MenuItem = ({ icon, textStyle, iconStyle, style, ...props }: MenuIt return ( - {Icon && ( - - )} + {Icon && } ( - - - {text1} - + + + {text1} + ), error: ({ text1, ...rest }) => ( - - - {text1} - + + + {text1} + ), info: ({ text1, ...rest }) => ( - - - {text1} - + + + {text1} + ) diff --git a/apps/mobile/src/components/search/filters/FiltersBar.tsx b/apps/mobile/src/components/search/filters/FiltersBar.tsx index 5e4de21047eb..1d521d17e02c 100644 --- a/apps/mobile/src/components/search/filters/FiltersBar.tsx +++ b/apps/mobile/src/components/search/filters/FiltersBar.tsx @@ -17,10 +17,10 @@ import { tw, twStyle } from '~/lib/tailwind'; import { SearchStackScreenProps } from '~/navigation/SearchStack'; import { FilterItem as FilterItemType, + getSearchStore, KindItem, SearchFilters, TagItem, - getSearchStore, useSearchStore } from '~/stores/searchStore'; @@ -56,7 +56,7 @@ const FiltersBar = () => { horizontal onContentSizeChange={() => { if (flatListRef.current && appliedFiltersLength < 2) { - flatListRef.current.scrollToOffset({ animated: true, offset: 0 }); + flatListRef.current.scrollToOffset({ animated: true, offset: 0 }); } }} data={Object.entries(searchStore.appliedFilters)} @@ -184,7 +184,7 @@ const TagView = ({ tags }: { tags: TagItem[] }) => ( ))} diff --git a/apps/mobile/src/components/search/filters/FiltersList.tsx b/apps/mobile/src/components/search/filters/FiltersList.tsx index cc58b151b40f..74ee8f54df09 100644 --- a/apps/mobile/src/components/search/filters/FiltersList.tsx +++ b/apps/mobile/src/components/search/filters/FiltersList.tsx @@ -13,7 +13,7 @@ import { Text, View } from 'react-native'; import Card from '~/components/layout/Card'; import SectionTitle from '~/components/layout/SectionTitle'; import { tw, twStyle } from '~/lib/tailwind'; -import { SearchFilters, getSearchStore, useSearchStore } from '~/stores/searchStore'; +import { getSearchStore, SearchFilters, useSearchStore } from '~/stores/searchStore'; import Extension from './Extension'; import Kind from './Kind'; @@ -53,7 +53,6 @@ const FiltersList = () => { ); const appliedFiltersLength = Object.keys(searchStore.appliedFilters).length; - useEffect(() => { //if there are selected filters but not applied reset them if (appliedFiltersLength === 0) { @@ -83,7 +82,8 @@ const FiltersList = () => { searchStore.resetFilter(searchFiltersLowercase); } }, - [selectedOptions, searchStore]) + [selectedOptions, searchStore] + ); return ( diff --git a/apps/mobile/src/components/search/filters/Kind.tsx b/apps/mobile/src/components/search/filters/Kind.tsx index a951270b2743..f54bb48652cf 100644 --- a/apps/mobile/src/components/search/filters/Kind.tsx +++ b/apps/mobile/src/components/search/filters/Kind.tsx @@ -1,9 +1,9 @@ import { IconTypes } from '@sd/assets/util'; -import { ObjectKind } from '@sd/client'; import { MotiView } from 'moti'; import { memo, useCallback, useMemo } from 'react'; import { FlatList, Pressable, Text, View } from 'react-native'; import { LinearTransition } from 'react-native-reanimated'; +import { ObjectKind } from '@sd/client'; import { Icon } from '~/components/icons/Icon'; import Card from '~/components/layout/Card'; import SectionTitle from '~/components/layout/SectionTitle'; @@ -39,21 +39,21 @@ const Kind = () => { sub="What kind of objects should be searched?" /> - - } - contentContainerStyle={tw`px-6`} - numColumns={kinds && Math.ceil(Number(kinds.length) / 2)} - key={kinds ? 'kindsSearch' : '_'} - scrollEnabled={false} - extraData={searchStore.filters.kind} - ItemSeparatorComponent={() => } - keyExtractor={(item) => item.value.toString()} - showsHorizontalScrollIndicator={false} - style={tw`flex-row`} - /> - + + } + contentContainerStyle={tw`px-6`} + numColumns={kinds && Math.ceil(Number(kinds.length) / 2)} + key={kinds ? 'kindsSearch' : '_'} + scrollEnabled={false} + extraData={searchStore.filters.kind} + ItemSeparatorComponent={() => } + keyExtractor={(item) => item.value.toString()} + showsHorizontalScrollIndicator={false} + style={tw`flex-row`} + /> + ); diff --git a/apps/mobile/src/components/search/filters/Locations.tsx b/apps/mobile/src/components/search/filters/Locations.tsx index 8d812803a542..94fc22e2f0c2 100644 --- a/apps/mobile/src/components/search/filters/Locations.tsx +++ b/apps/mobile/src/components/search/filters/Locations.tsx @@ -1,8 +1,8 @@ -import { Location, useLibraryQuery } from '@sd/client'; import { MotiView } from 'moti'; import { memo, useCallback, useMemo } from 'react'; import { FlatList, Pressable, Text, View } from 'react-native'; import { LinearTransition } from 'react-native-reanimated'; +import { Location, useLibraryQuery } from '@sd/client'; import { Icon } from '~/components/icons/Icon'; import Card from '~/components/layout/Card'; import Empty from '~/components/layout/Empty'; diff --git a/apps/mobile/src/components/search/filters/SavedSearches.tsx b/apps/mobile/src/components/search/filters/SavedSearches.tsx index 2d5dd4900df5..aed1ff8272d1 100644 --- a/apps/mobile/src/components/search/filters/SavedSearches.tsx +++ b/apps/mobile/src/components/search/filters/SavedSearches.tsx @@ -1,14 +1,14 @@ import { useNavigation } from '@react-navigation/native'; +import { MotiView } from 'moti'; +import { MotiPressable } from 'moti/interactions'; +import { X } from 'phosphor-react-native'; +import { FlatList, Pressable, Text, View } from 'react-native'; import { SavedSearch as ISavedSearch, useLibraryMutation, useLibraryQuery, useRspcLibraryContext } from '@sd/client'; -import { MotiView } from 'moti'; -import { MotiPressable } from 'moti/interactions'; -import { X } from 'phosphor-react-native'; -import { FlatList, Pressable, Text, View } from 'react-native'; import { Icon } from '~/components/icons/Icon'; import Card from '~/components/layout/Card'; import Empty from '~/components/layout/Empty'; @@ -40,7 +40,10 @@ const SavedSearches = () => { ListEmptyComponent={() => { return ( + icon="Folder" + description="No saved searches" + style={tw`w-full`} + /> ); }} renderItem={({ item }) => } @@ -67,7 +70,7 @@ const SavedSearch = ({ search }: Props) => { const navigation = useNavigation(); const dataForSearch = useSavedSearch(search); const rspc = useRspcLibraryContext(); - const deleteSearch = useLibraryMutation('search.saved.delete', { + const deleteSearch = useLibraryMutation('search.saved.delete', { onSuccess: () => rspc.queryClient.invalidateQueries(['search.saved.list']) }); return ( @@ -83,9 +86,7 @@ const SavedSearch = ({ search }: Props) => { }} > - await deleteSearch.mutateAsync(search.id)} - > + await deleteSearch.mutateAsync(search.id)}> diff --git a/apps/mobile/src/components/search/filters/Tags.tsx b/apps/mobile/src/components/search/filters/Tags.tsx index 37b4ae31529d..6b4ddb5f3d05 100644 --- a/apps/mobile/src/components/search/filters/Tags.tsx +++ b/apps/mobile/src/components/search/filters/Tags.tsx @@ -1,9 +1,9 @@ -import { Tag, useLibraryQuery } from '@sd/client'; import { MotiView } from 'moti'; import { memo, useCallback, useMemo } from 'react'; import { Pressable, Text, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { LinearTransition } from 'react-native-reanimated'; +import { Tag, useLibraryQuery } from '@sd/client'; import Card from '~/components/layout/Card'; import Empty from '~/components/layout/Empty'; import Fade from '~/components/layout/Fade'; @@ -30,23 +30,25 @@ const Tags = () => { title="Tags" sub="What tags would you like to filter by?" /> - + - } - extraData={searchStore.filters.tags} - alwaysBounceVertical={false} - numColumns={tagsData ? Math.max(Math.ceil(tagsData.length / 2), 2) : 1} - key={tagsData ? 'tagsSearch' : '_'} - ListEmptyComponent={} - ItemSeparatorComponent={() => } - keyExtractor={(item) => item.id.toString()} - showsHorizontalScrollIndicator={false} - style={tw`flex-row`} - /> - - + } + extraData={searchStore.filters.tags} + alwaysBounceVertical={false} + numColumns={tagsData ? Math.max(Math.ceil(tagsData.length / 2), 2) : 1} + key={tagsData ? 'tagsSearch' : '_'} + ListEmptyComponent={ + + } + ItemSeparatorComponent={() => } + keyExtractor={(item) => item.id.toString()} + showsHorizontalScrollIndicator={false} + style={tw`flex-row`} + /> + + ); }; diff --git a/apps/mobile/src/components/tags/GridTag.tsx b/apps/mobile/src/components/tags/GridTag.tsx index a2598126d784..869acbf40000 100644 --- a/apps/mobile/src/components/tags/GridTag.tsx +++ b/apps/mobile/src/components/tags/GridTag.tsx @@ -1,6 +1,6 @@ -import { Tag } from '@sd/client'; import { DotsThreeOutlineVertical } from 'phosphor-react-native'; import { Pressable, Text, View } from 'react-native'; +import { Tag } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; import Card from '../layout/Card'; diff --git a/apps/mobile/src/components/tags/ListTag.tsx b/apps/mobile/src/components/tags/ListTag.tsx index d7e0e0441f40..058dd245f927 100644 --- a/apps/mobile/src/components/tags/ListTag.tsx +++ b/apps/mobile/src/components/tags/ListTag.tsx @@ -1,9 +1,9 @@ -import { Tag } from '@sd/client'; import { DotsThreeVertical } from 'phosphor-react-native'; import { useRef } from 'react'; import { Pressable, Text, View } from 'react-native'; import { Swipeable } from 'react-native-gesture-handler'; import { ClassInput } from 'twrnc'; +import { Tag } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; import RightActions from './RightActions'; @@ -40,11 +40,7 @@ const ListTag = ({ tag, tagStyle }: ListTagProps) => { swipeRef.current?.openRight()}> - + diff --git a/apps/mobile/src/components/tags/TagItem.tsx b/apps/mobile/src/components/tags/TagItem.tsx index 7aba00a01274..8b79c01d19cd 100644 --- a/apps/mobile/src/components/tags/TagItem.tsx +++ b/apps/mobile/src/components/tags/TagItem.tsx @@ -1,9 +1,9 @@ -import { Tag } from '@sd/client'; import { useRef } from 'react'; import { Pressable } from 'react-native'; +import { ClassInput } from 'twrnc'; +import { Tag } from '@sd/client'; import { twStyle } from '~/lib/tailwind'; -import { ClassInput } from 'twrnc'; import { ModalRef } from '../layout/Modal'; import { TagModal } from '../modal/tag/TagModal'; import GridTag from './GridTag'; diff --git a/apps/mobile/src/hooks/useFiltersSearch.ts b/apps/mobile/src/hooks/useFiltersSearch.ts index 4c953df85cb6..bfdd877e8a35 100644 --- a/apps/mobile/src/hooks/useFiltersSearch.ts +++ b/apps/mobile/src/hooks/useFiltersSearch.ts @@ -1,6 +1,6 @@ -import { SearchFilterArgs, useLibraryQuery } from '@sd/client'; import { useEffect, useMemo } from 'react'; -import { Filters, SearchFilters, getSearchStore, useSearchStore } from '~/stores/searchStore'; +import { SearchFilterArgs, useLibraryQuery } from '@sd/client'; +import { Filters, getSearchStore, SearchFilters, useSearchStore } from '~/stores/searchStore'; /** * This hook merges the selected filters from Filters page in order @@ -9,53 +9,56 @@ import { Filters, SearchFilters, getSearchStore, useSearchStore } from '~/stores * @param search - search input string value */ - export function useFiltersSearch(search: string) { - const [name, ext] = useMemo(() => search.split('.'), [search]); const searchStore = useSearchStore(); const locations = useLibraryQuery(['locations.list'], { - keepPreviousData: true, + keepPreviousData: true }); - const filterFactory = (key: SearchFilters, value: Filters[keyof Filters]) => { - + const filterFactory = (key: SearchFilters, value: Filters[keyof Filters]) => { //hidden is the only boolean filter - so we can return it directly //Rest of the filters are arrays, so we map them to the correct format - const filterValue = Array.isArray(value) ? value.map((v: any) => { - return v.id ? v.id : v; - }) : value; + const filterValue = Array.isArray(value) + ? value.map((v: any) => { + return v.id ? v.id : v; + }) + : value; //switch case for each filter //This makes it easier to add new filters in the future and setup //the correct object of each filter accordingly and easily - switch (key) { - case 'locations': - return { filePath: { locations: { in: filterValue } } }; - case 'name': - return Array.isArray(filterValue) && filterValue.map((v: string) => { + switch (key) { + case 'locations': + return { filePath: { locations: { in: filterValue } } }; + case 'name': + return ( + Array.isArray(filterValue) && + filterValue.map((v: string) => { return { filePath: { [key]: { contains: v } } }; }) - case 'hidden': - return { filePath: { hidden: filterValue } }; - case 'extension': - return Array.isArray(filterValue) && filterValue.map((v: string) => { + ); + case 'hidden': + return { filePath: { hidden: filterValue } }; + case 'extension': + return ( + Array.isArray(filterValue) && + filterValue.map((v: string) => { return { filePath: { [key]: { in: [v] } } }; }) - case 'tags': - return { object: { tags: { in: filterValue } } }; - case 'kind': - return { object: { kind: { in: filterValue } } }; - default: - return {}; - } - } - + ); + case 'tags': + return { object: { tags: { in: filterValue } } }; + case 'kind': + return { object: { kind: { in: filterValue } } }; + default: + return {}; + } + }; const mergedFilters = useMemo(() => { - const filters = [] as SearchFilterArgs[]; //It's a global search if no locations have been selected @@ -69,36 +72,32 @@ export function useFiltersSearch(search: string) { if (ext) filters.push({ filePath: { extension: { in: [ext] } } }); // handle selected filters - for (const key in searchStore.filters) { - - const filterKey = key as SearchFilters; - //due to an issue with Valtio and Hermes Engine - need to do getSearchStore() - //https://github.com/pmndrs/valtio/issues/765 - const filterValue = getSearchStore().filters[filterKey]; - - // no need to add empty filters - if (Array.isArray(filterValue)) { - const realValues = filterValue.filter((v) => v !== ''); - if (realValues.length === 0) { - continue; - } + for (const key in searchStore.filters) { + const filterKey = key as SearchFilters; + //due to an issue with Valtio and Hermes Engine - need to do getSearchStore() + //https://github.com/pmndrs/valtio/issues/765 + const filterValue = getSearchStore().filters[filterKey]; + + // no need to add empty filters + if (Array.isArray(filterValue)) { + const realValues = filterValue.filter((v) => v !== ''); + if (realValues.length === 0) { + continue; } - - // create the filter object - const filter = filterFactory(filterKey, filterValue); - - // add the filter to the mergedFilters - filters.push(filter as SearchFilterArgs); - } - // makes sure the array is not 2D - return filters.flat(); + // create the filter object + const filter = filterFactory(filterKey, filterValue); - }, [searchStore.filters, search]); + // add the filter to the mergedFilters + filters.push(filter as SearchFilterArgs); + } + // makes sure the array is not 2D + return filters.flat(); + }, [searchStore.filters, search]); useEffect(() => { getSearchStore().mergedFilters = mergedFilters; }, [searchStore.filters, search]); -}; +} diff --git a/apps/mobile/src/hooks/useSavedSearch.ts b/apps/mobile/src/hooks/useSavedSearch.ts index 3434621e773d..45e82f3c8e72 100644 --- a/apps/mobile/src/hooks/useSavedSearch.ts +++ b/apps/mobile/src/hooks/useSavedSearch.ts @@ -1,5 +1,5 @@ -import { SavedSearch, SearchFilterArgs, useLibraryQuery } from '@sd/client'; import { useCallback, useMemo } from 'react'; +import { SavedSearch, SearchFilterArgs, useLibraryQuery } from '@sd/client'; import { kinds } from '~/components/search/filters/Kind'; import { Filters, SearchFilters } from '~/stores/searchStore'; @@ -13,17 +13,24 @@ export function useSavedSearch(search: SavedSearch) { // returns an array of keys of the filters being used in the Saved Search //i.e locations, tags, kind, etc... - const filterKeys: SearchFilters[] = parseFilters.reduce((acc: SearchFilters[], curr: keyof SearchFilterArgs) => { - const objectOrFilePath = Object.keys(curr)[0] as 'filePath' | 'object'; - const key = Object.keys(curr[objectOrFilePath])[0] as SearchFilters; - if (!acc.includes(key)) { - acc.push(key as SearchFilters); - } - return acc; - }, []); + const filterKeys: SearchFilters[] = parseFilters.reduce( + (acc: SearchFilters[], curr: keyof SearchFilterArgs) => { + const objectOrFilePath = Object.keys(curr)[0] as 'filePath' | 'object'; + const key = Object.keys(curr[objectOrFilePath])[0] as SearchFilters; + if (!acc.includes(key)) { + acc.push(key as SearchFilters); + } + return acc; + }, + [] + ); // this util function extracts the data of a filter from the Saved Search - const extractDataFromSavedSearch = (key: SearchFilters, filterTag: 'contains' | 'in', type: 'filePath' | 'object') => { + const extractDataFromSavedSearch = ( + key: SearchFilters, + filterTag: 'contains' | 'in', + type: 'filePath' | 'object' + ) => { // Iterate through each item in the data array for (const item of parseFilters) { // Check if 'filePath' | 'object' exists and contains a the key @@ -33,15 +40,15 @@ export function useSavedSearch(search: SavedSearch) { } } return null; - } + }; const locations = useLibraryQuery(['locations.list'], { keepPreviousData: true, - enabled: filterKeys.includes('locations'), + enabled: filterKeys.includes('locations') }); const tags = useLibraryQuery(['tags.list'], { keepPreviousData: true, - enabled: filterKeys.includes('tags'), + enabled: filterKeys.includes('tags') }); // Filters like locations, tags, and kind require data to be rendered as a Filter @@ -89,45 +96,47 @@ export function useSavedSearch(search: SavedSearch) { }, [locations, tags]); const filters: Partial = useMemo(() => { - return parseFilters.reduce((acc: Record, curr: keyof SearchFilterArgs) => { + return parseFilters.reduce( + (acc: Record, curr: keyof SearchFilterArgs) => { + const objectOrFilePath = Object.keys(curr)[0] as 'filePath' | 'object'; + const key = Object.keys(curr[objectOrFilePath])[0] as SearchFilters; //locations, tags, kind, etc... - const objectOrFilePath = Object.keys(curr)[0] as 'filePath' | 'object'; - const key = Object.keys(curr[objectOrFilePath])[0] as SearchFilters; //locations, tags, kind, etc... - - // this function extracts the data from the result of the "filters" object in the Saved Search - // and matches it with the values of the filters - const extractData = (key: SearchFilters) => { - const values: { - contains?: string; - in?: number[]; - } = curr[objectOrFilePath][key]; - const type = Object.keys(values)[0]; + // this function extracts the data from the result of the "filters" object in the Saved Search + // and matches it with the values of the filters + const extractData = (key: SearchFilters) => { + const values: { + contains?: string; + in?: number[]; + } = curr[objectOrFilePath][key]; + const type = Object.keys(values)[0]; - switch (type) { - case 'contains': - // some filters have a name property and some are just strings - return prepFilters()[key].filter((item: any) => { - return item.name ? item.name === values[type] : - item - }); - case 'in': - return prepFilters()[key].filter((item: any) => values[type]?.includes(item.id)); - default: - return values; - } - }; + switch (type) { + case 'contains': + // some filters have a name property and some are just strings + return prepFilters()[key].filter((item: any) => { + return item.name ? item.name === values[type] : item; + }); + case 'in': + return prepFilters()[key].filter((item: any) => + values[type]?.includes(item.id) + ); + default: + return values; + } + }; - // the data being setup for the filters so it can be rendered - if (!acc[key]) { - acc[key] = extractData(key); - //don't include false values i.e if the "Hidden" filter is false - if (acc[key] === false) { - delete acc[key]; + // the data being setup for the filters so it can be rendered + if (!acc[key]) { + acc[key] = extractData(key); + //don't include false values i.e if the "Hidden" filter is false + if (acc[key] === false) { + delete acc[key]; + } } - } - return acc; - }, {}); - + return acc; + }, + {} + ); }, [parseFilters]); return filters; diff --git a/apps/mobile/src/hooks/useSortBy.ts b/apps/mobile/src/hooks/useSortBy.ts index a896dd4552ac..48a04d6de947 100644 --- a/apps/mobile/src/hooks/useSortBy.ts +++ b/apps/mobile/src/hooks/useSortBy.ts @@ -1,5 +1,5 @@ -import { FilePathOrder } from "@sd/client"; -import { SortOptionsType, useSearchStore } from "~/stores/searchStore"; +import { FilePathOrder } from '@sd/client'; +import { SortOptionsType, useSearchStore } from '~/stores/searchStore'; /** * This hook provides a sorting order object based on user preferences @@ -7,24 +7,27 @@ import { SortOptionsType, useSearchStore } from "~/stores/searchStore"; */ export const useSortBy = (): FilePathOrder | null => { - const searchStore = useSearchStore(); - const { by, direction } = searchStore.sort; + const searchStore = useSearchStore(); + const { by, direction } = searchStore.sort; - // if no sort by field is selected, return null - if (by === 'none') return null; + // if no sort by field is selected, return null + if (by === 'none') return null; - // some sort by fields have common keys - const common = { field: by, value: direction }; + // some sort by fields have common keys + const common = { field: by, value: direction }; - const fields: Record,any> = { - name: common, - sizeInBytes: common, - dateIndexed: common, - dateCreated: common, - dateModified: common, - dateAccessed: { field: "object", value: { field: "dateAccessed", value: direction} }, - dateTaken: { field: "object", value: {field: 'mediaData', value: { field: "epochTime", value: direction}} } - }; + const fields: Record, any> = { + name: common, + sizeInBytes: common, + dateIndexed: common, + dateCreated: common, + dateModified: common, + dateAccessed: { field: 'object', value: { field: 'dateAccessed', value: direction } }, + dateTaken: { + field: 'object', + value: { field: 'mediaData', value: { field: 'epochTime', value: direction } } + } + }; - return fields[by]; + return fields[by]; }; diff --git a/apps/mobile/src/navigation/SearchStack.tsx b/apps/mobile/src/navigation/SearchStack.tsx index 84139f37df8f..0f0e4388e0fe 100644 --- a/apps/mobile/src/navigation/SearchStack.tsx +++ b/apps/mobile/src/navigation/SearchStack.tsx @@ -28,11 +28,17 @@ export default function SearchStack() { }} /> {/** This screen is already in BrowseStack - but added here as it offers the UX needed */} - ({ - header: (route) => + options={({ route: optionsRoute }) => ({ + header: (route) => ( + + ) })} /> diff --git a/apps/mobile/src/navigation/TabNavigator.tsx b/apps/mobile/src/navigation/TabNavigator.tsx index ade069de2967..07a14f433b74 100644 --- a/apps/mobile/src/navigation/TabNavigator.tsx +++ b/apps/mobile/src/navigation/TabNavigator.tsx @@ -2,6 +2,7 @@ import { BottomTabScreenProps, createBottomTabNavigator } from '@react-navigatio import { CompositeScreenProps, NavigatorScreenParams } from '@react-navigation/native'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { BlurView } from 'expo-blur'; +import * as Haptics from 'expo-haptics'; import { useEffect, useRef, useState } from 'react'; import { Platform, StyleSheet, ViewStyle } from 'react-native'; import { TouchableWithoutFeedback } from 'react-native-gesture-handler'; @@ -9,7 +10,6 @@ import Rive, { RiveRef } from 'rive-react-native'; import { Style } from 'twrnc/dist/esm/types'; import { tw } from '~/lib/tailwind'; -import * as Haptics from 'expo-haptics'; import { RootStackParamList } from '.'; import BrowseStack, { BrowseStackParamList } from './tabs/BrowseStack'; import NetworkStack, { NetworkStackParamList } from './tabs/NetworkStack'; @@ -148,7 +148,7 @@ export default function TabNavigator() { focus: () => { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); setActiveIndex(index); - }, + } })} /> ))} diff --git a/apps/mobile/src/navigation/index.tsx b/apps/mobile/src/navigation/index.tsx index 39e9d190aa31..13dab8721ddd 100644 --- a/apps/mobile/src/navigation/index.tsx +++ b/apps/mobile/src/navigation/index.tsx @@ -2,9 +2,9 @@ import { NavigatorScreenParams } from '@react-navigation/native'; import { createNativeStackNavigator, NativeStackScreenProps } from '@react-navigation/native-stack'; import NotFoundScreen from '~/screens/NotFound'; +import BackfillWaitingStack, { BackfillWaitingStackParamList } from './BackfillWaitingStack'; import DrawerNavigator, { DrawerNavParamList } from './DrawerNavigator'; import SearchStack, { SearchStackParamList } from './SearchStack'; -import BackfillWaitingStack, { BackfillWaitingStackParamList } from './BackfillWaitingStack'; const Stack = createNativeStackNavigator(); // This is the main navigator we nest everything under. diff --git a/apps/mobile/src/navigation/tabs/BrowseStack.tsx b/apps/mobile/src/navigation/tabs/BrowseStack.tsx index 50ca85a848f5..e7ce71588aee 100644 --- a/apps/mobile/src/navigation/tabs/BrowseStack.tsx +++ b/apps/mobile/src/navigation/tabs/BrowseStack.tsx @@ -16,9 +16,7 @@ const Stack = createNativeStackNavigator(); export default function BrowseStack() { return ( - + ({ header: (route) => ( - + ) })} /> @@ -75,7 +77,7 @@ export default function BrowseStack() { export type BrowseStackParamList = { Browse: undefined; - Location: { id: number; path?: string, name?: string }; + Location: { id: number; path?: string; name?: string }; Locations: undefined; Tag: { id: number; color: string }; Tags: undefined; diff --git a/apps/mobile/src/screens/BackfillWaiting.tsx b/apps/mobile/src/screens/BackfillWaiting.tsx index 1fec69460d42..aedc7236489d 100644 --- a/apps/mobile/src/screens/BackfillWaiting.tsx +++ b/apps/mobile/src/screens/BackfillWaiting.tsx @@ -1,6 +1,5 @@ import { useNavigation } from '@react-navigation/native'; import { AppLogo } from '@sd/assets/images'; -import { useLibraryMutation, useLibraryQuery } from '@sd/client'; import { Image } from 'expo-image'; import React, { useEffect } from 'react'; import { Dimensions, Text, View } from 'react-native'; @@ -12,6 +11,7 @@ import Animated, { withTiming } from 'react-native-reanimated'; import { Circle, Defs, RadialGradient, Stop, Svg } from 'react-native-svg'; +import { useLibraryMutation, useLibraryQuery } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; const { width } = Dimensions.get('window'); @@ -52,18 +52,23 @@ const BackfillWaiting = () => { const syncEnabled = useLibraryQuery(['sync.enabled']); useEffect(() => { - (async () => { + (async () => { await enableSync.mutateAsync(null); })(); }, []); return ( - + diff --git a/apps/mobile/src/screens/browse/Location.tsx b/apps/mobile/src/screens/browse/Location.tsx index e8984552de9b..6882025cdc25 100644 --- a/apps/mobile/src/screens/browse/Location.tsx +++ b/apps/mobile/src/screens/browse/Location.tsx @@ -1,5 +1,5 @@ -import { useLibraryQuery, useLibrarySubscription, usePathsExplorerQuery } from '@sd/client'; import { useEffect, useMemo } from 'react'; +import { useLibraryQuery, useLibrarySubscription, usePathsExplorerQuery } from '@sd/client'; import Explorer from '~/components/explorer/Explorer'; import Empty from '~/components/layout/Empty'; import { useSortBy } from '~/hooks/useSortBy'; @@ -14,22 +14,22 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP const locationData = location.data; const order = useSortBy(); const title = useMemo(() => { - return path?.split('/') - .filter((x) => x !== '') - .pop(); - }, [path]) + return path + ?.split('/') + .filter((x) => x !== '') + .pop(); + }, [path]); // makes sure that the location shows newest/modified objects // when a location is opened - useLibrarySubscription( - ['locations.quickRescan', { sub_path: path ?? '', location_id: id }], - { onData() {} } - ); + useLibrarySubscription(['locations.quickRescan', { sub_path: path ?? '', location_id: id }], { + onData() {} + }); const paths = usePathsExplorerQuery({ arg: { filters: [ - { filePath: { hidden: false }}, + { filePath: { hidden: false } }, { filePath: { locations: { in: [id] } } }, { filePath: { @@ -63,7 +63,7 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP navigation.setParams({ id: id, name: locationData?.name ?? 'Location' - }) + }); }, [id, locationData?.name, navigation, path, title]); useEffect(() => { @@ -71,14 +71,19 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP getExplorerStore().path = path ?? ''; }, [id, path]); - return } - {...paths} /> + return ( + + } + {...paths} + /> + ); } diff --git a/apps/mobile/src/screens/browse/Locations.tsx b/apps/mobile/src/screens/browse/Locations.tsx index 7dc4327e2d7a..e482baa9b39a 100644 --- a/apps/mobile/src/screens/browse/Locations.tsx +++ b/apps/mobile/src/screens/browse/Locations.tsx @@ -1,9 +1,9 @@ import { useNavigation } from '@react-navigation/native'; -import { useLibraryQuery } from '@sd/client'; import { Plus } from 'phosphor-react-native'; import { useMemo, useRef } from 'react'; import { FlatList, Pressable, View } from 'react-native'; import { useDebounce } from 'use-debounce'; +import { useLibraryQuery } from '@sd/client'; import Empty from '~/components/layout/Empty'; import { ModalRef } from '~/components/layout/Modal'; import ScreenContainer from '~/components/layout/ScreenContainer'; @@ -47,43 +47,43 @@ export default function LocationsScreen({ viewStyle }: Props) { - location.id.toString()} - ItemSeparatorComponent={() => } - showsVerticalScrollIndicator={false} - ListEmptyComponent={ - - } - numColumns={viewStyle === 'grid' ? 3 : 1} - renderItem={({ item }) => ( - - navigation.navigate('BrowseStack', { - screen: 'Location', - params: { id: item.id } - }) - } - editLocation={() => - navigation.navigate('SettingsStack', { - screen: 'EditLocationSettings', - params: { id: item.id } - }) - } - viewStyle="list" - location={item} - /> - )} - /> + location.id.toString()} + ItemSeparatorComponent={() => } + showsVerticalScrollIndicator={false} + ListEmptyComponent={ + + } + numColumns={viewStyle === 'grid' ? 3 : 1} + renderItem={({ item }) => ( + + navigation.navigate('BrowseStack', { + screen: 'Location', + params: { id: item.id } + }) + } + editLocation={() => + navigation.navigate('SettingsStack', { + screen: 'EditLocationSettings', + params: { id: item.id } + }) + } + viewStyle="list" + location={item} + /> + )} + /> diff --git a/apps/mobile/src/screens/browse/Tag.tsx b/apps/mobile/src/screens/browse/Tag.tsx index 3a955d85b5aa..572a5723c1a8 100644 --- a/apps/mobile/src/screens/browse/Tag.tsx +++ b/apps/mobile/src/screens/browse/Tag.tsx @@ -1,5 +1,5 @@ -import { useLibraryQuery, usePathsExplorerQuery } from '@sd/client'; import { useEffect } from 'react'; +import { useLibraryQuery, usePathsExplorerQuery } from '@sd/client'; import Explorer from '~/components/explorer/Explorer'; import Empty from '~/components/layout/Empty'; import { tw } from '~/lib/tailwind'; @@ -12,9 +12,7 @@ export default function TagScreen({ navigation, route }: BrowseStackScreenProps< const tagData = tag.data; const objects = usePathsExplorerQuery({ - arg: { filters: [ - { object: { tags: { in: [id] } } }, - ], take: 30 }, + arg: { filters: [{ object: { tags: { in: [id] } } }], take: 30 }, enabled: typeof id === 'number', order: null }); @@ -25,20 +23,26 @@ export default function TagScreen({ navigation, route }: BrowseStackScreenProps< navigation.setParams({ id: tagData.id, color: tagData.color as string - }) + }); navigation.setOptions({ - title: tagData.name ?? 'Tag', + title: tagData.name ?? 'Tag' }); } }, [tagData, id, navigation]); - return } {...objects} />; + return ( + + } + {...objects} + /> + ); } diff --git a/apps/mobile/src/screens/browse/Tags.tsx b/apps/mobile/src/screens/browse/Tags.tsx index fa89b6d57f99..7909d83d41ba 100644 --- a/apps/mobile/src/screens/browse/Tags.tsx +++ b/apps/mobile/src/screens/browse/Tags.tsx @@ -1,10 +1,10 @@ import { useNavigation } from '@react-navigation/native'; -import { useLibraryQuery } from '@sd/client'; import { Plus } from 'phosphor-react-native'; import { useMemo, useRef } from 'react'; import { Pressable, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import { useDebounce } from 'use-debounce'; +import { useLibraryQuery } from '@sd/client'; import Empty from '~/components/layout/Empty'; import { ModalRef } from '~/components/layout/Modal'; import ScreenContainer from '~/components/layout/ScreenContainer'; @@ -47,38 +47,38 @@ export default function TagsScreen({ viewStyle = 'list' }: Props) { - ( - { - navigation.navigate('BrowseStack', { - screen: 'Tag', - params: { id: item.id, color: item.color! } - }); - }} - /> - )} - ListEmptyComponent={ - - } - horizontal={false} - numColumns={viewStyle === 'grid' ? 3 : 1} - keyExtractor={(item) => item.id.toString()} - showsHorizontalScrollIndicator={false} - ItemSeparatorComponent={() => } - contentContainerStyle={twStyle( - 'py-6', - tagsData?.length === 0 && 'h-full items-center justify-center' - )} - /> + ( + { + navigation.navigate('BrowseStack', { + screen: 'Tag', + params: { id: item.id, color: item.color! } + }); + }} + /> + )} + ListEmptyComponent={ + + } + horizontal={false} + numColumns={viewStyle === 'grid' ? 3 : 1} + keyExtractor={(item) => item.id.toString()} + showsHorizontalScrollIndicator={false} + ItemSeparatorComponent={() => } + contentContainerStyle={twStyle( + 'py-6', + tagsData?.length === 0 && 'h-full items-center justify-center' + )} + /> diff --git a/apps/mobile/src/screens/search/Filters.tsx b/apps/mobile/src/screens/search/Filters.tsx index 1de25e88f502..bed24f49200d 100644 --- a/apps/mobile/src/screens/search/Filters.tsx +++ b/apps/mobile/src/screens/search/Filters.tsx @@ -6,7 +6,7 @@ const FiltersScreen = () => { return ( <> - + diff --git a/apps/mobile/src/screens/search/Search.tsx b/apps/mobile/src/screens/search/Search.tsx index ef70a107eaf0..1f83b90b2b11 100644 --- a/apps/mobile/src/screens/search/Search.tsx +++ b/apps/mobile/src/screens/search/Search.tsx @@ -1,9 +1,9 @@ import { useIsFocused } from '@react-navigation/native'; -import { useLibraryQuery, usePathsExplorerQuery } from '@sd/client'; import { ArrowLeft, DotsThree, FunnelSimple } from 'phosphor-react-native'; import { Suspense, useDeferredValue, useState } from 'react'; import { ActivityIndicator, Platform, Pressable, TextInput, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useLibraryQuery, usePathsExplorerQuery } from '@sd/client'; import Explorer from '~/components/explorer/Explorer'; import Empty from '~/components/layout/Empty'; import FiltersBar from '~/components/search/filters/FiltersBar'; @@ -29,7 +29,7 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Search'>) => { order, arg: { take: 30, - filters: searchStore.mergedFilters, + filters: searchStore.mergedFilters }, enabled: isFocused && searchStore.mergedFilters.length > 1, // only fetch when screen is focused & filters are applied suspense: true, @@ -46,11 +46,18 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Search'>) => { const noSearch = deferredSearch.length === 0 && appliedFiltersLength === 0; const searchIcon = - locations.length > 0 && noObjects && noSearch ? 'FolderNoSpace' : - noSearch && noObjects ? 'Search' : 'FolderNoSpace'; + locations.length > 0 && noObjects && noSearch + ? 'FolderNoSpace' + : noSearch && noObjects + ? 'Search' + : 'FolderNoSpace'; - const searchDescription = locations.length === 0 ? 'You have not added any locations to search' : noObjects - || noSearch ? 'No files found' : 'No results found for this search'; + const searchDescription = + locations.length === 0 + ? 'You have not added any locations to search' + : noObjects || noSearch + ? 'No files found' + : 'No results found for this search'; return ( ) => { > - {appliedFiltersLength > 0 && } + {appliedFiltersLength > 0 && } {/* Content */} }> - } - tabHeight={false} /> + {...objects} + isEmpty={noObjects} + emptyComponent={ + + } + tabHeight={false} + /> diff --git a/apps/mobile/src/screens/settings/Settings.tsx b/apps/mobile/src/screens/settings/Settings.tsx index 0e3357517eb0..6393fd78dd5a 100644 --- a/apps/mobile/src/screens/settings/Settings.tsx +++ b/apps/mobile/src/screens/settings/Settings.tsx @@ -1,4 +1,3 @@ -import { DebugState, useDebugState, useDebugStateEnabler } from '@sd/client'; import { ArrowsClockwise, Books, @@ -17,6 +16,7 @@ import { } from 'phosphor-react-native'; import React from 'react'; import { Platform, SectionList, Text, TouchableWithoutFeedback, View } from 'react-native'; +import { DebugState, useDebugState, useDebugStateEnabler } from '@sd/client'; import ScreenContainer from '~/components/layout/ScreenContainer'; import { SettingsItem } from '~/components/settings/SettingsItem'; import { tw, twStyle } from '~/lib/tailwind'; @@ -87,12 +87,12 @@ const sections: (debugState: DebugState) => SectionType[] = (debugState) => [ { icon: TagSimple, navigateTo: 'TagsSettings', - title: 'Tags', + title: 'Tags' }, { icon: Cloud, navigateTo: 'CloudSettings', - title: 'Cloud', + title: 'Cloud' }, { icon: ArrowsClockwise, diff --git a/apps/mobile/src/screens/settings/client/GeneralSettings.tsx b/apps/mobile/src/screens/settings/client/GeneralSettings.tsx index ca7e6df265e4..bcfa7082374d 100644 --- a/apps/mobile/src/screens/settings/client/GeneralSettings.tsx +++ b/apps/mobile/src/screens/settings/client/GeneralSettings.tsx @@ -1,5 +1,5 @@ -import { useBridgeQuery, useDebugState } from '@sd/client'; import { Text, View } from 'react-native'; +import { useBridgeQuery, useDebugState } from '@sd/client'; import Card from '~/components/layout/Card'; import ScreenContainer from '~/components/layout/ScreenContainer'; import { Divider } from '~/components/primitive/Divider'; diff --git a/apps/mobile/src/screens/settings/client/LibrarySettings.tsx b/apps/mobile/src/screens/settings/client/LibrarySettings.tsx index 99adaa81974c..21d8ae19a424 100644 --- a/apps/mobile/src/screens/settings/client/LibrarySettings.tsx +++ b/apps/mobile/src/screens/settings/client/LibrarySettings.tsx @@ -1,8 +1,8 @@ -import { LibraryConfigWrapped, useBridgeQuery, useLibraryContext } from '@sd/client'; import { DotsThreeOutlineVertical, Pen, Trash } from 'phosphor-react-native'; import React, { useEffect, useRef } from 'react'; import { Animated, FlatList, Pressable, Text, View } from 'react-native'; import { Swipeable } from 'react-native-gesture-handler'; +import { LibraryConfigWrapped, useBridgeQuery, useLibraryContext } from '@sd/client'; import { ModalRef } from '~/components/layout/Modal'; import ScreenContainer from '~/components/layout/ScreenContainer'; import DeleteLibraryModal from '~/components/modal/confirmModals/DeleteLibraryModal'; @@ -65,12 +65,14 @@ function LibraryItem({ - {library.config.name} - {current && ( - - Current - - )} + + {library.config.name} + + {current && ( + + Current + + )} {library.uuid} @@ -110,19 +112,19 @@ const LibrarySettingsScreen = ({ navigation }: SettingsStackScreenProps<'Library return ( - item.uuid} - renderItem={({ item, index }) => ( - - )} - /> + item.uuid} + renderItem={({ item, index }) => ( + + )} + /> ); }; diff --git a/apps/mobile/src/screens/settings/info/About.tsx b/apps/mobile/src/screens/settings/info/About.tsx index 445dfe5776e2..80e0100e2a22 100644 --- a/apps/mobile/src/screens/settings/info/About.tsx +++ b/apps/mobile/src/screens/settings/info/About.tsx @@ -1,8 +1,8 @@ -import { useBridgeQuery } from '@sd/client'; import { Image } from 'expo-image'; import { Globe } from 'phosphor-react-native'; import React from 'react'; import { Linking, Platform, Text, View } from 'react-native'; +import { useBridgeQuery } from '@sd/client'; import { DiscordIcon, GitHubIcon } from '~/components/icons/Brands'; import ScreenContainer from '~/components/layout/ScreenContainer'; import { Button } from '~/components/primitive/Button'; @@ -98,7 +98,7 @@ const AboutScreen = () => { diff --git a/apps/mobile/src/screens/settings/library/CloudSettings/CloudSettings.tsx b/apps/mobile/src/screens/settings/library/CloudSettings/CloudSettings.tsx index b904005bdca4..97b3d13d1c28 100644 --- a/apps/mobile/src/screens/settings/library/CloudSettings/CloudSettings.tsx +++ b/apps/mobile/src/screens/settings/library/CloudSettings/CloudSettings.tsx @@ -1,6 +1,6 @@ -import { useLibraryContext, useLibraryMutation, useLibraryQuery } from '@sd/client'; import { useMemo } from 'react'; import { ActivityIndicator, FlatList, Text, View } from 'react-native'; +import { useLibraryContext, useLibraryMutation, useLibraryQuery } from '@sd/client'; import Card from '~/components/layout/Card'; import Empty from '~/components/layout/Empty'; import ScreenContainer from '~/components/layout/ScreenContainer'; @@ -88,9 +88,7 @@ const Authenticated = () => { )} showsHorizontalScrollIndicator={false} ItemSeparatorComponent={() => } - renderItem={({ item }) => ( - - )} + renderItem={({ item }) => } keyExtractor={(item) => item.id} numColumns={1} /> diff --git a/apps/mobile/src/screens/settings/library/CloudSettings/Instance.tsx b/apps/mobile/src/screens/settings/library/CloudSettings/Instance.tsx index 81538e82639e..c3daa7f652eb 100644 --- a/apps/mobile/src/screens/settings/library/CloudSettings/Instance.tsx +++ b/apps/mobile/src/screens/settings/library/CloudSettings/Instance.tsx @@ -1,9 +1,9 @@ -import { CloudInstance, HardwareModel } from '@sd/client'; import { Text, View } from 'react-native'; -import { tw } from '~/lib/tailwind'; - +import { CloudInstance, HardwareModel } from '@sd/client'; import { Icon } from '~/components/icons/Icon'; import { hardwareModelToIcon } from '~/components/overview/Devices'; +import { tw } from '~/lib/tailwind'; + import { InfoBox } from './CloudSettings'; interface Props { @@ -14,41 +14,46 @@ const Instance = ({ data }: Props) => { return ( - - - - {data.metadata.name} + + + + + {data.metadata.name} + - Id: - - {data.id} - + Id: + + {data.id} + - - UUID: - - {data.uuid} - + + UUID: + + {data.uuid} + - - Public key: - - {data.identity} - + + Public key: + + {data.identity} + diff --git a/apps/mobile/src/screens/settings/library/CloudSettings/Library.tsx b/apps/mobile/src/screens/settings/library/CloudSettings/Library.tsx index 876007b1f5fe..7e385bd31d48 100644 --- a/apps/mobile/src/screens/settings/library/CloudSettings/Library.tsx +++ b/apps/mobile/src/screens/settings/library/CloudSettings/Library.tsx @@ -1,7 +1,7 @@ -import { CloudLibrary, useLibraryContext, useLibraryMutation } from '@sd/client'; import { CheckCircle, XCircle } from 'phosphor-react-native'; import { useMemo } from 'react'; import { Text, View } from 'react-native'; +import { CloudLibrary, useLibraryContext, useLibraryMutation } from '@sd/client'; import Card from '~/components/layout/Card'; import { Button } from '~/components/primitive/Button'; import { Divider } from '~/components/primitive/Divider'; diff --git a/apps/mobile/src/screens/settings/library/CloudSettings/Login.tsx b/apps/mobile/src/screens/settings/library/CloudSettings/Login.tsx index c818b9a05ff0..35b33991b008 100644 --- a/apps/mobile/src/screens/settings/library/CloudSettings/Login.tsx +++ b/apps/mobile/src/screens/settings/library/CloudSettings/Login.tsx @@ -15,10 +15,10 @@ const Login = () => { - - - To access cloud related features, please login - + + + To access cloud related features, please login + {(authState.status === 'notLoggedIn' || authState.status === 'loggingIn') && ( diff --git a/apps/mobile/src/stores/auth.ts b/apps/mobile/src/stores/auth.ts index 665a5b3d9b74..3ef1079d8435 100644 --- a/apps/mobile/src/stores/auth.ts +++ b/apps/mobile/src/stores/auth.ts @@ -1,7 +1,7 @@ import { RSPCError } from '@oscartbeaumont-sd/rspc-client'; -import { nonLibraryClient, useSolidStore } from '@sd/client'; import { Linking } from 'react-native'; import { createMutable } from 'solid-js/store'; +import { nonLibraryClient, useSolidStore } from '@sd/client'; interface Store { state: { status: 'loading' | 'notLoggedIn' | 'loggingIn' | 'loggedIn' | 'loggingOut' }; @@ -24,7 +24,7 @@ nonLibraryClient .catch((e) => { if (e instanceof RSPCError && e.code === 401) { // TODO: handle error? - console.error("error", e); + console.error('error', e); } store.state = { status: 'notLoggedIn' }; }); diff --git a/apps/mobile/src/stores/explorerStore.ts b/apps/mobile/src/stores/explorerStore.ts index a5019fb39950..c572ff521d6a 100644 --- a/apps/mobile/src/stores/explorerStore.ts +++ b/apps/mobile/src/stores/explorerStore.ts @@ -1,6 +1,6 @@ -import { ThumbKey, resetStore } from '@sd/client'; import { proxy, useSnapshot } from 'valtio'; import { proxySet } from 'valtio/utils'; +import { resetStore, ThumbKey } from '@sd/client'; export type ExplorerLayoutMode = 'list' | 'grid' | 'media'; diff --git a/apps/mobile/src/stores/modalStore.ts b/apps/mobile/src/stores/modalStore.ts index dcd87151b70b..12d46c2cb605 100644 --- a/apps/mobile/src/stores/modalStore.ts +++ b/apps/mobile/src/stores/modalStore.ts @@ -1,6 +1,6 @@ -import { ExplorerItem } from '@sd/client'; import { createRef } from 'react'; import { proxy, ref, useSnapshot } from 'valtio'; +import { ExplorerItem } from '@sd/client'; import { ModalRef } from '~/components/layout/Modal'; const store = proxy({ diff --git a/apps/mobile/src/stores/searchStore.ts b/apps/mobile/src/stores/searchStore.ts index 1ae39ec204c9..d26cf333a897 100644 --- a/apps/mobile/src/stores/searchStore.ts +++ b/apps/mobile/src/stores/searchStore.ts @@ -1,5 +1,5 @@ -import { SearchFilterArgs } from '@sd/client'; import { proxy, useSnapshot } from 'valtio'; +import { SearchFilterArgs } from '@sd/client'; import { IconName } from '~/components/icons/Icon'; export type SearchFilters = 'locations' | 'tags' | 'name' | 'extension' | 'hidden' | 'kind'; @@ -74,7 +74,7 @@ function updateArrayOrObject( array: T[], item: any, filterByKey: string = 'id', - isObject: boolean = false, + isObject: boolean = false ): T[] { if (isObject) { const index = (array as any).findIndex((i: any) => i.id === item[filterByKey]); @@ -139,10 +139,10 @@ const searchStore = proxy< //update the filter with the value switch (filter) { case 'locations': - searchStore.filters[filter] = [value] as FilterItem[] + searchStore.filters[filter] = [value] as FilterItem[]; break; case 'tags': - searchStore.filters[filter] = [value] as TagItem[] + searchStore.filters[filter] = [value] as TagItem[]; break; } //apply the filters so it shows in the UI diff --git a/crates/fda/README.md b/crates/fda/README.md index e3d2273cb2d0..f663e7f117e7 100644 --- a/crates/fda/README.md +++ b/crates/fda/README.md @@ -1,5 +1,5 @@ # Spacedrive FDA Handling -## MacOS +## `MacOS` -On MacOS, we are able to open the "Full disk access" settings prompt to instruct the user to allow Spacedrive full disk access, which should alleviate all permissions issues. +On `MacOS`, we are able to open the "Full disk access" settings prompt to instruct the user to allow Spacedrive full disk access, which should alleviate all permissions issues. diff --git a/docs/developers/architecture/vdfs.mdx b/docs/developers/architecture/vdfs.mdx index 2f7d5cad17a1..a2cc1ed4d25c 100644 --- a/docs/developers/architecture/vdfs.mdx +++ b/docs/developers/architecture/vdfs.mdx @@ -11,6 +11,7 @@ A virtual filesystem is simply a layer of abstraction between a physical filesys ## How it works Spacedrive defines a VDFS under the following data model; A Library, Nodes, Volumes, Locations, Paths and Objects. + - A node is a single machine running the VDFS software. - It syncronizes a CRDT based database, called the Library, with other nodes in the network. - The library consists of Locations, found on Volumes, diff --git a/docs/developers/p2p/discovery.mdx b/docs/developers/p2p/discovery.mdx index 57a0a58d45c4..a9fb22412ab7 100644 --- a/docs/developers/p2p/discovery.mdx +++ b/docs/developers/p2p/discovery.mdx @@ -8,9 +8,10 @@ index: 25 Discovery is the process of finding other nodes to connect with. We do this through the following 3 systems: - - Manual entry by the user - - [Local Network Discovery](/docs/developers/p2p/local-network-discovery) via mDNS - - [Relay](/docs/developers/p2p/relay) via mDNS + +- Manual entry by the user +- [Local Network Discovery](/docs/developers/p2p/local-network-discovery) via mDNS +- [Relay](/docs/developers/p2p/relay) via mDNS ## Overview of methods @@ -22,15 +23,15 @@ A quirk manual entry is that when we attempt a connection we don't know the remo This table summarises the differences between the methods: -| | mDNS | Relay | Manual | -|-------------------------------------------------------------------------|---------|----------|-----------| -| Requires upfront knowledge of existence of peer | No | Yes | Yes | -| Knows connection info (metadata, RemoteIdentity) ahead of connection | Yes | Yes | No | +| | mDNS | Relay | Manual | +| -------------------------------------------------------------------- | ---- | ----- | ------ | +| Requires upfront knowledge of existence of peer | No | Yes | Yes | +| Knows connection info (metadata, RemoteIdentity) ahead of connection | Yes | Yes | No | ## Manually provided peers The user can manually provide a set of [`SocketAddr`](https://doc.rust-lang.org/std/net/enum.SocketAddr.html)'s or [FQDN](https://en.wikipedia.org/wiki/Fully_qualified_domain_name)'s and the P2P system will attempt to connect to them. If a domain is provided the P2P system will resolve it to an IP address and then attempt to connect to that address. -This feature primarily exists for usage with Docker as mDNS discovery does not work correctly, however it could be useful for working around difficult network setups. It's important to note that you *must* use [port forwarding](https://en.wikipedia.org/wiki/Port_forwarding) and set a static port for the node when using this feature. +This feature primarily exists for usage with Docker as mDNS discovery does not work correctly, however it could be useful for working around difficult network setups. It's important to note that you _must_ use [port forwarding](https://en.wikipedia.org/wiki/Port_forwarding) and set a static port for the node when using this feature. -When you add a manual peer it will *not* show up in the nodes list until the P2P system is able to establish a connection. This is because without having established a connection the system is unable to determine the remote nodes identity or metadata which is required for it to be considered discovered. +When you add a manual peer it will _not_ show up in the nodes list until the P2P system is able to establish a connection. This is because without having established a connection the system is unable to determine the remote nodes identity or metadata which is required for it to be considered discovered. diff --git a/docs/developers/p2p/local-network-discovery.mdx b/docs/developers/p2p/local-network-discovery.mdx index beb1a0887cd2..941277c3cfbf 100644 --- a/docs/developers/p2p/local-network-discovery.mdx +++ b/docs/developers/p2p/local-network-discovery.mdx @@ -14,6 +14,7 @@ We make use of the [mdns-sd](https://docs.rs/mdns-sd) crate. ## Service Structure The following is an example of what would be broadcast from a single Spacedrive node: + ```toml # {remote_identity_of_self}._sd._udp.local. @@ -37,9 +38,10 @@ Within `sd-core` this is defined in two parts. The [`PeerMetadata` struct](https /> Within Spacedrive's settings the user is able to choose between three modes for local network discovery: - - **Contacts only**: Only devices that are in your contacts list will be able to see your device. - - **Enabled**: All devices on the local network will be able to see your device. - - **Disabled**: No devices on the local network will be able to see your device. + +- **Contacts only**: Only devices that are in your contacts list will be able to see your device. +- **Enabled**: All devices on the local network will be able to see your device. +- **Disabled**: No devices on the local network will be able to see your device. **Enabled** and **Disabled** are implemented by spawning and shutting down the [`sd_p2p::Mdns`](https://github.com/spacedriveapp/spacedrive/blob/44478207e72495b3777e294660d78939711b544f/crates/p2p/src/mdns.rs#L17) service as required within `sd-core`. @@ -47,7 +49,7 @@ Within Spacedrive's settings the user is able to choose between three modes for ## Integration with Spacedrive accounts -P2P is currently *not* integrated with Spacedrive accounts and we will integrate it in the future for better security. +P2P is currently _not_ integrated with Spacedrive accounts and we will integrate it in the future for better security. Right now we use a remote identity to identify the remote device, however tihs is not very user friendly. If a [MITM](https://en.wikipedia.org/wiki/Man-in-the-middle_attack)-style attack is preformed the remote identity will show up with the attacker's device but this isn't going to be particularly noticable by the user. @@ -71,7 +73,7 @@ This issue is tracked as [ENG-1343](https://linear.app/spacedriveapp/issue/ENG-1 ## Problems with mobile -mDNS discovery does not work on mobile at the moment. To prevent this affecting other devices, we patch [`if-watch`](https://docs.rs/if-watch) using the fork [spacedriveapp/if-watch](https://github.com/spacedriveapp/if-watch). This fork basically implements a *no-op* for mobile so that the core is able to compile. +mDNS discovery does not work on mobile at the moment. To prevent this affecting other devices, we patch [`if-watch`](https://docs.rs/if-watch) using the fork [spacedriveapp/if-watch](https://github.com/spacedriveapp/if-watch). This fork basically implements a _no-op_ for mobile so that the core is able to compile. This issue is tracked as [ENG-1108](https://linear.app/spacedriveapp/issue/ENG-1108/mdns-working-on-ios). @@ -88,4 +90,3 @@ When information about the device is exposed to the local network we introduce t The intention is for the contacts only mode to mitigate this risk as the device will only be discoverable by other devices that are in the users contacts and all information will be unintelligible. Apple outline some information about how they combat this for AirDrop [here](https://support.apple.com/en-au/guide/security/sec2261183f4/web) and we can do something similar. - diff --git a/docs/developers/p2p/overview.mdx b/docs/developers/p2p/overview.mdx index d1c51098fe44..cd50202fff7a 100644 --- a/docs/developers/p2p/overview.mdx +++ b/docs/developers/p2p/overview.mdx @@ -9,14 +9,14 @@ Our peer-to-peer technology works at the heart of Spacedrive allowing all of you ## Terminology - - **Node**: An application running Spacedrive's network stack. - - This could be the Spacedrive app or the P2P relay. - - If you have multiple Spacedrive installations open on your computer, each one is an independent node. - - **Library**: A logical collection of your data within Spacedrive. - - Conceptually, a library is the conflict resolved state of one or more **instances**, although a lot of the time we don't strictly treat it that way. - - **Instance**: An instance of a library running on a particular node. - - An instance correlates directly to each SQLite file. - - You could *technically* have more than one instance for a library on a single node, although our core would fall apart as we identify traffic by library. - - [`Identity`](https://github.com/spacedriveapp/spacedrive/blob/518d5836f6585a5f597c3ae5a0d27d084adc0a63/crates/p2p/src/identity.rs#L29) - A public/private keypair which represents the library or node. - - [`RemoteIdentity`](https://github.com/spacedriveapp/spacedrive/blob/518d5836f6585a5f597c3ae5a0d27d084adc0a63/crates/p2p/src/identity.rs#L70) - A public key which represents the library or node. - - [`PeerId`](https://docs.rs/libp2p/latest/libp2p/struct.PeerId.html) - The identifier libp2p uses. Can be derived from a `RemoteIdentity`. +- **Node**: An application running Spacedrive's network stack. + - This could be the Spacedrive app or the P2P relay. + - If you have multiple Spacedrive installations open on your computer, each one is an independent node. +- **Library**: A logical collection of your data within Spacedrive. + - Conceptually, a library is the conflict resolved state of one or more **instances**, although a lot of the time we don't strictly treat it that way. +- **Instance**: An instance of a library running on a particular node. + - An instance correlates directly to each SQLite file. + - You could _technically_ have more than one instance for a library on a single node, although our core would fall apart as we identify traffic by library. +- [`Identity`](https://github.com/spacedriveapp/spacedrive/blob/518d5836f6585a5f597c3ae5a0d27d084adc0a63/crates/p2p/src/identity.rs#L29) - A public/private keypair which represents the library or node. +- [`RemoteIdentity`](https://github.com/spacedriveapp/spacedrive/blob/518d5836f6585a5f597c3ae5a0d27d084adc0a63/crates/p2p/src/identity.rs#L70) - A public key which represents the library or node. +- [`PeerId`](https://docs.rs/libp2p/latest/libp2p/struct.PeerId.html) - The identifier libp2p uses. Can be derived from a `RemoteIdentity`. diff --git a/docs/developers/p2p/protocols.mdx b/docs/developers/p2p/protocols.mdx index 00fd6ce5304b..6e336851f069 100644 --- a/docs/developers/p2p/protocols.mdx +++ b/docs/developers/p2p/protocols.mdx @@ -18,15 +18,17 @@ We have the implementation of a basic ping protocol. This is not actually used w Spacedrop is a system for sending files quickly to other peers. It is intended for sending to peers that have not been paired into the library. It is great for sending a file to a friend on your same network running Spacedrive but you can use the regular file manager for sharing a file without another node in your library. This protocol works but some of the following are missing features: - - Pause/resumable transfers - - Transfer a folder - [ENG-1297](https://linear.app/spacedriveapp/issue/ENG-1297/spacedrop-create-folder-button-in-save-dialog-for-multiple-file) - - Usage with `sd-server` will result in bugs if you have multiple web clients - [ENG-1034](https://linear.app/spacedriveapp/issue/ENG-1034/spacedrop-on-multi-user-server-will-break) and [ENG-1522](https://linear.app/spacedriveapp/issue/ENG-1522/spacedrop-on-web) + +- Pause/resumable transfers +- Transfer a folder - [ENG-1297](https://linear.app/spacedriveapp/issue/ENG-1297/spacedrop-create-folder-button-in-save-dialog-for-multiple-file) +- Usage with `sd-server` will result in bugs if you have multiple web clients - [ENG-1034](https://linear.app/spacedriveapp/issue/ENG-1034/spacedrop-on-multi-user-server-will-break) and [ENG-1522](https://linear.app/spacedriveapp/issue/ENG-1522/spacedrop-on-web) The following are known bugs: - - [ENG-1298](https://linear.app/spacedriveapp/issue/ENG-1298/spacedrop-cancel-prior-to-toast) - - [ENG-1035](https://linear.app/spacedriveapp/issue/ENG-1035/spacedrop-show-toast-while-waiting-for-remote-to-acceptdeny-response) - - [ENG-1203](https://linear.app/spacedriveapp/issue/ENG-1203/spacedrop-ui-handle-timeouts) - - [ENG-1211](https://linear.app/spacedriveapp/issue/ENG-1211/spacedrop-what-if-file-content-changes-while-sending) + +- [ENG-1298](https://linear.app/spacedriveapp/issue/ENG-1298/spacedrop-cancel-prior-to-toast) +- [ENG-1035](https://linear.app/spacedriveapp/issue/ENG-1035/spacedrop-show-toast-while-waiting-for-remote-to-acceptdeny-response) +- [ENG-1203](https://linear.app/spacedriveapp/issue/ENG-1203/spacedrop-ui-handle-timeouts) +- [ENG-1211](https://linear.app/spacedriveapp/issue/ENG-1211/spacedrop-what-if-file-content-changes-while-sending) ## rspc @@ -35,16 +37,18 @@ The following are known bugs: This protocol was an experiment to expose the rspc router of a node over P2P. Although it works this is a security nightmare so it has been disabled by default and hidden behind the `wipP2P` feature flag. How to test this feature: - - Enable the `wipP2P` feature flag - - Enable the feature within the network page of settings - - Ensure "Enable remote acccess" is enabled on both nodes - - Click the "rspc remote" button on the node you want to connect to. - - If the connection fails you will be presented with a white screen, otherwise you will be given a library selection and once seleted you will be given Spacedrive UI running on the remote node. + +- Enable the `wipP2P` feature flag +- Enable the feature within the network page of settings +- Ensure "Enable remote acccess" is enabled on both nodes +- Click the "rspc remote" button on the node you want to connect to. +- If the connection fails you will be presented with a white screen, otherwise you will be given a library selection and once seleted you will be given Spacedrive UI running on the remote node. Major problems with this feature: - - This protocol doesn't have any security (hence it being disabled by default). It's also a nightmare to secure as it's full access (including to do filesystem actions) or no access. - [ENG-1646](https://linear.app/spacedriveapp/issue/ENG-1646/rspc-over-p2p-handle-condition-in-ui-of-remote-offline-node) - - The rspc websocket connection established over the P2P system is leaked so it will never be cleaned up. Fixing this would require changes to rspc. - [ENG-1647](https://linear.app/spacedriveapp/issue/ENG-1647/stop-leaking-rspc-p2p-websockets) - - Any usage of rspc outside the React context will still be using the local node's rspc router. We don't do this often but we definitely do it. - [ENG-1648](https://linear.app/spacedriveapp/issue/ENG-1648/prevent-any-usage-of-rspc-out-of-the-react-context) + +- This protocol doesn't have any security (hence it being disabled by default). It's also a nightmare to secure as it's full access (including to do filesystem actions) or no access. - [ENG-1646](https://linear.app/spacedriveapp/issue/ENG-1646/rspc-over-p2p-handle-condition-in-ui-of-remote-offline-node) +- The rspc websocket connection established over the P2P system is leaked so it will never be cleaned up. Fixing this would require changes to rspc. - [ENG-1647](https://linear.app/spacedriveapp/issue/ENG-1647/stop-leaking-rspc-p2p-websockets) +- Any usage of rspc outside the React context will still be using the local node's rspc router. We don't do this often but we definitely do it. - [ENG-1648](https://linear.app/spacedriveapp/issue/ENG-1648/prevent-any-usage-of-rspc-out-of-the-react-context) From my ([oscartbeaumont](https://github.com/oscartbeaumont)'s) perspective this was a cool experiment but not something we should ship because getting it's nightmare to get security right. diff --git a/docs/developers/p2p/relay.mdx b/docs/developers/p2p/relay.mdx index 3a81d870b555..32086ee671c2 100644 --- a/docs/developers/p2p/relay.mdx +++ b/docs/developers/p2p/relay.mdx @@ -68,14 +68,14 @@ Given the core of the `sd_p2p` crate is decoupled from libp2p we could easily im The major advantage to using WebRTC would be the ability to use a SaSS solution for hosting the relay infrastructure. WebRTC is based on [STUN](https://en.wikipedia.org/wiki/STUN) and [TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT) which are very ubiquitous protocols. The following is a comparison of some webrtc services: | | Pricing (per GB ) | Has Billing API | -|-------------------------------------------------------------------|-------------------|-----------------| +| ----------------------------------------------------------------- | ----------------- | --------------- | | [Cloudflare Calls](https://developers.cloudflare.com/calls/turn/) | 0.05$ | No | | [Twilio](https://www.twilio.com/stun-turn) | 0.40$ to 0.80$ | No | | [Metered](https://www.metered.ca/stun-turn) | 0.40$ to 0.10$ | Yes | WebRTC also has a built in system for authentication via the SDP object that needs to be exchanged between peers for a valid connection to be established. For an explaination of webrtc checkout [this Fireship video](https://www.youtube.com/watch?v=WmR9IMUD_CY). -libp2p *does* have a [WebRTC transport](https://docs.rs/libp2p-webrtc/0.7.1-alpha) but it seems to be only for browser to server communication not server to server like we require so I don't think it would be viable for our usecase. +libp2p _does_ have a [WebRTC transport](https://docs.rs/libp2p-webrtc/0.7.1-alpha) but it seems to be only for browser to server communication not server to server like we require so I don't think it would be viable for our usecase. ### Security @@ -90,17 +90,16 @@ The relay works through encrypted communication so we can not read the data that 1. Set up the server using the following command: - ```bash - cargo run -p sd-p2p-relay init - # You will be prompted to enter the p2p secret. - # It can be found in the `spacedrive-api` Vercel project as the `P2P_SECRET` environment variable. - ``` + ```bash + cargo run -p sd-p2p-relay init + # You will be prompted to enter the p2p secret. + # It can be found in the `spacedrive-api` Vercel project as the `P2P_SECRET` environment variable. + ``` 2. Now that you have set up the server, you can run the relay server using the following command: - ```bash - cargo run -p sd-p2p-relay - ``` - -*Note that you will need to ensure port `7373` is exposed through your firewall for this to work.* + ```bash + cargo run -p sd-p2p-relay + ``` +_Note that you will need to ensure port `7373` is exposed through your firewall for this to work._ diff --git a/docs/developers/p2p/sd_p2p.mdx b/docs/developers/p2p/sd_p2p.mdx index 791b4c77d90a..1e4c8109f264 100644 --- a/docs/developers/p2p/sd_p2p.mdx +++ b/docs/developers/p2p/sd_p2p.mdx @@ -24,8 +24,9 @@ There are special hooks called listeners. These are implemented as a superset of ## Default hooks? The `sd_p2p` crate comes with a few default hooks: - - `Mdns` - Local network discovery using mDNS - - `Quic` - Quic transport layer built on top of `libp2p` + +- `Mdns` - Local network discovery using mDNS +- `Quic` - Quic transport layer built on top of `libp2p` Spacedrive implements some of it's own hooks within the `core/src/p2p` directory to deal with libraries correctly. diff --git a/docs/developers/p2p/sd_p2p_block.mdx b/docs/developers/p2p/sd_p2p_block.mdx index d33d8b3ac1d0..eaaa29689cfc 100644 --- a/docs/developers/p2p/sd_p2p_block.mdx +++ b/docs/developers/p2p/sd_p2p_block.mdx @@ -20,9 +20,10 @@ Below I have outlined some of my thoughts on how to build an improved version: ### Progress tracking Currently the protocol works like the following: - - Send block of file - - Wait for ack - - Send next block + +- Send block of file +- Wait for ack +- Send next block This is obviously not ideal as it means the sender is waiting for the receiver to ack each block before sending the next one. @@ -34,7 +35,7 @@ We can either, remove acknowledgements or send them less frequently, some testin [Tracking issue](https://linear.app/spacedriveapp/issue/ENG-1312/spaceblock-file-checksum) -I think the new version of the protocol should compute a [Blake3](https://en.wikipedia.org/wiki/BLAKE_(hash_function)) hash on both the sender and the receiver and ensure they much before the transfer is considered complete. This ensures both any data corruption or bugs in the Spacedrop protocol don't result in data loss for the user. The current protocol lacks this feature. +I think the new version of the protocol should compute a [Blake3]() hash on both the sender and the receiver and ensure they much before the transfer is considered complete. This ensures both any data corruption or bugs in the Spacedrop protocol don't result in data loss for the user. The current protocol lacks this feature. ### Cancellation diff --git a/docs/developers/p2p/sd_p2p_proto.mdx b/docs/developers/p2p/sd_p2p_proto.mdx index fc80045472b0..de1385e2ac80 100644 --- a/docs/developers/p2p/sd_p2p_proto.mdx +++ b/docs/developers/p2p/sd_p2p_proto.mdx @@ -15,10 +15,10 @@ Before building, the performance of both [msgpack](https://docs.rs/rmp-serde) an This logically follows as if you use a synchronous serializer, you will do the following: - - Send the total length of the message - - Allocate a buffer for the message - - Wait asynchronously for the buffer to be filled - - Synchronously copy from the buffer into each of the struct fields +- Send the total length of the message +- Allocate a buffer for the message +- Wait asynchronously for the buffer to be filled +- Synchronously copy from the buffer into each of the struct fields When using an asynchronous serializer you can skip sending the messages length and allocating the intermediate buffer as we can rely on the known length of each field while decoding - this is a win for performance and memory usage. diff --git a/docs/developers/p2p/sd_p2p_tunnel.mdx b/docs/developers/p2p/sd_p2p_tunnel.mdx index 47fd3c581421..5b0d89dc2f79 100644 --- a/docs/developers/p2p/sd_p2p_tunnel.mdx +++ b/docs/developers/p2p/sd_p2p_tunnel.mdx @@ -7,7 +7,10 @@ index: 24 [Implementation](https://github.com/spacedriveapp/spacedrive/tree/main/crates/p2p/crates/tunnel) - + You can wrap an `UnicastStream` with a `sd_p2p_tunnel::Tunnel` to authenticate that the remote peer is who they say they are and to encrypt the data being sent between the two peers. @@ -17,7 +20,7 @@ So an attacker could setup a modified version of Spacedrive which presents it's Using `Tunnel` will prevent this attack by cryptographically verifying the remote peer holds the private key for the library instance they are advertising. This process also acts as an authentication mechanism for the remote peer to ensure they are allowed to request data within the library. -You **should** use this is your communicating with a library on a remote node (Eg. sync, request file) but if you're talking with the node (Eg. Spacedrop) you don't need it. +You **should** use this is your communicating with a library on a remote node (Eg. sync, request file) but if you're talking with the node (Eg. Spacedrop) you don't need it. ## Example diff --git a/docs/product/resources/privacy.mdx b/docs/product/resources/privacy.mdx index fa603ffb48fb..69b88747f4a8 100644 --- a/docs/product/resources/privacy.mdx +++ b/docs/product/resources/privacy.mdx @@ -11,6 +11,7 @@ This policy applies to information we collect: - Through mobile, web, and desktop applications you download from this Website, which provide dedicated non-browser-based interaction between you and this Website. It does not apply to information collected by: + - us offline or through any other means, including on any other website operated by Spacedrive or any third party; or - any third party, including through any application or content (including advertising) that may link to or be accessible from the Website. diff --git a/interface/app/$libraryId/Explorer/ContextMenu/AssignTagMenuItems.tsx b/interface/app/$libraryId/Explorer/ContextMenu/AssignTagMenuItems.tsx index 8c80dc54b608..c4a82643611f 100644 --- a/interface/app/$libraryId/Explorer/ContextMenu/AssignTagMenuItems.tsx +++ b/interface/app/$libraryId/Explorer/ContextMenu/AssignTagMenuItems.tsx @@ -1,11 +1,11 @@ import { Plus } from '@phosphor-icons/react'; -import { ExplorerItem, useLibraryQuery } from '@sd/client'; -import { Button, ModifierKeys, dialogManager, tw } from '@sd/ui'; import { useQueryClient } from '@tanstack/react-query'; import { useVirtualizer } from '@tanstack/react-virtual'; import clsx from 'clsx'; import { RefObject, useMemo, useRef } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; +import { ExplorerItem, useLibraryQuery } from '@sd/client'; +import { Button, dialogManager, ModifierKeys, tw } from '@sd/ui'; import CreateDialog, { AssignTagItems, useAssignItemsToTag @@ -38,7 +38,6 @@ function useData({ items }: Props) { { suspense: true } ); - return { tags: { ...tags, diff --git a/interface/app/$libraryId/Explorer/ExplorerPathBar.tsx b/interface/app/$libraryId/Explorer/ExplorerPathBar.tsx index 4e0e824baf9d..8af234b224ef 100644 --- a/interface/app/$libraryId/Explorer/ExplorerPathBar.tsx +++ b/interface/app/$libraryId/Explorer/ExplorerPathBar.tsx @@ -1,4 +1,8 @@ import { AppWindow, ArrowSquareOut, CaretRight, ClipboardText } from '@phosphor-icons/react'; +import clsx from 'clsx'; +import { memo, useMemo, useState } from 'react'; +import { useNavigate } from 'react-router'; +import { createSearchParams } from 'react-router-dom'; import { getExplorerItemData, getIndexedItemFilePath, @@ -6,13 +10,9 @@ import { useLibraryQuery } from '@sd/client'; import { ContextMenu } from '@sd/ui'; -import clsx from 'clsx'; -import { memo, useMemo, useState } from 'react'; -import { useNavigate } from 'react-router'; -import { createSearchParams } from 'react-router-dom'; -import { useTabsContext } from '~/TabsContext'; import { Icon } from '~/components'; import { useIsDark, useLocale, useOperatingSystem } from '~/hooks'; +import { useTabsContext } from '~/TabsContext'; import { usePlatform } from '~/util/Platform'; import { useExplorerContext } from './Context'; @@ -119,7 +119,9 @@ export const ExplorerPathBar = memo(() => { return (
{ const rescanLocation = useLibraryMutation('locations.subPathRescan'); const createFolder = useLibraryMutation(['files.createFolder'], { onError: (e) => { - toast.error({ title: t('create_folder_error'), body: t('error_message', { error: e }) }); + toast.error({ + title: t('create_folder_error'), + body: t('error_message', { error: e }) + }); console.error(e); }, onSuccess: (folder) => { @@ -66,7 +69,10 @@ export default (props: PropsWithChildren) => { }); const createEphemeralFolder = useLibraryMutation(['ephemeralFiles.createFolder'], { onError: (e) => { - toast.error({ title: t('create_folder_error'), body: t('error_message', { error: e }) }); + toast.error({ + title: t('create_folder_error'), + body: t('error_message', { error: e }) + }); console.error(e); }, onSuccess: (folder) => { diff --git a/interface/app/$libraryId/Explorer/TopBarOptions.tsx b/interface/app/$libraryId/Explorer/TopBarOptions.tsx index 1fcd4147d110..48e63a61408b 100644 --- a/interface/app/$libraryId/Explorer/TopBarOptions.tsx +++ b/interface/app/$libraryId/Explorer/TopBarOptions.tsx @@ -8,15 +8,15 @@ import { SquaresFour, Tag } from '@phosphor-icons/react'; -import { ExplorerLayout, useSelector } from '@sd/client'; import clsx from 'clsx'; import { useMemo } from 'react'; import { useDocumentEventListener } from 'rooks'; +import { ExplorerLayout, useSelector } from '@sd/client'; import { useKeyMatcher, useLocale } from '~/hooks'; import { KeyManager } from '../KeyManager'; import { Spacedrop, SpacedropButton } from '../Spacedrop'; -import TopBarOptions, { TOP_BAR_ICON_CLASSLIST, ToolOption } from '../TopBar/TopBarOptions'; +import TopBarOptions, { ToolOption, TOP_BAR_ICON_CLASSLIST } from '../TopBar/TopBarOptions'; import { useExplorerContext } from './Context'; import OptionsPanel from './OptionsPanel'; import { explorerStore } from './store'; @@ -83,7 +83,7 @@ export const useExplorerTopBarOptions = () => { toolTipLabel: t('show_inspector'), keybinds: [controlIcon, 'I'], onClick: () => { - explorerStore.showInspector = !explorerStore.showInspector + explorerStore.showInspector = !explorerStore.showInspector; }, icon: ( { [explorer.selectedItems, item] ); - const canGoBack = currentIndex !== 0; const canGoForward = currentIndex !== maxIndex; diff --git a/interface/app/$libraryId/Explorer/View/GridView/Item/index.tsx b/interface/app/$libraryId/Explorer/View/GridView/Item/index.tsx index 8621bd88ac35..aeb40ee0ffc0 100644 --- a/interface/app/$libraryId/Explorer/View/GridView/Item/index.tsx +++ b/interface/app/$libraryId/Explorer/View/GridView/Item/index.tsx @@ -1,15 +1,15 @@ +import clsx from 'clsx'; +import { memo, useMemo } from 'react'; import { - Tag, getItemFilePath, getItemObject, humanizeSize, + Tag, useExplorerLayoutStore, useLibraryQuery, useSelector, type ExplorerItem } from '@sd/client'; -import clsx from 'clsx'; -import { memo, useMemo } from 'react'; import { useLocale } from '~/hooks'; import { useExplorerContext } from '../../../Context'; @@ -132,21 +132,25 @@ const ItemTags = () => { const data = object || filePath; const tags = data && 'tags' in data ? data.tags : []; return ( -
- {tags?.slice(0, 3).map((tag: {tag: Tag}, i: number) => ( -
+ > + {tags?.slice(0, 3).map((tag: { tag: Tag }, i: number) => ( +
))}
); -} +}; const ItemSize = () => { const item = useGridViewItemContext(); diff --git a/interface/app/$libraryId/Explorer/View/GridView/index.tsx b/interface/app/$libraryId/Explorer/View/GridView/index.tsx index 15d3b6101111..082439d95f8e 100644 --- a/interface/app/$libraryId/Explorer/View/GridView/index.tsx +++ b/interface/app/$libraryId/Explorer/View/GridView/index.tsx @@ -1,7 +1,7 @@ import { Grid, useGrid } from '@virtual-grid/react'; import { useCallback } from 'react'; - import { useExplorerLayoutStore } from '@sd/client'; + import { useExplorerContext } from '../../Context'; import { getItemData, getItemId, uniqueId } from '../../util'; import { useExplorerViewContext } from '../Context'; @@ -18,7 +18,8 @@ export const GridView = () => { const explorerSettings = explorer.useSettingsSnapshot(); const layoutStore = useExplorerLayoutStore(); - const itemDetailsHeight = (layoutStore.showTags ? 60 : 44) + (explorerSettings.showBytesInGridView ? 20 : 0); + const itemDetailsHeight = + (layoutStore.showTags ? 60 : 44) + (explorerSettings.showBytesInGridView ? 20 : 0); const itemHeight = explorerSettings.gridItemSize + itemDetailsHeight; const BOTTOM_PADDING = layoutStore.showTags ? 16 : 12; diff --git a/interface/app/$libraryId/Explorer/View/ListView/Item.tsx b/interface/app/$libraryId/Explorer/View/ListView/Item.tsx index 7f9c2cdabf75..879b38c46212 100644 --- a/interface/app/$libraryId/Explorer/View/ListView/Item.tsx +++ b/interface/app/$libraryId/Explorer/View/ListView/Item.tsx @@ -1,7 +1,7 @@ -import { getItemFilePath, useSelector, type ExplorerItem } from '@sd/client'; import { flexRender, type Cell } from '@tanstack/react-table'; import clsx from 'clsx'; import { memo, useMemo } from 'react'; +import { getItemFilePath, useSelector, type ExplorerItem } from '@sd/client'; import { TABLE_PADDING_X } from '.'; import { useExplorerContext } from '../../Context'; diff --git a/interface/app/$libraryId/Explorer/View/ListView/useTable.tsx b/interface/app/$libraryId/Explorer/View/ListView/useTable.tsx index 2e1e01702f2a..74d8e4a5c558 100644 --- a/interface/app/$libraryId/Explorer/View/ListView/useTable.tsx +++ b/interface/app/$libraryId/Explorer/View/ListView/useTable.tsx @@ -1,13 +1,3 @@ -import { - getExplorerItemData, - getIndexedItemFilePath, - getItemFilePath, - getItemObject, - humanizeSize, - useExplorerLayoutStore, - useSelector, - type ExplorerItem -} from '@sd/client'; import { CellContext, functionalUpdate, @@ -19,6 +9,16 @@ import clsx from 'clsx'; import dayjs from 'dayjs'; import { memo, useMemo } from 'react'; import { stringify } from 'uuid'; +import { + getExplorerItemData, + getIndexedItemFilePath, + getItemFilePath, + getItemObject, + humanizeSize, + useExplorerLayoutStore, + useSelector, + type ExplorerItem +} from '@sd/client'; import { useLocale } from '~/hooks'; import { useExplorerContext } from '../../Context'; @@ -72,36 +72,37 @@ const NameCell = memo(({ item, selected }: { item: ExplorerItem; selected: boole idleClassName={clsx(explorerLayout.showTags ? '!w-4/5' : '!w-full')} editLines={3} /> - {explorerLayout.showTags && ( - - )} + {explorerLayout.showTags && }
); }); -const Tags = ({item}: {item: ExplorerItem}) => { +const Tags = ({ item }: { item: ExplorerItem }) => { const object = getItemObject(item); const filePath = getItemFilePath(item); const data = object || filePath; const tags = data && 'tags' in data ? data.tags : []; - return ( -
- {tags.map(({tag}, i: number) => ( -
- ))} -
- ) -} - + return ( +
+ {tags.map(({ tag }, i: number) => ( +
+ ))} +
+ ); +}; const KindCell = ({ kind }: { kind: string }) => { const explorer = useExplorerContext(); diff --git a/interface/app/$libraryId/Explorer/View/MediaView/index.tsx b/interface/app/$libraryId/Explorer/View/MediaView/index.tsx index 2637fed1bb46..6554d6a7df1d 100644 --- a/interface/app/$libraryId/Explorer/View/MediaView/index.tsx +++ b/interface/app/$libraryId/Explorer/View/MediaView/index.tsx @@ -1,6 +1,6 @@ -import { OrderingKey, getOrderingDirection, getOrderingKey } from '@sd/client'; import { LoadMoreTrigger, useGrid, useScrollMargin, useVirtualizer } from '@virtual-grid/react'; import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import { getOrderingDirection, getOrderingKey, OrderingKey } from '@sd/client'; import { useLocale } from '~/hooks'; import { useExplorerContext } from '../../Context'; diff --git a/interface/app/$libraryId/Explorer/index.tsx b/interface/app/$libraryId/Explorer/index.tsx index d2c765662802..f24ac0581395 100644 --- a/interface/app/$libraryId/Explorer/index.tsx +++ b/interface/app/$libraryId/Explorer/index.tsx @@ -1,4 +1,5 @@ import { FolderNotchOpen } from '@phosphor-icons/react'; +import { CSSProperties, useEffect, type PropsWithChildren, type ReactNode } from 'react'; import { explorerLayout, useExplorerLayoutStore, @@ -6,7 +7,6 @@ import { useRspcLibraryContext, useSelector } from '@sd/client'; -import { CSSProperties, useEffect, type PropsWithChildren, type ReactNode } from 'react'; import { useShortcut } from '~/hooks'; import { useTopBarContext } from '../TopBar/Context'; @@ -117,7 +117,9 @@ export default function Explorer(props: PropsWithChildren) { listViewOptions={{ hideHeaderBorder: true }} scrollPadding={{ top: topBar.topBarHeight, - bottom: showPathBar ? PATH_BAR_HEIGHT + (showTagBar ? TAG_BAR_HEIGHT : 0) : undefined + bottom: showPathBar + ? PATH_BAR_HEIGHT + (showTagBar ? TAG_BAR_HEIGHT : 0) + : undefined }} />
@@ -131,12 +133,12 @@ export default function Explorer(props: PropsWithChildren) { {showInspector && ( )} diff --git a/interface/app/$libraryId/Explorer/store.ts b/interface/app/$libraryId/Explorer/store.ts index da6e6a545ab8..4814f972aa0d 100644 --- a/interface/app/$libraryId/Explorer/store.ts +++ b/interface/app/$libraryId/Explorer/store.ts @@ -2,8 +2,8 @@ import { proxy } from 'valtio'; import { proxySet } from 'valtio/utils'; import { z } from 'zod'; import { - ThumbKey, resetStore, + ThumbKey, type DoubleClickAction, type ExplorerItem, type ExplorerLayout, diff --git a/interface/app/$libraryId/Explorer/useExplorer.ts b/interface/app/$libraryId/Explorer/useExplorer.ts index 88af654dfca9..36c798e6238e 100644 --- a/interface/app/$libraryId/Explorer/useExplorer.ts +++ b/interface/app/$libraryId/Explorer/useExplorer.ts @@ -1,3 +1,7 @@ +import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from 'react'; +import { useDebouncedCallback } from 'use-debounce'; +import { proxy, snapshot, subscribe, useSnapshot } from 'valtio'; +import { z } from 'zod'; import type { ExplorerItem, ExplorerLayout, @@ -8,10 +12,6 @@ import type { Tag } from '@sd/client'; import { ObjectKindEnum, type Ordering, type OrderingKeys } from '@sd/client'; -import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from 'react'; -import { useDebouncedCallback } from 'use-debounce'; -import { proxy, snapshot, subscribe, useSnapshot } from 'valtio'; -import { z } from 'zod'; import { createDefaultExplorerSettings } from './store'; import { uniqueId } from './util'; diff --git a/interface/app/$libraryId/Layout/Sidebar/DebugPopover.tsx b/interface/app/$libraryId/Layout/Sidebar/DebugPopover.tsx index 5f4dc7af2038..7af5e0ac77d6 100644 --- a/interface/app/$libraryId/Layout/Sidebar/DebugPopover.tsx +++ b/interface/app/$libraryId/Layout/Sidebar/DebugPopover.tsx @@ -86,7 +86,10 @@ export default () => { onClick={() => { // if debug telemetry sharing is about to be disabled, but telemetry logging is enabled // then disable it - if (!debugState.shareFullTelemetry === false && debugState.telemetryLogging) + if ( + !debugState.shareFullTelemetry === false && + debugState.telemetryLogging + ) debugState.telemetryLogging = false; debugState.shareFullTelemetry = !debugState.shareFullTelemetry; }} @@ -102,7 +105,10 @@ export default () => { onClick={() => { // if telemetry logging is about to be enabled, but debug telemetry sharing is disabled // then enable it - if (!debugState.telemetryLogging && debugState.shareFullTelemetry === false) + if ( + !debugState.telemetryLogging && + debugState.shareFullTelemetry === false + ) debugState.shareFullTelemetry = true; debugState.telemetryLogging = !debugState.telemetryLogging; }} @@ -119,7 +125,8 @@ export default () => { size="sm" variant="gray" onClick={() => { - if (nodeState?.data?.data_path) platform.openPath!(nodeState?.data?.data_path); + if (nodeState?.data?.data_path) + platform.openPath!(nodeState?.data?.data_path); }} > Open @@ -232,7 +239,11 @@ function FeatureFlagSelector() { iconProps={{ weight: 'bold', size: 16 }} onClick={() => toggleFeatureFlag(feat)} className="font-medium text-white" - icon={featureFlags.find((f) => feat === f) !== undefined ? CheckSquare : undefined} + icon={ + featureFlags.find((f) => feat === f) !== undefined + ? CheckSquare + : undefined + } /> ))} @@ -270,7 +281,9 @@ function CloudOriginSelect() { } value={origin.data} > - https://app.spacedrive.com + + https://app.spacedrive.com + http://localhost:3000 )} @@ -282,7 +295,10 @@ function ExplorerBehaviorSelect() { const { explorerOperatingSystem } = useExplorerOperatingSystem(); return ( - (explorerOperatingSystemStore.os = v)} + > macOS windows diff --git a/interface/app/$libraryId/Layout/Sidebar/SidebarLayout/LibrariesDropdown.tsx b/interface/app/$libraryId/Layout/Sidebar/SidebarLayout/LibrariesDropdown.tsx index 5ba21071793e..034be0f2ff76 100644 --- a/interface/app/$libraryId/Layout/Sidebar/SidebarLayout/LibrariesDropdown.tsx +++ b/interface/app/$libraryId/Layout/Sidebar/SidebarLayout/LibrariesDropdown.tsx @@ -2,11 +2,11 @@ import { CloudArrowDown, Gear, Lock, Plus } from '@phosphor-icons/react'; import clsx from 'clsx'; import { useClientContext } from '@sd/client'; import { dialogManager, Dropdown, DropdownMenu } from '@sd/ui'; +import JoinDialog from '~/app/$libraryId/settings/node/libraries/JoinDialog'; import { useLocale } from '~/hooks'; import CreateDialog from '../../../settings/node/libraries/CreateDialog'; import { useSidebarContext } from './Context'; -import JoinDialog from '~/app/$libraryId/settings/node/libraries/JoinDialog'; export default () => { const { library, libraries, currentLibraryId } = useClientContext(); @@ -67,7 +67,11 @@ export default () => { label={t('join_library')} icon={CloudArrowDown} iconProps={{ weight: 'bold', size: 16 }} - onClick={() => dialogManager.create((dp) => )} + onClick={() => + dialogManager.create((dp) => ( + + )) + } className="font-medium" /> { - +const AddDeviceDialog = ({ node, ...dialogProps }: Props) => { const form = useForm(); const { t } = useLocale(); return ( } - ctaLabel="Add" - closeLabel="Close" + dialog={useDialog(dialogProps)} + form={form} + title={t('add_device')} + description={t('Add Device Description')} + icon={ + + } + ctaLabel="Add" + closeLabel="Close" >
@@ -40,9 +37,9 @@ const AddDeviceDialog = ({node, ...dialogProps}: Props) => { - +
-
+ ); }; diff --git a/interface/app/$libraryId/Layout/Sidebar/sections/Devices/index.tsx b/interface/app/$libraryId/Layout/Sidebar/sections/Devices/index.tsx index 2c4a12726523..eb9084044553 100644 --- a/interface/app/$libraryId/Layout/Sidebar/sections/Devices/index.tsx +++ b/interface/app/$libraryId/Layout/Sidebar/sections/Devices/index.tsx @@ -1,5 +1,5 @@ import { HardwareModel, useBridgeQuery } from '@sd/client'; -import { Button, Tooltip, dialogManager } from '@sd/ui'; +import { Button, dialogManager, Tooltip } from '@sd/ui'; import { Icon } from '~/components'; import { useLocale } from '~/hooks'; import { hardwareModelToIcon } from '~/util/hardware'; @@ -9,38 +9,36 @@ import Section from '../../SidebarLayout/Section'; import AddDeviceDialog from './AddDeviceDialog'; export default function DevicesSection() { - const { data: node } = useBridgeQuery(['nodeState']); - const { t } = useLocale(); + const { data: node } = useBridgeQuery(['nodeState']); + const { t } = useLocale(); - return ( -
- {node && ( - - {node.device_model ? ( - - ) : ( - - )} + return ( +
+ {node && ( + + {node.device_model ? ( + + ) : ( + + )} - {node.name} - - )} + {node.name} + + )} - + -
- - ); +
+ ); } diff --git a/interface/app/$libraryId/Layout/Sidebar/sections/Locations/ContextMenu.tsx b/interface/app/$libraryId/Layout/Sidebar/sections/Locations/ContextMenu.tsx index 589ed0c9a198..6caebb3dae29 100644 --- a/interface/app/$libraryId/Layout/Sidebar/sections/Locations/ContextMenu.tsx +++ b/interface/app/$libraryId/Layout/Sidebar/sections/Locations/ContextMenu.tsx @@ -42,7 +42,6 @@ export const ContextMenu = ({ /> )); } - } catch (error) { toast.error(t('error_message', { error })); } diff --git a/interface/app/$libraryId/debug/cloud.tsx b/interface/app/$libraryId/debug/cloud.tsx index 0920488d3e2b..b74b95b763fa 100644 --- a/interface/app/$libraryId/debug/cloud.tsx +++ b/interface/app/$libraryId/debug/cloud.tsx @@ -1,15 +1,15 @@ import { CheckCircle, XCircle } from '@phosphor-icons/react'; +import { Suspense, useMemo } from 'react'; import { + auth, CloudInstance, CloudLibrary, HardwareModel, - auth, useLibraryContext, useLibraryMutation, useLibraryQuery } from '@sd/client'; import { Button, Card, Loader, tw } from '@sd/ui'; -import { Suspense, useMemo } from 'react'; import { Icon } from '~/components'; import { AuthRequiredOverlay } from '~/components/AuthRequiredOverlay'; import { LoginButton } from '~/components/LoginButton'; @@ -30,9 +30,11 @@ export const Component = () => { return (
-
- -

To access cloud related features, please login

+
+ +

+ To access cloud related features, please login +

@@ -45,7 +47,6 @@ export const Component = () => { return
{authSensitiveChild()}
; }; - // million-ignore function Authenticated() { const { library } = useLibraryContext(); @@ -77,26 +78,30 @@ function Authenticated() { ) : (
- -
- -

{t("cloud_connect_description")}

-
- + +
+ +

+ {t('cloud_connect_description')} +

+
+
)} diff --git a/interface/app/$libraryId/debug/index.ts b/interface/app/$libraryId/debug/index.ts index 195f5c624e91..4cf60b56c57c 100644 --- a/interface/app/$libraryId/debug/index.ts +++ b/interface/app/$libraryId/debug/index.ts @@ -2,5 +2,5 @@ import { RouteObject } from 'react-router'; export const debugRoutes = [ { path: 'cloud', lazy: () => import('./cloud') }, - { path: 'actors', lazy: () => import('./actors') }, + { path: 'actors', lazy: () => import('./actors') } ] satisfies RouteObject[]; diff --git a/interface/app/$libraryId/location/$id.tsx b/interface/app/$libraryId/location/$id.tsx index 76606d318b79..6f5d7157cc48 100644 --- a/interface/app/$libraryId/location/$id.tsx +++ b/interface/app/$libraryId/location/$id.tsx @@ -1,4 +1,6 @@ import { ArrowClockwise, Info } from '@phosphor-icons/react'; +import { useCallback, useEffect, useMemo } from 'react'; +import { stringify } from 'uuid'; import { arraysEqual, FilePathOrder, @@ -9,8 +11,6 @@ import { useOnlineLocations } from '@sd/client'; import { Loader, Tooltip } from '@sd/ui'; -import { useCallback, useEffect, useMemo } from 'react'; -import { stringify } from 'uuid'; import { LocationIdParamsSchema } from '~/app/route-schemas'; import { Folder, Icon } from '~/components'; import { diff --git a/interface/app/$libraryId/overview/StatCard.tsx b/interface/app/$libraryId/overview/StatCard.tsx index 98cc07e96818..f2912be21610 100644 --- a/interface/app/$libraryId/overview/StatCard.tsx +++ b/interface/app/$libraryId/overview/StatCard.tsx @@ -24,7 +24,6 @@ const StatCard = ({ icon, name, connectionType, ...stats }: StatCardProps) => { const totalSpaceSingleValue = humanizeSize(stats.totalSpace); const { totalSpace, freeSpace, usedSpaceSpace } = useMemo(() => { - const totalSpace = humanizeSize(stats.totalSpace, { no_thousands: false }); @@ -35,7 +34,6 @@ const StatCard = ({ icon, name, connectionType, ...stats }: StatCardProps) => { freeSpace, usedSpaceSpace: humanizeSize(totalSpace.bytes - freeSpace.bytes) }; - }, [stats]); useEffect(() => { @@ -58,7 +56,15 @@ const StatCard = ({ icon, name, connectionType, ...stats }: StatCardProps) => { progress={progress} strokeWidth={6} trackStrokeWidth={6} - strokeColor={progress >= 90 ? '#E14444' : progress >= 75 ? 'darkorange' : progress >= 60 ? 'yellow' : '#2599FF'} + strokeColor={ + progress >= 90 + ? '#E14444' + : progress >= 75 + ? 'darkorange' + : progress >= 60 + ? 'yellow' + : '#2599FF' + } fillColor="transparent" trackStrokeColor={isDark ? '#252631' : '#efefef'} strokeLinecap="square" diff --git a/interface/app/$libraryId/peer/$id.tsx b/interface/app/$libraryId/peer/$id.tsx index 62c76486ef70..6212ab82ad23 100644 --- a/interface/app/$libraryId/peer/$id.tsx +++ b/interface/app/$libraryId/peer/$id.tsx @@ -3,6 +3,7 @@ import { NodeIdParamsSchema } from '~/app/route-schemas'; import { Icon } from '~/components'; import { useRouteTitle, useZodRouteParams } from '~/hooks'; import { hardwareModelToIcon } from '~/util/hardware'; + import { TopBarPortal } from '../TopBar/Portal'; import StarfieldEffect from './StarfieldEffect'; // Import the StarfieldEffect component diff --git a/interface/app/$libraryId/peer/StarfieldEffect.tsx b/interface/app/$libraryId/peer/StarfieldEffect.tsx index 515ee2a2115c..dfd935b7753e 100644 --- a/interface/app/$libraryId/peer/StarfieldEffect.tsx +++ b/interface/app/$libraryId/peer/StarfieldEffect.tsx @@ -1,254 +1,334 @@ import React, { useEffect, useRef } from 'react'; const StarfieldEffect: React.FC = () => { - const canvasRef = useRef(null); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext('2d'); - if (!ctx) return; - - const resizeCanvas = () => { - const scale = window.devicePixelRatio || 1; - const width = canvas.parentElement?.clientWidth || 800; - const height = canvas.parentElement?.clientHeight || 300; - canvas.width = width * scale; - canvas.height = height * scale; - canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; - ctx.scale(scale, scale); - }; - - resizeCanvas(); - window.addEventListener('resize', resizeCanvas); - - canvas.style.position = 'absolute'; - canvas.oncontextmenu = e => e.preventDefault(); - - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const pix = imageData.data; - - const center = { x: canvas.width / 2, y: canvas.height / 2 }; - - let mouseActive = false; - let mouseDown = false; - let mousePos = { x: center.x, y: center.y }; - - let starSpeed = 20; - const starSpeedMin = starSpeed; - const starSpeedMax = 100; - const starDistance = 5000; - - let fov = 320; - const fovMin = 210; - const fovMax = fov; - - const starHolderCount = 8000; // Increased star count - const starHolder: any[] = []; - const starBgHolder: any[] = []; - - const backgroundColor = { r: 28, g: 29, b: 37, a: 255 }; - - const clearImageData = () => { - for (let i = 0, l = pix.length; i < l; i += 4) { - pix[i] = backgroundColor.r; - pix[i + 1] = backgroundColor.g; - pix[i + 2] = backgroundColor.b; - pix[i + 3] = backgroundColor.a; - } - }; - - const setPixel = (x: number, y: number, r: number, g: number, b: number, a: number) => { - const i = (x + y * canvas.width) * 4; - pix[i] = r; - pix[i + 1] = g; - pix[i + 2] = b; - pix[i + 3] = a; - }; - - const setPixelAdditive = (x: number, y: number, r: number, g: number, b: number, a: number) => { - const i = (x + y * canvas.width) * 4; - pix[i] += r; - pix[i + 1] += g; - pix[i + 2] += b; - pix[i + 3] = a; - }; - - const drawLine = (x1: number, y1: number, x2: number, y2: number, r: number, g: number, b: number, a: number) => { - const dx = Math.abs(x2 - x1); - const dy = Math.abs(y2 - y1); - const sx = x1 < x2 ? 1 : -1; - const sy = y1 < y2 ? 1 : -1; - let err = dx - dy; - let lx = x1; - let ly = y1; - - const continueLoop = true - while (continueLoop) { - if (lx > 0 && lx < canvas.width && ly > 0 && ly < canvas.height) { - setPixel(lx, ly, r, g, b, a); - } - if (lx === x2 && ly === y2) break; - const e2 = 2 * err; - if (e2 > -dx) { - err -= dy; - lx += sx; - } - if (e2 < dy) { - err += dx; - ly += sy; - } - } - }; - - const addParticle = (x: number, y: number, z: number, ox: number, oy: number, oz: number) => { - const particle = { x, y, z, ox, oy, x2d: 0, y2d: 0, color: { r: 0, g: 0, b: 0, a: 0 }, oColor: { r: 0, g: 0, b: 0, a: 0 }, w: 0, distance: 0, distanceTotal: 0 }; - return particle; - }; - - const addParticles = () => { - let x, y, z, colorValue, particle; - for (let i = 0; i < starHolderCount / 3; i++) { - x = Math.random() * 24000 - 12000; - y = Math.random() * 4500 - 2250; - z = Math.round(Math.random() * starDistance); - colorValue = 185; // Adjusted color - particle = addParticle(x, y, z, x, y, z); - particle.color = { r: 171, g: 172, b: 185, a: 255 }; - starBgHolder.push(particle); - } - for (let i = 0; i < starHolderCount; i++) { - x = Math.random() * 10000 - 5000; - y = Math.random() * 10000 - 5000; - z = Math.round(Math.random() * starDistance); - colorValue = 185; // Adjusted color - particle = addParticle(x, y, z, x, y, z); - particle.color = { r: 171, g: 172, b: 185, a: 255 }; - particle.oColor = { r: 171, g: 172, b: 185, a: 255 }; - particle.w = 1; - particle.distance = starDistance - z; - particle.distanceTotal = Math.round(starDistance + fov - particle.w); - starHolder.push(particle); - } - }; - - const animloop = () => { - requestAnimationFrame(animloop); - render(); - }; - - const render = () => { - clearImageData(); - let star, scale; - - if (mouseActive) { - starSpeed += 2; - if (starSpeed > starSpeedMax) starSpeed = starSpeedMax; - } else { - starSpeed -= 1; - if (starSpeed < starSpeedMin) starSpeed = starSpeedMin; - } - - fov += mouseActive ? -1 : 0.5; - fov = Math.max(fovMin, Math.min(fovMax, fov)); - - const warpSpeedValue = starSpeed * (starSpeed / (starSpeedMax / 2)); - - for (const bgStar of starBgHolder) { - star = bgStar; - scale = fov / (fov + star.z); - star.x2d = star.x * scale + center.x; - star.y2d = star.y * scale + center.y; - if (star.x2d > 0 && star.x2d < canvas.width && star.y2d > 0 && star.y2d < canvas.height) { - setPixel(star.x2d | 0, star.y2d | 0, star.color.r, star.color.g, star.color.b, 255); - } - } - - for (const mainStar of starHolder) { - star = mainStar; - star.z -= starSpeed; - star.distance += starSpeed; - if (star.z < -fov + star.w) { - star.z = starDistance; - star.distance = 0; - } - - const distancePercent = star.distance / star.distanceTotal; - star.color.r = Math.floor(star.oColor.r * distancePercent); - star.color.g = Math.floor(star.oColor.g * distancePercent); - star.color.b = Math.floor(star.oColor.b * distancePercent); - - scale = fov / (fov + star.z); - star.x2d = star.x * scale + center.x; - star.y2d = star.y * scale + center.y; - - if (star.x2d > 0 && star.x2d < canvas.width && star.y2d > 0 && star.y2d < canvas.height) { - setPixelAdditive(star.x2d | 0, star.y2d | 0, star.color.r, star.color.g, star.color.b, 255); - } - - if (starSpeed !== starSpeedMin) { - const nz = star.z + warpSpeedValue; - scale = fov / (fov + nz); - const x2d = star.x * scale + center.x; - const y2d = star.y * scale + center.y; - if (x2d > 0 && x2d < canvas.width && y2d > 0 && y2d < canvas.height) { - drawLine(star.x2d | 0, star.y2d | 0, x2d | 0, y2d | 0, star.color.r, star.color.g, star.color.b, 255); - } - } - } - - ctx.putImageData(imageData, 0, 0); - - center.x += (mousePos.x - center.x) * 0.015; - if (!mouseActive) { - center.x += (canvas.width / 2 - center.x) * 0.015; - } - }; - - const getMousePos = (event: MouseEvent) => { - const rect = canvas.getBoundingClientRect(); - return { x: event.clientX - rect.left, y: event.clientY - rect.top }; - }; - - const mouseMoveHandler = (event: MouseEvent) => { - mousePos = getMousePos(event); - }; - - const mouseEnterHandler = () => { - mouseActive = true; - }; - - const mouseLeaveHandler = () => { - mouseActive = false; - mouseDown = false; - }; - - canvas.addEventListener('mousemove', mouseMoveHandler); - canvas.addEventListener('mousedown', () => { mouseDown = true; }); - canvas.addEventListener('mouseup', () => { mouseDown = false; }); - canvas.addEventListener('mouseenter', mouseEnterHandler); - canvas.addEventListener('mouseleave', mouseLeaveHandler); - - addParticles(); - animloop(); - - return () => { - canvas.removeEventListener('mousemove', mouseMoveHandler); - canvas.removeEventListener('mousedown', () => { mouseDown = true; }); - canvas.removeEventListener('mouseup', () => { mouseDown = false; }); - canvas.removeEventListener('mouseenter', mouseEnterHandler); - canvas.removeEventListener('mouseleave', mouseLeaveHandler); - window.removeEventListener('resize', resizeCanvas); - }; - }, []); - - return ( - - Drop files here to send with Spacedrop - - ); + const canvasRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + const resizeCanvas = () => { + const scale = window.devicePixelRatio || 1; + const width = canvas.parentElement?.clientWidth || 800; + const height = canvas.parentElement?.clientHeight || 300; + canvas.width = width * scale; + canvas.height = height * scale; + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + ctx.scale(scale, scale); + }; + + resizeCanvas(); + window.addEventListener('resize', resizeCanvas); + + canvas.style.position = 'absolute'; + canvas.oncontextmenu = (e) => e.preventDefault(); + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const pix = imageData.data; + + const center = { x: canvas.width / 2, y: canvas.height / 2 }; + + let mouseActive = false; + let mouseDown = false; + let mousePos = { x: center.x, y: center.y }; + + let starSpeed = 20; + const starSpeedMin = starSpeed; + const starSpeedMax = 100; + const starDistance = 5000; + + let fov = 320; + const fovMin = 210; + const fovMax = fov; + + const starHolderCount = 8000; // Increased star count + const starHolder: any[] = []; + const starBgHolder: any[] = []; + + const backgroundColor = { r: 28, g: 29, b: 37, a: 255 }; + + const clearImageData = () => { + for (let i = 0, l = pix.length; i < l; i += 4) { + pix[i] = backgroundColor.r; + pix[i + 1] = backgroundColor.g; + pix[i + 2] = backgroundColor.b; + pix[i + 3] = backgroundColor.a; + } + }; + + const setPixel = (x: number, y: number, r: number, g: number, b: number, a: number) => { + const i = (x + y * canvas.width) * 4; + pix[i] = r; + pix[i + 1] = g; + pix[i + 2] = b; + pix[i + 3] = a; + }; + + const setPixelAdditive = ( + x: number, + y: number, + r: number, + g: number, + b: number, + a: number + ) => { + const i = (x + y * canvas.width) * 4; + pix[i] += r; + pix[i + 1] += g; + pix[i + 2] += b; + pix[i + 3] = a; + }; + + const drawLine = ( + x1: number, + y1: number, + x2: number, + y2: number, + r: number, + g: number, + b: number, + a: number + ) => { + const dx = Math.abs(x2 - x1); + const dy = Math.abs(y2 - y1); + const sx = x1 < x2 ? 1 : -1; + const sy = y1 < y2 ? 1 : -1; + let err = dx - dy; + let lx = x1; + let ly = y1; + + const continueLoop = true; + while (continueLoop) { + if (lx > 0 && lx < canvas.width && ly > 0 && ly < canvas.height) { + setPixel(lx, ly, r, g, b, a); + } + if (lx === x2 && ly === y2) break; + const e2 = 2 * err; + if (e2 > -dx) { + err -= dy; + lx += sx; + } + if (e2 < dy) { + err += dx; + ly += sy; + } + } + }; + + const addParticle = ( + x: number, + y: number, + z: number, + ox: number, + oy: number, + oz: number + ) => { + const particle = { + x, + y, + z, + ox, + oy, + x2d: 0, + y2d: 0, + color: { r: 0, g: 0, b: 0, a: 0 }, + oColor: { r: 0, g: 0, b: 0, a: 0 }, + w: 0, + distance: 0, + distanceTotal: 0 + }; + return particle; + }; + + const addParticles = () => { + let x, y, z, colorValue, particle; + for (let i = 0; i < starHolderCount / 3; i++) { + x = Math.random() * 24000 - 12000; + y = Math.random() * 4500 - 2250; + z = Math.round(Math.random() * starDistance); + colorValue = 185; // Adjusted color + particle = addParticle(x, y, z, x, y, z); + particle.color = { r: 171, g: 172, b: 185, a: 255 }; + starBgHolder.push(particle); + } + for (let i = 0; i < starHolderCount; i++) { + x = Math.random() * 10000 - 5000; + y = Math.random() * 10000 - 5000; + z = Math.round(Math.random() * starDistance); + colorValue = 185; // Adjusted color + particle = addParticle(x, y, z, x, y, z); + particle.color = { r: 171, g: 172, b: 185, a: 255 }; + particle.oColor = { r: 171, g: 172, b: 185, a: 255 }; + particle.w = 1; + particle.distance = starDistance - z; + particle.distanceTotal = Math.round(starDistance + fov - particle.w); + starHolder.push(particle); + } + }; + + const animloop = () => { + requestAnimationFrame(animloop); + render(); + }; + + const render = () => { + clearImageData(); + let star, scale; + + if (mouseActive) { + starSpeed += 2; + if (starSpeed > starSpeedMax) starSpeed = starSpeedMax; + } else { + starSpeed -= 1; + if (starSpeed < starSpeedMin) starSpeed = starSpeedMin; + } + + fov += mouseActive ? -1 : 0.5; + fov = Math.max(fovMin, Math.min(fovMax, fov)); + + const warpSpeedValue = starSpeed * (starSpeed / (starSpeedMax / 2)); + + for (const bgStar of starBgHolder) { + star = bgStar; + scale = fov / (fov + star.z); + star.x2d = star.x * scale + center.x; + star.y2d = star.y * scale + center.y; + if ( + star.x2d > 0 && + star.x2d < canvas.width && + star.y2d > 0 && + star.y2d < canvas.height + ) { + setPixel( + star.x2d | 0, + star.y2d | 0, + star.color.r, + star.color.g, + star.color.b, + 255 + ); + } + } + + for (const mainStar of starHolder) { + star = mainStar; + star.z -= starSpeed; + star.distance += starSpeed; + if (star.z < -fov + star.w) { + star.z = starDistance; + star.distance = 0; + } + + const distancePercent = star.distance / star.distanceTotal; + star.color.r = Math.floor(star.oColor.r * distancePercent); + star.color.g = Math.floor(star.oColor.g * distancePercent); + star.color.b = Math.floor(star.oColor.b * distancePercent); + + scale = fov / (fov + star.z); + star.x2d = star.x * scale + center.x; + star.y2d = star.y * scale + center.y; + + if ( + star.x2d > 0 && + star.x2d < canvas.width && + star.y2d > 0 && + star.y2d < canvas.height + ) { + setPixelAdditive( + star.x2d | 0, + star.y2d | 0, + star.color.r, + star.color.g, + star.color.b, + 255 + ); + } + + if (starSpeed !== starSpeedMin) { + const nz = star.z + warpSpeedValue; + scale = fov / (fov + nz); + const x2d = star.x * scale + center.x; + const y2d = star.y * scale + center.y; + if (x2d > 0 && x2d < canvas.width && y2d > 0 && y2d < canvas.height) { + drawLine( + star.x2d | 0, + star.y2d | 0, + x2d | 0, + y2d | 0, + star.color.r, + star.color.g, + star.color.b, + 255 + ); + } + } + } + + ctx.putImageData(imageData, 0, 0); + + center.x += (mousePos.x - center.x) * 0.015; + if (!mouseActive) { + center.x += (canvas.width / 2 - center.x) * 0.015; + } + }; + + const getMousePos = (event: MouseEvent) => { + const rect = canvas.getBoundingClientRect(); + return { x: event.clientX - rect.left, y: event.clientY - rect.top }; + }; + + const mouseMoveHandler = (event: MouseEvent) => { + mousePos = getMousePos(event); + }; + + const mouseEnterHandler = () => { + mouseActive = true; + }; + + const mouseLeaveHandler = () => { + mouseActive = false; + mouseDown = false; + }; + + canvas.addEventListener('mousemove', mouseMoveHandler); + canvas.addEventListener('mousedown', () => { + mouseDown = true; + }); + canvas.addEventListener('mouseup', () => { + mouseDown = false; + }); + canvas.addEventListener('mouseenter', mouseEnterHandler); + canvas.addEventListener('mouseleave', mouseLeaveHandler); + + addParticles(); + animloop(); + + return () => { + canvas.removeEventListener('mousemove', mouseMoveHandler); + canvas.removeEventListener('mousedown', () => { + mouseDown = true; + }); + canvas.removeEventListener('mouseup', () => { + mouseDown = false; + }); + canvas.removeEventListener('mouseenter', mouseEnterHandler); + canvas.removeEventListener('mouseleave', mouseLeaveHandler); + window.removeEventListener('resize', resizeCanvas); + }; + }, []); + + return ( + + Drop files here to send with Spacedrop + + ); }; export default StarfieldEffect; diff --git a/interface/app/$libraryId/recents.tsx b/interface/app/$libraryId/recents.tsx index 354b62728ac2..19b596ddac49 100644 --- a/interface/app/$libraryId/recents.tsx +++ b/interface/app/$libraryId/recents.tsx @@ -54,7 +54,7 @@ export function Component() { //since this is a recents page issue only - this is sufficient unless otherwise useEffect(() => { setForceRender((prev) => !prev); - }, [items.query.isFetching]); + }, [items.query.isFetching]); return ( @@ -76,7 +76,7 @@ export function Component() { )} - } diff --git a/interface/app/$libraryId/settings/Setting.tsx b/interface/app/$libraryId/settings/Setting.tsx index 4b314e6f8ea3..9bdf57c70f40 100644 --- a/interface/app/$libraryId/settings/Setting.tsx +++ b/interface/app/$libraryId/settings/Setting.tsx @@ -29,7 +29,12 @@ export default ({ mini, registerName, ...props }: PropsWithChildren) => {

{props.title}

{props.toolTipLabel && ( - props.infoUrl && platform.openLink(props.infoUrl)} size={15} /> + + props.infoUrl && platform.openLink(props.infoUrl) + } + size={15} + /> )}
@@ -38,7 +43,9 @@ export default ({ mini, registerName, ...props }: PropsWithChildren) => {
{mini && props.children}
- {registerName ? : null} + {registerName ? ( + + ) : null} ); }; diff --git a/interface/app/$libraryId/settings/client/index.ts b/interface/app/$libraryId/settings/client/index.ts index eb7e503bf261..3fa39ac433a8 100644 --- a/interface/app/$libraryId/settings/client/index.ts +++ b/interface/app/$libraryId/settings/client/index.ts @@ -10,5 +10,5 @@ export default [ { path: 'privacy', lazy: () => import('./privacy') }, { path: 'backups', lazy: () => import('./backups') }, { path: 'network', lazy: () => import('./network/index') }, - { path: 'network/debug', lazy: () => import('./network/debug') }, + { path: 'network/debug', lazy: () => import('./network/debug') } ] satisfies RouteObject[]; diff --git a/interface/app/$libraryId/settings/library/locations/IndexerRuleEditor/RulesForm.tsx b/interface/app/$libraryId/settings/library/locations/IndexerRuleEditor/RulesForm.tsx index b3946bb1ecf8..0461616b8536 100644 --- a/interface/app/$libraryId/settings/library/locations/IndexerRuleEditor/RulesForm.tsx +++ b/interface/app/$libraryId/settings/library/locations/IndexerRuleEditor/RulesForm.tsx @@ -22,7 +22,7 @@ const ruleKinds: UnionToTuple = [ 'RejectFilesByGlob', 'AcceptIfChildrenDirectoriesArePresent', 'RejectIfChildrenDirectoriesArePresent', - 'IgnoredByGit' + 'IgnoredByGit' ]; const ruleKindEnum = z.enum(ruleKinds); diff --git a/interface/app/$libraryId/settings/library/locations/PathInput.tsx b/interface/app/$libraryId/settings/library/locations/PathInput.tsx index fae0b5470e2a..3beb8a97b6fc 100644 --- a/interface/app/$libraryId/settings/library/locations/PathInput.tsx +++ b/interface/app/$libraryId/settings/library/locations/PathInput.tsx @@ -2,10 +2,10 @@ import clsx from 'clsx'; import { forwardRef } from 'react'; import { useFormContext } from 'react-hook-form'; import { InputField, InputFieldProps, toast } from '@sd/ui'; +import { useLocale } from '~/hooks'; import { usePlatform } from '~/util/Platform'; import { openDirectoryPickerDialog } from './openDirectoryPickerDialog'; -import { useLocale } from '~/hooks'; export const LocationPathInputField = forwardRef< HTMLInputElement, @@ -13,7 +13,7 @@ export const LocationPathInputField = forwardRef< >((props, ref) => { const platform = usePlatform(); const form = useFormContext(); - const {t} = useLocale() + const { t } = useLocale(); return ( toast.error(t('error_message', { error: String(error) })) - )} + .catch((error) => toast.error(t('error_message', { error: String(error) }))) + } readOnly={platform.platform !== 'web'} className={clsx('mb-3', platform.platform === 'web' || 'cursor-pointer')} /> diff --git a/interface/app/$libraryId/settings/library/sync.tsx b/interface/app/$libraryId/settings/library/sync.tsx index 37750f75171f..fe6124525a77 100644 --- a/interface/app/$libraryId/settings/library/sync.tsx +++ b/interface/app/$libraryId/settings/library/sync.tsx @@ -43,18 +43,17 @@ export const Component = () => { <> {syncEnabled.data === false ? ( - +
); diff --git a/interface/app/$libraryId/settings/library/tags/CreateDialog.tsx b/interface/app/$libraryId/settings/library/tags/CreateDialog.tsx index 4c7933aebf1c..af791e2dd7ef 100644 --- a/interface/app/$libraryId/settings/library/tags/CreateDialog.tsx +++ b/interface/app/$libraryId/settings/library/tags/CreateDialog.tsx @@ -8,7 +8,7 @@ import { useRspcLibraryContext, useZodForm } from '@sd/client'; -import { Dialog, InputField, UseDialogProps, useDialog, z } from '@sd/ui'; +import { Dialog, InputField, useDialog, UseDialogProps, z } from '@sd/ui'; import { ColorPicker } from '~/components'; import { useLocale } from '~/hooks'; diff --git a/interface/app/$libraryId/settings/node/libraries/JoinDialog.tsx b/interface/app/$libraryId/settings/node/libraries/JoinDialog.tsx index b32fd7109c6b..205cbd6df78e 100644 --- a/interface/app/$libraryId/settings/node/libraries/JoinDialog.tsx +++ b/interface/app/$libraryId/settings/node/libraries/JoinDialog.tsx @@ -1,3 +1,5 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { useNavigate } from 'react-router'; import { LibraryConfigWrapped, useBridgeMutation, @@ -8,8 +10,6 @@ import { useZodForm } from '@sd/client'; import { Button, Dialog, Select, SelectOption, toast, useDialog, UseDialogProps, z } from '@sd/ui'; -import { useQueryClient } from '@tanstack/react-query'; -import { useNavigate } from 'react-router'; import { useLocale } from '~/hooks'; import { usePlatform } from '~/util/Platform'; @@ -19,7 +19,6 @@ const schema = z.object({ }) }); - export default (props: UseDialogProps & { librariesCtx: LibraryConfigWrapped[] | undefined }) => { const cloudLibraries = useBridgeQuery(['cloud.library.list']); const joinLibrary = useBridgeMutation(['cloud.library.join']); @@ -82,9 +81,7 @@ export default (props: UseDialogProps & { librariesCtx: LibraryConfigWrapped[] | }); }} > - - {t('select_library')} - + {t('select_library')} {cloudLibraries.data .filter( (cloudLibrary) => diff --git a/interface/app/$libraryId/settings/resources/index.tsx b/interface/app/$libraryId/settings/resources/index.tsx index 0703c8ba56d2..b4d2e27628fc 100644 --- a/interface/app/$libraryId/settings/resources/index.tsx +++ b/interface/app/$libraryId/settings/resources/index.tsx @@ -2,6 +2,6 @@ import { RouteObject } from 'react-router'; export default [ { path: 'about', lazy: () => import('./about') }, - { path: 'changelog', lazy: () => import('./changelog') }, + { path: 'changelog', lazy: () => import('./changelog') } // { path: 'dependencies', lazy: () => import('./dependencies') }, ] satisfies RouteObject[]; diff --git a/interface/app/$libraryId/tag/$id.tsx b/interface/app/$libraryId/tag/$id.tsx index b6273f04b57e..e8e324d2d307 100644 --- a/interface/app/$libraryId/tag/$id.tsx +++ b/interface/app/$libraryId/tag/$id.tsx @@ -1,5 +1,5 @@ -import { ObjectOrder, objectOrderingKeysSchema, Tag, useLibraryQuery } from '@sd/client'; import { useCallback, useMemo } from 'react'; +import { ObjectOrder, objectOrderingKeysSchema, Tag, useLibraryQuery } from '@sd/client'; import { LocationIdParamsSchema } from '~/app/route-schemas'; import { Icon } from '~/components'; import { useLocale, useRouteTitle, useZodRouteParams } from '~/hooks'; diff --git a/interface/hooks/useRedirectToNewLocation.ts b/interface/hooks/useRedirectToNewLocation.ts index d34ec3096175..4310753bd3f8 100644 --- a/interface/hooks/useRedirectToNewLocation.ts +++ b/interface/hooks/useRedirectToNewLocation.ts @@ -1,6 +1,6 @@ -import { useLibraryQuery, useSelector } from '@sd/client'; import { useEffect } from 'react'; import { useNavigate } from 'react-router'; +import { useLibraryQuery, useSelector } from '@sd/client'; import { explorerStore } from '~/app/$libraryId/Explorer/store'; import { LibraryIdParamsSchema } from '../app/route-schemas'; diff --git a/interface/util/Platform.tsx b/interface/util/Platform.tsx index e9f1b0a8e740..d90cbcdf6041 100644 --- a/interface/util/Platform.tsx +++ b/interface/util/Platform.tsx @@ -1,5 +1,5 @@ import { createContext, useContext, type PropsWithChildren } from 'react'; -import { ThumbKey, auth } from '@sd/client'; +import { auth, ThumbKey } from '@sd/client'; export type OperatingSystem = 'browser' | 'linux' | 'macOS' | 'windows' | 'unknown'; diff --git a/package.json b/package.json index 6622fbdaf5ba..24f5d60b30f6 100644 --- a/package.json +++ b/package.json @@ -70,5 +70,5 @@ "eslintConfig": { "root": true }, - "packageManager": "pnpm@9.1.1" + "packageManager": "pnpm@9.4.0" } diff --git a/packages/client/src/lib/humanizeSize.ts b/packages/client/src/lib/humanizeSize.ts index 532208248065..ec2f35c6e882 100644 --- a/packages/client/src/lib/humanizeSize.ts +++ b/packages/client/src/lib/humanizeSize.ts @@ -141,7 +141,7 @@ export const humanizeSize = ( //TODO: Improve this // Convert to thousands when short is TB to show correct progress value //i.e 2.5 TB = 2500 - if (unit.short === "TB" && !no_thousands) { + if (unit.short === 'TB' && !no_thousands) { value = value * 1000; } diff --git a/packages/ui/src/Loader.tsx b/packages/ui/src/Loader.tsx index 0e07d85c86b3..4abe8dd46b92 100644 --- a/packages/ui/src/Loader.tsx +++ b/packages/ui/src/Loader.tsx @@ -1,7 +1,7 @@ import clsx from 'clsx'; import { Puff } from 'react-loading-icons'; -export function Loader(props: { className?: string, color?: string}) { +export function Loader(props: { className?: string; color?: string }) { return ( &2 exit 1 fi @@ -69,7 +74,7 @@ if [ "${1:-}" != "only-frontend" ]; then fi # Add all fixes for changes made in this branch -git diff --cached --name-only "$ancestor" | xargs git add +git diff --diff-filter=d --cached --name-only "${ancestor:?Ancestor is not set}" | xargs git add # Restore unrelated changes git restore .