mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-18 08:29:12 +00:00
job indicator + styling
This commit is contained in:
parent
3a3ec6251f
commit
2d8590c616
|
@ -65,7 +65,7 @@
|
|||
"title": "Spacedrive",
|
||||
"width": 1400,
|
||||
"height": 725,
|
||||
"minWidth": 700,
|
||||
"minWidth": 768,
|
||||
"minHeight": 500,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
|
|
|
@ -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?) })
|
||||
})
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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 } |
|
||||
|
|
32
packages/client/src/stores/themeStore.ts
Normal file
32
packages/client/src/stores/themeStore.ts
Normal 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;
|
||||
}
|
|
@ -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:*",
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
{isRunningJob ? (
|
||||
<Loader className="w-[20px] h-[20px]" />
|
||||
) : (
|
||||
<CheckCircle className="w-5 h-5" />
|
||||
)}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { PlusIcon } from '@heroicons/react/24/solid';
|
||||
import {
|
||||
getExplorerStore,
|
||||
onLibraryChange,
|
||||
queryClient,
|
||||
useCurrentLibrary,
|
||||
|
|
|
@ -42,7 +42,7 @@ export default function GeneralSettings() {
|
|||
</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,7 +51,7 @@ 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">
|
||||
<Database className="inline w-4 h-4 mr-1 -mt-[2px]" /> Data Folder
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }} />}
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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%;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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'}
|
||||
|
|
Loading…
Reference in a new issue