Skip to content

Commit

Permalink
feat: adding writer.updateDimensions method (#245)
Browse files Browse the repository at this point in the history
* adding a method to updateDimensions of an existing writer without replacing state

* reorganizing methods

* updating caniuse to keep tests happy

* adding a setPositioner test to keep codecov happy
  • Loading branch information
chanind authored Jul 12, 2021
1 parent 3766a8b commit f1a810c
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 24 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,6 @@
"testURL": "https://test.com/url#tag",
"collectCoverage": true,
"testEnvironment": "<rootDir>/../jest-jsdom-env"
}
},
"dependencies": {}
}
54 changes: 45 additions & 9 deletions src/HanziWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { GenericMutation } from './Mutation';
// Typings
import {
ColorOptions,
DimensionOptions,
HanziWriterOptions,
LoadingManagerOptions,
OnCompleteFunction,
Expand Down Expand Up @@ -322,6 +323,33 @@ export default class HanziWriter {
);
}

/** Updates the size of the writer instance without resetting render state */
updateDimensions({ width, height, padding }: Partial<DimensionOptions>) {
if (width !== undefined) this._options.width = width;
if (height !== undefined) this._options.height = height;
if (padding !== undefined) this._options.padding = padding;
this.target.updateDimensions(this._options.width, this._options.height);
// if there's already a character drawn, destroy and recreate the renderer in the same state
if (
this._character &&
this._renderState &&
this._hanziWriterRenderer &&
this._positioner
) {
this._hanziWriterRenderer.destroy();
const hanziWriterRenderer = this._initAndMountHanziWriterRenderer(this._character);
// TODO: this should probably implement EventEmitter instead of manually tracking updates like this
this._renderState.overwriteOnStateChange((nextState) =>
hanziWriterRenderer.render(nextState),
);
hanziWriterRenderer.render(this._renderState.state);
// update the current quiz as well, if one is active
if (this._quiz) {
this._quiz.setPositioner(this._positioner);
}
}
}

updateColor(
colorName: keyof ColorOptions,
colorVal: string | null,
Expand Down Expand Up @@ -403,22 +431,30 @@ export default class HanziWriter {
}

this._character = parseCharData(char, pathStrings);
const { width, height, padding } = this._options;
this._positioner = new Positioner({ width, height, padding });
const hanziWriterRenderer = new this._renderer.HanziWriterRenderer(
this._character,
this._positioner,
);
this._hanziWriterRenderer = hanziWriterRenderer;
this._renderState = new RenderState(this._character, this._options, (nextState) =>
hanziWriterRenderer.render(nextState),
);
this._hanziWriterRenderer.mount(this.target);
this._hanziWriterRenderer.render(this._renderState.state);

const hanziWriterRenderer = this._initAndMountHanziWriterRenderer(
this._character,
);
hanziWriterRenderer.render(this._renderState.state);
});
return this._withDataPromise;
}

_initAndMountHanziWriterRenderer(character: Character) {
const { width, height, padding } = this._options;
this._positioner = new Positioner({ width, height, padding });
const hanziWriterRenderer = new this._renderer.HanziWriterRenderer(
character,
this._positioner,
);
hanziWriterRenderer.mount(this.target);
this._hanziWriterRenderer = hanziWriterRenderer;
return hanziWriterRenderer;
}

async getCharacterData(): Promise<Character> {
if (!this._char) {
throw new Error('setCharacter() must be called before calling getCharacterData()');
Expand Down
4 changes: 4 additions & 0 deletions src/Quiz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export default class Quiz {
);
}

setPositioner(positioner: Positioner) {
this._positioner = positioner;
}

endUserStroke() {
if (!this._userStroke) return;

Expand Down
4 changes: 4 additions & 0 deletions src/RenderState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ export default class RenderState {
}
}

overwriteOnStateChange(onStateChange: OnStateChangeCallback) {
this._onStateChange = onStateChange;
}

updateState(stateChanges: RecursivePartial<RenderStateObject>) {
const nextState = copyAndMergeDeep(this.state, stateChanges);
this._onStateChange(nextState, this.state);
Expand Down
77 changes: 77 additions & 0 deletions src/__tests__/HanziWriter-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,83 @@ describe('HanziWriter', () => {
});
});

