blob: cc6d8ab0c9a276e588335a8e121ff38951eb1b18 [file] [log] [blame]
/**
* @module skottie-config-sk
* @description <h2><code>skottie-config-sk</code></h2>
*
* <p>
* A dialog for configuring how to render a lottie file.
* </p>
*
* <p>
* The form of the 'state' property looks like a serialized UploadRequest:
* </p>
* <pre>
* {
* filename: 'foo.json',
* lottie: {},
* assetsZip: 'data:application/zip;base64,...'
* assetsFileName: 'assets.zip'
* }
* <pre>
*
* @evt skottie-selected - This event is generated when the user presses Go.
* The updated state, width, and height is available in the event detail.
* There is also an indication if the lottie file was changed.
*
* @evt cancelled - This event is generated when the user presses Cancel.
*
*/
import 'elements-sk/styles/buttons'
import { define } from 'elements-sk/define'
import { errorMessage } from 'elements-sk/errorMessage'
import { html, render } from 'lit-html'
import { $$ } from 'common-sk/modules/dom'
const DEFAULT_SIZE = 128;
const allowZips = window.location.hostname === "skottie-internal.skia.org" ||
window.location.hostname === "localhost";
const cancelButton = (ele) => ele._hasCancel() ? html`<button id=cancel @click=${ele._cancel}>Cancel</button>` : '';
const template = (ele) => html`
<div ?hidden=${!allowZips}>
We support 3 types of uploads:
<ul>
<li>A plain JSON file.</li>
<li>A JSON file with a zip file of assets (e.g. images) used by the animation.</li>
<li>
A zip file produced by lottiefiles.com
(<a href="https://lottiefiles.com/1187-puppy-run">example</a>)
with a JSON file in the top level and an images/ directory.
</li>
</ul>
</div>
<label class=file>Lottie file to upload
<input type=file name=file id=file @change=${ele._onFileChange}/>
</label>
<div class="filename ${ele._state.filename ? '' : 'empty'}">
${ele._state.filename ? ele._state.filename : 'No file selected.'}
</div>
<label class=file ?hidden=${!allowZips}>Optional Asset Folder (.zip)
<input type=file name=folder id=folder @change=${ele._onFolderChange}/>
</label>
<div class="filename ${ele._state.assetsFilename ? '' : 'empty'}" ?hidden=${!allowZips}>
${ele._state.assetsFilename ? ele._state.assetsFilename : 'No asset folder selected.'}
</div>
<label class=number>
<input type=number id=width .value=${ele._width} required /> Width (px)
</label>
<label class=number>
<input type=number id=height .value=${ele._height} required /> Height (px)
</label>
<div>
0 for width/height means use the default from the animation
</div>
<div class=warning ?hidden=${ele._warningHidden()}>
<p>
The width or height of your file exceeds 1024, which may not fit on the screen.
Press a 'Rescale' button to fix the dimensions while preserving the aspect ratio.
</p>
<div>
<button @click=${(e) => ele._rescale(1024)}>Rescale to 1024</button>
<button @click=${(e) => ele._rescale(512)}>Rescale to 512</button>
<button @click=${(e) => ele._rescale(128)}>Rescale to 128</button>
</div>
</div>
<div id=dialog-buttons>
${cancelButton(ele)}
<button class=action ?disabled=${ele._readyToGo()} @click=${ele._go}>Go</button>
</div>
`;
class SkottieConfigSk extends HTMLElement {
constructor() {
super();
this._state = {
filename: '',
lottie: null,
assetsZip: '',
assetsFileName: '',
};
this._width = DEFAULT_SIZE;
this._height = DEFAULT_SIZE;
this._fileChanged = false;
this._starting_state = Object.assign({}, this._state);
}
connectedCallback() {
this._render();
this.addEventListener('input', this._inputEvent);
}
disconnectedCallback() {
this.removeEventListener('input', this._inputEvent);
}
/** @prop height {Number} Selected height for animation. */
get height() { return this._height; }
set height(val) {
this._height= +val;
this._render();
}
/** @prop state {string} Object that describes the state of the config dialog. */
get state() { return this._state; }
set state(val) {
this._state = Object.assign({}, val);
this._starting_state = Object.assign({}, this._state);
this._render();
}
/** @prop width {Number} Selected width for animation. */
get width() { return this._width; }
set width(val) {
this._width = +val;
this._render();
}
_hasCancel() {
return !!this._starting_state.lottie;
}
_readyToGo() {
return !this._state.filename && (this._state.lottie || this._state.assetsZip);
}
_onFileChange(e) {
this._fileChanged = true;
const toLoad = e.target.files[0];
const reader = new FileReader();
if (toLoad.name.endsWith('.json')) {
reader.addEventListener('load', () => {
let parsed = {};
try {
parsed = JSON.parse(reader.result);
}
catch(error) {
errorMessage(`Not a valid JSON file: ${error}`);
return;
}
this._state.lottie = parsed;
this._state.filename = toLoad.name;
this._width = parsed.w || DEFAULT_SIZE;
this._height = parsed.h || DEFAULT_SIZE;
this._render();
});
reader.addEventListener('error', () => {
errorMessage('Failed to load.');
});
reader.readAsText(toLoad);
} else if (allowZips && toLoad.name.endsWith('.zip')) {
reader.addEventListener('load', () => {
this._state.lottie = '';
this._state.assetsZip = reader.result;
this._state.filename = toLoad.name;
this._width = DEFAULT_SIZE;
this._height = DEFAULT_SIZE;
this._render();
});
reader.addEventListener('error', () => {
errorMessage('Failed to load '+ toLoad.name);
});
reader.readAsDataURL(toLoad);
} else {
let msg = `Bad file type ${toLoad.name}, only .json and .zip supported`;
if (!allowZips) {
msg = `Bad file type ${toLoad.name}, only .json supported`;
}
errorMessage(msg);
this._state.filename = '';
this._state.lottie = '';
}
}
_onFolderChange(e) {
this._fileChanged = true;
const toLoad = e.target.files[0];
const reader = new FileReader();
reader.addEventListener('load', () => {
this._state.assetsZip = reader.result;
this._state.assetsFilename = toLoad.name;
this._render();
});
reader.addEventListener('error', () => {
errorMessage('Failed to load '+ toLoad.name);
});
reader.readAsDataURL(toLoad);
}
_rescale(n) {
let max = Math.max(this._width, this._height);
if (max <= n) {
return
}
this._width = Math.floor(this._width * n / max);
this._height = Math.floor(this._height * n / max);
this._render();
}
_warningHidden() {
return this._width <= 1024 && this._width <= 1024;
}
_updateState() {
this._width = +$$('#width', this).value;
this._height = +$$('#height', this).value;
}
_go() {
this._updateState();
this.dispatchEvent(new CustomEvent('skottie-selected', { detail: {
'state' : this._state,
'fileChanged': this._fileChanged,
'width' : this._width,
'height': this._height,
}, bubbles: true }));
}
_cancel() {
this.dispatchEvent(new CustomEvent('cancelled', { bubbles: true }));
}
_inputEvent() {
this._updateState();
this._render();
}
_render() {
render(template(this), this, {eventContext: this});
}
};
define('skottie-config-sk', SkottieConfigSk);