├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.yaml ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── README.md ├── build ├── entitlements.mac.plist ├── icon.icns ├── icon.ico └── icon.png ├── drizzle.config.ts ├── electron-builder.yml ├── electron.vite.config.ts ├── package.json ├── pnpm-lock.yaml ├── resources └── icon.png ├── src ├── db │ └── schema.ts ├── main │ ├── db.ts │ └── index.ts ├── preload │ ├── index.d.ts │ └── index.ts └── renderer │ ├── index.html │ └── src │ ├── App.tsx │ ├── assets │ ├── base.css │ ├── electron.svg │ ├── main.css │ └── wavy-lines.svg │ ├── components │ └── Versions.tsx │ ├── db.ts │ ├── env.d.ts │ └── main.tsx ├── tsconfig.json ├── tsconfig.node.json └── tsconfig.web.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | .gitignore 5 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:react/recommended', 5 | 'plugin:react/jsx-runtime', 6 | '@electron-toolkit/eslint-config-ts/recommended', 7 | '@electron-toolkit/eslint-config-prettier' 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | .DS_Store 5 | *.log* 6 | *.db 7 | drizzle -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | electron_mirror=https://npmmirror.com/mirrors/electron/ 2 | electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ 3 | shamefully-hoist=true 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | pnpm-lock.yaml 4 | LICENSE.md 5 | tsconfig.json 6 | tsconfig.*.json 7 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | semi: false 3 | printWidth: 100 4 | trailingComma: none 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" 12 | }, 13 | "runtimeArgs": ["--sourcemap"], 14 | "env": { 15 | "REMOTE_DEBUGGING_PORT": "9222" 16 | } 17 | }, 18 | { 19 | "name": "Debug Renderer Process", 20 | "port": 9222, 21 | "request": "attach", 22 | "type": "chrome", 23 | "webRoot": "${workspaceFolder}/src/renderer", 24 | "timeout": 60000, 25 | "presentation": { 26 | "hidden": true 27 | } 28 | } 29 | ], 30 | "compounds": [ 31 | { 32 | "name": "Debug All", 33 | "configurations": ["Debug Main Process", "Debug Renderer Process"], 34 | "presentation": { 35 | "order": 1 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[json]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Demo of using SQLite with Drizzle ORM in Electron. 2 | 3 | ## Usage 4 | 5 | ```bash 6 | pnpm i 7 | 8 | pnpm drizzle-kit generate:sqlite 9 | 10 | npm run dev 11 | ``` -------------------------------------------------------------------------------- /build/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/electron-drizzle-sqlite-demo/f80a3c55df236fbe8a5e8ba8bbfb3ec7152be935/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/electron-drizzle-sqlite-demo/f80a3c55df236fbe8a5e8ba8bbfb3ec7152be935/build/icon.ico -------------------------------------------------------------------------------- /build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/electron-drizzle-sqlite-demo/f80a3c55df236fbe8a5e8ba8bbfb3ec7152be935/build/icon.png -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'drizzle-kit' 2 | 3 | export default { 4 | schema: './src/db/schema.ts', 5 | out: './drizzle', 6 | driver: 'better-sqlite' 7 | } satisfies Config 8 | -------------------------------------------------------------------------------- /electron-builder.yml: -------------------------------------------------------------------------------- 1 | appId: com.electron.app 2 | productName: sqlite-demo 3 | directories: 4 | buildResources: build 5 | files: 6 | - '!**/.vscode/*' 7 | - '!src/*' 8 | - '!electron.vite.config.{js,ts,mjs,cjs}' 9 | - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' 10 | - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' 11 | - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}' 12 | asarUnpack: 13 | - resources/** 14 | win: 15 | executableName: sqlite-demo 16 | nsis: 17 | artifactName: ${name}-${version}-setup.${ext} 18 | shortcutName: ${productName} 19 | uninstallDisplayName: ${productName} 20 | createDesktopShortcut: always 21 | mac: 22 | entitlementsInherit: build/entitlements.mac.plist 23 | extendInfo: 24 | - NSCameraUsageDescription: Application requests access to the device's camera. 25 | - NSMicrophoneUsageDescription: Application requests access to the device's microphone. 26 | - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. 27 | - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. 28 | notarize: false 29 | dmg: 30 | artifactName: ${name}-${version}.${ext} 31 | linux: 32 | target: 33 | - AppImage 34 | - snap 35 | - deb 36 | maintainer: electronjs.org 37 | category: Utility 38 | appImage: 39 | artifactName: ${name}-${version}.${ext} 40 | npmRebuild: false 41 | publish: 42 | provider: generic 43 | url: https://example.com/auto-updates 44 | electronDownload: 45 | mirror: https://npmmirror.com/mirrors/electron/ 46 | -------------------------------------------------------------------------------- /electron.vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { defineConfig, externalizeDepsPlugin } from 'electron-vite' 3 | import react from '@vitejs/plugin-react' 4 | 5 | export default defineConfig({ 6 | main: { 7 | plugins: [externalizeDepsPlugin()] 8 | }, 9 | preload: { 10 | plugins: [externalizeDepsPlugin()] 11 | }, 12 | renderer: { 13 | resolve: { 14 | alias: { 15 | '@renderer': resolve('src/renderer/src') 16 | } 17 | }, 18 | plugins: [react()] 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sqlite-demo", 3 | "version": "1.0.0", 4 | "description": "An Electron application with React and TypeScript", 5 | "main": "./out/main/index.js", 6 | "author": "example.com", 7 | "homepage": "https://electron-vite.org", 8 | "scripts": { 9 | "format": "prettier --write .", 10 | "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", 11 | "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", 12 | "typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false", 13 | "typecheck": "npm run typecheck:node && npm run typecheck:web", 14 | "start": "electron-vite preview", 15 | "dev": "electron-vite dev", 16 | "build": "npm run typecheck && electron-vite build", 17 | "postinstall": "electron-builder install-app-deps", 18 | "build:unpack": "npm run build && electron-builder --dir", 19 | "build:win": "npm run build && electron-builder --win", 20 | "build:mac": "electron-vite build && electron-builder --mac", 21 | "build:linux": "electron-vite build && electron-builder --linux" 22 | }, 23 | "dependencies": { 24 | "@electron-toolkit/preload": "^3.0.0", 25 | "@electron-toolkit/utils": "^3.0.0", 26 | "better-sqlite3": "^9.5.0", 27 | "drizzle-orm": "^0.30.9" 28 | }, 29 | "devDependencies": { 30 | "@electron-toolkit/eslint-config-prettier": "^2.0.0", 31 | "@electron-toolkit/eslint-config-ts": "^1.0.1", 32 | "@electron-toolkit/tsconfig": "^1.0.1", 33 | "@types/node": "^18.19.9", 34 | "@types/react": "^18.2.48", 35 | "@types/react-dom": "^18.2.18", 36 | "@vitejs/plugin-react": "^4.2.1", 37 | "drizzle-kit": "^0.20.17", 38 | "electron": "^28.2.0", 39 | "electron-builder": "^24.9.1", 40 | "electron-vite": "^2.0.0", 41 | "eslint": "^8.56.0", 42 | "eslint-plugin-react": "^7.33.2", 43 | "prettier": "^3.2.4", 44 | "react": "^18.2.0", 45 | "react-dom": "^18.2.0", 46 | "typescript": "^5.3.3", 47 | "vite": "^5.0.12" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/electron-drizzle-sqlite-demo/f80a3c55df236fbe8a5e8ba8bbfb3ec7152be935/resources/icon.png -------------------------------------------------------------------------------- /src/db/schema.ts: -------------------------------------------------------------------------------- 1 | import { int, sqliteTable, text } from "drizzle-orm/sqlite-core"; 2 | 3 | export const posts = sqliteTable('posts', { 4 | id: int("id").primaryKey().default(0), 5 | title: text("title").notNull().default(""), 6 | }) -------------------------------------------------------------------------------- /src/main/db.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from 'drizzle-orm/better-sqlite3' 2 | import Database from 'better-sqlite3' 3 | import { migrate } from 'drizzle-orm/better-sqlite3/migrator' 4 | import * as schema from '../db/schema' 5 | import fs from 'fs' 6 | import { app } from 'electron' 7 | import path from 'path' 8 | 9 | const dbPath = import.meta.env.DEV ? 'sqlite.db' : path.join(app.getPath('userData'), 'data.db') 10 | 11 | fs.mkdirSync(path.dirname(dbPath), { recursive: true }) 12 | 13 | const sqlite = new Database( 14 | dbPath 15 | ) 16 | 17 | export const db = drizzle(sqlite, { schema }) 18 | 19 | function toDrizzleResult(row: Record) 20 | function toDrizzleResult(rows: Record | Array>) { 21 | if (!rows) { 22 | return [] 23 | } 24 | if (Array.isArray(rows)) { 25 | return rows.map((row) => { 26 | return Object.keys(row).map((key) => row[key]) 27 | }) 28 | } else { 29 | return Object.keys(rows).map((key) => rows[key]) 30 | } 31 | } 32 | 33 | export const execute = async (e, sqlstr, params, method) => { 34 | const result = sqlite.prepare(sqlstr) 35 | const ret = result[method](...params) 36 | return toDrizzleResult(ret) 37 | } 38 | 39 | export const runMigrate = async () => { 40 | migrate(db, { 41 | migrationsFolder: path.join(__dirname, '../../drizzle') 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /src/main/index.ts: -------------------------------------------------------------------------------- 1 | import { app, shell, BrowserWindow, ipcMain } from 'electron' 2 | import { join } from 'path' 3 | import { electronApp, optimizer, is } from '@electron-toolkit/utils' 4 | import icon from '../../resources/icon.png?asset' 5 | import { execute, runMigrate } from './db' 6 | 7 | function createWindow(): void { 8 | // Create the browser window. 9 | const mainWindow = new BrowserWindow({ 10 | width: 900, 11 | height: 670, 12 | show: false, 13 | autoHideMenuBar: true, 14 | ...(process.platform === 'linux' ? { icon } : {}), 15 | webPreferences: { 16 | preload: join(__dirname, '../preload/index.js'), 17 | sandbox: false 18 | } 19 | }) 20 | 21 | mainWindow.on('ready-to-show', () => { 22 | mainWindow.show() 23 | }) 24 | 25 | mainWindow.webContents.setWindowOpenHandler((details) => { 26 | shell.openExternal(details.url) 27 | return { action: 'deny' } 28 | }) 29 | 30 | // HMR for renderer base on electron-vite cli. 31 | // Load the remote URL for development or the local html file for production. 32 | if (is.dev && process.env['ELECTRON_RENDERER_URL']) { 33 | mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) 34 | } else { 35 | mainWindow.loadFile(join(__dirname, '../renderer/index.html')) 36 | } 37 | } 38 | 39 | // This method will be called when Electron has finished 40 | // initialization and is ready to create browser windows. 41 | // Some APIs can only be used after this event occurs. 42 | app.whenReady().then(async () => { 43 | // Set app user model id for windows 44 | electronApp.setAppUserModelId('com.electron') 45 | 46 | // Default open or close DevTools by F12 in development 47 | // and ignore CommandOrControl + R in production. 48 | // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils 49 | app.on('browser-window-created', (_, window) => { 50 | optimizer.watchWindowShortcuts(window) 51 | }) 52 | 53 | // IPC test 54 | ipcMain.on('ping', () => console.log('pong')) 55 | ipcMain.handle('db:execute', execute) 56 | 57 | await runMigrate() 58 | createWindow() 59 | 60 | app.on('activate', function () { 61 | // On macOS it's common to re-create a window in the app when the 62 | // dock icon is clicked and there are no other windows open. 63 | if (BrowserWindow.getAllWindows().length === 0) createWindow() 64 | }) 65 | }) 66 | 67 | // Quit when all windows are closed, except on macOS. There, it's common 68 | // for applications and their menu bar to stay active until the user quits 69 | // explicitly with Cmd + Q. 70 | app.on('window-all-closed', () => { 71 | if (process.platform !== 'darwin') { 72 | app.quit() 73 | } 74 | }) 75 | 76 | // In this file you can include the rest of your app"s specific main process 77 | // code. You can also put them in separate files and require them here. 78 | -------------------------------------------------------------------------------- /src/preload/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ElectronAPI } from '@electron-toolkit/preload' 2 | 3 | declare global { 4 | interface Window { 5 | electron: ElectronAPI 6 | api: unknown 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/preload/index.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge, ipcRenderer } from 'electron' 2 | import { electronAPI } from '@electron-toolkit/preload' 3 | 4 | // Custom APIs for renderer 5 | const api = { 6 | execute: (...args) => ipcRenderer.invoke('db:execute', ...args) 7 | } 8 | 9 | // Use `contextBridge` APIs to expose Electron APIs to 10 | // renderer only if context isolation is enabled, otherwise 11 | // just add to the DOM global. 12 | if (process.contextIsolated) { 13 | try { 14 | contextBridge.exposeInMainWorld('electron', electronAPI) 15 | contextBridge.exposeInMainWorld('api', api) 16 | } catch (error) { 17 | console.error(error) 18 | } 19 | } else { 20 | // @ts-ignore (define in dts) 21 | window.electron = electronAPI 22 | // @ts-ignore (define in dts) 23 | window.api = api 24 | } 25 | -------------------------------------------------------------------------------- /src/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Electron 6 | 7 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/renderer/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { database } from "./db" 3 | import { posts } from "../../db/schema" 4 | 5 | function App(): JSX.Element { 6 | 7 | const [postList, setPosts] = useState([] as any[]) 8 | 9 | useEffect(() => { 10 | database.query.posts.findMany().then(result => { 11 | setPosts(result) 12 | }) 13 | }, []) 14 | 15 | return ( 16 |
17 |
18 |
{ 19 | e.preventDefault() 20 | 21 | const formData = new FormData(e.target as HTMLFormElement) 22 | const title = formData.get('title') as string 23 | if (title) { 24 | await database.insert(posts).values({ 25 | id: Math.floor(Math.random() * 1000), 26 | title 27 | }) 28 | 29 | // refetch 30 | const result = await database.query.posts.findMany() 31 | setPosts(result) 32 | } 33 | }}> 34 | 35 | 36 |
37 |
38 | {postList.map(post => { 39 | return ( 40 |
41 | {post.title} 42 |
43 | ) 44 | })} 45 |
46 | ) 47 | } 48 | 49 | export default App 50 | -------------------------------------------------------------------------------- /src/renderer/src/assets/base.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --ev-c-white: #ffffff; 3 | --ev-c-white-soft: #f8f8f8; 4 | --ev-c-white-mute: #f2f2f2; 5 | 6 | --ev-c-black: #1b1b1f; 7 | --ev-c-black-soft: #222222; 8 | --ev-c-black-mute: #282828; 9 | 10 | --ev-c-gray-1: #515c67; 11 | --ev-c-gray-2: #414853; 12 | --ev-c-gray-3: #32363f; 13 | 14 | --ev-c-text-1: rgba(255, 255, 245, 0.86); 15 | --ev-c-text-2: rgba(235, 235, 245, 0.6); 16 | --ev-c-text-3: rgba(235, 235, 245, 0.38); 17 | 18 | --ev-button-alt-border: transparent; 19 | --ev-button-alt-text: var(--ev-c-text-1); 20 | --ev-button-alt-bg: var(--ev-c-gray-3); 21 | --ev-button-alt-hover-border: transparent; 22 | --ev-button-alt-hover-text: var(--ev-c-text-1); 23 | --ev-button-alt-hover-bg: var(--ev-c-gray-2); 24 | } 25 | 26 | :root { 27 | --color-background: var(--ev-c-black); 28 | --color-background-soft: var(--ev-c-black-soft); 29 | --color-background-mute: var(--ev-c-black-mute); 30 | 31 | --color-text: var(--ev-c-text-1); 32 | } 33 | 34 | *, 35 | *::before, 36 | *::after { 37 | box-sizing: border-box; 38 | margin: 0; 39 | font-weight: normal; 40 | } 41 | 42 | ul { 43 | list-style: none; 44 | } 45 | 46 | body { 47 | min-height: 100vh; 48 | color: var(--color-text); 49 | background: var(--color-background); 50 | line-height: 1.6; 51 | font-family: 52 | Inter, 53 | -apple-system, 54 | BlinkMacSystemFont, 55 | 'Segoe UI', 56 | Roboto, 57 | Oxygen, 58 | Ubuntu, 59 | Cantarell, 60 | 'Fira Sans', 61 | 'Droid Sans', 62 | 'Helvetica Neue', 63 | sans-serif; 64 | text-rendering: optimizeLegibility; 65 | -webkit-font-smoothing: antialiased; 66 | -moz-osx-font-smoothing: grayscale; 67 | } 68 | -------------------------------------------------------------------------------- /src/renderer/src/assets/electron.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/renderer/src/assets/main.css: -------------------------------------------------------------------------------- 1 | @import './base.css'; 2 | 3 | body { 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | overflow: hidden; 8 | background-image: url('./wavy-lines.svg'); 9 | background-size: cover; 10 | user-select: none; 11 | } 12 | 13 | code { 14 | font-weight: 600; 15 | padding: 3px 5px; 16 | border-radius: 2px; 17 | background-color: var(--color-background-mute); 18 | font-family: 19 | ui-monospace, 20 | SFMono-Regular, 21 | SF Mono, 22 | Menlo, 23 | Consolas, 24 | Liberation Mono, 25 | monospace; 26 | font-size: 85%; 27 | } 28 | 29 | #root { 30 | display: flex; 31 | align-items: center; 32 | justify-content: center; 33 | flex-direction: column; 34 | margin-bottom: 80px; 35 | } 36 | 37 | .logo { 38 | margin-bottom: 20px; 39 | -webkit-user-drag: none; 40 | height: 128px; 41 | width: 128px; 42 | will-change: filter; 43 | transition: filter 300ms; 44 | } 45 | 46 | .logo:hover { 47 | filter: drop-shadow(0 0 1.2em #6988e6aa); 48 | } 49 | 50 | .creator { 51 | font-size: 14px; 52 | line-height: 16px; 53 | color: var(--ev-c-text-2); 54 | font-weight: 600; 55 | margin-bottom: 10px; 56 | } 57 | 58 | .text { 59 | font-size: 28px; 60 | color: var(--ev-c-text-1); 61 | font-weight: 700; 62 | line-height: 32px; 63 | text-align: center; 64 | margin: 0 10px; 65 | padding: 16px 0; 66 | } 67 | 68 | .tip { 69 | font-size: 16px; 70 | line-height: 24px; 71 | color: var(--ev-c-text-2); 72 | font-weight: 600; 73 | } 74 | 75 | .react { 76 | background: -webkit-linear-gradient(315deg, #087ea4 55%, #7c93ee); 77 | background-clip: text; 78 | -webkit-background-clip: text; 79 | -webkit-text-fill-color: transparent; 80 | font-weight: 700; 81 | } 82 | 83 | .ts { 84 | background: -webkit-linear-gradient(315deg, #3178c6 45%, #f0dc4e); 85 | background-clip: text; 86 | -webkit-background-clip: text; 87 | -webkit-text-fill-color: transparent; 88 | font-weight: 700; 89 | } 90 | 91 | .actions { 92 | display: flex; 93 | padding-top: 32px; 94 | margin: -6px; 95 | flex-wrap: wrap; 96 | justify-content: flex-start; 97 | } 98 | 99 | .action { 100 | flex-shrink: 0; 101 | padding: 6px; 102 | } 103 | 104 | .action a { 105 | cursor: pointer; 106 | text-decoration: none; 107 | display: inline-block; 108 | border: 1px solid transparent; 109 | text-align: center; 110 | font-weight: 600; 111 | white-space: nowrap; 112 | border-radius: 20px; 113 | padding: 0 20px; 114 | line-height: 38px; 115 | font-size: 14px; 116 | border-color: var(--ev-button-alt-border); 117 | color: var(--ev-button-alt-text); 118 | background-color: var(--ev-button-alt-bg); 119 | } 120 | 121 | .action a:hover { 122 | border-color: var(--ev-button-alt-hover-border); 123 | color: var(--ev-button-alt-hover-text); 124 | background-color: var(--ev-button-alt-hover-bg); 125 | } 126 | 127 | .versions { 128 | position: absolute; 129 | bottom: 30px; 130 | margin: 0 auto; 131 | padding: 15px 0; 132 | font-family: 'Menlo', 'Lucida Console', monospace; 133 | display: inline-flex; 134 | overflow: hidden; 135 | align-items: center; 136 | border-radius: 22px; 137 | background-color: #202127; 138 | backdrop-filter: blur(24px); 139 | } 140 | 141 | .versions li { 142 | display: block; 143 | float: left; 144 | border-right: 1px solid var(--ev-c-gray-1); 145 | padding: 0 20px; 146 | font-size: 14px; 147 | line-height: 14px; 148 | opacity: 0.8; 149 | &:last-child { 150 | border: none; 151 | } 152 | } 153 | 154 | @media (max-width: 720px) { 155 | .text { 156 | font-size: 20px; 157 | } 158 | } 159 | 160 | @media (max-width: 620px) { 161 | .versions { 162 | display: none; 163 | } 164 | } 165 | 166 | @media (max-width: 350px) { 167 | .tip, 168 | .actions { 169 | display: none; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/renderer/src/assets/wavy-lines.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/renderer/src/components/Versions.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | function Versions(): JSX.Element { 4 | const [versions] = useState(window.electron.process.versions) 5 | 6 | return ( 7 |
    8 |
  • Electron v{versions.electron}
  • 9 |
  • Chromium v{versions.chrome}
  • 10 |
  • Node v{versions.node}
  • 11 |
12 | ) 13 | } 14 | 15 | export default Versions 16 | -------------------------------------------------------------------------------- /src/renderer/src/db.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from 'drizzle-orm/sqlite-proxy' 2 | import * as schema from '../../db/schema' 3 | 4 | export const database = drizzle(async (...args) => { 5 | try { 6 | // @ts-expect-error 7 | const result = await window.api.execute(...args) 8 | return {rows: result} 9 | } catch (e: any) { 10 | console.error('Error from sqlite proxy server: ', e.response.data) 11 | return { rows: [] } 12 | } 13 | }, { 14 | schema: schema 15 | }) -------------------------------------------------------------------------------- /src/renderer/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/renderer/src/main.tsx: -------------------------------------------------------------------------------- 1 | import './assets/main.css' 2 | 3 | import React from 'react' 4 | import ReactDOM from 'react-dom/client' 5 | import App from './App' 6 | 7 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 8 | 9 | 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", 3 | "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/db/**/*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["electron-vite/node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.web.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.web.json", 3 | "include": [ 4 | "src/renderer/src/env.d.ts", 5 | "src/renderer/src/**/*", 6 | "src/renderer/src/**/*.tsx", 7 | "src/db/**/*" 8 | ], 9 | "compilerOptions": { 10 | "composite": true, 11 | "jsx": "react-jsx", 12 | "baseUrl": ".", 13 | "paths": { 14 | "@renderer/*": [ 15 | "src/renderer/src/*" 16 | ] 17 | } 18 | } 19 | } 20 | --------------------------------------------------------------------------------