describe('updateDimensions', () => {
it('resizes the SVG target', async () => {
document.body.innerHTML = '<div id="target"></div>';
const writer = HanziWriter.create('target', '人', {
charDataLoader,
width: 200,
height: 200,
padding: 10,
});
await writer.setCharacter('人');
writer.updateDimensions({ width: 300, height: 350, padding: 20 });
expect(document.querySelector('#target svg')?.attributes['width'].value).toBe(
'300',
);
expect(document.querySelector('#target svg')?.attributes['height'].value).toBe(
'350',
);
expect(writer._positioner?.width).toBe(300);
expect(writer._positioner?.height).toBe(350);
expect(writer._positioner?.padding).toBe(20);
});

it('resizes the target when using canvas', () => {
document.body.innerHTML = '<div id="target"></div>';
const writer = HanziWriter.create('target', '人', {
charDataLoader,
width: 200,
height: 200,
padding: 10,
renderer: 'canvas',
});
writer.updateDimensions({ width: 300, height: 350, padding: 20 });
expect(document.querySelector('#target canvas')?.attributes['width'].value).toBe(
'300',
);
expect(document.querySelector('#target canvas')?.attributes['height'].value).toBe(
'350',
);
});

it('updates the positioner for the active quiz, if it exists', async () => {
document.body.innerHTML = '<div id="target"></div>';
const writer = HanziWriter.create('target', '人', {
charDataLoader,
width: 200,
height: 200,
padding: 10,
});
await writer.setCharacter('人');
writer.quiz();
await resolvePromises();
writer.updateDimensions({ width: 300, height: 350, padding: 20 });
expect(writer._quiz!.setPositioner).toHaveBeenCalledWith(writer._positioner);
});

it('destroys the old renderer before recreating a new resized one', async () => {
document.body.innerHTML = '<div id="target"></div>';
const writer = HanziWriter.create('target', '人', {
charDataLoader,
width: 200,
height: 200,
padding: 10,
});
await writer.setCharacter('人');
const originalRenderer = writer._hanziWriterRenderer;
const destroyRendererSpy = jest.spyOn(writer._hanziWriterRenderer!, 'destroy');
writer.updateDimensions({ width: 300, height: 350, padding: 20 });
await resolvePromises();

// old renderer should be destroyed
expect(destroyRendererSpy).toHaveBeenCalledTimes(1);
expect(writer._hanziWriterRenderer?._positioner).toBe(writer._positioner);
// the renderer should be replaced
expect(writer._hanziWriterRenderer).not.toBe(originalRenderer);
});
});

describe('setCharacter', () => {
it('deletes the current character while loading', async () => {
document.body.innerHTML = '<div id="target"></div>';
Expand Down
13 changes: 13 additions & 0 deletions src/__tests__/Quiz-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,19 @@ describe('Quiz', () => {
});
});

describe('setPositioner', () => {
it('replaces the internal positioner', () => {
const quiz = new Quiz(
char,
createRenderState(),
new Positioner({ padding: 20, width: 200, height: 200 }),
);
const newPositoner = new Positioner({ padding: 30, width: 300, height: 300 });
quiz.setPositioner(newPositoner);
expect(quiz._positioner).toBe(newPositoner);
});
});

it('doesnt leave strokes partially drawn if the users finishes the quiz really fast', async () => {
(strokeMatches as any).mockImplementation(() => true);
const renderState = createRenderState();
Expand Down
5 changes: 5 additions & 0 deletions src/renderers/RenderTargetBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export default class RenderTargetBase<
return this.node.getBoundingClientRect();
}

updateDimensions(width: string | number, height: string | number) {
this.node.setAttribute('width', `${width}`);
this.node.setAttribute('height', `${height}`);
}

_eventify<TEvent extends Event>(evt: TEvent, pointFunc: (event: TEvent) => Point) {
return {
getPoint: () => pointFunc.call(this, evt),
Expand Down
13 changes: 8 additions & 5 deletions src/typings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,17 @@ export type HanziWriterOptions = Partial<PositionerOptions> &
LoadingManagerOptions &
BaseHanziWriterOptions;

export type DimensionOptions = {
width: number;
height: number;
padding: number;
};

export type ParsedHanziWriterOptions = QuizOptions &
LoadingManagerOptions &
BaseHanziWriterOptions &
ColorOptions & {
width: number;
height: number;
padding: number;
};
ColorOptions &
DimensionOptions;

export type RecursivePartial<T> = {
[P in keyof T]?: RecursivePartial<T[P]>;
Expand Down
13 changes: 4 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2604,15 +2604,10 @@ camelcase@^6.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==

caniuse-lite@^1.0.30001135:
version "1.0.30001137"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001137.tgz#6f0127b1d3788742561a25af3607a17fc778b803"
integrity sha512-54xKQZTqZrKVHmVz0+UvdZR6kQc7pJDgfhsMYDG19ID1BWoNnDMFm5Q3uSBSU401pBvKYMsHAt9qhEDcxmk8aw==

caniuse-lite@^1.0.30001154, caniuse-lite@^1.0.30001156, caniuse-lite@^1.0.30001173:
version "1.0.30001178"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001178.tgz#3ad813b2b2c7d585b0be0a2440e1e233c6eabdbc"
integrity sha512-VtdZLC0vsXykKni8Uztx45xynytOi71Ufx9T8kHptSw9AL4dpqailUJJHavttuzUe1KYuBYtChiWv+BAb7mPmQ==
caniuse-lite@^1.0.30001135, caniuse-lite@^1.0.30001154, caniuse-lite@^1.0.30001156, caniuse-lite@^1.0.30001173:
version "1.0.30001243"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001243.tgz"
integrity sha512-vNxw9mkTBtkmLFnJRv/2rhs1yufpDfCkBZexG3Y0xdOH2Z/eE/85E4Dl5j1YUN34nZVsSp6vVRFQRrez9wJMRA==

capture-exit@^2.0.0:
version "2.0.0"
Expand Down

0 comments on commit f1a810c

Please sign in to comment.