Skip to content

Commit 5b3e722

Browse files
committed
add patch view
1 parent bc02596 commit 5b3e722

File tree

9 files changed

+239
-87
lines changed

9 files changed

+239
-87
lines changed

src/components/editor.js

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class EditorTabs extends Tonic {
8181
index: this.index++
8282
}
8383

84-
tab.model.onDidChangeContent((...args) => editor.changes(tab, ...args))
84+
tab.model.onDidChangeContent((...args) => parent.editor.changes(tab, ...args))
8585

8686
this.state.tabs.set(node.id, tab)
8787
parent.editor.setModel(tab.model)
@@ -94,7 +94,7 @@ class EditorTabs extends Tonic {
9494
if (this.state.tabs.has(id)) {
9595
this.state.tabs.delete(id)
9696
}
97-
97+
9898
this.reRender()
9999
}
100100

@@ -265,7 +265,7 @@ class AppEditor extends Tonic {
265265
const coTabs = document.querySelector('editor-tabs')
266266
const coEditor = document.querySelector('app-editor')
267267

268-
if (!coTabs.tab) return
268+
if (!coTabs.tab || coTabs.tab.isReadOnly) return
269269

270270
const app = this.props.parent
271271
const value = this.editor.getValue()
@@ -290,12 +290,16 @@ class AppEditor extends Tonic {
290290
coTabs.tab.unsaved = false
291291
coTabs.reRender()
292292
} catch (err) {
293-
console.error(`Unable to write to ${pathToFile}`, err)
293+
console.error(`Unable to write to ${coTabs.tab.path}`, err)
294294
}
295295

296296
app.reloadPreviewWindows()
297297
}
298298

