@lincyaw/workbench 0.1.0 → 0.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -8
- package/package.json +27 -22
- package/src/MediaClipEditor.tsx +122 -0
- package/src/StepProductionEditor.tsx +130 -0
- package/src/approvals.tsx +60 -0
- package/src/assistant-mascot.ts +62 -0
- package/src/audio-recording-messages.ts +44 -0
- package/src/audio-recording.ts +192 -0
- package/src/audio-wav.ts +25 -0
- package/src/audio-waveform.ts +110 -0
- package/src/audition.ts +84 -0
- package/src/authoring-client.ts +136 -0
- package/src/authoring-draft-fragment.ts +28 -0
- package/src/base-controls.tsx +47 -0
- package/src/caption-timing-drafts.ts +61 -0
- package/src/caption-timing-messages.ts +14 -0
- package/src/caption-timing.css +20 -0
- package/src/caption-timing.ts +14 -0
- package/src/component-draft-actions.ts +54 -0
- package/src/component-draft-inspection.ts +35 -0
- package/src/component-properties.css +24 -0
- package/src/component-properties.ts +45 -0
- package/src/component-property-drafts.ts +273 -0
- package/src/component-property-messages.ts +31 -0
- package/src/components.tsx +547 -90
- package/src/context.tsx +27 -0
- package/src/diagnostics.ts +113 -0
- package/src/document-draft-store.ts +179 -0
- package/src/document-drafts.ts +178 -0
- package/src/document-editor/BlockNoteSurface.tsx +209 -0
- package/src/document-editor/ComponentMaterials.tsx +62 -0
- package/src/document-editor/ComponentPropertiesDialog.tsx +282 -0
- package/src/document-editor/DocumentBlockFallback.tsx +31 -0
- package/src/document-editor/DocumentBlockPreview.tsx +239 -0
- package/src/document-editor/DocumentComments.tsx +282 -0
- package/src/document-editor/DocumentEditor.tsx +192 -0
- package/src/document-editor/DocumentStepNarration.tsx +93 -0
- package/src/document-editor/NarrationEditor.tsx +110 -0
- package/src/document-editor/NewCommentDrafts.tsx +224 -0
- package/src/document-editor/authoring.tsx +259 -0
- package/src/document-editor/block-playback.ts +90 -0
- package/src/document-editor/block-preview-messages.ts +13 -0
- package/src/document-editor/blocknote-messages.ts +98 -0
- package/src/document-editor/clipboard.ts +40 -0
- package/src/document-editor/colors.ts +57 -0
- package/src/document-editor/comment-draft-actions.ts +58 -0
- package/src/document-editor/comment-draft-inspection.ts +38 -0
- package/src/document-editor/comment-drafts.ts +361 -0
- package/src/document-editor/comments.ts +410 -0
- package/src/document-editor/file-input.ts +71 -0
- package/src/document-editor/focus.ts +54 -0
- package/src/document-editor/inline.ts +104 -0
- package/src/document-editor/math.tsx +63 -0
- package/src/document-editor/messages.ts +14 -0
- package/src/document-editor/narration-schema.tsx +85 -0
- package/src/document-editor/narration.ts +37 -0
- package/src/document-editor/popover.tsx +107 -0
- package/src/document-editor/projection.ts +423 -0
- package/src/document-editor/rich-text.ts +152 -0
- package/src/document-editor/schema.tsx +117 -0
- package/src/document-editor/selection.ts +152 -0
- package/src/document-editor/style.css +9 -0
- package/src/document-editor/styles.d.ts +1 -0
- package/src/document-editor/table-identities.ts +67 -0
- package/src/document-preview.ts +112 -0
- package/src/document-return-point.ts +59 -0
- package/src/document-session.ts +678 -0
- package/src/document-timing-drafts.ts +61 -0
- package/src/document-voices.ts +2 -0
- package/src/history.ts +167 -0
- package/src/index.ts +33 -43
- package/src/jobs.ts +111 -0
- package/src/mascot-preference.ts +37 -0
- package/src/media-drafts.ts +83 -0
- package/src/media-messages.ts +53 -0
- package/src/media-time.ts +12 -0
- package/src/messages.ts +751 -203
- package/src/outline.ts +97 -0
- package/src/panels/ChangeTrail.tsx +79 -0
- package/src/panels/DocumentNarrationPanel.tsx +27 -0
- package/src/panels/DocumentView.tsx +456 -44
- package/src/panels/DocumentVoiceDialog.tsx +179 -0
- package/src/panels/DocumentVoiceField.tsx +35 -0
- package/src/panels/ExportPanel.tsx +133 -0
- package/src/panels/HistoryPanel.tsx +88 -0
- package/src/panels/HomePanel.tsx +184 -0
- package/src/panels/PageDeleteDialog.tsx +33 -0
- package/src/panels/PageRenameDialog.tsx +27 -0
- package/src/panels/PreviewPanel.tsx +79 -0
- package/src/panels/ProductionCaptionTiming.tsx +122 -0
- package/src/panels/ProductionMediaClips.tsx +156 -0
- package/src/panels/ProductionPanel.tsx +272 -0
- package/src/panels/ProductionRecording.tsx +107 -0
- package/src/panels/ProductionRecordingRange.tsx +118 -0
- package/src/panels/ProductionTimeline.tsx +62 -0
- package/src/panels/ProductionTranscription.tsx +140 -0
- package/src/panels/SourceDocumentPanel.tsx +215 -0
- package/src/panels/SpeechSettingsPanel.tsx +64 -0
- package/src/panels/TranscriptionAdoption.tsx +147 -0
- package/src/panels/VoiceCloneDialog.tsx +152 -0
- package/src/panels/VoicePreviewDialog.tsx +74 -0
- package/src/panels/VoiceScopeField.tsx +30 -0
- package/src/panels/VoiceoverDialog.tsx +216 -0
- package/src/places.ts +74 -6
- package/src/preview-playback.ts +125 -0
- package/src/preview-pool.ts +320 -0
- package/src/preview.ts +57 -6
- package/src/production-messages.ts +151 -0
- package/src/production-playback.ts +109 -0
- package/src/production-voice-playback.ts +114 -0
- package/src/recording-adoption.ts +33 -0
- package/src/schema-form/PmSchemaForm.tsx +187 -0
- package/src/schema-form/context.ts +26 -0
- package/src/schema-form/fields.tsx +59 -0
- package/src/schema-form/templates.tsx +50 -0
- package/src/schema-form/validator.ts +36 -0
- package/src/schema-form/widgets.tsx +60 -0
- package/src/shortcuts.ts +8 -0
- package/src/sortable-list.tsx +201 -0
- package/src/speech-settings-messages.ts +40 -0
- package/src/step-production.css +5 -0
- package/src/step-production.ts +51 -0
- package/src/styles.css +757 -361
- package/src/telemetry.ts +152 -0
- package/src/transcription-messages.ts +26 -0
- package/src/transcription-session.ts +159 -0
- package/src/use-recording-adoption.ts +84 -0
- package/src/voice-clone-messages.ts +92 -0
- package/src/voice-drafts.ts +89 -0
- package/src/voice-messages.ts +146 -0
- package/src/voice-snapshot.ts +55 -0
- package/src/voiceover-jobs.ts +87 -0
- package/src/workbench.ts +2328 -0
- package/src/workspace-client.ts +480 -0
- package/src/api.ts +0 -126
- package/src/editor.ts +0 -223
- package/src/merge.ts +0 -30
- package/src/panels/Changes.tsx +0 -128
- package/src/panels/Files.tsx +0 -73
- package/src/panels/Home.tsx +0 -224
- package/src/panels/Preview.tsx +0 -68
- package/src/panels/Problems.tsx +0 -42
- package/src/panels/Source.tsx +0 -118
- package/src/panels/Status.tsx +0 -26
- package/src/react.tsx +0 -59
- package/src/service.ts +0 -82
- package/src/store.ts +0 -599
package/src/editor.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
|
|
2
|
-
import { markdown } from '@codemirror/lang-markdown'
|
|
3
|
-
import { yaml, yamlFrontmatter } from '@codemirror/lang-yaml'
|
|
4
|
-
import { bracketMatching, HighlightStyle, indentOnInput, syntaxHighlighting } from '@codemirror/language'
|
|
5
|
-
import { lintGutter, setDiagnostics } from '@codemirror/lint'
|
|
6
|
-
import type { Diagnostic } from '@codemirror/lint'
|
|
7
|
-
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
|
|
8
|
-
import { EditorSelection, EditorState, StateEffect, StateField } from '@codemirror/state'
|
|
9
|
-
import type { Extension } from '@codemirror/state'
|
|
10
|
-
import { Decoration, drawSelection, EditorView, highlightActiveLine, highlightActiveLineGutter, keymap, lineNumbers, MatchDecorator, ViewPlugin } from '@codemirror/view'
|
|
11
|
-
import type { DecorationSet, ViewUpdate } from '@codemirror/view'
|
|
12
|
-
import { tags } from '@lezer/highlight'
|
|
13
|
-
|
|
14
|
-
export interface EditorOptions {
|
|
15
|
-
label: string
|
|
16
|
-
save(): void
|
|
17
|
-
changed(path: string, text: string): void
|
|
18
|
-
moved(line: number): void
|
|
19
|
-
selected?(selection: { startLine: number, endLine: number, text: string }): void
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface Problem {
|
|
23
|
-
line: number
|
|
24
|
-
severity: 'error' | 'warning' | 'info'
|
|
25
|
-
message: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface SourceEditor {
|
|
29
|
-
show(path: string, text: string): void
|
|
30
|
-
text(path: string): string | undefined
|
|
31
|
-
replace(path: string, text: string, fresh: boolean): void
|
|
32
|
-
reveal(line: number, endLine?: number, mark?: boolean): void
|
|
33
|
-
problems(path: string, problems: Problem[]): void
|
|
34
|
-
paths(): string[]
|
|
35
|
-
forget(path: string): void
|
|
36
|
-
focus(): void
|
|
37
|
-
destroy(): void
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const MYST = new MatchDecorator({
|
|
41
|
-
regexp: /^(?:`{3,}|:{3,})\{[\w.:-]+\}.*$|^:[\w-]+:(?=\s|$)|^\([^)\s]+\)=\s*$|\{[\w.:-]+\}`[^`]*`/g,
|
|
42
|
-
decoration: match => Decoration.mark({ class: match[0].startsWith('(') ? 'pm-cm-myst-label' : 'pm-cm-myst' }),
|
|
43
|
-
})
|
|
44
|
-
const mystMarks = ViewPlugin.fromClass(class {
|
|
45
|
-
decorations: DecorationSet
|
|
46
|
-
constructor(view: EditorView) {
|
|
47
|
-
this.decorations = MYST.createDeco(view)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
update(update: ViewUpdate) {
|
|
51
|
-
this.decorations = MYST.updateDeco(update, this.decorations)
|
|
52
|
-
}
|
|
53
|
-
}, { decorations: plugin => plugin.decorations })
|
|
54
|
-
|
|
55
|
-
const HIGHLIGHT = HighlightStyle.define([
|
|
56
|
-
{ tag: tags.heading, color: 'var(--pm-color-accent)', fontWeight: '600' },
|
|
57
|
-
{ tag: [tags.processingInstruction, tags.meta, tags.contentSeparator, tags.comment, tags.punctuation], color: 'var(--pm-color-muted)' },
|
|
58
|
-
{ tag: tags.emphasis, fontStyle: 'italic' },
|
|
59
|
-
{ tag: tags.strong, fontWeight: '600' },
|
|
60
|
-
{ tag: [tags.link, tags.url], color: 'var(--pm-color-accent)' },
|
|
61
|
-
{ tag: tags.monospace, color: 'var(--pm-color-syntax-string)' },
|
|
62
|
-
{ tag: [tags.propertyName, tags.definition(tags.propertyName), tags.attributeName, tags.labelName], color: 'var(--pm-color-syntax-key)' },
|
|
63
|
-
{ tag: [tags.string, tags.special(tags.string)], color: 'var(--pm-color-syntax-string)' },
|
|
64
|
-
{ tag: [tags.keyword, tags.bool, tags.null, tags.number, tags.atom], color: 'var(--pm-color-syntax-keyword)' },
|
|
65
|
-
])
|
|
66
|
-
|
|
67
|
-
const THEME = EditorView.theme({
|
|
68
|
-
'&': { height: '100%', backgroundColor: 'var(--pm-color-code)', color: 'var(--pm-color-ink)', fontSize: '0.84375rem' },
|
|
69
|
-
'&.cm-focused': { outline: 'none' },
|
|
70
|
-
'.cm-scroller': { fontFamily: 'var(--pm-font-mono)', lineHeight: '1.75' },
|
|
71
|
-
'.cm-content': { caretColor: 'var(--pm-color-ink)', padding: '14px 0' },
|
|
72
|
-
'.cm-line': { padding: '0 14px 0 6px' },
|
|
73
|
-
'.cm-gutters': { backgroundColor: 'var(--pm-color-code)', color: 'var(--pm-color-muted)', border: 'none' },
|
|
74
|
-
'.cm-lineNumbers .cm-gutterElement': { padding: '0 4px 0 12px', minWidth: '2.5em' },
|
|
75
|
-
'.cm-activeLineGutter': { backgroundColor: 'transparent', color: 'var(--pm-color-ink)' },
|
|
76
|
-
'.cm-activeLine': { backgroundColor: 'color-mix(in srgb, var(--pm-color-selected) 55%, transparent)' },
|
|
77
|
-
'&.cm-focused .cm-cursor': { borderLeftColor: 'var(--pm-color-ink)' },
|
|
78
|
-
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': { backgroundColor: 'var(--pm-color-accent-soft)' },
|
|
79
|
-
'.cm-line.pm-cm-picked': { backgroundColor: 'var(--pm-color-selected)', boxShadow: 'inset 2px 0 0 var(--pm-color-accent)' },
|
|
80
|
-
'.pm-cm-myst': { color: 'var(--pm-color-syntax-key)' },
|
|
81
|
-
'.pm-cm-myst-label': { color: 'var(--pm-color-muted)' },
|
|
82
|
-
'.cm-lintRange-error, .cm-lintRange-warning, .cm-lintRange-info': { backgroundImage: 'none', textDecoration: 'underline wavy', textUnderlineOffset: '3px' },
|
|
83
|
-
'.cm-lintRange-error': { textDecorationColor: 'var(--pm-color-danger)' },
|
|
84
|
-
'.cm-lintRange-warning': { textDecorationColor: 'var(--pm-color-warn)' },
|
|
85
|
-
'.cm-lintRange-info': { textDecorationColor: 'var(--pm-color-accent)' },
|
|
86
|
-
'.cm-tooltip': { border: '1px solid var(--pm-color-line)', backgroundColor: 'var(--pm-color-surface)', color: 'var(--pm-color-ink)', borderRadius: 'var(--pm-radius-small)' },
|
|
87
|
-
'.cm-panels': { backgroundColor: 'var(--pm-color-sunk)', color: 'var(--pm-color-ink)' },
|
|
88
|
-
'.cm-searchMatch': { backgroundColor: 'var(--pm-color-warn-soft)' },
|
|
89
|
-
'.cm-matchingBracket, &.cm-focused .cm-matchingBracket': { backgroundColor: 'var(--pm-color-accent-soft)' },
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
const pick = StateEffect.define<{ from: number, to: number } | null>()
|
|
93
|
-
const picked = StateField.define<DecorationSet>({
|
|
94
|
-
create: () => Decoration.none,
|
|
95
|
-
update(marks, transaction) {
|
|
96
|
-
marks = marks.map(transaction.changes)
|
|
97
|
-
for (const effect of transaction.effects) {
|
|
98
|
-
if (!effect.is(pick)) continue
|
|
99
|
-
if (!effect.value) return Decoration.none
|
|
100
|
-
const { doc } = transaction.state
|
|
101
|
-
const lines = []
|
|
102
|
-
for (let n = effect.value.from; n <= effect.value.to && n <= doc.lines; n++) lines.push(Decoration.line({ class: 'pm-cm-picked' }).range(doc.line(n).from))
|
|
103
|
-
return Decoration.set(lines)
|
|
104
|
-
}
|
|
105
|
-
return marks
|
|
106
|
-
},
|
|
107
|
-
provide: field => EditorView.decorations.from(field),
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
function language(path: string): Extension {
|
|
111
|
-
if (/\.(?:md|markdown)$/i.test(path)) return [yamlFrontmatter({ content: markdown() }), mystMarks]
|
|
112
|
-
if (/\.ya?ml$/i.test(path)) return yaml()
|
|
113
|
-
return []
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function createEditor(parent: HTMLElement, options: EditorOptions): SourceEditor {
|
|
117
|
-
const states = new Map<string, EditorState>()
|
|
118
|
-
const problems = new Map<string, Problem[]>()
|
|
119
|
-
let current: string | undefined
|
|
120
|
-
const listener = EditorView.updateListener.of((update) => {
|
|
121
|
-
if (!current) return
|
|
122
|
-
if (update.docChanged) options.changed(current, update.state.doc.toString())
|
|
123
|
-
if (update.docChanged || update.selectionSet) options.moved(update.state.doc.lineAt(update.state.selection.main.head).number)
|
|
124
|
-
const selection = update.state.selection.main
|
|
125
|
-
if (update.view.hasFocus && update.selectionSet && !selection.empty) {
|
|
126
|
-
options.selected?.({
|
|
127
|
-
startLine: update.state.doc.lineAt(selection.from).number,
|
|
128
|
-
endLine: update.state.doc.lineAt(Math.max(selection.from, selection.to - 1)).number,
|
|
129
|
-
text: update.state.sliceDoc(selection.from, selection.to),
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
const extensions = (path: string): Extension => [
|
|
134
|
-
lineNumbers(),
|
|
135
|
-
highlightActiveLineGutter(),
|
|
136
|
-
highlightActiveLine(),
|
|
137
|
-
history(),
|
|
138
|
-
drawSelection(),
|
|
139
|
-
indentOnInput(),
|
|
140
|
-
bracketMatching(),
|
|
141
|
-
highlightSelectionMatches(),
|
|
142
|
-
lintGutter(),
|
|
143
|
-
picked,
|
|
144
|
-
EditorView.lineWrapping,
|
|
145
|
-
syntaxHighlighting(HIGHLIGHT),
|
|
146
|
-
THEME,
|
|
147
|
-
language(path),
|
|
148
|
-
keymap.of([
|
|
149
|
-
{ key: 'Mod-s', preventDefault: true, run: () => {
|
|
150
|
-
options.save()
|
|
151
|
-
return true
|
|
152
|
-
} },
|
|
153
|
-
...defaultKeymap,
|
|
154
|
-
...historyKeymap,
|
|
155
|
-
...searchKeymap,
|
|
156
|
-
]),
|
|
157
|
-
EditorView.contentAttributes.of({ 'aria-label': options.label }),
|
|
158
|
-
listener,
|
|
159
|
-
]
|
|
160
|
-
const view = new EditorView({ parent, state: EditorState.create({ doc: '', extensions: [THEME, EditorState.readOnly.of(true)] }) })
|
|
161
|
-
|
|
162
|
-
const diagnose = () => {
|
|
163
|
-
if (!current) return
|
|
164
|
-
const { doc } = view.state
|
|
165
|
-
const diagnostics: Diagnostic[] = (problems.get(current) ?? []).filter(problem => problem.line >= 1 && problem.line <= doc.lines).map((problem) => {
|
|
166
|
-
const line = doc.line(problem.line)
|
|
167
|
-
return { from: line.from, to: line.to, severity: problem.severity, message: problem.message }
|
|
168
|
-
})
|
|
169
|
-
view.dispatch(setDiagnostics(view.state, diagnostics))
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
show(path, text) {
|
|
174
|
-
if (current === path) return
|
|
175
|
-
if (current) states.set(current, view.state)
|
|
176
|
-
current = path
|
|
177
|
-
view.setState(states.get(path) ?? EditorState.create({ doc: text, extensions: extensions(path) }))
|
|
178
|
-
diagnose()
|
|
179
|
-
options.moved(view.state.doc.lineAt(view.state.selection.main.head).number)
|
|
180
|
-
},
|
|
181
|
-
text(path) {
|
|
182
|
-
return (path === current ? view.state : states.get(path))?.doc.toString()
|
|
183
|
-
},
|
|
184
|
-
replace(path, text, fresh) {
|
|
185
|
-
if (path !== current) {
|
|
186
|
-
states.set(path, EditorState.create({ doc: text, extensions: extensions(path) }))
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
|
-
if (fresh) {
|
|
190
|
-
const head = Math.min(view.state.selection.main.head, text.length)
|
|
191
|
-
view.setState(EditorState.create({ doc: text, selection: EditorSelection.cursor(head), extensions: extensions(path) }))
|
|
192
|
-
diagnose()
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: text } })
|
|
196
|
-
},
|
|
197
|
-
reveal(line, endLine, mark) {
|
|
198
|
-
const { doc } = view.state
|
|
199
|
-
const first = Math.min(Math.max(line, 1), doc.lines)
|
|
200
|
-
const last = Math.min(Math.max(endLine ?? first, first), doc.lines)
|
|
201
|
-
const position = doc.line(first).from
|
|
202
|
-
view.dispatch({
|
|
203
|
-
selection: { anchor: position },
|
|
204
|
-
effects: [EditorView.scrollIntoView(position, { y: 'center' }), pick.of(mark ? { from: first, to: last } : null)],
|
|
205
|
-
})
|
|
206
|
-
},
|
|
207
|
-
problems(path, list) {
|
|
208
|
-
problems.set(path, list)
|
|
209
|
-
if (path === current) diagnose()
|
|
210
|
-
},
|
|
211
|
-
paths: () => [...new Set([...states.keys(), ...current ? [current] : []])],
|
|
212
|
-
forget(path) {
|
|
213
|
-
states.delete(path)
|
|
214
|
-
problems.delete(path)
|
|
215
|
-
if (path === current) {
|
|
216
|
-
current = undefined
|
|
217
|
-
view.setState(EditorState.create({ doc: '', extensions: [THEME, EditorState.readOnly.of(true)] }))
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
focus: () => view.focus(),
|
|
221
|
-
destroy: () => view.destroy(),
|
|
222
|
-
}
|
|
223
|
-
}
|
package/src/merge.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { diff3Merge } from 'node-diff3'
|
|
2
|
-
|
|
3
|
-
export interface Merged {
|
|
4
|
-
text: string
|
|
5
|
-
conflicts: number
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface MergeLabels {
|
|
9
|
-
mine: string
|
|
10
|
-
theirs: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function mergeTexts(mine: string, base: string, theirs: string, labels: MergeLabels): Merged {
|
|
14
|
-
const lines: string[] = []
|
|
15
|
-
let conflicts = 0
|
|
16
|
-
for (const region of diff3Merge(mine.split('\n'), base.split('\n'), theirs.split('\n'), { excludeFalseConflicts: true })) {
|
|
17
|
-
if (region.ok) {
|
|
18
|
-
lines.push(...region.ok)
|
|
19
|
-
continue
|
|
20
|
-
}
|
|
21
|
-
const { a, o, b } = region.conflict!
|
|
22
|
-
if (o.length === 0) {
|
|
23
|
-
lines.push(...b, ...a)
|
|
24
|
-
continue
|
|
25
|
-
}
|
|
26
|
-
conflicts++
|
|
27
|
-
lines.push(`<<<<<<< ${labels.mine}`, ...a, '=======', ...b, `>>>>>>> ${labels.theirs}`)
|
|
28
|
-
}
|
|
29
|
-
return { text: lines.join('\n'), conflicts }
|
|
30
|
-
}
|
package/src/panels/Changes.tsx
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import type { FormEvent } from 'react'
|
|
3
|
-
import { PmBadge, PmButton, PmDialog, PmIconButton, PmNotice } from '../components.tsx'
|
|
4
|
-
import { useMessages, useWorkbench, useWorkbenchStore } from '../react.tsx'
|
|
5
|
-
|
|
6
|
-
function Counts({ added, removed }: { added?: number, removed?: number }) {
|
|
7
|
-
return (
|
|
8
|
-
<span className="pmw-changes__counts">
|
|
9
|
-
{added !== undefined && <span data-sign="added">+{added}</span>}
|
|
10
|
-
{removed !== undefined && <span data-sign="removed">−{removed}</span>}
|
|
11
|
-
</span>
|
|
12
|
-
)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function ChangesPanel() {
|
|
16
|
-
const t = useMessages()
|
|
17
|
-
const workbench = useWorkbenchStore()
|
|
18
|
-
const locale = useWorkbench(state => state.locale)
|
|
19
|
-
const review = useWorkbench(state => state.review)
|
|
20
|
-
const changed = useWorkbench(state => state.changed)
|
|
21
|
-
const history = useWorkbench(state => state.history)
|
|
22
|
-
const failure = useWorkbench(state => state.changesFailure)
|
|
23
|
-
const dir = useWorkbench(state => state.document)
|
|
24
|
-
const short = (path: string) => (dir && path.startsWith(`${dir}/`) ? path.slice(dir.length + 1) : path)
|
|
25
|
-
const [reviewMessage, setReviewMessage] = useState('')
|
|
26
|
-
const [commitMessage, setCommitMessage] = useState('')
|
|
27
|
-
const [discarding, setDiscarding] = useState(false)
|
|
28
|
-
const pending = new Set(review?.changes.map(change => change.path))
|
|
29
|
-
const own = changed?.filter(file => !pending.has(file.path))
|
|
30
|
-
const submit = (work: () => Promise<boolean>, done: () => void) => async (event: FormEvent) => {
|
|
31
|
-
event.preventDefault()
|
|
32
|
-
if (await work()) done()
|
|
33
|
-
}
|
|
34
|
-
return (
|
|
35
|
-
<section className="pmw-changes" aria-label={t.changes}>
|
|
36
|
-
{failure && <PmNotice tone="danger">{t.changesFailed(failure)}</PmNotice>}
|
|
37
|
-
{review && (
|
|
38
|
-
<section className="pmw-changes__group" aria-label={t.review} data-group="review">
|
|
39
|
-
<h2 className="pmw-changes__head">{t.review}<PmBadge tone="accent">{review.changes.length}</PmBadge></h2>
|
|
40
|
-
<p className="pmw-muted">{review.running ? t.reviewRunning : t.reviewWaiting(review.changes.length)}</p>
|
|
41
|
-
<ul className="pmw-changes__list">
|
|
42
|
-
{review.changes.map(change => (
|
|
43
|
-
<li key={change.path} className="pmw-changes__file">
|
|
44
|
-
<details>
|
|
45
|
-
<summary><span className="pmw-changes__path" data-kind={change.kind} title={change.path}>{short(change.path)}</span><Counts added={change.added} removed={change.removed} /></summary>
|
|
46
|
-
{change.diff && <pre className="pmw-changes__diff">{change.diff}</pre>}
|
|
47
|
-
</details>
|
|
48
|
-
<PmIconButton icon="undo" small label={t.rejectFile(short(change.path))} disabled={review.running} onClick={() => void workbench.rejectReview([change.path])} />
|
|
49
|
-
</li>
|
|
50
|
-
))}
|
|
51
|
-
</ul>
|
|
52
|
-
{review.steps.length > 1 && (
|
|
53
|
-
<details className="pmw-changes__steps">
|
|
54
|
-
<summary>{t.checkpoints}</summary>
|
|
55
|
-
<ol>
|
|
56
|
-
{review.steps.map(step => (
|
|
57
|
-
<li key={step.id}>
|
|
58
|
-
<span>{step.label}</span>
|
|
59
|
-
<PmButton small disabled={review.running} onClick={() => void workbench.restoreReview(step.id)}>{t.restore}</PmButton>
|
|
60
|
-
</li>
|
|
61
|
-
))}
|
|
62
|
-
</ol>
|
|
63
|
-
</details>
|
|
64
|
-
)}
|
|
65
|
-
<form className="pmw-changes__form" onSubmit={submit(() => workbench.acceptReview(reviewMessage.trim() || undefined), () => setReviewMessage(''))}>
|
|
66
|
-
<input className="pm-ui-input" aria-label={t.optionalMessage} placeholder={t.optionalMessage} value={reviewMessage} onChange={event => setReviewMessage(event.target.value)} />
|
|
67
|
-
<PmButton type="submit" variant="primary" icon="check" disabled={review.running}>{t.accept}</PmButton>
|
|
68
|
-
<PmButton variant="danger" disabled={review.running} onClick={() => void workbench.rejectReview()}>{t.rejectAll}</PmButton>
|
|
69
|
-
</form>
|
|
70
|
-
</section>
|
|
71
|
-
)}
|
|
72
|
-
<section className="pmw-changes__group" aria-label={t.uncommittedFiles} data-group="uncommitted">
|
|
73
|
-
<h2 className="pmw-changes__head">{t.uncommittedFiles}{own?.length ? <PmBadge>{own.length}</PmBadge> : null}</h2>
|
|
74
|
-
{own?.length === 0 && <p className="pmw-muted">{t.noUncommitted}</p>}
|
|
75
|
-
{own && own.length > 0 && (
|
|
76
|
-
<>
|
|
77
|
-
<ul className="pmw-changes__list">
|
|
78
|
-
{own.map(file => (
|
|
79
|
-
<li key={file.path} className="pmw-changes__file">
|
|
80
|
-
<span className="pmw-changes__path" data-kind={file.kind} title={file.path}>{short(file.path)}</span>
|
|
81
|
-
<Counts added={file.added} removed={file.removed} />
|
|
82
|
-
</li>
|
|
83
|
-
))}
|
|
84
|
-
</ul>
|
|
85
|
-
<form className="pmw-changes__form" onSubmit={submit(() => workbench.commitChanges(commitMessage.trim()), () => setCommitMessage(''))}>
|
|
86
|
-
<input className="pm-ui-input" required aria-label={t.commitMessage} placeholder={t.commitMessage} value={commitMessage} onChange={event => setCommitMessage(event.target.value)} />
|
|
87
|
-
<PmButton type="submit" variant="primary" disabled={Boolean(review)}>{t.commit}</PmButton>
|
|
88
|
-
<PmButton disabled={Boolean(review)} onClick={() => setDiscarding(true)}>{t.discard}</PmButton>
|
|
89
|
-
</form>
|
|
90
|
-
</>
|
|
91
|
-
)}
|
|
92
|
-
</section>
|
|
93
|
-
<section className="pmw-changes__group" aria-label={t.history} data-group="history">
|
|
94
|
-
<h2 className="pmw-changes__head">{t.history}</h2>
|
|
95
|
-
{history?.length === 0 && <p className="pmw-muted">{t.noHistory}</p>}
|
|
96
|
-
<ol className="pmw-changes__list">
|
|
97
|
-
{history?.map(revision => (
|
|
98
|
-
<li key={revision.id} className="pmw-changes__revision" data-revision={revision.id}>
|
|
99
|
-
<span className="pmw-changes__subject">{revision.message.split('\n')[0]}</span>
|
|
100
|
-
<span className="pmw-muted pmw-changes__meta">
|
|
101
|
-
<PmBadge>{t.origins[revision.origin.kind] ?? revision.origin.kind}</PmBadge>
|
|
102
|
-
{revision.author.replace(/\s*<[^>]*>$/, '')} · {new Date(revision.date).toLocaleString(locale, { dateStyle: 'short', timeStyle: 'short' })}
|
|
103
|
-
</span>
|
|
104
|
-
{revision.revertedBy
|
|
105
|
-
? <span className="pmw-muted">{t.reverted}</span>
|
|
106
|
-
: <PmButton small icon="undo" onClick={() => void workbench.revert(revision.id)}>{t.revert}</PmButton>}
|
|
107
|
-
</li>
|
|
108
|
-
))}
|
|
109
|
-
</ol>
|
|
110
|
-
</section>
|
|
111
|
-
<PmDialog open={discarding} label={t.discard} closeLabel={t.close} onClose={() => setDiscarding(false)}>
|
|
112
|
-
<p>{t.confirmDiscard}</p>
|
|
113
|
-
<div className="pmw-operation__actions">
|
|
114
|
-
<PmButton onClick={() => setDiscarding(false)}>{t.cancel}</PmButton>
|
|
115
|
-
<PmButton
|
|
116
|
-
variant="danger"
|
|
117
|
-
onClick={() => {
|
|
118
|
-
setDiscarding(false)
|
|
119
|
-
void workbench.discardChanges()
|
|
120
|
-
}}
|
|
121
|
-
>
|
|
122
|
-
{t.discard}
|
|
123
|
-
</PmButton>
|
|
124
|
-
</div>
|
|
125
|
-
</PmDialog>
|
|
126
|
-
</section>
|
|
127
|
-
)
|
|
128
|
-
}
|
package/src/panels/Files.tsx
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { Tree } from 'react-arborist'
|
|
2
|
-
import type { NodeRendererProps } from 'react-arborist'
|
|
3
|
-
import { useMemo } from 'react'
|
|
4
|
-
import type { SourceFile } from '../api.ts'
|
|
5
|
-
import { PmIcon, PmIconButton, PmNotice } from '../components.tsx'
|
|
6
|
-
import { useMessages, useWorkbench, useWorkbenchStore } from '../react.tsx'
|
|
7
|
-
|
|
8
|
-
interface FileNode {
|
|
9
|
-
id: string
|
|
10
|
-
name: string
|
|
11
|
-
file?: SourceFile
|
|
12
|
-
children?: FileNode[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function fileTree(files: SourceFile[], document: string): FileNode[] {
|
|
16
|
-
const root: FileNode = { id: '', name: '', children: [] }
|
|
17
|
-
for (const file of files) {
|
|
18
|
-
const parts = (document && file.path.startsWith(`${document}/`) ? file.path.slice(document.length + 1) : file.path).split('/')
|
|
19
|
-
let parent = root
|
|
20
|
-
for (const [i, name] of parts.entries()) {
|
|
21
|
-
const leaf = i === parts.length - 1
|
|
22
|
-
const id = leaf ? file.path : `directory:${parts.slice(0, i + 1).join('/')}`
|
|
23
|
-
let child = parent.children!.find(node => node.id === id)
|
|
24
|
-
if (!child) {
|
|
25
|
-
child = { id, name, ...leaf ? { file } : { children: [] } }
|
|
26
|
-
parent.children!.push(child)
|
|
27
|
-
}
|
|
28
|
-
parent = child
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return root.children!
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function FileRow({ node, style }: NodeRendererProps<FileNode>) {
|
|
35
|
-
return (
|
|
36
|
-
<button type="button" tabIndex={-1} style={style} className="pmw-tree__file" data-path={node.data.file?.path} data-active={node.isSelected || undefined} disabled={node.data.file?.binary} onClick={() => node.isInternal ? node.toggle() : node.activate()}>
|
|
37
|
-
<PmIcon name={node.isInternal ? 'folder' : 'document'} size={15} />{node.data.name}
|
|
38
|
-
</button>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function FilesPanel({ onOpen }: { onOpen?(path: string): void }) {
|
|
43
|
-
const t = useMessages()
|
|
44
|
-
const workbench = useWorkbenchStore()
|
|
45
|
-
const document = useWorkbench(state => state.document)
|
|
46
|
-
const files = useWorkbench(state => state.files)
|
|
47
|
-
const active = useWorkbench(state => state.active)
|
|
48
|
-
const documents = useWorkbench(state => state.documents)
|
|
49
|
-
const failure = useWorkbench(state => state.openFailure)
|
|
50
|
-
const data = useMemo(() => fileTree(files?.files ?? [], files?.home ? '' : document ?? ''), [files, document])
|
|
51
|
-
if (document === undefined) return null
|
|
52
|
-
const title = files?.home ? documents?.title ?? t.workspace : documents?.documents.find(doc => doc.dir === document)?.title ?? (document || documents?.title || t.workspace)
|
|
53
|
-
return (
|
|
54
|
-
<nav className="pmw-files" aria-label={t.filesOf(title)}>
|
|
55
|
-
<div className="pmw-files__head">
|
|
56
|
-
<PmIconButton icon="arrow-left" label={t.backToDocuments} small onClick={() => workbench.closeDocument()} />
|
|
57
|
-
<h2 className="pmw-files__title">{title}</h2>
|
|
58
|
-
</div>
|
|
59
|
-
{failure && <PmNotice tone="danger">{t.openFailed(failure)}</PmNotice>}
|
|
60
|
-
{files && files.files.length === 0 && <p className="pmw-muted">{t.noFiles}</p>}
|
|
61
|
-
{files && (
|
|
62
|
-
<Tree<FileNode> className="pmw-tree" data={data} selection={active} width="100%" height={Math.min(480, Math.max(96, (files.files.length + data.length) * 32))} rowHeight={32} indent={16} openByDefault disableDrag disableDrop disableEdit disableMultiSelection disableSelect={node => Boolean(node.file?.binary)} aria-label={t.files} onActivate={node => {
|
|
63
|
-
if (!node.data.file || node.data.file.binary) return
|
|
64
|
-
void workbench.openFile(node.data.file.path)
|
|
65
|
-
onOpen?.(node.data.file.path)
|
|
66
|
-
}}>
|
|
67
|
-
{FileRow}
|
|
68
|
-
</Tree>
|
|
69
|
-
)}
|
|
70
|
-
{files?.truncated && <p className="pmw-muted">{t.truncated}</p>}
|
|
71
|
-
</nav>
|
|
72
|
-
)
|
|
73
|
-
}
|