Skip to content

Commit d8343b6

Browse files
committed
refactor: change lazy-rendering API
1 parent 627dd89 commit d8343b6

31 files changed

+384
-330
lines changed

.changeset/silver-mice-dream.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hydrofoil/shaperone-core": minor
3+
---
4+
5+
Change how lazy components are implemented

demos/lit-html/src/configure.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from '@hydrofoil/shaperone-wc'
1+
import type { Component } from '@hydrofoil/shaperone-core'
22
import * as nativeComponents from '@hydrofoil/shaperone-wc/NativeComponents'
33
import * as mwcComponents from '@hydrofoil/shaperone-wc-material/components'
44
import { component, matcher, metadata } from '@hydrofoil/shaperone-playground-examples/LanguageMultiSelect'

packages/core/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import RdfResource from '@tpluscode/rdfine/RdfResource'
33
import { GraphPointer } from 'clownface'
44

55
export type FocusNode = GraphPointer<BlankNode | NamedNode>
6-
export type { Component, SingleEditorComponent, MultiEditorComponent } from './models/components'
6+
export type { Component, SingleEditorComponent, MultiEditorComponent, RenderSingleEditor, RenderMultiEditor, Lazy } from './models/components'
77
export type { SingleEditor, MultiEditor } from './models/editors'
88

