Skip to content

Commit

Permalink
Merge pull request #57 from dhilt/issue-56-pause-resume-methods
Browse files Browse the repository at this point in the history
Pause/resume methods
  • Loading branch information
dhilt authored Mar 2, 2024
2 parents 8b35027 + 547d4b3 commit 045bbba
Show file tree
Hide file tree
Showing 14 changed files with 157 additions and 19 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vscroll",
"version": "1.6.0-beta.3",
"version": "1.6.0",
"description": "Virtual scroll engine",
"main": "dist/bundles/vscroll.umd.js",
"module": "dist/bundles/vscroll.esm5.js",
Expand Down
70 changes: 58 additions & 12 deletions src/classes/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { Logger } from './logger';
import { Buffer } from './buffer';
import { Reactive } from './reactive';
import {
AdapterPropName, AdapterPropType, EMPTY_ITEM, getDefaultAdapterProps, methodPreResult, reactiveConfigStorage
AdapterPropName,
AdapterPropType,
EMPTY_ITEM,
getDefaultAdapterProps,
methodPausedResult,
methodPreResult,
reactiveConfigStorage
} from './adapter/props';
import { wantedUtils } from './adapter/wanted';
import { Viewport } from './viewport';
Expand Down Expand Up @@ -42,9 +48,10 @@ type InitializationParams<Item> = {
}

const ADAPTER_PROPS_STUB = getDefaultAdapterProps();
const ALLOWED_METHODS_WHEN_PAUSED = ADAPTER_PROPS_STUB.filter(v => !!v.allowedWhenPaused).map(v => v.name);

const _has = (obj: unknown, prop: string): boolean =>
typeof obj === 'object' && obj !== null && Object.prototype.hasOwnProperty.call(obj, prop);
!!obj && typeof obj === 'object' && Object.prototype.hasOwnProperty.call(obj, prop);

