compose = {
class ComposeView {
constructor(name, view) {
this.name = name;
this.view = view;
}
}
class ComposeDefault {
constructor(view) {
this.view = view;
}
}
let compose_template = (template_fn, template_strings, ...input_cells) => {
let has_default = input_cells.some(cell => cell instanceof ComposeDefault);
const runtime = new ObservableRuntime({});
const main = runtime.module();
let cells_with_index = Object.entries(input_cells);
for (let [index, compose_view] of cells_with_index) {
let is_compose_value =
compose_view instanceof ComposeView ||
compose_view instanceof ComposeDefault;
let cell = is_compose_value ? compose_view.view : compose_view;
if (is_compose_value) {
precondition(
!(cell instanceof DocumentFragment),
`For now I do not allow DocumentFragment's in compose(...), as they would normally not be rendered in a notebook cell either`
);
}
main.define(`viewof_${index}`, cell);
}
if (has_default) {
precondition(
!input_cells.some(cell => cell instanceof ComposeView),
`When using a ComposeDefault, there are not ComposeValues allowed`
);
precondition(
input_cells.filter(cell => cell instanceof ComposeDefault).length === 1,
`You can't have multiple ComposeDefault's, that doesn't make sense!`
);
let [default_cell_index, _] = cells_with_index.find(
([_, cell]) => cell instanceof ComposeDefault
);
main.define('value', [`viewof_${default_cell_index}`], view => {
return Generators.input(view);
});
} else {
let compose_cells_with_index = cells_with_index.filter(
([_, view]) => view instanceof ComposeView
);
for (let [index, compose_value] of compose_cells_with_index) {
let name = compose_value.name;
main.define(`valueof_${index}`, [`viewof_${index}`], async function*(view) {
for await (let value of Generators.input(view)) {
yield [name, value];
}
});
}
main.define(
'value',
compose_cells_with_index.map(([index, _]) => `valueof_${index}`),
(...valueofs) => {
let value_object = {};
for (let [name, value] of valueofs) {
value_object[name] = value;
}
return value_object;
}
);
}
main.define(
'viewofs',
cells_with_index.map(([index, _]) => `viewof_${index}`),
(...viewofs) => viewofs
);
main.define('everything', ['viewofs', 'value'], function(viewofs, value) {
let previous = this || { viewofs: null, value: null };
return {
viewofs,
value,
viewofs_changed: viewofs !== previous.viewofs,
value_changed: value !== previous.value
};
});
return disposable(
run(async function*() {
let container_element = null;
let generator = generator_from_observable_variable(main, 'everything');
for await (let {
value,
viewofs,
value_changed,
viewofs_changed
} of generator) {
if (viewofs_changed) {
container_element = template_fn(template_strings, ...viewofs);
}
if (value_changed) {
container_element.value = value;
container_element.dispatchEvent(new CustomEvent('input'));
}
if (viewofs_changed) {
yield container_element;
}
}
}),
() => {
runtime.dispose();
}
);
};
let compose = template_fn => {
return (...args) => compose_template(template_fn, ...args);
};
compose.default = value => new ComposeDefault(value);
compose.viewof = definition_object => {
let [name, ...other_keys] = Object.keys(definition_object);
precondition(
other_keys.length === 0,
'compose({ ... }) takes an object with only one key'
);
precondition(
name != null,
'compose({ ... }) takes an object with at least one key'
);
let value = definition_object[name];
precondition(value != null, `compose({ ... }) can not have null as value`);
return new ComposeView(name, value);
};
compose.html = compose(html);
compose.md = compose(md);
return compose;
}