Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: serialize model settings in QP & add a "save" button #101

Merged
merged 1 commit into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ Inspired from : https://github.com/mo22/textstl
- [ ] finalize font-picker tests
- [x] Add somes options (~~multiple kerning~~, multiple suport spacing, round corner, hole in support)
- [x] multi-line text ? (#33)
- [ ] save/load text (via URL encoded / local storage ?)
- [ ] (or) QP to save current settings ?
- [x] save/load text via QP to save current settings
- [x] handle custom font

## Prerequisites
Expand Down
14 changes: 14 additions & 0 deletions app/components/save-modal.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Ui::Modal @onHide={{@onHide}}>
<h2 class="uk-modal-title" for="current-url">
{{t "save_modal.title"}}
</h2>
<p>
{{t "save_modal.info" currentUrl=@currentUrl htmlSafe=true}}
</p>

<Ui::TextArea {{on "click" this.selectAll}} rows="15" id="current-url" readonly @value={{@currentUrl}} />

<button class="uk-button uk-button-primary uk-align-center uk-modal-close" type="button">
{{t "save_modal.close"}}
</button>
</Ui::Modal>
14 changes: 14 additions & 0 deletions app/components/save-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';

interface SaveModalArgs {
currentUrl: string;
onHide?: () => void;
}

export default class SaveModal extends Component<SaveModalArgs> {
@action
selectAll({ target }: { target: HTMLTextAreaElement }) {
target.select();
}
}
5 changes: 5 additions & 0 deletions app/components/ui/modal.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div ...attributes uk-modal {{this.openModal}} {{on "hide" this.onHide}}>
<div class="uk-modal-dialog uk-modal-body">
{{yield}}
</div>
</div>
19 changes: 19 additions & 0 deletions app/components/ui/modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Component from '@glimmer/component';
import { modifier } from 'ember-modifier';
import UIkit from 'uikit';
import { action } from '@ember/object';

interface ModalArgs {
onHide?: () => void;
}

export default class UiModal extends Component<ModalArgs> {
openModal = modifier((element: HTMLDivElement) => {
UIkit.modal(element).show();
});

@action
onHide() {
this.args.onHide?.();
}
}
37 changes: 35 additions & 2 deletions app/controllers/app/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,27 @@ import FontManagerService from 'text2stl/services/font-manager';
import TextMakerService from 'text2stl/services/text-maker';
import STLExporterService from 'text2stl/services/stl-exporter';
import CounterService from 'text2stl/services/counter';
import type ApplicationRoute from 'text2stl/routes/app/generator';
import type IntlService from 'ember-intl/services/intl';
import { tracked } from '@glimmer/tracking';
import { trackedFunction } from 'ember-resources/util/function';
import { Registry as Services } from '@ember/service';

import type ApplicationRoute from 'text2stl/routes/app/generator';
import type IntlService from 'ember-intl/services/intl';

export default class GeneratorController extends Controller {
queryParams = ['modelSettings'];

// Serialize model settings as QP
get modelSettings() {
return this.model?.serialize() ?? '';
}

set modelSettings(_value: string) {
// Nothing needed here, model is update :
// By route according to QP when route is loaded
// By components for later update
}

@service declare textMaker: TextMakerService;

@service declare fontManager: FontManagerService;
Expand All @@ -21,6 +36,8 @@ export default class GeneratorController extends Controller {

@service('counter') declare counterService: CounterService;

@service declare router: Services['router'];

declare model: RouteModel<ApplicationRoute>;

_gtag = gtag;
Expand Down Expand Up @@ -74,6 +91,22 @@ export default class GeneratorController extends Controller {
this.counterService.updateCounter();
this.stlExporter.downloadMeshAsSTL(mesh);
}

@tracked saveModalVisible = false;

get currentUrl() {
return window.location.href;
}

@action
showSaveModal() {
this.saveModalVisible = true;
}

@action
hideSaveModal() {
this.saveModalVisible = false;
}
}

declare module '@ember/controller' {
Expand Down
90 changes: 87 additions & 3 deletions app/models/text-maker-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ interface TextMakerAdditionnalSettings {
variantName: Variant;
}

export class SupportPaddingSettings implements SupportPadding {
interface QPSerializable {
serialize(): string;
deserialize(json: string): void;
}

export class SupportPaddingSettings implements SupportPadding, QPSerializable {
@tracked top: number;
@tracked bottom: number;
@tracked left: number;
Expand All @@ -31,9 +36,26 @@ export class SupportPaddingSettings implements SupportPadding {
this.left = args.left;
this.right = args.right;
}

serialize(): string {
return JSON.stringify({
top: this.top,
bottom: this.bottom,
left: this.left,
right: this.right,
});
}

deserialize(json: string) {
const v = JSON.parse(json) as SupportPaddingSettings;
this.top = v.top;
this.bottom = v.bottom;
this.left = v.left;
this.right = v.right;
}
}

export class HandleSettings implements Handle {
export class HandleSettings implements Handle, QPSerializable {
@tracked type: 'hole' | 'handle' | 'none';
@tracked position: 'left' | 'top' | 'right' | 'bottom';
@tracked size: number;
Expand All @@ -49,9 +71,30 @@ export class HandleSettings implements Handle {
this.offsetX = args.offsetX;
this.offsetY = args.offsetY;
}

serialize(): string {
return JSON.stringify({
type: this.type,
position: this.position,
size: this.size,
size2: this.size2,
offsetX: this.offsetX,
offsetY: this.offsetY,
});
}

deserialize(json: string) {
const v = JSON.parse(json) as HandleSettings;
this.type = v.type;
this.position = v.position;
this.size = v.size;
this.size2 = v.size2;
this.offsetX = v.offsetX;
this.offsetY = v.offsetY;
}
}

export default class TextMakerSettings implements TextMakerParameters {
export default class TextMakerSettings implements TextMakerParameters, QPSerializable {
@tracked fontName: string;
@tracked variantName?: Variant;
@tracked text: string;
Expand Down Expand Up @@ -86,4 +129,45 @@ export default class TextMakerSettings implements TextMakerParameters {
args.handleSettings ?? textMakerDefault.handleSettings,
);
}

serialize(): string {
return JSON.stringify({
fontName: this.fontName,
variantName: this.variantName,
text: this.text,
size: this.size,
customFont: this.customFont,
height: this.height,
spacing: this.spacing,
vSpacing: this.vSpacing,
alignment: this.alignment,
type: this.type,
supportHeight: this.supportHeight,
supportBorderRadius: this.supportBorderRadius,
supportPadding: this.supportPadding.serialize(),
handleSettings: this.handleSettings.serialize(),
});
}

deserialize(json: string) {
const v = JSON.parse(json) as Omit<TextMakerSettings, 'supportPadding' | 'handleSettings'> & {
supportPadding: string;
handleSettings: string;
};

this.fontName = v.fontName;
this.variantName = v.variantName;
this.text = v.text;
this.size = v.size;
this.customFont = v.customFont;
this.height = v.height;
this.spacing = v.spacing;
this.vSpacing = v.vSpacing;
this.alignment = v.alignment;
this.type = v.type;
this.supportHeight = v.supportHeight;
this.supportBorderRadius = v.supportBorderRadius;
this.supportPadding.deserialize(v.supportPadding);
this.handleSettings.deserialize(v.handleSettings);
}
}
17 changes: 15 additions & 2 deletions app/routes/app/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,26 @@ const {
} = config;

export default class ApplicationRoute extends Route {
async model() {
queryParams = {
modelSettings: {
replace: true, // No history for model changes
},
};

async model(params: { modelSettings: string }) {
// Create a default settings for first rendering
return new TextMakerSettings({
const model = new TextMakerSettings({
...textMakerDefault,
supportPadding: { ...textMakerDefault.supportPadding },
handleSettings: { ...textMakerDefault.handleSettings },
});

// Load model settings from QP if any
if (params.modelSettings) {
model.deserialize(params.modelSettings);
}

return model;
}

afterModel() {
Expand Down
42 changes: 28 additions & 14 deletions app/templates/app/generator.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,29 @@
<div class='uk-width-1-1 uk-width-1-3@m'>
<SettingsForm @model={{this.model}} />

{{! TODO: move this into basic button component ? }}
<button
type='button'
class='uk-button uk-button-primary uk-align-center'
disabled={{this.exportDisabled}}
{{on 'click' this.exportSTL}}
>
{{#if this.meshGenerating}}
<div uk-spinner></div>
{{else}}
{{t 'export_stl'}}
{{/if}}
</button>

<button
type='button'
class='uk-button uk-button-primary uk-align-center'
disabled={{this.exportDisabled}}
{{on 'click' this.exportSTL}}
>
{{#if this.meshGenerating}}
<div uk-spinner></div>
{{else}}
{{t 'export_stl'}}
{{/if}}
</button>

<button
type='button'
class='uk-button uk-align-center'
{{on 'click' this.showSaveModal}}
>
{{t 'save'}}
</button>



{{#if this.counter}}
<div class='uk-text-small uk-text-muted uk-text-center'>
Expand All @@ -36,4 +46,8 @@
</div>
</ThreePreview>
</div>
</div>
</div>

{{#if this.saveModalVisible}}
<SaveModal @onHide={{this.hideSaveModal}} @currentUrl={{this.currentUrl}} />
{{/if}}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@types/qunit": "^2.11.1",
"@types/rsvp": "^4.0.3",
"@types/three": "~0.137.0",
"@types/uikit": "^3.14.1",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"broccoli-asset-rev": "^3.0.0",
Expand Down
24 changes: 21 additions & 3 deletions tests/unit/routes/app/generator-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { setupTest } from 'ember-qunit';
import TextMakerSettings from 'text2stl/models/text-maker-settings';
import config from 'text2stl/config/environment';

import type ApplicationRoute from 'text2stl/routes/index';
import type GeneratorRoute from 'text2stl/routes/app/generator';
const {
APP: { textMakerDefault },
} = config;
Expand All @@ -12,10 +12,28 @@ module('Unit | Route | app/generator', function (hooks) {
setupTest(hooks);

test('it creates a model with default values & fetch default font', async function (assert) {
const route = this.owner.lookup('route:app/generator') as ApplicationRoute;
const route = this.owner.lookup('route:app/generator') as GeneratorRoute;

const model = await route.model();
const model = await route.model({ modelSettings: '' });

assert.propEqual(model, new TextMakerSettings(textMakerDefault), 'settings model is conform');
});

test('it creates a model according to QP', async function (assert) {
const route = this.owner.lookup('route:app/generator') as GeneratorRoute;

const qpModel = new TextMakerSettings(textMakerDefault);
qpModel.text = 'something';
qpModel.size = 999;
qpModel.supportPadding.left = 10;
qpModel.supportPadding.right = 23;
qpModel.handleSettings.type = 'hole';
qpModel.handleSettings.offsetX = 55;

const modelSettings = qpModel.serialize();

const model = await route.model({ modelSettings });

assert.propEqual(model, qpModel, 'settings model is conform to QP');
});
});
5 changes: 5 additions & 0 deletions translations/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,8 @@ errors:
unknown_font: The font is not in font list
seo:
description: Generate 3D printable text in one click ! Choose a font from more than 1000 fonts or use your own, different shape and settings & download an STL file to print.
save: save settings
save_modal:
title: Save your current settings
info: Bookmark the <a href="{currentUrl}">current URL</a> or save the URL bellow to easily retrieve the current settings.
close: Close
5 changes: 5 additions & 0 deletions translations/fr-fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,8 @@ errors:
unknown_font: Police non trouvée dans la liste
seo:
description: Générer du texte 3D en 1 click ! Choisissez une police parmi plus de 1000 polices Google ou utilisez la vôtre, différentes formes et réglagles & téléchargez un fichier STL à imprimer.
save: enregister les réglages
save_modal:
title: Enregistrer vos réglages actuels
info: Ajouter l'<a href="{currentUrl}">URL actuelle</a> de la page à vos favoris ou enregistrer l'URL ci dessous pour facilement retrouvé les réglages actuels.
close: Fermer
Loading