const convertAppendArgs = <Item>(prepend: boolean, options: unknown, eof?: boolean) => {
let result = options as AdapterAppendOptions<Item> & AdapterPrependOptions<Item>;
Expand Down Expand Up @@ -107,20 +114,38 @@ export class Adapter<Item = unknown> implements IAdapter<Item> {
bof$: Reactive<boolean>;
eof: boolean;
eof$: Reactive<boolean>;
paused: boolean;
paused$: Reactive<boolean>;

private relax$: Reactive<AdapterMethodResult> | null;
private relaxRun: Promise<AdapterMethodResult> | null;

private getPromisifiedMethod(method: MethodResolver, defaultMethod: MethodResolver) {
private shouldIgnorePausedMethod(method: MethodResolver) {
const methodName = method.name as AdapterPropName;
return this.paused && !ALLOWED_METHODS_WHEN_PAUSED.includes(methodName);
}

private getPausedMethodResult(method: MethodResolver) {
this.logger?.log?.(() => 'scroller is paused: ' + method.name + ' method is ignored');
return Promise.resolve(methodPausedResult);
}

private getPromisifiedMethod(method: MethodResolver, args: unknown[]) {
return new Promise<AdapterMethodResult>(resolve => {
if (this.relax$) {
this.relax$.once(value => resolve(value));
}
method.apply(this, args);
});
}

private getWorkflowRunnerMethod(method: MethodResolver, defaultMethod: MethodResolver) {
return (...args: unknown[]): Promise<AdapterMethodResult> =>
this.relax$
? new Promise(resolve => {
if (this.relax$) {
this.relax$.once(value => resolve(value));
}
method.apply(this, args);
})
: defaultMethod.apply(this, args);
!this.relax$
? defaultMethod.apply(this, args)
: this.shouldIgnorePausedMethod(method)
? this.getPausedMethodResult(method)
: this.getPromisifiedMethod(method, args);
}

constructor(context: IAdapter<Item> | null, getWorkflow: WorkflowGetter<Item>, logger: Logger) {
Expand Down Expand Up @@ -249,7 +274,7 @@ export class Adapter<Item = unknown> implements IAdapter<Item> {
if (type === AdapterPropType.Function) {
value = (value as () => void).bind(this);
} else if (type === AdapterPropType.WorkflowRunner) {
value = this.getPromisifiedMethod(value as MethodResolver, defaultValue as MethodResolver);
value = this.getWorkflowRunnerMethod(value as MethodResolver, defaultValue as MethodResolver);
} else if (type === AdapterPropType.Reactive && reactivePropsStore[name]) {
value = (context as IAdapter)[name];
} else if (name === AdapterPropName.augmented) {
Expand Down Expand Up @@ -303,6 +328,8 @@ export class Adapter<Item = unknown> implements IAdapter<Item> {
state.cycle.innerLoop.busy.on(busy => this.loopPending = busy);
this.isLoading = state.cycle.busy.get();
state.cycle.busy.on(busy => this.isLoading = busy);
this.paused = state.paused.get();
state.paused.on(paused => this.paused = paused);

//viewport
this.setFirstOrLastVisible = ({ first, last, workflow }) => {
Expand Down Expand Up @@ -501,6 +528,25 @@ export class Adapter<Item = unknown> implements IAdapter<Item> {
});
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
pause(): any {
this.logger.logAdapterMethod('pause');
this.workflow.call({
process: AdapterProcess.pause,
status: ProcessStatus.start
});
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
resume(): any {
this.logger.logAdapterMethod('resume');
this.workflow.call({
process: AdapterProcess.pause,
status: ProcessStatus.start,
payload: { options: { resume: true } }
});
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
fix(options: AdapterFixOptions<Item>): any {
this.logger.logAdapterMethod('fix', options);
Expand Down
35 changes: 34 additions & 1 deletion src/classes/adapter/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export enum AdapterPropName {
bof$ = 'bof$',
eof = 'eof',
eof$ = 'eof$',
paused = 'paused',
paused$ = 'paused$',
reset = 'reset',
reload = 'reload',
append = 'append',
Expand All @@ -35,6 +37,8 @@ export enum AdapterPropName {
insert = 'insert',
replace = 'replace',
update = 'update',
pause = 'pause',
resume = 'resume',
fix = 'fix',
relax = 'relax',
showLog = 'showLog',
Expand All @@ -58,6 +62,12 @@ export const methodPreResult: AdapterMethodResult = {
details: 'Adapter is not initialized'
};

export const methodPausedResult: AdapterMethodResult = {
immediate: true,
success: true,
details: 'Scroller is paused'
};

const noopWF = () => Promise.resolve(methodPreResult);

const emptyPackageInfo: IPackages = {
Expand Down Expand Up @@ -173,10 +183,17 @@ export const getDefaultAdapterProps = (): IAdapterProp[] => [
value: false,
reactive: Name.eof$
},
{
type: Type.Scalar,
name: Name.paused,
value: false,
reactive: Name.paused$
},
{
type: Type.WorkflowRunner,
name: Name.reset,
value: noopWF
value: noopWF,
allowedWhenPaused: true
},
{
type: Type.WorkflowRunner,
Expand Down Expand Up @@ -223,6 +240,17 @@ export const getDefaultAdapterProps = (): IAdapterProp[] => [
name: Name.update,
value: noopWF
},
{
type: Type.WorkflowRunner,
name: Name.pause,
value: noopWF
},
{
type: Type.WorkflowRunner,
name: Name.resume,
value: noopWF,
allowedWhenPaused: true
},
{
type: Type.WorkflowRunner,
name: Name.fix,
Expand Down Expand Up @@ -274,6 +302,11 @@ export const getDefaultAdapterProps = (): IAdapterProp[] => [
type: Type.Reactive,
name: Name.eof$,
value: new Reactive<boolean>()
},
{
type: Type.Reactive,
name: Name.paused$,
value: new Reactive<boolean>()
}
];

Expand Down
4 changes: 4 additions & 0 deletions src/classes/state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Settings } from './settings';
import { Reactive } from './reactive';
import { WorkflowCycleModel } from './state/cycle';
import { FetchModel } from './state/fetch';
import { ClipModel } from './state/clip';
Expand All @@ -11,6 +12,7 @@ export class State implements IState {
readonly packageInfo: IPackages;
private settings: Settings;
initTime: number;
paused: Reactive<boolean>;

cycle: WorkflowCycleModel;
fetch: FetchModel;
Expand All @@ -26,6 +28,7 @@ export class State implements IState {
this.packageInfo = packageInfo;
this.settings = settings;
this.initTime = Number(new Date());
this.paused = new Reactive(false);

this.cycle = new WorkflowCycleModel(this.settings.instanceIndex, state ? state.cycle : void 0);
this.fetch = new FetchModel(settings.directionPriority);
Expand Down Expand Up @@ -80,6 +83,7 @@ export class State implements IState {
dispose(): void {
this.scroll.stop();
this.cycle.dispose();
this.paused.dispose();
this.endInnerLoop();
}

Expand Down
2 changes: 2 additions & 0 deletions src/inputs/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ export const AdapterMethods: AdapterProcessMap<{ [key: string]: string }> = {
[Process.insert]: AdapterInsertParams,
[Process.replace]: AdapterReplaceParams,
[Process.update]: AdapterUpdateParams,
[Process.pause]: AdapterNoParams,
[Process.fix]: AdapterFixParams,
};

Expand All @@ -251,5 +252,6 @@ export const ADAPTER_METHODS: AdapterProcessMap<ICommonProps<PropertyKey>> = {
[Process.insert]: INSERT_METHOD_PARAMS,
[Process.replace]: REPLACE_METHOD_PARAMS,
[Process.update]: UPDATE_METHOD_PARAMS,
[Process.pause]: NO_METHOD_PARAMS,
[Process.fix]: FIX_METHOD_PARAMS,
};
5 changes: 5 additions & 0 deletions src/interfaces/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface IAdapterProp {
wanted?: boolean;
onDemand?: boolean;
permanent?: boolean;
allowedWhenPaused?: boolean;
}

export interface ItemAdapter<Data = unknown> {
Expand Down Expand Up @@ -157,6 +158,8 @@ export interface IAdapter<Data = unknown> {
readonly bof$: Reactive<boolean>;
readonly eof: boolean;
readonly eof$: Reactive<boolean>;
readonly paused: boolean;
readonly paused$: Reactive<boolean>;
reset(datasource?: IDatasourceOptional): MethodResult;
reload(reloadIndex?: number | string): MethodResult;
append(options: AdapterAppendOptions<Data>): MethodResult;
Expand All @@ -170,6 +173,8 @@ export interface IAdapter<Data = unknown> {
insert(options: AdapterInsertOptions<Data>): MethodResult;
replace(options: AdapterReplaceOptions<Data>): MethodResult;
update(options: AdapterUpdateOptions<Data>): MethodResult;
pause(): MethodResult;
resume(): MethodResult;
fix(options: AdapterFixOptions<Data>): MethodResult; // experimental
relax(callback?: () => void): MethodResult;
showLog(): void;
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Direction } from '../inputs/index';
import { Reactive } from '../classes/reactive';
import { WorkflowCycleModel } from '../classes/state/cycle';
import { FetchModel } from '../classes/state/fetch';
import { ClipModel } from '../classes/state/clip';
Expand All @@ -15,6 +16,7 @@ export interface ScrollEventData {
export interface State {
packageInfo: IPackages;
initTime: number;
paused: Reactive<boolean>;
cycle: WorkflowCycleModel;
fetch: FetchModel;
clip: ClipModel;
Expand Down
28 changes: 28 additions & 0 deletions src/processes/adapter/pause.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Scroller } from '../../scroller';
import { BaseAdapterProcessFactory, AdapterProcess, ProcessStatus } from '../misc/index';

export default class Pause extends BaseAdapterProcessFactory(AdapterProcess.pause) {

static run(scroller: Scroller, options?: { resume: boolean }): void {
const resume = !!options?.resume;

// pause branch
if (!resume && !scroller.state.paused.get()) {
scroller.logger.log('pause scroller');
scroller.state.paused.set(true);
scroller.workflow.call({
process: AdapterProcess.pause,
status: ProcessStatus.done
});
return;
}

scroller.logger.log('resume scroller');
scroller.state.paused.set(false);
scroller.workflow.call({
process: AdapterProcess.pause,
status: ProcessStatus.next
});
}

}
3 changes: 2 additions & 1 deletion src/processes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import UserClip from './adapter/clip';
import Insert from './adapter/insert';
import Replace from './adapter/replace';
import Update from './adapter/update';
import Pause from './adapter/pause';
import Fix from './adapter/fix';
import Start from './start';
import PreFetch from './preFetch';
Expand All @@ -24,7 +25,7 @@ import { CommonProcess, AdapterProcess, ProcessStatus } from './misc/enums';

export {
Init, Scroll,
Reset, Reload, Append, Check, Remove, UserClip, Insert, Replace, Update, Fix,
Reset, Reload, Append, Check, Remove, UserClip, Insert, Replace, Update, Pause, Fix,
Start, PreFetch, Fetch, PostFetch, Render, PreClip, Clip, Adjust, End,
CommonProcess, AdapterProcess, ProcessStatus,
};
1 change: 1 addition & 0 deletions src/processes/misc/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export enum AdapterProcess {
update = 'adapter.update',
clip = 'adapter.clip',
insert = 'adapter.insert',
pause = 'adapter.pause',
fix = 'adapter.fix',
}

Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default {
name: 'vscroll',
version: '1.6.0-beta.3'
version: '1.6.0'
};
9 changes: 9 additions & 0 deletions src/workflow-transducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Insert,
Replace,
Update,
Pause,
Fix,
Start,
PreFetch,
Expand Down Expand Up @@ -131,6 +132,14 @@ export const runStateMachine = ({
run(Init)(process);
}
break;
case AdapterProcess.pause:
if (status === Status.start) {
run(Pause)(options);
}
if (status === Status.next) {
run(Init)(process);
}
break;
case AdapterProcess.fix:
if (status === Status.start) {
run(Fix)(options);
Expand Down
Loading

0 comments on commit 045bbba

Please sign in to comment.