99
export async function loadMixins(): Promise<void> {

packages/core/models/components/index.ts

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,46 +22,64 @@ export interface MultiEditorActions {
2222
focusOnObjectNode(): void
2323
}
2424

25-
export interface Component<TRenderResult> {
25+
export interface Component {
2626
editor: NamedNode
27-
render(...args: unknown[]): TRenderResult
28-
loadDependencies?(): Array<Promise<unknown>>
2927
}
3028

31-
export interface ComponentState<TRenderResult> extends Component<TRenderResult> {
32-
loaded: boolean
29+
export interface RenderFunc<Params, Actions, TRenderResult> {
30+
(params: Params, actions: Actions): TRenderResult
31+
}
32+
33+
export type RenderSingleEditor<TRenderResult> = RenderFunc<SingleEditorRenderParams, SingleEditorActions, TRenderResult>
34+
export type RenderMultiEditor<TRenderResult> = RenderFunc<MultiEditorRenderParams, MultiEditorActions, TRenderResult>
35+
36+
export interface ComponentState<TRenderResult> extends Component {
37+
render?: RenderFunc<any, any, TRenderResult>
38+
lazyRender?(): Promise<RenderFunc<any, any, TRenderResult>>
3339
loading: boolean
40+
loadingFailed?: {
41+
reason: string
42+
}
3443
}
3544

36-
export interface SingleEditorComponent<TRenderResult> extends Component<TRenderResult> {
37-
render(params: SingleEditorRenderParams, actions: SingleEditorActions): TRenderResult
45+
interface ComponentRender<Params, Actions, TRenderResult> {
46+
render: RenderFunc<Params, Actions, TRenderResult>
3847
}
3948

40-
export interface MultiEditorComponent<TRenderResult> extends Component<TRenderResult> {
41-
render(params: MultiEditorRenderParams, actions: MultiEditorActions): TRenderResult
49+
export type SingleEditorComponent<TRenderResult> = Component & ComponentRender<SingleEditorRenderParams, SingleEditorActions, TRenderResult>
50+
export type MultiEditorComponent<TRenderResult> = Component & ComponentRender<MultiEditorRenderParams, MultiEditorActions, TRenderResult>
51+
52+
export type Lazy<T extends ComponentRender<any, any, any>> = Omit<T, 'render'> & {
53+
lazyRender() : Promise<T['render']>
4254
}
4355

4456
export type ComponentsState<TRenderResult = any> = Record<string, ComponentState<TRenderResult>>
4557

4658
export const createComponentsModel = <TRenderResult>() => createModel({
4759
state: <ComponentsState<TRenderResult>>{},
48-
reducers: reducers<TRenderResult>(),
60+
reducers: reducers(),
4961
effects(store) {
5062
const dispatch = store.getDispatch()
5163

5264
return {
5365
async load(editor: NamedNode) {
5466
const state = store.getState()
5567

56-
const component = state.components[editor.value]
57-
if (component.loading || component.loaded) return
58-
59-
if (component.loadDependencies) {
60-
dispatch.components.loading(editor)
61-
await Promise.all(component.loadDependencies())
68+
const component: ComponentState<TRenderResult> = state.components[editor.value]
69+
if (!component.lazyRender) {
70+
dispatch.components.loadFailed({ editor, reason: 'lazyRender not implemented' })
71+
return
6272
}
73+
if (component.loading) return
6374

64-
dispatch.components.loaded(editor)
75+
try {
76+
dispatch.components.loaded({
77+
editor,
78+
render: await component.lazyRender(),
79+
})
80+
} catch (e) {
81+
dispatch.components.loadFailed({ editor, reason: e.message })
82+
}
6583
},
6684
}
6785
},

packages/core/models/components/reducers.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import produce from 'immer'
22
import { NamedNode } from 'rdf-js'
3-
import type { Component, ComponentsState } from './index'
3+
import type { Component, ComponentsState, RenderFunc } from './index'
44

5-
export default function <TRenderResult> () {
5+
export default function () {
66
return {
77
loading(components: ComponentsState, editor: NamedNode): ComponentsState {
88
return produce(components, (draft) => {
9-
draft[editor.value].loaded = false
109
draft[editor.value].loading = true
1110
})
1211
},
13-
loaded(components: ComponentsState, editor: NamedNode): ComponentsState {
12+
loadingFailed(components: ComponentsState, { editor, reason }: { editor: NamedNode; reason: string }): ComponentsState {
13+
return produce(components, (draft) => {
14+
draft[editor.value].loadingFailed = {
15+
reason,
16+
}
17+
})
18+
},
19+
loaded(components: ComponentsState, { editor, render } : {editor: NamedNode; render: RenderFunc<any, any, any>}): ComponentsState {
1420
return produce(components, (draft) => {
15-
draft[editor.value].loaded = true
1621
draft[editor.value].loading = false
22+
draft[editor.value].render = render
23+
draft[editor.value].loadingFailed = undefined
1724
})
1825
},
1926
removeComponents(components: ComponentsState, toRemove: NamedNode[]) {
@@ -23,16 +30,15 @@ export default function <TRenderResult> () {
2330
}
2431
})
2532
},
26-
pushComponents(components: ComponentsState, toAdd: Record<string, Component<TRenderResult>> | Component<TRenderResult>[]): ComponentsState {
33+
pushComponents(components: ComponentsState, toAdd: Record<string, Component> | Component[]): ComponentsState {
2734
return produce(components, (newComponents) => {
2835
const addedArray = Array.isArray(toAdd) ? toAdd : Object.values(toAdd)
2936

3037
for (const component of addedArray) {
31-
if (!components[component.editor.value] || components[component.editor.value].render !== component.render) {
38+
if (!components[component.editor.value]) {
3239
newComponents[component.editor.value] = {
3340
...component,
3441
loading: false,
35-
loaded: !component.loadDependencies,
3642
}
3743
}
3844
}

packages/wc-material/components.ts

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,52 @@
1-
export { textField } from './components/textField'
2-
export { urlEditor } from './components/urlEditor'
3-
export { textArea } from './components/textArea'
4-
export { enumSelect } from './components/enumSelect'
5-
export { datePicker, dateTimePicker } from './components/date'
1+
import { Lazy, SingleEditorComponent } from '@hydrofoil/shaperone-wc'
2+
import { dash } from '@tpluscode/rdf-ns-builders'
3+
import { namedNode } from '@rdf-esm/data-model'
4+
5+
export const textField: Lazy<SingleEditorComponent> = {
6+
editor: dash.TextFieldEditor,
7+
async lazyRender() {
8+
const { createTextField } = await import('./components/textField')
9+
return createTextField()
10+
},
11+
}
12+
13+
export const urlEditor: Lazy<SingleEditorComponent> = {
14+
editor: dash.URIEditor,
15+
async lazyRender() {
16+
const { createTextField } = await import('./components/textField')
17+
return createTextField({
18+
type: 'url',
19+
createTerm: value => namedNode(value),
20+
})
21+
},
22+
}
23+
24+
export const textArea: Lazy<SingleEditorComponent> = {
25+
editor: dash.TextAreaEditor,
26+
lazyRender() {
27+
return import('./components/textArea').then(m => m.textArea)
28+
},
29+
}
30+
31+
export const enumSelect: Lazy<SingleEditorComponent> = {
32+
editor: dash.EnumSelectEditor,
33+
lazyRender() {
34+
return import('./components/enumSelect').then(m => m.enumSelect)
35+
},
36+
}
37+
38+
export const datePicker: Lazy<SingleEditorComponent> = {
39+
editor: dash.DatePickerEditor,
40+
async lazyRender() {
41+
const { createTextField } = await import('./components/textField')
42+
return createTextField({ type: 'date' })
43+
},
44+
}
45+
46+
export const dateTimePicker: Lazy<SingleEditorComponent> = {
47+
editor: dash.DateTimePickerEditor,
48+
async lazyRender() {
49+
const { createTextField } = await import('./components/textField')
50+
return createTextField({ type: 'datetime-local' })
51+
},
52+
}

packages/wc-material/components/date.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
1-
import type { SingleEditorComponent } from '@hydrofoil/shaperone-wc'
2-
import { dash, rdfs } from '@tpluscode/rdf-ns-builders'
1+
import type { RenderSingleEditor } from '@hydrofoil/shaperone-wc'
2+
import { rdfs } from '@tpluscode/rdf-ns-builders'
33
import { html } from 'lit-html'
44
import { repeat } from 'lit-html/directives/repeat'
5+
import '@material/mwc-select/mwc-select'
6+
import '@material/mwc-list/mwc-list-item'
57

6-
export const enumSelect: SingleEditorComponent = {
7-
editor: dash.EnumSelectEditor,
8-
render({ value, property }, actions) {
9-
const choices = property.shape.inPointers
8+
export const enumSelect: RenderSingleEditor = function ({ value, property }, actions) {
9+
const choices = property.shape.inPointers
1010

11-
return html`<mwc-select @selected="${(e: CustomEvent) => actions.update(choices[e.detail.index].term)}">
11+
return html`<mwc-select @selected="${(e: CustomEvent) => actions.update(choices[e.detail.index].term)}">
1212
${repeat(choices, choice => html`<mwc-list-item ?selected="${choice.value === value.object?.value}" value="${choice}">
1313
${choice.out(rdfs.label).value || choice}
1414
</mwc-list-item>`)}
1515
</mwc-select>`
16-
},
17-
loadDependencies() {
18-
return [
19-
import('@material/mwc-select/mwc-select'),
20-
import('@material/mwc-list/mwc-list-item'),
21-
]
22-
},
2316
}

packages/wc-material/components/lib/textFieldFactory.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

packages/wc-material/components/lib/textFieldType.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)