Merge branch 'spacedriveapp:main' into navbar-transition

This commit is contained in:
matt
2022-08-25 20:46:53 -05:00
committed by GitHub
71 changed files with 381 additions and 303 deletions

View File

@@ -1,20 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
project: [
'apps/desktop/tsconfig.json',
'apps/web/tsconfig.json',
'apps/landing/tsconfig.json',
'apps/mobile/tsconfig.json',
'packages/client/tsconfig.json',
'packages/interface/tsconfig.json',
'packages/ui/tsconfig.json'
]
},
plugins: ['@typescript-eslint'],
extends: ['standard-with-typescript', 'prettier'],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off'
}
};

View File

@@ -48,10 +48,6 @@ jobs:
working-directory: core
if: steps.cache-prisma.outputs.cache-hit != 'true'
run: cargo run -p prisma-cli --release -- generate
# This is do the proc-macro `tauri::generate_context!()` doesn't panic
- name: Create fake `dist` folder
run: mkdir ./apps/desktop/dist
- name: Run Clippy
uses: actions-rs/clippy-check@v1

4
.gitignore vendored
View File

@@ -1,6 +1,7 @@
node_modules
.next
dist
!apps/desktop/dist
*.tsbuildinfo
package-lock.json
.eslintcache
@@ -15,7 +16,6 @@ storybook-static/
cache
.env
vendor/
dist
data
node_modules
packages/turbo-server/data/
@@ -61,4 +61,4 @@ todos.md
examples/*/*.lock
/target
/sdserver_data
/sdserver_data

BIN
Cargo.lock generated
View File

Binary file not shown.

View File

@@ -1,8 +1,9 @@
[workspace]
members = [
"apps/desktop/src-tauri",
"core",
"core/prisma",
"core/derive",
"apps/server"
"apps/desktop/src-tauri",
"core",
"core/prisma",
"core/derive",
"apps/server",
]
resolver = "2"

5
apps/desktop/dist/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
# This is done so that Tauri never complains that '../dist does not exist'

View File

@@ -29,7 +29,7 @@ function App() {
}
}
const [platform, setPlatform] = useState<Platform>('macOS');
const [platform, setPlatform] = useState<Platform>('unknown');
const [focused, setFocused] = useState(true);
useEffect(() => {

View File

@@ -0,0 +1,8 @@
module.exports = {
...require('@sd/config/eslint-react.js'),
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
},
ignorePatterns: ['**/*.js', '**/*.json', 'node_modules', 'public', 'dist']
};

View File

@@ -6,7 +6,8 @@
"vercel-build": "./vercel/deploy.sh",
"build": "vite build && vite build --ssr && vite-plugin-ssr prerender",
"server": "ts-node ./server",
"server:prod": "cross-env NODE_ENV=production ts-node ./server"
"server:prod": "cross-env NODE_ENV=production ts-node ./server",
"lint": "eslint src/**/*.{ts,tsx} && tsc --noEmit"
},
"dependencies": {
"@heroicons/react": "^1.0.6",
@@ -39,6 +40,7 @@
"vite-plugin-ssr": "^0.4.15"
},
"devDependencies": {
"@sd/config": "link:../../packages/config",
"@tailwindcss/line-clamp": "^0.4.0",
"@tailwindcss/typography": "^0.5.4",
"@types/prismjs": "^1.26.0",

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import clsx from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
@@ -87,9 +88,7 @@ const AppEmbed = () => {
/>
)}
{renderImage && (
<div className="z-40 h-full sm:w-auto fade-in-app-embed landing-img" />
)}
{renderImage && <div className="z-40 h-full sm:w-auto fade-in-app-embed landing-img" />}
</div>
</div>
</div>

View File

@@ -16,6 +16,7 @@ function FooterLink(props: { children: string | JSX.Element; link: string; blank
href={props.link}
target={props.blank ? '_blank' : ''}
className="text-gray-300 hover:text-white"
rel="noreferrer"
>
{props.children}
</a>

View File

@@ -15,6 +15,7 @@ function NavLink(props: { link?: string; children: string }) {
href={props.link ?? '#'}
target={props.link?.startsWith('http') ? '_blank' : undefined}
className="p-4 text-gray-300 no-underline transition cursor-pointer hover:text-gray-50"
rel="noreferrer"
>
{props.children}
</a>
@@ -54,6 +55,7 @@ export default function NavBar() {
setTimeout(onScroll, 0);
getWindow()?.addEventListener('scroll', onScroll);
return () => getWindow()?.removeEventListener('scroll', onScroll);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
@@ -136,10 +138,10 @@ export default function NavBar() {
buttonProps={{ className: '!p-1 ml-[140px]' }}
/>
<div className="absolute flex-row hidden space-x-5 right-3 lg:flex">
<a href="https://discord.gg/gTaF2Z44f5" target="_blank">
<a href="https://discord.gg/gTaF2Z44f5" target="_blank" rel="noreferrer">
<Discord className="text-white" />
</a>
<a href="https://github.com/spacedriveapp/spacedrive" target="_blank">
<a href="https://github.com/spacedriveapp/spacedrive" target="_blank" rel="noreferrer">
<Github className="text-white" />
</a>
</div>

View File

@@ -36,7 +36,7 @@ function Link(props: LinkProps) {
<a
className="duration-300 hover:scale-105 hover:opacity-80"
href={props.href}
rel="noreferer"
rel="noreferrer"
target="_blank"
>
{props.children}

View File

@@ -7,35 +7,35 @@ const ghostURL = import.meta.env.VITE_API_URL;
export const blogEnabled = !!(ghostURL && ghostKey);
export const api = blogEnabled
? new GhostContentAPI({
url: ghostURL,
key: ghostKey,
version: 'v4'
})
: null;
? new GhostContentAPI({
url: ghostURL,
key: ghostKey,
version: 'v5.0'
})
: null;
export async function getPosts() {
if (!api) {
return [];
}
const posts = await api.posts
.browse({
include: ['tags', 'authors']
})
.catch(() => []);
return posts;
if (!api) {
return [];
}
const posts = await api.posts
.browse({
include: ['tags', 'authors']
})
.catch(() => []);
return posts;
}
export async function getPost(slug: string) {
if (!api) {
return null;
}
return await api.posts
.read(
{ slug },
{
include: ['tags', 'authors']
}
)
.catch(() => null);
if (!api) {
return null;
}
return await api.posts
.read(
{ slug },
{
include: ['tags', 'authors']
}
)
.catch(() => null);
}

View File

@@ -1,22 +1,27 @@
import { getPosts } from './api';
import { blogEnabled, getPosts } from './api';
export async function onBeforeRender() {
const posts = await getPosts();
const posts = await getPosts();
return {
pageContext: {
pageProps: {
posts
}
}
};
return {
pageContext: {
pageProps: {
posts
}
}
};
}
export async function prerender() {
const posts = await getPosts();
const posts = await getPosts();
return {
url: '/blog',
pageContext: { pageProps: { posts } }
};
const individualPosts = posts.map((post) => ({
url: `/blog/${post.slug}`,
pageContext: { pageProps: { post } }
}));
return [...individualPosts, {
url: '/blog',
pageContext: { pageProps: { posts } }
}];
}

View File

@@ -8,7 +8,7 @@ import { blogEnabled } from './api';
function Page({ posts }: { posts: PostOrPage[] }) {
if (!blogEnabled) {
let window = getWindow();
const window = getWindow();
if (!window) return;
window.location.href = '/blog-not-enabled';
return <></>;
@@ -28,6 +28,7 @@ function Page({ posts }: { posts: PostOrPage[] }) {
{posts.map((post) => {
return (
<div
key={post.id}
onClick={() => {
window.location.href = `/blog/${post.slug}`;
}}
@@ -49,9 +50,9 @@ function Page({ posts }: { posts: PostOrPage[] }) {
{new Date(post.published_at ?? '').toLocaleDateString()}
</p>
<div className="flex flex-wrap gap-2 mt-4">
{post.tags?.map((tag: Tag) => {
return <BlogTag tag={tag} />;
})}
{post.tags?.map((tag: Tag) => (
<BlogTag key={tag.id} tag={tag} />
))}
</div>
</div>
</div>

View File

@@ -3,22 +3,13 @@ import { PageContextBuiltIn } from 'vite-plugin-ssr';
import { getPost, getPosts } from './api';
export async function onBeforeRender(pageContext: PageContextBuiltIn) {
const post = await getPost(pageContext.routeParams['slug']);
const post = await getPost(pageContext.routeParams['slug']);
return {
pageContext: {
pageProps: {
post
}
}
};
}
export async function prerender() {
const posts = await getPosts();
return posts.map((post) => ({
url: `/blog/${post.slug}`,
pageContext: { pageProps: { post } }
}));
return {
pageContext: {
pageProps: {
post
}
}
};
}

View File

@@ -12,10 +12,10 @@ function MarkdownPage({ post }: { post: PostOrPage }) {
Prism.highlightAll();
}, []);
let description =
const description =
post?.excerpt?.length || 0 > 160 ? post?.excerpt?.substring(0, 160) + '...' : post?.excerpt;
let featured_image =
const featured_image =
post?.feature_image ||
'https://raw.githubusercontent.com/spacedriveapp/.github/main/profile/spacedrive_icon.png';
@@ -50,9 +50,9 @@ function MarkdownPage({ post }: { post: PostOrPage }) {
</p>
</div>
<div className="flex flex-wrap gap-2">
{post?.tags?.map((tag: Tag) => {
return <BlogTag tag={tag} />;
})}
{post?.tags?.map((tag: Tag) => (
<BlogTag key={tag.id} tag={tag} />
))}
</div>
</section>
<article

View File

@@ -118,8 +118,11 @@ function Page() {
<h2 className="px-2 mb-0 text-4xl font-black leading-tight text-center">Our Values</h2>
<p className="mt-2 mb-4">What drives us daily.</p>
<div className="grid w-full grid-cols-1 gap-4 mt-5 sm:grid-cols-2">
{values.map((value) => (
<div className="flex flex-col p-10 bg-opacity-50 border border-gray-500 rounded-md bg-gray-550">
{values.map((value, index) => (
<div
key={value.title + index}
className="flex flex-col p-10 bg-opacity-50 border border-gray-500 rounded-md bg-gray-550"
>
<value.icon className="w-8 m-0" />
<h3 className="mt-4 mb-1 leading-snug text-2xl font-bold">{value.title}</h3>
<p className="mt-1 mb-0 text-gray-350">{value.desc}</p>
@@ -132,8 +135,9 @@ function Page() {
</h2>
<p className="mt-2 mb-4">We're behind you 100%.</p>
<div className="grid w-full grid-cols-1 gap-4 mt-5 sm:grid-cols-3">
{perks.map((value) => (
{perks.map((value, index) => (
<div
key={value.title + index}
style={{ backgroundColor: value.color + '10', borderColor: value.color + '30' }}
className="flex flex-col p-8 border rounded-md bg-gray-550 bg-opacity-30"
>
@@ -154,8 +158,11 @@ function Page() {
There are no positions open at this time. Please check back later!
</p>
) : (
positions.map((value) => (
<div className="flex flex-col p-10 bg-opacity-50 border border-gray-500 rounded-md bg-gray-550">
positions.map((value, index) => (
<div
key={value.name + index}
className="flex flex-col p-10 bg-opacity-50 border border-gray-500 rounded-md bg-gray-550"
>
<div className="flex flex-col sm:flex-row">
<h3 className="m-0 text-2xl leading-tight">{value.name}</h3>
<div className="mt-3 sm:mt-0.5">

View File

@@ -20,7 +20,7 @@ interface SectionProps {
}
function Section(props: SectionProps = { orientation: 'left' }) {
let info = (
const info = (
<div className="px-4 py-10 sm:px-10">
{props.heading && <h1 className="text-2xl font-black sm:text-4xl">{props.heading}</h1>}
{props.description && (
@@ -100,11 +100,13 @@ function Page() {
</Helmet>
<div className="mt-22 lg:mt-28" id="content" aria-hidden="true" />
<div className="mt-24 lg:mt-5" />
<NewBanner
headline="Spacedrive raises $2M led by OSS Capital"
href="/blog/spacedrive-funding-announcement"
link="Read post"
/>
{/* Disabled while blog post is not available */}
{/* TODO: re-enable */}
{/* <NewBanner */}
{/* headline="Spacedrive raises $2M led by OSS Capital" */}
{/* href="/blog/spacedrive-funding-announcement" */}
{/* link="Read post" */}
{/* /> */}
{unsubscribedFromWaitlist && (
<div
className={
@@ -144,6 +146,7 @@ function Page() {
className="transition text-primary-600 hover:text-primary-500"
href="https://github.com/spacedriveapp"
target="_blank"
rel="noreferrer"
>
Find out more
</a>

View File

@@ -245,7 +245,7 @@ function Page() {
<a
href="https://github.com/spacedriveapp/spacedrive/graphs/contributors"
target="_blank"
rel="noreferer"
rel="noreferrer"
className="duration-200 oss-credit-gradient hover:opacity-75"
>
open source contributors

View File

@@ -1,11 +1,11 @@
export type PageProps = {}
export type PageProps = Record<string, unknown>;
// The `pageContext` that are available in both on the server-side and browser-side
export type PageContext = {
Page: (pageProps: PageProps) => React.ReactElement
pageProps: PageProps
urlPathname: string
documentProps?: {
title?: string
description?: string
}
}
Page: (pageProps: PageProps) => React.ReactElement;
pageProps: PageProps;
urlPathname: string;
documentProps?: {
title?: string;
description?: string;
};
};

View File

@@ -6,9 +6,9 @@ export function getWindow(): (Window & typeof globalThis) | null {
return typeof window !== 'undefined' ? window : null;
}
// eslint-disable-next-line no-useless-escape
const FILE_NAME_REGEX = /^.*[\\\/]/;
/**
* Extracts the file name including its extension from a file path
*/
@@ -19,7 +19,7 @@ export function filename(path: string) {
/**
* Takes the result of `import.meta.globEager` and returns an object
* with the keys being the file names and the values being the imported file.
*
*
* Does not work with directories.
*/
export function resolveFilesGlob(files: Record<string, any>): Record<string, string> {

View File

@@ -1,4 +0,0 @@
node_modules/
android/
ios/
.expo

42
apps/mobile/.eslintrc.js Normal file
View File

@@ -0,0 +1,42 @@
module.exports = {
env: {
'react-native/react-native': true
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 12,
sourceType: 'module'
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:@typescript-eslint/recommended'
],
plugins: ['react', 'react-native'],
rules: {
'react/display-name': 'off',
'react/prop-types': 'off',
'react/no-unescaped-entities': 'off',
'react/react-in-jsx-scope': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'no-control-regex': 'off',
'no-mixed-spaces-and-tabs': ['warn', 'smart-tabs']
},
ignorePatterns: ['**/*.js', '**/*.json', 'node_modules', 'android', 'ios', '.expo'],
settings: {
react: {
version: 'detect'
}
}
};

View File

@@ -1,44 +0,0 @@
{
"env": {
"react-native/react-native": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["react", "react-native", "react-hooks", "@typescript-eslint"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"react/display-name": "off",
"react/prop-types": "off",
"no-control-regex": "off",
"no-mixed-spaces-and-tabs": ["warn", "smart-tabs"],
"react/react-in-jsx-scope": "off",
// Aggressively disable some rules for React Native.
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/no-floating-promises": "off"
},
"settings": {
"react": {
"version": "detect" // "detect" automatically picks the version you have installed.
}
}
}

View File

@@ -6,7 +6,8 @@
"scripts": {
"dev": "expo start --dev-client",
"android": "expo run:android",
"ios": "expo run:ios"
"ios": "expo run:ios",
"lint": "eslint src/**/*.{ts,tsx} && tsc --noEmit"
},
"dependencies": {
"@expo/vector-icons": "^13.0.0",
@@ -47,12 +48,17 @@
"@rnx-kit/metro-resolver-symlinks": "^0.1.21",
"@types/react": "~18.0.15",
"@types/react-native": "~0.69.5",
"@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7",
"eslint": "^8.21.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-native": "^4.0.0",
"metro-minify-terser": "^0.72.0",
"react-native-svg": "^12.4.3",
"react-native-svg-transformer": "^1.0.0"
"react-native-svg-transformer": "^1.0.0",
"typescript": "^4.7.4"
},
"private": true
}

View File

Binary file not shown.

View File

@@ -11,6 +11,7 @@ interface BrowseLocationItemProps {
const BrowseLocationItem: React.FC<BrowseLocationItemProps> = (props) => {
const { folderName, onPress } = props;
return (
<Pressable onPress={onPress}>
<View style={tw.style('flex mb-[4px] flex-row items-center py-2 px-2 rounded')}>

View File

@@ -8,6 +8,7 @@ declare module '*.svg' {
// This declaration is used by useNavigation, Link, ref etc.
declare global {
namespace ReactNavigation {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface RootParamList extends RootStackParamList {}
}
}

View File

@@ -8,7 +8,8 @@ repository = "https://github.com/spacedriveapp/spacedrive"
edition = "2021"
[features]
p2p = [] # This feature controlls whether the Spacedrive Core contains the Peer to Peer syncing engine (It isn't required for the hosted core so we can disable it).
p2p = [
] # This feature controlls whether the Spacedrive Core contains the Peer to Peer syncing engine (It isn't required for the hosted core so we can disable it).
[dependencies]
hostname = "0.3.1"
@@ -26,8 +27,17 @@ rmp = "^0.8.11"
rmp-serde = "^1.1.0"
# Project dependencies
prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", branch = "0.6.0", features = ["rspc"] }
rspc = { version = "0.0.4", features = ["axum", "tauri", "uuid", "chrono", "tracing"] }
prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", rev = "6a0119bce951c8d956542a59b2f783fc5a591fc7", features = [
"rspc",
"sqlite-create-many",
] }
rspc = { version = "0.0.4", features = [
"axum",
"tauri",
"uuid",
"chrono",
"tracing",
] }
walkdir = "^2.3.2"
uuid = { version = "1.1.2", features = ["v4", "serde"] }
sysinfo = "0.23.9"
@@ -42,7 +52,7 @@ webp = "0.2.2"
ffmpeg-next = "5.0.3"
fs_extra = "1.2.0"
tracing = "0.1.35"
tracing-subscriber = "0.3.14"
tracing-subscriber = { version = "0.3.14", features = ["env-filter"] }
async-stream = "0.3.3"
once_cell = "1.13.0"
ctor = "0.1.22"

View File

@@ -4,4 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust.git", branch = "0.6.0", features = ["rspc"] }
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust.git", rev = "6a0119bce951c8d956542a59b2f783fc5a591fc7", features = [
"rspc",
"sqlite-create-many",
] }

View File

@@ -26,8 +26,7 @@ pub(crate) fn mount() -> RouterBuilder {
library
.db
.file()
.find_unique(file::id::equals(args.id))
.update(vec![file::note::set(args.note)])
.update(file::id::equals(args.id), vec![file::note::set(args.note)])
.exec()
.await?;
@@ -54,8 +53,10 @@ pub(crate) fn mount() -> RouterBuilder {
library
.db
.file()
.find_unique(file::id::equals(args.id))
.update(vec![file::favorite::set(args.favorite)])
.update(
file::id::equals(args.id),
vec![file::favorite::set(args.favorite)],
)
.exec()
.await?;
@@ -82,8 +83,7 @@ pub(crate) fn mount() -> RouterBuilder {
library
.db
.file()
.find_unique(file::id::equals(id))
.delete()
.delete(file::id::equals(id))
.exec()
.await?;

View File

@@ -133,8 +133,10 @@ pub(crate) fn mount() -> RouterBuilder {
library
.db
.location()
.find_unique(location::id::equals(args.id))
.update(vec![location::name::set(args.name)])
.update(
location::id::equals(args.id),
vec![location::name::set(args.name)],
)
.exec()
.await?;
@@ -147,16 +149,14 @@ pub(crate) fn mount() -> RouterBuilder {
library
.db
.file_path()
.find_many(vec![file_path::location_id::equals(Some(location_id))])
.delete()
.delete_many(vec![file_path::location_id::equals(Some(location_id))])
.exec()
.await?;
library
.db
.location()
.find_unique(location::id::equals(location_id))
.delete()
.delete(location::id::equals(location_id))
.exec()
.await?;

View File

@@ -97,8 +97,10 @@ pub(crate) fn mount() -> RouterBuilder {
library
.db
.tag()
.find_unique(tag::id::equals(args.id))
.update(vec![tag::name::set(args.name), tag::color::set(args.color)])
.update(
tag::id::equals(args.id),
vec![tag::name::set(args.name), tag::color::set(args.color)],
)
.exec()
.await?;
@@ -117,13 +119,7 @@ pub(crate) fn mount() -> RouterBuilder {
.mutation("delete", |ctx, arg: LibraryArgs<i32>| async move {
let (id, library) = arg.get_library(&ctx).await?;
library
.db
.tag()
.find_unique(tag::id::equals(id))
.delete()
.exec()
.await?;
library.db.tag().delete(tag::id::equals(id)).exec().await?;
invalidate_query!(
library,

View File

@@ -3,7 +3,7 @@ use super::checksum::generate_cas_id;
use crate::{
job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext},
library::LibraryContext,
prisma::{self, file, file_path, location},
prisma::{file, file_path, location},
};
use chrono::{DateTime, FixedOffset};
use prisma_client_rust::{prisma_models::PrismaValue, raw, raw::Raw, Direction};
@@ -160,10 +160,10 @@ impl StatefulJob for FileIdentifierJob {
if let Err(e) = library_ctx
.db
.file_path()
.find_unique(file_path::id::equals(
*cas_lookup.get(&existing_file.cas_id).unwrap(),
))
.update(vec![file_path::file_id::set(Some(existing_file.id))])
.update(
file_path::id::equals(*cas_lookup.get(&existing_file.cas_id).unwrap()),
vec![file_path::file_id::set(Some(existing_file.id))],
)
.exec()
.await
{
@@ -205,6 +205,7 @@ impl StatefulJob for FileIdentifierJob {
),
values,
))
.exec()
.await
.unwrap_or_else(|e| {
error!("Error inserting files: {:#?}", e);
@@ -219,10 +220,10 @@ impl StatefulJob for FileIdentifierJob {
.library_ctx()
.db
.file_path()
.find_unique(file_path::id::equals(
*cas_lookup.get(&created_file.cas_id).unwrap(),
))
.update(vec![file_path::file_id::set(Some(created_file.id))])
.update(
file_path::id::equals(*cas_lookup.get(&created_file.cas_id).unwrap()),
vec![file_path::file_id::set(Some(created_file.id))],
)
.exec()
.await
{
@@ -277,12 +278,13 @@ struct CountRes {
pub async fn count_orphan_file_paths(
ctx: &LibraryContext,
location_id: i64,
) -> Result<usize, prisma::QueryError> {
) -> Result<usize, prisma_client_rust::QueryError> {
let files_count = ctx.db
._query_raw::<CountRes>(raw!(
"SELECT COUNT(*) AS count FROM file_paths WHERE file_id IS NULL AND is_dir IS FALSE AND location_id = {}",
PrismaValue::Int(location_id)
))
.exec()
.await?;
Ok(files_count[0].count.unwrap_or(0))
}
@@ -290,7 +292,7 @@ pub async fn count_orphan_file_paths(
pub async fn get_orphan_file_paths(
ctx: &LibraryContext,
cursor: i32,
) -> Result<Vec<file_path::Data>, prisma::QueryError> {
) -> Result<Vec<file_path::Data>, prisma_client_rust::QueryError> {
info!(
"discovering {} orphan file paths at cursor: {:?}",
CHUNK_SIZE, cursor
@@ -302,7 +304,7 @@ pub async fn get_orphan_file_paths(
file_path::is_dir::equals(false),
])
.order_by(file_path::id::order(Direction::Asc))
.cursor(file_path::id::cursor(cursor))
.cursor(file_path::id::equals(cursor))
.take(CHUNK_SIZE as i64)
.exec()
.await

View File

@@ -86,6 +86,7 @@ impl StatefulJob for IndexerJob {
.library_ctx()
.db
._query_raw::<QueryRes>(raw!("SELECT MAX(id) id FROM file_paths"))
.exec()
.await
{
Ok(rows) => rows[0].id.unwrap_or(0),
@@ -241,7 +242,7 @@ impl StatefulJob for IndexerJob {
files
);
let count = ctx.library_ctx().db._execute_raw(raw).await;
let count = ctx.library_ctx().db._execute_raw(raw).exec().await;
info!("Inserted {:?} records", count);

View File

@@ -6,7 +6,7 @@ use crate::{
},
job::{worker::Worker, DynJob, Job, JobError},
library::LibraryContext,
prisma::{self, job, node},
prisma::{job, node},
};
use int_enum::IntEnum;
use rspc::Type;
@@ -117,7 +117,9 @@ impl JobManager {
ret
}
pub async fn get_history(ctx: &LibraryContext) -> Result<Vec<JobReport>, prisma::QueryError> {
pub async fn get_history(
ctx: &LibraryContext,
) -> Result<Vec<JobReport>, prisma_client_rust::QueryError> {
let jobs = ctx
.db
.job()
@@ -291,15 +293,17 @@ impl JobReport {
pub async fn update(&self, ctx: &LibraryContext) -> Result<(), JobError> {
ctx.db
.job()
.find_unique(job::id::equals(self.id.as_bytes().to_vec()))
.update(vec![
job::status::set(self.status.int_value()),
job::data::set(self.data.clone()),
job::task_count::set(self.task_count),
job::completed_task_count::set(self.completed_task_count),
job::date_modified::set(chrono::Utc::now().into()),
job::seconds_elapsed::set(self.seconds_elapsed),
])
.update(
job::id::equals(self.id.as_bytes().to_vec()),
vec![
job::status::set(self.status.int_value()),
job::data::set(self.data.clone()),
job::task_count::set(self.task_count),
job::completed_task_count::set(self.completed_task_count),
job::date_modified::set(chrono::Utc::now().into()),
job::seconds_elapsed::set(self.seconds_elapsed),
],
)
.exec()
.await?;
Ok(())

View File

@@ -1,4 +1,4 @@
use crate::{prisma, sys::LocationError};
use crate::sys::LocationError;
use rmp_serde::{decode::Error as DecodeError, encode::Error as EncodeError};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{collections::VecDeque, fmt::Debug};
@@ -14,7 +14,7 @@ pub use worker::*;
#[derive(Error, Debug)]
pub enum JobError {
#[error("Database error: {0}")]
DatabaseError(#[from] prisma::QueryError),
DatabaseError(#[from] prisma_client_rust::QueryError),
#[error("Location error: {0}")]
LocationError(#[from] LocationError),
#[error("I/O error: {0}")]

View File

@@ -10,11 +10,7 @@ use tokio::sync::RwLock;
use uuid::Uuid;
use crate::{
invalidate_query,
node::Platform,
prisma::{self, node},
util::db::load_and_migrate,
NodeContext,
invalidate_query, node::Platform, prisma::node, util::db::load_and_migrate, NodeContext,
};
use super::{LibraryConfig, LibraryConfigWrapped, LibraryContext};
@@ -36,7 +32,7 @@ pub enum LibraryManagerError {
#[error("error serializing or deserializing the JSON in the config file")]
Json(#[from] serde_json::Error),
#[error("Database error: {0}")]
Database(#[from] prisma::QueryError),
Database(#[from] prisma_client_rust::QueryError),
#[error("Library not found error")]
LibraryNotFound,
#[error("error migrating the config file")]

View File

@@ -8,7 +8,7 @@ use crate::{
invalidate_query,
job::Job,
library::LibraryContext,
prisma::{self, location},
prisma::location,
};
use rspc::ErrorCode;
@@ -195,7 +195,7 @@ pub enum LocationError {
#[error("Failed to connect to database (error: {0:?})")]
IOError(io::Error),
#[error("Database error")]
DatabaseError(#[from] prisma::QueryError),
DatabaseError(#[from] prisma_client_rust::QueryError),
}
impl From<LocationError> for rspc::Error {

View File

@@ -1,7 +1,4 @@
use crate::{
library::LibraryContext,
prisma::{self, volume::*},
};
use crate::{library::LibraryContext, prisma::volume::*};
use rspc::Type;
use serde::{Deserialize, Serialize};
use thiserror::Error;
@@ -24,7 +21,7 @@ pub struct Volume {
#[derive(Error, Debug)]
pub enum VolumeError {
#[error("Database error: {0}")]
DatabaseErr(#[from] prisma::QueryError),
DatabaseErr(#[from] prisma_client_rust::QueryError),
#[error("FromUtf8Error: {0}")]
FromUtf8Error(#[from] std::string::FromUtf8Error),
}

View File

@@ -14,7 +14,7 @@ pub enum MigrationError {
#[error("An error occurred while initialising a new database connection")]
DatabaseInitialization(#[from] NewClientError),
#[error("An error occurred with the database while applying migrations")]
DatabaseError(#[from] prisma_client_rust::queries::Error),
DatabaseError(#[from] prisma_client_rust::QueryError),
#[error("An error occurred reading the embedded migration files. {0}. Please report to Spacedrive developers!")]
InvalidEmbeddedMigration(&'static str),
}
@@ -27,11 +27,12 @@ pub async fn load_and_migrate(db_url: &str) -> Result<PrismaClient, MigrationErr
._query_raw::<serde_json::Value>(raw!(
"SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'"
))
.exec()
.await?
.is_empty();
if migrations_table_missing {
client._execute_raw(raw!(INIT_MIGRATION)).await?;
client._execute_raw(raw!(INIT_MIGRATION)).exec().await?;
}
let mut migration_directories = MIGRATIONS_DIR
@@ -102,11 +103,13 @@ pub async fn load_and_migrate(db_url: &str) -> Result<PrismaClient, MigrationErr
let steps = migration_file_raw.split(';').collect::<Vec<&str>>();
let steps = &steps[0..steps.len() - 1];
for (i, step) in steps.iter().enumerate() {
client._execute_raw(raw!(*step)).await?;
client._execute_raw(raw!(*step)).exec().await?;
client
.migration()
.find_unique(migration::checksum::equals(checksum.clone()))
.update(vec![migration::steps_applied::set(i as i32 + 1)])
.update(
migration::checksum::equals(checksum.clone()),
vec![migration::steps_applied::set(i as i32 + 1)],
)
.exec()
.await?;
}

View File

@@ -26,15 +26,7 @@
"@cspell/dict-typescript": "^2.0.1",
"@evilmartians/lefthook": "^1.0.5",
"@trivago/prettier-plugin-sort-imports": "^3.3.0",
"@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7",
"cspell": "^6.4.0",
"eslint": "^8.20.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-standard-with-typescript": "^22.0.0",
"eslint-plugin-import": ">=2.25.2 <3.0.0",
"eslint-plugin-n": ">=15.0.0 <16.0.0",
"eslint-plugin-promise": ">=6.0.0 <7.0.0",
"markdown-link-check": "^3.10.2",
"prettier": "^2.7.1",
"typescript": "^4.7.4"

View File

@@ -2,7 +2,7 @@ import { createContext } from 'react';
export const AppPropsContext = createContext<AppProps | null>(null);
export type Platform = 'browser' | 'macOS' | 'windows' | 'linux';
export type Platform = 'browser' | 'macOS' | 'windows' | 'linux' | 'unknown';
export type CdnUrl = 'internal' | string;
export interface AppProps {

View File

@@ -1,11 +0,0 @@
module.exports = {
extends: ['next', 'prettier'],
settings: {
next: {
rootDir: ['apps/*/', 'packages/*/']
}
},
rules: {
'no-html-link-for-pages': 'off'
}
};

View File

@@ -0,0 +1,44 @@
module.exports = {
env: {
browser: true,
node: true
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 12,
sourceType: 'module'
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
plugins: ['react'],
rules: {
'react/display-name': 'off',
'react/prop-types': 'off',
'react/no-unescaped-entities': 'off',
'react/react-in-jsx-scope': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'no-control-regex': 'off',
'no-mixed-spaces-and-tabs': ['warn', 'smart-tabs']
},
ignorePatterns: ['**/*.js', '**/*.json', 'node_modules'],
settings: {
react: {
version: 'detect'
}
}
};

View File

@@ -4,6 +4,14 @@
"main": "index.js",
"license": "GPL-3.0-only",
"files": [
"eslint-preset.js"
]
"eslint-react.js"
],
"devDependencies": {
"eslint": "^8.21.0",
"@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0"
}
}

View File

@@ -0,0 +1,7 @@
module.exports = {
...require('@sd/config/eslint-react.js'),
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
};

View File

@@ -11,7 +11,8 @@
"./components/*": "./src/components/*"
},
"scripts": {
"icons": "./scripts/generateSvgImports.mjs"
"icons": "./scripts/generateSvgImports.mjs",
"lint": "eslint src/**/*.{ts,tsx} && tsc --noEmit"
},
"dependencies": {
"@apollo/client": "^3.6.9",
@@ -67,6 +68,7 @@
"zustand": "4.0.0"
},
"devDependencies": {
"@sd/config": "workspace:*",
"@types/babel-core": "^6.25.7",
"@types/byte-size": "^8.1.0",
"@types/lodash": "^4.14.182",

View File

@@ -6,7 +6,7 @@ import {
useBridgeQuery,
useInvalidateQuery
} from '@sd/client';
import { QueryClientProvider } from '@tanstack/react-query';
import { QueryClientProvider, defaultContext } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import React, { useEffect, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
@@ -21,10 +21,10 @@ function RouterContainer(props: { props: AppProps }) {
const { data: client } = useBridgeQuery(['getNode']);
useEffect(() => {
setAppProps({
setAppProps((appProps) => ({
...appProps,
data_path: client?.data_path
});
}));
}, [client?.data_path]);
return (
@@ -39,10 +39,19 @@ function RouterContainer(props: { props: AppProps }) {
export default function SpacedriveInterface(props: AppProps) {
useInvalidateQuery();
// hotfix for bug where props are not updated, not sure of the cause
if (props.platform === 'unknown') {
// this should be a loading screen if we can't fix the issue above
return <></>;
}
return (
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
<QueryClientProvider client={queryClient}>
{import.meta.env.MODE === 'development' && <ReactQueryDevtools position="bottom-right" />}
<ErrorBoundary FallbackComponent={ErrorFallback}>
<QueryClientProvider client={queryClient} contextSharing={true}>
{/* The `context={defaultContext}` part is required for this to work on Windows. Why, idk, don't question it */}
{import.meta.env.MODE === 'development' && (
<ReactQueryDevtools position="bottom-right" context={defaultContext} />
)}
<RouterContainer props={props} />
</QueryClientProvider>
</ErrorBoundary>

View File

@@ -44,7 +44,7 @@ export function AppRouter() {
if (libraryState.currentLibraryUuid === null && libraries && libraries.length > 0) {
libraryState.switchLibrary(libraries[0].uuid);
}
}, [libraryState.currentLibraryUuid, libraries]);
}, [libraryState, libraryState.currentLibraryUuid, libraries]);
return (
<>

View File

@@ -73,10 +73,11 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb
index: goingUp ? selectedRowIndex - 1 : selectedRowIndex
});
}
}, [selectedRowIndex]);
}, [goingUp, selectedRowIndex]);
useEffect(() => {
setLocationId(props.location_id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.location_id]);
useKey('ArrowUp', (e) => {
@@ -168,7 +169,7 @@ interface RenderItemProps {
const RenderGridItem: React.FC<RenderItemProps> = ({ item, index, dirId }) => {
const { selectedRowIndex, setSelectedRowIndex } = useExplorerStore();
let [_, setSearchParams] = useSearchParams();
const [_, setSearchParams] = useSearchParams();
return (
<FileItem
@@ -189,7 +190,7 @@ const RenderGridItem: React.FC<RenderItemProps> = ({ item, index, dirId }) => {
const RenderRow: React.FC<RenderItemProps> = ({ item, index, dirId }) => {
const { selectedRowIndex, setSelectedRowIndex } = useExplorerStore();
const isActive = selectedRowIndex === index;
let [_, setSearchParams] = useSearchParams();
const [_, setSearchParams] = useSearchParams();
return useMemo(
() => (
@@ -217,6 +218,7 @@ const RenderRow: React.FC<RenderItemProps> = ({ item, index, dirId }) => {
))}
</div>
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[item.id, isActive]
);
};

View File

@@ -32,7 +32,7 @@ export default function FileThumb(props: {
}
if (icons[props.file.extension as keyof typeof icons]) {
let Icon = icons[props.file.extension as keyof typeof icons];
const Icon = icons[props.file.extension as keyof typeof icons];
return <Icon className={clsx('max-w-[170px] w-full h-full', props.className)} />;
}
return <div></div>;

View File

@@ -62,6 +62,7 @@ export const Inspector = (props: {
return () => {
clearTimeout(handler);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [note]);
const toggleFavorite = () => {

View File

@@ -18,7 +18,7 @@ import RunningJobsWidget from '../jobs/RunningJobsWidget';
import { MacTrafficLights } from '../os/TrafficLights';
import { DefaultProps } from '../primitive/types';
interface SidebarProps extends DefaultProps {}
type SidebarProps = DefaultProps;
export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode }) => (
<NavLink {...props}>
@@ -88,6 +88,7 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
useEffect(() => {
if (libraries && !currentLibraryUuid) initLibraries(libraries);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [libraries, currentLibraryUuid]);
const { mutate: createLocation } = useLibraryMutation('locations.create');

View File

@@ -55,8 +55,9 @@ export default function RunningJobsWidget() {
return (
<div className="flex flex-col space-y-4">
{jobs?.map((job) => (
{jobs?.map((job, index) => (
<Transition
key={job.id + index}
show={true}
enter="transition-translate ease-in-out duration-200"
enterFrom="translate-y-24"

View File

@@ -9,7 +9,7 @@ import { useNavigate } from 'react-router-dom';
import { Shortcut } from '../primitive/Shortcut';
import { DefaultProps } from '../primitive/types';
export interface TopBarProps extends DefaultProps {}
export type TopBarProps = DefaultProps;
export interface TopBarButtonProps
extends DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
icon: React.ComponentType<IconProps>;
@@ -18,7 +18,7 @@ export interface TopBarButtonProps
left?: boolean;
right?: boolean;
}
interface SearchBarProps extends DefaultProps {}
type SearchBarProps = DefaultProps;
const TopBarButton: React.FC<TopBarButtonProps> = ({
icon: Icon,
@@ -90,7 +90,7 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
}
});
let navigate = useNavigate();
const navigate = useNavigate();
return (
<>
<div

View File

@@ -1,7 +1,7 @@
import React from 'react';
import ReactJson, { ReactJsonViewProps } from 'react-json-view';
export interface CodeBlockProps extends ReactJsonViewProps {}
export type CodeBlockProps = ReactJsonViewProps;
export default function CodeBlock(props: CodeBlockProps) {
return (

View File

@@ -16,7 +16,7 @@ export default function Listbox(props: { options: ListboxOption[]; className?: s
if (!selected) {
setSelected(props.options[0]);
}
}, [props.options]);
}, [props.options, selected]);
return (
<>

View File

@@ -1,6 +1,6 @@
import React from 'react';
export const ContentScreen: React.FC<{}> = (props) => {
export const ContentScreen: React.FC<unknown> = (props) => {
// const [address, setAddress] = React.useState('');
return (
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">

View File

@@ -4,7 +4,7 @@ import React, { useContext } from 'react';
import CodeBlock from '../components/primitive/Codeblock';
export const DebugScreen: React.FC<{}> = (props) => {
export const DebugScreen: React.FC<unknown> = (props) => {
const appPropsContext = useContext(AppPropsContext);
const { data: nodeState } = useBridgeQuery(['getNode']);
const { data: libraryState } = useBridgeQuery(['library.get']);

View File

@@ -6,12 +6,12 @@ import { FileList } from '../components/file/FileList';
import { Inspector } from '../components/file/Inspector';
import { TopBar } from '../components/layout/TopBar';
export const ExplorerScreen: React.FC<{}> = () => {
let [searchParams] = useSearchParams();
let path = searchParams.get('path') || '';
export const ExplorerScreen: React.FC<unknown> = () => {
const [searchParams] = useSearchParams();
const path = searchParams.get('path') || '';
let { id } = useParams();
let location_id = Number(id);
const { id } = useParams();
const location_id = Number(id);
const [limit, setLimit] = React.useState(100);

View File

@@ -70,7 +70,7 @@ const StatItem: React.FC<StatItemProps> = (props) => {
setCount((count) => count + 1);
}, quadratic(appProps?.demoMode ? 1000 : 500, +size.value, count));
}
}, [count, size]);
}, [appProps?.demoMode, count, size]);
return (
<div
@@ -139,6 +139,7 @@ export const OverviewScreen = () => {
setOverviewStats(newStatistics);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appProps, libraryStatistics]);
// useEffect(() => {
@@ -187,7 +188,7 @@ export const OverviewScreen = () => {
<Dialog
title="Add Device"
description="Connect a new device to your library. Either enter another device's code or copy this one."
ctaAction={() => {}}
// ctaAction={() => {}}
ctaLabel="Connect"
trigger={
<Button

View File

@@ -1,6 +1,6 @@
import React from 'react';
export const PhotosScreen: React.FC<{}> = (props) => {
export const PhotosScreen: React.FC<unknown> = (props) => {
return (
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
<div className="flex flex-col space-y-5 pb-7">

View File

@@ -12,6 +12,7 @@ export const RedirectPage: React.FC<RedirectPageProps> = (props) => {
useEffect(() => {
navigate(destination);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { useParams } from 'react-router-dom';
export const TagScreen: React.FC<{}> = () => {
let { id } = useParams();
export const TagScreen: React.FC<unknown> = () => {
const { id } = useParams();
return (
<div className="w-full p-5">

View File

@@ -35,6 +35,7 @@ export default function LibraryGeneralSettings() {
});
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nameDebounced, descriptionDebounced]);
useEffect(() => {
@@ -42,6 +43,7 @@ export default function LibraryGeneralSettings() {
setName(currentLibrary.config.name);
setDescription(currentLibrary.config.description);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [libraries]);
useEffect(() => {
@@ -50,6 +52,7 @@ export default function LibraryGeneralSettings() {
setName(currentLibrary.config.name);
setDescription(currentLibrary.config.description);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentLibraryUuid]);
useEffect(() => {

View File

@@ -47,10 +47,11 @@ export default function TagsSettings() {
if (!currentTag && tags?.length) {
setSelectedTag(tags[0].id);
}
}, [tags]);
}, [currentTag, tags]);
useEffect(() => {
reset(currentTag);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentTag]);
const { register, handleSubmit, watch, reset, control } = useForm({
@@ -59,6 +60,7 @@ export default function TagsSettings() {
const submitTagUpdate = handleSubmit((data) => updateTag(data));
// eslint-disable-next-line react-hooks/exhaustive-deps
const autoUpdateTag = useCallback(useDebounce(submitTagUpdate, 500), []);
useEffect(() => {

View File

@@ -39,7 +39,7 @@ function LibraryListItem(props: { library: LibraryConfigWrapped }) {
ctaDanger
ctaLabel="Delete"
trigger={
<Button variant="gray" className="!p-1.5" onClick={() => {}}>
<Button variant="gray" className="!p-1.5">
<TrashIcon className="w-4 h-4" />
</Button>
}

BIN
pnpm-lock.yaml generated
View File

Binary file not shown.