Skip to content

Commit

Permalink
add patch view
Browse files Browse the repository at this point in the history
  • Loading branch information
heapwolf committed Apr 1, 2024
1 parent bc02596 commit 5b3e722
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 87 deletions.
93 changes: 86 additions & 7 deletions src/components/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class EditorTabs extends Tonic {
index: this.index++
}

tab.model.onDidChangeContent((...args) => editor.changes(tab, ...args))
tab.model.onDidChangeContent((...args) => parent.editor.changes(tab, ...args))

this.state.tabs.set(node.id, tab)
parent.editor.setModel(tab.model)
Expand All @@ -94,7 +94,7 @@ class EditorTabs extends Tonic {
if (this.state.tabs.has(id)) {
this.state.tabs.delete(id)
}

this.reRender()
}

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

if (!coTabs.tab) return
if (!coTabs.tab || coTabs.tab.isReadOnly) return

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

app.reloadPreviewWindows()
}

async reload () {
this.loadProjectNode(this.state.projectNode)
}

async loadProjectNode (projectNode) {
if (!projectNode) return

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

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


const userColors = this.props.parent.state.settings?.userColors ?? []

const base = `vs${theme.includes('dark') ? '-dark' : ''}`
monaco.editor.defineTheme(theme, {
base,
inherit: true,
rules: [
{ token: 'lineFile', foreground: colors.accent }, // Green for added lines
{ token: 'lineHeader', foreground: colors.info }, // Green for added lines
{ token: 'lineAdded', foreground: colors.success }, // Green for added lines
{ token: 'lineRemoved', foreground: colors.error }, // Red for removed lines

{ token: 'identifier', foreground: colors.primary },
{ token: 'keyword', foreground: colors.accent },
{ token: 'punctuation', foreground: colors.primary },
Expand Down Expand Up @@ -471,7 +479,6 @@ class AppEditor extends Tonic {

connected () {
let theme
const app = this.props.parent

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

this.updateSettings()

monaco.languages.registerFoldingRangeProvider('patch', {
provideFoldingRanges: function (model, context, token) {
const hunkStartRegex = /^@@ -\d+(,\d+)? \+\d+(,\d+)? @@.*/
const diffStartRegex = /^diff --git a\/.+ b\/.+/
const foldingRanges = []
let currentHunkStart = -1

for (let i = 0; i < model.getLineCount(); i++) {
const lineContent = model.getLineContent(i + 1)

if (hunkStartRegex.test(lineContent)) {
if (currentHunkStart !== -1) {
foldingRanges.push({
start: currentHunkStart,
end: i,
kind: monaco.languages.FoldingRangeKind.Region
})
}
currentHunkStart = i + 1
} else if (diffStartRegex.test(lineContent) && currentHunkStart !== -1) {
foldingRanges.push({
start: currentHunkStart,
end: i,
kind: monaco.languages.FoldingRangeKind.Region
})
currentHunkStart = -1
}
}

if (currentHunkStart !== -1) {
foldingRanges.push({
start: currentHunkStart,
end: model.getLineCount(),
kind: monaco.languages.FoldingRangeKind.Region
})
}

return foldingRanges
}
})

monaco.languages.register({ id: 'patch' })

monaco.languages.setMonarchTokensProvider('patch', {
tokenizer: {
root: [
[/^index \w+\.\.\w+( \d+)?/, 'lineHeader'],
[/^---.*/, 'lineHeader'],
[/^\+\+\+.*/, 'lineHeader'],
[/^@@ -\d+(,\d+)? \+\d+(,\d+)? @@.*/, 'lineHeader'],
[/^diff --git a\/.+ b\/.+/, 'lineFile'],
[/^From:.*<[^>]+>/, 'lineHeader'],
[/^Date:.+/, 'lineHeader'],
[/^Subject: \[PATCH\].+/, 'lineHeader'],
[/^From [0-9a-fA-F]+ Mon .+/, 'lineHeader'],

[/^\+(?!\+\+).*/, 'lineAdded'],
[/^-(?!-).*/, 'lineRemoved']
]
}
})

this.editor.onDidChangeModelContent(event => {
const coTabs = document.querySelector('editor-tabs')
this.editor.updateOptions({ readOnly: false })

if (coTabs.tab?.label.endsWith('.patch')) {
this.editor.updateOptions({ readOnly: true })
this.editor.getAction('editor.foldAll').run()
}
})

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
this.refreshColors(event)
})
Expand Down
52 changes: 40 additions & 12 deletions src/components/patch-requests.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,51 @@
import Tonic from '@socketsupply/tonic'
import { exec } from 'socket:child_process'

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

const { event, value } = el.dataset
const app = this.props.app
const row = el.closest('.row')
const value = row.dataset.value

const { data: patch } = await app.db.patches.get(value)

const { event } = el.dataset

if (event === 'view') {
const coProjectSummary = document.querySelector('view-project-summary')
const coEditor = document.querySelector('app-editor')
const name = (patch.patchId || patch.headers.parent.slice(6)) + '.patch'

coEditor.loadProjectNode({
isReadOnly: true,
id: name,
label: name,
value: patch.src
})

coProjectSummary.hide()
}

if (event === 'apply') {
console.log('APPLY', patch)
}

if (event === 'trash') {
console.log('TRASH', patch)
}

if (event === 'trust') {
const { data: hasKey } = await app.db.keys.has(value.headers.from)
const { data: hasKey } = await app.db.keys.has(patch.headers.from)

if (!hasKey) {
await app.db.keys.put(value.headers.from, value.publicKey)
await app.db.keys.put(patch.headers.from, patch.publicKey)
this.reRender()
return
}

await app.db.keys.del(value.headers.from)
await app.db.keys.del(patch.headers.from)
this.reRender()
}
}
Expand All @@ -28,17 +55,18 @@ class PatchRequests extends Tonic {

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

let patches = []
const patches = []

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

if (patch.publicKey) {
if (hasKey && patch.publicKey && patch.headers.from) {
const { data: dataKey } = await app.db.keys.get(patch.headers.from)

const savedB64pk = Buffer.from(dataKey).toString('base64')
const savedB64pk = Buffer.from(dataKey || '').toString('base64')
const thisB64pk = Buffer.from(patch.publicKey || '').toString('base64')

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

patches.push(this.html`
<div class="row" data-event="view-patch" data-value="${patchId}">
<div class="row" data-event="view" data-value=${patchId}>
<span>${patch.headers.from}</span>
<span><relative-date ts="${ts}"></relative-date></span>
<span>${patch.summary.split('\n').pop()}</span>
<span class="actions">
<tonic-button type="icon" symbol-id="plus-icon" title="Apply the patch" size="16px" data-event="load" data-value="${patchId}"></tonic-button>
<tonic-button type="icon" symbol-id="${statusIcon}-icon" title="${statusTitle}" size="16px" data-event="trust" data-value="${patch}"></tonic-button>
<tonic-button type="icon" symbol-id="trash-icon" title="Discard this patch" size="16px" data-event="trash" data-value="${patchId}"></tonic-button>
<tonic-button type="icon" symbol-id="plus-icon" title="Apply the patch" size="16px" data-event="apply"></tonic-button>
<tonic-button type="icon" symbol-id="${statusIcon}-icon" title="${statusTitle}" size="16px" data-event="trust"></tonic-button>
<tonic-button type="icon" symbol-id="trash-icon" title="Discard this patch" size="16px" data-event="trash"></tonic-button>
</span>
</div>
`)
Expand Down
3 changes: 1 addition & 2 deletions src/components/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,6 @@ class AppProject extends Tonic {

async onSelection (node, isToggle) {
if (!isToggle) {
const app = this.props.parent
const projectNode = this.getProjectNode(node)
const coImagePreview = document.querySelector('view-image-preview')
const coProjectSummary = document.querySelector('view-project-summary')
Expand Down Expand Up @@ -629,7 +628,7 @@ class AppProject extends Tonic {

async load () {
const app = this.props.parent
let oldState = this.state.tree
const oldState = this.state.tree
let oldChild = this.getNodeByProperty('id', 'home', oldState)

const tree = {
Expand Down
13 changes: 7 additions & 6 deletions src/components/properties.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import Tonic from '@socketsupply/tonic'
import fs from 'socket:fs'
import path from 'socket:path'
import process from 'socket:process'

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

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

//
Expand All @@ -37,12 +33,17 @@ class AppProperties extends Tonic {
//
if (event === 'property') {
await config.set(section, el.id, el.value)
editor.loadProjectNode(node)
const coTabs = document.querySelector('editor-tabs')
const coEditor = document.querySelector('app-editor')

if (coTabs.tab && coTabs.tab.isRootSettingsFile) {
coEditor.reload()
}

notifications?.create({
type: 'info',
title: 'Note',
message: 'A restart of the app your building may be required.'
message: 'A rebuild of your app may be required.'
})
}
}
Expand Down
6 changes: 0 additions & 6 deletions src/components/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { exec, execSync } from 'socket:child_process'
import Tonic from '@socketsupply/tonic'
import { TonicDialog } from '@socketsupply/components/dialog'

import Config from '../lib/config.js'

export class DialogPublish extends TonicDialog {
click (e) {
super.click(e)
Expand Down Expand Up @@ -125,10 +123,6 @@ export class DialogPublish extends TonicDialog {
//
// Try to commit the changes.
//
const msg = {
parent: currentHash,
message: commitMessage
}

try {
output = await exec(`git commit -m "${currentHash}" -m "${commitMessage}"`, { cwd })
Expand Down
49 changes: 49 additions & 0 deletions src/components/relative-date.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Tonic from '@socketsupply/tonic'

const T_YEARS = 1000 * 60 * 60 * 24 * 365
const T_MONTHS = 1000 * 60 * 60 * 24 * 30
const T_WEEKS = 1000 * 60 * 60 * 24 * 7
const T_DAYS = 1000 * 60 * 60 * 24
const T_HOURS = 1000 * 60 * 60
const T_MINUTES = 1000 * 60
const T_SECONDS = 1000

class RelativeDate extends Tonic {
calculate () {
const ts = this.props.ts
const t = Math.abs(ts - new Date().getTime())
const toString = i => String(parseInt(i, 10))

if (t > T_YEARS) {
return { value: `${toString(t / T_YEARS)}y` }
} else if (t > T_MONTHS) {
return { value: `${toString(t / T_MONTHS)}m` }
} else if (t > T_WEEKS) {
return { value: `${toString(t / T_WEEKS)}w` }
} else if (t > T_DAYS) {
return { value: `${toString(t / T_DAYS)}d` }
} else if (t > T_HOURS) {
return { value: `${toString(t / T_HOURS)}h` }
} else if (t > T_MINUTES) {
return { value: `${toString(t / T_MINUTES)}m`, timer: true }
}
return { value: `${toString(t / T_SECONDS)}s`, timer: true }
}

render () {
let updates = 60
const o = this.calculate()
const timer = setInterval(() => {
if (--updates === 0) return clearInterval(timer)
const o = this.calculate()
this.innerHTML = o.value
}, 1000)

return this.html`
${o.value}
`
}
}

export default RelativeDate
export { RelativeDate }
Loading

0 comments on commit 5b3e722

Please sign in to comment.