Skip to content

Commit

Permalink
Add pilot implementation of view presets
Browse files Browse the repository at this point in the history
  • Loading branch information
lahmatiy committed Jan 30, 2019
1 parent 6443bdd commit c8d70bf
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 65 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
- Added `Emitter` as a base class for `Widget`/`App`, `PageRenderer` and `ViewRenderer` classes
- Removed `Widget#definePage()` method, use `Widget#page.define()` instead
- Extracted query and view editors from `report` page to a separate module, as `Widget#view.QueryEditor` and `Widget#view.ViewEditor` classes
- Added `content` option for `auto-link` view config
- Added `content` option in `auto-link` view config
- Changed `source` view:
- Removed `refs` preprocessing logic, now it takes array of `{ type: "error" | "ignore" | "link", range: [number, number], href?: string }` objects
- Disabled syntax highlighting when source size over 100k to avoid page freezing
- Added a pilot implementation of view presets. Preset's API available via `Widget#preset` and very common with page and view renderers. Preset can be used in views as preset name with `preset/` prefix (i.e. `{ view: 'preset/name', ... }`)

## 1.0.0-beta.7 (23-01-2019)

Expand Down
31 changes: 31 additions & 0 deletions client/core/dict.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* eslint-env browser */

import Emitter from './emitter.js';

const entries = new WeakMap();

export default class Dictionary extends Emitter {
constructor() {
super();

entries.set(this, Object.create(null));
}

define(key, value) {
entries.get(this)[key] = value;

this.emit('define', key, value);
}

isDefined(key) {
return key in entries.get(this);
}

get(key) {
return entries.get(this)[key];
}

get names() {
return Object.keys(entries.get(this)).sort();
}
}
39 changes: 12 additions & 27 deletions client/core/page.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/* eslint-env browser */

import Emitter from './emitter.js';
import Dict from './dict.js';

