├── .node-version ├── src-tauri ├── bin │ └── .gitkeep ├── build.rs ├── .taurignore ├── icons │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 32x32.png │ ├── icon.icns │ ├── StoreLogo.png │ ├── 128x128@2x.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── ios │ │ ├── AppIcon-512@2x.png │ │ ├── AppIcon-20x20@1x.png │ │ ├── AppIcon-20x20@2x-1.png │ │ ├── AppIcon-20x20@2x.png │ │ ├── AppIcon-20x20@3x.png │ │ ├── AppIcon-29x29@1x.png │ │ ├── AppIcon-29x29@2x-1.png │ │ ├── AppIcon-29x29@2x.png │ │ ├── AppIcon-29x29@3x.png │ │ ├── AppIcon-40x40@1x.png │ │ ├── AppIcon-40x40@2x-1.png │ │ ├── AppIcon-40x40@2x.png │ │ ├── AppIcon-40x40@3x.png │ │ ├── AppIcon-60x60@2x.png │ │ ├── AppIcon-60x60@3x.png │ │ ├── AppIcon-76x76@1x.png │ │ ├── AppIcon-76x76@2x.png │ │ └── AppIcon-83.5x83.5@2x.png │ └── android │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ └── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png ├── Makefile ├── .gitignore ├── gen │ └── schemas │ │ ├── capabilities.json │ │ └── acl-manifests.json ├── capabilities │ └── main.json ├── Cargo.toml ├── src │ └── main.rs └── tauri.conf.json ├── src-server ├── pnpm-workspace.yaml ├── .eslintignore ├── .env.example ├── .npmrc ├── pkg.json ├── .gitignore ├── .prettierrc ├── src │ ├── env.ts │ ├── index.ts │ ├── server │ │ └── index.ts │ ├── utils.ts │ ├── schema.ts │ └── server.ts ├── migrations │ ├── meta │ │ ├── _journal.json │ │ └── 0000_snapshot.json │ └── 0000_clear_shockwave.sql ├── drizzle.config.ts ├── tsconfig.json ├── esbuild.config.mjs ├── README.md ├── .eslintrc.json └── package.json ├── .env ├── .env.example ├── .npmrc ├── .husky └── pre-commit ├── .gitignore ├── public ├── logo.png └── scripts │ ├── disable-reload.js │ ├── disable-context-menu.js │ ├── accessibility-only-when-focused.js │ └── disable-zoom.js ├── .prettierrc ├── postcss.config.js ├── src ├── types │ ├── fs.ts │ └── compression.ts ├── assets │ ├── fonts │ │ ├── poppins │ │ │ ├── Poppins-Italic.ttf │ │ │ └── Poppins-Regular.ttf │ │ └── index.ts │ └── icons │ │ ├── tick.svg │ │ ├── info.svg │ │ ├── low-res-heart.svg │ │ ├── curved-arrow.svg │ │ ├── play.svg │ │ ├── redo.svg │ │ ├── file-explorer.svg │ │ ├── cross.svg │ │ ├── save.svg │ │ ├── trash.svg │ │ ├── question.svg │ │ ├── error.svg │ │ ├── warning.svg │ │ ├── moon.svg │ │ ├── drag-and-drop.svg │ │ ├── setting.svg │ │ ├── star.svg │ │ ├── github.svg │ │ ├── sun.svg │ │ ├── video-file.svg │ │ └── logo.svg ├── utils │ ├── tailwind.ts │ ├── fs.ts │ ├── animation.ts │ └── string.ts ├── components │ ├── Layout │ │ ├── context.ts │ │ ├── Footer.tsx │ │ ├── Header.tsx │ │ └── index.tsx │ ├── Code │ │ └── index.tsx │ ├── Spinner │ │ └── index.tsx │ ├── Link │ │ └── index.tsx │ ├── Slider │ │ └── Slider.tsx │ ├── Checkbox │ │ └── index.tsx │ ├── Toast │ │ └── index.tsx │ ├── Progress │ │ └── index.tsx │ ├── Tooltip │ │ └── index.tsx │ ├── Divider │ │ └── index.tsx │ ├── Tabs │ │ └── index.tsx │ ├── Select │ │ └── index.tsx │ ├── Icon │ │ ├── index.tsx │ │ └── registry.ts │ ├── Button │ │ └── index.tsx │ ├── Image │ │ └── index.tsx │ ├── ErrorBoundary │ │ └── index.tsx │ ├── ThemeSwitcher │ │ └── index.tsx │ ├── Drawer │ │ └── index.tsx │ └── Modal │ │ └── index.tsx ├── providers │ ├── ThemeProvider.tsx │ └── UIProvider.tsx ├── app │ ├── head.tsx │ ├── (root) │ │ ├── types.ts │ │ ├── page.tsx │ │ └── state.ts │ ├── layout.tsx │ └── globals.css ├── ui │ ├── Patterns │ │ └── DotPattern.tsx │ └── Dialogs │ │ └── AlertDialog.tsx └── tauri │ └── components │ └── VideoPicker │ └── index.tsx ├── next-env.d.ts ├── tsconfig.json ├── next.config.mjs ├── README.md ├── tailwind.config.ts ├── .eslintrc.json └── package.json /.node-version: -------------------------------------------------------------------------------- 1 | v20.10.0 -------------------------------------------------------------------------------- /src-tauri/bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src-server/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src-server/.eslintignore: -------------------------------------------------------------------------------- 1 | pkg.require.js -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Local 2 | DATABASE_URL=sqlite.db 3 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=sqlite.db 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=*@nextui-org/* -------------------------------------------------------------------------------- /src-server/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=sqlite.db 2 | -------------------------------------------------------------------------------- /src-server/.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/.taurignore: -------------------------------------------------------------------------------- 1 | **/*.db 2 | **/*.db-shm 3 | **/*.db-wal 4 | **/sqlite.* -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm lint-staged -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.bundle.js 2 | 3 | **/node_modules 4 | 5 | **/target 6 | 7 | .next 8 | dist -------------------------------------------------------------------------------- /src-server/pkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": ["node20-linux-x64"], 3 | "assets": "migrations/**/*" 4 | } 5 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/public/logo.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "tabWidth": 2, 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /src-server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.db 3 | .env 4 | bundle 5 | dist 6 | build 7 | bin/* 8 | !bin/.gitkeep 9 | ./bundle.js -------------------------------------------------------------------------------- /src-server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "tabWidth": 2, 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/Makefile: -------------------------------------------------------------------------------- 1 | include .env 2 | 3 | generate-entities: 4 | cargo bin sea-orm-cli generate entity -u $(DATABASE_URL) -o src/lib/db/entity --with-serde both -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-512@2x.png -------------------------------------------------------------------------------- /src/types/fs.ts: -------------------------------------------------------------------------------- 1 | export type FileMetadata = { 2 | path: string 3 | fileName: string 4 | mimeType: string 5 | extension: string 6 | size: number 7 | } 8 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | **/target/* 2 | 3 | src/lib/prisma.rs 4 | 5 | **/*.db 6 | **/*.db-journal 7 | **/sqlite.* 8 | 9 | .bin 10 | bin/* 11 | !bin/.gitkeep -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /src-server/src/env.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | 3 | export const envs = { 4 | DATABASE_URL: process.env.DATABASE_URL as string, 5 | } 6 | 7 | export default envs 8 | -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src/assets/fonts/poppins/Poppins-Italic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src/assets/fonts/poppins/Poppins-Regular.ttf -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src/utils/tailwind.ts: -------------------------------------------------------------------------------- 1 | import { clsx, ClassValue } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...classes: ClassValue[]) { 5 | return twMerge(clsx(...classes)) 6 | } 7 | -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforreal1/Local-First-Desktop-App-Rust-NodeJS/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src/components/Layout/context.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const LayoutContext = React.createContext<{ 4 | isValid: boolean 5 | }>({ 6 | isValid: false, 7 | }) 8 | LayoutContext.displayName = 'LayoutContext' 9 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /src-server/migrations/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "dialect": "sqlite", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "5", 8 | "when": 1680196495699, 9 | "tag": "0000_clear_shockwave", 10 | "breakpoints": true 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /src-server/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from 'drizzle-kit' 2 | import env from './src/env' 3 | 4 | export default { 5 | out: './migrations', 6 | schema: './src/schema.ts', 7 | breakpoints: true, 8 | dialect: 'sqlite', 9 | dbCredentials: { 10 | url: env.DATABASE_URL, 11 | }, 12 | } satisfies Config 13 | -------------------------------------------------------------------------------- /src/utils/fs.ts: -------------------------------------------------------------------------------- 1 | import prettyBytes from 'pretty-bytes' 2 | 3 | /** 4 | * Formats bytes to appropriate human readable format like KB, MB, GB, etc 5 | * @param {number} bytes: Bytes to format 6 | */ 7 | export function formatBytes(bytes: number): string { 8 | if (!bytes) return '' 9 | return prettyBytes(bytes) 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Code/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Code as NextUICode, 4 | type CodeProps as NextUICodeProps, 5 | } from '@nextui-org/code' 6 | 7 | interface CodeProps extends NextUICodeProps {} 8 | 9 | function Code(props: CodeProps) { 10 | return 11 | } 12 | 13 | export default Code 14 | -------------------------------------------------------------------------------- /src/components/Spinner/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Spinner as NextUISpinner, 3 | type SpinnerProps as NextUISpinnerProps, 4 | } from '@nextui-org/spinner' 5 | 6 | interface SpinnerPros extends NextUISpinnerProps {} 7 | 8 | function Spinner(props: SpinnerPros) { 9 | return 10 | } 11 | 12 | export default Spinner 13 | -------------------------------------------------------------------------------- /src/components/Link/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link as NextLink, LinkProps as NextLinkProps } from '@nextui-org/link' 3 | 4 | interface LinkProps {} 5 | 6 | function Link(props: LinkProps & NextLinkProps) { 7 | const { ...nextLinkProps } = props 8 | return 9 | } 10 | 11 | export default Link 12 | -------------------------------------------------------------------------------- /src/components/Slider/Slider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Slider as NextUISlider, 4 | type SliderProps as NextUISliderProps, 5 | } from '@nextui-org/slider' 6 | 7 | interface SliderProps extends NextUISliderProps {} 8 | 9 | function Slider(props: SliderProps) { 10 | return 11 | } 12 | 13 | export default Slider 14 | -------------------------------------------------------------------------------- /src/components/Checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Checkbox as NextUICheckbox, 4 | type CheckboxProps as NextUICheckboxProps, 5 | } from '@nextui-org/checkbox' 6 | 7 | interface CheckboxProps extends NextUICheckboxProps {} 8 | 9 | function Checkbox(props: CheckboxProps) { 10 | return 11 | } 12 | 13 | export default Checkbox 14 | -------------------------------------------------------------------------------- /src-tauri/gen/schemas/capabilities.json: -------------------------------------------------------------------------------- 1 | {"main":{"identifier":"main","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","shell:default",{"identifier":"shell:allow-execute","allow":[{"args":false,"cmd":"","name":"./bin/server","sidecar":true}]}],"platforms":["linux","macOS","windows"]}} -------------------------------------------------------------------------------- /src/components/Toast/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Toaster as NativeToaster, toast } from 'sonner' 3 | import { useTheme } from 'next-themes' 4 | 5 | export function Toaster() { 6 | const { theme } = useTheme() 7 | return ( 8 | 13 | ) 14 | } 15 | 16 | export { toast } 17 | -------------------------------------------------------------------------------- /src/assets/icons/tick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/Layout/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React, { ComponentProps } from 'react' 2 | 3 | import { LayoutContext } from './context' 4 | 5 | function Footer(props: ComponentProps<'div'>) { 6 | const { isValid } = React.useContext(LayoutContext) 7 | 8 | if (!isValid) { 9 | throw new Error('`Layout.Footer` must be used inside `Layout` component.') 10 | } 11 | 12 | return
13 | } 14 | 15 | export default Footer 16 | -------------------------------------------------------------------------------- /src/components/Layout/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { ComponentProps } from 'react' 2 | 3 | import { LayoutContext } from './context' 4 | 5 | function Header(props: ComponentProps<'div'>) { 6 | const { isValid } = React.useContext(LayoutContext) 7 | 8 | if (!isValid) { 9 | throw new Error('`Layout.Header` must be used inside `Layout` component.') 10 | } 11 | 12 | return
13 | } 14 | 15 | export default Header 16 | -------------------------------------------------------------------------------- /src/providers/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ThemeProvider as NextThemeProvider } from 'next-themes' 3 | 4 | function ThemeProvider({ children }: { children: React.ReactNode }) { 5 | return ( 6 | 11 | {children} 12 | 13 | ) 14 | } 15 | 16 | export default ThemeProvider 17 | -------------------------------------------------------------------------------- /src/components/Progress/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | CircularProgress as NextUICircularProgress, 4 | type CircularProgressProps as NextUICircularProgressProps, 5 | } from '@nextui-org/progress' 6 | 7 | interface CircularProgressProps extends NextUICircularProgressProps {} 8 | 9 | function CircularProgress(props: CircularProgressProps) { 10 | return 11 | } 12 | 13 | export default CircularProgress 14 | -------------------------------------------------------------------------------- /src/components/Tooltip/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Tooltip as NextUITooltip, 4 | type TooltipProps as NextUITooltipProps, 5 | } from '@nextui-org/tooltip' 6 | 7 | interface TooltipProps extends NextUITooltipProps {} 8 | 9 | function Tooltip(props: TooltipProps) { 10 | return ( 11 | 12 | {props?.children} 13 | 14 | ) 15 | } 16 | 17 | export default Tooltip 18 | -------------------------------------------------------------------------------- /src/providers/UIProvider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { NextUIProvider } from '@nextui-org/react' 3 | 4 | function UIProvider({ children }: { children: React.ReactNode }) { 5 | return ( 6 | // vaul-drawer-wrapper="" is required here for scaling effect for drawer effect. See @/components/Drawer 7 | 8 | {children} 9 | 10 | ) 11 | } 12 | 13 | export default UIProvider 14 | -------------------------------------------------------------------------------- /src-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "Node", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "incremental": true, 14 | "paths": { 15 | "@/*": ["./src/*"] 16 | } 17 | }, 18 | "include": ["**/*.ts", "./*.mjs"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/animation.ts: -------------------------------------------------------------------------------- 1 | import { Variants } from 'framer-motion' 2 | 3 | export const defaultEntryExitTransition: Variants = { 4 | initial: { scale: 0.9, opacity: 0 }, 5 | animate: { 6 | scale: 1, 7 | opacity: 1, 8 | transition: { 9 | duration: 0.6, 10 | bounce: 0.3, 11 | type: 'spring', 12 | }, 13 | }, 14 | exit: { 15 | scale: 0.9, 16 | opacity: 0, 17 | transition: { 18 | duration: 0.2, 19 | bounce: 0.2, 20 | type: 'spring', 21 | }, 22 | }, 23 | } as const 24 | -------------------------------------------------------------------------------- /src/assets/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/Divider/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Divider as NextUIDivider, 4 | type DividerProps as NextUIDividerProps, 5 | } from '@nextui-org/divider' 6 | 7 | import { cn } from '@/utils/tailwind' 8 | 9 | interface DividerProps extends NextUIDividerProps {} 10 | 11 | function Divider(props: DividerProps) { 12 | return ( 13 | 17 | ) 18 | } 19 | 20 | export default Divider 21 | -------------------------------------------------------------------------------- /src/components/Tabs/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Tabs as NextUITabs, 4 | Tab as NextUITab, 5 | type TabsProps as NextUITabsProps, 6 | type TabItemProps as NextUITabProps, 7 | } from '@nextui-org/tabs' 8 | 9 | interface TabsProps extends NextUITabsProps {} 10 | 11 | function Tabs(props: TabsProps) { 12 | return 13 | } 14 | 15 | interface TabProps extends NextUITabProps {} 16 | 17 | export function Tab(props: TabProps) { 18 | return 19 | } 20 | 21 | export default Tabs 22 | -------------------------------------------------------------------------------- /src-server/migrations/0000_clear_shockwave.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE posts ( 2 | `id` integer PRIMARY KEY NOT NULL, 3 | `title` text NOT NULL, 4 | `body` text NOT NULL, 5 | `author_id` integer NOT NULL, 6 | `created_at` integer DEFAULT (strftime('%s', 'now')), 7 | `updated_at` integer DEFAULT (strftime('%s', 'now')), 8 | FOREIGN KEY (`author_id`) REFERENCES users(`id`) 9 | ); 10 | --> statement-breakpoint 11 | CREATE TABLE users ( 12 | `id` integer PRIMARY KEY NOT NULL, 13 | `name` text NOT NULL, 14 | `email` text NOT NULL, 15 | `created_at` integer DEFAULT (strftime('%s', 'now')) 16 | ); 17 | -------------------------------------------------------------------------------- /public/scripts/disable-reload.js: -------------------------------------------------------------------------------- 1 | ;((document, window) => { 2 | if (!document || !window) { 3 | return 4 | } 5 | 6 | window.addEventListener( 7 | 'keydown', 8 | function (evt) { 9 | if ( 10 | (evt.ctrlKey || evt.metaKey) && 11 | (evt.key === 'r' || 12 | evt.key === 'R' || 13 | (evt.shiftKey && (evt.key === 'r' || evt.key === 'R'))) 14 | ) { 15 | evt.preventDefault() 16 | evt.stopImmediatePropagation() 17 | return false 18 | } 19 | }, 20 | { passive: false }, 21 | ) 22 | })(document, window) 23 | -------------------------------------------------------------------------------- /src-server/src/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { serve } from '@hono/node-server' 3 | import { migrate } from 'drizzle-orm/better-sqlite3/migrator' 4 | import { app, db } from './server' 5 | 6 | const PORT = 6969 7 | 8 | async function main() { 9 | migrate(db, { 10 | migrationsFolder: path.join(__dirname, '../migrations'), 11 | }) 12 | 13 | serve(app) 14 | .listen(PORT) 15 | .once('listening', () => { 16 | console.log(`🚀 Server started on port ${PORT}`) 17 | }) 18 | } 19 | 20 | main().catch((err) => { 21 | console.error(err) 22 | process.exit(1) 23 | }) 24 | -------------------------------------------------------------------------------- /src/assets/fonts/index.ts: -------------------------------------------------------------------------------- 1 | import localFont from "next/font/local"; 2 | 3 | const kayakRegular = localFont({ 4 | preload: true, 5 | variable: "--font-poppins", 6 | src: [ 7 | { 8 | path: "./poppins/Poppins-Regular.ttf", 9 | weight: "400", 10 | style: "normal", 11 | }, 12 | { 13 | path: "./poppins/Poppins-Italic.ttf", 14 | weight: "400", 15 | style: "italic", 16 | }, 17 | ], 18 | }); 19 | 20 | const fonts = [kayakRegular]; 21 | 22 | export const combinedFonts = fonts 23 | .map((font) => `${font.className} ${font.variable}`) 24 | .join(" "); 25 | -------------------------------------------------------------------------------- /src-server/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { serve } from '@hono/node-server' 3 | import { migrate } from 'drizzle-orm/better-sqlite3/migrator' 4 | import { app, db } from '../server' 5 | 6 | const PORT = 6969 7 | 8 | async function main() { 9 | migrate(db, { 10 | migrationsFolder: path.join(__dirname, '../migrations'), 11 | }) 12 | 13 | serve(app) 14 | .listen(PORT) 15 | .once('listening', () => { 16 | console.log(`🚀 Server started on port ${PORT}`) 17 | }) 18 | } 19 | 20 | main().catch((err) => { 21 | console.error(err) 22 | process.exit(1) 23 | }) 24 | -------------------------------------------------------------------------------- /src/assets/icons/low-res-heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/Select/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Select as NextUISelect, 4 | SelectItem as NextUISelectItem, 5 | type SelectProps as NextUISelectProps, 6 | type SelectItemProps as NextUISelectItemProps, 7 | } from '@nextui-org/select' 8 | 9 | interface SelectProps extends NextUISelectProps {} 10 | 11 | function Select(props: SelectProps) { 12 | return 13 | } 14 | 15 | interface SelectItemProps extends NextUISelectItemProps {} 16 | 17 | export function SelectItem(props: SelectItemProps) { 18 | return 19 | } 20 | 21 | export default Select 22 | -------------------------------------------------------------------------------- /public/scripts/disable-context-menu.js: -------------------------------------------------------------------------------- 1 | ;((document, window) => { 2 | if (!document || !window) { 3 | return 4 | } 5 | 6 | const isReleaseMode = 7 | document.currentScript.getAttribute('data-env') === 'production' 8 | 9 | if (isReleaseMode) { 10 | document.addEventListener( 11 | 'contextmenu', 12 | (e) => { 13 | e.preventDefault() 14 | return false 15 | }, 16 | { capture: true }, 17 | ) 18 | 19 | document.addEventListener( 20 | 'selectstart', 21 | (e) => { 22 | e.preventDefault() 23 | return false 24 | }, 25 | { capture: true }, 26 | ) 27 | } 28 | })(document, window) 29 | -------------------------------------------------------------------------------- /src-server/esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type{import("esbuild").BuildOptions} */ 2 | 3 | import esbuild from 'esbuild' 4 | import 'dotenv/config' 5 | 6 | const define = {} 7 | 8 | Object.keys(process.env).forEach((key) => { 9 | define[`process.env.${key}`] = JSON.stringify(process.env[key]) 10 | }) 11 | 12 | esbuild 13 | .build({ 14 | entryPoints: ['./src/server/index.ts'], 15 | bundle: true, 16 | minify: true, 17 | sourcemap: false, 18 | outfile: './bundle/server.bundle.js', 19 | platform: 'node', 20 | target: ['node20.0'], 21 | logLevel: 'info', 22 | define, 23 | external: ['better-sqlite3'], 24 | }) 25 | .catch(() => process.exit(1)) 26 | -------------------------------------------------------------------------------- /src-server/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function aggregateOneToMany< 2 | TRow extends Record, 3 | TOne extends keyof TRow, 4 | TMany extends keyof TRow, 5 | >( 6 | rows: TRow[], 7 | one: TOne, 8 | many: TMany, 9 | ): { 10 | [K in TOne]: TRow[TOne] & { [K in TMany]: NonNullable[] } 11 | }[] { 12 | const map: Record = {} 13 | for (const row of rows) { 14 | const id = row[one] 15 | if (!map[id]) { 16 | map[id] = { one: row[one], many: [] } 17 | } 18 | if (row[many] != null) { 19 | map[id]!.many.push(row[many]) 20 | } 21 | } 22 | return Object.values(map).map((r) => ({ ...r.one, [many]: r.many })) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Icon/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import registry from './registry' 4 | 5 | export interface IconProps { 6 | name: keyof typeof registry 7 | size?: number 8 | className?: string 9 | } 10 | 11 | function Icon(props: IconProps) { 12 | const { name, size = 20, className } = props 13 | 14 | const SVGComponent = registry[name] 15 | 16 | if (SVGComponent == null) { 17 | // eslint-disable-next-line no-console 18 | console.warn(`No such icon named ${name}`) 19 | return null 20 | } 21 | 22 | return ( 23 | 28 | ) 29 | } 30 | 31 | export default Icon 32 | -------------------------------------------------------------------------------- /src/utils/string.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts string duration to milliseconds 3 | * 4 | * @param {string} duration: Time Duration. The format can be "HH:MM:SS" or "HH:MM:SS.mm" 5 | * @returns {number}: The converted milliseconds. If the format is wrong, it'll return 0. 6 | */ 7 | export function convertDurationToMilliseconds(duration: string) { 8 | try { 9 | const [h, m, s] = duration.split(':') 10 | const hours = Number.parseInt(h, 10) 11 | const minutes = Number.parseInt(m, 10) 12 | const seconds = Number.parseFloat(s) 13 | const milliseconds = 14 | hours * 60 * 60 * 1000 + minutes * 60 * 1000 + seconds * 1000 15 | return Number.isNaN(milliseconds) ? 0 : milliseconds 16 | } catch (_) { 17 | return 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src-tauri/capabilities/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "main", 4 | "description": "Capability for the main window", 5 | "platforms": ["linux", "macOS", "windows"], 6 | "local": true, 7 | "windows": ["main"], 8 | "permissions": [ 9 | "path:default", 10 | "event:default", 11 | "window:default", 12 | "app:default", 13 | "resources:default", 14 | "menu:default", 15 | "tray:default", 16 | "shell:default", 17 | { 18 | "identifier": "shell:allow-execute", 19 | "allow": [ 20 | { 21 | "args": false, 22 | "cmd": "", 23 | "name": "./bin/server", 24 | "sidecar": true 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Button as NextButton, 4 | ButtonProps as NextButtonProps, 5 | } from '@nextui-org/button' 6 | 7 | import { cn } from '@/utils/tailwind' 8 | 9 | export interface ButtonProps extends NextButtonProps { 10 | color?: NextButtonProps['color'] 11 | variant?: NextButtonProps['variant'] 12 | } 13 | 14 | function Button(props: ButtonProps) { 15 | const { color, variant, ...nextButtonProps } = props 16 | 17 | return ( 18 | 25 | ) 26 | } 27 | 28 | export default Button 29 | -------------------------------------------------------------------------------- /src/types/compression.ts: -------------------------------------------------------------------------------- 1 | export const extensions = { 2 | video: { mp4: 'mp4', mov: 'mov', mkv: 'mkv', webm: 'webm', avi: 'avi' }, 3 | } as const 4 | 5 | export const compressionPresets = { 6 | ironclad: 'ironclad', 7 | thunderbolt: 'thunderbolt', 8 | } as const 9 | 10 | export type CompressionResult = { 11 | fileName: string 12 | filePath: string 13 | } 14 | 15 | export enum CustomEvents { 16 | VideoCompressionProgress = 'VideoCompressionProgress', 17 | CancelInProgressCompression = 'CancelInProgressCompression', 18 | } 19 | 20 | export type VideoCompressionProgress = { 21 | videoId: string 22 | fileName: string 23 | currentDuration: string 24 | } 25 | 26 | export type VideoThumbnail = { 27 | id: string 28 | fileName: string 29 | filePath: string 30 | } 31 | -------------------------------------------------------------------------------- /src-server/README.md: -------------------------------------------------------------------------------- 1 | # Running Node.js project as a single executable binary 2 | 3 | Steps: 4 | 5 | 1. Make a single javascript bundle file using `esbuild`. 6 | 7 | ``` 8 | pnpm bundle 9 | ``` 10 | 11 | 2. Package the bundled file into a single executable binary using `@yao-pkg/pkg` 12 | 13 | ``` 14 | pkg bundle/bundle.js -o ./bin/server- 15 | ``` 16 | 17 | You need to make binaries for the platform you want to support. By default it generates one for the platform that your machine is using. See [target](https://github.com/yao-pkg/pkg?tab=readme-ov-file#targets) section for more info. 18 | 19 | OR 20 | 21 | If you have Rust installed, you can just run: 22 | 23 | ``` 24 | pnpm package 25 | ``` 26 | 27 | The above command will add appropriate binary name of your platform. 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "Node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": [ 26 | "next-env.d.ts", 27 | "**/*.ts", 28 | "**/*.tsx", 29 | "./dist/types/**/*.ts", 30 | ".next/types/**/*.ts", 31 | "**/*.mjs" 32 | ], 33 | "exclude": ["node_modules"] 34 | } 35 | -------------------------------------------------------------------------------- /src/assets/icons/curved-arrow.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 8 | 9 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/icons/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/icons/redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/icons/file-explorer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/icons/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/Image/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Image as NextUIImage, 4 | type ImageProps as NextUIImageProps, 5 | } from '@nextui-org/image' 6 | 7 | interface ImageProps { 8 | src: string 9 | alt: string 10 | } 11 | function Image(props: ImageProps & Exclude) { 12 | const { ...nextImageProps } = props 13 | 14 | const [isFallbackImage, setIsFallbackImage] = React.useState(false) 15 | return ( 16 | { 18 | setIsFallbackImage(false) 19 | }} 20 | onError={() => { 21 | setIsFallbackImage(true) 22 | }} 23 | {...nextImageProps} 24 | {...(isFallbackImage 25 | ? { 26 | src: 27 | nextImageProps?.fallbackSrc?.toString() ?? '/default-blurred.jpg', 28 | fallbackSrc: null, 29 | } 30 | : {})} 31 | /> 32 | ) 33 | } 34 | 35 | export default Image 36 | -------------------------------------------------------------------------------- /src/assets/icons/save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/icons/trash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | import analyzeBundle from '@next/bundle-analyzer' 4 | 5 | const packageJSON = await import('./package.json', { 6 | assert: { type: 'json' }, 7 | }) 8 | 9 | const withBundleAnalyzer = analyzeBundle({ 10 | enabled: process.env.ANALYZE === 'true', 11 | }) 12 | 13 | const nextConfig = withBundleAnalyzer({ 14 | output: 'export', 15 | distDir: './dist', 16 | cleanDistDir: true, 17 | reactStrictMode: false, 18 | webpack(config, { webpack }) { 19 | config.module.rules.push({ 20 | test: /\.svg$/, 21 | use: ['@svgr/webpack'], 22 | }) 23 | 24 | if (process.env.NODE_ENV === 'production') { 25 | config.plugins.push( 26 | new webpack.DefinePlugin({ 27 | 'globalThis.__DEV__': false, 28 | }), 29 | ) 30 | } 31 | 32 | return config 33 | }, 34 | env: { 35 | version: packageJSON?.default?.version, 36 | }, 37 | }) 38 | 39 | export default nextConfig 40 | -------------------------------------------------------------------------------- /src/assets/icons/question.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/icons/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 12 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/head.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function Head() { 4 | return ( 5 | 6 | 10 | 11 | 16 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | 36 | export default Head; 37 | -------------------------------------------------------------------------------- /src/assets/icons/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/icons/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version = "1.0.0" 4 | description = "" 5 | authors = ["you"] 6 | default-run = "app" 7 | edition = "2021" 8 | rust-version = "1.75" 9 | 10 | 11 | [build-dependencies] 12 | tauri-build = { version = "2.0.0-beta", features = [] } 13 | 14 | [dependencies] 15 | serde_json = "1.0" 16 | serde = { version = "1.0", features = ["derive"] } 17 | tauri = { version = "2.0.0-beta.19", features = ["protocol-asset"] } 18 | tokio = { version = "1.34.0", features = [ 19 | "rt", 20 | "rt-multi-thread", 21 | "macros", 22 | "process", 23 | ] } 24 | log = "0.4.21" 25 | crossbeam-channel = "0.5.12" 26 | strum = { version = "0.26.2", features = ["derive"] } 27 | tauri-plugin-shell = "2.0.0-beta.4" 28 | 29 | 30 | [features] 31 | # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. 32 | # If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes. 33 | # DO NOT REMOVE!!z 34 | custom-protocol = ["tauri/custom-protocol"] 35 | -------------------------------------------------------------------------------- /src/assets/icons/drag-and-drop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface Props { 4 | children?: React.ReactNode 5 | } 6 | 7 | interface State { 8 | hasError: boolean 9 | } 10 | 11 | class ErrorBoundary extends React.Component { 12 | constructor(props: Props) { 13 | super(props) 14 | 15 | this.state = { hasError: false } 16 | } 17 | 18 | static getDerivedStateFromError() { 19 | return { hasError: true } 20 | } 21 | 22 | componentDidCatch(error: Error, errorInfo: any) { 23 | // eslint-disable-next-line no-console 24 | console.log({ error, errorInfo }) 25 | } 26 | 27 | render() { 28 | const { hasError } = this.state 29 | const { children } = this.props 30 | if (hasError) { 31 | return ( 32 |
33 |

Oops, there is an error!

34 | 40 |
41 | ) 42 | } 43 | 44 | return children 45 | } 46 | } 47 | 48 | export default ErrorBoundary 49 | -------------------------------------------------------------------------------- /src-server/src/schema.ts: -------------------------------------------------------------------------------- 1 | import { sql } from 'drizzle-orm' 2 | import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' 3 | import { createInsertSchema, createSelectSchema } from 'drizzle-zod' 4 | 5 | export const users = sqliteTable('users', { 6 | id: integer('id').primaryKey(), 7 | name: text('name').notNull(), 8 | email: text('email').notNull(), 9 | createdAt: integer('created_at', { mode: 'timestamp' }).default( 10 | sql`(strftime('%s', 'now'))`, 11 | ), 12 | }) 13 | export const insertUserSchema = createInsertSchema(users) 14 | export const selectUserSchema = createSelectSchema(users) 15 | 16 | export const posts = sqliteTable('posts', { 17 | id: integer('id').primaryKey(), 18 | title: text('title').notNull(), 19 | body: text('body').notNull(), 20 | authorId: integer('author_id') 21 | .notNull() 22 | .references(() => users.id), 23 | createdAt: integer('created_at', { mode: 'timestamp' }).default( 24 | sql`(strftime('%s', 'now'))`, 25 | ), 26 | updateAt: integer('updated_at', { mode: 'timestamp' }).default( 27 | sql`(strftime('%s', 'now'))`, 28 | ), 29 | }) 30 | export const insertPostSchema = createInsertSchema(posts) 31 | export const selectPostSchema = createSelectSchema(posts) 32 | -------------------------------------------------------------------------------- /src/ui/Patterns/DotPattern.tsx: -------------------------------------------------------------------------------- 1 | import { useId } from 'react' 2 | 3 | import { cn } from '@/utils/tailwind' 4 | 5 | interface DotPatternProps { 6 | width?: any 7 | height?: any 8 | x?: any 9 | y?: any 10 | cx?: any 11 | cy?: any 12 | cr?: any 13 | className?: string 14 | [key: string]: any 15 | } 16 | function DotPattern({ 17 | width = 50, 18 | height = 50, 19 | x = 0, 20 | y = 0, 21 | cx = 1, 22 | cy = 1, 23 | cr = 1, 24 | className, 25 | ...props 26 | }: DotPatternProps) { 27 | const id = useId() 28 | 29 | return ( 30 | 53 | ) 54 | } 55 | 56 | export default DotPattern 57 | -------------------------------------------------------------------------------- /src/components/ThemeSwitcher/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useTheme } from 'next-themes' 3 | 4 | import Button from '../Button' 5 | import Icon from '../Icon' 6 | import Tooltip from '../Tooltip' 7 | 8 | interface ThemeSwitcherChildrenProps { 9 | theme: string | undefined 10 | 11 | setTheme(theme: string | undefined): void 12 | } 13 | 14 | interface ThemeSwitcherProps { 15 | children?(props: ThemeSwitcherChildrenProps): React.ReactNode 16 | } 17 | 18 | function ThemeSwitcher(props: ThemeSwitcherProps) { 19 | const { children } = props 20 | 21 | const { theme, setTheme } = useTheme() 22 | const [mounted, setMounted] = React.useState(false) 23 | 24 | React.useEffect(() => { 25 | setMounted(true) 26 | }, []) 27 | 28 | if (!mounted) { 29 | return null 30 | } 31 | 32 | return children == null ? ( 33 | 34 | 43 | 44 | ) : ( 45 | children({ theme, setTheme }) 46 | ) 47 | } 48 | 49 | export default ThemeSwitcher 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cross platform desktop app with local-first architecture. 2 | 3 | This project is an example of how to use Node.js as a sidecar in Tauri if you find building the backend in Rust very complex. While, the point of using Rust is to make the desktop app perform better and secure, but you still have the option to use other tech. You can even use Python with this approach. You can find this same project whose backend was written in Rust from [here](https://github.com/codeforreal1/Local-First-Desktop-App-Rust). 4 | 5 | ### Tech: 6 | 7 | - Next.js(UI) 8 | - Node.js(Server) 9 | - Tauri 10 | - Sqlite with Drizzle ORM 11 | 12 | ### Building 13 | 14 | This project uses Hono server created in Node.js as a sidecar for Tauri. The project uses `@yao-pkg/pkg` for compiling the Node.js project into a single binary after making a single bundle js file using esbuild. 15 | 16 | For local development: 17 | 18 | ``` 19 | cd src-server 20 | pnpm install 21 | pnpm server:build 22 | ``` 23 | 24 | `pnpm server:build` will create a bundle file and place the server binary inside `src-tauri/bin/server-. See [Tauri Sidecar](https://tauri.app/v1/guides/building/sidecar/) for more info. 25 | 26 | Then you can run the tauri dev server using: 27 | 28 | ``` 29 | pnpm tauri:dev 30 | pnpm next:dev 31 | ``` 32 | 33 | For prod app build: 34 | 35 | ``` 36 | pnpm tauri:build 37 | ``` 38 | -------------------------------------------------------------------------------- /src/app/(root)/types.ts: -------------------------------------------------------------------------------- 1 | import { extensions, compressionPresets } from '@/types/compression' 2 | 3 | export type VideoConfig = { 4 | convertToExtension: keyof typeof extensions.video 5 | presetName: keyof typeof compressionPresets 6 | shouldDisableCompression: boolean 7 | shouldMuteVideo: boolean 8 | quality?: number | null 9 | shouldEnableQuality?: boolean 10 | } 11 | 12 | export type Video = { 13 | id?: string | null 14 | isFileSelected: boolean 15 | pathRaw?: string | null 16 | path?: string | null 17 | fileName?: string | null 18 | mimeType?: string | null 19 | sizeInBytes?: number | null 20 | size?: string | null 21 | extension?: null | string 22 | thumbnailPathRaw?: string | null 23 | thumbnailPath?: string | null 24 | isThumbnailGenerating?: boolean 25 | videoDurationMilliseconds?: number | null 26 | videDurationRaw?: string | null 27 | isCompressing?: boolean 28 | isCompressionSuccessful?: boolean 29 | compressedVideo?: { 30 | pathRaw?: string | null 31 | path?: string | null 32 | fileName?: string | null 33 | fileNameToDisplay?: string | null 34 | mimeType?: string | null 35 | sizeInBytes?: number | null 36 | size?: string | null 37 | extension?: null | string 38 | isSaved?: boolean 39 | isSaving?: boolean 40 | savedPath?: string 41 | } | null 42 | compressionProgress?: number 43 | config: VideoConfig 44 | } 45 | -------------------------------------------------------------------------------- /src/tauri/components/VideoPicker/index.tsx: -------------------------------------------------------------------------------- 1 | import { FileResponse, open } from '@tauri-apps/plugin-dialog' 2 | import { extensions } from '@/types/compression' 3 | 4 | type ChildrenFnParams = { onClick: () => void } 5 | 6 | type Error = { 7 | message: string 8 | data?: any 9 | } 10 | 11 | type VideoPickerProps = { 12 | children: (_: ChildrenFnParams) => React.ReactNode 13 | onSuccess?: (_: { file: FileResponse }) => void 14 | onError?: (_: Error) => void 15 | } 16 | 17 | const videoExtensions = Object.keys(extensions?.video) 18 | 19 | export default function VideoPicker({ 20 | children, 21 | onSuccess, 22 | onError, 23 | }: VideoPickerProps) { 24 | async function onClick() { 25 | try { 26 | const file = await open({ 27 | directory: false, 28 | multiple: false, 29 | title: 'Select video to compress', 30 | filters: [{ name: 'video', extensions: videoExtensions }], 31 | }) 32 | if (file == null) { 33 | const message = 'File selection config is invalid.' 34 | // eslint-disable-next-line no-console 35 | console.warn(message) 36 | onError?.({ message }) 37 | return 38 | } 39 | onSuccess?.({ file }) 40 | } catch (error: any) { 41 | onError?.({ 42 | message: error?.message ?? 'Could not select video.', 43 | data: error, 44 | }) 45 | } 46 | } 47 | 48 | return children({ onClick }) 49 | } 50 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import React from 'react' 4 | import Script from 'next/script' 5 | 6 | import './globals.css' 7 | 8 | import { Toaster } from '@/components/Toast' 9 | import { combinedFonts } from '@/assets/fonts' 10 | import UIProvider from '../providers/UIProvider' 11 | import ThemeProvider from '../providers/ThemeProvider' 12 | import Head from './head' 13 | 14 | const version = process.env.version 15 | const env = process.env.NODE_ENV 16 | 17 | export default function RootLayout({ 18 | children, 19 | }: { 20 | children: React.ReactNode 21 | }) { 22 | return ( 23 | 24 | 25 | 26 | 27 | {children} 28 | 29 | 30 |