mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-25 18:49:11 +03:00
Merge branch 'master' of https://github.com/gitbutlerapp/gitbutler-client
This commit is contained in:
commit
b569a6b099
30
package.json
30
package.json
@ -13,40 +13,16 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.4.2",
|
||||
"@codemirror/commands": "^6.2.0",
|
||||
"@codemirror/lang-angular": "github:codemirror/lang-angular",
|
||||
"@codemirror/lang-css": "github:codemirror/lang-css",
|
||||
"@codemirror/lang-html": "github:codemirror/lang-html",
|
||||
"@codemirror/lang-java": "github:codemirror/lang-java",
|
||||
"@codemirror/lang-javascript": "^6.1.4",
|
||||
"@codemirror/lang-json": "github:codemirror/lang-json",
|
||||
"@codemirror/lang-markdown": "github:codemirror/lang-markdown",
|
||||
"@codemirror/lang-php": "github:codemirror/lang-php",
|
||||
"@codemirror/lang-python": "github:codemirror/lang-python",
|
||||
"@codemirror/lang-rust": "github:codemirror/lang-rust",
|
||||
"@codemirror/lang-vue": "github:codemirror/lang-vue",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/merge": "^0.1.3",
|
||||
"@codemirror/state": "^6.2.0",
|
||||
"@codemirror/view": "^6.7.3",
|
||||
"@lezer/common": "^1.0.2",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/javascript": "^1.4.1",
|
||||
"@lezer/lr": "^1.3.3",
|
||||
"@nextjournal/lang-clojure": "^1.0.0",
|
||||
"@replit/codemirror-lang-csharp": "^6.1.0",
|
||||
"@replit/codemirror-lang-svelte": "^6.0.0",
|
||||
"@square/svelte-store": "^1.0.14",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"diff": "^5.1.0",
|
||||
"fluent-svelte": "^1.6.0",
|
||||
"idb-keyval": "^6.2.0",
|
||||
"diff2html": "^3.4.33",
|
||||
"highlight.js": "^11.7.0",
|
||||
"highlightjs-svelte": "^1.0.6",
|
||||
"inter-ui": "^3.19.3",
|
||||
"nanoid": "^4.0.1",
|
||||
"posthog-js": "^1.46.1",
|
||||
"seti-icons": "^0.0.4",
|
||||
"svelte-french-toast": "^1.0.3",
|
||||
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log"
|
||||
},
|
||||
|
1145
pnpm-lock.yaml
1145
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
99
src/lib/components/CodeViewer.svelte
Normal file
99
src/lib/components/CodeViewer.svelte
Normal file
@ -0,0 +1,99 @@
|
||||
<script lang="ts">
|
||||
import { type Delta, Operation } from '$lib/deltas';
|
||||
import { createTwoFilesPatch } from 'diff';
|
||||
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/base16/gruvbox-dark-medium.css';
|
||||
|
||||
import { parse } from 'diff2html';
|
||||
import type { DiffBlock, LineType } from 'diff2html/lib/types';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(hljs.initHighlighting);
|
||||
|
||||
export let doc: string;
|
||||
export let deltas: Delta[];
|
||||
export let filepath: string;
|
||||
|
||||
const applyDeltas = (text: string, deltas: Delta[]) => {
|
||||
const operations = deltas.flatMap((delta) => delta.operations);
|
||||
|
||||
operations.forEach((operation) => {
|
||||
if (Operation.isInsert(operation)) {
|
||||
text =
|
||||
text.slice(0, operation.insert[0]) +
|
||||
operation.insert[1] +
|
||||
text.slice(operation.insert[0]);
|
||||
} else if (Operation.isDelete(operation)) {
|
||||
text =
|
||||
text.slice(0, operation.delete[0]) +
|
||||
text.slice(operation.delete[0] + operation.delete[1]);
|
||||
}
|
||||
});
|
||||
return text;
|
||||
};
|
||||
|
||||
const highlightBlocks = (blocks: DiffBlock[]) =>
|
||||
blocks.map(({ header, lines }) => ({
|
||||
header,
|
||||
lines: lines.map(({ oldNumber, newNumber, type, content }) => ({
|
||||
oldNumber,
|
||||
newNumber,
|
||||
type,
|
||||
prefix: content.substring(0, 1),
|
||||
originalContent: content.substring(1),
|
||||
content: hljs.highlight(content.substring(1), { language }).value
|
||||
}))
|
||||
}));
|
||||
|
||||
const getLanguage = (filepath: string) => {
|
||||
const ext = filepath.split('.').pop();
|
||||
return ext && hljs.getLanguage(ext) ? ext : 'plaintext';
|
||||
};
|
||||
|
||||
let editor: HTMLElement;
|
||||
|
||||
$: left = deltas.length > 0 ? applyDeltas(doc, deltas.slice(0, deltas.length - 1)) : doc;
|
||||
$: right = deltas.length > 0 ? applyDeltas(left, deltas.slice(deltas.length - 1)) : left;
|
||||
$: patch = createTwoFilesPatch(filepath, filepath, left, right, '', '', { context: 100000 });
|
||||
$: language = getLanguage(filepath);
|
||||
$: parsed = parse(patch);
|
||||
$: parsed &&
|
||||
editor &&
|
||||
editor.querySelector('.changed')?.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
||||
|
||||
const bgColor = (type: LineType) =>
|
||||
type === 'insert' ? 'bg-[#14FF00]/20' : type === 'delete' ? 'bg-[#FF0000]/20' : '';
|
||||
</script>
|
||||
|
||||
<table class="w-full h-full whitespace-pre font-mono" bind:this={editor}>
|
||||
{#each parsed as hunk}
|
||||
<tbody>
|
||||
{#each highlightBlocks(hunk.blocks) as block}
|
||||
{#each block.lines as line}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="flex select-none justify-between gap-2">
|
||||
<div>{line.oldNumber ?? ''}</div>
|
||||
<div>{line.newNumber ?? ''}</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td
|
||||
class={bgColor(line.type)}
|
||||
class:changed={line.type === 'insert' || line.type === 'delete'}
|
||||
>
|
||||
<div class="d2h-code-line relative px-4">
|
||||
{#if line.content}
|
||||
<span>{@html line.content}</span>
|
||||
{:else}
|
||||
<span class="d2h-code-line-ctn whitespace-pre">{line.originalContent}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{/each}
|
||||
</tbody>
|
||||
{/each}
|
||||
</table>
|
@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Delta } from '$lib/deltas';
|
||||
import codeviewer from './action';
|
||||
|
||||
export let doc: string;
|
||||
export let deltas: Delta[];
|
||||
export let filepath: string;
|
||||
</script>
|
||||
|
||||
{#key doc + filepath}
|
||||
<code
|
||||
style:color-scheme="dark"
|
||||
class="h-full w-full"
|
||||
use:codeviewer={{ doc, deltas, filepath }}
|
||||
/>
|
||||
{/key}
|
@ -1,152 +0,0 @@
|
||||
import { Operation, type Delta } from '$lib/deltas';
|
||||
import {
|
||||
Text,
|
||||
ChangeSet,
|
||||
EditorState,
|
||||
EditorSelection,
|
||||
type ChangeSpec,
|
||||
SelectionRange
|
||||
} from '@codemirror/state';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { getLanguage } from './languages';
|
||||
import extensions from './extensions';
|
||||
import { markChanges } from './mark';
|
||||
|
||||
const toChangeSpec = (operation: Operation): ChangeSpec => {
|
||||
if (Operation.isInsert(operation)) {
|
||||
return {
|
||||
from: operation.insert[0],
|
||||
insert: operation.insert[1]
|
||||
};
|
||||
} else if (Operation.isDelete(operation)) {
|
||||
return {
|
||||
from: operation.delete[0],
|
||||
to: operation.delete[0] + operation.delete[1]
|
||||
};
|
||||
} else {
|
||||
throw new Error(`${operation} is not supported`);
|
||||
}
|
||||
};
|
||||
|
||||
type Params = { doc: string; deltas: Delta[]; filepath: string };
|
||||
|
||||
const makeBaseState = (doc: string, filepath: string) => {
|
||||
const language = getLanguage(filepath);
|
||||
return EditorState.create({
|
||||
doc,
|
||||
extensions: language ? [...extensions, language] : extensions
|
||||
});
|
||||
};
|
||||
|
||||
const toChangeSet = (deltas: Delta[], initLength: number): ChangeSet => {
|
||||
const specs = deltas.flatMap(({ operations }) => operations.map(toChangeSpec));
|
||||
const sets = specs.reduce((sets: ChangeSet[], spec) => {
|
||||
const set = ChangeSet.of(spec, sets.length > 0 ? sets[sets.length - 1].newLength : initLength);
|
||||
return [...sets, set];
|
||||
}, [] as ChangeSet[]);
|
||||
return sets.length > 0 ? sets.reduce((a, b) => a.compose(b)) : ChangeSet.empty(initLength);
|
||||
};
|
||||
|
||||
const toRange = (operation: Operation) => {
|
||||
if (Operation.isInsert(operation)) {
|
||||
const anchor = operation.insert[0];
|
||||
const head = operation.insert[0] + operation.insert[1].length;
|
||||
return EditorSelection.range(anchor, head);
|
||||
} else if (Operation.isDelete(operation)) {
|
||||
const anchor = operation.delete[0];
|
||||
const head = operation.delete[0] + operation.delete[1];
|
||||
return EditorSelection.range(anchor, head);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const toSelection = (changes: ChangeSet, delta: Delta | undefined): EditorSelection | undefined => {
|
||||
if (delta === undefined) return undefined;
|
||||
if (delta.operations.length === 0) return undefined;
|
||||
const ranges = delta.operations
|
||||
.map(toRange)
|
||||
.filter((r): r is SelectionRange => r !== undefined)
|
||||
.filter((range) => range.head <= changes.newLength && range.anchor <= changes.newLength);
|
||||
return ranges.length ? EditorSelection.create(ranges) : undefined;
|
||||
};
|
||||
|
||||
// this action assumes:
|
||||
// * that deltas list is append only.
|
||||
// * that each (filepath, doc) pair never changes.
|
||||
export default (parent: HTMLElement, { doc, deltas, filepath }: Params) => {
|
||||
const view = new EditorView({ state: makeBaseState(doc, filepath), parent });
|
||||
|
||||
const changes = toChangeSet(deltas, doc.length);
|
||||
const selection = toSelection(changes, deltas[deltas.length - 1]);
|
||||
view.dispatch(
|
||||
view.state.update({
|
||||
scrollIntoView: true,
|
||||
changes,
|
||||
selection,
|
||||
effects: markChanges(selection)
|
||||
})
|
||||
);
|
||||
|
||||
let currentFilepath = filepath;
|
||||
const stateCache: Record<string, EditorState> = {};
|
||||
const deltasCache: Record<string, Delta[]> = {};
|
||||
|
||||
stateCache[filepath] = view.state;
|
||||
deltasCache[filepath] = deltas;
|
||||
|
||||
return {
|
||||
update: ({ doc, deltas: newDeltas, filepath }: Params) => {
|
||||
if (filepath !== currentFilepath) {
|
||||
view.setState(stateCache[filepath] ?? makeBaseState(doc, filepath));
|
||||
}
|
||||
|
||||
const currentDeltas = deltasCache[filepath] || [];
|
||||
if (currentDeltas.length > newDeltas.length) {
|
||||
// rewind backward
|
||||
|
||||
const baseText = Text.of([doc]);
|
||||
const targetChange = toChangeSet(newDeltas, baseText.length);
|
||||
const targetText = targetChange.apply(baseText);
|
||||
|
||||
const deltasToRevert = currentDeltas.slice(newDeltas.length);
|
||||
const revertChange = toChangeSet(deltasToRevert, targetText.length);
|
||||
const changes = revertChange.invert(targetText);
|
||||
|
||||
const selection = toSelection(changes, newDeltas.at(-1));
|
||||
|
||||
view.dispatch({
|
||||
changes,
|
||||
selection,
|
||||
scrollIntoView: true,
|
||||
effects: markChanges(selection)
|
||||
});
|
||||
} else if (currentDeltas.length < newDeltas.length) {
|
||||
// rewind forward
|
||||
|
||||
// verify that deltas are append only
|
||||
currentDeltas.forEach((delta, i) => {
|
||||
if (i >= newDeltas.length) return;
|
||||
if (delta !== newDeltas[i]) throw new Error('deltas are not append only');
|
||||
});
|
||||
|
||||
const deltasToApply = newDeltas.slice(currentDeltas.length);
|
||||
const changes = toChangeSet(deltasToApply, view.state.doc.length);
|
||||
const selection = toSelection(changes, deltasToApply.at(-1));
|
||||
|
||||
view.dispatch({
|
||||
changes,
|
||||
selection,
|
||||
scrollIntoView: true,
|
||||
effects: markChanges(selection)
|
||||
});
|
||||
}
|
||||
|
||||
// don't forget to update caches
|
||||
stateCache[filepath] = view.state;
|
||||
deltasCache[filepath] = newDeltas;
|
||||
currentFilepath = filepath;
|
||||
},
|
||||
destroy: () => view.destroy()
|
||||
};
|
||||
};
|
@ -1,60 +0,0 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { HighlightStyle } from '@codemirror/language';
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import type { Theme } from './themes/theme';
|
||||
|
||||
export const colorTheme = (theme: Theme, options?: { dark: boolean }) =>
|
||||
EditorView.theme(
|
||||
{
|
||||
'&': {
|
||||
color: theme.fg0,
|
||||
backgroundColor: theme.bg0
|
||||
},
|
||||
'.cm-gutters': {
|
||||
color: theme.gray,
|
||||
backgroundColor: theme.bg0,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-mark': { backgroundColor: theme.bg2 }
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
export const highlightStyle = (theme: Theme) =>
|
||||
HighlightStyle.define([
|
||||
{ tag: t.tagName, color: theme.orange },
|
||||
{ tag: t.keyword, color: theme.red },
|
||||
{ tag: [t.propertyName, t.name, t.deleted, t.character, t.macroName], color: theme.blue },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: theme.green, fontWeight: 'bold' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: theme.yellow },
|
||||
{
|
||||
tag: [
|
||||
t.typeName,
|
||||
t.className,
|
||||
t.number,
|
||||
t.changed,
|
||||
t.annotation,
|
||||
t.modifier,
|
||||
t.self,
|
||||
t.namespace
|
||||
],
|
||||
color: theme.fg1
|
||||
},
|
||||
{
|
||||
tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)],
|
||||
color: theme.orange,
|
||||
fontStyle: 'italic'
|
||||
},
|
||||
{ tag: [t.meta, t.comment], color: theme.gray, fontStyle: 'italic' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: theme.blue },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: theme.purple },
|
||||
{
|
||||
tag: [t.processingInstruction, t.string, t.inserted],
|
||||
color: theme.green,
|
||||
fontStyle: 'italic'
|
||||
},
|
||||
{ tag: t.invalid, color: theme.fg0 }
|
||||
]);
|
@ -1,20 +0,0 @@
|
||||
import { colorTheme, highlightStyle } from './colors';
|
||||
import { EditorView, lineNumbers } from '@codemirror/view';
|
||||
import { syntaxHighlighting } from '@codemirror/language';
|
||||
import { gruvbox } from './themes';
|
||||
|
||||
const theme = gruvbox.dark;
|
||||
|
||||
const sizes = EditorView.theme({
|
||||
'&': { height: '100%', width: '100%' },
|
||||
'.cm-scroller': { overflow: 'scroll' }
|
||||
});
|
||||
|
||||
export default [
|
||||
colorTheme(theme), // set color theme
|
||||
syntaxHighlighting(highlightStyle(theme)), // highlight syntax
|
||||
EditorView.editable.of(false), // disable editing
|
||||
EditorView.lineWrapping, // wrap lines
|
||||
lineNumbers(), // show line numbers
|
||||
sizes // set size
|
||||
];
|
@ -1,3 +0,0 @@
|
||||
import { default as CodeViewer } from './CodeViewer.svelte';
|
||||
|
||||
export default CodeViewer;
|
@ -1,39 +0,0 @@
|
||||
import type { Extension } from '@codemirror/state';
|
||||
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { rust } from '@codemirror/lang-rust';
|
||||
import { markdown } from '@codemirror/lang-markdown';
|
||||
import { python } from '@codemirror/lang-python';
|
||||
import { html } from '@codemirror/lang-html';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { php } from '@codemirror/lang-php';
|
||||
import { java } from '@codemirror/lang-java';
|
||||
import { css } from '@codemirror/lang-css';
|
||||
import { svelte } from '@replit/codemirror-lang-svelte';
|
||||
import { vue } from '@codemirror/lang-vue';
|
||||
import { angular } from '@codemirror/lang-angular';
|
||||
import { csharp } from '@replit/codemirror-lang-csharp';
|
||||
import { clojure } from '@nextjournal/lang-clojure';
|
||||
|
||||
const supported = new Map<string, Extension>([
|
||||
['.js', javascript({ jsx: false, typescript: false })],
|
||||
['.ts', javascript({ jsx: false, typescript: true })],
|
||||
['.jsx', javascript({ jsx: true, typescript: false })],
|
||||
['.tsx', javascript({ jsx: true, typescript: true })],
|
||||
['.rs', rust()],
|
||||
['.md', markdown()],
|
||||
['.py', python()],
|
||||
['.html', html()],
|
||||
['.json', json()],
|
||||
['.php', php()],
|
||||
['.java', java()],
|
||||
['.css', css()],
|
||||
['.svelte', svelte()],
|
||||
['.vue', vue()],
|
||||
['.angular', angular()],
|
||||
['.cs', csharp()],
|
||||
['.clj', clojure()]
|
||||
]);
|
||||
|
||||
export const getLanguage = (filepath: string) =>
|
||||
supported.get(filepath.slice(filepath.lastIndexOf('.')));
|
@ -1,29 +0,0 @@
|
||||
import { EditorView, Decoration, type DecorationSet } from '@codemirror/view';
|
||||
import { StateField, StateEffect, EditorSelection, RangeSet } from '@codemirror/state';
|
||||
|
||||
const addMark = StateEffect.define<{ from: number; to: number }>({
|
||||
map: ({ from, to }, change) => ({ from: change.mapPos(from), to: change.mapPos(to) })
|
||||
});
|
||||
|
||||
const mark = Decoration.mark({ class: 'cm-mark' });
|
||||
|
||||
const changes = StateField.define<DecorationSet>({
|
||||
create: () => Decoration.none,
|
||||
update: (_old, transaction) =>
|
||||
RangeSet.of(
|
||||
transaction.effects
|
||||
.filter((effect) => effect.is(addMark))
|
||||
.map((effect) => mark.range(effect.value.from, effect.value.to))
|
||||
),
|
||||
provide: (field) => EditorView.decorations.from(field)
|
||||
});
|
||||
|
||||
export const markChanges = (selection: EditorSelection | undefined) => {
|
||||
if (selection === undefined) return undefined;
|
||||
|
||||
const effects: StateEffect<{ from: number; to: number }>[] = selection.ranges
|
||||
.filter((r) => !r.empty)
|
||||
.map(({ from, to }) => addMark.of({ from, to }));
|
||||
|
||||
return effects.length > 0 ? [...effects, StateEffect.appendConfig.of([changes])] : undefined;
|
||||
};
|
@ -1,94 +0,0 @@
|
||||
import type { Theme } from './theme';
|
||||
|
||||
const palette = {
|
||||
dark0Hard: '#1d2021',
|
||||
dark0: '#282828',
|
||||
dark0Soft: '#32302f',
|
||||
dark1: '#3c3836',
|
||||
dark2: '#504945',
|
||||
dark3: '#665c54',
|
||||
dark4: '#7c6f64',
|
||||
light0Hard: '#f9f5d7',
|
||||
light0: '#fbf1c7',
|
||||
light0Soft: '#f2e5bc',
|
||||
light1: '#ebdbb2',
|
||||
light2: '#d5c4a1',
|
||||
light3: '#bdae93',
|
||||
light4: '#a89984',
|
||||
brightRed: '#fb4934',
|
||||
brightGreen: '#b8bb26',
|
||||
brightYellow: '#fabd2f',
|
||||
brightBlue: '#83a598',
|
||||
brightPurple: '#d3869b',
|
||||
brightAqua: '#8ec07c',
|
||||
brightOrange: '#fe8019',
|
||||
neutralRed: '#cc241d',
|
||||
neutralGreen: '#98971a',
|
||||
neutralYellow: '#d79921',
|
||||
neutralBlue: '#458588',
|
||||
neutralPurple: '#b16286',
|
||||
neutralAqua: '#689d6a',
|
||||
neutralOrange: '#d65d0e',
|
||||
fadedRed: '#9d0006',
|
||||
fadedGreen: '#79740e',
|
||||
fadedYellow: '#b57614',
|
||||
fadedBlue: '#076678',
|
||||
fadedPurple: '#8f3f71',
|
||||
fadedAqua: '#427b58',
|
||||
fadedOrange: '#af3a03',
|
||||
gray: '#928374'
|
||||
};
|
||||
|
||||
export const dark: Theme = {
|
||||
bg0: palette.dark0,
|
||||
bg1: palette.dark1,
|
||||
bg2: palette.dark2,
|
||||
bg3: palette.dark3,
|
||||
bg4: palette.dark4,
|
||||
fg0: palette.light0,
|
||||
fg1: palette.light1,
|
||||
fg2: palette.light2,
|
||||
fg3: palette.light3,
|
||||
fg4: palette.light4,
|
||||
red: palette.brightRed,
|
||||
green: palette.brightGreen,
|
||||
yellow: palette.brightYellow,
|
||||
blue: palette.brightBlue,
|
||||
purple: palette.brightPurple,
|
||||
aqua: palette.brightAqua,
|
||||
orange: palette.brightOrange,
|
||||
neutralRed: palette.neutralRed,
|
||||
neutralGreen: palette.neutralGreen,
|
||||
neutralYellow: palette.neutralYellow,
|
||||
neutralBlue: palette.neutralBlue,
|
||||
neutralPurple: palette.neutralPurple,
|
||||
neutralAqua: palette.neutralAqua,
|
||||
gray: palette.gray
|
||||
};
|
||||
|
||||
export const light: Theme = {
|
||||
bg0: palette.light0,
|
||||
bg1: palette.light1,
|
||||
bg2: palette.light2,
|
||||
bg3: palette.light3,
|
||||
bg4: palette.light4,
|
||||
fg0: palette.dark0,
|
||||
fg1: palette.dark1,
|
||||
fg2: palette.dark2,
|
||||
fg3: palette.dark3,
|
||||
fg4: palette.dark4,
|
||||
red: palette.fadedRed,
|
||||
green: palette.fadedGreen,
|
||||
yellow: palette.fadedYellow,
|
||||
blue: palette.fadedBlue,
|
||||
purple: palette.fadedPurple,
|
||||
aqua: palette.fadedAqua,
|
||||
orange: palette.fadedOrange,
|
||||
neutralRed: palette.neutralRed,
|
||||
neutralGreen: palette.neutralGreen,
|
||||
neutralYellow: palette.neutralYellow,
|
||||
neutralBlue: palette.neutralBlue,
|
||||
neutralPurple: palette.neutralPurple,
|
||||
neutralAqua: palette.neutralAqua,
|
||||
gray: palette.gray
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * as gruvbox from './gruvbox';
|
@ -1,28 +0,0 @@
|
||||
export type Color = string;
|
||||
|
||||
export type Theme = {
|
||||
bg0: Color;
|
||||
bg1: Color;
|
||||
bg2: Color;
|
||||
bg3: Color;
|
||||
bg4: Color;
|
||||
fg0: Color;
|
||||
fg1: Color;
|
||||
fg2: Color;
|
||||
fg3: Color;
|
||||
fg4: Color;
|
||||
red: Color;
|
||||
green: Color;
|
||||
yellow: Color;
|
||||
blue: Color;
|
||||
purple: Color;
|
||||
aqua: Color;
|
||||
orange: Color;
|
||||
neutralRed: Color;
|
||||
neutralGreen: Color;
|
||||
neutralYellow: Color;
|
||||
neutralBlue: Color;
|
||||
neutralPurple: Color;
|
||||
neutralAqua: Color;
|
||||
gray: Color;
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
export { default as BackForwardButtons } from './BackForwardButtons.svelte';
|
||||
export { default as Login } from './Login.svelte';
|
||||
export { default as Breadcrumbs } from './Breadcrumbs.svelte';
|
||||
export { default as CodeViewer } from './CodeViewer';
|
||||
export { default as CodeViewer } from './CodeViewer.svelte';
|
||||
|
@ -312,13 +312,13 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row h-full w-full bg-black">
|
||||
<div class="flex h-full w-full flex-row bg-black">
|
||||
<div class="w-24 flex-shrink-0 border-r border-zinc-700 p-2">
|
||||
<div class="text-lg font-bold font-zinc-100 mb-2">Daily Work</div>
|
||||
<div class="font-zinc-100 mb-2 text-lg font-bold">Daily Work</div>
|
||||
{#each Object.entries(sessionDays) as [day, sessions]}
|
||||
{#if day == currentDay}
|
||||
<div
|
||||
class="flex flex-col text-center text-white bg-zinc-800 p-2 rounded shadow mb-2 cursor-pointer"
|
||||
class="mb-2 flex cursor-pointer flex-col rounded bg-zinc-800 p-2 text-center text-white shadow"
|
||||
on:click={selectDay(day)}
|
||||
>
|
||||
<div class="">{ymdToDay(day)}</div>
|
||||
@ -326,7 +326,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="flex flex-col text-center bg-zinc-900 p-2 rounded shadow mb-2 cursor-pointer"
|
||||
class="mb-2 flex cursor-pointer flex-col rounded bg-zinc-900 p-2 text-center shadow"
|
||||
on:click={selectDay(day)}
|
||||
>
|
||||
<div class="">{ymdToDay(day)}</div>
|
||||
@ -336,17 +336,15 @@
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<div class="flex flex-col h-full w-full">
|
||||
<div class="flex-grow overflow-auto p-2">
|
||||
<div class="flex h-full w-full flex-col">
|
||||
<div class="flex-auto overflow-x-hidden overflow-y-scroll text-clip p-2">
|
||||
{#if dayPlaylist[currentDay] !== undefined}
|
||||
{#if currentEdit !== null}
|
||||
<div class="h-full overflow-auto">
|
||||
<CodeViewer
|
||||
doc={currentEdit.doc}
|
||||
deltas={currentEdit.ops}
|
||||
filepath={currentEdit.filepath}
|
||||
/>
|
||||
</div>
|
||||
<CodeViewer
|
||||
doc={currentEdit.doc}
|
||||
deltas={currentEdit.ops}
|
||||
filepath={currentEdit.filepath}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
loading...
|
||||
@ -367,12 +365,12 @@
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col p-2 bg-zinc-800 p-2">
|
||||
<div class="flex flex-col bg-zinc-800 p-2 p-2">
|
||||
{#if dayPlaylist[currentDay] !== undefined}
|
||||
<div class="w-full h-0 justify-between">
|
||||
<div class="h-0 w-full justify-between">
|
||||
{#each dayPlaylist[currentDay].chapters as chapter}
|
||||
<div
|
||||
class="inline-block bg-white rounded h-2"
|
||||
class="inline-block h-2 rounded bg-white"
|
||||
style="width: {Math.round(
|
||||
(chapter.editCount / dayPlaylist[currentDay].editCount) * 100
|
||||
)}%"
|
||||
@ -386,7 +384,7 @@
|
||||
{#if dayPlaylist[currentDay] !== undefined}
|
||||
<input
|
||||
type="range"
|
||||
class="w-full -mt-3 cursor-pointer appearance-none rounded-lg border-transparent bg-transparent"
|
||||
class="-mt-3 w-full cursor-pointer appearance-none rounded-lg border-transparent bg-transparent"
|
||||
max={dayPlaylist[currentDay].editCount}
|
||||
step="1"
|
||||
bind:value={currentPlayerValue}
|
||||
@ -405,7 +403,7 @@
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-6 h-6 icon-pointer"
|
||||
class="icon-pointer h-6 w-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@ -425,7 +423,7 @@
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-6 h-6 icon-pointer"
|
||||
class="icon-pointer h-6 w-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@ -444,9 +442,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-80 flex-shrink-0 bg-black overflow-auto border-l border-zinc-700 p-2">
|
||||
<div class="w-80 flex-shrink-0 overflow-auto border-l border-zinc-700 bg-black p-2">
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="text-lg font-bold font-zinc-100 mb-2">Sessions</div>
|
||||
<div class="font-zinc-100 mb-2 text-lg font-bold">Sessions</div>
|
||||
<div>{Object.entries(sessionDays[currentDay]).length}</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
@ -454,7 +452,7 @@
|
||||
{#if currentEdit !== null && currentEdit.sessionId == session.id}
|
||||
<div
|
||||
id="currentSession"
|
||||
class="bg-zinc-700 text-white rounded shadow mb-2 overflow-auto border-zinc-800"
|
||||
class="mb-2 overflow-auto rounded border-zinc-800 bg-zinc-700 text-white shadow"
|
||||
>
|
||||
<div class="flex flex-row justify-between px-2 pt-2">
|
||||
<div class="font-bold">{dateRange(session.meta)}</div>
|
||||
@ -482,7 +480,7 @@
|
||||
on:click={() => {
|
||||
currentPlayerValue = max(dayPlaylist[currentDay].editOffsets[session.id], 1);
|
||||
}}
|
||||
class="bg-zinc-800 rounded shadow mb-2 overflow-auto border-zinc-800 pointer-cursor"
|
||||
class="pointer-cursor mb-2 overflow-auto rounded border-zinc-800 bg-zinc-800 shadow"
|
||||
>
|
||||
<div class="flex flex-row justify-between px-2 pt-2">
|
||||
<div>{dateRange(session.meta)}</div>
|
||||
|
Loading…
Reference in New Issue
Block a user