working location creation + thumbnail gen

This commit is contained in:
Jamie Pine committed 2022-04-12 19:38:50 -07:00
1 parent 232acd3eff
commit 65b9dcf71c
15 files changed
+176 -70

No files matched your search

+1
View File
@@ -34,6 +34,7 @@
"@radix-ui/react-dialog": "^0.1.7",
"@radix-ui/react-dropdown-menu": "^0.1.6",
"@radix-ui/react-icons": "^1.1.0",
"@radix-ui/react-progress": "^0.1.4",
"@radix-ui/react-slider": "^0.1.4",
"@sd/client": "workspace:*",
"@sd/core": "workspace:*",
+3 -3
View File
@@ -135,10 +135,10 @@ function NotFound() {
role="alert"
className="flex flex-col items-center justify-center w-full h-full p-4 rounded-lg dark:text-white"
>
<p className="m-3 text-sm font-bold text-gray-400">Error: 404</p>
<h1 className="text-2xl font-bold">Not found bozo.</h1>
<p className="m-3 mt-20 text-sm font-semibold text-gray-500 uppercase">Error: 404</p>
<h1 className="text-4xl font-bold">You chose nothingness.</h1>
<div className="flex flex-row space-x-2">
<Button variant="primary" className="mt-2" onClick={() => navigate(-1)}>
<Button variant="primary" className="mt-4" onClick={() => navigate(-1)}>
Go Back
</Button>
</div>
+20 -4
View File
@@ -14,11 +14,20 @@ import { useSearchParams } from 'react-router-dom';
type ExplorerState = {
selectedRowIndex: number;
setSelectedRowIndex: (index: number) => void;
locationId: number;
setLocationId: (index: number) => void;
newThumbnails: Record<string, boolean>;
addNewThumbnail: (cas_id: string) => void;
};
export const useExplorerState = create<ExplorerState>((set) => ({
selectedRowIndex: 1,
setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index }))
setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })),
locationId: -1,
setLocationId: (id: number) => set((state) => ({ ...state, locationId: id })),
newThumbnails: {},
addNewThumbnail: (cas_id: string) =>
set((state) => ({ ...state, newThumbnails: { ...state.newThumbnails, [cas_id]: true } }))
}));
interface IColumn {
@@ -36,7 +45,7 @@ function ensureIsColumns<T extends IColumn[]>(data: T) {
const columns = ensureIsColumns([
{ column: 'Name', key: 'name', width: 280 } as const,
{ column: 'Size', key: 'size_in_bytes', width: 120 } as const,
// { column: 'Size', key: 'size_in_bytes', width: 120 } as const,
{ column: 'Type', key: 'extension', width: 100 } as const
]);
@@ -51,7 +60,7 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb
const path = props.path;
const { selectedRowIndex, setSelectedRowIndex } = useExplorerState();
const { selectedRowIndex, setSelectedRowIndex, setLocationId } = useExplorerState();
const [goingUp, setGoingUp] = useState(false);
const { data: currentDir } = useBridgeQuery('LibGetExplorerDir', {
@@ -71,6 +80,10 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb
}
}, [selectedRowIndex]);
useEffect(() => {
setLocationId(props.location_id);
}, [props.location_id]);
useKey('ArrowUp', (e) => {
e.preventDefault();
setGoingUp(true);
@@ -197,6 +210,9 @@ const RenderCell: React.FC<{ colKey?: ColumnKey; dirId?: number; file?: FilePath
refetchOnWindowFocus: false
});
const location = useContext(LocationContext);
const { newThumbnails } = useExplorerState();
const hasThumbnail = !!row?.has_local_thumbnail || !!newThumbnails[row?.temp_cas_id ?? ''];
switch (colKey) {
case 'name':
@@ -206,7 +222,7 @@ const RenderCell: React.FC<{ colKey?: ColumnKey; dirId?: number; file?: FilePath
{row.is_dir ? (
<img className="mt-0.5 pointer-events-none z-90" src="/svg/folder.svg" />
) : (
row.has_local_thumbnail &&
hasThumbnail &&
client?.data_path && (
<img
className="mt-0.5 pointer-events-none z-90"
+18 -4
View File
@@ -9,8 +9,10 @@ import { TrafficLights } from '../os/TrafficLights';
import { Button } from '../primitive';
import { Dropdown } from '../primitive/Dropdown';
import { DefaultProps } from '../primitive/types';
import { useBridgeQuery } from '@sd/client';
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
import { platform } from '@tauri-apps/api/os';
import { dialog } from '@tauri-apps/api';
import RunningJobsWidget from '../jobs/RunningJobsWidget';
interface SidebarProps extends DefaultProps {}
@@ -54,6 +56,7 @@ export function MacOSTrafficLights() {
export const Sidebar: React.FC<SidebarProps> = (props) => {
const [isMacos, setIsMacos] = useState(false);
const { data: locations } = useBridgeQuery('SysGetLocations');
const { mutate: createLocation } = useBridgeCommand('LocCreate');
useEffect(() => {
platform().then((platform) => {
@@ -72,7 +75,7 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
];
return (
<div className="flex flex-col flex-shrink-0 min-h-full px-3 pb-1 overflow-x-hidden overflow-y-scroll border-r border-gray-100 w-46 bg-gray-50 dark:bg-gray-850 dark:border-gray-600">
<div className="flex flex-col w-56 min-h-full px-3 pb-1 overflow-x-hidden overflow-y-scroll border-r border-gray-100 bg-gray-50 dark:bg-gray-850 dark:border-gray-600">
{isMacos ? (
<>
<MacOSTrafficLights /> <div className="mt-6" />
@@ -157,9 +160,17 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
</div>
);
})}
<div className="w-full px-2 py-1.5 mt-1 text-xs font-bold text-center text-gray-400 dark:text-gray-500 border border-dashed rounded border-transparent cursor-normal border-gray-350 dark:border-gray-550">
<button
onClick={() => {
dialog.open({ directory: true }).then((result) => {
createLocation({ path: result });
});
}}
className="w-full px-2 py-1.5 mt-1 text-xs font-bold text-center text-gray-400 dark:text-gray-500 border border-dashed rounded border-transparent cursor-normal border-gray-350 dark:border-gray-550 hover:dark:border-gray-500 transition"
>
Add Location
</div>
</button>
</div>
<div>
<Heading>Tags</Heading>
@@ -176,6 +187,9 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
</div>
</div>
<div className="flex-grow" />
<RunningJobsWidget />
{/* <div className="flex w-full">
</div> */}
<div className="mb-2">
<NavLink to="/settings/general">
{({ isActive }) => (
@@ -0,0 +1,30 @@
import React from 'react';
import { useBridgeQuery } from '@sd/client';
import ProgressBar from '../primitive/ProgressBar';
import { Transition } from '@headlessui/react';
export default function RunningJobsWidget() {
const { data: jobs } = useBridgeQuery('JobGetRunning');
return (
<div className="flex flex-col space-y-4">
{jobs?.map((job) => (
<Transition
show={true}
enter="transition-translate ease-in-out duration-200"
enterFrom="translate-y-24"
enterTo="translate-y-0"
leave="transition-translate ease-in-out duration-200"
leaveFrom="translate-y-0"
leaveTo="translate-y-24"
>
<div key={job.id} className="flex flex-col px-2 pt-1.5 pb-2 bg-gray-700 rounded">
{/* <span className="mb-0.5 text-tiny font-bold text-gray-400">{job.status} Job</span> */}
<span className="mb-1.5 truncate text-gray-450 text-tiny">{job.message}</span>
<ProgressBar value={job.completed_task_count} total={job.task_count} />
</div>
</Transition>
))}
</div>
);
}
+21 -4
View File
@@ -1,12 +1,22 @@
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline';
import clsx from 'clsx';
import { ArrowsLeftRight, Cloud, FolderPlus, Key, Tag, TerminalWindow } from 'phosphor-react';
import {
ArrowsClockwise,
ArrowsLeftRight,
Cloud,
FolderPlus,
Key,
Tag,
TerminalWindow
} from 'phosphor-react';
import React from 'react';
import { ButtonProps } from '../primitive';
import { Shortcut } from '../primitive/Shortcut';
import { DefaultProps } from '../primitive/types';
import { invoke } from '@tauri-apps/api';
import { useLocation, useNavigate } from 'react-router-dom';
import { useBridgeCommand } from '@sd/client';
import { useExplorerState } from '../file/FileList';
export interface TopBarProps extends DefaultProps {}
export interface TopBarButtonProps extends ButtonProps {
@@ -38,7 +48,12 @@ const TopBarButton: React.FC<TopBarButtonProps> = ({ icon: Icon, ...props }) =>
};
export const TopBar: React.FC<TopBarProps> = (props) => {
let location = useLocation();
const { locationId } = useExplorerState();
const { mutate: generateThumbsForLocation } = useBridgeCommand('GenerateThumbsForLocation', {
onMutate: (data) => {
console.log('GenerateThumbsForLocation', data);
}
});
let navigate = useNavigate();
return (
<>
@@ -75,8 +90,10 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
<TopBarButton icon={Key} />
<TopBarButton icon={Cloud} />
<TopBarButton
icon={ArrowsLeftRight}
onClick={() => invoke('start_watcher', { path: '/Users/jamie' })}
icon={ArrowsClockwise}
onClick={() => {
generateThumbsForLocation({ id: locationId, path: '' });
}}
/>
</div>
</div>
@@ -0,0 +1,24 @@
import * as ProgressPrimitive from '@radix-ui/react-progress';
import React, { useEffect } from 'react';
interface Props {
value: number;
total: number;
}
const ProgressBar = (props: Props) => {
const percentage = Math.round((props.value / props.total) * 100);
return (
<ProgressPrimitive.Root
value={percentage}
className="w-full h-1 overflow-hidden bg-gray-200 rounded-full dark:bg-gray-500"
>
<ProgressPrimitive.Indicator
style={{ width: `${percentage}%` }}
className="h-full duration-300 ease-in-out bg-primary "
/>
</ProgressPrimitive.Root>
);
};
export default ProgressBar;
+5
View File
@@ -3,12 +3,17 @@ import { emit, listen, Event } from '@tauri-apps/api/event';
// import { useExplorerStore } from '../store/explorer';
import { CoreEvent } from '../../../../core';
import { useQuery, useQueryClient } from 'react-query';
import { useExplorerState } from '../components/file/FileList';
export function useCoreEvents() {
const client = useQueryClient();
const { addNewThumbnail } = useExplorerState();
useEffect(() => {
listen('core_event', (e: Event<CoreEvent>) => {
switch (e.payload?.key) {
case 'NewThumbnail':
addNewThumbnail(e.payload.data.cas_id);
break;
case 'InvalidateQuery':
case 'InvalidateQueryDebounced':
let query = [e.payload.data.key];
@@ -11,7 +11,7 @@ import { useBridgeCommand, useBridgeQuery } from '@sd/client';
export default function GeneralSettings() {
const { data: volumes } = useBridgeQuery('SysGetVolumes');
const [tempWatchDir, setTempWatchDir] = useState('/users/jamie/Projects');
const [tempWatchDir, setTempWatchDir] = useState('/users/jamie/Desktop/Pictures');
const [fakeSliderVal, setFakeSliderVal] = useState([30, 0]);
@@ -33,24 +33,12 @@ export default function GeneralSettings() {
return (
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
<div className="mt-3 mb-6">
<div className="mt-3 mb-3">
<h1 className="text-2xl font-bold">General Settings</h1>
<p className="mt-1 text-sm text-gray-400">Basic settings related to this client</p>
{/* <hr className="mt-4 border-gray-550" /> */}
</div>
<div className="flex flex-row pb-4 space-x-2">
<Button
className="w-40"
variant="gray"
size="sm"
onClick={() =>
generateThumbsForLocation({
id: 1
})
}
>
Generate Thumbnails
</Button>
<Button className="w-40" variant="gray" size="sm" onClick={() => {}}>
Open data folder
</Button>
@@ -58,7 +46,7 @@ export default function GeneralSettings() {
Purge database
</Button>
</div>
<InputContainer
{/* <InputContainer
title="Test scan directory"
description="This will create a job to scan the directory you specify to the database."
>
@@ -81,38 +69,8 @@ export default function GeneralSettings() {
Scan Now
</Button>
</div>
</InputContainer>
</InputContainer> */}
<ReactJson
// collapsed
enableClipboard={false}
displayDataTypes={false}
theme="ocean"
src={{ runningJobs: jobs }}
style={{
padding: 20,
borderRadius: 5,
backgroundColor: '#101016',
border: 1,
borderColor: '#1E1E27',
borderStyle: 'solid'
}}
/>
<ReactJson
// collapsed
enableClipboard={false}
displayDataTypes={false}
theme="ocean"
src={{ jobsHistory: [...(jobsHistory || [])].reverse() }}
style={{
padding: 20,
borderRadius: 5,
backgroundColor: '#101016',
border: 1,
borderColor: '#1E1E27',
borderStyle: 'solid'
}}
/>
<InputContainer
title="Locations"
description="Local cache storage for media previews and thumbnails."
@@ -146,13 +104,12 @@ export default function GeneralSettings() {
/>
</InputContainer>
<InputContainer
title="Media cache directory"
title="A long input"
description="Local cache storage for media previews and thumbnails."
>
<div className="flex flex-row">
<Input
className="flex-grow"
value={'uuuuuu'}
placeholder="/users/jamie/Library/Application Support/spacedrive/cache"
/>
</div>
+4 -4
View File
@@ -140,9 +140,9 @@ pub async fn scan_path(
on_progress(vec![
ScanProgress::SavedChunks(i as usize),
ScanProgress::Message(format!(
"Writing {} files to db at chunk {}",
chunk.len(),
i
"Writing {} of {} to db",
i * chunk.len(),
paths.len(),
)),
]);
@@ -220,7 +220,7 @@ fn prepare_values(
location.id,
materialized_path,
name,
extension,
extension.to_lowercase(),
parent_id
.clone()
.map(|id| format!("\"{}\"", &id))
+10 -1
View File
@@ -1,13 +1,14 @@
use crate::job::jobs::JobReportUpdate;
use crate::prisma::FilePathData;
use crate::state::client;
use crate::sys;
use crate::{
job::{jobs::Job, worker::WorkerContext},
prisma::FilePath,
CoreContext,
};
use crate::{sys, CoreEvent};
use anyhow::Result;
use futures::executor::block_on;
use image::*;
use prisma_client_rust::or;
use std::fs;
@@ -47,6 +48,8 @@ impl Job for ThumbnailJob {
println!("Found {:?} files", image_files.len());
let is_background = self.background.clone();
tokio::task::spawn_blocking(move || {
ctx.progress(vec![
JobReportUpdate::TaskCount(image_files.len()),
@@ -76,6 +79,12 @@ impl Job for ThumbnailJob {
}
ctx.progress(vec![JobReportUpdate::CompletedTaskCount(i + 1)]);
if !is_background {
block_on(ctx.core_ctx.emit(CoreEvent::NewThumbnail {
cas_id: checksum.to_string(),
}));
};
}
})
.await?;
+2 -1
View File
@@ -231,7 +231,7 @@ impl Core {
}
async fn exec_command(&mut self, cmd: ClientCommand) -> Result<CoreResponse, CoreError> {
info!("Core command: {:?}", cmd);
println!("Core command: {:?}", cmd);
let ctx = self.get_context();
Ok(match cmd {
// CRUD for locations
@@ -359,6 +359,7 @@ pub enum CoreEvent {
InvalidateQuery(ClientQuery),
InvalidateQueryDebounced(ClientQuery),
InvalidateResource(CoreResource),
NewThumbnail { cas_id: String },
Log { message: String },
DatabaseDisconnected { reason: Option<String> },
}
+6 -1
View File
@@ -3,7 +3,7 @@ use crate::{
prisma::{FilePath, Location},
state::client,
sys::{volumes, volumes::Volume},
CoreContext,
ClientQuery, CoreContext, CoreEvent,
};
use anyhow::Result;
use log::info;
@@ -176,6 +176,11 @@ pub async fn create_location(ctx: &CoreContext, path: &str) -> Result<LocationRe
Ok(_) => (),
Err(e) => Err(LocationError::DotfileWriteFailure(e, path.to_string()))?,
}
ctx
.emit(CoreEvent::InvalidateQuery(ClientQuery::SysGetLocations))
.await;
location
}
};
+14
View File
@@ -7,6 +7,20 @@ module.exports = {
darkMode: 'media',
mode: 'jit',
theme: {
fontSize: {
'tiny': '.65rem',
'xs': '.75rem',
'sm': '.84rem',
'base': '1rem',
'lg': '1.125rem',
'xl': '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
'5xl': '3rem',
'6xl': '4rem',
'7xl': '5rem'
},
extend: {
boxShadow: {
box: '0px 4px 9px rgba(0, 0, 0, 0.05)',
+13
View File
@@ -18,6 +18,7 @@ importers:
'@radix-ui/react-dialog': ^0.1.7
'@radix-ui/react-dropdown-menu': ^0.1.6
'@radix-ui/react-icons': ^1.1.0
'@radix-ui/react-progress': ^0.1.4
'@radix-ui/react-slider': ^0.1.4
'@sd/client': workspace:*
'@sd/core': workspace:*
@@ -74,6 +75,7 @@ importers:
'@radix-ui/react-dialog': 0.1.7_761282c47ea27a9f40e1344337910648
'@radix-ui/react-dropdown-menu': 0.1.6_761282c47ea27a9f40e1344337910648
'@radix-ui/react-icons': 1.1.0_react@18.0.0
'@radix-ui/react-progress': 0.1.4_react@18.0.0
'@radix-ui/react-slider': 0.1.4_react@18.0.0
'@sd/client': link:../../packages/client
'@sd/core': link:../../core
@@ -826,6 +828,17 @@ packages:
react: 18.0.0
dev: false
/@radix-ui/react-progress/0.1.4_react@18.0.0:
resolution: {integrity: sha512-Q+H7r/S0HMlwVriksgyUgBtol5TgkNllQPVtaXDDaeJBNFK2uiToHBp3j1Gi88/4Fw+EKT+SyWpeM/5nVtGujw==}
peerDependencies:
react: ^16.8 || ^17.0
dependencies:
'@babel/runtime': 7.17.9
'@radix-ui/react-context': 0.1.1_react@18.0.0
'@radix-ui/react-primitive': 0.1.4_react@18.0.0
react: 18.0.0
dev: false
/@radix-ui/react-roving-focus/0.1.5_react@18.0.0:
resolution: {integrity: sha512-ClwKPS5JZE+PaHCoW7eu1onvE61pDv4kO8W4t5Ra3qMFQiTJLZMdpBQUhksN//DaVygoLirz4Samdr5Y1x1FSA==}
peerDependencies: