mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-05 05:46:24 -04:00
Merge branch 'spacedriveapp:main' into navbar-transition
This commit is contained in:
20
.eslintrc.js
20
.eslintrc.js
@@ -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'
|
||||
}
|
||||
};
|
||||
4
.github/workflows/clippy.yml
vendored
4
.github/workflows/clippy.yml
vendored
@@ -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
4
.gitignore
vendored
@@ -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
BIN
Cargo.lock
generated
Binary file not shown.
11
Cargo.toml
11
Cargo.toml
@@ -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
5
apps/desktop/dist/.gitignore
vendored
Normal 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'
|
||||
@@ -29,7 +29,7 @@ function App() {
|
||||
}
|
||||
}
|
||||
|
||||
const [platform, setPlatform] = useState<Platform>('macOS');
|
||||
const [platform, setPlatform] = useState<Platform>('unknown');
|
||||
const [focused, setFocused] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
8
apps/landing/.eslintrc.js
Normal file
8
apps/landing/.eslintrc.js
Normal 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']
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 } }
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
node_modules/
|
||||
android/
|
||||
ios/
|
||||
.expo
|
||||
42
apps/mobile/.eslintrc.js
Normal file
42
apps/mobile/.eslintrc.js
Normal 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'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
BIN
apps/mobile/pnpm-lock.yaml
generated
BIN
apps/mobile/pnpm-lock.yaml
generated
Binary file not shown.
@@ -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')}>
|
||||
|
||||
1
apps/mobile/src/types/declarations.d.ts
vendored
1
apps/mobile/src/types/declarations.d.ts
vendored
@@ -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 {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
] }
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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}")]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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?;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ['next', 'prettier'],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ['apps/*/', 'packages/*/']
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'no-html-link-for-pages': 'off'
|
||||
}
|
||||
};
|
||||
44
packages/config/eslint-react.js
Normal file
44
packages/config/eslint-react.js
Normal 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'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
7
packages/interface/.eslintrc.js
Normal file
7
packages/interface/.eslintrc.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
...require('@sd/config/eslint-react.js'),
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -62,6 +62,7 @@ export const Inspector = (props: {
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [note]);
|
||||
|
||||
const toggleFavorite = () => {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -12,6 +12,7 @@ export const RedirectPage: React.FC<RedirectPageProps> = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
navigate(destination);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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
BIN
pnpm-lock.yaml
generated
Binary file not shown.
Reference in New Issue
Block a user