UNPKG

abundance

Version:
454 lines (421 loc) 13.1 kB
const Finder = require('tre-finder') const PropertySheet = require('tre-property-sheet') const StylePanel = require('tre-style-panel') const Shell = require('tre-editor-shell') const WatchMerged = require('tre-prototypes') const {makePane, makeDivider, makeSplitPane} = require('tre-split-pane') const h = require('mutant/html-element') const Value = require('mutant/value') const MutantArray = require('mutant/array') const watch = require('mutant/watch') const computed = require('mutant/computed') const setStyle = require('module-styles')('abundance') const RenderFonts = require('./render-fonts') const MultiEditor = require('tre-multi-editor') const Webapps = require('tre-webapps') const Icons = require('./icons-by-name') const RoleSelector = require('./role-selector') const LanguageSwitch = require('./language-switch') const IdleControl = require('./idle-control') const UAParser = require('ua-parser-js') const isElectron = require('is-electron') const browserVersion = UAParser().browser module.exports = function(ssb, config, opts) { const {importer} = opts const _render = opts.render || (function() {}) styles() const renderFonts = RenderFonts(ssb, config) document.body.appendChild(renderFonts()) const watchMerged = WatchMerged(ssb) const primarySelection = Value() const secondarySelections = Value([]) const mergedKvObs = computed(primarySelection, kv => { const c = content(kv) if (!c) return return watchMerged(c.revisionRoot || kv.key, { allowAllAuthors: true, suppressIntermediate: true }) }) function isIgnored(kv) { if (!kv) return false return kv.value.content.branch == config.tre.branches.trash } const renderStylePanel = StylePanel(ssb, {isIgnored}) const iconByName = Icons(ssb, config) const renderFinder = Finder(ssb, { idle: true, maxTime: 200, importer, skipFirstLevel: true, primarySelection, secondarySelections, prolog: (kv, ctx) => { if (!kv) return [] const meta = kv.meta return [ h('div.indicators', [ meta && meta["prototype-chain"] ? iconByName('gift', {title: 'Node has a prototype'}) : [], meta && meta.forked ? iconByName('git branch', {title: 'Node has multiple heads'}) : [], meta && meta.incomplete ? iconByName('alert', {title: 'Node has incomplete history'}) : [], meta && meta.change_requests ? iconByName('notifications', {title: 'Change requests'}) : [] ]) ] }, details: (kv, ctx) => { if (!kv) return [] return [ h('div.actions', [ //iconByName('add circle outline', {title: 'Add child node'}), iconByName('albums', { title: 'Clone', action: (e, ctx) => { e.preventDefault() const content = Object.assign({}, unmergeKv(kv).value.content) delete content.revisionRoot delete content.revisionBranch content.name = `Copy of ${content.name}` console.log('Cloning', content) ssb.publish(content, (err, msg) => { if (err) return console.error(err.message) console.log('cloned. New message:', msg) }) } }), iconByName('trash', { title: 'Move to trash', action: (e, ctx) => { e.preventDefault() ssb.revisions.patch(kv.key, content => { content.branch = config.tre.branches.trash }, (err, msg) => { if (err) return console.error(err.message) console.log('moved to trash', msg) }) } }), //iconByName('more', {title: 'more ...'}) ]) ] } }) const renderRoleSelector = RoleSelector(ssb) const renderMultiEditor = MultiEditor(ssb, opts) const renderLanguageSwitch = LanguageSwitch(ssb, config) const {languagesObs, currentLanguageObs} = renderLanguageSwitch const renderIdleControl = IdleControl({paused: true, seconds: 30}) const where = Value('editor') const modes = [ // splitpane? right? editor? fullscreen? {name: 'edit', ui: true, ri: true, ed: true, fs: false }, {name: 'sidebar', ui: true, ri: true, ed: false, fs: true }, {name: 'fullscreen', ui: false, ri: false, ed: false, fs: true }, {name: 'overlay', ui: true, ri: false, ed: false, fs: true } ] console.warn('is electron', isElectron()) const mode = Value(isElectron() ? 0 : 2) console.warn('Initial view mode:', modes[mode()].name) window.addEventListener('keydown', e =>{ if (e.key === 'Tab' && e.shiftKey) { mode.set( (mode() + 1) % modes.length) console.log('new view mode:', modes[mode()].name) e.preventDefault() } }) const idleControls = renderIdleControl() const {idleTimer} = idleControls window.addEventListener('click', e =>{ idleTimer.reset() }) const isKiosk = computed([mode, primarySelection], (mode, kvm) =>{ const isStation = kvm && kvm.value && kvm.value.content && kvm.value.content.type == 'station' return isStation && mode == 2 }) const abort = watch(isKiosk, kiosk => { if (kiosk) { console.log('KIOSK mode') if (idleControls.pausedObs()) { idleControls.pausedObs.set(false) idleTimer.resume() } } }) const canAutoUpdate = computed([isKiosk, idleTimer.isIdleObs], (kiosk, idle) =>{ return kiosk && idle }) const renderWebapp = Webapps(ssb, { canAutoUpdate }) const commonContext = { languagesObs, currentLanguageObs, idleTimer, primarySelection, secondarySelections } function render(kv, ctx) { return _render(kv, Object.assign({}, commonContext, ctx)) } function renderStage() { console.warn('render stage') return h('.abundance-stage', {}, [ computed(mergedKvObs, kv => { if (!kv) return [] return render(kv, { where: 'stage' }) }) ]) } function display(obs) { return { display: computed(obs, s => s ? 'block' : 'none') } } function renderTopBar() { const address = Value() const bootMsg = Value() const bootRev = config.bootMsgRevision console.warn('webapp version:', bootRev) ssb.getAddress((err, addr)=>{ if (err) { console.error('error getting ssb address') address.set(err) } else { console.warn('get address success') const [schema, ip, port, id] = addr.split(':') const a = {schema, ip, port, id} console.warn('set address to', JSON.stringify(a)) address.set(a) } }) if (bootRev) ssb.get(bootRev, (err, value) => { if (err) return console.error(err.message) bootMsg.set({key: bootRev, value}) }) return h('.abundance-topbar', [ computed(bootMsg, kv => { if (!bootRev) return h('div.dev', 'dev version') if (!kv) return return renderWebapp(kv, {where: 'status'}) }), h('.browser-version', [ h('span.name', browserVersion.name), h('span.version', browserVersion.version), ]), renderRoleSelector(mode, primarySelection), renderLanguageSwitch(), computed(currentLanguageObs, l => { return h(`span.emoji.emoji-${l}`) }), idleControls, h('.sbot-address', [computed(address, address => { console.warn('address computed input', address) if (!address) return h('span', 'getting address ...') if (address.message) return h('span.error', address.message) const {schema, ip, port, id} = address return h(`.${schema}`, [ h('.ip', ip), h('.port', port), h('.id', id), ]) })]) ]) } const widgets = MutantArray() function addWidget(name, factory) { widgets.push({name, factory}) } addWidget('Stylesheets', renderStylePanel ) let finder function renderSidebar() { return h('.abundance-sidebar', { }, [ makeSplitPane({horiz: false}, [ makePane('50%', [ finder || (finder = renderFinder(config.tre.branches.root, {path: []})) ]), makeDivider(), makePane('50%', [ h('.abundance-widgets', computed(widgets, widgets => { return widgets.map( ({name, factory}) => { return h('details', [ h('summary', name), factory() ]) }) })) ]) ]) ]) } function whenVisible(aspect, a, b) { return computed(mode, mode => { console.warn('when visible', aspect, modes[mode][aspect]) return modes[mode][aspect] ? a() : b() }) } const topBar = renderTopBar() const ret = h('.abundance', { hooks: [el => abort], classList: computed(mode, mode => `viewmode-${[modes[mode].name]}`) }, [ whenVisible('ri', () => [], ()=> whenVisible('fs', ()=>[ renderStage(), h('div', { style: { display: 'none' } }, [ renderStylePanel()]) ], ()=>[])), whenVisible('ui', ()=>[ h('.abundance-ui', [ makeSplitPane({horiz: false}, [ makePane('4em', [topBar]), makeDivider(), makeSplitPane({horiz: true}, [ makePane('25%', [renderSidebar()]), makeDivider(), makePane('70%',/* { style: {opacity: whenVisible('ri', 1, 0)} },*/ [ whenVisible('fs', ()=>renderStage(), ()=>[]), h('div.abundance-editor', { style: {display: whenVisible('ed', ()=>'block', ()=>'none')} }, computed(renderFinder.primarySelectionObs, kv => { if (!kv) return [] console.warn('renderMultiEditor') return renderMultiEditor(kv, Object.assign({}, { render, }, commonContext)) })) ]) ]) ]) ]) ], ()=>[]) ]) ret.addWidget = addWidget return ret } function content(kv) { return kv && kv.value && kv.value.content } function unmergeKv(kv) { // if the message has prototypes and they were merged into this message value, // return the unmerged/original value return kv && kv.meta && kv.meta['prototype-chain'] && kv.meta['prototype-chain'][0] || kv } function revisionRoot(kv) { return kv && kv.value.content && kv.value.content.revisionRoot || kv && kv.key } function styles() { setStyle(` body, html, .abundance, .abundance-ui, .abundance-stage, .tre-multi-editor { height: 100%; margin: 0; padding: 0; } body { --tre-selection-color: green; --tre-secondary-selection-color: #6f8f5f; font-family: sans-serif; } .abundance { position: relative; } .abundance-sidebar { height: 100%; } .tre-style-panel { height: 100%; } .abundance.viewmode-overlay > .abundance-ui { opacity: 0.7; position: absolute; z-index: 2; top: 0; } .abundance.viewmode-overlay > .abundance-ui > .horizontal-split-pane > .pane:nth-child(3) { opacity: 0; } h1 { font-size: 18px; } .pane { background: #eee; } .tre-finder .summary select { font-size: 9pt; background: transparent; border: none; width: 50px; } .tre-finder summary { white-space: nowrap; } .tre-finder summary:focus { outline: 1px solid rgba(255,255,255,0.1); } .tre-property-sheet { font-size: 9pt; background: #4a4a4b; color: #b6b6b6; height: 100%; } .tre-property-sheet summary { font-weight: bold; text-shadow: 0 0 4px black; margin-top: .3em; padding-top: .4em; background: #555454; border-top: 1px solid #807d7d; margin-bottom: .1em; } .tre-property-sheet input { background: #D0D052; border: none; margin-left: .5em; } .tre-property-sheet .inherited input { background: #656464; } .tre-property-sheet .new input { background: white; } .tre-property-sheet details > div { padding-left: 1em; } .tre-property-sheet [data-schema-type="number"] input { width: 4em; } .property[data-schema-type=string] { grid-column: span 3; } .tre-property-sheet .properties { width: 100%; display: grid; grid-template-columns: repeat(auto-fill, 5em); } .tre-property-sheet details { grid-column: 1/-1; } .tre-folders { background-color: #777; } .tre-folders .tile { border: 1px solid #444; background: #666; } .tre-folders .tile > .name { font-size: 9pt; background: #444; color: #aaa; } .tile>.tre-image-thumbnail { max-width: 100%; max-height: 100%; width: auto; height: auto; } `) }