mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-02-20 07:37:26 -05:00
[ENG-631] TopBar improvements & misc fixes (#837)
* fix things * added back/forward buttons to settings * split top bar context into left and right * hook up path * fix background jobs hidden from job manager * core * fix type + quick preview transition * fix selected item color contrast * fix close button on quick preview * clean up job ui for light theme * Improve media view overscan --------- Co-authored-by: Brendan Allan <brendonovich@outlook.com>
This commit is contained in:
@@ -167,9 +167,7 @@ impl JobManager {
|
||||
|
||||
for worker in self.running_workers.read().await.values() {
|
||||
let report = worker.lock().await.report();
|
||||
if !report.is_background {
|
||||
ret.push(report);
|
||||
}
|
||||
ret.push(report);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
@@ -428,25 +428,26 @@ pub async fn light_scan_location(
|
||||
}
|
||||
|
||||
let location_base_data = location::Data::from(&location);
|
||||
|
||||
// removed grouping for background jobs, they don't need to be grouped as only running ones are shown to the user
|
||||
library
|
||||
.spawn_job(
|
||||
Job::new_with_action(
|
||||
ShallowIndexerJobInit {
|
||||
location,
|
||||
sub_path: sub_path.clone(),
|
||||
},
|
||||
"light_scan_location",
|
||||
)
|
||||
.queue_next(ShallowFileIdentifierJobInit {
|
||||
location: location_base_data.clone(),
|
||||
sub_path: sub_path.clone(),
|
||||
})
|
||||
.queue_next(ShallowThumbnailerJobInit {
|
||||
location: location_base_data,
|
||||
sub_path,
|
||||
}),
|
||||
)
|
||||
.spawn_job(ShallowIndexerJobInit {
|
||||
location,
|
||||
sub_path: sub_path.clone(),
|
||||
})
|
||||
.await
|
||||
.unwrap_or(());
|
||||
library
|
||||
.spawn_job(ShallowFileIdentifierJobInit {
|
||||
location: location_base_data.clone(),
|
||||
sub_path: sub_path.clone(),
|
||||
})
|
||||
.await
|
||||
.unwrap_or(());
|
||||
library
|
||||
.spawn_job(ShallowThumbnailerJobInit {
|
||||
location: location_base_data.clone(),
|
||||
sub_path: sub_path.clone(),
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const GridViewItem = memo(({ data, selected, index, ...props }: GridViewItemProp
|
||||
className={clsx(
|
||||
'mb-1 flex items-center justify-center justify-items-center rounded-lg border-2 border-transparent text-center active:translate-y-[1px]',
|
||||
{
|
||||
'bg-app-selected/20': selected
|
||||
'bg-app-selectedItem': selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -32,8 +32,8 @@ const MediaViewItem = memo(({ data, index }: MediaViewItemProps) => {
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'group relative flex aspect-square items-center justify-center hover:bg-app-selected/20',
|
||||
selected && 'bg-app-selected/20'
|
||||
'hover:bg-app-selectedItem group relative flex aspect-square items-center justify-center',
|
||||
selected && 'bg-app-selectedItem'
|
||||
)}
|
||||
>
|
||||
<FileThumb
|
||||
@@ -82,7 +82,7 @@ export default () => {
|
||||
measureElement: () => itemSize,
|
||||
paddingStart: gridPadding,
|
||||
paddingEnd: gridPadding,
|
||||
overscan: !dismissibleNoticeStore.mediaView ? 2 : 1
|
||||
overscan: !dismissibleNoticeStore.mediaView ? 8 : 4
|
||||
});
|
||||
|
||||
const columnVirtualizer = useVirtualizer({
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { animated, useTransition } from '@react-spring/web';
|
||||
import { XCircle } from 'phosphor-react';
|
||||
import { X, XCircle } from 'phosphor-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { subscribeKey } from 'valtio/utils';
|
||||
import { ExplorerItem } from '@sd/client';
|
||||
import { getExplorerStore } from '~/hooks';
|
||||
import FileThumb from './File/Thumb';
|
||||
import { Button } from '@sd/ui';
|
||||
|
||||
const AnimatedDialogOverlay = animated(Dialog.Overlay);
|
||||
const AnimatedDialogContent = animated(Dialog.Content);
|
||||
@@ -42,12 +43,12 @@ export function QuickPreview({ transformOrigin }: QuickPreviewProps) {
|
||||
const transitions = useTransition(isOpen, {
|
||||
from: {
|
||||
opacity: 0,
|
||||
transform: `translateY(20px)`,
|
||||
transformOrigin: transformOrigin || 'bottom'
|
||||
transform: `translateY(20px) scale(0.9)`,
|
||||
transformOrigin: transformOrigin || 'center top'
|
||||
},
|
||||
enter: { opacity: 1, transform: `translateY(0px)` },
|
||||
leave: { opacity: 0, transform: `translateY(20px)` },
|
||||
config: { mass: 0.4, tension: 200, friction: 10, bounce: 0 }
|
||||
enter: { opacity: 1, transform: `translateY(0px) scale(1)` },
|
||||
leave: { opacity: 0, transform: `translateY(40px) scale(0.9)` },
|
||||
config: { mass: 0.2, tension: 300, friction: 20, bounce: 0 }
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -80,10 +81,13 @@ export function QuickPreview({ transformOrigin }: QuickPreviewProps) {
|
||||
<div className="!pointer-events-auto flex h-5/6 max-h-screen w-11/12 flex-col rounded-md border border-app-line bg-app-box text-ink shadow-app-shade">
|
||||
<nav className="flex w-full flex-row">
|
||||
<Dialog.Close
|
||||
className="ml-2 text-ink-dull"
|
||||
className="m-2"
|
||||
aria-label="Close"
|
||||
>
|
||||
<XCircle size={16} />
|
||||
<Button size="icon" variant="outline" className='flex flex-row'>
|
||||
<X weight='bold' className=' h-3 w-3 text-ink-faint' />
|
||||
<span className='ml-1 text-tiny font-medium text-ink-faint'>ESC</span>
|
||||
</Button>
|
||||
</Dialog.Close>
|
||||
<Dialog.Title className="mx-auto my-1 font-bold">
|
||||
Preview -{' '}
|
||||
|
||||
@@ -84,6 +84,7 @@ interface Props {
|
||||
hasNextPage?: boolean;
|
||||
isFetchingNextPage?: boolean;
|
||||
viewClassName?: string;
|
||||
scrollRef?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export default memo((props: Props) => {
|
||||
@@ -97,7 +98,7 @@ export default memo((props: Props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={scrollRef}
|
||||
ref={props.scrollRef || scrollRef}
|
||||
className={clsx(
|
||||
'custom-scroll explorer-scroll h-screen',
|
||||
layoutMode === 'grid' && 'overflow-x-hidden',
|
||||
@@ -110,7 +111,7 @@ export default memo((props: Props) => {
|
||||
<ViewContext.Provider
|
||||
value={{
|
||||
data: props.data,
|
||||
scrollRef: scrollRef,
|
||||
scrollRef: props.scrollRef || scrollRef,
|
||||
onLoadMore: props.onLoadMore,
|
||||
hasNextPage: props.hasNextPage,
|
||||
isFetchingNextPage: props.isFetchingNextPage
|
||||
|
||||
@@ -20,6 +20,7 @@ interface Props {
|
||||
children?: ReactNode;
|
||||
inspectorClassName?: string;
|
||||
explorerClassName?: string;
|
||||
scrollRef?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export default function Explorer(props: Props) {
|
||||
@@ -63,6 +64,7 @@ export default function Explorer(props: Props) {
|
||||
<ExplorerContextMenu>
|
||||
{props.items && (
|
||||
<View
|
||||
scrollRef={props.scrollRef}
|
||||
data={props.items}
|
||||
onLoadMore={props.onLoadMore}
|
||||
hasNextPage={props.hasNextPage}
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
:root {
|
||||
--border-color: rgba(50, 51, 67, 1);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--border-color: rgba(50, 51, 67, 0.192); // Use lighter color for light mode
|
||||
}
|
||||
}
|
||||
|
||||
ul.groupedjob {
|
||||
position: relative;
|
||||
&::before {
|
||||
@@ -6,7 +16,7 @@ ul.groupedjob {
|
||||
top: 37px;
|
||||
height: 48.75px;
|
||||
width: 11px;
|
||||
border-left: 1px solid rgba(50, 51, 67, 1);
|
||||
border-left: 1px solid var(--border-color);
|
||||
content: '';
|
||||
left: 25px;
|
||||
}
|
||||
@@ -26,7 +36,7 @@ ul.groupedjob {
|
||||
height: 37.5px;
|
||||
top: 0;
|
||||
width: 10px;
|
||||
border-left: 1px solid rgba(50, 51, 67, 1);
|
||||
border-left: 1px solid var(--border-color);
|
||||
content: '';
|
||||
left: 25px;
|
||||
}
|
||||
@@ -36,10 +46,10 @@ ul.groupedjob {
|
||||
top: 23.5px;
|
||||
height: 1em;
|
||||
width: 10px;
|
||||
border-bottom: 1px solid rgba(50, 51, 67, 1);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
content: '';
|
||||
display: inline-block;
|
||||
left: 10px;
|
||||
left: -14px;
|
||||
}
|
||||
&::after {
|
||||
position: absolute;
|
||||
@@ -47,7 +57,7 @@ ul.groupedjob {
|
||||
height: 100%;
|
||||
top: 0;
|
||||
width: 10px;
|
||||
border-left: 1px solid rgba(50, 51, 67, 1);
|
||||
border-left: 1px solid var(--border-color);
|
||||
content: '';
|
||||
left: 25px;
|
||||
}
|
||||
|
||||
@@ -32,28 +32,31 @@ const getNiceData = (
|
||||
name: isGroup
|
||||
? 'Indexing paths'
|
||||
: job.metadata?.location_path
|
||||
? `Indexed paths at ${job.metadata?.location_path} `
|
||||
: `Processing added location...`,
|
||||
? `Indexed paths at ${job.metadata?.location_path} `
|
||||
: `Processing added location...`,
|
||||
icon: Folder,
|
||||
filesDiscovered: `${numberWithCommas(
|
||||
job.metadata?.total_paths || 0
|
||||
)} ${JobCountTextCondition(job, 'path')}`
|
||||
},
|
||||
thumbnailer: {
|
||||
name: `${
|
||||
job.status === 'Running' || job.status === 'Queued'
|
||||
? 'Generating thumbnails'
|
||||
: 'Generated thumbnails'
|
||||
}`,
|
||||
name: `${job.status === 'Running' || job.status === 'Queued'
|
||||
? 'Generating thumbnails'
|
||||
: 'Generated thumbnails'
|
||||
}`,
|
||||
icon: Camera,
|
||||
filesDiscovered: `${numberWithCommas(job.task_count)} ${JobCountTextCondition(job, 'item')}`
|
||||
},
|
||||
shallow_thumbnailer: {
|
||||
name: `Generating thumbnails for current directory`,
|
||||
icon: Camera,
|
||||
filesDiscovered: `${numberWithCommas(job.task_count)} ${JobCountTextCondition(job, 'item')}`
|
||||
},
|
||||
file_identifier: {
|
||||
name: `${
|
||||
job.status === 'Running' || job.status === 'Queued'
|
||||
? 'Extracting metadata'
|
||||
: 'Extracted metadata'
|
||||
}`,
|
||||
name: `${job.status === 'Running' || job.status === 'Queued'
|
||||
? 'Extracting metadata'
|
||||
: 'Extracted metadata'
|
||||
}`,
|
||||
icon: Eye,
|
||||
filesDiscovered:
|
||||
job.message ||
|
||||
@@ -134,7 +137,7 @@ function Job({ job, clearJob, className, isGroup }: JobProps) {
|
||||
isGroup ? `joblistitem pr-3 pt-0` : 'p-3'
|
||||
)}
|
||||
>
|
||||
<div className="ml-7 flex">
|
||||
<div className="flex">
|
||||
<div>
|
||||
<niceData.icon
|
||||
className={clsx(
|
||||
|
||||
@@ -72,10 +72,8 @@ function JobGroup({ data, clearJob }: JobGroupProps) {
|
||||
<div className="truncate">
|
||||
<p className="truncate font-semibold">
|
||||
{allJobsCompleted
|
||||
? `Added location "${
|
||||
data.metadata.init.location.name || ''
|
||||
}"`
|
||||
: 'Processing added location...'}
|
||||
? `Added location "${data.metadata.init.location.name || ''}"`
|
||||
: `Indexing "${data.metadata.init.location.name || ''}"`}
|
||||
</p>
|
||||
<p className="my-[2px] text-ink-faint">
|
||||
<b>{tasks.total} </b>
|
||||
|
||||
@@ -81,15 +81,15 @@ export function JobsManager() {
|
||||
</PopoverClose>
|
||||
</div>
|
||||
<div className="no-scrollbar h-full overflow-x-hidden">
|
||||
{runningIndividualJobs?.map((job) => (
|
||||
<Job key={job.id} job={job} />
|
||||
))}
|
||||
{groupedJobs.map((data) => (
|
||||
<JobGroup key={data.id} data={data} clearJob={clearJobHandler} />
|
||||
))}
|
||||
{orphanJobs?.map((job) => (
|
||||
<Job key={job?.id} job={job} />
|
||||
))}
|
||||
{runningIndividualJobs?.map((job) => (
|
||||
<Job key={job.id} job={job} />
|
||||
))}
|
||||
{individualJobs?.map((job) => (
|
||||
<Job clearJob={clearJobHandler} key={job.id} job={job} />
|
||||
))}
|
||||
|
||||
@@ -26,7 +26,7 @@ export default () => {
|
||||
}
|
||||
// we override the sidebar dropdown item's hover styles
|
||||
// because the dark style clashes with the sidebar
|
||||
className="data-[side=bottom]:slide-in-from-top-2 mt-1 shadow-none dark:divide-menu-selected/30 dark:border-sidebar-line dark:bg-sidebar-box"
|
||||
className="mt-1 shadow-none data-[side=bottom]:slide-in-from-top-2 dark:divide-menu-selected/30 dark:border-sidebar-line dark:bg-sidebar-box"
|
||||
alignToTrigger
|
||||
>
|
||||
{libraries.data?.map((lib) => (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import { MacTrafficLights, NavigationButtons } from '~/components';
|
||||
import { MacTrafficLights } from '~/components';
|
||||
import { useOperatingSystem } from '~/hooks';
|
||||
import Contents from './Contents';
|
||||
import Footer from './Footer';
|
||||
@@ -18,11 +18,7 @@ export default () => {
|
||||
)}
|
||||
>
|
||||
{showControls && <MacTrafficLights className="absolute left-[13px] top-[13px] z-50" />}
|
||||
{(os !== 'browser' || showControls) && (
|
||||
<div className="mt-[-4px] flex justify-end">
|
||||
<NavigationButtons />
|
||||
</div>
|
||||
)}
|
||||
{os === 'macOS' && <div data-tauri-drag-region className="h-5 w-full" />}
|
||||
<LibrariesDropdown />
|
||||
<Contents />
|
||||
<Footer />
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
import { RefObject, createContext, useRef } from 'react';
|
||||
import { RefObject, createContext, useContext, useRef } from 'react';
|
||||
import { Outlet } from 'react-router';
|
||||
import TopBar from '.';
|
||||
|
||||
interface TopBarContext {
|
||||
topBarChildrenRef: RefObject<HTMLDivElement> | null;
|
||||
left: RefObject<HTMLDivElement>;
|
||||
right: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export const TopBarContext = createContext<TopBarContext>({
|
||||
topBarChildrenRef: null
|
||||
});
|
||||
const TopBarContext = createContext<TopBarContext | null>(null);
|
||||
|
||||
export const Component = () => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const left = useRef<HTMLDivElement>(null);
|
||||
const right = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<TopBarContext.Provider value={{ topBarChildrenRef: ref }}>
|
||||
<TopBar ref={ref} />
|
||||
<TopBarContext.Provider value={{ left, right }}>
|
||||
<TopBar leftRef={left} rightRef={right} />
|
||||
<Outlet />
|
||||
</TopBarContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useTopBarContext() {
|
||||
const ctx = useContext(TopBarContext);
|
||||
|
||||
if (!ctx) throw new Error('TopBarContext not found!');
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Tooltip } from '@sd/ui';
|
||||
import { ArrowLeft, ArrowRight } from 'phosphor-react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { Button, Tooltip } from '@sd/ui';
|
||||
import { useSearchStore } from '~/hooks';
|
||||
import TopBarButton from './TopBarButton';
|
||||
|
||||
export const NavigationButtons = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -9,26 +10,26 @@ export const NavigationButtons = () => {
|
||||
const idx = history.state.idx as number;
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
<div data-tauri-drag-region className="flex">
|
||||
<Tooltip label="Navigate back">
|
||||
<Button
|
||||
size="icon"
|
||||
className="text-[14px] text-ink-dull"
|
||||
<TopBarButton
|
||||
rounding='left'
|
||||
// className="text-[14px] text-ink-dull"
|
||||
onClick={() => navigate(-1)}
|
||||
disabled={isFocused || idx === 0}
|
||||
>
|
||||
<ArrowLeft weight="bold" />
|
||||
</Button>
|
||||
<ArrowLeft size={14} className='m-[4px]' weight="bold" />
|
||||
</TopBarButton>
|
||||
</Tooltip>
|
||||
<Tooltip label="Navigate forward">
|
||||
<Button
|
||||
size="icon"
|
||||
className="text-[14px] text-ink-dull"
|
||||
<TopBarButton
|
||||
rounding='right'
|
||||
// className="text-[14px] text-ink-dull"
|
||||
onClick={() => navigate(1)}
|
||||
disabled={isFocused || idx === history.length - 1}
|
||||
>
|
||||
<ArrowRight weight="bold" />
|
||||
</Button>
|
||||
<ArrowRight size={14} className='m-[4px]' weight="bold" />
|
||||
</TopBarButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
18
interface/app/$libraryId/TopBar/Portal.tsx
Normal file
18
interface/app/$libraryId/TopBar/Portal.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTopBarContext } from './Layout';
|
||||
|
||||
interface Props {
|
||||
left?: ReactNode;
|
||||
right?: ReactNode;
|
||||
}
|
||||
export const TopBarPortal = ({ left, right }: Props) => {
|
||||
const ctx = useTopBarContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
{left && ctx.left.current && createPortal(left, ctx.left.current)}
|
||||
{right && ctx.right.current && createPortal(right, ctx.right.current)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DotsThreeCircle } from 'phosphor-react';
|
||||
import React, { HTMLAttributes, forwardRef } from 'react';
|
||||
import { Popover } from '@sd/ui';
|
||||
import { TOP_BAR_ICON_STYLE, ToolOption } from '.';
|
||||
import TopBarButton, { TopBarButtonProps } from './TopBarButton';
|
||||
import { TOP_BAR_ICON_STYLE, ToolOption } from './TopBarOptions';
|
||||
|
||||
const GroupTool = forwardRef<
|
||||
HTMLButtonElement,
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
import clsx from 'clsx';
|
||||
import { useContext, useLayoutEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { Popover, Tooltip } from '@sd/ui';
|
||||
import { ToolOption } from '.';
|
||||
import { TopBarContext } from './Layout';
|
||||
import TopBarButton from './TopBarButton';
|
||||
import TopBarMobile from './TopBarMobile';
|
||||
|
||||
interface TopBarChildrenProps {
|
||||
toolOptions?: ToolOption[][];
|
||||
export interface ToolOption {
|
||||
icon: JSX.Element;
|
||||
onClick?: () => void;
|
||||
individual?: boolean;
|
||||
toolTipLabel: string;
|
||||
topBarActive?: boolean;
|
||||
popOverComponent?: JSX.Element;
|
||||
showAtResolution: ShowAtResolution;
|
||||
}
|
||||
|
||||
export default ({ toolOptions }: TopBarChildrenProps) => {
|
||||
const ctx = useContext(TopBarContext);
|
||||
const target = ctx.topBarChildrenRef?.current;
|
||||
export type ShowAtResolution = 'sm:flex' | 'md:flex' | 'lg:flex' | 'xl:flex' | '2xl:flex';
|
||||
interface TopBarChildrenProps {
|
||||
options?: ToolOption[][];
|
||||
}
|
||||
|
||||
export const TOP_BAR_ICON_STYLE = 'm-0.5 w-[18px] h-[18px] text-ink-dull';
|
||||
|
||||
export default ({ options }: TopBarChildrenProps) => {
|
||||
const [windowSize, setWindowSize] = useState(0);
|
||||
const toolsNotSmFlex = toolOptions
|
||||
const toolsNotSmFlex = options
|
||||
?.flatMap((group) => group)
|
||||
.filter((t) => t.showAtResolution !== 'sm:flex');
|
||||
|
||||
@@ -28,14 +36,10 @@ export default ({ toolOptions }: TopBarChildrenProps) => {
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
if (!target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
return (
|
||||
<div data-tauri-drag-region className="flex w-full flex-row justify-end">
|
||||
<div data-tauri-drag-region className={`flex gap-0`}>
|
||||
{toolOptions?.map((group, groupIndex) => {
|
||||
{options?.map((group, groupIndex) => {
|
||||
return group.map(
|
||||
(
|
||||
{
|
||||
@@ -49,14 +53,14 @@ export default ({ toolOptions }: TopBarChildrenProps) => {
|
||||
},
|
||||
index
|
||||
) => {
|
||||
const groupCount = toolOptions.length;
|
||||
const groupCount = options.length;
|
||||
const roundingCondition = individual
|
||||
? 'both'
|
||||
: index === 0
|
||||
? 'left'
|
||||
: index === group.length - 1
|
||||
? 'right'
|
||||
: 'none';
|
||||
? 'left'
|
||||
: index === group.length - 1
|
||||
? 'right'
|
||||
: 'none';
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
@@ -109,12 +113,11 @@ export default ({ toolOptions }: TopBarChildrenProps) => {
|
||||
})}
|
||||
</div>
|
||||
<TopBarMobile
|
||||
toolOptions={toolOptions}
|
||||
className={`${
|
||||
toolOptions={options}
|
||||
className={
|
||||
windowSize <= 1279 && (toolsNotSmFlex?.length as number) > 0 ? 'flex' : 'hidden'
|
||||
}`}
|
||||
}
|
||||
/>
|
||||
</div>,
|
||||
target
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,37 +1,33 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { RefObject } from 'react';
|
||||
import { NavigationButtons } from './NavigationButtons';
|
||||
import SearchBar from './SearchBar';
|
||||
|
||||
export interface ToolOption {
|
||||
icon: JSX.Element;
|
||||
onClick?: () => void;
|
||||
individual?: boolean;
|
||||
toolTipLabel: string;
|
||||
topBarActive?: boolean;
|
||||
popOverComponent?: JSX.Element;
|
||||
showAtResolution: ShowAtResolution;
|
||||
}
|
||||
|
||||
export type ShowAtResolution = 'sm:flex' | 'md:flex' | 'lg:flex' | 'xl:flex' | '2xl:flex';
|
||||
|
||||
export const TOP_BAR_ICON_STYLE = 'm-0.5 w-5 h-5 text-ink-dull';
|
||||
export const TOP_BAR_HEIGHT = 46;
|
||||
|
||||
const TopBar = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
interface Props {
|
||||
leftRef?: RefObject<HTMLDivElement>;
|
||||
rightRef?: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const TopBar = (props: Props) => {
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="
|
||||
duration-250 top-bar-blur absolute left-0 top-0 z-50 flex
|
||||
h-[46px] w-full flex-row items-center justify-center overflow-hidden
|
||||
border-b border-sidebar-divider bg-app/90 px-5
|
||||
border-b border-sidebar-divider bg-app/90 px-3.5
|
||||
transition-[background-color,border-color] ease-out
|
||||
"
|
||||
>
|
||||
<div className="flex-1" />
|
||||
<div data-tauri-drag-region className="flex flex-1 flex-row items-center">
|
||||
<NavigationButtons />
|
||||
<div ref={props.leftRef} />
|
||||
</div>
|
||||
<SearchBar />
|
||||
<div className="flex-1" ref={ref} />
|
||||
<div className="flex-1" ref={props.rightRef} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default TopBar;
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useKey } from 'rooks';
|
||||
import { z } from 'zod';
|
||||
import { useLibraryContext, useLibraryMutation, useRspcLibraryContext } from '@sd/client';
|
||||
import { dialogManager } from '@sd/ui';
|
||||
import { useLibraryContext, useLibraryMutation, useLibraryQuery, useRspcLibraryContext } from '@sd/client';
|
||||
import { Folder, dialogManager } from '@sd/ui';
|
||||
import {
|
||||
getExplorerStore,
|
||||
useExplorerStore,
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
import Explorer from '../Explorer';
|
||||
import DeleteDialog from '../Explorer/File/DeleteDialog';
|
||||
import { useExplorerOrder, useExplorerSearchParams } from '../Explorer/util';
|
||||
import TopBarChildren from '../TopBar/TopBarChildren';
|
||||
import { TopBarPortal } from '../TopBar/Portal';
|
||||
import TopBarOptions from '../TopBar/TopBarOptions';
|
||||
|
||||
const PARAMS = z.object({
|
||||
id: z.coerce.number()
|
||||
@@ -25,6 +26,8 @@ export const Component = () => {
|
||||
const { explorerViewOptions, explorerControlOptions, explorerToolOptions } =
|
||||
useExplorerTopBarOptions();
|
||||
|
||||
const { data: location } = useLibraryQuery(['locations.get', location_id]);
|
||||
|
||||
// we destructure this since `mutate` is a stable reference but the object it's in is not
|
||||
const { mutate: quickRescan } = useLibraryMutation('locations.quickRescan');
|
||||
|
||||
@@ -55,8 +58,20 @@ export const Component = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBarChildren
|
||||
toolOptions={[explorerViewOptions, explorerToolOptions, explorerControlOptions]}
|
||||
<TopBarPortal
|
||||
left={
|
||||
<>
|
||||
<Folder size={22} className="ml-3 mr-2 -mt-[1px] inline-block" />
|
||||
<span className="text-sm font-medium">
|
||||
{path ? getLastSectionOfPath(path) : location?.name}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
right={
|
||||
<TopBarOptions
|
||||
options={[explorerViewOptions, explorerToolOptions, explorerControlOptions]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<div className="relative flex w-full flex-col">
|
||||
<Explorer
|
||||
@@ -109,3 +124,13 @@ const useItems = () => {
|
||||
|
||||
return { query, items };
|
||||
};
|
||||
|
||||
|
||||
function getLastSectionOfPath(path: string): string | undefined {
|
||||
if (path.endsWith('/')) {
|
||||
path = path.slice(0, -1);
|
||||
}
|
||||
const sections = path.split('/');
|
||||
const lastSection = sections[sections.length - 1];
|
||||
return lastSection;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default ({ category, icon, items, selected, onClick }: CategoryButtonProp
|
||||
onClick={onClick}
|
||||
className={clsx(
|
||||
'flex shrink-0 items-center rounded-md px-1.5 py-1 text-sm',
|
||||
selected && 'bg-app-selected/20'
|
||||
selected && 'bg-app-selectedItem'
|
||||
)}
|
||||
>
|
||||
<img src={icon} className="mr-3 h-12 w-12" />
|
||||
|
||||
@@ -16,7 +16,8 @@ import { useExplorerStore, useExplorerTopBarOptions, useIsDark } from '~/hooks';
|
||||
import Explorer from '../Explorer';
|
||||
import { SEARCH_PARAMS, useExplorerOrder } from '../Explorer/util';
|
||||
import { usePageLayout } from '../PageLayout';
|
||||
import TopBarChildren from '../TopBar/TopBarChildren';
|
||||
import { TopBarPortal } from '../TopBar/Portal';
|
||||
import TopBarOptions from '../TopBar/TopBarOptions';
|
||||
import CategoryButton from '../overview/CategoryButton';
|
||||
import Statistics from '../overview/Statistics';
|
||||
|
||||
@@ -92,12 +93,12 @@ export const Component = () => {
|
||||
favorite: isFavoritesCategory ? true : undefined,
|
||||
...(explorerStore.layoutMode === 'media'
|
||||
? {
|
||||
kind: [5, 7].includes(kind)
|
||||
? [kind]
|
||||
: isFavoritesCategory
|
||||
kind: [5, 7].includes(kind)
|
||||
? [kind]
|
||||
: isFavoritesCategory
|
||||
? [5, 7]
|
||||
: [5, 7, kind]
|
||||
}
|
||||
}
|
||||
: { kind: isFavoritesCategory ? [] : [kind] })
|
||||
}
|
||||
}
|
||||
@@ -128,19 +129,25 @@ export const Component = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopBarChildren
|
||||
toolOptions={[explorerViewOptions, explorerToolOptions, explorerControlOptions]}
|
||||
<TopBarPortal
|
||||
right={
|
||||
<TopBarOptions
|
||||
options={[explorerViewOptions, explorerToolOptions, explorerControlOptions]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Explorer
|
||||
inspectorClassName="!pt-0 !fixed !top-[50px] !right-[10px] !w-[260px]"
|
||||
inspectorClassName="!pt-0 !fixed !top-[50px] !right-[10px] !w-[260px]"
|
||||
explorerClassName="!overflow-visible" // required to ensure categories are sticky, remove with caution
|
||||
viewClassName="!pl-0 !pt-0 !h-auto"
|
||||
items={items}
|
||||
onLoadMore={query.fetchNextPage}
|
||||
hasNextPage={query.hasNextPage}
|
||||
isFetchingNextPage={query.isFetchingNextPage}
|
||||
scrollRef={page?.ref}
|
||||
>
|
||||
<Statistics />
|
||||
<div className="no-scrollbar sticky top-0 z-50 mt-2 flex space-x-[1px] overflow-x-scroll bg-app/90 px-5 py-1.5 backdrop-blur">
|
||||
<div className="no-scrollbar sticky top-0 z-10 mt-2 flex space-x-[1px] overflow-x-scroll bg-app/90 px-5 py-1.5 backdrop-blur">
|
||||
{categories.data?.map((category) => {
|
||||
const iconString = CategoryToIcon[category.name] || 'Document';
|
||||
return (
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
} from '~/hooks';
|
||||
import Explorer from './Explorer';
|
||||
import { getExplorerItemData } from './Explorer/util';
|
||||
import TopBarChildren from './TopBar/TopBarChildren';
|
||||
import { TopBarPortal } from './TopBar/Portal';
|
||||
import TopBarOptions from './TopBar/TopBarOptions';
|
||||
|
||||
const SEARCH_PARAMS = z.object({
|
||||
search: z.string().optional(),
|
||||
@@ -49,12 +50,16 @@ const ExplorerStuff = memo((props: { args: SearchArgs }) => {
|
||||
<>
|
||||
{items && items.length > 0 ? (
|
||||
<>
|
||||
<TopBarChildren
|
||||
toolOptions={[
|
||||
explorerViewOptions,
|
||||
explorerToolOptions,
|
||||
explorerControlOptions
|
||||
]}
|
||||
<TopBarPortal
|
||||
right={
|
||||
<TopBarOptions
|
||||
options={[
|
||||
explorerViewOptions,
|
||||
explorerToolOptions,
|
||||
explorerControlOptions
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Explorer items={items} />
|
||||
</>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { tw } from '@sd/ui';
|
||||
import { useOperatingSystem } from '~/hooks/useOperatingSystem';
|
||||
import Icon from '../Layout/Sidebar/Icon';
|
||||
import SidebarLink from '../Layout/Sidebar/Link';
|
||||
import { NavigationButtons } from '../TopBar/NavigationButtons';
|
||||
|
||||
const Heading = tw.div`mb-1 ml-1 text-xs font-semibold text-gray-400`;
|
||||
const Section = tw.div`space-y-0.5`;
|
||||
@@ -26,10 +27,13 @@ export default () => {
|
||||
return (
|
||||
<div className="custom-scroll no-scrollbar h-full w-60 max-w-[180px] shrink-0 border-r border-app-line/50 pb-5">
|
||||
{os !== 'browser' ? (
|
||||
<div data-tauri-drag-region className="h-5 w-full" />
|
||||
<div data-tauri-drag-region className="mb-3 h-3 w-full p-3 pl-[14px] pt-[10px]">
|
||||
<NavigationButtons />
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-3" />
|
||||
)}
|
||||
|
||||
<div className="space-y-6 px-4 py-3">
|
||||
<Section>
|
||||
<Heading>Client</Heading>
|
||||
|
||||
@@ -4,7 +4,6 @@ export * from './ColorPicker';
|
||||
export * from './DismissibleNotice';
|
||||
export * from './DragRegion';
|
||||
export * from './ExternalObject';
|
||||
export * from './NavigationButtons';
|
||||
export * from './PasswordMeter';
|
||||
export * from './SubtleButton';
|
||||
export * from './TrafficLights';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { proxy, useSnapshot } from 'valtio';
|
||||
import { ExplorerItem, Ordering } from '@sd/client';
|
||||
import { ExplorerItem, FilePathSearchOrdering } from '@sd/client';
|
||||
import { resetStore } from '@sd/client/src/stores/util';
|
||||
|
||||
type UnionKeys<T> = T extends any ? keyof T : never;
|
||||
@@ -14,7 +14,7 @@ export enum ExplorerKind {
|
||||
|
||||
export type CutCopyType = 'Cut' | 'Copy';
|
||||
|
||||
export type ExplorerOrderByKeys = UnionKeys<Ordering> | 'none';
|
||||
export type ExplorerOrderByKeys = UnionKeys<FilePathSearchOrdering> | 'none';
|
||||
|
||||
export type ExplorerDirection = 'asc' | 'desc';
|
||||
|
||||
@@ -28,8 +28,6 @@ const state = {
|
||||
tagAssignMode: false,
|
||||
showInspector: false,
|
||||
multiSelectIndexes: [] as number[],
|
||||
contextMenuObjectId: null as number | null,
|
||||
contextMenuActiveObject: null as object | null,
|
||||
newThumbnails: {} as Record<string, boolean | undefined>,
|
||||
cutCopyState: {
|
||||
sourcePath: '', // this is used solely for preventing copy/cutting to the same path (as that will truncate the file)
|
||||
@@ -44,7 +42,7 @@ const state = {
|
||||
mediaAspectSquare: true,
|
||||
orderBy: 'dateCreated' as ExplorerOrderByKeys,
|
||||
orderByDirection: 'desc' as ExplorerDirection,
|
||||
groupBy: 'none'
|
||||
groupBy: 'none',
|
||||
};
|
||||
|
||||
// Keep the private and use `useExplorerState` or `getExplorerStore` or you will get production build issues.
|
||||
|
||||
@@ -11,13 +11,16 @@ import {
|
||||
Tag
|
||||
} from 'phosphor-react';
|
||||
import OptionsPanel from '~/app/$libraryId/Explorer/OptionsPanel';
|
||||
import { TOP_BAR_ICON_STYLE, ToolOption } from '~/app/$libraryId/TopBar';
|
||||
import { TOP_BAR_ICON_STYLE, ToolOption } from '~/app/$libraryId/TopBar/TopBarOptions';
|
||||
import { KeyManager } from '../app/$libraryId/KeyManager';
|
||||
import { getExplorerStore, useExplorerStore } from './useExplorerStore';
|
||||
import { useLibraryMutation } from '@sd/client';
|
||||
|
||||
export const useExplorerTopBarOptions = () => {
|
||||
const explorerStore = useExplorerStore();
|
||||
|
||||
const reload = useLibraryMutation('locations.quickRescan');
|
||||
|
||||
const explorerViewOptions: ToolOption[] = [
|
||||
{
|
||||
toolTipLabel: 'Grid view',
|
||||
@@ -94,7 +97,12 @@ export const useExplorerTopBarOptions = () => {
|
||||
showAtResolution: 'xl:flex'
|
||||
},
|
||||
{
|
||||
toolTipLabel: 'Regenerate thumbs (temp)',
|
||||
toolTipLabel: 'Reload',
|
||||
onClick: () => {
|
||||
if (explorerStore.locationId) {
|
||||
reload.mutate({ location_id: explorerStore.locationId, sub_path: '' })
|
||||
}
|
||||
},
|
||||
icon: <ArrowClockwise className={TOP_BAR_ICON_STYLE} />,
|
||||
individual: true,
|
||||
showAtResolution: 'xl:flex'
|
||||
|
||||
@@ -103,6 +103,8 @@ export type NodeConfig = { id: string; name: string; p2p_port: number | null; p2
|
||||
|
||||
export type CategoryItem = { name: string; count: number }
|
||||
|
||||
export type Location = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string }
|
||||
|
||||
/**
|
||||
* This denotes the `StoredKey` version.
|
||||
*/
|
||||
@@ -117,12 +119,12 @@ export type EncryptedKey = number[]
|
||||
|
||||
export type PeerId = string
|
||||
|
||||
export type MediaData = { id: number; pixel_width: number | null; pixel_height: number | null; longitude: number | null; latitude: number | null; fps: number | null; capture_device_make: string | null; capture_device_model: string | null; capture_device_software: string | null; duration_seconds: number | null; codecs: string | null; streams: number | null }
|
||||
|
||||
export type GenerateThumbsForLocationArgs = { id: number; path: string }
|
||||
|
||||
export type LibraryConfigWrapped = { uuid: string; config: LibraryConfig }
|
||||
|
||||
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
|
||||
|
||||
/**
|
||||
* These parameters define the password-hashing level.
|
||||
*
|
||||
@@ -199,19 +201,13 @@ export type ObjectSearchArgs = { take?: number | null; tagId?: number | null; cu
|
||||
|
||||
export type SetNoteArgs = { id: number; note: string | null }
|
||||
|
||||
export type JobReport = { id: string; name: string; action: string | null; data: number[] | null; metadata: any | null; is_background: boolean; errors_text: string[]; created_at: string | null; started_at: string | null; completed_at: string | null; parent_id: string | null; status: JobStatus; task_count: number; completed_task_count: number; message: string }
|
||||
|
||||
export type Statistics = { id: number; date_captured: string; total_object_count: number; library_db_size: string; total_bytes_used: string; total_bytes_capacity: string; total_unique_bytes: string; total_bytes_free: string; preview_media_bytes: string }
|
||||
|
||||
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
|
||||
|
||||
export type FilePathSearchOrdering = { name: boolean } | { sizeInBytes: boolean } | { dateCreated: boolean } | { dateModified: boolean } | { dateIndexed: boolean } | { object: ObjectSearchOrdering }
|
||||
|
||||
export type FileCopierJobInit = { source_location_id: number; source_path_id: number; target_location_id: number; target_path: string; target_file_name_suffix: string | null }
|
||||
|
||||
export type Location = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string }
|
||||
export type Statistics = { id: number; date_captured: string; total_object_count: number; library_db_size: string; total_bytes_used: string; total_bytes_capacity: string; total_unique_bytes: string; total_bytes_free: string; preview_media_bytes: string }
|
||||
|
||||
export type Object = { id: number; pub_id: number[]; kind: number; key_id: number | null; hidden: boolean; favorite: boolean; important: boolean; has_thumbnail: boolean; has_thumbstrip: boolean; has_video_preview: boolean; ipfs_id: string | null; note: string | null; date_created: string; date_accessed: string | null }
|
||||
export type FilePath = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string }
|
||||
|
||||
export type BuildInfo = { version: string; commit: string }
|
||||
|
||||
@@ -224,12 +220,14 @@ export type Algorithm = "XChaCha20Poly1305" | "Aes256Gcm"
|
||||
|
||||
export type ObjectSearchOrdering = { dateAccessed: boolean }
|
||||
|
||||
export type Tag = { id: number; pub_id: number[]; name: string | null; color: string | null; total_objects: number | null; redundancy_goal: number | null; date_created: string; date_modified: string }
|
||||
|
||||
export type OwnedOperationItem = { id: any; data: OwnedOperationData }
|
||||
|
||||
export type MediaData = { id: number; pixel_width: number | null; pixel_height: number | null; longitude: number | null; latitude: number | null; fps: number | null; capture_device_make: string | null; capture_device_model: string | null; capture_device_software: string | null; duration_seconds: number | null; codecs: string | null; streams: number | null }
|
||||
|
||||
export type CRDTOperationType = SharedOperation | RelationOperation | OwnedOperation
|
||||
|
||||
export type IndexerRule = { id: number; kind: number; name: string; default: boolean; parameters: number[]; date_created: string; date_modified: string }
|
||||
|
||||
/**
|
||||
* TODO: P2P event for the frontend
|
||||
*/
|
||||
@@ -239,6 +237,8 @@ export type SpacedropArgs = { peer_id: PeerId; file_path: string[] }
|
||||
|
||||
export type RenameFileArgs = { location_id: number; file_name: string; new_file_name: string }
|
||||
|
||||
export type JobReport = { id: string; name: string; action: string | null; data: number[] | null; metadata: any | null; is_background: boolean; errors_text: string[]; created_at: string | null; started_at: string | null; completed_at: string | null; parent_id: string | null; status: JobStatus; task_count: number; completed_task_count: number; message: string }
|
||||
|
||||
export type OwnedOperation = { model: string; items: OwnedOperationItem[] }
|
||||
|
||||
export type ObjectWithFilePaths = { id: number; pub_id: number[]; kind: number; key_id: number | null; hidden: boolean; favorite: boolean; important: boolean; has_thumbnail: boolean; has_thumbstrip: boolean; has_video_preview: boolean; ipfs_id: string | null; note: string | null; date_created: string; date_accessed: string | null; file_paths: FilePath[] }
|
||||
@@ -289,6 +289,8 @@ export type OwnedOperationData = { Create: { [key: string]: any } } | { CreateMa
|
||||
|
||||
export type SharedOperationData = SharedOperationCreateData | { field: string; value: any } | null
|
||||
|
||||
export type Tag = { id: number; pub_id: number[]; name: string | null; color: string | null; total_objects: number | null; redundancy_goal: number | null; date_created: string; date_modified: string }
|
||||
|
||||
export type SearchData<T> = { cursor: number[] | null; items: T[] }
|
||||
|
||||
export type OptionalRange<T> = { from: T | null; to: T | null }
|
||||
@@ -301,13 +303,13 @@ export type TagAssignArgs = { object_id: number; tag_id: number; unassign: boole
|
||||
|
||||
export type ChangeNodeNameArgs = { name: string }
|
||||
|
||||
export type Object = { id: number; pub_id: number[]; kind: number; key_id: number | null; hidden: boolean; favorite: boolean; important: boolean; has_thumbnail: boolean; has_thumbstrip: boolean; has_video_preview: boolean; ipfs_id: string | null; note: string | null; date_created: string; date_accessed: string | null }
|
||||
|
||||
/**
|
||||
* This defines all available password hashing algorithms.
|
||||
*/
|
||||
export type HashingAlgorithm = { name: "Argon2id"; params: Params } | { name: "BalloonBlake3"; params: Params }
|
||||
|
||||
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" | "CompletedWithErrors"
|
||||
|
||||
export type FilePathWithObject = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string; object: Object | null }
|
||||
|
||||
export type LocationWithIndexerRules = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string; indexer_rules: ({ indexer_rule: IndexerRule })[] }
|
||||
@@ -323,12 +325,10 @@ export type AutomountUpdateArgs = { uuid: string; status: boolean }
|
||||
|
||||
export type Protected<T> = T
|
||||
|
||||
export type FilePath = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string }
|
||||
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" | "CompletedWithErrors"
|
||||
|
||||
export type RestoreBackupArgs = { password: Protected<string>; secret_key: Protected<string>; path: string }
|
||||
|
||||
export type IndexerRule = { id: number; kind: number; name: string; default: boolean; parameters: number[]; date_created: string; date_modified: string }
|
||||
|
||||
export type RelationOperation = { relation_item: string; relation_group: string; relation: string; data: RelationOperationData }
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
--color-app-button: var(--dark-hue), 15%, 23%;
|
||||
--color-app-hover: var(--dark-hue), 15%, 25%;
|
||||
--color-app-selected: var(--dark-hue), 15%, 26%;
|
||||
--color-app-selected-item: var(--dark-hue), 15%, 18%;
|
||||
--color-app-active: var(--dark-hue), 15%, 30%;
|
||||
--color-app-shade: var(--dark-hue), 15%, 0%;
|
||||
--color-app-frame: var(--dark-hue), 15%, 25%;
|
||||
@@ -71,7 +72,7 @@
|
||||
// main
|
||||
--color-app: var(--light-hue), 5%, 100%;
|
||||
--color-app-box: var(--light-hue), 5%, 98%;
|
||||
--color-app-dark-box: var(--light-hue), 5%, 93%;
|
||||
--color-app-dark-box: var(--light-hue), 5%, 97%;
|
||||
--color-app-light-box: var(--light-hue), 5%, 100%;
|
||||
--color-app-overlay: var(--light-hue), 5%, 100%;
|
||||
--color-app-input: var(--light-hue), 5%, 100%;
|
||||
@@ -80,6 +81,7 @@
|
||||
--color-app-button: var(--light-hue), 5%, 100%;
|
||||
--color-app-divider: var(--light-hue), 5%, 80%;
|
||||
--color-app-selected: var(--light-hue), 5%, 93%;
|
||||
--color-app-selected-item: var(--light-hue), 5%, 96%;
|
||||
--color-app-hover: var(--light-hue), 5%, 97%;
|
||||
--color-app-active: var(--light-hue), 5%, 87%;
|
||||
--color-app-shade: var(--light-hue), 15%, 50%;
|
||||
|
||||
@@ -73,6 +73,7 @@ module.exports = function (app, options) {
|
||||
divider: alpha('--color-app-divider'),
|
||||
button: alpha('--color-app-button'),
|
||||
selected: alpha('--color-app-selected'),
|
||||
selectedItem: alpha('--color-app-selected-item'),
|
||||
hover: alpha('--color-app-hover'),
|
||||
active: alpha('--color-app-active'),
|
||||
shade: alpha('--color-app-shade'),
|
||||
|
||||
Reference in New Issue
Block a user