feat: create separate component library subpackage

This commit is contained in:
ndom91 2024-06-26 18:01:09 +02:00
parent 6da1b444ba
commit 091b45d5e9
No known key found for this signature in database
49 changed files with 3140 additions and 59 deletions

View File

@ -1,5 +1,5 @@
{
"name": "@gitbutler/ui",
"name": "@gitbutler/app",
"private": true,
"version": "0.0.0",
"type": "module",
@ -121,10 +121,5 @@
},
"dependencies": {
"openai": "^4.47.3"
},
"eslintConfig": {
"extends": [
"plugin:storybook/recommended"
]
}
}

View File

@ -1,6 +1,6 @@
{
"build": {
"beforeBuildCommand": "pnpm build && cargo build --release -p gitbutler-git && bash ./gitbutler-tauri/inject-git-binaries.sh"
"beforeBuildCommand": "pnpm build:app && cargo build --release -p gitbutler-git && bash ./gitbutler-tauri/inject-git-binaries.sh"
},
"package": {
"productName": "GitButler"

View File

@ -1,22 +1,23 @@
{
"name": "git-butler-tauri",
"name": "git-butler",
"private": true,
"engines": {
"node": ">=20.11"
},
"packageManager": "pnpm@9.2.0",
"scripts": {
"dev": "pnpm --filter @gitbutler/ui run dev",
"test": "pnpm --filter @gitbutler/ui run test",
"test:watch": "pnpm --filter @gitbutler/ui run test:watch",
"build": "pnpm --filter @gitbutler/ui run build",
"build:nightly": "pnpm --filter @gitbutler/ui run build:nightly",
"build:development": "pnpm --filter @gitbutler/ui run build:development",
"check": "pnpm --filter @gitbutler/ui run check",
"lint": "pnpm --filter @gitbutler/ui run lint",
"format": "pnpm --filter @gitbutler/ui run format",
"dev": "pnpm --filter @gitbutler/app run dev",
"test": "pnpm --filter @gitbutler/app run test",
"test:watch": "pnpm --filter @gitbutler/app run test:watch",
"build:ui": "pnpm --filter @gitbutler/ui run build",
"build:app": "pnpm --filter @gitbutler/app run build",
"build:nightly": "pnpm --filter @gitbutler/app run build:nightly",
"build:development": "pnpm --filter @gitbutler/app run build:development",
"check": "pnpm --filter @gitbutler/app run check",
"lint": "pnpm --filter @gitbutler/app run lint",
"format": "pnpm --filter @gitbutler/app run format",
"tauri": "tauri",
"prepare": "pnpm --filter @gitbutler/ui run prepare",
"prepare": "pnpm --filter @gitbutler/app run prepare",
"rustfmt": "cargo +nightly fmt -- --config-path rustfmt-nightly.toml"
},
"devDependencies": {

View File

@ -292,6 +292,132 @@ importers:
specifier: ^0.34.6
version: 0.34.6(playwright@1.44.1)
ui:
devDependencies:
'@chromatic-com/storybook':
specifier: ^1.5.0
version: 1.5.0(react@18.3.1)
'@eslint/js':
specifier: ^9.5.0
version: 9.5.0
'@storybook/addon-essentials':
specifier: ^8.1.10
version: 8.1.10(@types/react@18.3.3)(prettier@3.3.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@storybook/addon-interactions':
specifier: ^8.1.10
version: 8.1.10(vitest@0.34.6(playwright@1.44.1))
'@storybook/addon-links':
specifier: ^8.1.10
version: 8.1.10(react@18.3.1)
'@storybook/blocks':
specifier: ^8.1.10
version: 8.1.10(@types/react@18.3.3)(prettier@3.3.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@storybook/svelte':
specifier: ^8.1.10
version: 8.1.10(prettier@3.3.2)(svelte@5.0.0-next.149)
'@storybook/sveltekit':
specifier: ^8.1.10
version: 8.1.10(@babel/core@7.24.7)(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.149)(vite@5.2.13(@types/node@20.5.9)))(postcss-load-config@5.1.0(postcss@8.4.38))(postcss@8.4.38)(prettier@3.3.2)(svelte@5.0.0-next.149)(typescript@5.4.5)(vite@5.2.13(@types/node@20.5.9))
'@storybook/test':
specifier: ^8.1.10
version: 8.1.10(vitest@0.34.6(playwright@1.44.1))
'@sveltejs/adapter-static':
specifier: ^3.0.1
version: 3.0.1(@sveltejs/kit@2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.149)(vite@5.2.13(@types/node@20.5.9)))(svelte@5.0.0-next.149)(vite@5.2.13(@types/node@20.5.9)))
'@sveltejs/kit':
specifier: ^2.5.10
version: 2.5.10(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.149)(vite@5.2.13(@types/node@20.5.9)))(svelte@5.0.0-next.149)(vite@5.2.13(@types/node@20.5.9))
'@sveltejs/vite-plugin-svelte':
specifier: ^3.1.1
version: 3.1.1(svelte@5.0.0-next.149)(vite@5.2.13(@types/node@20.5.9))
'@types/crypto-js':
specifier: ^4.2.2
version: 4.2.2
'@types/diff':
specifier: ^5.2.1
version: 5.2.1
'@types/diff-match-patch':
specifier: ^1.0.36
version: 1.0.36
'@types/eslint__js':
specifier: ^8.42.3
version: 8.42.3
'@types/git-url-parse':
specifier: ^9.0.3
version: 9.0.3
'@types/lscache':
specifier: ^1.3.4
version: 1.3.4
'@types/marked':
specifier: ^5.0.2
version: 5.0.2
'@typescript-eslint/parser':
specifier: ^7.13.1
version: 7.13.1(eslint@9.5.0)(typescript@5.4.5)
eslint:
specifier: ^9.5.0
version: 9.5.0
eslint-config-prettier:
specifier: ^9.1.0
version: 9.1.0(eslint@9.5.0)
eslint-import-resolver-typescript:
specifier: ^3.6.1
version: 3.6.1(@typescript-eslint/parser@7.13.1(eslint@9.5.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@9.5.0)
eslint-plugin-import-x:
specifier: ^0.5.1
version: 0.5.1(eslint@9.5.0)(typescript@5.4.5)
eslint-plugin-storybook:
specifier: ^0.8.0
version: 0.8.0(eslint@9.5.0)(typescript@5.4.5)
eslint-plugin-svelte:
specifier: 2.40.0
version: 2.40.0(eslint@9.5.0)(svelte@5.0.0-next.149)
globals:
specifier: ^15.6.0
version: 15.6.0
prettier:
specifier: ^3.3.2
version: 3.3.2
prettier-plugin-svelte:
specifier: ^3.2.4
version: 3.2.4(prettier@3.3.2)(svelte@5.0.0-next.149)
storybook:
specifier: ^8.1.10
version: 8.1.10(@babel/preset-env@7.24.7(@babel/core@7.24.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
svelte:
specifier: 5.0.0-next.149
version: 5.0.0-next.149
svelte-check:
specifier: ^3.8.0
version: 3.8.0(@babel/core@7.24.7)(postcss-load-config@5.1.0(postcss@8.4.38))(postcss@8.4.38)(svelte@5.0.0-next.149)
svelte-eslint-parser:
specifier: github:gitbutlerapp/svelte-eslint-parser
version: https://codeload.github.com/gitbutlerapp/svelte-eslint-parser/tar.gz/839ccb1266e4b3660d0b5ca4daf735febb1e06f0(svelte@5.0.0-next.149)
svelte-floating-ui:
specifier: ^1.5.8
version: 1.5.8
svelte-french-toast:
specifier: ^1.2.0
version: 1.2.0(svelte@5.0.0-next.149)
svelte-loadable-store:
specifier: ^2.0.1
version: 2.0.1(svelte@5.0.0-next.149)
svelte-resize-observer:
specifier: ^2.0.0
version: 2.0.0
typescript:
specifier: ^5.4.5
version: 5.4.5
typescript-eslint:
specifier: ^7.13.1
version: 7.13.1(eslint@9.5.0)(typescript@5.4.5)
vite:
specifier: ^5.2.13
version: 5.2.13(@types/node@20.5.9)
vitest:
specifier: ^0.34.6
version: 0.34.6(playwright@1.44.1)
packages:
'@aashutoshrathi/word-wrap@1.2.6':
@ -1233,10 +1359,6 @@ packages:
resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
'@jridgewell/gen-mapping@0.3.3':
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
engines: {node: '>=6.0.0'}
'@jridgewell/gen-mapping@0.3.5':
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@ -1245,10 +1367,6 @@ packages:
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
engines: {node: '>=6.0.0'}
'@jridgewell/set-array@1.1.2':
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
engines: {node: '>=6.0.0'}
'@jridgewell/set-array@1.2.1':
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
engines: {node: '>=6.0.0'}
@ -2641,11 +2759,6 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.11.3:
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.12.0:
resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==}
engines: {node: '>=0.4.0'}
@ -5973,8 +6086,8 @@ snapshots:
'@ampproject/remapping@2.2.1':
dependencies:
'@jridgewell/gen-mapping': 0.3.3
'@jridgewell/trace-mapping': 0.3.19
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
'@aw-web-design/x-default-browser@1.4.126':
dependencies:
@ -7136,12 +7249,6 @@ snapshots:
dependencies:
'@sinclair/typebox': 0.27.8
'@jridgewell/gen-mapping@0.3.3':
dependencies:
'@jridgewell/set-array': 1.1.2
'@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.19
'@jridgewell/gen-mapping@0.3.5':
dependencies:
'@jridgewell/set-array': 1.2.1
@ -7150,8 +7257,6 @@ snapshots:
'@jridgewell/resolve-uri@3.1.1': {}
'@jridgewell/set-array@1.1.2': {}
'@jridgewell/set-array@1.2.1': {}
'@jridgewell/sourcemap-codec@1.4.15': {}
@ -9147,29 +9252,27 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
acorn-import-assertions@1.9.0(acorn@8.11.3):
acorn-import-assertions@1.9.0(acorn@8.12.0):
dependencies:
acorn: 8.11.3
acorn: 8.12.0
optional: true
acorn-import-attributes@1.9.5(acorn@8.11.3):
acorn-import-attributes@1.9.5(acorn@8.12.0):
dependencies:
acorn: 8.11.3
acorn: 8.12.0
acorn-jsx@5.3.2(acorn@8.12.0):
dependencies:
acorn: 8.12.0
acorn-typescript@1.4.13(acorn@8.11.3):
acorn-typescript@1.4.13(acorn@8.12.0):
dependencies:
acorn: 8.11.3
acorn: 8.12.0
acorn-walk@8.2.0: {}
acorn@8.10.0: {}
acorn@8.11.3: {}
acorn@8.12.0: {}
address@1.2.2: {}
@ -10737,23 +10840,23 @@ snapshots:
import-in-the-middle@1.4.2:
dependencies:
acorn: 8.11.3
acorn-import-assertions: 1.9.0(acorn@8.11.3)
acorn: 8.12.0
acorn-import-assertions: 1.9.0(acorn@8.12.0)
cjs-module-lexer: 1.3.1
module-details-from-path: 1.0.3
optional: true
import-in-the-middle@1.7.4:
dependencies:
acorn: 8.11.3
acorn-import-attributes: 1.9.5(acorn@8.11.3)
acorn: 8.12.0
acorn-import-attributes: 1.9.5(acorn@8.12.0)
cjs-module-lexer: 1.3.1
module-details-from-path: 1.0.3
import-in-the-middle@1.8.0:
dependencies:
acorn: 8.11.3
acorn-import-attributes: 1.9.5(acorn@8.11.3)
acorn: 8.12.0
acorn-import-attributes: 1.9.5(acorn@8.12.0)
cjs-module-lexer: 1.3.1
module-details-from-path: 1.0.3
@ -12253,15 +12356,15 @@ snapshots:
'@ampproject/remapping': 2.2.1
'@jridgewell/sourcemap-codec': 1.4.15
'@types/estree': 1.0.5
acorn: 8.11.3
acorn-typescript: 1.4.13(acorn@8.11.3)
acorn: 8.12.0
acorn-typescript: 1.4.13(acorn@8.12.0)
aria-query: 5.3.0
axobject-query: 4.0.0
esm-env: 1.0.0
esrap: 1.2.2
is-reference: 3.0.2
locate-character: 3.0.0
magic-string: 0.30.5
magic-string: 0.30.10
zimmerframe: 1.1.2
sveltedoc-parser@4.2.1:

View File

@ -1,2 +1,3 @@
packages:
- 'app'
- 'ui'

28
ui/.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
dist
dist-ssr
*.local
build
.svelte-kit
package
!.env.example
vite.config.ts.timestamp-*
# Written to disk when using `act`
.pnpm-store
# playwright
test-results*
playwright-report
*storybook.log

2
ui/.npmrc Normal file
View File

@ -0,0 +1,2 @@
engine-strict=true
auto-install-peers=true

22
ui/.prettierignore Normal file
View File

@ -0,0 +1,22 @@
.DS_Store
node_modules
butler/target
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
# Ignore not svelte dirs
/.github
/.vscode
/src-tauri
# Written to disk when using `act`
.pnpm-store

15
ui/.prettierrc.js Normal file
View File

@ -0,0 +1,15 @@
/**
* @see https://prettier.io/docs/en/configuration.html
* @type {import("prettier").Config}
*/
const config = {
useTabs: true,
singleQuote: true,
trailingComma: 'none',
printWidth: 100,
plugins: ['prettier-plugin-svelte'],
overrides: [{ files: '*.svelte', options: { parser: 'svelte' } }],
endOfLine: 'auto'
};
export default config;

View File

@ -1,7 +1,7 @@
import type { StorybookConfig } from '@storybook/sveltekit';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
stories: ['../src/stories/**/*.mdx', '../src/stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',

143
ui/eslint.config.js Normal file
View File

@ -0,0 +1,143 @@
import js from '@eslint/js';
import tsParser from '@typescript-eslint/parser';
import eslintConfigPrettier from 'eslint-config-prettier';
import eslintPluginSvelte from 'eslint-plugin-svelte';
import globals from 'globals';
import svelteParser from 'svelte-eslint-parser';
import tsEslint from 'typescript-eslint';
import pluginImportX from 'eslint-plugin-import-x';
export default tsEslint.config(
js.configs.recommended,
...tsEslint.configs.recommended,
...eslintPluginSvelte.configs['flat/recommended'],
eslintConfigPrettier,
...eslintPluginSvelte.configs['flat/prettier'],
{
files: ['**/*.svelte'],
languageOptions: {
ecmaVersion: 2021,
sourceType: 'module',
globals: {
...globals.node,
...globals.browser,
$state: 'readonly',
$derived: 'readonly',
$props: 'readonly',
$bindable: 'readonly',
$inspect: 'readonly',
$host: 'readonly'
},
parser: svelteParser,
parserOptions: {
parser: tsParser,
extraFileExtensions: ['.svelte']
}
}
},
{
ignores: [
'**/.*', // dotfiles aren't ignored by default in FlatConfig
'.*', // dotfiles aren't ignored by default in FlatConfig
'**/.DS_Store',
'**/node_modules',
'butler/target',
'build',
'.svelte-kit',
'package',
'e2e',
'**/.env',
'**/.env.*',
'!**/.env.example',
'**/pnpm-lock.yaml',
'**/package-lock.json',
'**/yarn.lock',
'.github',
'.vscode',
'src-tauri',
'eslint.config.js',
'svelte.config.js',
'postcss.config.cjs',
'playwright.config.ts',
'**/.pnpm-store'
]
},
{
languageOptions: {
parserOptions: {
parser: tsEslint.parser,
project: true,
extraFileExtensions: ['.svelte']
}
},
rules: {
eqeqeq: ['error', 'always'],
'import-x/no-cycle': 'error',
'import-x/order': [
'error',
{
alphabetize: {
order: 'asc',
orderImportKind: 'asc',
caseInsensitive: false
},
groups: [
'index',
'sibling',
'parent',
'internal',
'external',
'builtin',
'object',
'type'
],
'newlines-between': 'never'
}
],
'import-x/no-unresolved': [
'error',
{
ignore: ['^\\$app', '^\\$env']
}
],
'func-style': [2, 'declaration'],
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_'
}
],
'no-return-await': 'off',
'@typescript-eslint/return-await': ['error', 'always'],
'@typescript-eslint/promise-function-async': 'error',
'@typescript-eslint/await-thenable': 'error',
'svelte/no-at-html-tags': 'off'
},
settings: {
'import-x/extensions': ['.ts'],
'import-x/parsers': {
'@typescript-eslint/parser': ['.ts']
},
'import-x/resolver': {
typescript: {
project: ['./tsconfig.json', './.svelte-kit/tsconfig.json']
}
}
},
plugins: {
'import-x': pluginImportX
}
}
);

76
ui/package.json Normal file
View File

@ -0,0 +1,76 @@
{
"name": "@gitbutler/ui",
"version": "0.0.1",
"description": "GitButler Component Library",
"keywords": [
"components",
"documentation",
"Svelte",
"SvelteKit"
],
"type": "module",
"engines": {
"node": ">=20.11"
},
"packageManager": "pnpm@9.2.0",
"scripts": {
"dev": "vite dev",
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"fix": "eslint --fix .",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "pnpm check --watch",
"package": "svelte-kit sync && svelte-package && publint",
"prepublishOnly": "npm run package",
"prepare": "svelte-kit sync",
"storybook": "storybook dev --no-open -p 6006"
},
"devDependencies": {
"@chromatic-com/storybook": "^1.5.0",
"@eslint/js": "^9.5.0",
"@storybook/addon-essentials": "^8.1.10",
"@storybook/addon-interactions": "^8.1.10",
"@storybook/addon-links": "^8.1.10",
"@storybook/blocks": "^8.1.10",
"@storybook/svelte": "^8.1.10",
"@storybook/sveltekit": "^8.1.10",
"@storybook/test": "^8.1.10",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.5.10",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@types/crypto-js": "^4.2.2",
"@types/diff": "^5.2.1",
"@types/diff-match-patch": "^1.0.36",
"@types/eslint__js": "^8.42.3",
"@types/git-url-parse": "^9.0.3",
"@types/lscache": "^1.3.4",
"@types/marked": "^5.0.2",
"@typescript-eslint/parser": "^7.13.1",
"eslint": "^9.5.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import-x": "^0.5.1",
"eslint-plugin-storybook": "^0.8.0",
"eslint-plugin-svelte": "2.40.0",
"globals": "^15.6.0",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.4",
"storybook": "^8.1.10",
"svelte": "5.0.0-next.149",
"svelte-check": "^3.8.0",
"svelte-eslint-parser": "github:gitbutlerapp/svelte-eslint-parser",
"svelte-floating-ui": "^1.5.8",
"svelte-french-toast": "^1.2.0",
"svelte-loadable-store": "^2.0.1",
"svelte-resize-observer": "^2.0.0",
"typescript": "^5.4.5",
"typescript-eslint": "^7.13.1",
"vite": "^5.2.13",
"vitest": "^0.34.6"
},
"eslintConfig": {
"extends": [
"plugin:storybook/recommended"
]
}
}

13
ui/src/app.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

15
ui/src/app.html Normal file
View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div>%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1,45 @@
<script lang="ts">
import type { BaseNode, Color } from '$lib/commitLines/types';
interface Props {
baseNode: BaseNode;
color: Color;
}
const { baseNode: _baseNode, color: _color }: Props = $props();
</script>
<div class="container">
<div class="node">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.32501 7.25C4.67247 5.53832 6.18578 4.25 8 4.25C9.81422 4.25 11.3275 5.53832 11.675 7.25H14V8.75H11.675C11.3275 10.4617 9.81422 11.75 8 11.75C6.18578 11.75 4.67247 10.4617 4.32501 8.75H2V7.25H4.32501ZM8 5.75C6.75736 5.75 5.75 6.75736 5.75 8C5.75 9.24264 6.75736 10.25 8 10.25C9.24264 10.25 10.25 9.24264 10.25 8C10.25 6.75736 9.24264 5.75 8 5.75Z"
fill="white"
/>
</svg>
</div>
</div>
<style lang="postcss">
.container {
z-index: var(--z-lifted);
& .node {
height: 16px;
width: 16px;
margin-top: -8px;
margin-bottom: -8px;
margin-left: -9px;
margin-right: -7px;
background-color: var(--clr-commit-remote);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -0,0 +1,18 @@
<script lang="ts">
import Fork from '$lib/commitLines/Cell/Fork.svelte';
import Straight from '$lib/commitLines/Cell/Straight.svelte';
import type { Cell } from '$lib/commitLines/types';
interface Props {
cell: Cell;
isBottom?: boolean;
}
const { cell, isBottom = false }: Props = $props();
</script>
{#if cell.type === 'fork'}
<Fork color={cell.color} style={cell.style} {isBottom} />
{:else}
<Straight color={cell.color} style={cell.style} />
{/if}

View File

@ -0,0 +1,66 @@
<script lang="ts">
import type { Color, Style } from '$lib/commitLines/types';
interface Props {
color: Color;
style?: Style;
isBottom?: boolean;
}
const { color, style, isBottom = false }: Props = $props();
</script>
<div
class="commit-line fork"
class:none={color === 'none'}
class:remote={color === 'remote'}
class:local={color === 'local'}
class:local-and-remote={color === 'localAndRemote'}
class:shadow={color === 'shadow'}
class:integrated={color === 'integrated'}
class:dashed={style === 'dashed'}
>
<div class="row top-row">
<div></div>
<div class="fork-top"></div>
</div>
<div class="row bottom-row" class:is-bottom={isBottom}>
<div class="fork-bottom"></div>
<div></div>
</div>
</div>
<style lang="postcss">
.fork {
display: flex;
flex-direction: column;
}
.row {
display: grid;
grid-template-columns: 1fr 1fr;
flex-grow: 1;
&.bottom-row {
height: 16px;
flex-grow: 0;
}
&.is-bottom {
height: 8px;
}
}
.fork-top {
border-right: 2px var(--border-style) var(--border-color);
border-bottom: 2px var(--border-style) var(--border-color);
border-bottom-right-radius: 8px;
margin-bottom: -2px;
}
.fork-bottom {
border-left: 2px var(--border-style) var(--border-color);
border-top: 2px var(--border-style) var(--border-color);
border-top-left-radius: 8px;
}
</style>

View File

@ -0,0 +1,27 @@
<script lang="ts">
import type { Color, Style } from '$lib/commitLines/types';
interface Props {
color: Color;
style?: Style;
}
const { color, style }: Props = $props();
</script>
<div
class="commit-line straight"
class:none={color === 'none'}
class:remote={color === 'remote'}
class:local={color === 'local'}
class:local-and-remote={color === 'localAndRemote'}
class:shadow={color === 'shadow'}
class:integrated={color === 'integrated'}
class:dashed={style === 'dashed'}
></div>
<style lang="postcss">
.straight {
border-right: 2px var(--border-style) var(--border-color);
}
</style>

View File

@ -0,0 +1,113 @@
<script lang="ts">
import { tooltip } from '$lib/utils/tooltip';
import { isDefined } from '$lib/utils/typeguards';
import type { CommitNode, Color } from '$lib/commitLines/types';
interface Props {
commitNode: CommitNode;
color: Color;
}
const { commitNode, color }: Props = $props();
const hoverText = $derived(
[
commitNode.commit?.author.name,
commitNode.commit?.title,
commitNode.commit?.id.substring(0, 7)
]
.filter(isDefined)
.join('\n')
);
</script>
<div
class="container"
class:none={color === 'none'}
class:remote={color === 'remote'}
class:local={color === 'local'}
class:local-and-remote={color === 'localAndRemote'}
class:shadow={color === 'shadow'}
class:integrated={color === 'integrated'}
>
{#if commitNode.type === 'large' && commitNode.commit}
<div class="large-node">
<img
class="avatar"
alt="Gravatar for {commitNode.commit.author.email}"
srcset="{commitNode.commit.author.gravatarUrl} 2x"
width="100"
height="100"
use:tooltip={hoverText}
/>
</div>
{:else}
<div class="small-node" use:tooltip={hoverText}></div>
{/if}
</div>
<style lang="postcss">
.container {
z-index: var(--z-ground);
&.none {
--border-color: transparent;
}
&.remote {
--border-color: var(--clr-commit-upstream);
}
&.local {
--border-color: var(--clr-commit-local);
}
&.local-and-remote {
--border-color: var(--clr-commit-remote);
}
&.shadow {
--border-color: var(--clr-commit-shadow);
}
&.integrated {
--border-color: var(--clr-commit-shadow);
}
& .small-node {
height: 10px;
width: 10px;
margin-top: -5px;
margin-bottom: -5px;
margin-left: -6px;
margin-right: -4px;
background-color: var(--border-color);
border-radius: 8px;
}
& .large-node {
height: 16px;
width: 16px;
margin-top: -8px;
margin-bottom: -8px;
margin-left: -9px;
margin-right: -7px;
background-color: var(--border-color);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
}
.avatar {
position: relative;
width: 12px;
height: 12px;
border-radius: 6px;
}
</style>

View File

@ -0,0 +1,57 @@
<script lang="ts">
import BaseNode from '$lib/commitLines/BaseNode.svelte';
import Cell from '$lib/commitLines/Cell.svelte';
import CommitNode from '$lib/commitLines/CommitNode.svelte';
import { pxToRem } from '$lib/utils/pxToRem';
import type { Line } from '$lib/commitLines/types';
interface Props {
line: Line;
topHeightPx?: number;
}
const { line, topHeightPx = 24 }: Props = $props();
</script>
<div class="line">
<div
class="line-top"
style:--top-height={pxToRem(topHeightPx)}
class:has-branch-node={line.baseNode}
>
<Cell cell={line.top} />
</div>
{#if line.commitNode}
<CommitNode commitNode={line.commitNode} color={line.bottom.color} />
{:else if line.baseNode}
<BaseNode baseNode={line.baseNode} color={line.top.color} />
{/if}
<div class="line-bottom">
<Cell cell={line.bottom} isBottom />
</div>
</div>
<style lang="postcss">
.line {
height: 100%;
display: flex;
flex-direction: column;
align-items: flex-end;
width: 24px;
margin-right: -2px;
}
.line-top {
height: var(--top-height);
width: 100%;
&.has-branch-node {
height: 24px;
}
}
.line-bottom {
flex-grow: 1;
width: 100%;
}
</style>

View File

@ -0,0 +1,24 @@
<script lang="ts">
import Line from '$lib/commitLines/Line.svelte';
import type { LineGroup } from '$lib/commitLines/types';
interface Props {
lineGroup: LineGroup;
topHeightPx?: number;
}
const { lineGroup, topHeightPx }: Props = $props();
</script>
<div class="line-group">
{#each lineGroup.lines as line}
<Line {line} {topHeightPx} />
{/each}
</div>
<style lang="postcss">
.line-group {
display: flex;
height: 100%;
}
</style>

View File

@ -0,0 +1,489 @@
import type { CommitData, LineGroup, Line, Color } from '$lib/commitLines/types';
interface Commits {
remoteCommits: CommitData[];
localCommits: CommitData[];
localAndRemoteCommits: CommitData[];
integratedCommits: CommitData[];
}
function generateSameForkpoint({
remoteCommits,
localCommits,
localAndRemoteCommits,
integratedCommits
}: Commits) {
const LEFT_COLUMN_INDEX = 0;
const RIGHT_COLUMN_INDEX = 1;
const remoteBranchGroups = mapToCommitLineGroupPair(remoteCommits, 3);
const localBranchGroups = mapToCommitLineGroupPair(localCommits, 3);
const localAndRemoteBranchGroups = mapToCommitLineGroupPair(localAndRemoteCommits, 3);
const integratedBranchGroups = mapToCommitLineGroupPair(integratedCommits, 3);
const base = blankLineGroup(3);
remoteBranchGroups.forEach(({ commit, lineGroup }, index) => {
// We don't color in top half of the first remote commit
if (index !== 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'remote';
}
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'remote';
lineGroup.lines[LEFT_COLUMN_INDEX].commitNode = { type: 'large', commit };
// If there are local commits we want to fill in a local dashed line
if (localBranchGroups.length > 0) {
lineGroup.lines[RIGHT_COLUMN_INDEX].top.color = 'local';
lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.color = 'local';
lineGroup.lines[RIGHT_COLUMN_INDEX].top.style = 'dashed';
lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.style = 'dashed';
}
});
let localCommitWithChangeIdFound = false;
localBranchGroups.forEach(({ commit, lineGroup }, index) => {
// The first local commit should have the top be dashed
if (index === 0) {
lineGroup.lines[RIGHT_COLUMN_INDEX].top.style = 'dashed';
}
lineGroup.lines[RIGHT_COLUMN_INDEX].top.color = 'local';
lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.color = 'local';
lineGroup.lines[RIGHT_COLUMN_INDEX].commitNode = { type: 'large', commit };
// We need to use either remote or localAndRemote depending on what is above
let leftStyle: Color | undefined;
if (remoteBranchGroups.length > 0) {
leftStyle = 'remote';
} else {
leftStyle = 'localAndRemote';
}
if (localCommitWithChangeIdFound) {
// If a commit with a change ID has been found above this commit, use the leftStyle
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = leftStyle;
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = leftStyle;
if (commit.relatedRemoteCommit) {
lineGroup.lines[LEFT_COLUMN_INDEX].commitNode = {
type: 'small',
commit: commit.relatedRemoteCommit
};
}
} else {
if (commit.relatedRemoteCommit) {
// For the first commit with a change ID found, only set the top if there are any remote commits
if (remoteBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = leftStyle;
}
lineGroup.lines[LEFT_COLUMN_INDEX].commitNode = {
type: 'small',
commit: commit.relatedRemoteCommit
};
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = leftStyle;
localCommitWithChangeIdFound = true;
} else {
// If there are any remote commits, continue the line
if (remoteBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'remote';
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'remote';
}
}
}
});
localAndRemoteBranchGroups.forEach(({ commit, lineGroup }, index) => {
if (index === 0) {
// Copy the top color from any commits above for the first commit
if (localBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color =
localBranchGroups.at(-1)!.lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color;
} else if (remoteBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color =
remoteBranchGroups.at(-1)!.lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color;
}
} else {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'localAndRemote';
}
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'localAndRemote';
lineGroup.lines[LEFT_COLUMN_INDEX].commitNode = { type: 'large', commit };
});
integratedBranchGroups.forEach(({ commit, lineGroup }, index) => {
if (index === 0) {
// Copy the top color from any commits above for the first commit
if (localAndRemoteBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color =
localAndRemoteBranchGroups.at(-1)!.lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color;
} else if (localBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color =
localBranchGroups.at(-1)!.lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color;
} else if (remoteBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color =
remoteBranchGroups.at(-1)!.lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color;
}
} else {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'integrated';
}
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'integrated';
lineGroup.lines[LEFT_COLUMN_INDEX].commitNode = { type: 'large', commit };
});
// Set forkpoints
if (localBranchGroups.length > 0) {
if (integratedBranchGroups.length === 0 && localAndRemoteBranchGroups.length === 0) {
localBranchGroups.at(-1)!.lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.type = 'fork';
} else if (localAndRemoteBranchGroups.length > 0) {
localAndRemoteBranchGroups[0].lineGroup.lines[RIGHT_COLUMN_INDEX].top.type = 'fork';
localAndRemoteBranchGroups[0].lineGroup.lines[RIGHT_COLUMN_INDEX].top.color = 'local';
} else if (integratedBranchGroups.length > 0) {
integratedBranchGroups[0].lineGroup.lines[RIGHT_COLUMN_INDEX].top.type = 'fork';
integratedBranchGroups[0].lineGroup.lines[RIGHT_COLUMN_INDEX].top.color = 'local';
}
}
// Remove padding column if unrequired
if (localBranchGroups.length === 0) {
remoteBranchGroups.forEach(({ lineGroup }) => lineGroup.lines.pop());
localBranchGroups.forEach(({ lineGroup }) => lineGroup.lines.pop());
localAndRemoteBranchGroups.forEach(({ lineGroup }) => lineGroup.lines.pop());
integratedBranchGroups.forEach(({ lineGroup }) => lineGroup.lines.pop());
base.lines.pop();
}
// Set base
base.lines[LEFT_COLUMN_INDEX].baseNode = {};
base.lines[LEFT_COLUMN_INDEX].top.style = 'dashed';
if (integratedBranchGroups.length > 0) {
base.lines[LEFT_COLUMN_INDEX].top.color = 'integrated';
} else if (localAndRemoteBranchGroups.length > 0) {
base.lines[LEFT_COLUMN_INDEX].top.color = 'localAndRemote';
} else if (localBranchGroups.length > 0) {
base.lines[LEFT_COLUMN_INDEX].top.color = 'local';
} else if (remoteBranchGroups.length > 0) {
base.lines[LEFT_COLUMN_INDEX].top.color = 'remote';
} else {
base.lines[LEFT_COLUMN_INDEX].baseNode = undefined;
}
const data = new Map<string, LineGroup>([
...remoteBranchGroups.map(({ commit, lineGroup }) => [commit.id, lineGroup]),
...localBranchGroups.map(({ commit, lineGroup }) => [commit.id, lineGroup]),
...localAndRemoteBranchGroups.map(({ commit, lineGroup }) => [commit.id, lineGroup]),
...integratedBranchGroups.map(({ commit, lineGroup }) => [commit.id, lineGroup])
] as [string, LineGroup][]);
return { data, base };
}
function generateDifferentForkpoint({
remoteCommits,
localCommits,
localAndRemoteCommits,
integratedCommits
}: Commits) {
const LEFT_COLUMN_INDEX = 0;
const MIDDLE_COLUMN_INDEX = 1;
const RIGHT_COLUMN_INDEX = 2;
if (localAndRemoteCommits.length > 0) {
throw new Error('There should never be local and remote commits with a different forkpoint');
}
const remoteBranchGroups = mapToCommitLineGroupPair(remoteCommits, 4);
const localBranchGroups = mapToCommitLineGroupPair(localCommits, 4);
const integratedBranchGroups = mapToCommitLineGroupPair(integratedCommits, 4);
const base = blankLineGroup(4);
remoteBranchGroups.forEach(({ commit, lineGroup }, index) => {
// Don't color top half if its the first commit of the list
if (index !== 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'remote';
}
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'remote';
lineGroup.lines[LEFT_COLUMN_INDEX].commitNode = { type: 'large', commit };
// If there are local commits further down, render a dashed line from the top of the list
if (localBranchGroups.length > 0) {
lineGroup.lines[RIGHT_COLUMN_INDEX].top.color = 'local';
lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.color = 'local';
lineGroup.lines[RIGHT_COLUMN_INDEX].top.style = 'dashed';
lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.style = 'dashed';
}
});
let localCommitWithChangeIdFound = false;
localBranchGroups.forEach(({ commit, lineGroup }, index) => {
// Make the top commit dashed
if (index === 0) {
lineGroup.lines[RIGHT_COLUMN_INDEX].top.style = 'dashed';
}
lineGroup.lines[RIGHT_COLUMN_INDEX].top.color = 'local';
lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.color = 'local';
lineGroup.lines[RIGHT_COLUMN_INDEX].commitNode = { type: 'large', commit };
if (localCommitWithChangeIdFound) {
// If a previous local commit with change ID was found, color with "shadow"
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'shadow';
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'shadow';
if (commit.relatedRemoteCommit) {
lineGroup.lines[LEFT_COLUMN_INDEX].commitNode = {
type: 'small',
commit: commit.relatedRemoteCommit
};
}
} else {
if (commit.relatedRemoteCommit) {
// If the commit has a commit with a matching change ID, mark it in the shadow lane
// Since this is the first, if there were any remote commits, we should inherit that color
if (remoteBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'remote';
}
lineGroup.lines[LEFT_COLUMN_INDEX].commitNode = {
type: 'small',
commit: commit.relatedRemoteCommit
};
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'shadow';
localCommitWithChangeIdFound = true;
} else {
// Otherwise maintain the left color if it exists
if (remoteBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'remote';
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'remote';
}
}
}
});
integratedBranchGroups.forEach(({ commit, lineGroup }, index) => {
if (localBranchGroups.length === 0 && remoteBranchGroups.length === 0) {
// If there are no local or remote branches, we want to have a single dashed line
// Don't color in if its the first commit
if (index !== 0) {
lineGroup.lines[MIDDLE_COLUMN_INDEX].top.color = 'integrated';
lineGroup.lines[MIDDLE_COLUMN_INDEX].top.style = 'dashed';
}
lineGroup.lines[MIDDLE_COLUMN_INDEX].bottom.color = 'integrated';
lineGroup.lines[MIDDLE_COLUMN_INDEX].bottom.style = 'dashed';
lineGroup.lines[MIDDLE_COLUMN_INDEX].commitNode = { type: 'large', commit };
} else {
// If we have local branches, maintain that color on the top half of the first commit
if (index === 0) {
lineGroup.lines[RIGHT_COLUMN_INDEX].top.color =
localBranchGroups.at(-1)?.lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.color || 'none';
} else {
lineGroup.lines[RIGHT_COLUMN_INDEX].top.color = 'integrated';
}
lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.color = 'integrated';
lineGroup.lines[RIGHT_COLUMN_INDEX].commitNode = { type: 'large', commit };
if (localCommitWithChangeIdFound) {
// If there is a commit with change id above, just use shadow style
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'shadow';
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'shadow';
if (commit.relatedRemoteCommit) {
lineGroup.lines[LEFT_COLUMN_INDEX].commitNode = {
type: 'small',
commit: commit.relatedRemoteCommit
};
}
} else {
if (commit.relatedRemoteCommit) {
// If we have just found a commit with a shadow style, match the top style
if (remoteBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'remote';
}
lineGroup.lines[LEFT_COLUMN_INDEX].commitNode = {
type: 'small',
commit: commit.relatedRemoteCommit
};
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'shadow';
localCommitWithChangeIdFound = true;
} else {
// Otherwise style as remote if there are any
if (remoteBranchGroups.length > 0) {
lineGroup.lines[LEFT_COLUMN_INDEX].top.color = 'remote';
lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color = 'remote';
}
}
}
}
});
// Mark the fork point below the integrated or local branch groups if present
if (integratedBranchGroups.length > 0) {
integratedBranchGroups.at(-1)!.lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.type = 'fork';
} else if (localBranchGroups.length > 0) {
localBranchGroups.at(-1)!.lineGroup.lines[RIGHT_COLUMN_INDEX].bottom.type = 'fork';
}
function setLeftSideBase() {
let color: Color | undefined;
if (integratedBranchGroups.length > 0) {
color = integratedBranchGroups.at(-1)!.lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color;
} else if (localBranchGroups.length > 0) {
color = localBranchGroups.at(-1)!.lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color;
} else if (remoteBranchGroups.length > 0) {
color = remoteBranchGroups.at(-1)!.lineGroup.lines[LEFT_COLUMN_INDEX].bottom.color;
} else {
color = 'none';
}
base.lines[LEFT_COLUMN_INDEX].top.color = color;
base.lines[LEFT_COLUMN_INDEX].bottom.color = color;
base.lines[LEFT_COLUMN_INDEX].top.style = 'dashed';
base.lines[LEFT_COLUMN_INDEX].bottom.style = 'dashed';
}
// Set base
if (integratedBranchGroups.length > 0) {
base.lines[MIDDLE_COLUMN_INDEX].top.color = 'integrated';
base.lines[MIDDLE_COLUMN_INDEX].top.style =
integratedBranchGroups.at(-1)!.lineGroup.lines[MIDDLE_COLUMN_INDEX].bottom.style;
base.lines[MIDDLE_COLUMN_INDEX].baseNode = {};
setLeftSideBase();
} else if (localBranchGroups.length > 0) {
base.lines[MIDDLE_COLUMN_INDEX].top.color = 'local';
base.lines[MIDDLE_COLUMN_INDEX].baseNode = {};
setLeftSideBase();
} else if (remoteBranchGroups.length > 0) {
base.lines[LEFT_COLUMN_INDEX].top.color = 'remote';
base.lines[LEFT_COLUMN_INDEX].top.style = 'dashed';
base.lines[LEFT_COLUMN_INDEX].baseNode = {};
}
function removeLeftMostColumn() {
remoteBranchGroups.forEach(({ lineGroup }) => lineGroup.lines.shift());
localBranchGroups.forEach(({ lineGroup }) => lineGroup.lines.shift());
integratedBranchGroups.forEach(({ lineGroup }) => lineGroup.lines.shift());
base.lines.shift();
}
function removeRightMostColumn() {
remoteBranchGroups.forEach(({ lineGroup }) => lineGroup.lines.pop());
localBranchGroups.forEach(({ lineGroup }) => lineGroup.lines.pop());
integratedBranchGroups.forEach(({ lineGroup }) => lineGroup.lines.pop());
base.lines.pop();
}
// Remove the left column if there is no ghost line
const hasGhostLine = [
...remoteBranchGroups,
...localBranchGroups,
...integratedBranchGroups
].some(
({ lineGroup }) =>
lineGroup.lines[LEFT_COLUMN_INDEX].top.color !== 'none' ||
lineGroup.lines[LEFT_COLUMN_INDEX].top.color !== 'none'
);
if (!hasGhostLine) {
removeLeftMostColumn();
}
// Remove the right two columns if there is only remote commits
if (integratedBranchGroups.length === 0 && localBranchGroups.length === 0) {
removeRightMostColumn();
removeRightMostColumn();
}
// Remove one right column if there is only integrated with no local or remote commits
if (integratedBranchGroups.length > 0 && localBranchGroups.length === 0 && remoteBranchGroups) {
removeRightMostColumn();
}
const data = new Map<string, LineGroup>([
...remoteBranchGroups.map(({ commit, lineGroup }) => [commit.id, lineGroup]),
...localBranchGroups.map(({ commit, lineGroup }) => [commit.id, lineGroup]),
...integratedBranchGroups.map(({ commit, lineGroup }) => [commit.id, lineGroup])
] as [string, LineGroup][]);
return { data, base };
}
function mapToCommitLineGroupPair(commits: CommitData[], groupSize: number) {
const groupings = commits.map((commit) => ({
commit,
lineGroup: blankLineGroup(groupSize)
}));
return groupings;
}
function blankLineGroup(lineCount: number): LineGroup {
const lines = Array(lineCount)
.fill(undefined)
.map(
(): Line => ({
top: { type: 'straight', color: 'none' },
bottom: { type: 'straight', color: 'none' }
})
);
return {
lines
};
}
/**
* The Line Manager assumes that the groups of commits will be in the following order:
* 1. Remote Commits (Commits you don't have in your branch)
* 2. Local Commits (Commits that you have changed locally)
* 3. LocalAndRemote Commits (Commits that exist locally and on the remote and have the same hash)
* 4. Integrated Commits (Commits that exist locally and perhaps on the remote that are in the trunk)
*/
export class LineManager {
private data: Map<string, LineGroup>;
base: LineGroup;
constructor(commits: Commits, sameForkpoint: boolean) {
// We should never have local and remote commits with a different forkpoint
if (sameForkpoint || commits.localAndRemoteCommits.length > 0) {
const { data, base } = generateSameForkpoint(commits);
this.data = data;
this.base = base;
} else {
const { data, base } = generateDifferentForkpoint(commits);
this.data = data;
this.base = base;
}
}
get(commitId: string) {
if (!this.data.has(commitId)) {
throw new Error(`Failed to find commit ${commitId} in line manager`);
}
return this.data.get(commitId)!;
}
}
export class LineManagerFactory {
build(commits: Commits, sameForkpoint: boolean) {
return new LineManager(commits, sameForkpoint);
}
}

View File

@ -0,0 +1,26 @@
import type { CommitData, Author } from '$lib/commitLines/types';
import type { AnyCommit } from '$lib/vbranches/types';
export function transformAnyCommit(anyCommit: AnyCommit): CommitData {
const output = pullCommitDetails(anyCommit);
if (anyCommit.relatedTo) {
output.relatedRemoteCommit = pullCommitDetails(anyCommit.relatedTo);
}
return output;
}
function pullCommitDetails(anyCommit: AnyCommit): CommitData {
const author: Author = {
name: anyCommit.author.name,
email: anyCommit.author.email,
gravatarUrl: anyCommit.author.gravatarUrl
};
return {
id: anyCommit.id,
title: anyCommit.descriptionTitle,
author
};
}

View File

@ -0,0 +1,43 @@
export type Color = 'local' | 'localAndRemote' | 'remote' | 'integrated' | 'shadow' | 'none';
export type Style = 'dashed';
export interface Cell {
type: 'straight' | 'fork';
color: Color;
style?: Style;
}
export interface CommitNode {
type: 'small' | 'large';
commit?: CommitData;
}
export interface BaseNode {}
export interface Line {
top: Cell;
bottom: Cell;
commitNode?: CommitNode;
baseNode?: BaseNode;
}
export interface LineGroup {
// A tuple of two, three, or four lines
lines: Line[];
}
export interface Author {
name?: string;
email?: string;
gravatarUrl?: URL;
}
/**
* A minimal set of data required to represent a commit for line drawing purpouses
*/
export interface CommitData {
id: string;
title?: string;
author: Author;
relatedRemoteCommit?: CommitData;
}

View File

@ -0,0 +1,4 @@
export function pxToRem(px: number | undefined, base: number = 16) {
if (!px) return;
return `${px / base}rem`;
}

119
ui/src/lib/utils/tooltip.ts Normal file
View File

@ -0,0 +1,119 @@
export interface ToolTipOptions {
text: string;
noMaxWidth?: boolean;
delay?: number;
}
const defaultOptions: Partial<ToolTipOptions> = {
delay: 1200,
noMaxWidth: false
};
export function tooltip(node: HTMLElement, optsOrString: ToolTipOptions | string | undefined) {
// The tooltip element we are adding to the dom
let tooltip: HTMLDivElement | undefined;
// Note that we use this both for delaying show, as well as delaying hide
let timeoutId: any;
// Options
let { text, delay, noMaxWidth } = defaultOptions;
// Most use cases only involve passing a string, so we allow either opts of
// simple text.
function setOpts(opts: ToolTipOptions | string | undefined) {
if (typeof opts === 'string') {
text = opts;
} else if (opts) {
({ text, delay, noMaxWidth } = opts || {});
}
if (tooltip && text) tooltip.innerText = text;
}
setOpts(optsOrString);
function onMouseOver() {
// If tooltip is displayed we clear hide timeout
if (tooltip && timeoutId) clearTimeout(timeoutId);
// If no tooltip and no timeout id we set a show timeout
else if (!tooltip && !timeoutId) timeoutId = setTimeout(() => show(), delay);
}
function onMouseLeave() {
// If tooltip shown when mouse out then we hide after delay
if (tooltip) hide();
// But if we mouse out before tooltip is shown, we cancel the show timer
else if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = undefined;
}
}
function show() {
if (!text || !node.isConnected) return;
tooltip = document.createElement('div') as HTMLDivElement;
// TODO: Can we co-locate tooltip.js & tooltip.postcss?
tooltip.classList.add('tooltip', 'text-base-11'); // see tooltip.postcss
if (noMaxWidth) tooltip.classList.add('no-max-width');
tooltip.innerText = text;
document.body.appendChild(tooltip);
adjustPosition();
}
function hide() {
if (tooltip) tooltip.remove();
tooltip = undefined;
timeoutId = undefined;
}
function adjustPosition() {
if (!tooltip) return;
// Dimensions and position of target element
const nodeRect = node.getBoundingClientRect();
const nodeHeight = nodeRect.height;
const nodeWidth = nodeRect.width;
const nodeLeft = nodeRect.left;
const nodeTop = nodeRect.top;
// Padding
const padding = 4;
// Window dimensions
const windowHeight = window.innerHeight;
const windowWidth = window.innerWidth;
const tooltipHeight = tooltip.offsetHeight;
const tooltipWidth = tooltip.offsetWidth;
const showBelow = windowHeight > nodeTop + nodeHeight + tooltipHeight + padding;
// Note that we don't check if width of tooltip is wider than the window.
if (showBelow) {
tooltip.style.top = `${nodeTop + nodeHeight + padding}px`;
} else {
tooltip.style.top = `${nodeTop - tooltipHeight - padding}px`;
}
let leftPos = nodeLeft - (tooltipWidth - nodeWidth) / 2;
if (leftPos < padding) leftPos = padding;
if (leftPos + tooltipWidth > windowWidth) leftPos = windowWidth - tooltipWidth - padding;
tooltip.style.left = `${leftPos}px`;
}
node.addEventListener('mouseover', onMouseOver);
node.addEventListener('mouseleave', onMouseLeave);
return {
update(opts: ToolTipOptions | string | undefined) {
setOpts(opts);
},
destroy() {
tooltip?.remove();
timeoutId && clearTimeout(timeoutId);
node.removeEventListener('mouseover', onMouseOver);
node.removeEventListener('mouseleave', onMouseLeave);
}
};
}

View File

@ -0,0 +1,23 @@
export function isDefined<T>(file: T | undefined | null): file is T {
return file !== undefined;
}
export function notNull<T>(file: T | undefined | null): file is T {
return file !== null;
}
export type UnknownObject = Record<string, unknown>;
/**
* Checks if the provided value is a non-empty object.
* @param something - The value to be checked.
* @returns A boolean indicating whether the value is a non-empty object.
*/
export function isNonEmptyObject(something: unknown): something is UnknownObject {
return (
typeof something === 'object' &&
something !== null &&
!Array.isArray(something) &&
(Object.keys(something).length > 0 || Object.getOwnPropertySymbols(something).length > 0)
);
}

View File

@ -0,0 +1,3 @@
<h1>Welcome to your library project</h1>
<p>Create your package using @sveltejs/package and preview/showcase your work with SvelteKit</p>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>

33
ui/src/styles/card.css Normal file
View File

@ -0,0 +1,33 @@
.card {
display: flex;
flex-direction: column;
border: 1px solid var(--clr-border-2);
border-radius: var(--radius-m);
background: var(--clr-bg-1);
}
.card__header {
display: flex;
justify-content: space-between;
padding: 16px;
gap: 8px;
border-bottom: 1px solid var(--clr-border-2);
}
.card__title {
padding: 4px 6px;
}
.card__content {
display: flex;
flex-direction: column;
padding: 16px;
}
.card__footer {
display: flex;
gap: 6px;
padding: 16px;
justify-content: space-between;
border-top: 1px solid var(--clr-border-2);
}

View File

@ -0,0 +1,53 @@
.commit-line {
position: relative;
width: 100%;
height: 100%;
--border-color: var(--clr-commit-shadow);
--border-style: solid;
--border-dashed: dashed;
&.none {
--border-color: transparent;
}
&.remote {
--border-color: var(--clr-commit-upstream);
}
&.local {
--border-color: var(--clr-commit-local);
}
&.local-and-remote {
--border-color: var(--clr-commit-remote);
}
&.shadow {
--border-color: var(--clr-commit-shadow);
}
&.integrated {
--border-color: var(--clr-commit-shadow);
}
&.dashed {
--border-style: none;
&:before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 2px;
height: calc(100% + 1px);
background: repeating-linear-gradient(
0,
transparent,
transparent 2px,
var(--border-color) 2px,
var(--border-color) 4px
);
}
}
}

47
ui/src/styles/diff.css Normal file
View File

@ -0,0 +1,47 @@
:root {
--hunk-line-selected-bg: #60a5fa;
--hunk-line-selected-border: #2563eb;
}
:root.dark {
--hunk-line-selected-bg: #044289;
--hunk-line-selected-border: #005cc5;
}
.inner-diff {
border-radius: 2px;
}
.diff-line-marker-addition,
.diff-line-addition {
--override-addition-background-color: hsl(144deg 55% 49% / 20%);
background-color: var(--override-addition-background-color);
}
.diff-line-marker-deletion,
.diff-line-deletion {
--override-deletion-background-color: rgba(220, 38, 38, 0.2);
background-color: var(--override-deletion-background-color);
}
.diff-line-addition .inner-diff {
--override-addition-inner-diff-background-color: hsl(144deg 55% 49% / 60%);
background-color: var(--override-addition-inner-diff-background-color);
}
.diff-line-deletion .inner-diff {
--override-deletion-inner-diff-background-color: rgba(220, 38, 38, 0.3);
background-color: var(--override-deletion-inner-diff-background-color);
}
.diff-line-spacer {
color: rgb(229, 231, 235);
text-align: left;
background-color: rgba(24, 24, 27, 1);
padding-left: 8px;
padding-right: 8px;
}

37
ui/src/styles/fonts.css Normal file
View File

@ -0,0 +1,37 @@
@font-face {
font-family: 'PP Editorial New';
src: url('/fonts/PPEditorialNew-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'Spline Sans Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/SplineSansMono-Regular.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
U+FFFD;
}
@font-face {
font-family: 'Spline Sans Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('/fonts/SplineSansMono-Medium.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
U+FFFD;
}
@font-face {
font-family: 'Spline Sans Mono';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('/fonts/SplineSansMono-Semibold.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
U+FFFD;
}

204
ui/src/styles/main.css Normal file
View File

@ -0,0 +1,204 @@
@layer reset;
@import './reset.css';
@import './fonts.css';
@import './diff.css';
@import './syntax-highlighting.css';
@import './tokens.css';
@import './text-classes.css';
@import './card.css';
@import './tooltip.css';
@import './text-input.css';
@import './commit-lines.css';
@import './markdown.css';
/* CSS VARIABLES */
:root {
--transition-fast: 0.06s ease-in-out;
--transition-medium: 0.15s ease-in-out;
--transition-slow: 0.2s ease-in-out;
/* Z-index */
--z-ground: 1;
--z-lifted: 2;
--z-floating: 3;
--z-modal: 4;
--z-tooltip: 9;
--z-blocker: 10;
/* TODO: add focus color */
--focus-color: var(--clr-scale-pop-50);
--resizer-color: var(--clr-scale-pop-50);
}
/* scrollbar helpers */
.hide-native-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* custom scrollbar */
.scrollbar,
pre {
&::-webkit-scrollbar {
background-color: transaparent;
width: 14px;
}
&::-webkit-scrollbar-track {
background-color: transaparent;
}
&::-webkit-scrollbar-thumb {
background-color: var(--clr-border-1);
background-clip: padding-box;
border-radius: 12px;
border: 4px solid rgba(0, 0, 0, 0);
opacity: 0.3;
}
&::-webkit-scrollbar-thumb:hover {
opacity: 0.8;
}
&::-webkit-scrollbar-button {
display: none;
}
}
.link {
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
/**
* Prevents elements within drop-zones from firing mouse events, making
* it much easier to manage in/out/over/leave events since they fire less
* frequently.
*/
.drop-zone-hover * {
pointer-events: none;
}
/* FOCUS STATE */
.focus-state {
&:focus-within {
outline: 1px solid transaparent;
animation: focus-animation var(--transition-fast) forwards;
}
}
@keyframes focus-animation {
0% {
outline-offset: 0;
}
100% {
outline-offset: 2px;
outline: 1px solid var(--focus-color);
}
}
/* CODE */
.code-string {
font-family: 'Spline Sans Mono', monospace;
border-radius: var(--radius-s);
background: oklch(from var(--clr-scale-ntrl-50) l c h / 0.2);
padding: 2px 4px;
}
/* TRANSITION ANIMATION */
.transition-fly {
animation: transition-fly 0.25s forwards ease-in-out;
}
@keyframes transition-fly {
0% {
transform: translateY(6px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
/* STATES */
.wiggle {
animation: wiggle-animation 0.35s forwards;
}
@keyframes wiggle-animation {
0% {
transform: translateX(-3px);
}
25% {
transform: translateX(3px);
}
50% {
transform: translateX(-2px);
}
75% {
transform: translateX(2px);
}
100% {
transform: translateX(0);
}
}
.locked-file-animation {
--locked-color: oklch(from var(--clr-scale-warn-50) l c h / 0.2);
border: 1px solid var(--clr-bg-1);
animation: locked-file-animation 1.4s ease-out forwards;
}
@keyframes locked-file-animation {
0% {
transform: translateX(-3px);
background-color: var(--clr-bg-1);
}
10% {
transform: translateX(3px);
background-color: var(--locked-color);
}
15% {
transform: translateX(-3px);
}
25% {
transform: translateX(3px);
background-color: var(--locked-color);
}
30%,
70% {
transform: translateX(0);
}
100% {
background-color: var(--clr-bg-1);
}
}

View File

@ -0,0 +1,87 @@
.markdown {
& p:last-child,
& ul:last-child,
& ol:last-child,
& blockquote:last-child,
& pre:last-child,
& hr:last-child {
margin-bottom: 0;
}
h1 {
font-size: 2em;
margin-bottom: 0.8em;
}
h2 {
font-size: 1.5em;
margin-bottom: 0.8em;
}
h3 {
font-size: 1.17em;
margin-bottom: 0.8em;
}
h4 {
font-size: 1em;
margin-bottom: 0.8em;
}
p {
margin-bottom: 1.2em;
}
ul {
display: block;
list-style-type: disc;
margin: 1em 0;
padding: 0 0 0 2em;
}
ol {
display: block;
list-style-type: decimal;
margin: 1em 0;
padding: 0 0 0 2em;
}
li {
margin: 0.5em 0;
}
blockquote {
margin: 1em 0;
padding: 0 0 0 2em;
}
pre {
margin: 1em 0;
padding: 1em;
background-color: var(--clr-scale-ntrl-90);
border: 1px solid var(--clr-scale-ntrl-70);
overflow: auto;
border-radius: var(--radius-m);
}
code {
font-family: monospace;
background-color: var(--clr-scale-ntrl-90);
border: 1px solid var(--clr-scale-ntrl-70);
padding: 0 0.5em;
}
a {
text-decoration: underline;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
margin: 2em 0;
opacity: 0.2;
}
b,
strong {
font-weight: bolder;
}
}

254
ui/src/styles/reset.css Normal file
View File

@ -0,0 +1,254 @@
/* BOILERPLATE CSS */
/* reset all styles */
@layer reset {
/* base */
*,
:after,
:before {
box-sizing: border-box;
border: 0 solid;
}
html {
overscroll-behavior: none;
}
body {
font-family: 'Inter', sans-serif;
font-size: 13px;
line-height: inherit;
height: 100vh;
width: 100vw;
overflow-y: hidden;
padding: 0;
margin: 0;
line-height: inherit;
color: var(--clr-text-1);
background-color: var(--clr-bg-2);
/* optimise font rendering */
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
/* elements */
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
text-decoration: inherit;
}
b,
strong {
font-weight: bolder;
}
code,
kbd,
pre,
samp {
font-family:
Söhne Mono,
monospace;
font-feature-settings: normal;
font-variation-settings: normal;
font-size: 1em;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
font-size: 100%;
font-weight: inherit;
line-height: inherit;
letter-spacing: inherit;
color: inherit;
margin: 0;
padding: 0;
}
button,
select {
text-transform: none;
}
button,
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
-webkit-appearance: button;
background-color: transparent;
background-image: none;
}
:-moz-focusring {
outline: auto;
}
:-moz-ui-invalid {
box-shadow: none;
}
progress {
vertical-align: baseline;
}
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
[type='search'] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
summary {
display: list-item;
}
blockquote,
dd,
dl,
figure,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
}
fieldset,
legend {
padding: 0;
}
menu,
ol,
ul {
list-style: none;
margin: 0;
padding: 0;
}
dialog {
padding: 0;
}
textarea {
resize: vertical;
}
input::-moz-placeholder,
textarea::-moz-placeholder {
opacity: 1;
color: #9ca3af;
}
input::placeholder,
textarea::placeholder {
opacity: 1;
color: #9ca3af;
}
[role='button'],
button {
cursor: pointer;
}
:disabled {
cursor: default;
}
audio,
canvas,
embed,
iframe,
img,
object,
svg,
video {
display: block;
vertical-align: middle;
}
img,
video {
max-width: 100%;
height: auto;
}
[hidden] {
display: none;
}
}

View File

@ -0,0 +1,197 @@
.token-variable {
color: #8953800;
}
.token-property {
color: #0550ae;
}
.token-type {
color: #116329;
}
.token-variable-special {
color: #953800;
}
.token-definition {
color: #953800;
}
/* .token-builtin {
color: #d3869b;
} */
.token-number {
color: #0550ae;
}
.token-string {
color: #0550ae;
}
.token-string-special {
color: #0a3069;
}
/* .token-atom {
color: #0a3069;
} */
.token-keyword {
color: #cf222e;
}
.token-comment {
color: #6e7781;
}
.token-meta {
color: #1f2328;
}
.token-invalid {
color: #82071e;
}
.token-tag {
color: #116329;
}
.token-attribute {
color: #1f2328;
}
.token-attribute-value {
color: var(--color-token-attribute-value);
}
.token-inserted {
color: #116329;
}
.token-deleted {
color: #82071e;
}
.token-heading {
color: var(--color-token-variable-special);
font-weight: bold;
}
.token-link {
color: var(--color-token-variable-special);
text-decoration: underline;
}
.token-strikethrough {
text-decoration: strike-through;
}
.token-strong {
font-weight: bold;
}
.token-emphasis {
font-style: italic;
}
.dark {
.token-variable {
color: #79c0ff;
}
.token-property {
color: #79c0ff;
}
.token-type {
color: #7ee787;
}
.token-variable-special {
color: #79c0ff;
}
.token-definition {
color: #ffa657;
}
/* .token-builtin {
color: #d3869b;
} */
.token-number {
color: #a5d6ff;
}
.token-string {
color: #79c0ff;
}
.token-string-special {
color: #a5d6ff;
}
/* .token-atom {
color: #0a3069;
} */
.token-keyword {
color: #ff7b72;
}
.token-comment {
color: #8b949e;
}
.token-meta {
color: #ffa657;
}
.token-invalid {
color: #ffa198;
}
.token-tag {
color: #7ee787;
}
.token-attribute {
color: #e6edf3;
}
.token-attribute-value {
color: var(--color-token-attribute-value);
}
.token-inserted {
color: #7ee787;
}
.token-deleted {
color: #ffa198;
}
.token-heading {
color: var(--color-token-variable-special);
font-weight: bold;
}
.token-link {
color: var(--color-token-variable-special);
text-decoration: underline;
}
.token-strikethrough {
text-decoration: strike-through;
}
.token-strong {
font-weight: bold;
}
.token-emphasis {
font-style: italic;
}
}

View File

@ -0,0 +1,186 @@
:root {
--base-font-family: 'Inter', sans-serif;
--serif-font-family: 'PP Editorial New', serif;
--base-font-weight: 500;
--semidold-font-weight: 600;
--bold-font-weight: 700;
--base-line-height: 120%;
--text-body-line-height: 150%;
}
/* text Base Classes */
.text-base-8 {
font-family: var(--base-font-family);
font-size: 0.5rem;
font-weight: var(--base-font-weight);
line-height: var(--base-line-height);
}
.text-base-9 {
font-family: var(--base-font-family);
font-size: 0.563rem;
font-weight: var(--base-font-weight);
line-height: var(--base-line-height);
}
.text-base-10 {
font-family: var(--base-font-family);
font-size: 0.625rem;
font-weight: var(--base-font-weight);
line-height: var(--base-line-height);
}
.text-base-11 {
font-family: var(--base-font-family);
font-size: 0.6875rem;
font-weight: var(--base-font-weight);
line-height: var(--base-line-height);
}
.text-base-12 {
font-family: var(--base-font-family);
font-size: 0.75rem;
font-weight: var(--base-font-weight);
line-height: var(--base-line-height);
}
.text-base-13 {
font-family: var(--base-font-family);
font-size: 0.8125rem;
font-weight: var(--base-font-weight);
line-height: var(--base-line-height);
}
.text-base-14 {
font-family: var(--base-font-family);
font-size: 0.875rem;
font-weight: var(--base-font-weight);
line-height: var(--base-line-height);
}
.text-base-15 {
font-family: var(--base-font-family);
font-size: 0.938rem;
font-weight: var(--base-font-weight);
line-height: var(--base-line-height);
}
.text-base-16 {
font-family: var(--base-font-family);
font-size: 1rem;
font-weight: var(--base-font-weight);
line-height: var(--base-line-height);
}
.text-base-18 {
font-family: var(--base-font-family);
font-size: 1.125rem;
font-weight: var(--base-font-weight);
line-height: var(--base-line-height);
}
/* text Base Body Classes */
.text-base-body-10 {
font-family: var(--base-font-family);
font-size: 0.625rem;
font-weight: var(--base-font-weight);
line-height: var(--text-body-line-height);
}
.text-base-body-11 {
font-family: var(--base-font-family);
font-size: 0.6875rem;
font-weight: var(--base-font-weight);
line-height: var(--text-body-line-height);
}
.text-base-body-12 {
font-family: var(--base-font-family);
font-size: 0.75rem;
font-weight: var(--base-font-weight);
line-height: var(--text-body-line-height);
}
.text-base-body-13 {
font-family: var(--base-font-family);
font-size: 0.8125rem;
font-weight: var(--base-font-weight);
line-height: var(--text-body-line-height);
}
.text-base-body-14 {
font-family: var(--base-font-family);
font-size: 0.875rem;
font-weight: var(--base-font-weight);
line-height: var(--text-body-line-height);
}
.text-base-body-15 {
font-family: var(--base-font-family);
font-size: 0.938rem;
font-weight: var(--base-font-weight);
line-height: var(--text-body-line-height);
}
.text-base-body-16 {
font-family: var(--base-font-family);
font-size: 1rem;
font-weight: var(--base-font-weight);
line-height: var(--text-body-line-height);
}
.text-base-body-18 {
font-family: var(--base-font-family);
font-size: 1.125rem;
font-weight: var(--base-font-weight);
line-height: var(--text-body-line-height);
}
/* text Head Classes */
.text-head-20 {
font-family: var(--base-font-family);
font-size: 1.25rem;
font-weight: var(--bold-font-weight);
line-height: var(--base-line-height);
}
.text-head-22 {
font-family: var(--base-font-family);
font-size: 1.375rem;
font-weight: var(--bold-font-weight);
line-height: var(--base-line-height);
}
.text-head-24 {
font-family: var(--base-font-family);
font-size: 1.5rem;
font-weight: var(--bold-font-weight);
line-height: var(--base-line-height);
}
/* Serif Classes */
.text-serif-40 {
font-family: var(--serif-font-family);
font-size: 2.5rem;
line-height: var(--text-body-line-height);
}
.text-serif-24 {
font-family: var(--serif-font-family);
font-size: 1.5rem;
line-height: var(--text-body-line-height);
}
/* modifiers */
.text-light {
font-weight: var(--light-font-weight);
}
.text-semibold {
font-weight: var(--semidold-font-weight);
}
.text-bold {
font-weight: var(--bold-font-weight);
}

View File

@ -0,0 +1,32 @@
.text-input {
padding: 4px 10px;
color: var(--clr-scale-ntrl-0);
background-color: var(--clr-bg-1);
border: 1px solid var(--clr-border-2);
border-radius: var(--radius-s);
transition: border-color var(--transition-fast);
&::placeholder {
color: var(--clr-text-3);
}
&:hover {
border-color: var(--clr-border-1);
}
&:focus,
&:focus-within {
border-color: var(--clr-border-1);
outline: none;
}
&:invalid {
border-color: var(--clr-theme-err-element);
}
&:disabled {
color: var(--clr-scale-ntrl-60);
border-color: var(--clr-scale-ntrl-70);
background-color: var(--clr-bg-2);
}
}

368
ui/src/styles/tokens.css Normal file
View File

@ -0,0 +1,368 @@
/**
* Design Tokens
* Autogenerated from tokens.json.
* DO NOT EDIT!
*/
:root {
--clr-bg-1: var(--clr-core-ntrl-100);
--clr-bg-1-muted: var(--clr-core-ntrl-95);
--clr-bg-2: var(--clr-core-ntrl-90);
--clr-bg-2-muted: var(--clr-core-ntrl-80);
--clr-bg-3: var(--clr-core-ntrl-80);
--clr-bg-3-muted: var(--clr-core-ntrl-70);
--clr-border-1: var(--clr-core-ntrl-60);
--clr-border-2: var(--clr-core-ntrl-70);
--clr-border-3: var(--clr-core-ntrl-80);
--clr-commit-local: var(--clr-core-pop-50);
--clr-commit-remote: var(--clr-core-ntrl-50);
--clr-commit-shadow: var(--clr-core-ntrl-60);
--clr-commit-upstream: var(--clr-core-warn-50);
--clr-core-err-5: #260d0f;
--clr-core-err-10: #4c1a1f;
--clr-core-err-20: #6b242b;
--clr-core-err-30: #95323c;
--clr-core-err-40: #bd4852;
--clr-core-err-50: #dc606b;
--clr-core-err-60: #edb0ba;
--clr-core-err-70: #f7d9dc;
--clr-core-err-80: #fbe5e7;
--clr-core-err-90: #fdf1f4;
--clr-core-err-95: #fffafc;
--clr-core-ntrl-0: #0a0a0a;
--clr-core-ntrl-5: #181412;
--clr-core-ntrl-10: #25211f;
--clr-core-ntrl-20: #302c2b;
--clr-core-ntrl-30: #4f4b48;
--clr-core-ntrl-40: #67635e;
--clr-core-ntrl-50: #867e79;
--clr-core-ntrl-60: #b4afac;
--clr-core-ntrl-70: #d4d0ce;
--clr-core-ntrl-80: #eae9e8;
--clr-core-ntrl-90: #f3f3f2;
--clr-core-ntrl-95: #f7f7f6;
--clr-core-ntrl-100: #ffffff;
--clr-core-pop-5: #0e2a29;
--clr-core-pop-10: #133937;
--clr-core-pop-20: #1c5451;
--clr-core-pop-30: #256f6b;
--clr-core-pop-40: #2a928d;
--clr-core-pop-50: #48a8a3;
--clr-core-pop-60: #97cecb;
--clr-core-pop-70: #c6e7e5;
--clr-core-pop-80: #daf1f0;
--clr-core-pop-90: #e9f7f6;
--clr-core-pop-95: #f4fbfa;
--clr-core-purp-5: #281d44;
--clr-core-purp-10: #322352;
--clr-core-purp-20: #4b3479;
--clr-core-purp-30: #5e429a;
--clr-core-purp-40: #7657b7;
--clr-core-purp-50: #9978dd;
--clr-core-purp-60: #c2acf1;
--clr-core-purp-70: #dfd3f8;
--clr-core-purp-80: #e9e0fa;
--clr-core-purp-90: #f2edfc;
--clr-core-purp-95: #f9f6fd;
--clr-core-succ-5: #0d261a;
--clr-core-succ-10: #18392a;
--clr-core-succ-20: #22533c;
--clr-core-succ-30: #2a7e57;
--clr-core-succ-40: #469b73;
--clr-core-succ-50: #4eb182;
--clr-core-succ-60: #a1ceb9;
--clr-core-succ-70: #cae8da;
--clr-core-succ-80: #d6f0e4;
--clr-core-succ-90: #e8f7f0;
--clr-core-succ-95: #f6fcfb;
--clr-core-warn-5: #261c0d;
--clr-core-warn-10: #432d0f;
--clr-core-warn-20: #604116;
--clr-core-warn-30: #8a5914;
--clr-core-warn-40: #c77f1a;
--clr-core-warn-50: #dc9b14;
--clr-core-warn-60: #f4bb6c;
--clr-core-warn-70: #feddae;
--clr-core-warn-80: #ffe8c7;
--clr-core-warn-90: #fff2e0;
--clr-core-warn-95: #fdf7ed;
--clr-illustration-bg: #d6f0ee;
--clr-illustration-fill: #fcfcf1;
--clr-illustration-outline: #475050;
--clr-overlay-bg: #d6d6d666;
--clr-scale-err-5: var(--clr-core-err-5);
--clr-scale-err-10: var(--clr-core-err-10);
--clr-scale-err-20: var(--clr-core-err-20);
--clr-scale-err-30: var(--clr-core-err-30);
--clr-scale-err-40: var(--clr-core-err-40);
--clr-scale-err-50: var(--clr-core-err-50);
--clr-scale-err-60: var(--clr-core-err-60);
--clr-scale-err-70: var(--clr-core-err-70);
--clr-scale-err-80: var(--clr-core-err-80);
--clr-scale-err-90: var(--clr-core-err-90);
--clr-scale-err-95: var(--clr-core-err-95);
--clr-scale-ntrl-0: var(--clr-core-ntrl-0);
--clr-scale-ntrl-5: var(--clr-core-ntrl-5);
--clr-scale-ntrl-10: var(--clr-core-ntrl-10);
--clr-scale-ntrl-20: var(--clr-core-ntrl-20);
--clr-scale-ntrl-30: var(--clr-core-ntrl-30);
--clr-scale-ntrl-40: var(--clr-core-ntrl-40);
--clr-scale-ntrl-50: var(--clr-core-ntrl-50);
--clr-scale-ntrl-60: var(--clr-core-ntrl-60);
--clr-scale-ntrl-70: var(--clr-core-ntrl-70);
--clr-scale-ntrl-80: var(--clr-core-ntrl-80);
--clr-scale-ntrl-90: var(--clr-core-ntrl-90);
--clr-scale-ntrl-95: var(--clr-core-ntrl-95);
--clr-scale-ntrl-100: var(--clr-core-ntrl-100);
--clr-scale-pop-5: var(--clr-core-pop-5);
--clr-scale-pop-10: var(--clr-core-pop-10);
--clr-scale-pop-20: var(--clr-core-pop-20);
--clr-scale-pop-30: var(--clr-core-pop-30);
--clr-scale-pop-40: var(--clr-core-pop-40);
--clr-scale-pop-50: var(--clr-core-pop-50);
--clr-scale-pop-60: var(--clr-core-pop-60);
--clr-scale-pop-70: var(--clr-core-pop-70);
--clr-scale-pop-80: var(--clr-core-pop-80);
--clr-scale-pop-90: var(--clr-core-pop-90);
--clr-scale-pop-95: var(--clr-core-pop-95);
--clr-scale-purp-5: var(--clr-core-purp-5);
--clr-scale-purp-10: var(--clr-core-purp-10);
--clr-scale-purp-20: var(--clr-core-purp-20);
--clr-scale-purp-30: var(--clr-core-purp-30);
--clr-scale-purp-40: var(--clr-core-purp-40);
--clr-scale-purp-50: var(--clr-core-purp-50);
--clr-scale-purp-60: var(--clr-core-purp-60);
--clr-scale-purp-70: var(--clr-core-purp-70);
--clr-scale-purp-80: var(--clr-core-purp-80);
--clr-scale-purp-90: var(--clr-core-purp-90);
--clr-scale-purp-95: var(--clr-core-purp-95);
--clr-scale-succ-5: var(--clr-core-succ-5);
--clr-scale-succ-10: var(--clr-core-succ-10);
--clr-scale-succ-20: var(--clr-core-succ-20);
--clr-scale-succ-30: var(--clr-core-succ-30);
--clr-scale-succ-40: var(--clr-core-succ-40);
--clr-scale-succ-50: var(--clr-core-succ-50);
--clr-scale-succ-60: var(--clr-core-succ-60);
--clr-scale-succ-70: var(--clr-core-succ-70);
--clr-scale-succ-80: var(--clr-core-succ-80);
--clr-scale-succ-90: var(--clr-core-succ-90);
--clr-scale-succ-95: var(--clr-core-succ-95);
--clr-scale-warn-5: var(--clr-core-warn-5);
--clr-scale-warn-10: var(--clr-core-warn-10);
--clr-scale-warn-20: var(--clr-core-warn-20);
--clr-scale-warn-30: var(--clr-core-warn-30);
--clr-scale-warn-40: var(--clr-core-warn-40);
--clr-scale-warn-50: var(--clr-core-warn-50);
--clr-scale-warn-60: var(--clr-core-warn-60);
--clr-scale-warn-70: var(--clr-core-warn-70);
--clr-scale-warn-80: var(--clr-core-warn-80);
--clr-scale-warn-90: var(--clr-core-warn-90);
--clr-scale-warn-95: var(--clr-core-warn-95);
--clr-text-1: var(--clr-core-ntrl-5);
--clr-text-2: var(--clr-core-ntrl-50);
--clr-text-3: var(--clr-core-ntrl-60);
--clr-theme-err-bg: var(--clr-core-err-90);
--clr-theme-err-element: var(--clr-core-err-50);
--clr-theme-err-element-hover: var(--clr-core-err-40);
--clr-theme-err-on-element: var(--clr-core-err-95);
--clr-theme-err-on-soft: var(--clr-core-err-20);
--clr-theme-err-soft: var(--clr-core-err-80);
--clr-theme-err-soft-hover: var(--clr-core-err-70);
--clr-theme-ntrl-element: var(--clr-core-ntrl-30);
--clr-theme-ntrl-element-hover: var(--clr-core-ntrl-20);
--clr-theme-ntrl-on-element: var(--clr-core-ntrl-100);
--clr-theme-ntrl-on-soft: var(--clr-scale-ntrl-0);
--clr-theme-ntrl-soft: var(--clr-core-ntrl-90);
--clr-theme-ntrl-soft-hover: var(--clr-core-ntrl-80);
--clr-theme-pop-bg: var(--clr-core-pop-90);
--clr-theme-pop-element: var(--clr-core-pop-50);
--clr-theme-pop-element-hover: var(--clr-core-pop-40);
--clr-theme-pop-on-element: var(--clr-core-ntrl-100);
--clr-theme-pop-on-soft: var(--clr-core-pop-20);
--clr-theme-pop-soft: var(--clr-core-pop-80);
--clr-theme-pop-soft-hover: var(--clr-core-pop-70);
--clr-theme-purp-bg: var(--clr-core-purp-90);
--clr-theme-purp-element: var(--clr-core-purp-50);
--clr-theme-purp-element-hover: var(--clr-core-purp-40);
--clr-theme-purp-on-element: var(--clr-core-purp-95);
--clr-theme-purp-on-soft: var(--clr-core-purp-20);
--clr-theme-purp-soft: var(--clr-core-purp-80);
--clr-theme-purp-soft-hover: var(--clr-core-purp-70);
--clr-theme-succ-bg: var(--clr-core-succ-90);
--clr-theme-succ-element: var(--clr-core-succ-50);
--clr-theme-succ-element-hover: var(--clr-core-succ-40);
--clr-theme-succ-on-element: var(--clr-core-succ-95);
--clr-theme-succ-on-soft: var(--clr-core-succ-20);
--clr-theme-succ-soft: var(--clr-core-succ-80);
--clr-theme-succ-soft-hover: var(--clr-core-succ-70);
--clr-theme-warn-bg: var(--clr-core-warn-90);
--clr-theme-warn-element: var(--clr-core-warn-50);
--clr-theme-warn-element-hover: var(--clr-core-warn-40);
--clr-theme-warn-on-element: var(--clr-core-warn-95);
--clr-theme-warn-on-soft: var(--clr-core-warn-20);
--clr-theme-warn-soft: var(--clr-core-warn-80);
--clr-theme-warn-soft-hover: var(--clr-core-warn-70);
--fx-shadow-l: 0 10px 40px 0 #0000001a;
--fx-shadow-m: 0 6px 30px 0 #00000014;
--fx-shadow-s: 0 4px 14px 0 #0000000f;
--radius-l: 0.75rem;
--radius-m: 0.375rem;
--radius-s: 0.25rem;
--size-2: 0.125rem;
--size-4: 0.25rem;
--size-6: 0.375rem;
--size-8: 0.5rem;
--size-10: 0.625rem;
--size-12: 0.75rem;
--size-14: 0.875rem;
--size-16: 1rem;
--size-20: 1.25rem;
--size-24: 1.5rem;
--size-26: 1.625rem;
--size-28: 1.75rem;
--size-32: 2rem;
--size-36: 2.25rem;
--size-40: 2.5rem;
--size-48: 3rem;
--size-56: 3.5rem;
--size-64: 4rem;
--size-80: 5rem;
--size-96: 6rem;
--size-button: 1.75rem;
--size-card-padding: 0.875rem;
--size-cta: 2rem;
--size-icon: 1rem;
--size-tag: 1.375rem;
}
:root.dark {
--clr-bg-1: var(--clr-core-ntrl-10);
--clr-bg-1-muted: var(--clr-core-ntrl-20);
--clr-bg-2: var(--clr-core-ntrl-5);
--clr-bg-2-muted: var(--clr-core-ntrl-10);
--clr-bg-3: var(--clr-core-ntrl-0);
--clr-bg-3-muted: var(--clr-core-ntrl-5);
--clr-border-1: var(--clr-core-ntrl-40);
--clr-border-2: var(--clr-core-ntrl-30);
--clr-border-3: var(--clr-core-ntrl-20);
--clr-commit-local: var(--clr-core-pop-50);
--clr-commit-remote: var(--clr-core-ntrl-50);
--clr-commit-shadow: var(--clr-core-ntrl-40);
--clr-commit-upstream: var(--clr-core-warn-50);
--clr-illustration-bg: #174543;
--clr-illustration-fill: #95b5aa;
--clr-illustration-outline: #142222;
--clr-overlay-bg: #00000059;
--clr-scale-err-5: var(--clr-core-err-95);
--clr-scale-err-10: var(--clr-core-err-90);
--clr-scale-err-20: var(--clr-core-err-80);
--clr-scale-err-30: var(--clr-core-err-70);
--clr-scale-err-40: var(--clr-core-err-60);
--clr-scale-err-50: var(--clr-core-err-50);
--clr-scale-err-60: var(--clr-core-err-40);
--clr-scale-err-70: var(--clr-core-err-30);
--clr-scale-err-80: var(--clr-core-err-20);
--clr-scale-err-90: var(--clr-core-err-10);
--clr-scale-err-95: var(--clr-core-err-5);
--clr-scale-ntrl-0: var(--clr-core-ntrl-100);
--clr-scale-ntrl-5: var(--clr-core-ntrl-95);
--clr-scale-ntrl-10: var(--clr-core-ntrl-90);
--clr-scale-ntrl-20: var(--clr-core-ntrl-80);
--clr-scale-ntrl-30: var(--clr-core-ntrl-70);
--clr-scale-ntrl-40: var(--clr-core-ntrl-60);
--clr-scale-ntrl-50: var(--clr-core-ntrl-50);
--clr-scale-ntrl-60: var(--clr-core-ntrl-40);
--clr-scale-ntrl-70: var(--clr-core-ntrl-30);
--clr-scale-ntrl-80: var(--clr-core-ntrl-20);
--clr-scale-ntrl-90: var(--clr-core-ntrl-10);
--clr-scale-ntrl-95: var(--clr-core-ntrl-5);
--clr-scale-ntrl-100: var(--clr-core-ntrl-0);
--clr-scale-pop-5: var(--clr-core-pop-95);
--clr-scale-pop-10: var(--clr-core-pop-90);
--clr-scale-pop-20: var(--clr-core-pop-80);
--clr-scale-pop-30: var(--clr-core-pop-70);
--clr-scale-pop-40: var(--clr-core-pop-60);
--clr-scale-pop-50: var(--clr-core-pop-50);
--clr-scale-pop-60: var(--clr-core-pop-40);
--clr-scale-pop-70: var(--clr-core-pop-30);
--clr-scale-pop-80: var(--clr-core-pop-20);
--clr-scale-pop-90: var(--clr-core-pop-10);
--clr-scale-pop-95: var(--clr-core-pop-5);
--clr-scale-purp-5: var(--clr-core-purp-95);
--clr-scale-purp-10: var(--clr-core-purp-90);
--clr-scale-purp-20: var(--clr-core-purp-80);
--clr-scale-purp-30: var(--clr-core-purp-70);
--clr-scale-purp-40: var(--clr-core-purp-60);
--clr-scale-purp-50: var(--clr-core-purp-50);
--clr-scale-purp-60: var(--clr-core-purp-40);
--clr-scale-purp-70: var(--clr-core-purp-30);
--clr-scale-purp-80: var(--clr-core-purp-20);
--clr-scale-purp-90: var(--clr-core-purp-10);
--clr-scale-purp-95: var(--clr-core-purp-5);
--clr-scale-succ-5: var(--clr-core-succ-95);
--clr-scale-succ-10: var(--clr-core-succ-90);
--clr-scale-succ-20: var(--clr-core-succ-80);
--clr-scale-succ-30: var(--clr-core-succ-70);
--clr-scale-succ-40: var(--clr-core-succ-60);
--clr-scale-succ-50: var(--clr-core-succ-50);
--clr-scale-succ-60: var(--clr-core-succ-40);
--clr-scale-succ-70: var(--clr-core-succ-30);
--clr-scale-succ-80: var(--clr-core-succ-20);
--clr-scale-succ-90: var(--clr-core-succ-10);
--clr-scale-succ-95: var(--clr-core-succ-5);
--clr-scale-warn-5: var(--clr-core-warn-95);
--clr-scale-warn-10: var(--clr-core-warn-90);
--clr-scale-warn-20: var(--clr-core-warn-80);
--clr-scale-warn-30: var(--clr-core-warn-70);
--clr-scale-warn-40: var(--clr-core-warn-60);
--clr-scale-warn-50: var(--clr-core-warn-50);
--clr-scale-warn-60: var(--clr-core-warn-40);
--clr-scale-warn-70: var(--clr-core-warn-30);
--clr-scale-warn-80: var(--clr-core-warn-20);
--clr-scale-warn-90: var(--clr-core-warn-10);
--clr-scale-warn-95: var(--clr-core-warn-5);
--clr-text-1: var(--clr-core-ntrl-95);
--clr-text-2: var(--clr-core-ntrl-60);
--clr-text-3: var(--clr-core-ntrl-40);
--clr-theme-err-bg: var(--clr-core-err-10);
--clr-theme-err-element: var(--clr-core-err-40);
--clr-theme-err-element-hover: var(--clr-core-err-30);
--clr-theme-err-on-element: var(--clr-core-err-95);
--clr-theme-err-on-soft: var(--clr-core-err-90);
--clr-theme-err-soft: var(--clr-core-err-20);
--clr-theme-err-soft-hover: var(--clr-core-err-30);
--clr-theme-ntrl-element: var(--clr-core-ntrl-80);
--clr-theme-ntrl-element-hover: var(--clr-core-ntrl-95);
--clr-theme-ntrl-on-element: var(--clr-core-ntrl-0);
--clr-theme-ntrl-on-soft: var(--clr-core-ntrl-90);
--clr-theme-ntrl-soft: var(--clr-core-ntrl-20);
--clr-theme-ntrl-soft-hover: var(--clr-core-ntrl-30);
--clr-theme-pop-bg: var(--clr-core-pop-10);
--clr-theme-pop-element: var(--clr-core-pop-40);
--clr-theme-pop-element-hover: var(--clr-core-pop-30);
--clr-theme-pop-on-element: var(--clr-core-ntrl-100);
--clr-theme-pop-on-soft: var(--clr-core-pop-90);
--clr-theme-pop-soft: var(--clr-core-pop-10);
--clr-theme-pop-soft-hover: var(--clr-core-pop-20);
--clr-theme-purp-bg: var(--clr-core-purp-10);
--clr-theme-purp-element: var(--clr-core-purp-40);
--clr-theme-purp-element-hover: var(--clr-core-purp-30);
--clr-theme-purp-on-element: var(--clr-core-purp-90);
--clr-theme-purp-on-soft: var(--clr-core-purp-90);
--clr-theme-purp-soft: var(--clr-core-purp-20);
--clr-theme-purp-soft-hover: var(--clr-core-purp-10);
--clr-theme-succ-bg: var(--clr-core-succ-10);
--clr-theme-succ-element: var(--clr-core-succ-40);
--clr-theme-succ-element-hover: var(--clr-core-succ-30);
--clr-theme-succ-on-element: var(--clr-core-succ-90);
--clr-theme-succ-on-soft: var(--clr-core-succ-90);
--clr-theme-succ-soft: var(--clr-core-succ-10);
--clr-theme-succ-soft-hover: var(--clr-core-succ-20);
--clr-theme-warn-bg: var(--clr-core-warn-10);
--clr-theme-warn-element: var(--clr-core-warn-40);
--clr-theme-warn-element-hover: var(--clr-core-warn-30);
--clr-theme-warn-on-element: var(--clr-core-warn-90);
--clr-theme-warn-on-soft: var(--clr-core-warn-90);
--clr-theme-warn-soft: var(--clr-core-warn-20);
--clr-theme-warn-soft-hover: var(--clr-core-warn-30);
}

33
ui/src/styles/tooltip.css Normal file
View File

@ -0,0 +1,33 @@
.tooltip {
pointer-events: none;
background-color: var(--clr-core-ntrl-10);
border-radius: var(--radius-s);
border: 1px solid var(--clr-core-ntrl-30);
color: var(--clr-core-ntrl-60);
display: inline-block;
padding: 6px 8px;
z-index: var(--z-tooltip);
max-width: 180px;
position: absolute;
left: -9999px;
top: -9999px;
opacity: 0;
animation: showup-tooltip-animation 0.1s ease-out forwards;
&.no-max-width {
max-width: unset;
}
}
@keyframes showup-tooltip-animation {
from {
opacity: 0;
transform: translateY(-0.2rem) scale(0.9);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}

BIN
ui/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

22
ui/svelte.config.js Normal file
View File

@ -0,0 +1,22 @@
import staticAdapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: staticAdapter({
pages: 'build',
assets: 'build',
fallback: 'index.html',
precompress: true,
strict: false
})
},
compilerOptions: {
css: 'injected',
enableSourcemap: true
}
};
export default config;

19
ui/tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"target": "es6",
"lib": ["dom", "dom.iterable", "ES2021"],
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"experimentalDecorators": true,
"types": ["vitest/importMeta"]
}
}

38
ui/vite.config.ts Normal file
View File

@ -0,0 +1,38 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [
sveltekit()
],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// prevent vite from obscuring rust errors
clearScreen: false,
// tauri expects a fixed port, fail if that port is not available
server: {
port: 1420,
strictPort: true
},
// to make use of `TAURI_DEBUG` and other env variables
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
envPrefix: ['VITE_', 'TAURI_'],
resolve: {
conditions: ['es2015']
},
build: {
// Tauri supports es2021
target: process.env.TAURI_PLATFORM === 'windows' ? 'chrome105' : 'safari13',
// minify production builds
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
// ship sourcemaps for better sentry error reports
sourcemap: true
},
test: {
deps: {
inline: ['sorcery']
},
includeSource: ['src/**/*.{js,ts}'],
exclude: ['**/e2e/playwright/**/*', 'node_modules/**/*']
}
});