bunch 'o refactor

performance gains on file list
better typings
This commit is contained in:
Jamie 2021-10-14 21:00:37 -07:00
parent 0a021982c6
commit eb64c90f83
28 changed files with 490 additions and 702 deletions

12
.TODO Normal file
View file

@ -0,0 +1,12 @@
# SpaceDrive development log / todo list
✔ Configured a custom MacOS style Tauri window
✔ Connect & create sqlite database in application data folder
✔ Auto migrate database on app launch
✔ Define data schema in Rust
✔ Scan given directory recursively and write to database
✔ App frame and router set up
✔ Primative UI components (Button, Input, Shortcut etc)
✔ Render basic file list with database data
☐ Create a global store
☐ Set up Tauri updater

View file

@ -7,6 +7,7 @@
"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",
@ -26,19 +27,23 @@
"@headlessui/react": "^1.4.0",
"@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",
"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",
"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"
}
}

View file

@ -1,15 +1,18 @@
use crate::db::entity::file;
use crate::filesystem::{init, reader};
use crate::filesystem::retrieve::Directory;
use crate::{db, filesystem};
use anyhow::Result;
use once_cell::sync::OnceCell;
use sea_orm::{DatabaseConnection, EntityTrait};
use sea_orm::ColumnTrait;
use sea_orm::{DatabaseConnection, EntityTrait, QueryFilter};
pub static DB_INSTANCE: OnceCell<DatabaseConnection> = OnceCell::new();
async fn db_instance() -> Result<&'static DatabaseConnection> {
async fn db_instance() -> Result<&'static DatabaseConnection, String> {
if DB_INSTANCE.get().is_none() {
let db = db::connection::get_connection().await?;
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 {
@ -18,38 +21,44 @@ async fn db_instance() -> Result<&'static DatabaseConnection> {
}
#[tauri::command(async)]
pub async fn scan_dir(path: &str) -> Result<(), String> {
db_instance().await.map_err(|e| e.to_string())?;
pub async fn scan_dir(path: String) -> Result<(), String> {
db_instance().await?;
let directories = filesystem::explorer::scan(path)
let files = filesystem::indexer::scan(&path)
.await
.map_err(|e| e.to_string())?;
println!("file: {:?}", directories);
println!("file: {:?}", files);
Ok(())
}
#[tauri::command(async)]
pub async fn get_files() -> Result<Vec<file::Model>, String> {
let connection = db_instance().await.map_err(|e| e.to_string())?;
pub async fn get_files(path: String) -> Result<Directory, String> {
let connection = db_instance().await?;
let files = file::Entity::find()
println!("getting files... {:?}", &path);
let directories = file::Entity::find()
.filter(file::Column::Uri.eq(path))
.all(connection)
.await
.map_err(|e| e.to_string())?;
println!("files found, {:?}", files.len());
if directories.is_empty() {
return Err("fuk".to_owned());
}
Ok(files[..100].to_vec())
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,
})
}
// #[tauri::command(async)]
// pub async fn generate_buffer_checksum(path: &str) -> Result<File, InvokeError> {
// let mut file = file::read_file(path)
// .await
// .map_err(|error| InvokeError::from(format!("Failed to read file: {}", error)))?;
// // file.buffer_checksum = Some(checksum::create_hash(&file.uri).await.unwrap());
// Ok(file)
// }

View file

@ -28,6 +28,7 @@ pub struct Model {
pub enum Relation {
StorageDevice,
Library,
File,
}
impl RelationTrait for Relation {
@ -41,10 +42,13 @@ impl RelationTrait for Relation {
.from(Column::StorageDeviceId)
.to(super::storage_device::Column::Id)
.into(),
Self::File => Entity::has_many(super::file::Entity).into(),
}
}
}
// BRB, getting something from the store, 15 mins max!
impl Related<super::library::Entity> for Entity {
fn to() -> RelationDef {
Relation::Library.def()
@ -57,4 +61,10 @@ impl Related<super::storage_device::Entity> for Entity {
}
}
impl Related<super::file::Entity> for Entity {
fn to() -> RelationDef {
Relation::File.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -16,6 +16,7 @@ pub struct Model {
#[sea_orm(unique)]
pub meta_checksum: String,
pub uri: String,
pub is_dir: bool,
// date
pub date_created: Option<NaiveDateTime>,
pub date_modified: Option<NaiveDateTime>,
@ -25,7 +26,6 @@ pub struct Model {
pub extension: String,
pub size_in_bytes: String,
pub library_id: u32,
pub directory_id: Option<u32>,
// #[sea_orm(column_type = "Int")]
// pub encryption: crypto::Encryption,
// ownership
@ -36,13 +36,12 @@ pub struct Model {
#[sea_orm(nullable)]
pub capture_device_id: Option<u32>,
#[sea_orm(nullable)]
pub parent_file_id: Option<u32>,
pub parent_id: Option<u32>,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
Library,
Directory,
StorageDevice,
CaptureDevice,
ParentFile,
@ -55,10 +54,6 @@ impl RelationTrait for Relation {
.from(Column::LibraryId)
.to(super::library::Column::Id)
.into(),
Self::Directory => Entity::belongs_to(super::dir::Entity)
.from(Column::DirectoryId)
.to(super::dir::Column::Id)
.into(),
Self::StorageDevice => Entity::belongs_to(super::storage_device::Entity)
.from(Column::StorageDeviceId)
.to(super::storage_device::Column::Id)
@ -68,7 +63,7 @@ impl RelationTrait for Relation {
.to(super::capture_device::Column::Id)
.into(),
Self::ParentFile => Entity::belongs_to(Entity)
.from(Column::ParentFileId)
.from(Column::ParentId)
.to(Column::Id)
.into(),
}
@ -79,11 +74,6 @@ impl Related<super::library::Entity> for Entity {
Relation::Library.def()
}
}
impl Related<super::dir::Entity> for Entity {
fn to() -> RelationDef {
Relation::Directory.def()
}
}
impl Related<super::storage_device::Entity> for Entity {
fn to() -> RelationDef {
Relation::StorageDevice.def()

View file

@ -1,5 +1,5 @@
pub mod capture_device;
pub mod dir;
// pub mod dir;
pub mod file;
pub mod library;
pub mod space;

View file

@ -23,6 +23,7 @@ CREATE TABLE IF NOT EXISTS spaces (
CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uri TEXT NOT NULL,
is_dir BOOLEAN NOT NULL DEFAULT FALSE,
meta_checksum TEXT NOT NULL UNIQUE,
buffer_checksum TEXT,
name TEXT,
@ -37,31 +38,12 @@ CREATE TABLE IF NOT EXISTS files (
storage_device_id INTEGER,
directory_id INTEGER,
capture_device_id INTEGER,
parent_file_id INTEGER,
parent_id INTEGER,
FOREIGN KEY(library_id) REFERENCES libraries(id),
FOREIGN KEY(directory_id) REFERENCES directories(id),
FOREIGN KEY(parent_file_id) REFERENCES files(id),
FOREIGN KEY(parent_id) REFERENCES files(id),
FOREIGN KEY(storage_device_id) REFERENCES storage_devices(id),
FOREIGN KEY(capture_device_id) REFERENCES capture_devices(id)
);
CREATE TABLE IF NOT EXISTS directories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
uri TEXT NOT NULL,
watch BOOLEAN NOT NULL DEFAULT FALSE,
encryption INTEGER DEFAULT 0,
calculated_size_in_bytes TEXT DEFAULT "0",
calculated_file_count INTEGER DEFAULT 0,
date_created DATE NOT NULL DEFAULT (datetime('now')),
date_modified DATE NOT NULL DEFAULT (datetime('now')),
date_indexed DATE NOT NULL DEFAULT (datetime('now')),
library_id INTEGER NOT NULL,
storage_device_id INTEGER,
parent_directory_id INTEGER,
FOREIGN KEY(library_id) REFERENCES libraries(id),
FOREIGN KEY(parent_directory_id) REFERENCES directories(id),
FOREIGN KEY(storage_device_id) REFERENCES storage_devices(id)
);
CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
@ -78,13 +60,6 @@ CREATE TABLE IF NOT EXISTS tags_files (
FOREIGN KEY(tag_id) REFERENCES tags(id),
FOREIGN KEY(file_id) REFERENCES files(id)
);
CREATE TABLE IF NOT EXISTS tags_directories (
tag_id INTEGER NOT NULL,
directory_id INTEGER NOT NULL,
date_created DATE NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY(tag_id) REFERENCES tags(id),
FOREIGN KEY(directory_id) REFERENCES files(id)
);
CREATE TABLE IF NOT EXISTS storage_devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,

View file

@ -1,57 +0,0 @@
use crate::db::entity;
use crate::filesystem;
use anyhow::Result;
use walkdir::{DirEntry, WalkDir};
fn is_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| s.starts_with("."))
.unwrap_or(false)
}
fn is_app_bundle(entry: &DirEntry) -> bool {
let is_dir = entry.metadata().unwrap().is_dir();
let contains_dot = entry
.file_name()
.to_str()
.map(|s| s.contains("."))
.unwrap_or(false);
is_dir && contains_dot
}
pub async fn scan(path: &str) -> Result<()> {
println!("Scanning directory: {}", &path);
// read the scan directory
let file_or_dir = filesystem::reader::path(path, None).await?;
if let Some(dir) = file_or_dir.dir {
let mut current_dir: entity::dir::Model = dir;
for entry in WalkDir::new(path)
.into_iter()
.filter_entry(|e| !is_hidden(e) && !is_app_bundle(e))
{
let entry = entry?;
let path = entry.path().to_str().unwrap();
let child_file_or_dir = filesystem::reader::path(&path, Some(current_dir.id))
.await
.unwrap_or_else(|e| {
println!("could not read path, {}", e);
return filesystem::reader::FileOrDir {
dir: None,
file: None,
};
});
if child_file_or_dir.dir.is_some() {
current_dir = child_file_or_dir.dir.unwrap()
}
println!("{}", entry.path().display());
}
}
Ok(())
}

View file

@ -0,0 +1,127 @@
use crate::commands::DB_INSTANCE;
use crate::db::entity::file;
use crate::filesystem::{checksum, init};
use crate::util::time;
use anyhow::Result;
use sea_orm::entity::*;
use sea_orm::ActiveModelTrait;
use sea_orm::QueryFilter;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::{fs, path};
use walkdir::{DirEntry, WalkDir};
fn is_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| s.starts_with("."))
.unwrap_or(false)
}
fn is_app_bundle(entry: &DirEntry) -> bool {
let is_dir = entry.metadata().unwrap().is_dir();
let contains_dot = entry
.file_name()
.to_str()
.map(|s| s.contains("."))
.unwrap_or(false);
is_dir && contains_dot
}
pub async fn scan(path: &str) -> Result<()> {
println!("Scanning directory: {}", &path);
// read the scan directory
let file = self::path(path, None).await?;
// hashmap to store refrences to directories
let mut dirs: HashMap<String, u32> = HashMap::new();
if file.is_dir {
// 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))
{
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());
// analyse the child file
let child_file = self::path(&path, parent_dir).await.unwrap();
// 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);
}
// println!("{}", entry.path().display());
}
}
Ok(())
}
// 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> {
let db = DB_INSTANCE.get().unwrap();
let path_buff = path::PathBuf::from(path);
let metadata = fs::metadata(&path)?;
let size = metadata.len();
let meta_checksum = checksum::create_meta_checksum(path_buff.to_str().unwrap_or_default(), size)?;
let existing_files = file::Entity::find()
.filter(file::Column::MetaChecksum.contains(&meta_checksum))
.all(&db)
.await?;
if existing_files.len() == 0 {
let primary_library = init::get_primary_library(&db).await?;
let file = file::ActiveModel {
is_dir: Set(metadata.is_dir()),
parent_id: Set(parent_id.map(|x| x.clone())),
meta_checksum: Set(meta_checksum.to_owned()),
name: Set(extract_name(path_buff.file_name())),
extension: Set(extract_name(path_buff.extension())),
uri: Set(path_buff.to_str().unwrap().to_owned()),
library_id: Set(primary_library.id),
size_in_bytes: Set(size.to_string()),
date_created: Set(Some(time::system_time_to_date_time(metadata.created())?)),
date_modified: Set(Some(time::system_time_to_date_time(metadata.modified())?)),
date_indexed: Set(Some(time::system_time_to_date_time(metadata.modified())?)),
..Default::default()
};
let file = file.save(&db).await.unwrap();
println!("FILE: {:?}", file);
// REPLACE WHEN SEA QL PULLS THROUGH
let existing_files = file::Entity::find()
.filter(file::Column::MetaChecksum.contains(&meta_checksum))
.all(&db)
.await?;
let existing_file = existing_files.first().unwrap().clone();
Ok(existing_file)
} else {
let existing_file = existing_files.first().unwrap().clone();
Ok(existing_file)
}
}
// extract name from OsStr returned by PathBuff
fn extract_name(os_string: Option<&OsStr>) -> String {
os_string
.unwrap_or_default()
.to_str()
.unwrap_or_default()
.to_owned()
}

View file

@ -1,4 +1,4 @@
pub mod checksum;
pub mod explorer;
pub mod indexer;
pub mod init;
pub mod reader;
pub mod retrieve;

View file

@ -1,144 +0,0 @@
use crate::commands::DB_INSTANCE;
use crate::db::entity::dir;
use crate::db::entity::file;
use crate::filesystem::{checksum, init};
use crate::util::time;
use anyhow::{anyhow, Result};
use sea_orm::entity::*;
use sea_orm::QueryFilter;
use std::ffi::OsStr;
use std::{fs, path};
pub struct FileOrDir {
pub dir: Option<dir::Model>,
pub file: Option<file::Model>,
}
// reads the metadata associated with the file or directory
// found at the supplied path and processes accordingly
pub async fn path(path: &str, dir_id: Option<u32>) -> Result<FileOrDir> {
let path_buff = path::PathBuf::from(path);
let metadata = fs::metadata(&path)?;
if metadata.is_dir() {
Ok(FileOrDir {
dir: Some(read_dir(path_buff, metadata, &dir_id).await?),
file: None,
})
} else {
Ok(FileOrDir {
dir: None,
file: Some(read_file(path_buff, metadata, &dir_id).await?),
})
}
}
// Read a file from path returning the File struct
// Generates meta checksum and extracts metadata
pub async fn read_file(
path_buff: path::PathBuf,
metadata: fs::Metadata,
dir_id: &Option<u32>,
) -> Result<file::Model> {
let db = DB_INSTANCE.get().unwrap();
let size = metadata.len();
let meta_checksum = checksum::create_meta_checksum(path_buff.to_str().unwrap_or_default(), size)?;
let existing_files = file::Entity::find()
.filter(file::Column::MetaChecksum.contains(&meta_checksum))
.all(&db)
.await?;
if existing_files.len() == 0 {
let primary_library = init::get_primary_library(&db).await?;
let file = file::ActiveModel {
meta_checksum: Set(meta_checksum.to_owned()),
directory_id: Set(*dir_id),
name: Set(extract_name(path_buff.file_name())),
extension: Set(extract_name(path_buff.extension())),
uri: Set(path_buff.to_str().unwrap().to_owned()),
library_id: Set(primary_library.id),
size_in_bytes: Set(size.to_string()),
date_created: Set(Some(time::system_time_to_date_time(metadata.created())?)),
date_modified: Set(Some(time::system_time_to_date_time(metadata.modified())?)),
date_indexed: Set(Some(time::system_time_to_date_time(metadata.modified())?)),
..Default::default()
};
let file = file.save(&db).await?;
println!("FILE: {:?}", file);
// REPLACE WHEN SEA QL PULLS THROUGH
let existing_files = file::Entity::find()
.filter(file::Column::MetaChecksum.contains(&meta_checksum))
.all(&db)
.await?;
let existing_file = existing_files.first().unwrap().clone();
Ok(existing_file)
} else {
let existing_file = existing_files.first().unwrap().clone();
Ok(existing_file)
}
}
pub async fn read_dir(
path_buff: path::PathBuf,
metadata: fs::Metadata,
dir_id: &Option<u32>,
) -> Result<dir::Model> {
let db = DB_INSTANCE.get().unwrap();
let path_str = path_buff.to_str().unwrap();
let file_name = path_buff.file_name().unwrap().to_str().unwrap().to_owned();
if file_name.contains(".") {
return Err(anyhow!("Directory is bundle, do not index"));
}
let existing_dirs = dir::Entity::find()
.filter(dir::Column::Uri.contains(&path_str))
.all(&db)
.await?;
if existing_dirs.is_empty() {
let primary_library = init::get_primary_library(&db).await?;
let directory = dir::ActiveModel {
name: Set(path_buff.file_name().unwrap().to_str().unwrap().to_owned()),
uri: Set(path_str.to_owned()),
watch: Set(false),
parent_directory_id: Set(*dir_id),
library_id: Set(primary_library.id),
date_created: Set(Some(time::system_time_to_date_time(metadata.created())?)),
date_modified: Set(Some(time::system_time_to_date_time(metadata.modified())?)),
date_indexed: Set(Some(time::system_time_to_date_time(metadata.modified())?)),
..Default::default()
};
let directory = directory.save(&db).await?;
println!("DIR: {:?}", &directory);
let existing_dirs = dir::Entity::find()
.filter(dir::Column::Uri.contains(&path_str))
.all(&db)
.await?;
let existing_dir = existing_dirs.first().unwrap().clone();
Ok(existing_dir)
} else {
let existing_dir = existing_dirs.first().unwrap().clone();
Ok(existing_dir)
}
}
// extract name from OsStr returned by PathBuff
fn extract_name(os_string: Option<&OsStr>) -> String {
os_string
.unwrap_or_default()
.to_str()
.unwrap_or_default()
.to_owned()
}

View file

@ -0,0 +1,8 @@
use crate::db::entity::file;
use serde::Serialize;
#[derive(Serialize)]
pub struct Directory {
pub directory: file::Model,
pub contents: Vec<file::Model>,
}

View file

@ -10,17 +10,17 @@ mod device;
mod filesystem;
mod util;
use crate::app::menu;
use env_logger;
use futures::executor::block_on;
// use systemstat::{saturating_sub_bytes, Platform, System};
fn main() {
let mounts = device::volumes_c::get_mounts();
println!("mounted drives: {:?}", &mounts);
// env_logger::builder()
// .filter_level(log::LevelFilter::Debug)
// .is_test(true)
// .init();
// let mounts = device::volumes_c::get_mounts();
// println!("mounted drives: {:?}", &mounts);
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();

View file

@ -13,7 +13,7 @@ export default function App() {
<TopBar />
<div className="flex flex-row min-h-full">
<Sidebar />
<div className="w-full flex">
<div className="w-full flex bg-gray-50 dark:bg-gray-800">
<Switch>
<Route path="/settings">
<SettingsScreen />

View file

@ -1,7 +1,12 @@
import { DocumentIcon, DotsVerticalIcon, FilmIcon, MusicNoteIcon } from '@heroicons/react/solid';
import { DocumentIcon, DotsVerticalIcon, FilmIcon, FolderIcon } from '@heroicons/react/solid';
import clsx from 'clsx';
import React, { useMemo, useState } from 'react';
import { FileData } from '../../types';
import React, { useCallback, useMemo, useState } from 'react';
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 { DirectoryResponse } from '../../screens/Explorer';
interface Column {
column: string;
@ -9,18 +14,6 @@ interface Column {
width: number;
}
interface FileListData {
id: number;
type?: string;
tags?: Tag[];
[key: string]: any;
}
interface Tag {
name: string;
color: string;
}
// Function ensure no types are loss, but guarantees that they are Column[]
function ensureIsColumns<T extends Column[]>(data: T) {
return data;
@ -35,38 +28,120 @@ const columns = ensureIsColumns([
type ColumnKey = typeof columns[number]['key'];
// const data: FileListData[] = [
// {
// id: 1,
// name: 'MyNameJeff.mp4',
// type: 'video',
// size: '5GB'
// // tags: [{ name: 'Keepsafe', color: '#3076E6' }]
// },
// { id: 2, name: 'cow.ogg', type: 'audio', size: '345KB' },
// { id: 3, name: 'calvin.fig', size: '34MB' },
// { id: 4, name: 'calvin.fig' },
// { id: 5, name: 'calvin.fig' },
// { id: 6, name: 'calvin.fig' }
// ];
export const FileList: React.FC<{}> = (props) => {
// 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 RenderCell = ({ colKey, row }: { colKey?: ColumnKey; row?: FileData }) => {
if (!row || !colKey || !row[colKey]) return <></>;
useKey('ArrowUp', (e) => {
e.preventDefault();
if (selectedRow > 1) setSelectedRow(selectedRow - 1);
else setSelectedRow(currentDir.children_count);
});
useKey('ArrowDown', (e) => {
e.preventDefault();
if (selectedRow < currentDir.children_count) setSelectedRow(selectedRow + 1);
else setSelectedRow(0);
});
const renderCellWithIcon = (Icon: any) => {
return (
<div className="flex flex-row items-center">
{colKey == 'name' && <Icon className="w-5 h-5 mr-3 flex-shrink-0" />}
<span className="truncate">{row[colKey]}</span>
return useMemo(
() => (
<div className="table-container w-full h-full overflow-scroll bg-white dark:bg-gray-900 p-3 ">
<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">
{currentDir?.children?.map((row, index) => (
<RenderRow key={row.id} row={row} rowIndex={index} />
))}
</div>
</div>
);
};
),
[activeDirHash]
);
};
const RenderRow: React.FC<{ row: IFile; rowIndex: number }> = ({ row, rowIndex }) => {
const [collectDir, selectedRow, setSelectedRow] = useExplorerStore((state) => [
state.collectDir,
state.selected,
state.setSelected
]);
const isActive = selectedRow === row.id;
const isAlternate = rowIndex % 2 == 0;
return useMemo(
() => (
<div
onClick={() => setSelectedRow(row.id as number)}
onDoubleClick={() => {
if (row.is_dir) {
invoke<DirectoryResponse>('get_files', { path: row.uri }).then((res) => {
collectDir(res.directory, res.contents);
});
}
}}
className={clsx('table-body-row flex flex-row rounded-lg border-2 border-[#00000000]', {
'bg-[#00000006] dark:bg-[#00000030]': isAlternate,
'border-primary-500': isActive
})}
>
{columns.map((col) => (
<div key={col.key} className="table-body-cell px-4 py-2" style={{ width: col.width }}>
<RenderCell row={row} colKey={col?.key} />
</div>
))}
</div>
),
[isActive]
);
};
const RenderCell: React.FC<{ colKey?: ColumnKey; row?: IFile }> = ({ colKey, row }) => {
if (!row || !colKey || !row[colKey]) return <></>;
const value = row[colKey];
switch (colKey) {
case 'name':
return renderCellWithIcon(FilmIcon);
return (
<div className="flex flex-row items-center">
{colKey == 'name' &&
(() => {
switch (row.extension.toLowerCase()) {
case 'mov' || 'mp4':
return <FilmIcon className="w-5 h-5 mr-3 flex-shrink-0" />;
default:
if (row.is_dir) return <FolderIcon className="w-5 h-5 mr-3 flex-shrink-0" />;
return <DocumentIcon className="w-5 h-5 mr-3 flex-shrink-0" />;
}
})()}
<span className="truncate">{row[colKey]}</span>
</div>
);
case 'size_in_bytes':
return renderCellWithIcon(MusicNoteIcon);
return <span>{byteSize(Number(value || 0))}</span>;
case 'meta_checksum':
return <span className="truncate">{value}</span>;
// case 'tags':
// return renderCellWithIcon(MusicNoteIcon);
@ -74,53 +149,3 @@ const RenderCell = ({ colKey, row }: { colKey?: ColumnKey; row?: FileData }) =>
return <></>;
}
};
export const FileList: React.FC<{ files: FileData[] }> = (props) => {
const [selectedRow, setSelectedRow] = useState(0);
return (
<div className="table-container w-full h-full overflow-scroll bg-gray-900 rounded-md p-2 shadow-md">
<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="">{col.column}</span>
</div>
))}
</div>
</div>
<div className="table-body">
{props.files?.map((row, index) => (
<div
key={row.id}
onClick={() => setSelectedRow(row.id as number)}
className={clsx('table-body-row flex flex-row rounded-lg border-2 border-[#00000000]', {
'bg-[#00000030]': index % 2 == 0,
'border-primary-500': selectedRow === row.id
})}
>
{columns.map((col) => (
<div key={col.key} className="table-body-cell px-4 py-2" style={{ width: col.width }}>
{useMemo(
() => (
<RenderCell row={row} colKey={col?.key} />
),
[row, col?.key]
)}
</div>
))}
</div>
))}
</div>
</div>
);
};
// const columnKeyMap = columns.reduce((obj, column, index) => {
// obj[column.key] = index;
// return obj;
// }, {} as Record<string, number>);

View file

@ -1,67 +0,0 @@
import clsx from 'clsx';
import React, { useMemo, useState } from 'react';
import { useTable } from 'react-table';
const columns = [
{ Header: 'Name', accessor: 'name' },
{ Header: 'Size', accessor: 'size' }
];
export const dummyFileData = [
{ id: 1, name: 'MyNameJeff.mp4', type: 'video', size: '5GB' },
{ id: 2, name: 'cow.ogg', type: 'audio', size: '345KB' },
{ id: 3, name: 'calvin.fig' },
{ id: 4, name: 'calvin.fig' },
{ id: 5, name: 'calvin.fig' },
{ id: 6, name: 'calvin.fig' }
];
export const FileListTable: React.FC<{}> = (props) => {
// @ts-expect-error
const tableInstance = useTable({ columns, data: dummyFileData });
const [selectedRow, setSelectedRow] = useState(0);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = tableInstance;
return (
<div className="w-full bg-gray-900 p-2 rounded-lg">
<table className="w-full rounded-lg shadow-lg border-collapse" {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th
className="px-3 pt-2 text-left text-gray-500 text-sm"
{...column.getHeaderProps()}
>
{column.render('Header')}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
return (
<tr
onClick={() => setSelectedRow(row.original.id || 0)}
className={clsx('even:bg-[#00000040] border-2 border-opacity-0 rounded-sm', {
'border-2 border-opacity-100 border-primary z-50': selectedRow === row.original.id
})}
{...row.getRowProps()}
>
{row.cells.map((cell) => {
return (
<td className={clsx('py-2 px-4 rounded-sm')} {...cell.getCellProps()}>
{cell.render('Cell')}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
);
};

View file

@ -1,12 +0,0 @@
import React from 'react';
import { DefaultProps } from '../primative/types';
interface FileRowProps extends DefaultProps {}
export const FileRow: React.FC<FileRowProps> = (props) => {
return (
<div className="max-w py-2 px-4 rounded-md bg-gray-50 dark:bg-gray-800">
<span className="text-white text-sm">Filename.mp4</span>
</div>
);
};

View file

@ -37,8 +37,9 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
{tabs.map((button, index) => (
<NavLink
className="max-w rounded px-2 py-1 flex flex-row items-center hover:bg-gray-700 text-sm"
activeClassName="bg-gray-500 hover:bg-gray-500"
key={index}
className="max-w rounded px-2 py-1 flex flex-row items-center hover:bg-gray-200 dark:hover:bg-gray-700 text-sm"
activeClassName="bg-gray-200 hover:bg-gray-200 dark:bg-gray-500 dark:hover:bg-gray-500"
to={button.uri}
>
{button.icon && <button.icon className="w-4 h-4 mr-2" />}

View file

@ -21,9 +21,9 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
<>
<div
data-tauri-drag-region
className="flex flex-shrink-0 h-10 max-w items-center bg-gray-50 dark:bg-gray-900 border-gray-100 dark:border-gray-900 shadow-sm dark:border-t"
className="flex flex-shrink-0 h-10 max-w items-center bg-gray-100 dark:bg-gray-800 border-gray-100 dark:border-gray-900 shadow-sm "
>
<div className="w-52 ml-1" data-tauri-drag-region>
<div className="mr-32 ml-1 ">
<TrafficLights className="p-1.5" />
</div>
<Button noBorder noPadding className="rounded-r-none mr-[1px]">
@ -33,14 +33,14 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
<ChevronRightIcon className="m-0.5 w-4 h-4 dark:text-white" />
</Button>
<div className="w-4"></div>
<Button noBorder noPadding className="rounded-r-none mr-[1px]">
<ViewGridIcon className="m-0.5 w-4 h-4 dark:text-white" />
<Button variant="selected" noBorder noPadding className="rounded-r-none mr-[1px]">
<ViewListIcon className="m-0.5 w-4 h-4 dark:text-white" />
</Button>
<Button noBorder noPadding className="rounded-none mr-[1px]">
<ViewBoardsIcon className="m-0.5 w-4 h-4 dark:text-white" />
</Button>
<Button noBorder noPadding className="rounded-l-none">
<ViewListIcon className="m-0.5 w-4 h-4 dark:text-white" />
<ViewGridIcon className="m-0.5 w-4 h-4 dark:text-white" />
</Button>
<div className="w-4"></div>
<div className="relative flex h-7">
@ -55,10 +55,10 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
</div>
<div className="flex-grow"></div>
<Button noBorder noPadding className="mr-2">
<CogIcon className="m-0.5 w-4 h-4 text-white" />
<CogIcon className="m-0.5 w-4 h-4 dark:text-white" />
</Button>
</div>
<div className="h-[1px] flex-shrink-0 max-w bg-gray-100 dark:bg-gray-700" />
<div className="h-[1px] flex-shrink-0 max-w bg-gray-200 dark:bg-gray-700" />
</>
);
};

View file

@ -1,6 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://localhost:8097"></script>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

View file

@ -2,22 +2,32 @@ import React, { useEffect, useState } from 'react';
import { FileList } from '../components/file/FileList';
import { emit, listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api';
import { FileData } from '../types';
import { IFile } from '../types';
import { useExplorerStore } from '../store/explorer';
export interface DirectoryResponse {
directory: IFile;
contents: IFile[];
}
export const ExplorerScreen: React.FC<{}> = () => {
const [files, setFiles] = useState<FileData[] | null>(null);
const [activeDirHash, collectDir] = useExplorerStore((state) => [
state.activeDirHash,
state.collectDir
]);
useEffect(() => {
invoke('get_files').then((res) => {
setFiles(res as FileData[]);
invoke<DirectoryResponse>('get_files', { path: '/Users/jamie/Downloads' }).then((res) => {
console.log({ res });
collectDir(res.directory, res.contents);
});
}, []);
console.log({ files });
if (!files) return <></>;
if (!activeDirHash) return <></>;
return (
<div className="w-full m-3">
<FileList files={files} />
<div className="w-full">
<FileList />
</div>
);
};

View file

@ -1,7 +1,7 @@
import { DuplicateIcon, PencilAltIcon, TrashIcon } from '@heroicons/react/solid';
import { invoke } from '@tauri-apps/api';
import React, { useRef } from 'react';
// import { dummyFileData, FileList } from '../components/file/FileList';
// import { dummyIFile, FileList } from '../components/file/FileList';
import { Input, Toggle } from '../components/primative';
import { Button } from '../components/primative/Button';
import { Checkbox } from '../components/primative/Checkbox';
@ -17,7 +17,7 @@ export const SettingsScreen: React.FC<{}> = () => {
return (
<div>
<div className="p-3">
{/* <FileList files={dummyFileData} /> */}
{/* <FileList files={dummyIFile} /> */}
<div className="flex space-x-2 mt-4">
<InputContainer
title="Quick scan directory"

49
src/store/explorer.ts Normal file
View file

@ -0,0 +1,49 @@
import create from 'zustand';
import { IDirectory, IFile } from '../types';
import produce from 'immer';
interface ExplorerStore {
dirs: Record<string, IDirectory>;
activeDirHash: string;
history: string[];
selected: number;
collectDir: (dirHash: IFile, files: IFile[]) => void;
currentDir?: () => IFile[];
setSelected: (id: number) => void;
}
export const useExplorerStore = create<ExplorerStore>((set, get) => ({
dirs: {},
activeDirHash: '',
history: [],
selected: 0,
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];
})
);
}
},
setSelected: (id) =>
set((state) =>
produce(state, (draft) => {
draft.selected = id;
})
)
}));

11
src/store/index.ts Normal file
View file

@ -0,0 +1,11 @@
import create from 'zustand';
interface AppState {
bears: number;
}
const useAppStateStore = create<AppState>((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 })
}));

View file

@ -1,5 +0,0 @@
export interface ClientDevice {
id: string;
}
export interface StorageDevice {}
export interface CaptureDevice {}

View file

@ -1,10 +1,11 @@
import { Encryption } from './library';
import { ImageMeta, VideoMeta } from './media';
export interface FileData {
export interface IFile {
id?: number;
meta_checksum: string;
uri: string;
is_dir: string;
date_created: Date;
date_modified: Date;
@ -15,38 +16,18 @@ export interface FileData {
size_in_bytes: string;
library_id: string;
directory_id: string;
ipfs_id: string;
storage_device_id: string;
capture_device_id: string;
parent_file_id: string;
parent_id: string;
tags?: ITag[];
}
export interface Directory {
id: string;
name: string;
calculated_size: string;
calculated_object_count: number;
storage_device_id: string;
parent_directory_id: string;
user_id: string;
date_created: Date;
date_modified: Date;
date_indexed: Date;
export interface IDirectory extends IFile {
children?: IFile[];
children_count: number;
}
export interface Tag {
export interface ITag {
id: string;
}
export interface TagObject {
object_id: string;
tag_id: string;
}
export enum ObjectType {
FILE,
LINK
}

View file

@ -40,6 +40,7 @@ module.exports = {
600: '#434656',
700: '#353845',
800: '#282A34',
850: '#21212B',
900: '#1B1C23'
}
}

264
yarn.lock
View file

@ -62,22 +62,6 @@
jsesc "^2.5.1"
source-map "^0.5.0"
"@babel/generator@^7.15.4":
version "7.15.8"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.8.tgz#fa56be6b596952ceb231048cf84ee499a19c0cd1"
integrity sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==
dependencies:
"@babel/types" "^7.15.6"
jsesc "^2.5.1"
source-map "^0.5.0"
"@babel/helper-annotate-as-pure@^7.0.0":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz#3d0e43b00c5e49fdb6c57e421601a7a658d5f835"
integrity sha512-QwrtdNvUNsPCj2lfNQacsGSQvGX8ee1ttrBrcozUP2Sv/jylewBP/8QFe6ZkBsC8T/GYWonNAWJV4aRR9AL2DA==
dependencies:
"@babel/types" "^7.15.4"
"@babel/helper-compilation-targets@^7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz#973df8cbd025515f3ff25db0c05efc704fa79818"
@ -97,15 +81,6 @@
"@babel/template" "^7.14.5"
"@babel/types" "^7.14.5"
"@babel/helper-function-name@^7.15.4":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz#845744dafc4381a4a5fb6afa6c3d36f98a787ebc"
integrity sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==
dependencies:
"@babel/helper-get-function-arity" "^7.15.4"
"@babel/template" "^7.15.4"
"@babel/types" "^7.15.4"
"@babel/helper-get-function-arity@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz#25fbfa579b0937eee1f3b805ece4ce398c431815"
@ -113,13 +88,6 @@
dependencies:
"@babel/types" "^7.14.5"
"@babel/helper-get-function-arity@^7.15.4":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz#098818934a137fce78b536a3e015864be1e2879b"
integrity sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==
dependencies:
"@babel/types" "^7.15.4"
"@babel/helper-hoist-variables@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz#e0dd27c33a78e577d7c8884916a3e7ef1f7c7f8d"
@ -127,13 +95,6 @@
dependencies:
"@babel/types" "^7.14.5"
"@babel/helper-hoist-variables@^7.15.4":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz#09993a3259c0e918f99d104261dfdfc033f178df"
integrity sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==
dependencies:
"@babel/types" "^7.15.4"
"@babel/helper-member-expression-to-functions@^7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz#0ddaf5299c8179f27f37327936553e9bba60990b"
@ -141,13 +102,6 @@
dependencies:
"@babel/types" "^7.15.0"
"@babel/helper-module-imports@^7.0.0":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz#e18007d230632dea19b47853b984476e7b4e103f"
integrity sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==
dependencies:
"@babel/types" "^7.15.4"
"@babel/helper-module-imports@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz#6d1a44df6a38c957aa7c312da076429f11b422f3"
@ -205,13 +159,6 @@
dependencies:
"@babel/types" "^7.14.5"
"@babel/helper-split-export-declaration@^7.15.4":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz#aecab92dcdbef6a10aa3b62ab204b085f776e257"
integrity sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==
dependencies:
"@babel/types" "^7.15.4"
"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9":
version "7.14.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48"
@ -245,11 +192,6 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862"
integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==
"@babel/parser@^7.15.4":
version "7.15.8"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.8.tgz#7bacdcbe71bdc3ff936d510c15dcea7cf0b99016"
integrity sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==
"@babel/plugin-transform-react-jsx-self@^7.14.5":
version "7.14.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.14.9.tgz#33041e665453391eb6ee54a2ecf3ba1d46bd30f4"
@ -280,15 +222,6 @@
"@babel/parser" "^7.14.5"
"@babel/types" "^7.14.5"
"@babel/template@^7.15.4":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194"
integrity sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==
dependencies:
"@babel/code-frame" "^7.14.5"
"@babel/parser" "^7.15.4"
"@babel/types" "^7.15.4"
"@babel/traverse@^7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.0.tgz#4cca838fd1b2a03283c1f38e141f639d60b3fc98"
@ -304,21 +237,6 @@
debug "^4.1.0"
globals "^11.1.0"
"@babel/traverse@^7.4.5":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.4.tgz#ff8510367a144bfbff552d9e18e28f3e2889c22d"
integrity sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==
dependencies:
"@babel/code-frame" "^7.14.5"
"@babel/generator" "^7.15.4"
"@babel/helper-function-name" "^7.15.4"
"@babel/helper-hoist-variables" "^7.15.4"
"@babel/helper-split-export-declaration" "^7.15.4"
"@babel/parser" "^7.15.4"
"@babel/types" "^7.15.4"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.14.5", "@babel/types@^7.14.8", "@babel/types@^7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd"
@ -327,41 +245,11 @@
"@babel/helper-validator-identifier" "^7.14.9"
to-fast-properties "^2.0.0"
"@babel/types@^7.15.4", "@babel/types@^7.15.6":
version "7.15.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.6.tgz#99abdc48218b2881c058dd0a7ab05b99c9be758f"
integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==
dependencies:
"@babel/helper-validator-identifier" "^7.14.9"
to-fast-properties "^2.0.0"
"@cush/relative@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@cush/relative/-/relative-1.0.0.tgz#8cd1769bf9bde3bb27dac356b1bc94af40f6cc16"
integrity sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==
"@emotion/is-prop-valid@^0.8.8":
version "0.8.8"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
dependencies:
"@emotion/memoize" "0.7.4"
"@emotion/memoize@0.7.4":
version "0.7.4"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
"@emotion/stylis@^0.8.4":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
"@emotion/unitless@^0.7.4":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
"@graphql-typed-document-node/core@^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950"
@ -524,6 +412,11 @@
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"
@ -539,14 +432,6 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724"
integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
"@types/hoist-non-react-statics@*":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@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"
@ -574,6 +459,13 @@
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"
@ -631,15 +523,6 @@
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
"@types/styled-components@^5.1.14":
version "5.1.14"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.14.tgz#e9cf8cdb5eef9d139628183a84c083f630635d67"
integrity sha512-d6P1/tyNytqKwam3cQXq7a9uPtovc/mdAs7dBiz1YbDdNIT3X4WmuFU78YdSYh84TXVuhOwezZ3EeKuNBhwsHQ==
dependencies:
"@types/hoist-non-react-statics" "*"
"@types/react" "*"
csstype "^3.0.2"
"@types/tailwindcss@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@types/tailwindcss/-/tailwindcss-2.2.1.tgz#891349cc71b5a85208ca5796938d91dd48cc0417"
@ -820,21 +703,6 @@ autoprefixer@^9:
postcss "^7.0.32"
postcss-value-parser "^4.1.0"
"babel-plugin-styled-components@>= 1.12.0":
version "1.13.2"
resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.2.tgz#ebe0e6deff51d7f93fceda1819e9b96aeb88278d"
integrity sha512-Vb1R3d4g+MUfPQPVDMCGjm3cDocJEUTR7Xq7QS95JWWeksN1wdFRYpD2kulDgI3Huuaf1CZd+NK4KQmqUFh5dA==
dependencies:
"@babel/helper-annotate-as-pure" "^7.0.0"
"@babel/helper-module-imports" "^7.0.0"
babel-plugin-syntax-jsx "^6.18.0"
lodash "^4.17.11"
babel-plugin-syntax-jsx@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@ -986,6 +854,11 @@ buffer@^5.2.1, buffer@^5.5.0:
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"
@ -1063,11 +936,6 @@ camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
camelize@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001248:
version "1.0.30001251"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz#6853a606ec50893115db660f82c094d18f096d85"
@ -1386,25 +1254,11 @@ crypto-random-string@^2.0.0:
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=
css-color-names@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=
css-to-react-native@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756"
integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==
dependencies:
camelize "^1.0.0"
css-color-keywords "^1.0.0"
postcss-value-parser "^4.0.2"
css-unit-converter@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21"
@ -2195,7 +2049,7 @@ history@^4.9.0:
tiny-warning "^1.0.0"
value-equal "^1.0.1"
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -2287,6 +2141,11 @@ imagemin@8.0.0:
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"
@ -2672,12 +2531,17 @@ load-json-file@^1.0.0:
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"
integrity sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=
lodash@^4.17.11, lodash@^4.17.21:
lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -2856,16 +2720,6 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
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"
@ -3244,6 +3098,11 @@ pend@~1.2.0:
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"
@ -3328,7 +3187,7 @@ postcss-value-parser@^3.3.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
@ -3394,6 +3253,11 @@ prettier@^2.3.2:
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"
@ -3467,6 +3331,13 @@ quick-lru@^5.1.1:
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"
@ -3534,11 +3405,6 @@ react-router@5.2.0:
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-table@^7.7.0:
version "7.7.0"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.7.0.tgz#e2ce14d7fe3a559f7444e9ecfe8231ea8373f912"
integrity sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==
react@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
@ -3764,6 +3630,14 @@ rollup@^2.38.5:
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"
@ -3854,11 +3728,6 @@ set-blocking@~2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
shallowequal@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
sharp@0.28.3:
version "0.28.3"
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.28.3.tgz#ecd74cefd020bee4891bb137c9850ee2ce277a8b"
@ -4137,28 +4006,12 @@ strtok3@^6.0.3:
"@tokenizer/token" "^0.3.0"
peek-readable "^4.0.1"
styled-components@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.1.tgz#8a86dcd31bff7049c2ed408bae36fa23f03f071a"
integrity sha512-JThv2JRzyH0NOIURrk9iskdxMSAAtCfj/b2Sf1WJaCUsloQkblepy1jaCLX/bYE+mhYo3unmwVSI9I5d9ncSiQ==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/traverse" "^7.4.5"
"@emotion/is-prop-valid" "^0.8.8"
"@emotion/stylis" "^0.8.4"
"@emotion/unitless" "^0.7.4"
babel-plugin-styled-components ">= 1.12.0"
css-to-react-native "^3.0.0"
hoist-non-react-statics "^3.0.0"
shallowequal "^1.1.0"
supports-color "^5.5.0"
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
supports-color@^5.3.0, supports-color@^5.5.0:
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@ -4666,3 +4519,8 @@ zopflipng-bin@^6.0.0:
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==