job indicator + styling

This commit is contained in:
Jamie Pine 2022-10-24 01:17:10 -07:00
parent 3a3ec6251f
commit 2d8590c616
20 changed files with 133 additions and 44 deletions

View file

@ -65,7 +65,7 @@
"title": "Spacedrive",
"width": 1400,
"height": 725,
"minWidth": 700,
"minWidth": 768,
"minHeight": 500,
"resizable": true,
"fullscreen": false,

View file

@ -20,6 +20,9 @@ pub(crate) fn mount() -> RouterBuilder {
.library_query("getRunning", |t| {
t(|ctx, _: (), _| async move { Ok(ctx.jobs.get_running().await) })
})
.library_query("isRunning", |t| {
t(|ctx, _: (), _| async move { Ok(ctx.jobs.get_running().await.len() > 0) })
})
.library_query("getHistory", |t| {
t(|_, _: (), library| async move { Ok(JobManager::get_history(&library).await?) })
})

View file

@ -112,6 +112,7 @@ impl Worker {
}
drop(worker);
invalidate_query!(ctx, "jobs.isRunning");
// spawn task to handle receiving events from the worker
let library_ctx = ctx.clone();
tokio::spawn(Worker::track_progress(
@ -234,6 +235,7 @@ impl Worker {
error!("failed to update job report: {:#?}", e);
}
invalidate_query!(library, "jobs.isRunning");
invalidate_query!(library, "jobs.getRunning");
invalidate_query!(library, "jobs.getHistory");

View file

@ -18,7 +18,7 @@
"@rspc/react": "^0.0.0-main-7c0a67c1",
"@sd/config": "workspace:*",
"@tanstack/react-query": "^4.12.0",
"valtio": "^1.7.0",
"valtio": "^1.7.4",
"valtio-persist": "^1.0.2"
},
"devDependencies": {

View file

@ -7,6 +7,7 @@ export type Procedures = {
{ key: "getNode", input: never, result: NodeState } |
{ key: "jobs.getHistory", input: LibraryArgs<null>, result: Array<JobReport> } |
{ key: "jobs.getRunning", input: LibraryArgs<null>, result: Array<JobReport> } |
{ key: "jobs.isRunning", input: LibraryArgs<null>, result: boolean } |
{ key: "library.getStatistics", input: LibraryArgs<null>, result: Statistics } |
{ key: "library.list", input: never, result: Array<LibraryConfigWrapped> } |
{ key: "locations.getById", input: LibraryArgs<number>, result: Location | null } |

View file

@ -0,0 +1,32 @@
import { proxy, useSnapshot } from 'valtio';
import proxyWithPersist, { PersistStrategy, ProxyPersistStorageEngine } from 'valtio-persist';
const storage: ProxyPersistStorageEngine = {
getItem: (name) => window.localStorage.getItem(name),
setItem: (name, value) => window.localStorage.setItem(name, value),
removeItem: (name) => window.localStorage.removeItem(name),
getAllKeys: () => Object.keys(window.localStorage)
};
const appThemeStore = proxyWithPersist({
// must be unique, files/paths will be created with this prefix
name: 'appTheme',
version: 0,
initialState: {
themeName: 'vanilla',
themeMode: 'light' as 'light' | 'dark',
syncThemeWithSystem: false,
hueValue: null as number | null
},
persistStrategies: PersistStrategy.SingleFile,
migrations: {},
getStorage: () => storage
});
export function useThemeStore() {
return useSnapshot(appThemeStore);
}
export function getThemeStore() {
return appThemeStore;
}

View file

@ -37,6 +37,8 @@
"byte-size": "^8.1.0",
"clsx": "^1.2.1",
"dayjs": "^1.11.5",
"iconoir": "^5.3.2",
"iconoir-react": "^5.3.2",
"phosphor-react": "^1.4.1",
"react": "^18.2.0",
"react-colorful": "^5.6.1",
@ -51,7 +53,7 @@
"tailwindcss": "^3.1.8",
"use-count-up": "^3.0.1",
"use-debounce": "^8.0.4",
"valtio": "^1.7.0"
"valtio": "^1.7.4"
},
"devDependencies": {
"@sd/config": "workspace:*",

View file

@ -105,8 +105,8 @@ export default function ExplorerContextMenu(props: PropsWithChildren) {
<CM.Item label="PNG" />
<CM.Item label="WebP" />
</CM.SubMenu>
<CM.Item label="Rescan Directory" icon={Package} keybind="⌘B" />
<CM.Item label="Regen Thumbnails" icon={Package} keybind="⌘B" />
<CM.Item label="Rescan Directory" icon={Package} />
<CM.Item label="Regen Thumbnails" icon={Package} />
<CM.Item variant="danger" label="Secure delete" icon={TrashSimple} />
</CM.SubMenu>

View file

@ -57,7 +57,7 @@ function elapsed(seconds: number) {
return new Date(seconds * 1000).toUTCString().match(/(\d\d:\d\d:\d\d)/)?.[0];
}
const HeaderContainer = tw.div`z-20 flex items-center w-full h-10 px-2 border-b border-app-line rounded-t-md bg-app-selected`;
const HeaderContainer = tw.div`z-20 flex items-center w-full h-10 px-2 border-b border-app-line/50 rounded-t-md `;
export function JobsManager() {
const runningJobs = useLibraryQuery(['jobs.getRunning']);
@ -71,6 +71,9 @@ export function JobsManager() {
<Button size="icon">
<EllipsisHorizontalIcon className="w-5" />
</Button>
<Button size="icon">
<EllipsisHorizontalIcon className="w-5" />
</Button>
</HeaderContainer>
<div className="h-full mr-1 overflow-x-hidden custom-scroll inspector-scroll">
<div className="">
@ -108,7 +111,7 @@ function Job({ job }: { job: JobReport }) {
<ProgressBar value={job.completed_task_count} total={job.task_count} />
</div>
)}
<div className="flex items-center text-ink-dull">
<div className="flex items-center text-ink-faint">
<span className="text-xs">
{isRunning ? 'Elapsed' : job.status === 'Failed' ? 'Failed after' : 'Took'}{' '}
{job.seconds_elapsed
@ -122,7 +125,7 @@ function Job({ job }: { job: JobReport }) {
</span>
}
</div>
<span className="mt-0.5 opacity-50 text-tiny text-ink-faint">{job.id}</span>
{/* <span className="mt-0.5 opacity-50 text-tiny text-ink-faint">{job.id}</span> */}
</div>
<div className="flex-grow" />
<div className="flex flex-row space-x-2 ml-7">

View file

@ -8,7 +8,16 @@ import {
useLibraryQuery,
usePlatform
} from '@sd/client';
import { Button, ButtonLink, CategoryHeading, Dropdown, OverlayPanel, cva, tw } from '@sd/ui';
import {
Button,
ButtonLink,
CategoryHeading,
Dropdown,
Loader,
OverlayPanel,
cva,
tw
} from '@sd/ui';
import clsx from 'clsx';
import { CheckCircle, CirclesFour, Planet, ShareNetwork } from 'phosphor-react';
import React, { PropsWithChildren } from 'react';
@ -23,8 +32,10 @@ import { MacTrafficLights } from '../os/TrafficLights';
export function Sidebar() {
const os = useOperatingSystem();
const { library, libraries, isLoading: isLoadingLibraries, switchLibrary } = useCurrentLibrary();
// const itemStyles = macOnly(os, 'dark:hover:bg-sidebar-box dark:hover:bg-opacity-50');
const { data: isRunningJob } = useLibraryQuery(['jobs.isRunning']);
// const itemStyles = macOnly(os, 'dark:hover:bg-sidebar-box dark:hover:bg-opacity-50');
return (
<div
className={clsx(
@ -37,7 +48,7 @@ export function Sidebar() {
<div className="flex flex-col px-2.5 flex-grow pt-1 pb-10 overflow-x-hidden overflow-y-scroll no-scrollbar mask-fade-out">
<Dropdown.Root
// usually this panel is styled with bg-menu, but as the dark theme sidebar is dark, we need to override it for dark:
itemsClassName="dark:bg-sidebar-box"
// itemsClassName="dark:bg-sidebar-box"
button={
<Dropdown.Button
variant="gray"
@ -100,7 +111,7 @@ export function Sidebar() {
{/* <div className="fixed w-[174px] bottom-[2px] left-[2px] h-20 rounded-[8px] bg-gradient-to-t from-sidebar-box/90 via-sidebar-box/50 to-transparent" /> */}
<div className="flex flex-col mb-3 px-2.5">
<div className="flex flex-row">
<div className="flex">
<ButtonLink to="/settings/general" size="icon" variant="outline">
<CogIcon className="w-5 h-5" />
</ButtonLink>
@ -114,7 +125,11 @@ export function Sidebar() {
variant="outline"
className="radix-state-open:bg-sidebar-selected/50"
>
<CheckCircle className="w-5 h-5" />
{isRunningJob ? (
<Loader className="w-[20px] h-[20px]" />
) : (
<CheckCircle className="w-5 h-5" />
)}
</Button>
}
>

View file

@ -15,7 +15,7 @@ import { SettingsHeading, SettingsIcon } from './SettingsHeader';
export const SettingsSidebar = () => {
return (
<div className="h-full border-r max-w-[180px] flex-shrink-0 border-app-line/50 w-60">
<div className="h-full border-r max-w-[180px] flex-shrink-0 border-app-line/50 w-60 custom-scroll no-scrollbar pb-5">
<div data-tauri-drag-region className="w-full h-7" />
<div className="px-4 py-2.5">
<SettingsHeading className="!mt-0">Client</SettingsHeading>

View file

@ -1,5 +1,6 @@
import { PlusIcon } from '@heroicons/react/24/solid';
import {
getExplorerStore,
onLibraryChange,
queryClient,
useCurrentLibrary,

View file

@ -35,14 +35,14 @@ export default function GeneralSettings() {
<NodeSettingLabel>Node Name</NodeSettingLabel>
<Input value={node?.name} />
</div>
<div className="flex flex-col ">
<div className="flex flex-col">
<NodeSettingLabel>Node Port</NodeSettingLabel>
<Input contentEditable={false} value={node?.p2p_port || 5795} />
</div>
</div>
<div className="flex items-center mt-5 space-x-3">
<Switch size="sm" checked />
<span className="text-sm text-ink-dull">Run daemon when app closed</span>
<span className="text-sm font-medium text-ink-dull">Run daemon when app closed</span>
</div>
<div className="mt-3">
<div
@ -51,9 +51,9 @@ export default function GeneralSettings() {
platform.openLink(node.data_path);
}
}}
className="text-xs font-medium leading-relaxed text-ink-faint"
className="text-sm font-medium text-ink-faint"
>
<b className="inline mr-2 truncate ">
<b className="inline mr-2 truncate">
<Database className="inline w-4 h-4 mr-1 -mt-[2px]" /> Data Folder
</b>
<span className="select-text">{node?.data_path}</span>

View file

@ -1,4 +1,4 @@
import { useBridgeMutation } from '@sd/client';
import { onLibraryChange, useBridgeMutation } from '@sd/client';
import { useCurrentLibrary } from '@sd/client';
import { Button, Input, Switch } from '@sd/ui';
import { useEffect, useState } from 'react';
@ -21,15 +21,20 @@ export default function LibraryGeneralSettings() {
});
}, 500);
const { register, watch } = useForm({
defaultValues: {
name: library?.config.name,
description: library?.config.description
}
const { register, watch, reset, getValues } = useForm({
defaultValues: { id: library?.uuid, ...library?.config }
});
watch(debounced); // Listen for form changes
// This forces the debounce to run when the component is unmounted
// ensure the form is updated when the library changes
useEffect(() => {
if (library?.uuid !== getValues('id')) {
reset({ id: library?.uuid, ...library?.config });
}
}, [library, getValues, reset]);
watch(debounced); // listen for form changes
// force the debounce to run when the component is unmounted
useEffect(() => () => debounced.flush(), [debounced]);
return (

View file

@ -41,7 +41,7 @@ body {
width: 8px;
}
&::-webkit-scrollbar-track {
@apply bg-[#00000006] dark:bg-[#00000000] mt-[53px] rounded-[6px];
@apply bg-[#00000000] mt-[53px] rounded-[6px];
}
&::-webkit-scrollbar-thumb {
@apply rounded-[6px] bg-app-box;

View file

@ -12,9 +12,8 @@ const MENU_CLASSES = `
flex flex-col
min-w-[8rem] px-1 py-0.5
text-left text-sm text-menu-ink
bg-menu border-menu-border
border border-transparent
shadow-md shadow-menu-shade/20
bg-menu cool-shadow
border border-menu-line
select-none cursor-default rounded-md
`;
@ -48,7 +47,7 @@ export const SubMenu = ({
}: RadixCM.MenuSubContentProps & ItemProps) => {
return (
<RadixCM.Sub>
<RadixCM.SubTrigger className="[&[data-state='open']_div]:bg-primary focus:outline-none py-0.5">
<RadixCM.SubTrigger className="[&[data-state='open']_div]:bg-accent [&[data-state='open']_div]:text-white focus:outline-none text-menu-ink py-[3px]">
<DivItem rightArrow {...{ label, icon }} />
</RadixCM.SubTrigger>
<RadixCM.Portal>
@ -63,14 +62,14 @@ export const SubMenu = ({
const itemStyles = cva(
[
'flex flex-row items-center justify-start flex-1',
'px-2 py-1 space-x-2',
'px-2 py-[3px] space-x-2',
'cursor-default rounded',
'focus:outline-none'
],
{
variants: {
variant: {
default: 'hover:bg-accent focus:bg-accent',
default: 'hover:bg-accent focus:bg-accent hover:text-white',
danger: [
'text-red-600 dark:text-red-400',
'hover:text-white focus:text-white',
@ -102,7 +101,7 @@ export const Item = ({
}: ItemProps & RadixCM.MenuItemProps) => (
<RadixCM.Item
{...props}
className="!cursor-default select-none group focus:outline-none py-0.5 active:opacity-80"
className="!cursor-default select-none group text-menu-ink focus:outline-none py-0.5 active:opacity-80"
>
<div className={itemStyles({ variant })}>
{children ? children : <ItemInternals {...{ icon, label, rightArrow, keybind }} />}

View file

@ -8,7 +8,7 @@ export function Loader(props: { className?: string }) {
strokeOpacity={4}
strokeWidth={5}
speed={1}
className={clsx('ml-0.5 mt-[2px] -mr-1 w-7 h-7', props.className)}
className={clsx('w-7 h-7', props.className)}
/>
);
}

View file

@ -1,5 +1,5 @@
:root {
--dark-hue: 235; //300, 295
--dark-hue: 230; //300, 295
--light-hue: 235;
// global
@ -35,8 +35,8 @@
--color-app-shade: var(--dark-hue), 15%, 0%;
--color-app-frame: var(--dark-hue), 15%, 25%;
// menu
--color-menu: var(--dark-hue), 16%, 7%;
--color-menu-line: var(--dark-hue), 5%, 18%;
--color-menu: var(--dark-hue), 15%, 7%;
--color-menu-line: var(--dark-hue), 15%, 7%;
--color-menu-ink: var(--dark-hue), 5%, 100%;
--color-menu-faint: var(--dark-hue), 5%, 80%;
--color-menu-hover: var(--dark-hue), 15%, 30%;
@ -56,16 +56,15 @@
// text
--color-ink: var(--light-hue), 5%, 20%;
--color-ink-dull: var(--light-hue), 5%, 30%;
--color-ink-faint: var(--light-hue), 5%, 60%;
--color-ink-faint: var(--light-hue), 5%, 40%;
// sidebar
--color-sidebar: var(--light-hue), 5%, 97%;
--color-sidebar: var(--light-hue), 5%, 96%;
--color-sidebar-box: var(--light-hue), 5%, 100%;
--color-sidebar-line: var(--light-hue), 10%, 85%;
--color-sidebar-divider: var(--light-hue), 15%, 93%;
--color-sidebar-button: var(--light-hue), 15%, 100%;
--color-sidebar-selected: var(--light-hue), 10%, 80%;
--color-sidebar-shade: var(--light-hue), 15%, 100%;
// main
--color-app: var(--light-hue), 5%, 100%;
--color-app-box: var(--light-hue), 5%, 98%;
@ -79,12 +78,19 @@
--color-app-hover: var(--light-hue), 5%, 100%;
--color-app-shade: var(--light-hue), 15%, 50%;
--color-app-frame: 0, 0%, 100%;
// menu
--color-menu: var(--light-hue), 16%, 14%;
--color-menu: var(--light-hue), 16%, 0%;
--color-menu-line: var(--light-hue), 5%, 18%;
--color-menu-ink: var(--light-hue), 5%, 100%;
--color-menu-faint: var(--light-hue), 5%, 80%;
--color-menu-hover: var(--light-hue), 15%, 80%;
--color-menu-selected: var(--light-hue), 5%, 30%;
--color-menu-shade: var(--light-hue), 5%, 0%;
--color-menu: var(--light-hue), 16%, 99%;
--color-menu-line: var(--light-hue), 5%, 90%;
--color-menu-ink: var(--light-hue), 5%, 30%;
--color-menu-faint: var(--light-hue), 5%, 80%;
--color-menu-hover: var(--light-hue), 15%, 20%;
--color-menu-selected: var(--light-hue), 5%, 30%;
--color-menu-shade: var(--light-hue), 5%, 0%;

View file

@ -30,3 +30,7 @@
// -webkit-mask-image: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1));
mask-image: linear-gradient(to top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100px);
}
.cool-shadow {
box-shadow: rgb(0 0 0 / 9%) 0px 3px 12px;
}

View file

@ -446,6 +446,8 @@ importers:
byte-size: ^8.1.0
clsx: ^1.2.1
dayjs: ^1.11.5
iconoir: ^5.3.2
iconoir-react: ^5.3.2
phosphor-react: ^1.4.1
prettier: ^2.7.1
react: ^18.2.0
@ -462,7 +464,7 @@ importers:
typescript: ^4.8.4
use-count-up: ^3.0.1
use-debounce: ^8.0.4
valtio: ^1.7.0
valtio: ^1.7.4
vite: ^3.1.4
vite-plugin-svgr: ^2.2.1
dependencies:
@ -488,6 +490,8 @@ importers:
byte-size: 8.1.0
clsx: 1.2.1
dayjs: 1.11.5
iconoir: 5.3.2
iconoir-react: 5.3.2_react@18.2.0
phosphor-react: 1.4.1_react@18.2.0
react: 18.2.0
react-colorful: 5.6.1_biqbaboplfbrettd7655fr4n2y
@ -13697,6 +13701,18 @@ packages:
engines: {node: '>=10.17.0'}
dev: true
/iconoir-react/5.3.2_react@18.2.0:
resolution: {integrity: sha512-5p5FQAkX6ZAuk1UE/4RmjI9BgI5FSqTbgyUT/m74bfHxl57YAuU7sRdnvrLe97I9IbbiC2uKnGl90xVcBD07rQ==}
peerDependencies:
react: ^16.8.6 || ^17 || ^18
dependencies:
react: 18.2.0
dev: false
/iconoir/5.3.2:
resolution: {integrity: sha512-om9zHIRaoRs4XUXq11+/RB7lucY8eiz2nX1+DV0VjefMDAlncKctygz6Z/11lQDkISuyaBcbYDpJQvFcaEGvdg==}
dev: false
/iconv-lite/0.4.23:
resolution: {integrity: sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==}
engines: {node: '>=0.10.0'}