From 600adc8fa94754414f04b068fe92c8a899582f09 Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 19 Oct 2021 07:31:56 -0700 Subject: [PATCH] Rust+Swift bridge Co-authored-by: Brendonovich --- .TODO | 19 +++++- package.json | 1 + src-tauri/Cargo.lock | 3 + src-tauri/Cargo.toml | 5 ++ src-tauri/src/build.rs | 87 +++++++++++++++++++++++++++- src-tauri/src/filesystem/init.rs | 2 +- src-tauri/src/main.rs | 71 +++++++++++++++++++++-- src-tauri/src/swift/Package.swift | 28 +++++++++ src-tauri/swift/.gitignore | 7 +++ src-tauri/swift/Package.swift | 30 ++++++++++ src-tauri/swift/README.md | 3 + src-tauri/swift/src/spacedrive.swift | 62 ++++++++++++++++++++ src-tauri/tauri.conf.json | 4 +- src/App.tsx | 9 ++- src/components/file/FileList.tsx | 70 ++++++++++++++++------ src/components/file/Inspector.tsx | 47 +++++++++++++++ src/components/file/Sidebar.tsx | 14 ++--- src/components/layout/TopBar.tsx | 63 +++++++++++++------- src/screens/Explorer.tsx | 4 +- src/store/explorer.ts | 11 ++-- tailwind.config.js | 22 ++++--- yarn.lock | 34 ++++++++++- 22 files changed, 520 insertions(+), 76 deletions(-) create mode 100644 src-tauri/src/swift/Package.swift create mode 100644 src-tauri/swift/.gitignore create mode 100644 src-tauri/swift/Package.swift create mode 100644 src-tauri/swift/README.md create mode 100644 src-tauri/swift/src/spacedrive.swift create mode 100644 src/components/file/Inspector.tsx diff --git a/.TODO b/.TODO index 594d9d3bc..414833079 100644 --- a/.TODO +++ b/.TODO @@ -7,6 +7,21 @@ ✔ App frame and router set up ✔ Primative UI components (Button, Input, Shortcut etc) ✔ Render basic file list with database data -☐ Create a global store +✔ Create a global store +☐ File inspector <- CURRENT +☐ Tag creation and assignment +☐ Generate buffer hash +☐ Right click menu +☐ Settings screen ☐ Set up Tauri updater -☐ \ No newline at end of file +☐ Volume identification +☐ Job queue system +☐ Native file previews +☐ Secret keystore +☐ File encryptor +☐ File viewer / player +☐ Open with +☐ Explorer grid view +☐ Explorer path viewer / editor +☐ Statistic calucations + Folder size, Volume size, Total capacity, Total unique etc.. diff --git a/package.json b/package.json index 430181d9a..d7f243e7c 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "react-dom": "^17.0.2", "react-dropzone": "^11.3.4", "react-router-dom": "^5.2.0", + "react-virtualized": "^9.22.3", "rooks": "^5.7.1", "tailwindcss": "^2.2.16", "vite": "^2.4.4", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index d1e677901..79f2dc41b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -10,6 +10,7 @@ dependencies = [ "bytesize", "cargo-edit", "chrono", + "cocoa", "crossbeam", "data-encoding", "env_logger", @@ -18,6 +19,8 @@ dependencies = [ "libc", "log", "nix 0.23.0", + "objc", + "objc-foundation", "once_cell", "rebind", "refinery", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9b1489f2d..ec6b05123 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -11,6 +11,8 @@ build = "src/build.rs" [build-dependencies] tauri-build = { version = "1.0.0-beta.3" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" [dependencies] tauri = { version = "1.0.0-beta.5", features = ["api-all", "menu"] } @@ -40,6 +42,9 @@ bytesize = "1.1.0" libc = "0.2.103" nix = "0.23.0" log = "0.4.14" +objc = "0.2.7" +cocoa = "0.24.0" +objc-foundation = "0.1.1" [features] default = [ "custom-protocol" ] diff --git a/src-tauri/src/build.rs b/src-tauri/src/build.rs index 795b9b7c8..3583e2c09 100644 --- a/src-tauri/src/build.rs +++ b/src-tauri/src/build.rs @@ -1,3 +1,86 @@ -fn main() { - tauri_build::build() +use serde::Deserialize; +use serde_json; +use std::env; +use std::process::Command; + +const MACOS_TARGET_VERSION: &str = "11"; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SwiftTargetInfo { + triple: String, + unversioned_triple: String, + module_triple: String, + swift_runtime_compatibility_version: String, + #[serde(rename = "librariesRequireRPath")] + libraries_require_rpath: bool, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SwiftPaths { + runtime_library_paths: Vec, + runtime_library_import_paths: Vec, + runtime_resource_path: String, +} + +#[derive(Debug, Deserialize)] +struct SwiftTarget { + target: SwiftTargetInfo, + paths: SwiftPaths, +} + +/// Builds mac_ddc library Swift project, sets the library search options right so we link +/// against Swift run-time correctly. +fn build_swift_natives() { + let profile = env::var("PROFILE").unwrap(); + let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let target = format!("{}-apple-macosx{}", arch, MACOS_TARGET_VERSION); + + let swift_target_info_str = Command::new("swift") + .args(&["-target", &target, "-print-target-info"]) + .output() + .unwrap() + .stdout; + let swift_target_info: SwiftTarget = serde_json::from_slice(&swift_target_info_str).unwrap(); + if swift_target_info.target.libraries_require_rpath { + panic!("Libraries require RPath! Change minimum MacOS value to fix.") + } + + if !Command::new("swift") + .args(&["build", "-c", &profile]) + .current_dir("./swift") + .status() + .unwrap() + .success() + { + panic!("Swift natives compilation failed") + } + + swift_target_info + .paths + .runtime_library_paths + .iter() + .for_each(|path| { + println!("cargo:rustc-link-search=native={}", path); + }); + println!( + "cargo:rustc-link-search=native=./swift/.build/{}/{}", + swift_target_info.target.unversioned_triple, profile + ); + println!("cargo:rustc-link-lib=static=spacedrive"); + println!("cargo:rerun-if-changed=swift/src/*.swift"); + println!( + "cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={}", + MACOS_TARGET_VERSION + ); +} + +fn main() { + // let target = env::var("CARGO_CFG_TARGET_OS").unwrap(); + // if target == "macos" { + build_swift_natives(); + // } + + tauri_build::build(); } diff --git a/src-tauri/src/filesystem/init.rs b/src-tauri/src/filesystem/init.rs index a4a9e7fef..1d6dd838c 100644 --- a/src-tauri/src/filesystem/init.rs +++ b/src-tauri/src/filesystem/init.rs @@ -39,7 +39,7 @@ pub async fn init_library() -> Result<()> { println!("created library {:?}", &library); } else { - println!("library loaded {:?}", library.unwrap()); + // println!("library loaded {:?}", library.unwrap()); }; Ok(()) } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 8221f2f76..a110d4aa5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -14,13 +14,76 @@ use env_logger; use futures::executor::block_on; // use systemstat::{saturating_sub_bytes, Platform, System}; +#[derive(Debug)] +#[repr(C)] +struct FFIString { + // _phantom: u8, + data: *const u8, + length: u64, +} + +#[derive(Debug)] +#[repr(C)] +struct FFIData { + // _phantom: u8, + data: *const u8, + length: u64, +} + +extern "C" { + fn get_file_thumbnail(ptr: *const u8, length: u64) -> FFIData; + fn test(ptr: *const u8, length: u64) -> FFIData; +} + fn main() { + let path = "/Users/jamie/Downloads/Audio Hijack.app"; + // let string = FFIString { + // data: path.as_ptr(), + // length: path.len(), + // }; + // let thumbnail = unsafe { + // println!("{:?}", string); + // get_file_thumbnail(&string) + // }; + + println!("Struct Data: {:?}", unsafe { + let res = test(path.as_ptr(), path.len() as u64); + + let mut vec = Vec::::new(); + + let mut pointer_str = String::new(); + + for j in 8..16 { + let num = *(res.data.add(j)); + vec.push(num); + + pointer_str = format!("{:x}", num) + &pointer_str; + } + + let pointer_num = u64::from_str_radix(&pointer_str, 16).unwrap(); + let pointer = pointer_num as *const u8; + + let mut length = 0; + for j in 16..24 { + let num = *(res.data.add(j)); + length += num as usize * 256_usize.pow(j as u32 - 16_u32) + } + + let mut data = Vec::new(); + + for i in 0..length { + data.push(*pointer.add(i)); + } + + res + }); + // unsafe { get_icon("/Users/jamie/Downloads/Audio Hijack.app") } // let mounts = device::volumes_c::get_mounts(); // println!("mounted drives: {:?}", &mounts); - env_logger::builder() - .filter_level(log::LevelFilter::Debug) - .is_test(true) - .init(); + // env_logger::builder() + // .filter_level(log::LevelFilter::Debug) + // .is_test(true) + // .init(); // create primary data base if not exists block_on(db::connection::create_primary_db()).unwrap(); diff --git a/src-tauri/src/swift/Package.swift b/src-tauri/src/swift/Package.swift new file mode 100644 index 000000000..29d4a78ce --- /dev/null +++ b/src-tauri/src/swift/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version:5.5 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "drive_mac", + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "swift", + targets: ["swift"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + // .package(url: /* package url */, from: "1.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "swift", + dependencies: []), + .testTarget( + name: "swiftTests", + dependencies: ["swift"]), + ] +) diff --git a/src-tauri/swift/.gitignore b/src-tauri/swift/.gitignore new file mode 100644 index 000000000..bb460e7be --- /dev/null +++ b/src-tauri/swift/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/src-tauri/swift/Package.swift b/src-tauri/swift/Package.swift new file mode 100644 index 000000000..0361ed2bf --- /dev/null +++ b/src-tauri/swift/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.5 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "spacedrive", + platforms: [ + .macOS(.v11), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "spacedrive", + type: .static, + targets: ["spacedrive"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + // .package(url: /* package url */, from: "1.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "spacedrive", + dependencies: [], + path: "src") + ] +) diff --git a/src-tauri/swift/README.md b/src-tauri/swift/README.md new file mode 100644 index 000000000..a1d10f5d2 --- /dev/null +++ b/src-tauri/swift/README.md @@ -0,0 +1,3 @@ +# spacedive_swift + +A description of this package. diff --git a/src-tauri/swift/src/spacedrive.swift b/src-tauri/swift/src/spacedrive.swift new file mode 100644 index 000000000..823526b2e --- /dev/null +++ b/src-tauri/swift/src/spacedrive.swift @@ -0,0 +1,62 @@ +import Foundation +import AppKit + +public class FFIString: NSObject { + var data: UnsafePointer + var length: UInt64 + + init(data: UnsafePointer, length: UInt64) { + self.data = data + self.length = length + } +} + +public class FFIData: NSObject { + var data: UnsafePointer + var length: UInt64 + + init(data: UnsafePointer, length: UInt64) { + self.data = data + self.length = length + } +} + +@_cdecl("get_file_thumbnail") +public func getFileThumbnail(path_ptr: UnsafePointer, path_length: UInt64) -> FFIData { + print(path_length) + let path = String(data: Data(bytes: path_ptr, count: Int(path_length)), encoding: String.Encoding.utf8)! + print(path) + let image = NSWorkspace.shared.icon(forFile: path) + let bitmap = NSBitmapImageRep(data: image.tiffRepresentation!)!.representation(using: .png, properties: [:])! + + let pointer = UnsafeMutablePointer.allocate(capacity: bitmap.count) + bitmap.copyBytes(to: pointer, count: bitmap.count) + + return FFIData(data: UnsafePointer(pointer), length: UInt64(bitmap.count)) +} + +@_cdecl("test") +public func test(path_ptr: UnsafePointer, path_length: UInt64) -> FFIString { + let path = String(data: Data(bytes: path_ptr, count: Int(path_length)), encoding: String.Encoding.utf8)! + print(path) + let image = NSWorkspace.shared.icon(forFile: path) + let bitmap = NSBitmapImageRep(data: image.tiffRepresentation!)!.representation(using: .png, properties: [:])! + + let pointer = UnsafeMutablePointer.allocate(capacity: bitmap.count) + bitmap.copyBytes(to: pointer, count: bitmap.count) + + let data = UnsafePointer(pointer); + let length = UInt64(bitmap.count) + + print(data) + print(length) + + let ret = FFIString( + data: UnsafePointer(strdup(path)!), + length: UInt64(path.lengthOfBytes(using: .utf8)) + ) + + print(ret) + + return ret +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 79c1f5ee6..b38a8105c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -53,8 +53,8 @@ "windows": [ { "title": "SpaceDrive", - "width": 1200, - "height": 700, + "width": 1100, + "height": 600, "resizable": true, "fullscreen": false, "alwaysOnTop": false, diff --git a/src/App.tsx b/src/App.tsx index a01a1616b..f42fc6a62 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import React, { useRef } from 'react'; -import { Route, BrowserRouter as Router, Switch } from 'react-router-dom'; +import { Route, BrowserRouter as Router, Switch, Redirect } from 'react-router-dom'; import { Sidebar } from './components/file/Sidebar'; import { TopBar } from './components/layout/TopBar'; import { useInputState } from './hooks/useInputState'; @@ -9,12 +9,15 @@ import { ExplorerScreen } from './screens/Explorer'; export default function App() { return ( -
+
-
+
+ + + diff --git a/src/components/file/FileList.tsx b/src/components/file/FileList.tsx index 54aaf9e8d..740f2b757 100644 --- a/src/components/file/FileList.tsx +++ b/src/components/file/FileList.tsx @@ -1,6 +1,6 @@ import { DocumentIcon, DotsVerticalIcon, FilmIcon, FolderIcon } from '@heroicons/react/solid'; import clsx from 'clsx'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { IFile } from '../../types'; import byteSize from 'pretty-bytes'; import { useKey } from 'rooks'; @@ -22,13 +22,16 @@ function ensureIsColumns(data: T) { const columns = ensureIsColumns([ { column: 'Name', key: 'name', width: 280 } as const, { column: 'Size', key: 'size_in_bytes', width: 120 } as const, - { column: 'Checksum', key: 'meta_checksum', width: 120 } as const + { column: 'Type', key: 'extension', width: 100 } as const + // { column: 'Checksum', key: 'meta_checksum', width: 120 } as const // { column: 'Tags', key: 'tags', width: 120 } as const ]); type ColumnKey = typeof columns[number]['key']; export const FileList: React.FC<{}> = (props) => { + const scrollContainer = useRef(null); + const [rowHeight, setRowHeight] = useState(0); // const [selectedRow, setSelectedRow] = useState(0); const [currentDir, activeDirHash, collectDir, selectedRow, setSelectedRow] = useExplorerStore( (state) => [ @@ -42,18 +45,35 @@ export const FileList: React.FC<{}> = (props) => { useKey('ArrowUp', (e) => { e.preventDefault(); - if (selectedRow > 1) setSelectedRow(selectedRow - 1); - else setSelectedRow(currentDir.children_count); + if (!selectedRow || !currentDir?.children) return; + if (selectedRow?.index > 0) + // decrement selected index + setSelectedRow(selectedRow.index - 1, currentDir.children[selectedRow.index - 1]); + // loop to bottom + else setSelectedRow(currentDir.children_count, currentDir.children[currentDir.children_count]); }); useKey('ArrowDown', (e) => { e.preventDefault(); - if (selectedRow < currentDir.children_count) setSelectedRow(selectedRow + 1); - else setSelectedRow(0); + if (!selectedRow || !currentDir?.children) return; + // increment if rows below exist + if (selectedRow.index < currentDir.children_count) + setSelectedRow(selectedRow.index + 1, currentDir.children[selectedRow.index + 1]); + else setSelectedRow(0, currentDir.children[0]); }); + function isRowOutOfView(rowHeight: number, rowIndex: number) { + const scrollTop = scrollContainer.current?.scrollTop || 0; + } + + function handleScroll() {} + return useMemo( () => ( -
+
{columns.map((col) => ( @@ -68,7 +88,7 @@ export const FileList: React.FC<{}> = (props) => { ))}
-
+
{currentDir?.children?.map((row, index) => ( ))} @@ -86,13 +106,18 @@ const RenderRow: React.FC<{ row: IFile; rowIndex: number }> = ({ row, rowIndex } state.setSelected ]); - const isActive = selectedRow === row.id; + const isActive = selectedRow?.index === rowIndex; const isAlternate = rowIndex % 2 == 0; + function selectFile() { + if (selectedRow?.index == rowIndex) setSelectedRow(null); + else setSelectedRow(rowIndex, row); + } + return useMemo( () => (
setSelectedRow(row.id as number)} + onClick={selectFile} onDoubleClick={() => { if (row.is_dir) { invoke('get_files', { path: row.uri }).then((res) => { @@ -106,7 +131,11 @@ const RenderRow: React.FC<{ row: IFile; rowIndex: number }> = ({ row, rowIndex } })} > {columns.map((col) => ( -
+
))} @@ -123,25 +152,28 @@ const RenderCell: React.FC<{ colKey?: ColumnKey; row?: IFile }> = ({ colKey, row switch (colKey) { case 'name': return ( -
+
{colKey == 'name' && (() => { switch (row.extension.toLowerCase()) { case 'mov' || 'mp4': - return ; + return ; default: - if (row.is_dir) return ; - return ; + if (row.is_dir) + return ; + return ; } })()} - {row[colKey]} + {row[colKey]}
); case 'size_in_bytes': - return {byteSize(Number(value || 0))}; - case 'meta_checksum': - return {value}; + return {byteSize(Number(value || 0))}; + case 'extension': + return {value.toLowerCase()}; + // case 'meta_checksum': + // return {value}; // case 'tags': // return renderCellWithIcon(MusicNoteIcon); diff --git a/src/components/file/Inspector.tsx b/src/components/file/Inspector.tsx new file mode 100644 index 000000000..8c92c2247 --- /dev/null +++ b/src/components/file/Inspector.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { useExplorerStore } from '../../store/explorer'; +import { Transition } from '@headlessui/react'; + +interface MetaItemProps { + title: string; + value: string; +} + +const MetaItem = (props: MetaItemProps) => { + return ( +
+
{props.title}
+

{props.value}

+
+ ); +}; + +const Divider = () =>
; + +export const Inspector = () => { + const [selectedFile] = useExplorerStore((state) => [state.selected?.file]); + + const isOpen = !!selectedFile; + + return ( + +
+
+
+

{selectedFile?.name}

+ + + +
+
+ + ); +}; diff --git a/src/components/file/Sidebar.tsx b/src/components/file/Sidebar.tsx index 49f38759c..cf3355128 100644 --- a/src/components/file/Sidebar.tsx +++ b/src/components/file/Sidebar.tsx @@ -13,22 +13,22 @@ import { Dropdown } from '../primative/Dropdown'; import { DefaultProps } from '../primative/types'; const tabs = [ - { name: 'Spaces', icon: Planet, uri: '/spaces' }, - { name: 'Photos', icon: PhotographIcon, uri: '/photos' }, - { name: 'Storage', icon: ServerIcon, uri: '/storage' }, + // { name: 'Spaces', icon: Planet, uri: '/spaces' }, { name: 'Explorer', icon: CubeTransparentIcon, uri: '/explorer' }, - { name: 'Settings', icon: CogIcon, uri: '/settings' } + { name: 'Photos', icon: PhotographIcon, uri: '/photos' }, + { name: 'Storage', icon: ServerIcon, uri: '/settings' } + // { name: 'Settings', icon: CogIcon, uri: '/settings' } ]; interface SidebarProps extends DefaultProps {} export const Sidebar: React.FC = (props) => { return ( -
+
= (props) => { {tabs.map((button, index) => ( diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx index b69cbfa6b..07c28dc2f 100644 --- a/src/components/layout/TopBar.tsx +++ b/src/components/layout/TopBar.tsx @@ -7,46 +7,67 @@ import { ViewGridIcon, ViewListIcon } from '@heroicons/react/outline'; +import clsx from 'clsx'; import { HouseSimple } from 'phosphor-react'; import React from 'react'; +import { useExplorerStore } from '../../store/explorer'; import { TrafficLights } from '../os/TrafficLights'; -import { Button, Input } from '../primative'; +import { Button, ButtonProps, Input } from '../primative'; import { Shortcut } from '../primative/Shortcut'; import { DefaultProps } from '../primative/types'; export interface TopBarProps extends DefaultProps {} +export interface TopBarButtonProps extends ButtonProps { + icon: any; + group?: boolean; + active?: boolean; + left?: boolean; + right?: boolean; +} + +const TopBarButton: React.FC = ({ icon: Icon, ...props }) => { + return ( + + ); +}; export const TopBar: React.FC = (props) => { + const [goBack] = useExplorerStore((state) => [state.goBack]); return ( <>
- - + + +
- - - + + +
@@ -54,11 +75,9 @@ export const TopBar: React.FC = (props) => {
- +
-
+ {/*
*/} ); }; diff --git a/src/screens/Explorer.tsx b/src/screens/Explorer.tsx index afdbb7984..b9b21d4f3 100644 --- a/src/screens/Explorer.tsx +++ b/src/screens/Explorer.tsx @@ -4,6 +4,7 @@ import { emit, listen } from '@tauri-apps/api/event'; import { invoke } from '@tauri-apps/api'; import { IFile } from '../types'; import { useExplorerStore } from '../store/explorer'; +import { Inspector } from '../components/file/Inspector'; export interface DirectoryResponse { directory: IFile; @@ -26,8 +27,9 @@ export const ExplorerScreen: React.FC<{}> = () => { if (!activeDirHash) return <>; return ( -
+
+
); }; diff --git a/src/store/explorer.ts b/src/store/explorer.ts index b3de58ecd..f9de22f6e 100644 --- a/src/store/explorer.ts +++ b/src/store/explorer.ts @@ -6,17 +6,18 @@ interface ExplorerStore { dirs: Record; activeDirHash: string; history: string[]; - selected: number; + selected: null | { index: number; file: IFile }; collectDir: (dirHash: IFile, files: IFile[]) => void; currentDir?: () => IFile[]; - setSelected: (id: number) => void; + setSelected: (index: number | null, file?: IFile) => void; + goBack: () => void; } export const useExplorerStore = create((set, get) => ({ dirs: {}, activeDirHash: '', history: [], - selected: 0, + selected: null, collectDir: (directory, files) => { set((state) => produce(state, (draft) => { @@ -40,10 +41,10 @@ export const useExplorerStore = create((set, get) => ({ ); } }, - setSelected: (id) => + setSelected: (index?: number | null, file?: IFile) => set((state) => produce(state, (draft) => { - draft.selected = id; + draft.selected = !index || !file ? null : { index, file }; }) ) })); diff --git a/tailwind.config.js b/tailwind.config.js index 5f465be77..190c04c82 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -33,15 +33,23 @@ module.exports = { DEFAULT: '#505468', 50: '#F1F1F4', 100: '#E8E9ED', - 200: '#E0E1E6', + 150: '#E0E1E6', + 200: '#D8DAE3', + 250: '#D2D4DC', 300: '#C0C2CE', - 400: '#6F7590', + 350: '#A6AABF', + 400: '#9196A8', + 450: '#71758A', 500: '#505468', - 600: '#434656', - 700: '#353845', - 800: '#282A34', - 850: '#21212B', - 900: '#1B1C23' + 550: '#434656', + 600: '#3E414F', + 650: '#353845', + 700: '#333745', + 750: '#282A34', + 800: '#262832', + 850: '#30303E', + 900: '#22242F', + 950: '#15161D' } } // fontFamily: { sans: ['Inter', ...defaultTheme.fontFamily.sans] } diff --git a/yarn.lock b/yarn.lock index 4ab26df90..96d4ecf18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -213,6 +213,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7": + version "7.15.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" + integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" @@ -1060,7 +1067,7 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= -clsx@^1.1.1: +clsx@^1.0.4, clsx@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== @@ -1440,6 +1447,14 @@ dlv@^1.1.3: resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== +dom-helpers@^5.1.3: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -3371,6 +3386,11 @@ react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + react-refresh@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" @@ -3405,6 +3425,18 @@ react-router@5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-virtualized@^9.22.3: + version "9.22.3" + resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421" + integrity sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw== + dependencies: + "@babel/runtime" "^7.7.2" + clsx "^1.0.4" + dom-helpers "^5.1.3" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.4" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"