mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-03-10 02:26:21 -04:00
lazy load icons, refactor store,
This commit is contained in:
2
.TODO
2
.TODO
@@ -18,6 +18,8 @@
|
||||
☐ Job queue system
|
||||
☐ Native file previews
|
||||
☐ Secret keystore
|
||||
☐ Search
|
||||
Efficient way to search sqlite: make file table WITHOUT ROWID
|
||||
☐ File encryptor
|
||||
☐ File viewer / player
|
||||
☐ Open with
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dropzone": "^11.3.4",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-spline": "^1.2.1",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"rooks": "^5.7.1",
|
||||
"tailwindcss": "^2.2.16",
|
||||
|
||||
BIN
src-tauri/Cargo.lock
generated
BIN
src-tauri/Cargo.lock
generated
Binary file not shown.
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "SpaceDrive"
|
||||
name = "spacedrive"
|
||||
version = "0.1.0"
|
||||
description = "The next gen private virtual filesystem."
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
default-run = "SpaceDrive"
|
||||
default-run = "spacedrive"
|
||||
edition = "2018"
|
||||
build = "src/build.rs"
|
||||
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
use crate::db::entity::file;
|
||||
use crate::db::connection::db_instance;
|
||||
use crate::filesystem;
|
||||
use crate::filesystem::retrieve::Directory;
|
||||
use crate::swift::get_file_thumbnail_base64;
|
||||
use crate::{db, filesystem};
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::OnceCell;
|
||||
use sea_orm::ColumnTrait;
|
||||
use sea_orm::{DatabaseConnection, EntityTrait, QueryFilter};
|
||||
|
||||
pub static DB_INSTANCE: OnceCell<DatabaseConnection> = OnceCell::new();
|
||||
use serde::Serialize;
|
||||
|
||||
async fn db_instance() -> Result<&'static DatabaseConnection, String> {
|
||||
if DB_INSTANCE.get().is_none() {
|
||||
let db = db::connection::get_connection()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
DB_INSTANCE.set(db).unwrap_or_default();
|
||||
Ok(DB_INSTANCE.get().unwrap())
|
||||
} else {
|
||||
Ok(DB_INSTANCE.get().unwrap())
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
pub enum GlobalEventKind {
|
||||
FileTypeThumb,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GlobalEvent<T> {
|
||||
pub kind: GlobalEventKind,
|
||||
pub data: T,
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
pub struct GenFileTypeIconsResponse {
|
||||
pub thumbnail_b64: String,
|
||||
pub file_id: u32,
|
||||
}
|
||||
|
||||
pub fn reply<T: Serialize>(window: &tauri::Window, kind: GlobalEventKind, data: T) {
|
||||
let _message = window
|
||||
.emit("message", GlobalEvent { kind, data })
|
||||
.map_err(|e| println!("{}", e));
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
pub async fn scan_dir(path: String) -> Result<(), String> {
|
||||
pub async fn scan_dir(window: tauri::Window, path: String) -> Result<(), String> {
|
||||
db_instance().await?;
|
||||
|
||||
// reply(&window, GlobalEventKind::JEFF, "jeff");
|
||||
|
||||
let files = filesystem::indexer::scan(&path)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
@@ -35,37 +44,31 @@ pub async fn scan_dir(path: String) -> Result<(), String> {
|
||||
}
|
||||
#[tauri::command(async)]
|
||||
pub async fn get_file_thumb(path: &str) -> Result<String, String> {
|
||||
let thumbnail_b46 = get_file_thumbnail_base64(path).to_string();
|
||||
let thumbnail_b64 = get_file_thumbnail_base64(path).to_string();
|
||||
|
||||
Ok(thumbnail_b46)
|
||||
Ok(thumbnail_b64)
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
pub async fn get_files(path: String) -> Result<Directory, String> {
|
||||
let connection = db_instance().await?;
|
||||
Ok(filesystem::retrieve::get_dir_with_contents(&path).await?)
|
||||
}
|
||||
|
||||
println!("getting files... {:?}", &path);
|
||||
|
||||
let directories = file::Entity::find()
|
||||
.filter(file::Column::Uri.eq(path))
|
||||
.all(connection)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if directories.is_empty() {
|
||||
return Err("fuk".to_owned());
|
||||
#[tauri::command]
|
||||
pub async fn get_thumbs_for_directory(window: tauri::Window, path: &str) -> Result<(), String> {
|
||||
let dir = filesystem::retrieve::get_dir_with_contents(&path).await?;
|
||||
for file in dir.contents.into_iter() {
|
||||
let thumbnail_b64 = get_file_thumbnail_base64(&file.uri).to_string();
|
||||
println!("getting thumb: {:?}", file.id);
|
||||
reply(
|
||||
&window,
|
||||
GlobalEventKind::FileTypeThumb,
|
||||
GenFileTypeIconsResponse {
|
||||
thumbnail_b64,
|
||||
file_id: file.id,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
let directory = &directories[0];
|
||||
|
||||
let files = file::Entity::find()
|
||||
.filter(file::Column::ParentId.eq(directory.id))
|
||||
.all(connection)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(Directory {
|
||||
directory: directory.clone(),
|
||||
contents: files,
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::app::config;
|
||||
use anyhow::{Context, Result};
|
||||
use once_cell::sync::OnceCell;
|
||||
use rusqlite::Connection;
|
||||
use sea_orm::{Database, DatabaseConnection, DbErr};
|
||||
// use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode};
|
||||
@@ -16,6 +17,17 @@ pub async fn get_connection() -> Result<DatabaseConnection, DbErr> {
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
pub static DB_INSTANCE: OnceCell<DatabaseConnection> = OnceCell::new();
|
||||
pub async fn db_instance() -> Result<&'static DatabaseConnection, String> {
|
||||
if DB_INSTANCE.get().is_none() {
|
||||
let db = get_connection().await.map_err(|e| e.to_string())?;
|
||||
DB_INSTANCE.set(db).unwrap_or_default();
|
||||
Ok(DB_INSTANCE.get().unwrap())
|
||||
} else {
|
||||
Ok(DB_INSTANCE.get().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_primary_db() -> Result<(), sqlx::Error> {
|
||||
let config = config::get_config();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::commands::DB_INSTANCE;
|
||||
use crate::db::connection::DB_INSTANCE;
|
||||
use crate::db::entity::file;
|
||||
use crate::filesystem::{checksum, init};
|
||||
use crate::util::time;
|
||||
@@ -24,10 +24,18 @@ fn is_app_bundle(entry: &DirEntry) -> bool {
|
||||
let contains_dot = entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map(|s| s.contains("."))
|
||||
.map(|s| s.contains(".app") | s.contains(".bundle"))
|
||||
.unwrap_or(false);
|
||||
|
||||
is_dir && contains_dot
|
||||
let is_app_bundle = is_dir && contains_dot;
|
||||
// if is_app_bundle {
|
||||
// let path_buff = entry.path();
|
||||
// let path = path_buff.to_str().unwrap();
|
||||
|
||||
// self::path(&path, );
|
||||
// }
|
||||
|
||||
is_app_bundle
|
||||
}
|
||||
|
||||
pub async fn scan(path: &str) -> Result<()> {
|
||||
@@ -42,33 +50,50 @@ pub async fn scan(path: &str) -> Result<()> {
|
||||
// insert root directory
|
||||
dirs.insert(path.to_owned(), file.id);
|
||||
// iterate over files and subdirectories
|
||||
for entry in WalkDir::new(path)
|
||||
.into_iter()
|
||||
.filter_entry(|e| !is_hidden(e) && !is_app_bundle(e))
|
||||
{
|
||||
for entry in WalkDir::new(path).into_iter().filter_entry(|dir| {
|
||||
let approved = !is_hidden(dir) && !is_app_bundle(dir);
|
||||
approved
|
||||
}) {
|
||||
let entry = entry?;
|
||||
let path_buff = entry.path();
|
||||
let path = path_buff.to_str().unwrap();
|
||||
// get the parent directory from the path
|
||||
let parent = path_buff.parent().unwrap().to_str().unwrap();
|
||||
// get parent dir database id from hashmap
|
||||
let parent_dir = dirs.get(&parent.to_owned());
|
||||
let child_path = entry.path().to_str().unwrap();
|
||||
let parent_dir = get_parent_dir_id(&dirs, &entry);
|
||||
// analyse the child file
|
||||
let child_file = self::path(&path, parent_dir).await.unwrap();
|
||||
let child_file = self::path(&child_path, parent_dir).await.unwrap();
|
||||
|
||||
println!(
|
||||
"Reading file from dir {:?} {:?} assigned id {:?}",
|
||||
parent_dir, child_path, child_file.id
|
||||
);
|
||||
// if this file is a directory, save in hashmap with database id
|
||||
if child_file.is_dir {
|
||||
dirs.insert(path.to_owned(), child_file.id);
|
||||
dirs.insert(child_path.to_owned(), child_file.id);
|
||||
}
|
||||
|
||||
// println!("{}", entry.path().display());
|
||||
}
|
||||
}
|
||||
|
||||
println!("Scanning complete: {}", &path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_parent_dir_id(dirs: &HashMap<String, u32>, entry: &DirEntry) -> Option<u32> {
|
||||
let path = entry.path();
|
||||
let parent_path = path
|
||||
.parent()
|
||||
.unwrap_or_else(|| path)
|
||||
.to_str()
|
||||
.unwrap_or_default();
|
||||
let parent_dir_id = dirs.get(&parent_path.to_owned());
|
||||
match parent_dir_id {
|
||||
Some(x) => Some(x.clone()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Read a file from path returning the File struct
|
||||
// Generates meta checksum and extracts metadata
|
||||
pub async fn path(path: &str, parent_id: Option<&u32>) -> Result<file::Model> {
|
||||
pub async fn path(path: &str, parent_id: Option<u32>) -> Result<file::Model> {
|
||||
let db = DB_INSTANCE.get().unwrap();
|
||||
|
||||
let path_buff = path::PathBuf::from(path);
|
||||
@@ -99,9 +124,7 @@ pub async fn path(path: &str, parent_id: Option<&u32>) -> Result<file::Model> {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let file = file.save(&db).await.unwrap();
|
||||
|
||||
println!("FILE: {:?}", file);
|
||||
let _file = file.save(&db).await.unwrap();
|
||||
|
||||
// REPLACE WHEN SEA QL PULLS THROUGH
|
||||
let existing_files = file::Entity::find()
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::db::connection::db_instance;
|
||||
use crate::db::entity::file;
|
||||
use anyhow::Result;
|
||||
use sea_orm::ColumnTrait;
|
||||
use sea_orm::{EntityTrait, QueryFilter};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -6,3 +10,32 @@ pub struct Directory {
|
||||
pub directory: file::Model,
|
||||
pub contents: Vec<file::Model>,
|
||||
}
|
||||
|
||||
pub async fn get_dir_with_contents(path: &str) -> Result<Directory, String> {
|
||||
let connection = db_instance().await?;
|
||||
|
||||
println!("getting files... {:?}", &path);
|
||||
|
||||
let directories = file::Entity::find()
|
||||
.filter(file::Column::Uri.eq(path))
|
||||
.all(connection)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if directories.is_empty() {
|
||||
return Err("fuk".to_owned());
|
||||
}
|
||||
|
||||
let directory = &directories[0];
|
||||
|
||||
let files = file::Entity::find()
|
||||
.filter(file::Column::ParentId.eq(directory.id))
|
||||
.all(connection)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(Directory {
|
||||
directory: directory.clone(),
|
||||
contents: files,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ mod util;
|
||||
use crate::app::menu;
|
||||
use env_logger;
|
||||
use futures::executor::block_on;
|
||||
use tauri::Manager;
|
||||
|
||||
// use systemstat::{saturating_sub_bytes, Platform, System};
|
||||
|
||||
fn main() {
|
||||
@@ -31,10 +33,17 @@ fn main() {
|
||||
// block_on(filesystem::device::discover_storage_devices()).unwrap();
|
||||
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
// let main_window = app.get_window("main").unwrap();
|
||||
// // would need to emit this elsewhere in my Rust code
|
||||
// main_window.emit("my-event", "payload");
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::scan_dir,
|
||||
commands::get_files,
|
||||
commands::get_file_thumb
|
||||
commands::get_file_thumb,
|
||||
commands::get_thumbs_for_directory
|
||||
])
|
||||
.menu(menu::get_menu())
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "co.sd.client",
|
||||
"identifier": "co.spacedrive.client",
|
||||
"icon": ["icons/icon.icns"],
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Route, BrowserRouter as Router, Switch, Redirect } from 'react-router-dom';
|
||||
import { Sidebar } from './components/file/Sidebar';
|
||||
import { TopBar } from './components/layout/TopBar';
|
||||
@@ -6,11 +6,15 @@ import { useInputState } from './hooks/useInputState';
|
||||
import { SettingsScreen } from './screens/Settings';
|
||||
import { ExplorerScreen } from './screens/Explorer';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { DebugGlobalStore } from './store/Debug';
|
||||
import { useGlobalEvents } from './hooks/useGlobalEvents';
|
||||
|
||||
export default function App() {
|
||||
useGlobalEvents();
|
||||
return (
|
||||
<Router>
|
||||
<div className="flex flex-col select-none h-screen rounded-xl border border-gray-200 dark:border-gray-450 bg-white text-gray-900 dark:text-white dark:bg-gray-800 overflow-hidden ">
|
||||
<DebugGlobalStore />
|
||||
<TopBar />
|
||||
<div className="flex flex-row min-h-full">
|
||||
<Sidebar />
|
||||
|
||||
1
src/assets/spline/scene.json
Normal file
1
src/assets/spline/scene.json
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/spline/windows.json
Normal file
1
src/assets/spline/windows.json
Normal file
File diff suppressed because one or more lines are too long
@@ -5,7 +5,13 @@ import { IFile } from '../../types';
|
||||
import byteSize from 'pretty-bytes';
|
||||
import { useKey } from 'rooks';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { useExplorerStore } from '../../store/explorer';
|
||||
import {
|
||||
useCurrentDir,
|
||||
useExplorerStore,
|
||||
useFile,
|
||||
useSelectedFile,
|
||||
useSelectedFileIndex
|
||||
} from '../../store/explorer';
|
||||
import { DirectoryResponse } from '../../screens/Explorer';
|
||||
|
||||
interface Column {
|
||||
@@ -14,13 +20,6 @@ interface Column {
|
||||
width: number;
|
||||
}
|
||||
|
||||
function renderIcon(dirHash: string, path: string, rowIndex: number) {
|
||||
const setIconForFile = useExplorerStore.getState().setIconForFile;
|
||||
invoke('get_file_thumb', { path }).then((imageData) => {
|
||||
if (dirHash) setIconForFile(dirHash, rowIndex, imageData as string);
|
||||
});
|
||||
}
|
||||
|
||||
// Function ensure no types are loss, but guarantees that they are Column[]
|
||||
function ensureIsColumns<T extends Column[]>(data: T) {
|
||||
return data;
|
||||
@@ -40,38 +39,20 @@ export const FileList: React.FC<{}> = (props) => {
|
||||
const scrollContainer = useRef<null | HTMLDivElement>(null);
|
||||
const [rowHeight, setRowHeight] = useState(0);
|
||||
// const [selectedRow, setSelectedRow] = useState(0);
|
||||
const [currentDir, activeDirHash, collectDir, selectedRow, setSelectedRow] = useExplorerStore(
|
||||
(state) => [
|
||||
state.dirs[state.activeDirHash],
|
||||
state.activeDirHash,
|
||||
state.collectDir,
|
||||
state.selected,
|
||||
state.setSelected
|
||||
]
|
||||
);
|
||||
const currentDir = useCurrentDir();
|
||||
console.log({ currentDir });
|
||||
|
||||
if (!currentDir) return <></>;
|
||||
|
||||
const explorer = useExplorerStore.getState();
|
||||
|
||||
useKey('ArrowUp', (e) => {
|
||||
e.preventDefault();
|
||||
if (!selectedRow || !currentDir?.children) return;
|
||||
if (selectedRow?.index > 0) {
|
||||
const nextRowIndex = selectedRow.index - 1;
|
||||
const row = currentDir.children[selectedRow.index - 1];
|
||||
setSelectedRow(nextRowIndex, row);
|
||||
if (!row.icon_b64) renderIcon(activeDirHash, row.uri, nextRowIndex);
|
||||
}
|
||||
// loop to bottom
|
||||
else setSelectedRow(currentDir.children_count, currentDir.children[currentDir.children_count]);
|
||||
if (explorer.selectedFile) explorer.selectFile(currentDir.id, explorer.selectedFile, 'above');
|
||||
});
|
||||
useKey('ArrowDown', (e) => {
|
||||
e.preventDefault();
|
||||
if (!selectedRow || !currentDir?.children) return;
|
||||
// increment if rows below exist
|
||||
if (selectedRow.index < currentDir.children_count) {
|
||||
const nextRowIndex = selectedRow.index + 1;
|
||||
const row = currentDir.children[selectedRow.index + 1];
|
||||
setSelectedRow(nextRowIndex, row);
|
||||
if (!row.icon_b64) renderIcon(activeDirHash, row.uri, nextRowIndex);
|
||||
} else setSelectedRow(0, currentDir.children[0]);
|
||||
if (explorer.selectedFile) explorer.selectFile(currentDir.id, explorer.selectedFile, 'below');
|
||||
});
|
||||
|
||||
function isRowOutOfView(rowHeight: number, rowIndex: number) {
|
||||
@@ -80,58 +61,48 @@ export const FileList: React.FC<{}> = (props) => {
|
||||
|
||||
function handleScroll() {}
|
||||
|
||||
return useMemo(
|
||||
() => (
|
||||
<div
|
||||
ref={scrollContainer}
|
||||
onScroll={handleScroll}
|
||||
className="table-container w-full h-full overflow-scroll bg-white dark:bg-gray-900 p-3 cursor-default"
|
||||
>
|
||||
<div className="table-head">
|
||||
<div className="table-head-row flex flex-row p-2">
|
||||
{columns.map((col) => (
|
||||
<div
|
||||
key={col.key}
|
||||
className="table-head-cell flex flex-row items-center relative group px-4"
|
||||
style={{ width: col.width }}
|
||||
>
|
||||
<DotsVerticalIcon className="hidden absolute group-hover:block drag-handle w-5 h-5 opacity-10 -ml-5 cursor-move" />
|
||||
<span className="text-sm text-gray-500 font-medium">{col.column}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="table-body pb-10">
|
||||
{currentDir?.children?.map((row, index) => (
|
||||
<RenderRow key={row.id} row={row} rowIndex={index} dirId={currentDir.meta_checksum} />
|
||||
return (
|
||||
<div
|
||||
ref={scrollContainer}
|
||||
onScroll={handleScroll}
|
||||
className="table-container w-full h-full overflow-scroll bg-white dark:bg-gray-900 p-3 cursor-default"
|
||||
>
|
||||
<div className="table-head">
|
||||
<div className="table-head-row flex flex-row p-2">
|
||||
{columns.map((col) => (
|
||||
<div
|
||||
key={col.key}
|
||||
className="table-head-cell flex flex-row items-center relative group px-4"
|
||||
style={{ width: col.width }}
|
||||
>
|
||||
<DotsVerticalIcon className="hidden absolute group-hover:block drag-handle w-5 h-5 opacity-10 -ml-5 cursor-move" />
|
||||
<span className="text-sm text-gray-500 font-medium">{col.column}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
[activeDirHash]
|
||||
<div className="table-body pb-10">
|
||||
{currentDir?.children?.map((row, index) => (
|
||||
<RenderRow key={row.id} row={row} rowIndex={index} dirId={currentDir.id} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderRow: React.FC<{ row: IFile; rowIndex: number; dirId: string }> = ({
|
||||
const RenderRow: React.FC<{ row: IFile; rowIndex: number; dirId: number }> = ({
|
||||
row,
|
||||
rowIndex,
|
||||
dirId
|
||||
}) => {
|
||||
const [setIconForFile] = useExplorerStore((state) => [state.setIconForFile]);
|
||||
const selectedFileIndex = useSelectedFileIndex(dirId);
|
||||
|
||||
const [collectDir, selectedRow, setSelectedRow] = useExplorerStore((state) => [
|
||||
state.collectDir,
|
||||
state.selected,
|
||||
state.setSelected
|
||||
]);
|
||||
|
||||
const isActive = selectedRow?.index === rowIndex;
|
||||
const isActive = selectedFileIndex === rowIndex;
|
||||
const isAlternate = rowIndex % 2 == 0;
|
||||
|
||||
function selectFile() {
|
||||
if (!row.icon_b64) renderIcon(dirId, row.uri, rowIndex);
|
||||
if (selectedRow?.index == rowIndex) setSelectedRow(null);
|
||||
else setSelectedRow(rowIndex, row);
|
||||
if (selectedFileIndex == rowIndex) useExplorerStore.getState().clearSelectedFiles();
|
||||
else useExplorerStore.getState().selectFile(dirId, row.id);
|
||||
}
|
||||
|
||||
return useMemo(
|
||||
@@ -141,7 +112,7 @@ const RenderRow: React.FC<{ row: IFile; rowIndex: number; dirId: string }> = ({
|
||||
onDoubleClick={() => {
|
||||
if (row.is_dir) {
|
||||
invoke<DirectoryResponse>('get_files', { path: row.uri }).then((res) => {
|
||||
collectDir(res.directory, res.contents);
|
||||
useExplorerStore.getState().ingestDir(res.directory, res.contents);
|
||||
});
|
||||
}
|
||||
}}
|
||||
@@ -156,7 +127,7 @@ const RenderRow: React.FC<{ row: IFile; rowIndex: number; dirId: string }> = ({
|
||||
className="table-body-cell px-4 py-2 flex items-center pr-2"
|
||||
style={{ width: col.width }}
|
||||
>
|
||||
<RenderCell rowIndex={rowIndex} dirHash={dirId} colKey={col?.key} />
|
||||
<RenderCell fileId={row.id} dirId={dirId} colKey={col?.key} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -165,13 +136,13 @@ const RenderRow: React.FC<{ row: IFile; rowIndex: number; dirId: string }> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const RenderCell: React.FC<{ colKey?: ColumnKey; dirHash?: string; rowIndex?: number }> = ({
|
||||
const RenderCell: React.FC<{ colKey?: ColumnKey; dirId?: number; fileId?: number }> = ({
|
||||
colKey,
|
||||
rowIndex,
|
||||
dirHash
|
||||
fileId,
|
||||
dirId
|
||||
}) => {
|
||||
if (!rowIndex || !colKey || !dirHash) return <></>;
|
||||
const [row] = useExplorerStore((state) => [state.dirs[dirHash].children?.[rowIndex]]);
|
||||
if (!fileId || !colKey || !dirId) return <></>;
|
||||
const row = useFile(fileId);
|
||||
if (!row) return <></>;
|
||||
const value = row[colKey];
|
||||
if (!value) return <></>;
|
||||
@@ -180,9 +151,11 @@ const RenderCell: React.FC<{ colKey?: ColumnKey; dirHash?: string; rowIndex?: nu
|
||||
case 'name':
|
||||
return (
|
||||
<div className="flex flex-row items-center overflow-hidden">
|
||||
{!!row?.icon_b64 && (
|
||||
<img src={'data:image/png;base64, ' + row.icon_b64} className="w-6 h-6 mr-2" />
|
||||
)}
|
||||
<div className="w-6 h-6 mr-2">
|
||||
{!!row?.icon_b64 && (
|
||||
<img src={'data:image/png;base64, ' + row.icon_b64} className="w-6 h-6 mr-2" />
|
||||
)}
|
||||
</div>
|
||||
{/* {colKey == 'name' &&
|
||||
(() => {
|
||||
switch (row.extension.toLowerCase()) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useExplorerStore } from '../../store/explorer';
|
||||
import { useExplorerStore, useSelectedFile } from '../../store/explorer';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { IFile } from '../../types';
|
||||
|
||||
interface MetaItemProps {
|
||||
title: string;
|
||||
@@ -19,10 +20,12 @@ const MetaItem = (props: MetaItemProps) => {
|
||||
const Divider = () => <div className="w-full my-1 h-[1px] bg-gray-700" />;
|
||||
|
||||
export const Inspector = () => {
|
||||
const [selectedFile] = useExplorerStore((state) => [state.selected?.file]);
|
||||
const selectedFile = useSelectedFile();
|
||||
|
||||
const isOpen = !!selectedFile;
|
||||
|
||||
const file = selectedFile;
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={isOpen}
|
||||
@@ -36,14 +39,14 @@ export const Inspector = () => {
|
||||
<div className="h-full w-60 absolute right-0 top-0 m-2">
|
||||
<div className="flex flex-col overflow-hidden h-full rounded-lg bg-gray-700 shadow-lg select-text">
|
||||
<div className="h-32 bg-gray-750 w-full flex justify-center items-center">
|
||||
{!!selectedFile?.icon_b64 && (
|
||||
<img src={'data:image/png;base64, ' + selectedFile.icon_b64} className=" h-24" />
|
||||
{!!file?.icon_b64 && (
|
||||
<img src={'data:image/png;base64, ' + file.icon_b64} className=" h-24" />
|
||||
)}
|
||||
</div>
|
||||
<h3 className="font-bold p-3 text-base">{selectedFile?.name}</h3>
|
||||
<MetaItem title="Checksum" value={selectedFile?.meta_checksum as string} />
|
||||
<h3 className="font-bold p-3 text-base">{file?.name}</h3>
|
||||
<MetaItem title="Checksum" value={file?.meta_checksum as string} />
|
||||
<Divider />
|
||||
<MetaItem title="Uri" value={selectedFile?.uri as string} />
|
||||
<MetaItem title="Uri" value={file?.uri as string} />
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { TrafficLights } from '../os/TrafficLights';
|
||||
import { Button, ButtonProps, Input } from '../primative';
|
||||
import { Shortcut } from '../primative/Shortcut';
|
||||
import { DefaultProps } from '../primative/types';
|
||||
import { appWindow } from '@tauri-apps/api/window';
|
||||
|
||||
export interface TopBarProps extends DefaultProps {}
|
||||
export interface TopBarButtonProps extends ButtonProps {
|
||||
@@ -33,12 +34,12 @@ const TopBarButton: React.FC<TopBarButtonProps> = ({ icon: Icon, ...props }) =>
|
||||
<button
|
||||
{...props}
|
||||
className={clsx(
|
||||
'mr-[1px] py-1 px-1 text-md font-medium dark:bg-gray-550 dark:hover:bg-gray-600 dark:active:bg-gray-500 rounded-md transition-colors duration-100',
|
||||
'mr-[1px] py-0.5 px-0.5 text-md font-medium dark:bg-gray-550 dark:hover:bg-gray-600 dark:active:bg-gray-500 rounded-md transition-colors duration-100',
|
||||
{
|
||||
'rounded-r-none rounded-l-none': props.group && !props.left && !props.right,
|
||||
'rounded-r-none': props.group && props.left,
|
||||
'rounded-l-none': props.group && props.right,
|
||||
'dark:bg-gray-550 dark:hover:bg-gray-550 dark:active:bg-gray-550': props.active
|
||||
'dark:bg-gray-450 dark:hover:bg-gray-450 dark:active:bg-gray-450': props.active
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
@@ -57,32 +58,38 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
|
||||
className="flex flex-shrink-0 h-11 -mt-0.5 max-w items-center border-b bg-gray-100 dark:bg-gray-550 border-gray-100 dark:border-gray-900 shadow-sm"
|
||||
>
|
||||
<div className="mr-32 ml-1 ">
|
||||
<TrafficLights className="p-1.5" />
|
||||
<TrafficLights
|
||||
onClose={appWindow.close}
|
||||
onFullscreen={appWindow.maximize}
|
||||
onMinimize={appWindow.minimize}
|
||||
className="p-1.5"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TopBarButton group left icon={ChevronLeftIcon} onClick={goBack} />
|
||||
<TopBarButton group right icon={ChevronRightIcon} />
|
||||
<div className="w-8"></div>
|
||||
<TopBarButton active group left icon={ViewListIcon} />
|
||||
<TopBarButton group icon={ViewBoardsIcon} />
|
||||
<TopBarButton group right icon={ViewGridIcon} />
|
||||
<div className="w-8"></div>
|
||||
<div className="flex mx-8 space-x-[1px]">
|
||||
<TopBarButton active group left icon={ViewListIcon} />
|
||||
<TopBarButton group icon={ViewBoardsIcon} />
|
||||
<TopBarButton group right icon={ViewGridIcon} />
|
||||
</div>
|
||||
|
||||
<div className="relative flex h-7">
|
||||
<Input
|
||||
<input
|
||||
placeholder="Search"
|
||||
className="placeholder-gray-600 bg-gray-50 dark:bg-gray-500 dark:border-gray-500 dark:hover:!bg-gray-550 text-xs w-32 focus:w-52 transition-all"
|
||||
className="w-32 focus:w-52 text-xs p-2 rounded-md outline-none focus:ring-2 placeholder-gray-600 dark:placeholder-gray-300 bg-gray-50 dark:bg-gray-500 dark:border-gray-500 focus:ring-gray-100 dark:focus:ring-gray-600 transition-all"
|
||||
/>
|
||||
<div className="space-x-1 absolute top-[1px] right-1">
|
||||
<Shortcut chars="⌘" />
|
||||
<Shortcut chars="S" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-8"></div>
|
||||
<TopBarButton icon={TagIcon} />
|
||||
<TopBarButton icon={FolderAddIcon} />
|
||||
<TopBarButton icon={CloudIcon} />
|
||||
<div className="w-8"></div>
|
||||
<TopBarButton icon={ArrowsLeftRight} />
|
||||
<div className="flex mx-8 space-x-2">
|
||||
<TopBarButton icon={TagIcon} />
|
||||
<TopBarButton icon={FolderAddIcon} />
|
||||
<TopBarButton icon={CloudIcon} />
|
||||
<TopBarButton icon={ArrowsLeftRight} />
|
||||
</div>
|
||||
<div className="flex-grow"></div>
|
||||
<TopBarButton className="mr-[8px]" icon={CogIcon} />
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,8 @@ interface LightProps {
|
||||
const Light: React.FC<LightProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx('w-[12px] h-[12px] rounded-full', {
|
||||
onClick={props.action}
|
||||
className={clsx('w-[12px] h-[12px] rounded-full cursor-pointer', {
|
||||
'bg-red-400': props.mode == 'close',
|
||||
'bg-green-400': props.mode == 'fullscreen',
|
||||
'bg-yellow-400': props.mode == 'minimize'
|
||||
|
||||
27
src/hooks/useGlobalEvents.tsx
Normal file
27
src/hooks/useGlobalEvents.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useEffect } from 'react';
|
||||
import { emit, listen, Event } from '@tauri-apps/api/event';
|
||||
import { useExplorerStore } from '../store/explorer';
|
||||
|
||||
export interface RustEvent {
|
||||
kind: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
export function useGlobalEvents() {
|
||||
useEffect(() => {
|
||||
listen('message', (e: Event<RustEvent>) => {
|
||||
console.log({ e });
|
||||
|
||||
switch (e.payload?.kind) {
|
||||
case 'FileTypeThumb':
|
||||
useExplorerStore
|
||||
.getState()
|
||||
.tempInjectThumb(e.payload.data.file_id, e.payload.data.thumbnail_b64);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
}
|
||||
1
src/index.d.ts
vendored
Normal file
1
src/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module 'react-spline';
|
||||
@@ -12,21 +12,17 @@ export interface DirectoryResponse {
|
||||
}
|
||||
|
||||
export const ExplorerScreen: React.FC<{}> = () => {
|
||||
const [activeDirHash, collectDir] = useExplorerStore((state) => [
|
||||
state.activeDirHash,
|
||||
state.collectDir
|
||||
]);
|
||||
const [currentDir] = useExplorerStore((state) => [state.currentDir]);
|
||||
|
||||
useEffect(() => {
|
||||
invoke<DirectoryResponse>('get_files', { path: '/Users/jamie/Downloads/CoolFolder' }).then(
|
||||
(res) => {
|
||||
console.log({ res });
|
||||
collectDir(res.directory, res.contents);
|
||||
}
|
||||
);
|
||||
invoke<DirectoryResponse>('get_files', { path: '/Users/jamie/Downloads' }).then((res) => {
|
||||
console.log({ res });
|
||||
useExplorerStore.getState().ingestDir(res.directory, res.contents);
|
||||
invoke('get_thumbs_for_directory', { path: '/Users/jamie/Downloads' });
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!activeDirHash) return <></>;
|
||||
if (currentDir === null) return <></>;
|
||||
|
||||
return (
|
||||
<div className="relative w-full flex flex-row bg-white dark:bg-gray-900">
|
||||
|
||||
@@ -9,15 +9,24 @@ import { Dropdown } from '../components/primative/Dropdown';
|
||||
import { InputContainer } from '../components/primative/InputContainer';
|
||||
import { Shortcut } from '../components/primative/Shortcut';
|
||||
import { useInputState } from '../hooks/useInputState';
|
||||
//@ts-ignore
|
||||
// import { Spline } from 'react-spline';
|
||||
// import WINDOWS_SCENE from '../assets/spline/scene.json';
|
||||
|
||||
export const SettingsScreen: React.FC<{}> = () => {
|
||||
const fileUploader = useRef<HTMLInputElement | null>(null);
|
||||
const inputState = useInputState('/Users/jamie/Downloads');
|
||||
const inputState = useInputState('/Users/jamie/Downloads/CoolFolder');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="p-3">
|
||||
<div className="px-5">
|
||||
{/* <FileList files={dummyIFile} /> */}
|
||||
{/* <Spline scene={WINDOWS_SCENE} /> */}
|
||||
{/* <iframe
|
||||
src="https://my.spline.design/windowscopy-8e92a2e9b7cb4d9237100441e8c4f688/"
|
||||
width="100%"
|
||||
height="100%"
|
||||
></iframe> */}
|
||||
<div className="flex space-x-2 mt-4">
|
||||
<InputContainer
|
||||
title="Quick scan directory"
|
||||
@@ -25,15 +34,10 @@ export const SettingsScreen: React.FC<{}> = () => {
|
||||
>
|
||||
<Input {...inputState} placeholder="/users/jamie/Desktop" />
|
||||
</InputContainer>
|
||||
<InputContainer
|
||||
title="Quick scan directory"
|
||||
description="The directory for which this application will perform a detailed scan of the contents and sub directories"
|
||||
>
|
||||
<Input {...inputState} placeholder="/users/jamie/Desktop" />
|
||||
</InputContainer>
|
||||
</div>
|
||||
<div className="space-x-2 flex flex-row mt-6">
|
||||
<div className="space-x-2 flex flex-row mt-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
invoke('scan_dir', {
|
||||
@@ -43,14 +47,8 @@ export const SettingsScreen: React.FC<{}> = () => {
|
||||
>
|
||||
Scan Now
|
||||
</Button>
|
||||
<Button>Cancel</Button>
|
||||
</div>
|
||||
<div className="flex space-x-2 mt-2">
|
||||
<Button size="sm" variant="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="sm">Cancel</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-x-2 flex flex-row mt-4">
|
||||
<Toggle initialState={false} />
|
||||
</div>
|
||||
|
||||
7
src/store/Debug.tsx
Normal file
7
src/store/Debug.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useExplorerStore } from './explorer';
|
||||
|
||||
export function DebugGlobalStore() {
|
||||
useExplorerStore();
|
||||
return <></>;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import create from 'zustand';
|
||||
import { IDirectory, IFile } from '../types';
|
||||
import produce from 'immer';
|
||||
|
||||
interface ExplorerStore {
|
||||
dirs: Record<string, IDirectory>;
|
||||
activeDirHash: string;
|
||||
history: string[];
|
||||
selected: null | { index: number; file: IFile };
|
||||
collectDir: (dirHash: IFile, files: IFile[]) => void;
|
||||
currentDir?: () => IFile[];
|
||||
setSelected: (index: number | null, file?: IFile) => void;
|
||||
goBack: () => void;
|
||||
setIconForFile: (dirId: string, fileIndex: number, b64: string) => void;
|
||||
}
|
||||
|
||||
export const useExplorerStore = create<ExplorerStore>((set, get) => ({
|
||||
dirs: {},
|
||||
activeDirHash: '',
|
||||
history: [],
|
||||
selected: null,
|
||||
collectDir: (directory, files) => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
draft.history.push(directory.meta_checksum);
|
||||
draft.activeDirHash = directory.meta_checksum;
|
||||
draft.dirs[directory.meta_checksum] = {
|
||||
children: files,
|
||||
children_count: files.length,
|
||||
...directory
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
goBack: () => {
|
||||
if (get().history.length > 1) {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
draft.history.pop();
|
||||
draft.activeDirHash = draft.history[draft.history.length - 1];
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
setIconForFile: (dirId: string, fileIndex: number, b64: string) => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
console.log({ dirId, fileIndex, b64: b64?.slice(1, 50) });
|
||||
const dir = draft.dirs[dirId];
|
||||
const file = dir.children?.[fileIndex];
|
||||
if (file) {
|
||||
file.icon_b64 = b64;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
setSelected: (index?: number | null, file?: IFile) =>
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
draft.selected = !index || !file ? null : { index, file };
|
||||
})
|
||||
)
|
||||
}));
|
||||
146
src/store/explorer.tsx
Normal file
146
src/store/explorer.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import create from 'zustand';
|
||||
import { IDirectory, IFile } from '../types';
|
||||
import produce from 'immer';
|
||||
|
||||
interface ExplorerStore {
|
||||
// storage
|
||||
files: Record<number, IFile>;
|
||||
// indexes
|
||||
dirs: Record<number, number[]>;
|
||||
// ingest
|
||||
ingestDir: (dir: IFile, children: IFile[]) => void;
|
||||
// selection
|
||||
selectedFile: number | null;
|
||||
selectedFiles: number[];
|
||||
selectedFilesHistory: string[][];
|
||||
selectFile: (dirId: number, fileId: number, type?: 'below' | 'above') => void;
|
||||
clearSelectedFiles: () => void;
|
||||
// selectAnotherFile?: (fileId: number) => void;
|
||||
// selectFilesBetween?: (firstFileId: number, secondFileId: number) => void;
|
||||
// explorer state
|
||||
currentDir: number | null;
|
||||
dirHistory: number[];
|
||||
goBack?: () => void;
|
||||
goForward?: () => void;
|
||||
|
||||
tempInjectThumb: (fileId: number, b64: string) => void;
|
||||
}
|
||||
|
||||
export const useExplorerStore = create<ExplorerStore>((set, get) => ({
|
||||
files: {},
|
||||
dirs: {},
|
||||
selectedFile: null,
|
||||
selectedFiles: [],
|
||||
selectedFilesHistory: [],
|
||||
currentDir: null,
|
||||
dirHistory: [],
|
||||
|
||||
ingestDir: (dir, children) => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
draft.files[dir.id] = dir;
|
||||
// extract directory index of file ids
|
||||
draft.dirs[dir.id] = children.map((file) => file.id);
|
||||
// save files by id
|
||||
for (const index in children) {
|
||||
const child = children[index];
|
||||
draft.files[child.id] = child;
|
||||
}
|
||||
// if this dir in the history stack, remove history since then
|
||||
const existingDirHistoryIndex = draft.dirHistory.findIndex((i) => i === dir.id);
|
||||
if (existingDirHistoryIndex != -1) {
|
||||
draft.dirHistory.splice(existingDirHistoryIndex);
|
||||
}
|
||||
// push onto history stack
|
||||
draft.dirHistory.push(dir.id);
|
||||
|
||||
draft.currentDir = dir.id;
|
||||
})
|
||||
);
|
||||
},
|
||||
goBack: () => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
const prevDirId = draft.dirHistory[draft.dirHistory.length - 2];
|
||||
if (prevDirId == undefined) return;
|
||||
draft.currentDir = prevDirId;
|
||||
})
|
||||
);
|
||||
},
|
||||
goForward: () => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
const nextDirId = draft.dirHistory[draft.dirHistory.length];
|
||||
if (nextDirId == undefined) return;
|
||||
draft.currentDir = nextDirId;
|
||||
})
|
||||
);
|
||||
},
|
||||
selectFile: (dirId, fileId, type) => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
if (!draft.files[fileId]) return;
|
||||
if (!type) {
|
||||
draft.selectedFile = fileId;
|
||||
}
|
||||
// this is the logic for up / down movement on selected file
|
||||
const dirIndex = draft.dirs[dirId];
|
||||
const maxIndex = dirIndex.length - 1;
|
||||
const activeIndex = dirIndex.findIndex((i) => i === fileId);
|
||||
switch (type) {
|
||||
case 'above':
|
||||
if (activeIndex - 1 < 0) draft.selectedFile = dirIndex[maxIndex];
|
||||
else draft.selectedFile = dirIndex[activeIndex - 1];
|
||||
break;
|
||||
case 'below':
|
||||
if (activeIndex + 1 > maxIndex) draft.selectedFile = dirIndex[0];
|
||||
else draft.selectedFile = dirIndex[activeIndex + 1];
|
||||
break;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
clearSelectedFiles: () => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
draft.selectedFile = null;
|
||||
})
|
||||
);
|
||||
},
|
||||
tempInjectThumb: (fileId: number, b64: string) => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
if (!draft.files[fileId]) return;
|
||||
draft.files[fileId].icon_b64 = b64;
|
||||
})
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
export function useSelectedFile(): null | IFile {
|
||||
const [file] = useExplorerStore((state) => [state.files[state.selectedFile || -1]]);
|
||||
return file;
|
||||
}
|
||||
|
||||
export function useSelectedFileIndex(dirId: number): null | number {
|
||||
return useExplorerStore((state) =>
|
||||
state.dirs[dirId].findIndex((i) => i === state.files[state.selectedFile || -1]?.id)
|
||||
);
|
||||
}
|
||||
|
||||
export function useFile(fileId: number): null | IFile {
|
||||
return useExplorerStore((state) => state.files[fileId || -1]);
|
||||
}
|
||||
|
||||
export function useCurrentDir(): IDirectory | null {
|
||||
return useExplorerStore((state) => {
|
||||
const children = state.dirs[state.currentDir || -1].map((id) => state.files[id]);
|
||||
const directory = state.files[state.currentDir || -1];
|
||||
|
||||
return {
|
||||
...directory,
|
||||
children,
|
||||
children_count: children.length
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { Encryption } from './library';
|
||||
import { ImageMeta, VideoMeta } from './media';
|
||||
|
||||
export interface IFile {
|
||||
id?: number;
|
||||
id: number;
|
||||
meta_checksum: string;
|
||||
uri: string;
|
||||
is_dir: string;
|
||||
@@ -22,6 +22,11 @@ export interface IFile {
|
||||
parent_id: string;
|
||||
tags?: ITag[];
|
||||
|
||||
// this state is used to tell the renderer to look in the designated
|
||||
// folder for this media type
|
||||
has_native_icon?: boolean;
|
||||
has_thumb?: boolean;
|
||||
has_preview_media?: boolean;
|
||||
icon_b64?: string;
|
||||
}
|
||||
|
||||
|
||||
188
yarn-error.log
188
yarn-error.log
@@ -1,8 +1,8 @@
|
||||
Arguments:
|
||||
/usr/local/bin/node /usr/local/Cellar/yarn/1.22.5/libexec/bin/yarn.js add @types/heroicons -D
|
||||
/usr/local/bin/node /usr/local/Cellar/yarn/1.22.5/libexec/bin/yarn.js add @types/react-spline
|
||||
|
||||
PATH:
|
||||
/Users/jamie/Library/Android/sdk/tools:/Users/jamie/Library/Android/sdk/platform-tools:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Library/Apple/usr/bin:/Users/jamie/.cargo/bin
|
||||
/Users/jamie/Library/Android/sdk/tools:/Users/jamie/Library/Android/sdk/platform-tools:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Library/Apple/usr/bin:/Users/jamie/Library/Android/sdk/tools:/Users/jamie/Library/Android/sdk/platform-tools:/Users/jamie/.cargo/bin
|
||||
|
||||
Yarn version:
|
||||
1.22.5
|
||||
@@ -14,7 +14,7 @@ Platform:
|
||||
darwin x64
|
||||
|
||||
Trace:
|
||||
Error: https://registry.yarnpkg.com/@types%2fheroicons: Not found
|
||||
Error: https://registry.yarnpkg.com/@types%2freact-spline: Not found
|
||||
at Request.params.callback [as _callback] (/usr/local/Cellar/yarn/1.22.5/libexec/lib/cli.js:66988:18)
|
||||
at Request.self.callback (/usr/local/Cellar/yarn/1.22.5/libexec/lib/cli.js:140749:22)
|
||||
at Request.emit (events.js:315:20)
|
||||
@@ -36,8 +36,10 @@ npm manifest:
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.0.0-beta.6",
|
||||
"@types/babel-core": "^6.25.7",
|
||||
"@types/byte-size": "^8.1.0",
|
||||
"@types/react": "^17.0.18",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/react-router-dom": "^5.3.1",
|
||||
"@types/tailwindcss": "^2.2.1",
|
||||
"concurrently": "^6.2.1",
|
||||
"prettier": "^2.3.2",
|
||||
@@ -52,22 +54,28 @@ npm manifest:
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.4.7",
|
||||
"@headlessui/react": "^1.4.0",
|
||||
"@tailwindcss/forms": "^0.3.3",
|
||||
"@heroicons/react": "^1.0.4",
|
||||
"@tauri-apps/api": "^1.0.0-beta.5",
|
||||
"@types/pretty-bytes": "^5.2.0",
|
||||
"@types/react-table": "^7.7.6",
|
||||
"@vitejs/plugin-react-refresh": "^1.3.6",
|
||||
"autoprefixer": "^9",
|
||||
"byte-size": "^8.1.0",
|
||||
"clsx": "^1.1.1",
|
||||
"heroicons": "^1.0.4",
|
||||
"mobx": "^6.3.3",
|
||||
"mobx-state-tree": "^5.0.3",
|
||||
"immer": "^9.0.6",
|
||||
"phosphor-react": "^1.3.1",
|
||||
"pretty-bytes": "^5.6.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dropzone": "^11.3.4",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-spline": "^1.2.1",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"rooks": "^5.7.1",
|
||||
"tailwindcss": "^2.2.16",
|
||||
"vite": "^2.4.4",
|
||||
"vite-tsconfig-paths": "^3.3.13"
|
||||
"vite-tsconfig-paths": "^3.3.13",
|
||||
"zustand": "^3.5.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +298,13 @@ Lockfile:
|
||||
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"
|
||||
@@ -337,6 +352,11 @@ Lockfile:
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.4.0.tgz#c6d424d8ab10ac925e4423d7f3cbab02c30d736a"
|
||||
integrity sha512-C+FmBVF6YGvqcEI5fa2dfVbEaXr2RGR6Kw1E5HXIISIZEfsrH/yuCgsjWw5nlRF9vbCxmQ/EKs64GAdKeb8gCw==
|
||||
|
||||
"@heroicons/react@^1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.4.tgz#11847eb2ea5510419d7ada9ff150a33af0ad0863"
|
||||
integrity sha512-3kOrTmo8+Z8o6AL0rzN82MOf8J5CuxhRLFhpI8mrn+3OqekA6d5eb1GYO3EYYo1Vn6mYQSMNTzCWbEwUInb0cQ==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
@@ -395,13 +415,6 @@ Lockfile:
|
||||
dependencies:
|
||||
defer-to-connect "^2.0.0"
|
||||
|
||||
"@tailwindcss/forms@^0.3.3":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.3.3.tgz#a29d22668804f3dae293dcadbef1aa6315c45b64"
|
||||
integrity sha512-U8Fi/gq4mSuaLyLtFISwuDYzPB73YzgozjxOIHsK6NXgg/IWD1FLaHbFlWmurAMyy98O+ao74ksdQefsquBV1Q==
|
||||
dependencies:
|
||||
mini-svg-data-uri "^1.2.3"
|
||||
|
||||
"@tauri-apps/api@^1.0.0-beta.5":
|
||||
version "1.0.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.0.0-beta.5.tgz#32727b3b478f67330a79409ceaf16a4a51410fce"
|
||||
@@ -491,6 +504,11 @@ Lockfile:
|
||||
dependencies:
|
||||
"@types/babel-types" "*"
|
||||
|
||||
"@types/byte-size@^8.1.0":
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/byte-size/-/byte-size-8.1.0.tgz#013a3995d1ff2d85ad27da0801029f13328bd91b"
|
||||
integrity sha512-LCIlZh8vyx+I2fgRycE1D34c33QDppYY6quBYYoaOpQ1nGhJ/avSP2VlrAefVotjJxgSk6WkKo0rTcCJwGG7vA==
|
||||
|
||||
"@types/cacheable-request@^6.0.1":
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9"
|
||||
@@ -501,6 +519,11 @@ Lockfile:
|
||||
"@types/node" "*"
|
||||
"@types/responselike" "*"
|
||||
|
||||
"@types/history@*":
|
||||
version "4.7.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724"
|
||||
integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
|
||||
|
||||
"@types/http-cache-semantics@*":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812"
|
||||
@@ -528,6 +551,13 @@ Lockfile:
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
|
||||
|
||||
"@types/pretty-bytes@^5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/pretty-bytes/-/pretty-bytes-5.2.0.tgz#857dcf4a21839e5bfb1c188dda62f986fdfa2348"
|
||||
integrity sha512-dJhMFphDp6CE+OAZVyqzha9KsmgeqRMbZN4dIbMSrfObiuzfjucwKdn6zu+ttrjMwmz+Vz71/xXgHx5pO0axhA==
|
||||
dependencies:
|
||||
pretty-bytes "*"
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
|
||||
@@ -540,6 +570,30 @@ Lockfile:
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-router-dom@^5.3.1":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.1.tgz#76700ccce6529413ec723024b71f01fc77a4a980"
|
||||
integrity sha512-UvyRy73318QI83haXlaMwmklHHzV9hjl3u71MmM6wYNu0hOVk9NLTa0vGukf8zXUqnwz4O06ig876YSPpeK28A==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
"@types/react-router" "*"
|
||||
|
||||
"@types/react-router@*":
|
||||
version "5.1.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.17.tgz#087091006213b11042f39570e5cd414863693968"
|
||||
integrity sha512-RNSXOyb3VyRs/EOGmjBhhGKTbnN6fHWvy5FNLzWfOWOGjgVUKqJZXfpKzLmgoU8h6Hj8mpALj/mbXQASOb92wQ==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-table@^7.7.6":
|
||||
version "7.7.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.6.tgz#4899ccc46b4a1de08cc1daf120842e37e112e51e"
|
||||
integrity sha512-ZMFHh1sG5AGDmhVRpz9mgGByGmBFAqnZ7QnyqGa5iAlKtcSC3vb/gul47lM0kJ1uvlawc+qN5k+++pe+GBdJ+g==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^17.0.18":
|
||||
version "17.0.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.18.tgz#4109cbbd901be9582e5e39e3d77acd7b66bb7fbe"
|
||||
@@ -892,6 +946,11 @@ Lockfile:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.1.13"
|
||||
|
||||
byte-size@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-8.1.0.tgz#6353d0bc14ab7a69abcefbf11f8db0145a862cb5"
|
||||
integrity sha512-FkgMTAg44I0JtEaUAvuZTtU2a2YDmBRbQxdsQNSMtLCjhG0hMcF5b1IMN9UjSCJaU4nvlj/GER7B9sI4nKdCgA==
|
||||
|
||||
bytes@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||
@@ -1093,7 +1152,7 @@ Lockfile:
|
||||
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==
|
||||
@@ -1473,6 +1532,14 @@ Lockfile:
|
||||
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"
|
||||
@@ -2065,11 +2132,6 @@ Lockfile:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
heroicons@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/heroicons/-/heroicons-1.0.4.tgz#58a9c5fafae7185fca79f8b47251484903e4dc0a"
|
||||
integrity sha512-dn1OvwZruCNEmNDr+TRvG5MejL9kW4VtVQO2Y1yfp7rz0+yWqpkjwwFRTQ8VHBF26oNMMUofQYA+Jl363Y7JdA==
|
||||
|
||||
hex-color-regex@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||
@@ -2179,6 +2241,11 @@ Lockfile:
|
||||
p-pipe "^4.0.0"
|
||||
replace-ext "^2.0.0"
|
||||
|
||||
immer@^9.0.6:
|
||||
version "9.0.6"
|
||||
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.6.tgz#7a96bf2674d06c8143e327cbf73539388ddf1a73"
|
||||
integrity sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==
|
||||
|
||||
import-cwd@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-3.0.0.tgz#20845547718015126ea9b3676b7592fb8bd4cf92"
|
||||
@@ -2564,6 +2631,11 @@ Lockfile:
|
||||
pinkie-promise "^2.0.0"
|
||||
strip-bom "^2.0.0"
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||
|
||||
lodash.topath@^4.5.2:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009"
|
||||
@@ -2731,11 +2803,6 @@ Lockfile:
|
||||
"@babel/runtime" "^7.12.1"
|
||||
tiny-warning "^1.0.3"
|
||||
|
||||
mini-svg-data-uri@^1.2.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.3.3.tgz#91d2c09f45e056e5e1043340b8b37ba7b50f4fac"
|
||||
integrity sha512-+fA2oRcR1dJI/7ITmeQJDrYWks0wodlOz0pAEhKYJ2IVc1z0AnwJUsKY2fzFmPAM3Jo9J0rBx8JAA9QQSJ5PuA==
|
||||
|
||||
minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
@@ -2753,16 +2820,6 @@ Lockfile:
|
||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||
|
||||
mobx-state-tree@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/mobx-state-tree/-/mobx-state-tree-5.0.3.tgz#f026a26be39ba4ef71e4256b9f27d6176a3b2072"
|
||||
integrity sha512-68VKZb30bLWN6MFyYvpvwM93VMEqyJLJsbjTDrYmbRAK594n5LJqQXa7N3N00L/WjcujzMtqkT7CcbM0zqTIcA==
|
||||
|
||||
mobx@^6.3.3:
|
||||
version "6.3.3"
|
||||
resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.3.tgz#a3006c56243b1c7ea4ee671a66f963b9f43cf1af"
|
||||
integrity sha512-JoNU50rO6d1wHwKPJqKq4rmUMbYnI9CsJmBo+Cu4exBYenFvIN77LWrZENpzW6reZPADtXMmB1DicbDSfy8Clw==
|
||||
|
||||
modern-normalize@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7"
|
||||
@@ -3141,6 +3198,11 @@ Lockfile:
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||
|
||||
phosphor-react@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/phosphor-react/-/phosphor-react-1.3.1.tgz#96e33f44370d83cda15b60cccc17087ad0060684"
|
||||
@@ -3291,6 +3353,11 @@ Lockfile:
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d"
|
||||
integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==
|
||||
|
||||
pretty-bytes@*, pretty-bytes@^5.6.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
||||
integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
|
||||
|
||||
pretty-hrtime@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
|
||||
@@ -3364,6 +3431,13 @@ Lockfile:
|
||||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
||||
|
||||
raf@^3.4.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
||||
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
|
||||
dependencies:
|
||||
performance-now "^2.1.0"
|
||||
|
||||
rc@^1.2.7, rc@^1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
@@ -3397,6 +3471,11 @@ Lockfile:
|
||||
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"
|
||||
@@ -3431,6 +3510,25 @@ Lockfile:
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-spline@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-spline/-/react-spline-1.2.1.tgz#c074178cf752b1f582fb62af0c330d9a3f5e0099"
|
||||
integrity sha512-d17gOvoKjWfHmsXm7H1YbK715dDEVuIc72eNcrrpNp7T/H8zH2yfNvk1NSJQSuGchTXaa90hbiGRC3FGpJHMZA==
|
||||
dependencies:
|
||||
three "0.123.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"
|
||||
@@ -3656,6 +3754,14 @@ Lockfile:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
rooks@^5.7.1:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/rooks/-/rooks-5.7.1.tgz#a9c91e94e760e21a9df5afc1de4ba07d09a6156d"
|
||||
integrity sha512-Gztycgm+e+bS0vqLMSGlGe8f7rkXMxjfPj3FucM06/xu1CEFQx1pZ0zMVdWVxDeMXRePaQ2/g1K7ArIlGKyHbQ==
|
||||
dependencies:
|
||||
lodash.debounce "^4.0.8"
|
||||
raf "^3.4.1"
|
||||
|
||||
run-async@^2.4.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
|
||||
@@ -4147,6 +4253,11 @@ Lockfile:
|
||||
temp-dir "^1.0.0"
|
||||
uuid "^3.0.1"
|
||||
|
||||
three@0.123.0:
|
||||
version "0.123.0"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.123.0.tgz#3bb6d8f908a432eb7cd450f7eab6dd40fde53085"
|
||||
integrity sha512-KNnx/IbilvoHRkxOtL0ouozoDoElyuvAXhFB21RK7F5IPWSmqyFelICK6x3hJerLNSlAdHxR0hkuvMMhH9pqXg==
|
||||
|
||||
through@^2.3.6, through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
@@ -4537,3 +4648,8 @@ Lockfile:
|
||||
bin-build "^3.0.0"
|
||||
bin-wrapper "^4.0.1"
|
||||
logalot "^2.1.0"
|
||||
|
||||
zustand@^3.5.13:
|
||||
version "3.5.13"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.5.13.tgz#fd1af8c14a23ec7efdeb9ab167b8e69a8fc0980a"
|
||||
integrity sha512-orO/XcYwSWffsrPVTdCtuKM/zkUaOIyKDasOk/lecsD3R0euELsj+cB65uKZ1KyinrK2STHIuUhRoLpH8QprQg==
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@@ -3425,6 +3425,13 @@ react-router@5.2.0:
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-spline@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-spline/-/react-spline-1.2.1.tgz#c074178cf752b1f582fb62af0c330d9a3f5e0099"
|
||||
integrity sha512-d17gOvoKjWfHmsXm7H1YbK715dDEVuIc72eNcrrpNp7T/H8zH2yfNvk1NSJQSuGchTXaa90hbiGRC3FGpJHMZA==
|
||||
dependencies:
|
||||
three "0.123.0"
|
||||
|
||||
react-virtualized@^9.22.3:
|
||||
version "9.22.3"
|
||||
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421"
|
||||
@@ -4161,6 +4168,11 @@ tempfile@^2.0.0:
|
||||
temp-dir "^1.0.0"
|
||||
uuid "^3.0.1"
|
||||
|
||||
three@0.123.0:
|
||||
version "0.123.0"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.123.0.tgz#3bb6d8f908a432eb7cd450f7eab6dd40fde53085"
|
||||
integrity sha512-KNnx/IbilvoHRkxOtL0ouozoDoElyuvAXhFB21RK7F5IPWSmqyFelICK6x3hJerLNSlAdHxR0hkuvMMhH9pqXg==
|
||||
|
||||
through@^2.3.6, through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
Reference in New Issue
Block a user