299+
async reload () {
300+
this.loadProjectNode(this.state.projectNode)
301+
}
302+
299303
async loadProjectNode (projectNode) {
300304
if (!projectNode) return
301305

@@ -313,7 +317,7 @@ class AppEditor extends Tonic {
313317
const mappings = app.state.settings.extensionLanguageMappings
314318
const lang = mappings[ext] || ext.slice(1)
315319
monaco.editor.setModelLanguage(this.editor.getModel(), lang)
316-
let data = await fs.promises.readFile(projectNode.id, 'utf8')
320+
let data = projectNode.value || await fs.promises.readFile(projectNode.id, 'utf8')
317321

318322
if (path.extname(projectNode.id) === '.json') {
319323
try {
@@ -342,14 +346,18 @@ class AppEditor extends Tonic {
342346
success: rgbaToHex(styles.getPropertyValue('--tonic-success').trim())
343347
}
344348

345-
346349
const userColors = this.props.parent.state.settings?.userColors ?? []
347350

348351
const base = `vs${theme.includes('dark') ? '-dark' : ''}`
349352
monaco.editor.defineTheme(theme, {
350353
base,
351354
inherit: true,
352355
rules: [
356+
{ token: 'lineFile', foreground: colors.accent }, // Green for added lines
357+
{ token: 'lineHeader', foreground: colors.info }, // Green for added lines
358+
{ token: 'lineAdded', foreground: colors.success }, // Green for added lines
359+
{ token: 'lineRemoved', foreground: colors.error }, // Red for removed lines
360+
353361
{ token: 'identifier', foreground: colors.primary },
354362
{ token: 'keyword', foreground: colors.accent },
355363
{ token: 'punctuation', foreground: colors.primary },
@@ -471,7 +479,6 @@ class AppEditor extends Tonic {
471479

472480
connected () {
473481
let theme
474-
const app = this.props.parent
475482

476483
this.editor = monaco.editor.create(this.querySelector('.editor'), {
477484
value: '',
@@ -486,6 +493,78 @@ class AppEditor extends Tonic {
486493

487494
this.updateSettings()
488495

496+
monaco.languages.registerFoldingRangeProvider('patch', {
497+
provideFoldingRanges: function (model, context, token) {
498+
const hunkStartRegex = /^@@ -\d+(,\d+)? \+\d+(,\d+)? @@.*/
499+
const diffStartRegex = /^diff --git a\/.+ b\/.+/
500+
const foldingRanges = []
501+
let currentHunkStart = -1
502+
503+
for (let i = 0; i < model.getLineCount(); i++) {
504+
const lineContent = model.getLineContent(i + 1)
505+
506+
if (hunkStartRegex.test(lineContent)) {
507+
if (currentHunkStart !== -1) {
508+
foldingRanges.push({
509+
start: currentHunkStart,
510+
end: i,
511+
kind: monaco.languages.FoldingRangeKind.Region
512+
})
513+
}
514+
currentHunkStart = i + 1
515+
} else if (diffStartRegex.test(lineContent) && currentHunkStart !== -1) {
516+
foldingRanges.push({
517+
start: currentHunkStart,
518+
end: i,
519+
kind: monaco.languages.FoldingRangeKind.Region
520+
})
521+
currentHunkStart = -1
522+
}
523+
}
524+
525+
if (currentHunkStart !== -1) {
526+
foldingRanges.push({
527+
start: currentHunkStart,
528+
end: model.getLineCount(),
529+
kind: monaco.languages.FoldingRangeKind.Region
530+
})
531+
}
532+
533+
return foldingRanges
534+
}
535+
})
536+
537+
monaco.languages.register({ id: 'patch' })
538+
539+
monaco.languages.setMonarchTokensProvider('patch', {
540+
tokenizer: {
541+
root: [
542+
[/^index \w+\.\.\w+( \d+)?/, 'lineHeader'],
543+
[/^---.*/, 'lineHeader'],
544+
[/^\+\+\+.*/, 'lineHeader'],
545+
[/^@@ -\d+(,\d+)? \+\d+(,\d+)? @@.*/, 'lineHeader'],
546+
[/^diff --git a\/.+ b\/.+/, 'lineFile'],
547+
[/^From:.*<[^>]+>/, 'lineHeader'],
548+
[/^Date:.+/, 'lineHeader'],
549+
[/^Subject: \[PATCH\].+/, 'lineHeader'],
550+
[/^From [0-9a-fA-F]+ Mon .+/, 'lineHeader'],
551+
552+
[/^\+(?!\+\+).*/, 'lineAdded'],
553+
[/^-(?!-).*/, 'lineRemoved']
554+
]
555+
}
556+
})
557+
558+
this.editor.onDidChangeModelContent(event => {
559+
const coTabs = document.querySelector('editor-tabs')
560+
this.editor.updateOptions({ readOnly: false })
561+
562+
if (coTabs.tab?.label.endsWith('.patch')) {
563+
this.editor.updateOptions({ readOnly: true })
564+
this.editor.getAction('editor.foldAll').run()
565+
}
566+
})
567+
489568
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
490569
this.refreshColors(event)
491570
})

src/components/patch-requests.js

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,51 @@
11
import Tonic from '@socketsupply/tonic'
2-
import { exec } from 'socket:child_process'
32

43
class PatchRequests extends Tonic {
54
async click (e) {
65
const el = Tonic.match(e.target, '[data-event]')
76
if (!el) return
87

9-
const { event, value } = el.dataset
108
const app = this.props.app
9+
const row = el.closest('.row')
10+
const value = row.dataset.value
11+
12+
const { data: patch } = await app.db.patches.get(value)
13+
14+
const { event } = el.dataset
15+
16+
if (event === 'view') {
17+
const coProjectSummary = document.querySelector('view-project-summary')
18+
const coEditor = document.querySelector('app-editor')
19+
const name = (patch.patchId || patch.headers.parent.slice(6)) + '.patch'
20+
21+
coEditor.loadProjectNode({
22+
isReadOnly: true,
23+
id: name,
24+
label: name,
25+
value: patch.src
26+
})
27+
28+
coProjectSummary.hide()
29+
}
30+
31+
if (event === 'apply') {
32+
console.log('APPLY', patch)
33+
}
34+
35+
if (event === 'trash') {
36+
console.log('TRASH', patch)
37+
}
1138

1239
if (event === 'trust') {
13-
const { data: hasKey } = await app.db.keys.has(value.headers.from)
40+
const { data: hasKey } = await app.db.keys.has(patch.headers.from)
1441

1542
if (!hasKey) {
16-
await app.db.keys.put(value.headers.from, value.publicKey)
43+
await app.db.keys.put(patch.headers.from, patch.publicKey)
1744
this.reRender()
1845
return
1946
}
2047

21-
await app.db.keys.del(value.headers.from)
48+
await app.db.keys.del(patch.headers.from)
2249
this.reRender()
2350
}
2451
}
@@ -28,17 +55,18 @@ class PatchRequests extends Tonic {
2855

2956
const { data: dataPatches } = await app.db.patches.readAll()
3057

31-
let patches = []
58+
const patches = []
3259

3360
for (const [patchId, patch] of dataPatches.entries()) {
3461
const ts = (new Date(patch.headers.date)).getTime()
3562
let statusIcon = 'warning'
3663
let statusTitle = 'This patch is not associated with a trusted public key. Click here to trust it.'
64+
const { data: hasKey } = await app.db.keys.has(patch.headers.from)
3765

38-
if (patch.publicKey) {
66+
if (hasKey && patch.publicKey && patch.headers.from) {
3967
const { data: dataKey } = await app.db.keys.get(patch.headers.from)
4068

41-
const savedB64pk = Buffer.from(dataKey).toString('base64')
69+
const savedB64pk = Buffer.from(dataKey || '').toString('base64')
4270
const thisB64pk = Buffer.from(patch.publicKey || '').toString('base64')
4371

4472
if (dataKey && savedB64pk === thisB64pk) {
@@ -51,14 +79,14 @@ class PatchRequests extends Tonic {
5179
}
5280

5381
patches.push(this.html`
54-
<div class="row" data-event="view-patch" data-value="${patchId}">
82+
<div class="row" data-event="view" data-value=${patchId}>
5583
<span>${patch.headers.from}</span>
5684
<span><relative-date ts="${ts}"></relative-date></span>
5785
<span>${patch.summary.split('\n').pop()}</span>
5886
<span class="actions">
59-
<tonic-button type="icon" symbol-id="plus-icon" title="Apply the patch" size="16px" data-event="load" data-value="${patchId}"></tonic-button>
60-
<tonic-button type="icon" symbol-id="${statusIcon}-icon" title="${statusTitle}" size="16px" data-event="trust" data-value="${patch}"></tonic-button>
61-
<tonic-button type="icon" symbol-id="trash-icon" title="Discard this patch" size="16px" data-event="trash" data-value="${patchId}"></tonic-button>
87+
<tonic-button type="icon" symbol-id="plus-icon" title="Apply the patch" size="16px" data-event="apply"></tonic-button>
88+
<tonic-button type="icon" symbol-id="${statusIcon}-icon" title="${statusTitle}" size="16px" data-event="trust"></tonic-button>
89+
<tonic-button type="icon" symbol-id="trash-icon" title="Discard this patch" size="16px" data-event="trash"></tonic-button>
6290
</span>
6391
</div>
6492
`)

src/components/project.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,6 @@ class AppProject extends Tonic {
502502

503503
async onSelection (node, isToggle) {
504504
if (!isToggle) {
505-
const app = this.props.parent
506505
const projectNode = this.getProjectNode(node)
507506
const coImagePreview = document.querySelector('view-image-preview')
508507
const coProjectSummary = document.querySelector('view-project-summary')
@@ -629,7 +628,7 @@ class AppProject extends Tonic {
629628

630629
async load () {
631630
const app = this.props.parent
632-
let oldState = this.state.tree
631+
const oldState = this.state.tree
633632
let oldChild = this.getNodeByProperty('id', 'home', oldState)
634633

635634
const tree = {

src/components/properties.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import Tonic from '@socketsupply/tonic'
2-
import fs from 'socket:fs'
3-
import path from 'socket:path'
42
import process from 'socket:process'
53

64
import Config from '../lib/config.js'
@@ -14,8 +12,6 @@ class AppProperties extends Tonic {
1412

1513
const app = this.props.parent
1614
const notifications = document.querySelector('#notifications')
17-
const editor = document.querySelector('app-editor')
18-
const project = document.querySelector('app-project')
1915
const config = new Config(app.state.currentProject?.id)
2016

2117
//
@@ -37,12 +33,17 @@ class AppProperties extends Tonic {
3733
//
3834
if (event === 'property') {
3935
await config.set(section, el.id, el.value)
40-
editor.loadProjectNode(node)
36+
const coTabs = document.querySelector('editor-tabs')
37+
const coEditor = document.querySelector('app-editor')
38+
39+
if (coTabs.tab && coTabs.tab.isRootSettingsFile) {
40+
coEditor.reload()
41+
}
4142

4243
notifications?.create({
4344
type: 'info',
4445
title: 'Note',
45-
message: 'A restart of the app your building may be required.'
46+
message: 'A rebuild of your app may be required.'
4647
})
4748
}
4849
}

src/components/publish.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import { exec, execSync } from 'socket:child_process'
55
import Tonic from '@socketsupply/tonic'
66
import { TonicDialog } from '@socketsupply/components/dialog'
77

8-
import Config from '../lib/config.js'
9-
108
export class DialogPublish extends TonicDialog {
119
click (e) {
1210
super.click(e)
@@ -125,10 +123,6 @@ export class DialogPublish extends TonicDialog {
125123
//
126124
// Try to commit the changes.
127125
//
128-
const msg = {
129-
parent: currentHash,
130-
message: commitMessage
131-
}
132126

133127
try {
134128
output = await exec(`git commit -m "${currentHash}" -m "${commitMessage}"`, { cwd })

src/components/relative-date.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Tonic from '@socketsupply/tonic'
2+
3+
const T_YEARS = 1000 * 60 * 60 * 24 * 365
4+
const T_MONTHS = 1000 * 60 * 60 * 24 * 30
5+
const T_WEEKS = 1000 * 60 * 60 * 24 * 7
6+
const T_DAYS = 1000 * 60 * 60 * 24
7+
const T_HOURS = 1000 * 60 * 60
8+
const T_MINUTES = 1000 * 60
9+
const T_SECONDS = 1000
10+
11+
class RelativeDate extends Tonic {
12+
calculate () {
13+
const ts = this.props.ts
14+
const t = Math.abs(ts - new Date().getTime())
15+
const toString = i => String(parseInt(i, 10))
16+
17+
if (t > T_YEARS) {
18+
return { value: `${toString(t / T_YEARS)}y` }
19+
} else if (t > T_MONTHS) {
20+
return { value: `${toString(t / T_MONTHS)}m` }
21+
} else if (t > T_WEEKS) {
22+
return { value: `${toString(t / T_WEEKS)}w` }
23+
} else if (t > T_DAYS) {
24+
return { value: `${toString(t / T_DAYS)}d` }
25+
} else if (t > T_HOURS) {
26+
return { value: `${toString(t / T_HOURS)}h` }
27+
} else if (t > T_MINUTES) {
28+
return { value: `${toString(t / T_MINUTES)}m`, timer: true }
29+
}
30+
return { value: `${toString(t / T_SECONDS)}s`, timer: true }
31+
}
32+
33+
render () {
34+
let updates = 60
35+
const o = this.calculate()
36+
const timer = setInterval(() => {
37+
if (--updates === 0) return clearInterval(timer)
38+
const o = this.calculate()
39+
this.innerHTML = o.value
40+
}, 1000)
41+
42+
return this.html`
43+
${o.value}
44+
`
45+
}
46+
}
47+
48+
export default RelativeDate
49+
export { RelativeDate }

0 commit comments

Comments
 (0)