Skip to content

Commit

Permalink
Add setStageTitle method to the prepare context API
Browse files Browse the repository at this point in the history
  • Loading branch information
lahmatiy committed Sep 27, 2024
1 parent 056ead7 commit 1648775
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 21 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
## next

- Added `setStageTitle` method to the prepare context API to display additional text on the progress bar:
```js
export function async prepare(input, { setStageTitle }) {
await setStageTitle('phase 1');
// ...
await setStageTitle('phase 2');
// ...
}
```
- Fixed crashing the entire render tree on an exception in a view's `render` function; now, crashes are isolated to the affected view
- Fixed unnecessary view rendering when returning to the discovery page
- Fixed hiding a popup with `hideOnResize: true` when scrolling outside of the popup element
Expand Down
35 changes: 26 additions & 9 deletions src/core/utils/progressbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ export default class Progressbar extends Observer<ProgressbarState> {
appearanceDelay: number;
domReady: Promise<any>;
el: HTMLElement;
#titleEl: HTMLElement;
#stepEl: HTMLElement;

constructor({ onTiming, onFinish, delay, domReady }: ProgressbarOptions) {
super({ stage: 'inited', progress: null, error: null });
Expand All @@ -152,7 +154,10 @@ export default class Progressbar extends Observer<ProgressbarState> {
this.domReady = domReady || Promise.resolve();

this.el = createElement('div', 'view-progress init', [
createElement('div', 'title'),
createElement('div', 'content main-secondary', [
this.#titleEl = createElement('span', 'main'),
this.#stepEl = createElement('span', 'secondary')
]),
createElement('div', 'progress')
]);
}
Expand All @@ -173,6 +178,16 @@ export default class Progressbar extends Observer<ProgressbarState> {
this.onTiming(entry);
}

async #awaitRenderIfNeeded(enforce = false, now = performance.now()) {
const timeSinceAwaitRepaint = now - (this.awaitRepaint || 0);
const timeSinceLastStageStart = now - (this.lastStageStart || 0);

if (enforce || (timeSinceAwaitRepaint > 65 && timeSinceLastStageStart > 200)) {
await letRepaintIfNeeded();
this.awaitRepaint = performance.now();
}
}

async setState(state: Partial<ProgressbarState>) {
const { stage = this.lastStage, progress = null, error = null } = state;

Expand Down Expand Up @@ -224,18 +239,20 @@ export default class Progressbar extends Observer<ProgressbarState> {
}

const { title, progressValue } = decodeStageProgress(stage, progress);
const titleEl = this.el.querySelector('.title');

this.el.style.setProperty('--progress', String(progressValue));

if (titleEl !== null) {
titleEl.textContent = title;
}
this.#titleEl.textContent = title;
this.#stepEl.textContent = '';

if (stageChanged || (now - (this.awaitRepaint || 0) > 65 && now - (this.lastStageStart || 0) > 200)) {
await letRepaintIfNeeded();
this.awaitRepaint = performance.now();
}
return this.#awaitRenderIfNeeded(stageChanged, now);
}

async setStateStep(name: string) {
this.#titleEl.textContent = (this.#titleEl.textContent || '').replace(/(\.{3}|:)?$/, ':');
this.#stepEl.textContent = name;

return this.#awaitRenderIfNeeded(true);
}

finish(error?: Error) {
Expand Down
7 changes: 4 additions & 3 deletions src/main/model-extension-api.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import jora from 'jora';
import type { Model, ModelOptions, PageParams, PageRef, PrepareContextApiWrapper, SetupMethods } from './model.js';
import type { Model, ModelOptions, PageParams, PageRef, PrepareContextApiWrapper, SetDataOptions, SetupMethods } from './model.js';
import type { ObjectMarkerConfig } from '../core/object-marker.js';
import jora from 'jora';

export function createExtensionApi(host: Model): PrepareContextApiWrapper {
export function createExtensionApi(host: Model, options?: SetDataOptions): PrepareContextApiWrapper {
return {
before() {
host.objectMarkers.reset();
},
contextApi: {
markers: host.objectMarkers.markerMap(),
setStageTitle: options?.setPrepareStepTitle || (() => Promise.resolve()),
rejectData(message: string, renderContent: any) {
throw Object.assign(new Error(message), { renderContent });
}
Expand Down
9 changes: 5 additions & 4 deletions src/main/model-legacy-extension-api.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import jora from 'jora';
import ObjectMarker, { ObjectMarkerConfig } from '../core/object-marker.js';
import type { LegacyPrepareContextApi, PrepareContextApiWrapper, Model, Query, PageRef, PageParams } from './model.js';
import type { LegacyPrepareContextApi, PrepareContextApiWrapper, Model, Query, PageRef, PageParams, SetDataOptions } from './model.js';
import type { ValueAnnotationContext, Widget } from './widget.js';
import ObjectMarker, { ObjectMarkerConfig } from '../core/object-marker.js';
import jora from 'jora';

export function createLegacyExtensionApi(host: Model): PrepareContextApiWrapper {
export function createLegacyExtensionApi(host: Model, options?: SetDataOptions): PrepareContextApiWrapper {
const objectMarkers = new ObjectMarker();
const linkResolvers: Model['linkResolvers'] = [];
const annotations: Widget['annotations'] = [];
const contextApi: LegacyPrepareContextApi = {
setStageTitle: options?.setPrepareStepTitle || (() => Promise.resolve()),
rejectData(message: string, renderContent: any) {
throw Object.assign(new Error(message), { renderContent });
},
Expand Down
9 changes: 6 additions & 3 deletions src/main/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type ModelDataset = Dataset | RawDataDataset;
export type GetDecodeParams = (pageId: string) => (entries: [string, any][]) => object;

export interface SetDataOptions {
setPrepareStepTitle?: (name: string) => Promise<void>;
dataset?: Dataset;
}

Expand All @@ -64,10 +65,12 @@ export type PrepareContextApiWrapper = {
contextApi: PrepareContextApi | LegacyPrepareContextApi;
};
export interface PrepareContextApi {
setStageTitle: (name: string) => Promise<void>;
rejectData: (message: string, extra: any) => void;
markers: Record<string, (value: unknown) => void>;
}
export interface LegacyPrepareContextApi {
setStageTitle: (name: string) => Promise<void>;
rejectData: (message: string, extra: any) => void;
defineObjectMarker<T extends object>(name: string, options: ObjectMarkerConfig<T>): ObjectMarker<T>['mark'];
lookupObjectMarker(value: any, type?: string): ObjectMarkerDescriptor<object> | null;
Expand Down Expand Up @@ -227,7 +230,7 @@ export class Model<
this.prepare = fn;
}

setData(data: unknown, options: SetDataOptions) {
setData(data: unknown, options?: SetDataOptions) {
options = options || {};

// mark as last setData promise
Expand All @@ -243,8 +246,8 @@ export class Model<
};

const prepareApi = this.#legacyPrepare
? createLegacyExtensionApi(this)
: createExtensionApi(this);
? createLegacyExtensionApi(this, options)
: createExtensionApi(this, options);
const setDataPromise = Promise.resolve()
.then(() => {
checkIsNotPrevented();
Expand Down
1 change: 1 addition & 0 deletions src/main/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ export class Widget<
await progressbar?.setState({ stage: 'prepare' });
await this.setData(data, context, {
dataset,
setPrepareStepTitle: progressbar?.setStateStep.bind(progressbar),
render: false
});

Expand Down
12 changes: 12 additions & 0 deletions src/views/controls/progress.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,15 @@
/* transition: transform .2s; */ /* since Chrome (tested on 85) freezes transition during js loop */
background-color: var(--color, #1f7ec5);
}

.view-progress > .content.main-secondary {
display: flex;
white-space: nowrap;
gap: 1ex;
}
.view-progress > .content > .secondary {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
color: #888;
}
7 changes: 5 additions & 2 deletions src/views/controls/progress.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import usage from './progress.usage.js';
export default function(host) {
host.view.define('progress', function(el, config, data, context) {
const { content, progress, color } = config;
const progressEl = el.appendChild(createElement('div', {

el.append(createElement('div', {
class: 'progress',
style: `--progress: ${Math.max(0, Math.min(1, Number(progress)))};--color: ${color || 'unset'};`
}));

if (content) {
const contentEl = el.insertBefore(createElement('div', { class: 'content' }), progressEl);
const contentEl = createElement('div', 'content');

el.prepend(contentEl);

return host.view.render(contentEl, content, data, context);
}
Expand Down

0 comments on commit 1648775

Please sign in to comment.