const pages = new WeakMap();
const BUILDIN_NOT_FOUND = {
name: 'not-found',
render: (el, { name }) => {
Expand All @@ -11,40 +10,26 @@ const BUILDIN_NOT_FOUND = {
}
};

export default class PageRenderer extends Emitter {
export default class PageRenderer extends Dict {
constructor(view) {
super();

this.view = view;
this.lastPage = null;
pages.set(this, Object.create(null));
this.lastPageId = null;
}

define(name, render, options) {
pages.get(this)[name] = Object.freeze({
super.define(name, Object.freeze({
name,
render: typeof render === 'function'
? render
? render.bind(this.view)
: (el, data, context) => this.view.render(el, render, data, context),
options: Object.freeze(Object.assign({}, options))
});

this.emit('define', name);
}

isDefined(name) {
return name in pages.get(this);
}

get(name) {
return pages.get(this)[name];
}

get names() {
return Object.keys(pages.get(this)).sort();
}));
}

render(oldPageEl, name, data, context) {
render(prevPageEl, name, data, context) {
const renderStartTime = Date.now();
let page = this.get(name);
let rendered;
Expand All @@ -58,12 +43,12 @@ export default class PageRenderer extends Emitter {
const pageChanged = this.lastPage !== name;
const pageRef = context && context.id;
const pageRefChanged = this.lastPageId !== pageRef;
const newPageEl = reuseEl && !pageChanged ? oldPageEl : document.createElement('article');
const parentEl = oldPageEl.parentNode;
const newPageEl = reuseEl && !pageChanged ? prevPageEl : document.createElement('article');
const parentEl = prevPageEl.parentNode;

this.lastPage = name;
this.lastPageId = pageRef;
newPageEl.id = oldPageEl.id;
newPageEl.id = prevPageEl.id;
newPageEl.classList.add('page', 'page-' + name);

if (pageChanged && typeof init === 'function') {
Expand All @@ -78,8 +63,8 @@ export default class PageRenderer extends Emitter {
console.error(e);
}

if (newPageEl !== oldPageEl) {
parentEl.replaceChild(newPageEl, oldPageEl);
if (newPageEl !== prevPageEl) {
parentEl.replaceChild(newPageEl, prevPageEl);
}

if (pageChanged || pageRefChanged || !keepScrollOffset) {
Expand Down
39 changes: 39 additions & 0 deletions client/core/preset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* eslint-env browser */

import Dict from './dict.js';

export default class PresetRenderer extends Dict {
constructor(view) {
super();

this.view = view;
}

define(name, config) {
// FIXME: add check that config is serializable object
config = JSON.parse(JSON.stringify(config));

super.define(name, Object.freeze({
name,
render: (el, _, data, context) => this.view.render(el, config, data, context),
config
}));
}

render(container, name, data, context) {
let preset = this.get(name);

if (!preset) {
const errorMsg = 'Preset `' + name + '` is not found';
console.error(errorMsg, name);

const el = container.appendChild(document.createElement('div'));
el.style.cssText = 'color:#a00;border:1px dashed #a00;font-size:12px;padding:4px';
el.innerText = errorMsg;

return Promise.resolve();
}

return preset.render(container, null, data, context);
}
}
60 changes: 33 additions & 27 deletions client/core/view.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/* eslint-env browser */

import Emitter from './emitter.js';
import Dict from './dict.js';

const views = new WeakMap();
const STUB_OBJECT = Object.freeze({});
const BUILDIN_FALLBACK = {
name: 'fallback',
Expand Down Expand Up @@ -36,36 +35,21 @@ function renderDom(renderer, placeholder, config, data, context) {
});
}

export default class ViewRenderer extends Emitter {
export default class ViewRenderer extends Dict {
constructor(host) {
super();

this.host = host;
views.set(this, Object.create(null));
}

define(name, customRender, options) {
views.get(this)[name] = Object.freeze({
define(name, render, options) {
super.define(name, Object.freeze({
name,
render: typeof customRender === 'function'
? customRender.bind(this)
: (el, config, data, context) => this.render(el, customRender, data, context),
render: typeof render === 'function'
? render.bind(this)
: (el, _, data, context) => this.render(el, render, data, context),
options: Object.freeze(Object.assign({}, options))
});

this.emit('define', name);
}

isDefined(name) {
return name in views.get(this);
}

get(name) {
return views.get(this)[name];
}

get names() {
return Object.keys(views.get(this)).sort();
}));
}

render(container, config, data, context) {
Expand Down Expand Up @@ -96,9 +80,31 @@ export default class ViewRenderer extends Emitter {
};
}

let renderer = typeof config.view === 'function'
? { render: config.view, name: false, options: STUB_OBJECT }
: this.get(config.view);
let renderer = null;

switch (typeof config.view) {
case 'function':
renderer = { render: config.view, name: false, options: STUB_OBJECT };
break;

case 'string':
if (config.view.startsWith('preset/')) {
const presetName = config.view.substr(7);

if (this.host.preset.isDefined(presetName)) {
renderer = {
render: this.host.preset.get(presetName).render,
name: false,
options: { tag: false }
};
} else {
return this.host.preset.render(container, presetName, data, context);
}
} else {
renderer = this.get(config.view);
}
break;
}

if (!renderer) {
const errorMsg = typeof config.view === 'string'
Expand Down
42 changes: 32 additions & 10 deletions client/pages/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,19 @@ import copyText from '../core/utils/copy-text.js';

const defaultViewSource = '{\n view: \'struct\',\n expanded: 1\n}';
const defaultViewPresets = [
{ name: 'Table', content: '{\n view: \'table\'\n}' },
{ name: 'Autolink list', content: '{\n view: \'ol\',\n item: \'auto-link\'\n}' }
{
name: 'Table',
content: JSON.stringify({
view: 'table'
}, null, 4)
},
{
name: 'Auto-link list',
content: JSON.stringify({
view: 'ol',
item: 'auto-link'
}, null, 4)
}
];

function valueDescriptor(value) {
Expand Down Expand Up @@ -114,6 +125,15 @@ export default function(discovery) {
}, replace);
}

function createPresetTab(name, content) {
return createElement('div', {
class: 'tab',
onclick: () => updateParams({
view: content // JSON.stringify(content, null, 4)
})
}, name || 'Untitled preset');
}

//
// Header
//
Expand All @@ -128,7 +148,7 @@ export default function(discovery) {
placeholder: 'Untitled report',
oninput: (e) => updateParams({
title: e.target.value
}),
}, true),
onkeypress: (e) => {
if (event.charCode === 13 || event.keyCode === 13) {
e.target.blur();
Expand Down Expand Up @@ -241,6 +261,7 @@ export default function(discovery) {
//
let viewSetupEl;
let availableViewListEl;
// let availablePresetListEl;
let viewModeTabsEls;
let viewLiveEditEl;
const viewEditor = new discovery.view.ViewEditor(discovery).on('change', value =>
Expand All @@ -256,13 +277,8 @@ export default function(discovery) {
}, true)
}, viewMode)
)),
createElement('div', 'tabs presets', viewPresets.map(({ name, content }) =>
createElement('div', {
class: 'tab',
onclick: () => updateParams({
view: content
})
}, name || 'Untitled preset')
/* availablePresetListEl = */createElement('div', 'tabs presets', viewPresets.map(({ name, content }) =>
createPresetTab(name, content)
)),
viewSetupEl = createElement('div', {
class: 'view-editor',
Expand Down Expand Up @@ -309,6 +325,12 @@ export default function(discovery) {
updateAvailableViewList();
discovery.view.on('define', updateAvailableViewList);

// sync view list
// const updateAvailablePresetList = (name, preset) =>
// availablePresetListEl.appendChild(createPresetTab(name, preset.config));

// discovery.preset.on('define', updateAvailablePresetList);

//
// Report form & content
//
Expand Down
2 changes: 2 additions & 0 deletions client/widget/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import Emitter from '../core/emitter.js';
import ViewRenderer from '../core/view.js';
import PresetRenderer from '../core/preset.js';
import PageRenderer from '../core/page.js';
import * as views from '../views/index.js';
import * as pages from '../pages/index.js';
Expand Down Expand Up @@ -115,6 +116,7 @@ export default class Widget extends Emitter {

this.options = options || {};
this.view = new ViewRenderer(this);
this.preset = new PresetRenderer(this.view);
this.page = new PageRenderer(this.view);
this.page.on('define', name => this.addValueLinkResolver(extractValueLinkResolver(this, name)));
this.entityResolvers = [];
Expand Down

0 comments on commit c8d70bf

Please sign in to comment.