From 8b9b2073f888ac283d60a425ead5fddf38aa68a3 Mon Sep 17 00:00:00 2001 From: heapwolf Date: Fri, 22 Mar 2024 15:45:53 +0100 Subject: [PATCH] wip project sharing --- src/components/editor.js | 5 +- src/components/image-preview.js | 132 ------------------ src/components/project.js | 31 +++- src/components/properties.js | 113 +++++++-------- src/components/publish.js | 29 +--- src/components/sprite.js | 16 +++ src/components/subscribe.js | 13 +- src/components/terminal.js | 1 - .../{editor.main.css => component-editor.css} | 0 .../{project.css => component-project.css} | 0 ...roperties.css => component-properties.css} | 18 +++ .../{publish.css => component-publish.css} | 0 ...{subscribe.css => component-subscribe.css} | 2 +- src/css/{xterm.css => component-terminal.css} | 0 src/css/index.css | 38 +++-- src/css/preview.css | 53 ------- src/css/view-home.css | 59 ++++++++ ...age-preview.css => view-image-preview.css} | 24 ++-- src/index.html | 20 +-- src/index.js | 43 +++--- src/lib/config.js | 101 ++++++++++++++ src/lib/ini.js | 71 ---------- src/preview.js | 7 +- src/views/home.js | 53 +++++++ src/views/image-preview.js | 122 ++++++++++++++++ src/worker.js | 4 +- 26 files changed, 534 insertions(+), 421 deletions(-) delete mode 100644 src/components/image-preview.js rename src/css/{editor.main.css => component-editor.css} (100%) rename src/css/{project.css => component-project.css} (100%) rename src/css/{properties.css => component-properties.css} (68%) rename src/css/{publish.css => component-publish.css} (100%) rename src/css/{subscribe.css => component-subscribe.css} (97%) rename src/css/{xterm.css => component-terminal.css} (100%) delete mode 100644 src/css/preview.css create mode 100644 src/css/view-home.css rename src/css/{image-preview.css => view-image-preview.css} (75%) create mode 100644 src/lib/config.js delete mode 100644 src/lib/ini.js create mode 100644 src/views/home.js create mode 100644 src/views/image-preview.js diff --git a/src/components/editor.js b/src/components/editor.js index 3a47ee5..b35630c 100644 --- a/src/components/editor.js +++ b/src/components/editor.js @@ -1,12 +1,9 @@ import fs from 'socket:fs' import path from 'socket:path' -import { lookup } from 'socket:mime' import * as monaco from 'monaco-editor' import Tonic from '@socketsupply/tonic' -import { resizePNG } from '../lib/icon.js' - function rgbaToHex (rgbaString) { const rgbaValues = rgbaString.match(/\d+/g) @@ -216,7 +213,7 @@ class AppEditor extends Tonic { return } - coTerminal.info(`Settings file updated.`) + coTerminal.info('Settings file updated.') app.activatePreviewWindows() } diff --git a/src/components/image-preview.js b/src/components/image-preview.js deleted file mode 100644 index fec9aca..0000000 --- a/src/components/image-preview.js +++ /dev/null @@ -1,132 +0,0 @@ -import fs from 'socket:fs' -import path from 'socket:path' - -import Tonic from '@socketsupply/tonic' - -import * as ini from '../lib/ini.js' - -class AppImagePreview extends Tonic { - async click (e) { - const el = Tonic.match(e.target, '[data-event]') - if (!el) return - - const { event, value } = el.dataset - - const pickerOpts = { - types: [ - { - description: 'Images', - accept: { - 'image/*': ['.png'] - } - } - ], - excludeAcceptAllOption: true, - multiple: false - } - - if (event === 'size') { - const [fileHandle] = await window.showOpenFilePicker(pickerOpts) - - /* const kFileSystemHandleFullName = Object - .getOwnPropertySymbols(data) - .find(s => s.description === 'kFileSystemHandleFullName') - const pathToFile = fileHandle[kFileSystemHandleFullName] - */ - - const file = fileHandle.getFile() - const buf = await file.arrayBuffer() - - if (value === 'all') { - const imagePreview = this.querySelector('.image-preview') - const blob = new Blob([buf], { type: 'image/png' }) - const url = URL.createObjectURL(blob) - ;[...imagePreview.querySelectorAll('img')].forEach(img => (img.src = url)) - return - } - - const blob = await resizePNG(buf, parseInt(value)) - - el.src = URL.createObjectURL(blob) - } - } - - show () { - this.classList.add('show') - } - - hide () { - this.classList.remove('show') - } - - async load (projectNode) { - this.state.pathToFile = projectNode.id - this.reRender() - } - - async render () { - const app = this.props.parent - const settings = app.state.settings - const currentProject = app.state.currentProject - - let src = '' - - if (!currentProject) return this.html`` - - const cwd = currentProject?.id - - try { - const pathToConfigFile = path.join(cwd, 'socket.ini') - src = await fs.promises.readFile(pathToConfigFile, 'utf8') - } catch (err) { - const notifications = document.querySelector('#notifications') - notifications?.create({ - type: 'error', - title: 'Error', - message: err.message - }) - } - - const getSizes = platform => ini - .get(src, platform, 'icon_sizes') - .replace(/"/g, '') - .split(' ') - .map(pair => { - let { 0: size, 1: scale } = pair.split('@') - scale = parseInt(scale) - - const src = this.state.pathToFile.replace(path.HOME, '/user/home') - const scaled = size * scale - - return this.html` -
- - -
- ` - }) - - return this.html` -
-

Icon Preview

- Update -
-
-

MacOS

-
${getSizes('mac')}
- -

iOS

-
${getSizes('ios')}
- -

Linux

-
${getSizes('linux')}
- -

Windows

-
${getSizes('win')}
-
- ` - } -} - -export { AppImagePreview } -export default AppImagePreview diff --git a/src/components/project.js b/src/components/project.js index f3d9eb7..e2a5fe8 100644 --- a/src/components/project.js +++ b/src/components/project.js @@ -35,7 +35,7 @@ async function cp (srcDir, destDir) { const destPath = path.join(destDir, file.name) if (file.isDirectory()) { - await copyDirectory(srcPath, destPath) + await cp(srcPath, destPath) } else { await fs.promises.copyFile(srcPath, destPath, fs.constants.COPYFILE_FICLONE) } @@ -462,7 +462,6 @@ class AppProject extends Tonic { async onSelection (node, isToggle) { if (!isToggle) { - const projectNode = this.getProjectNode(node) // Check if the project has changed, refresh the props component @@ -476,10 +475,20 @@ class AppProject extends Tonic { this.state.currentProject = projectNode.id + const coImagePreview = document.querySelector('view-image-preview') + const coHome = document.querySelector('view-home') + + coImagePreview.hide() + coHome.hide() + + if (projectNode.id === 'home') { + coHome.show() + return + } + // Check if this is an image type that we can present const ext = path.extname(node.id) const type = await lookup(ext.slice(1)) - const coImagePreview = document.querySelector('app-image-preview') if (type.length) { if (/image/.test(type[0].mime)) { @@ -489,8 +498,6 @@ class AppProject extends Tonic { } } - coImagePreview.hide() - // Load the code editor const coEditor = document.querySelector('app-editor') coEditor.loadProjectNode(node) @@ -568,12 +575,24 @@ class AppProject extends Tonic { async load () { const oldState = this.state.tree + const oldChild = this.getNodeByProperty('id', 'home', oldState) const tree = { id: 'root', children: [] } + tree.children.push({ + id: 'home', + parent: tree, + selected: oldChild?.selected ?? 0, + state: oldChild?.state ?? 0, + isDirectory: false, + icon: 'home-icon', + label: 'Home', + children: [] + }) + const readDir = async (dirPath, parent) => { let entries = [] @@ -627,8 +646,6 @@ class AppProject extends Tonic { } } - const app = document.querySelector('app-view') - try { await readDir(path.join(path.DATA, 'projects'), tree) this.state.tree = tree diff --git a/src/components/properties.js b/src/components/properties.js index 05edeb0..b08972d 100644 --- a/src/components/properties.js +++ b/src/components/properties.js @@ -4,13 +4,9 @@ import path from 'socket:path' import { exec } from 'socket:child_process' import { Encryption, sha256 } from 'socket:network' -import * as ini from '../lib/ini.js' +import Config from '../lib/config.js' class AppProperties extends Tonic { - constructor () { - super() - } - async change (e) { const el = Tonic.match(e.target, '[data-event]') if (!el) return @@ -21,7 +17,29 @@ class AppProperties extends Tonic { const notifications = document.querySelector('#notifications') const editor = document.querySelector('app-editor') const project = document.querySelector('app-project') + const config = new Config(app.currentProject?.id) + + // + // if the user changes the project link, we need to update the project. + // projects are keyed on the bundle id, so get the bundle id from the + // config, then update the sharedSecret, then await configt the network. + // + if (event === 'project-link') { + if (!config) return + + let bundleId = await config.get('meta', 'bundle_identifier') + if (bundleId) bundleId = bundleId.replace(/"/g, '') + + const { data: dataBundle } = await app.db.projects.get(bundleId) + dataBundle.sharedSecret = el.value + await app.db.projects.put(bundleId, dataBundle) + await app.initNetwork() + } + + // + // when the user wants to toggle one of the preview windows they have configured + // if (event === 'preview') { const pathToSettingsFile = path.join(path.DATA, 'projects', 'settings.json') const previewWindow = app.state.settings.previewWindows.find(o => o.title === value) @@ -59,13 +77,11 @@ class AppProperties extends Tonic { } } + // + // When the user wants to make a change to the one of the properties in the await config file + // if (event === 'property') { - const node = project.getNodeByProperty('id', 'socket.ini') - node.data = ini.set(node.data, section, el.id, el.value) - - const dest = path.join(app.state.cwd, node.id) - await fs.promises.writeFile(dest, node.data) - + await config.set(section, el.id, el.value) editor.loadProjectNode(node) notifications?.create({ @@ -77,14 +93,17 @@ class AppProperties extends Tonic { } async click (e) { + const elCopy = Tonic.match(e.target, '[symbol-id="copy-icon"]') + + if (elCopy) { + navigator.clipboard.writeText('union://' + elCopy.nextElementSibling.value) + return + } + const el = Tonic.match(e.target, '[data-event]') if (!el) return - const { event, propertyValue } = el.dataset - - if (event === 'copy-link') { - navigator.clipboard.writeText(el.value) - } + const { event } = el.dataset if (event === 'publish') { const coDialogPublish = document.querySelector('dialog-publish') @@ -98,27 +117,9 @@ class AppProperties extends Tonic { } async render () { - let src = '' - const app = this.props.parent const settings = app.state.settings - const currentProject = app.state.currentProject - const cwd = currentProject?.id - - if (currentProject) { - try { - const pathToConfigFile = path.join(cwd, 'socket.ini') - src = await fs.promises.readFile(pathToConfigFile, 'utf8') - } catch (err) { - const notifications = document.querySelector('#notifications') - notifications?.create({ - type: 'error', - title: 'Error', - message: err.message - }) - } - } - + const config = new Config(settings.currentProject?.id) const previewWindows = [] if (settings?.previewWindows) { @@ -143,10 +144,11 @@ class AppProperties extends Tonic { } } - let bundleId = ini.get(src, 'meta', 'bundle_identifier') + let bundleId = await config.get('meta', 'bundle_identifier') if (bundleId) bundleId = bundleId.replace(/"/g, '') let sharedSecret = '' + const cwd = app.state.currentProject?.id const { data: hasBundle } = await app.db.projects.has(bundleId) @@ -175,7 +177,7 @@ class AppProperties extends Tonic { clusterId, subclusterId, sharedKey, - sharedSecret // TODO(@heapwolf): encrypt sharedSecret via initial global password + sharedSecret // TODO(@heapwolf): encrypt sharedSecret via await configtial global password }) // @@ -189,13 +191,13 @@ class AppProperties extends Tonic { if (cwd) { // - // If there is a current project, check if its been git initialized. + // If there is a current project, check if its been git await configtialized. // try { await fs.promises.stat(path.join(cwd, '.git')) } catch (err) { try { - gitStatus = await exec('git init', { cwd }) + gitStatus = await exec('git await configt', { cwd }) } catch (err) { gitStatus.stderr = err.message } @@ -256,27 +258,27 @@ class AppProperties extends Tonic { id="application" label="Desktop Features" > - - - + + + - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/components/publish.js b/src/components/publish.js index 3d33215..8449b1d 100644 --- a/src/components/publish.js +++ b/src/components/publish.js @@ -1,12 +1,11 @@ import fs from 'socket:fs' import path from 'socket:path' -import { Encryption } from 'socket:network' -import { spawn, exec } from 'socket:child_process' +import { exec } from 'socket:child_process' import Tonic from '@socketsupply/tonic' import { TonicDialog } from '@socketsupply/components/dialog' -import * as ini from '../lib/ini.js' +import Config from '../lib/config.js' export class DialogPublish extends TonicDialog { click (e) { @@ -25,23 +24,8 @@ export class DialogPublish extends TonicDialog { const currentProject = app.state.currentProject if (!currentProject) return - let src - - try { - const fp = path.join(currentProject.id, 'socket.ini') - src = await fs.promises.readFile(fp, 'utf8') - } catch (err) { - const notifications = document.querySelector('#notifications') - notifications?.create({ - type: 'error', - title: 'Error', - message: err.message - }) - - return - } - - let bundleId = ini.get(src, 'meta', 'bundle_identifier') + let config = new Config(currentProject.id) + let bundleId = await config.get('meta', 'bundle_identifier') bundleId = bundleId.replace(/"/g, '') const { data: hasProject } = await app.db.projects.has(bundleId) @@ -54,11 +38,10 @@ export class DialogPublish extends TonicDialog { async publish (type, value) { const app = this.props.parent - const settings = app.state.settings const dataProject = await this.getProject() const opts = { - + // TODO(@heapwolf): probably chain with previousId } const subcluster = app.socket.subclusters.get(dataProject.subclusterId) @@ -193,7 +176,7 @@ export class DialogPublish extends TonicDialog { // Just publish the diff // try { - output = await exec(`git format-patch -1 HEAD --stdout`, { cwd }) + output = await exec('git format-patch -1 HEAD --stdout', { cwd }) } catch (err) { output.stderr = err.message } diff --git a/src/components/sprite.js b/src/components/sprite.js index 59a281b..7586d21 100644 --- a/src/components/sprite.js +++ b/src/components/sprite.js @@ -22,6 +22,22 @@ class AppSprite extends Tonic { c-4.8,0.7-7.8,3.9-7.8,9.6v35.8C17.5,94.6,20.5,97.7,26.8,97.7z M33.1,28.7c0-12.3,7.9-18.8,17-18.8s17,6.5,17,18.8V42h-34V28.7z"/> + + + + + + + + + + + + diff --git a/src/components/subscribe.js b/src/components/subscribe.js index 7ce76aa..f60240f 100644 --- a/src/components/subscribe.js +++ b/src/components/subscribe.js @@ -12,6 +12,10 @@ export class DialogSubscribe extends TonicDialog { await this.reRender() } + async click (e) { + // TODO(@heapwolf): create project entry, call initNetwork + } + async render () { return this.html`
@@ -19,16 +23,9 @@ export class DialogSubscribe extends TonicDialog {

- Enter the unique shared secret for the project you want to subscribe to. + Enter the unique link for the project you want to subscribe to.

- - - div, -section { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow: hidden; -} - -section { - top: 22px; -} - label.title { position: absolute; top: 0; @@ -162,12 +148,6 @@ label.title { white-space: nowrap; } -section.render { - padding: 10%; - text-align: center; - overflow: auto; -} - main-component { position: absolute; top: 38px; @@ -184,7 +164,7 @@ header { app-view > header { display: none; - grid-template-columns: 1fr 34px 250px 34px 1fr; + grid-template-columns: 1fr 280px 34px 34px 1fr; gap: 12px; grid-template-rows: auto; align-content: center; @@ -196,6 +176,22 @@ app-view > header { right: 0; } + +app-view > header .build-controls { + position: relative; +} + +app-view > header .build-controls tonic-button { + position: absolute; + top: 4px; + left: 12px; + z-index: 1; +} + +app-view > header .build-controls tonic-select select { + text-indent: 26px; +} + #split-main { top: 0px; border-top: 1px solid var(--tonic-border); diff --git a/src/css/preview.css b/src/css/preview.css deleted file mode 100644 index eb3d3de..0000000 --- a/src/css/preview.css +++ /dev/null @@ -1,53 +0,0 @@ -app-preview { - position: absolute; - top: 0; bottom: 0; left: 0; right: 0; - background: var(--tonic-background); -} - -app-preview .container { - position: absolute; - top: 0px; bottom: 0px; left: 0px; right: 0px; -} - -app-preview .device { - position: relative; - height: 100%; - border: 1px solid var(--tonic-border); - overflow: hidden; -} - -app-preview .device iframe { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - height: 100%; - width: 100%; -} - -/* Source: https://github.com/kylebshr/ScreenCorners */ - -app-preview .device[device="ios"] { - aspect-ratio: 9 / 19.5; - border-radius: 39px; - margin: 60px; -} - -app-preview .device[device="ipad"] { - aspect-ratio: 3 / 4; - border-radius: 18px; - margin: 60px; -} - -app-preview .device[device="darwin"] { - aspect-ratio: initial; - border-radius: 12px; - position: absolute; - top: 60px; - border-top: 1px solid var(--tonic-border); - left: 60px; - right: 60px; - bottom: 60px; - height: unset; -} diff --git a/src/css/view-home.css b/src/css/view-home.css new file mode 100644 index 0000000..c36b9e1 --- /dev/null +++ b/src/css/view-home.css @@ -0,0 +1,59 @@ +view-home { + display: none; + grid-template-rows: auto 1fr; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: -1; + opacity: 0; + background: var(--tonic-background); + padding: 6%; +} + +view-home.show { + display: grid; + z-index: 40; + opacity: 1; +} + +view-home h1 { + font-family: var(--tonic-body); + text-transform: uppercase; + font-size: 38px; + margin-top: 0; + font-weight: 100; +} + +view-home h1 b { + font-family: var(--tonic-header); + text-transform: uppercase; + font-size: 38px; + font-weight: 900; +} + +view-home section { + position: relative; +} + +view-home section.hero { + height: 80px; +} + +view-home tonic-tabs { + margin-left: -6px; +} + +view-home tonic-tab a { + padding: 6px 10px; + border-radius: 4px; + text-decoration: none; + text-transform: uppercase; + color: var(--tonic-primary); +} + +view-home tonic-tab a[aria-selected="true"] { + background: var(--tonic-accent); + color: white; +} diff --git a/src/css/image-preview.css b/src/css/view-image-preview.css similarity index 75% rename from src/css/image-preview.css rename to src/css/view-image-preview.css index 8f8d5eb..1c76c84 100644 --- a/src/css/image-preview.css +++ b/src/css/view-image-preview.css @@ -1,4 +1,4 @@ -app-image-preview { +view-image-preview { position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow: auto; @@ -15,12 +15,12 @@ app-image-preview { padding: 10%; } -app-image-preview.show { +view-image-preview.show { opacity: 1; z-index: 30; } -app-image-preview .top { +view-image-preview .top { display: grid; justify-content: center; align-items: center; @@ -28,10 +28,10 @@ app-image-preview .top { margin-bottom: 100px; } -app-image-preview .bottom { +view-image-preview .bottom { } -app-image-preview .bottom h2 { +view-image-preview .bottom h2 { border-bottom: 1px solid var(--tonic-info); font-size: 14px; text-transform: uppercase; @@ -39,7 +39,7 @@ app-image-preview .bottom h2 { color: var(--tonic-info); } -app-image-preview .bottom .icon-grid { +view-image-preview .bottom .icon-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; padding: 60px 0; @@ -48,33 +48,33 @@ app-image-preview .bottom .icon-grid { justify-content: center; } -app-image-preview tonic-button { +view-image-preview tonic-button { margin: auto; } -app-image-preview h1 { +view-image-preview h1 { color: var(--tonic-border); font-size: 28px; font-family: var(--tonic-body); } -app-image-preview img { +view-image-preview img { margin: auto; } -app-image-preview .size { +view-image-preview .size { display: grid; justify-content: center; align-items: center; } -app-image-preview .size label { +view-image-preview .size label { text-align: center; display: inline-block; margin-top: 10px; color: var(--tonic-primary); } -app-image-preview .size icon { +view-image-preview .size icon { cursor: pointer; } diff --git a/src/index.html b/src/index.html index 6527a7a..6b235c9 100644 --- a/src/index.html +++ b/src/index.html @@ -15,18 +15,20 @@ object-src 'none'; " > - - - - - + - - - - + + + + + + + + + + diff --git a/src/index.js b/src/index.js index c0392e0..4df0217 100644 --- a/src/index.js +++ b/src/index.js @@ -12,12 +12,14 @@ import components from '@socketsupply/components' import Database from './db/index.js' +import { ViewHome } from './views/home.js' +import { ViewImagePreview } from './views/image-preview.js' + import { AppTerminal } from './components/terminal.js' import { AppProject } from './components/project.js' import { AppProperties } from './components/properties.js' import { AppSprite } from './components/sprite.js' import { AppEditor } from './components/editor.js' -import { AppImagePreview } from './components/image-preview.js' import { DialogPublish } from './components/publish.js' import { DialogSubscribe } from './components/subscribe.js' @@ -53,7 +55,7 @@ class AppView extends Tonic { // if the user currently has the config file open in the editor... if (currentProject.label === 'settings.json' && currentProject.parent.id === 'root') { - const coEditor = document.querySelctor('app-editor') + const coEditor = document.querySelector('app-editor') try { coEditor.value = JSON.stringify(this.state.settings, null, 2) @@ -511,6 +513,7 @@ class AppView extends Tonic { ; File: + Save: s + CommandOrControl New Project: n + CommandOrControl Add Shared Project: G + CommandOrControl --- @@ -540,7 +543,8 @@ class AppView extends Tonic { ; Build & Run: - Evaluate Editor Source: r + CommandOrControl + Shift + Evaluate Source: r + CommandOrControl + Shift + Toggle Realtime Preview: k + CommandOrControl + Shift --- Android: s + CommandOrControl iOS: s + CommandOrControl @@ -679,18 +683,22 @@ class AppView extends Tonic {
- +
+ + + + + + + + + +
+ + - - - - - - - - - + @@ -712,7 +720,9 @@ class AppView extends Tonic { - + + + @@ -733,7 +743,7 @@ class AppView extends Tonic { @@ -745,7 +755,6 @@ class AppView extends Tonic { window.onload = () => { Tonic.add(AppEditor) - Tonic.add(AppImagePreview) Tonic.add(AppProperties) Tonic.add(AppProject) Tonic.add(AppSprite) @@ -753,4 +762,6 @@ window.onload = () => { Tonic.add(AppView) Tonic.add(DialogPublish) Tonic.add(DialogSubscribe) + Tonic.add(ViewHome) + Tonic.add(ViewImagePreview) } diff --git a/src/lib/config.js b/src/lib/config.js new file mode 100644 index 0000000..78588da --- /dev/null +++ b/src/lib/config.js @@ -0,0 +1,101 @@ +import fs from 'socket:fs' +import path from 'socket:path' + +class Config { + #src = '' + + constructor (src) { + if (src) this.#src = path.join(src, 'socket.ini') + } + + /* + * Update a INI file key's value by section + */ + async set (section, key, value) { + if (!this.#src) return '' + + const lines = src.split(/\r?\n/).map(s => s.trim()) + const exists = get(src, section, key).length + + let sectionMatch = false + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim() + + if (!sectionMatch && (line.startsWith('[') && line.endsWith(']'))) { + if (line.slice(1, -1) === section) { + sectionMatch = true + continue + } + } + + if (sectionMatch) { + if (line === '') continue + if (line[0] === ';' && !line.slice(1).includes(key)) { + continue + } + + if (!exists) { + value = `${key} = ${value}` + let deletes = 0 + if (line[0] === ';') deletes = 1 + lines.splice(i, deletes, value) + return lines.join('\n') + } + + const parts = line.split(/\s*=\s*/) + if (parts[0] === key) { + value = `${parts[0]} = ${value}` + lines[i] = value + } + } + } + + const result = lines.join('\n') + + try { + await fs.promises.writeFile(this.#src, result) + } catch { + return '' + } + } + + async get (section, key) { + if (!this.#src) return '' + + let src = '' + + try { + src = await fs.promises.readFile(this.#src, 'utf8') + } catch { + return '' + } + + const lines = src.split(/\r?\n/).map(s => s.trim()) + + let sectionMatch = false + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + + if (!sectionMatch && (line.startsWith('[') && line.endsWith(']'))) { + if (line.slice(1, -1) === section) { + sectionMatch = true + continue + } + } + + if (sectionMatch) { + if (line[0] === ';') continue + if (line.trim() === '') continue + const parts = line.split(/\s*=\s*/) + if (parts[0] === key) return parts[1] + } + } + + return '' + } +} + +export default Config +export { Config } diff --git a/src/lib/ini.js b/src/lib/ini.js deleted file mode 100644 index 29e486f..0000000 --- a/src/lib/ini.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Update a INI file key's value by section - */ -function set (src, section, key, value) { - const lines = src.split(/\r?\n/).map(s => s.trim()) - const exists = get(src, section, key).length - - let sectionMatch = false - - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim() - - if (!sectionMatch && (line.startsWith('[') && line.endsWith(']'))) { - if (line.slice(1, -1) === section) { - sectionMatch = true - continue - } - } - - if (sectionMatch) { - if (line === '') continue - if (line[0] === ';' && !line.slice(1).includes(key)) { - continue - } - - if (!exists) { - value = `${key} = ${value}` - let deletes = 0 - if (line[0] === ';') deletes = 1 - lines.splice(i, deletes, value) - return lines.join('\n') - } - - const parts = line.split(/\s*=\s*/) - if (parts[0] === key) { - value = `${parts[0]} = ${value}` - lines[i] = value - } - } - } - - return lines.join('\n') -} - -function get (src, section, key) { - const lines = src.split(/\r?\n/).map(s => s.trim()) - - let sectionMatch = false - - for (let i = 0; i < lines.length; i++) { - const line = lines[i] - - if (!sectionMatch && (line.startsWith('[') && line.endsWith(']'))) { - if (line.slice(1, -1) === section) { - sectionMatch = true - continue - } - } - - if (sectionMatch) { - if (line[0] === ';') continue - if (line.trim() === '') continue - const parts = line.split(/\s*=\s*/) - if (parts[0] === key) return parts[1] - } - } - - return '' -} - -export { get, set } diff --git a/src/preview.js b/src/preview.js index 39416eb..54fb0fc 100644 --- a/src/preview.js +++ b/src/preview.js @@ -1,3 +1,6 @@ +import { inspect } from 'socket:util' +import application from 'socket:application' + globalThis.RUNTIME_APPLICATION_ALLOW_MULTI_WINDOWS = true let currentWindow = null @@ -18,17 +21,13 @@ for (const method of consoleMethods) { } } -import application from 'socket:application' import process from 'socket:process' -import { inspect } from 'socket:util' const previewWindowTitleBar = 38 const previewWindowMargin = 12 const deviceWidth = (1179 / 4) - previewWindowMargin const deviceHeight = (2556 / 4) - previewWindowTitleBar -let timeout - const scaleToFit = e => { const windowWidth = window.innerWidth const windowHeight = window.innerHeight diff --git a/src/views/home.js b/src/views/home.js new file mode 100644 index 0000000..7940381 --- /dev/null +++ b/src/views/home.js @@ -0,0 +1,53 @@ +import fs from 'socket:fs' +import path from 'socket:path' + +import Tonic from '@socketsupply/tonic' + +class ViewHome extends Tonic { + show () { + this.classList.add('show') + } + + hide () { + this.classList.remove('show') + } + + async render () { + return this.html` +
+

Union App Studio

+
+
+ + What's New + Projects + Docs + + + + Content One + + + + Content Two + + + + Content Three + +
+ ` + } +} + +export default ViewHome +export { ViewHome } diff --git a/src/views/image-preview.js b/src/views/image-preview.js new file mode 100644 index 0000000..d2101a5 --- /dev/null +++ b/src/views/image-preview.js @@ -0,0 +1,122 @@ +import fs from 'socket:fs' +import path from 'socket:path' + +import Tonic from '@socketsupply/tonic' + +import Config from '../lib/config.js' + +class ViewImagePreview extends Tonic { + async click (e) { + const el = Tonic.match(e.target, '[data-event]') + if (!el) return + + const { event, value } = el.dataset + + const pickerOpts = { + types: [ + { + description: 'Images', + accept: { + 'image/*': ['.png'] + } + } + ], + excludeAcceptAllOption: true, + multiple: false + } + + if (event === 'size') { + const [fileHandle] = await window.showOpenFilePicker(pickerOpts) + + /* const kFileSystemHandleFullName = Object + .getOwnPropertySymbols(data) + .find(s => s.description === 'kFileSystemHandleFullName') + const pathToFile = fileHandle[kFileSystemHandleFullName] + */ + + const file = fileHandle?.getFile() + + if (file) { + const buf = await file.arrayBuffer() + + if (value === 'all') { + const imagePreview = this.querySelector('.image-preview') + const blob = new Blob([buf], { type: 'image/png' }) + const url = URL.createObjectURL(blob) + ;[...imagePreview.querySelectorAll('img')].forEach(img => (img.src = url)) + return + } + + const blob = await resizePNG(buf, parseInt(value)) + + el.src = URL.createObjectURL(blob) + } + } + } + + show () { + this.classList.add('show') + } + + hide () { + this.classList.remove('show') + } + + async load (projectNode) { + this.state.pathToFile = projectNode.id + this.reRender() + } + + async render () { + const app = this.props.parent + const currentProject = app.state.currentProject + const config = new Config(currentProject?.id) + + if (!currentProject) return this.html`` + + const templateSizes = async platform => { + const spec = await config.get(platform, 'icon_sizes') + + return spec.replace(/"/g, '') + .split(' ') + .map(pair => { + let { 0: size, 1: scale } = pair.split('@') + scale = parseInt(scale) + + const src = this.state.pathToFile.replace(path.HOME, '/user/home') + const scaled = size * scale + + return this.html` +
+ + +
+ ` + }) + } + + return this.html` +
+

Icon Preview

+ Update +
+ +
+

MacOS

+
${await templateSizes('mac')}
+ +

iOS

+
${await templateSizes('ios')}
+ +

Linux

+
${await templateSizes('linux')}
+ +

Windows

+
${await templateSizes('win')}
+
+ ` + } +} + +export { ViewImagePreview } +export default ViewImagePreview diff --git a/src/worker.js b/src/worker.js index 126b757..6e1ac1f 100644 --- a/src/worker.js +++ b/src/worker.js @@ -1,6 +1,5 @@ import path from 'socket:path' import { lookup } from 'socket:mime' -import fs from 'socket:fs' import application from 'socket:application' const mount = '/user/home' @@ -8,7 +7,7 @@ const navigatorPath = path.DATA.replace(path.HOME, mount) export default async function (req, env, ctx) { const url = new URL(req.url) - const pattern = new URLPattern({ pathname: '/preview/*' }) + const pattern = new globalThis.URLPattern({ pathname: '/preview/*' }) const route = pattern.exec(url) if (!route) return @@ -17,7 +16,6 @@ export default async function (req, env, ctx) { const params = url.searchParams const res = await fetch(p) const data = await res.text() - const id = ctx.event.clientId const windows = await application.getWindows() const w = Object.values(windows)[0]