Compare commits
77 Commits
rollup-bui
...
asdf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9988505b2c | ||
|
|
0e6610c007 | ||
|
|
74477dfe6b | ||
|
|
59713b7687 | ||
|
|
62102fecab | ||
|
|
b1fc4d1c57 | ||
|
|
02e0206285 | ||
|
|
8f21972e70 | ||
|
|
8d9ed5ad3e | ||
|
|
be5a96a42a | ||
|
|
eeafef4e14 | ||
|
|
067ce686b5 | ||
|
|
c78ad6c795 | ||
|
|
be818caf48 | ||
|
|
4abc7f40d1 | ||
|
|
903c8b5d02 | ||
|
|
4c03ceee04 | ||
|
|
a2c465abdf | ||
|
|
251dc2ce9b | ||
|
|
e8948167db | ||
|
|
9e1fb6dfb4 | ||
|
|
2d1a3b543a | ||
|
|
4aaa5ffa78 | ||
|
|
6fa13b919b | ||
|
|
fcef2b2d3e | ||
|
|
4d5761c780 | ||
|
|
be3ab8b6d1 | ||
|
|
49620e4c8f | ||
|
|
3d1ecc1215 | ||
|
|
25fb1a9c80 | ||
|
|
3ae1cf86f5 | ||
|
|
a699a5c4dc | ||
|
|
613401c541 | ||
|
|
f450373e3f | ||
|
|
750872aca6 | ||
|
|
beaabe47dc | ||
|
|
8f7369068c | ||
|
|
10bfd60e20 | ||
|
|
7f08348509 | ||
|
|
f77ddac652 | ||
|
|
13631f1cfc | ||
|
|
f11e692d58 | ||
|
|
f0221b626d | ||
|
|
10c5ed0495 | ||
|
|
d945c79796 | ||
|
|
30b628c1b9 | ||
|
|
6ebf94d1b6 | ||
|
|
a229662bed | ||
|
|
e995b445ef | ||
|
|
6da590c7d0 | ||
|
|
56e10b3aa2 | ||
|
|
fd87ae7d2a | ||
|
|
5df7dd7590 | ||
|
|
013946b137 | ||
|
|
81c183b0d6 | ||
|
|
f523db6403 | ||
|
|
cc6ea9e11c | ||
|
|
bd4b67037b | ||
|
|
8c5c97e106 | ||
|
|
a9d3bd71b5 | ||
|
|
0d0a9b4cdf | ||
|
|
f583770696 | ||
|
|
bae243ccdb | ||
|
|
02c113a68f | ||
|
|
600eead007 | ||
|
|
05416768d5 | ||
|
|
35d31f2324 | ||
|
|
82fadac70e | ||
|
|
47f9d22dd8 | ||
|
|
9420dba3bc | ||
|
|
e462875807 | ||
|
|
0747d2c419 | ||
|
|
4c658b79ef | ||
|
|
685558847f | ||
|
|
63ac34a662 | ||
|
|
42f9e4aed2 | ||
|
|
e14790f0b9 |
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/codecs/**/*.js linguist-generated=true
|
||||||
|
/codecs/*/pkg*/*.d.ts linguist-generated=true
|
||||||
22
.github/workflows/node.js.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Node.js CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- id: nvmrc
|
||||||
|
uses: browniebroke/read-nvmrc-action@v1
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: '${{ steps.nvmrc.outputs.node_version }}'
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run build
|
||||||
12
.prettierignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
codecs
|
||||||
|
.tmp
|
||||||
|
node_modules
|
||||||
|
*.scss.d.ts
|
||||||
|
*.css.d.ts
|
||||||
|
build
|
||||||
|
*.o
|
||||||
|
|
||||||
|
# Auto-generated by lib/feature-plugin.js
|
||||||
|
src/features-worker/index.ts
|
||||||
|
src/client/lazy-app/worker-bridge/meta.ts
|
||||||
|
src/client/lazy-app/feature-meta/index.ts
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
cache: npm
|
|
||||||
script: npm run build
|
|
||||||
after_success: npm run sizereport
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- windows
|
|
||||||
0
codecs/avif/enc/avif_enc_mt.wasm
Executable file → Normal file
@@ -14,12 +14,33 @@ thread_local const val ImageData = val::global("ImageData");
|
|||||||
// R, G, B, A
|
// R, G, B, A
|
||||||
#define COMPONENTS_PER_PIXEL 4
|
#define COMPONENTS_PER_PIXEL 4
|
||||||
|
|
||||||
|
#ifndef JXL_DEBUG_ON_ALL_ERROR
|
||||||
|
#define JXL_DEBUG_ON_ALL_ERROR 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JXL_DEBUG_ON_ALL_ERROR
|
||||||
|
#define EXPECT_TRUE(a) \
|
||||||
|
if (!(a)) { \
|
||||||
|
fprintf(stderr, "Assertion failure (%d): %s\n", __LINE__, #a); \
|
||||||
|
return val::null(); \
|
||||||
|
}
|
||||||
|
#define EXPECT_EQ(a, b) \
|
||||||
|
{ \
|
||||||
|
int a_ = a; \
|
||||||
|
int b_ = b; \
|
||||||
|
if (a_ != b_) { \
|
||||||
|
fprintf(stderr, "Assertion failure (%d): %s (%d) != %s (%d)\n", __LINE__, #a, a_, #b, b_); \
|
||||||
|
return val::null(); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
#else
|
||||||
#define EXPECT_TRUE(a) \
|
#define EXPECT_TRUE(a) \
|
||||||
if (!(a)) { \
|
if (!(a)) { \
|
||||||
return val::null(); \
|
return val::null(); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define EXPECT_EQ(a, b) EXPECT_TRUE((a) == (b));
|
#define EXPECT_EQ(a, b) EXPECT_TRUE((a) == (b));
|
||||||
|
#endif
|
||||||
|
|
||||||
val decode(std::string data) {
|
val decode(std::string data) {
|
||||||
std::unique_ptr<JxlDecoder,
|
std::unique_ptr<JxlDecoder,
|
||||||
|
|||||||
@@ -38,7 +38,14 @@ val encode(std::string image, int width, int height, JXLOptions options) {
|
|||||||
|
|
||||||
// Reduce memory usage of tree learning for lossless data.
|
// Reduce memory usage of tree learning for lossless data.
|
||||||
// TODO(veluca93): this is a mitigation for excessive memory usage in the JXL encoder.
|
// TODO(veluca93): this is a mitigation for excessive memory usage in the JXL encoder.
|
||||||
cparams.options.nb_repeats = 0.1;
|
float megapixels = width * height * 0.000001;
|
||||||
|
if (megapixels > 8) {
|
||||||
|
cparams.options.nb_repeats = 0.1;
|
||||||
|
} else if (megapixels > 4) {
|
||||||
|
cparams.options.nb_repeats = 0.3;
|
||||||
|
} else {
|
||||||
|
// default is OK.
|
||||||
|
}
|
||||||
|
|
||||||
float quality = options.quality;
|
float quality = options.quality;
|
||||||
|
|
||||||
@@ -59,8 +66,10 @@ val encode(std::string image, int width, int height, JXLOptions options) {
|
|||||||
|
|
||||||
if (options.progressive) {
|
if (options.progressive) {
|
||||||
cparams.qprogressive_mode = true;
|
cparams.qprogressive_mode = true;
|
||||||
cparams.progressive_dc = 1;
|
|
||||||
cparams.responsive = 1;
|
cparams.responsive = 1;
|
||||||
|
if (!cparams.modular_mode) {
|
||||||
|
cparams.progressive_dc = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cparams.modular_mode) {
|
if (cparams.modular_mode) {
|
||||||
|
|||||||
12
codecs/oxipng/Cargo.lock
generated
@@ -247,18 +247,18 @@ checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdeflate-sys"
|
name = "libdeflate-sys"
|
||||||
version = "0.5.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21e39efa87b84db3e13ff4e2dfac1e57220abcbd7fe8ec44d238f7f4f787cc1f"
|
checksum = "2f5b1582a0ebf8c55a46166c04d7c66f6bb17add3a6cbf69a082ac2219f31671"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdeflater"
|
name = "libdeflater"
|
||||||
version = "0.5.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4810980d791f26d470e2d7d91a3d4d22aa3a4b709fb7e9c5e43ee54f83a01f2"
|
checksum = "93edd93a53970951da84ef733a8b6e30189a8f8a9e19610f69e4cc5bb1f4d654"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libdeflate-sys",
|
"libdeflate-sys",
|
||||||
]
|
]
|
||||||
@@ -359,9 +359,9 @@ checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxipng"
|
name = "oxipng"
|
||||||
version = "4.0.0"
|
version = "4.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea40b366cecfce76ee3b082e7e6567b82cdef75644a22442ca8584bc666ff4eb"
|
checksum = "9fefb26bde273c3db896a313151301a69e698a7495ee577fe2168ed7065c29c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-vec",
|
"bit-vec",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
|||||||
4
codecs/oxipng/build.sh
Executable file → Normal file
@@ -3,6 +3,8 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
rm -rf pkg,{-parallel}
|
rm -rf pkg,{-parallel}
|
||||||
wasm-pack build --target web
|
wasm-pack build -t web
|
||||||
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory' wasm-pack build -t web -d pkg-parallel -- -Z build-std=panic_abort,std --features=parallel
|
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory' wasm-pack build -t web -d pkg-parallel -- -Z build-std=panic_abort,std --features=parallel
|
||||||
|
# Workaround https://github.com/rustwasm/wasm-bindgen/issues/2133:
|
||||||
|
sed -i "s|maybe_memory:|maybe_memory?:|" pkg-parallel/squoosh_oxipng.d.ts
|
||||||
rm pkg{,-parallel}/.gitignore
|
rm pkg{,-parallel}/.gitignore
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "oxipng",
|
"name": "oxipng",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "RUST_IMG=rustlang/rust:8bb115b1090d ../build-rust.sh ./build.sh"
|
"build": "RUST_IMG=rustlang/rust@sha256:744aeea5a38f95aa7a96ec37269a65f0c6197a1cdd87d6534e12bb869141d807 ../build-rust.sh ./build.sh"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,4 @@
|
|||||||
"module": "squoosh_oxipng.js",
|
"module": "squoosh_oxipng.js",
|
||||||
"types": "squoosh_oxipng.d.ts",
|
"types": "squoosh_oxipng.d.ts",
|
||||||
"sideEffects": false
|
"sideEffects": false
|
||||||
}
|
}
|
||||||
47
codecs/oxipng/pkg-parallel/squoosh_oxipng.d.ts
generated
vendored
@@ -1,29 +1,24 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/**
|
/**
|
||||||
* @param {Uint8Array} data
|
* @param {Uint8Array} data
|
||||||
* @param {number} level
|
* @param {number} level
|
||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
export function optimise(data: Uint8Array, level: number): Uint8Array;
|
export function optimise(data: Uint8Array, level: number): Uint8Array;
|
||||||
/**
|
/**
|
||||||
* @param {number} num
|
* @param {number} num
|
||||||
* @returns {any}
|
* @returns {any}
|
||||||
*/
|
*/
|
||||||
export function worker_initializer(num: number): any;
|
export function worker_initializer(num: number): any;
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
export function start_main_thread(): void;
|
export function start_main_thread(): void;
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
export function start_worker_thread(): void;
|
export function start_worker_thread(): void;
|
||||||
|
|
||||||
export type InitInput =
|
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||||
| RequestInfo
|
|
||||||
| URL
|
|
||||||
| Response
|
|
||||||
| BufferSource
|
|
||||||
| WebAssembly.Module;
|
|
||||||
|
|
||||||
export interface InitOutput {
|
export interface InitOutput {
|
||||||
readonly optimise: (a: number, b: number, c: number, d: number) => void;
|
readonly optimise: (a: number, b: number, c: number, d: number) => void;
|
||||||
@@ -39,15 +34,13 @@ export interface InitOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||||
*
|
*
|
||||||
* @param {InitInput | Promise<InitInput>} module_or_path
|
* @param {InitInput | Promise<InitInput>} module_or_path
|
||||||
* @param {WebAssembly.Memory} maybe_memory
|
* @param {WebAssembly.Memory} maybe_memory
|
||||||
*
|
*
|
||||||
* @returns {Promise<InitOutput>}
|
* @returns {Promise<InitOutput>}
|
||||||
*/
|
*/
|
||||||
export default function init(
|
export default function init (module_or_path?: InitInput | Promise<InitInput>, maybe_memory?: WebAssembly.Memory): Promise<InitOutput>;
|
||||||
module_or_path?: InitInput | Promise<InitInput>,
|
|
||||||
maybe_memory?: WebAssembly.Memory,
|
|
||||||
): Promise<InitOutput>;
|
|
||||||
234
codecs/oxipng/pkg-parallel/squoosh_oxipng.js
generated
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
let wasm;
|
let wasm;
|
||||||
let memory;
|
let memory;
|
||||||
|
|
||||||
@@ -8,189 +9,172 @@ heap.push(undefined, null, true, false);
|
|||||||
let heap_next = heap.length;
|
let heap_next = heap.length;
|
||||||
|
|
||||||
function addHeapObject(obj) {
|
function addHeapObject(obj) {
|
||||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||||
const idx = heap_next;
|
const idx = heap_next;
|
||||||
heap_next = heap[idx];
|
heap_next = heap[idx];
|
||||||
|
|
||||||
heap[idx] = obj;
|
heap[idx] = obj;
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedTextDecoder = new TextDecoder('utf-8', {
|
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||||
ignoreBOM: true,
|
|
||||||
fatal: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
cachedTextDecoder.decode();
|
cachedTextDecoder.decode();
|
||||||
|
|
||||||
let cachegetUint8Memory0 = null;
|
let cachegetUint8Memory0 = null;
|
||||||
function getUint8Memory0() {
|
function getUint8Memory0() {
|
||||||
if (
|
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.__wbindgen_export_0.buffer) {
|
||||||
cachegetUint8Memory0 === null ||
|
cachegetUint8Memory0 = new Uint8Array(wasm.__wbindgen_export_0.buffer);
|
||||||
cachegetUint8Memory0.buffer !== wasm.__wbindgen_export_0.buffer
|
}
|
||||||
) {
|
return cachegetUint8Memory0;
|
||||||
cachegetUint8Memory0 = new Uint8Array(wasm.__wbindgen_export_0.buffer);
|
|
||||||
}
|
|
||||||
return cachegetUint8Memory0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStringFromWasm0(ptr, len) {
|
function getStringFromWasm0(ptr, len) {
|
||||||
return cachedTextDecoder.decode(getUint8Memory0().slice(ptr, ptr + len));
|
return cachedTextDecoder.decode(getUint8Memory0().slice(ptr, ptr + len));
|
||||||
}
|
}
|
||||||
|
|
||||||
let WASM_VECTOR_LEN = 0;
|
let WASM_VECTOR_LEN = 0;
|
||||||
|
|
||||||
function passArray8ToWasm0(arg, malloc) {
|
function passArray8ToWasm0(arg, malloc) {
|
||||||
const ptr = malloc(arg.length * 1);
|
const ptr = malloc(arg.length * 1);
|
||||||
getUint8Memory0().set(arg, ptr / 1);
|
getUint8Memory0().set(arg, ptr / 1);
|
||||||
WASM_VECTOR_LEN = arg.length;
|
WASM_VECTOR_LEN = arg.length;
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachegetInt32Memory0 = null;
|
let cachegetInt32Memory0 = null;
|
||||||
function getInt32Memory0() {
|
function getInt32Memory0() {
|
||||||
if (
|
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.__wbindgen_export_0.buffer) {
|
||||||
cachegetInt32Memory0 === null ||
|
cachegetInt32Memory0 = new Int32Array(wasm.__wbindgen_export_0.buffer);
|
||||||
cachegetInt32Memory0.buffer !== wasm.__wbindgen_export_0.buffer
|
}
|
||||||
) {
|
return cachegetInt32Memory0;
|
||||||
cachegetInt32Memory0 = new Int32Array(wasm.__wbindgen_export_0.buffer);
|
|
||||||
}
|
|
||||||
return cachegetInt32Memory0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArrayU8FromWasm0(ptr, len) {
|
function getArrayU8FromWasm0(ptr, len) {
|
||||||
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
|
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {Uint8Array} data
|
* @param {Uint8Array} data
|
||||||
* @param {number} level
|
* @param {number} level
|
||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
export function optimise(data, level) {
|
export function optimise(data, level) {
|
||||||
try {
|
try {
|
||||||
const retptr = wasm.__wbindgen_export_1.value - 16;
|
const retptr = wasm.__wbindgen_export_1.value - 16;
|
||||||
wasm.__wbindgen_export_1.value = retptr;
|
wasm.__wbindgen_export_1.value = retptr;
|
||||||
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
|
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
|
||||||
var len0 = WASM_VECTOR_LEN;
|
var len0 = WASM_VECTOR_LEN;
|
||||||
wasm.optimise(retptr, ptr0, len0, level);
|
wasm.optimise(retptr, ptr0, len0, level);
|
||||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||||
var v1 = getArrayU8FromWasm0(r0, r1).slice();
|
var v1 = getArrayU8FromWasm0(r0, r1).slice();
|
||||||
wasm.__wbindgen_free(r0, r1 * 1);
|
wasm.__wbindgen_free(r0, r1 * 1);
|
||||||
return v1;
|
return v1;
|
||||||
} finally {
|
} finally {
|
||||||
wasm.__wbindgen_export_1.value += 16;
|
wasm.__wbindgen_export_1.value += 16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getObject(idx) {
|
function getObject(idx) { return heap[idx]; }
|
||||||
return heap[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
function dropObject(idx) {
|
function dropObject(idx) {
|
||||||
if (idx < 36) return;
|
if (idx < 36) return;
|
||||||
heap[idx] = heap_next;
|
heap[idx] = heap_next;
|
||||||
heap_next = idx;
|
heap_next = idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
function takeObject(idx) {
|
function takeObject(idx) {
|
||||||
const ret = getObject(idx);
|
const ret = getObject(idx);
|
||||||
dropObject(idx);
|
dropObject(idx);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {number} num
|
* @param {number} num
|
||||||
* @returns {any}
|
* @returns {any}
|
||||||
*/
|
*/
|
||||||
export function worker_initializer(num) {
|
export function worker_initializer(num) {
|
||||||
var ret = wasm.worker_initializer(num);
|
var ret = wasm.worker_initializer(num);
|
||||||
return takeObject(ret);
|
return takeObject(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
export function start_main_thread() {
|
export function start_main_thread() {
|
||||||
wasm.start_main_thread();
|
wasm.start_main_thread();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
export function start_worker_thread() {
|
export function start_worker_thread() {
|
||||||
wasm.start_worker_thread();
|
wasm.start_worker_thread();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load(module, imports, maybe_memory) {
|
async function load(module, imports, maybe_memory) {
|
||||||
if (typeof Response === 'function' && module instanceof Response) {
|
if (typeof Response === 'function' && module instanceof Response) {
|
||||||
memory = imports.wbg.memory = new WebAssembly.Memory({
|
memory = imports.wbg.memory = new WebAssembly.Memory({initial:17,maximum:16384,shared:true});
|
||||||
initial: 17,
|
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||||
maximum: 16384,
|
try {
|
||||||
shared: true,
|
return await WebAssembly.instantiateStreaming(module, imports);
|
||||||
});
|
|
||||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
} catch (e) {
|
||||||
try {
|
if (module.headers.get('Content-Type') != 'application/wasm') {
|
||||||
return await WebAssembly.instantiateStreaming(module, imports);
|
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||||
} catch (e) {
|
|
||||||
if (module.headers.get('Content-Type') != 'application/wasm') {
|
} else {
|
||||||
console.warn(
|
throw e;
|
||||||
'`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n',
|
}
|
||||||
e,
|
}
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bytes = await module.arrayBuffer();
|
const bytes = await module.arrayBuffer();
|
||||||
return await WebAssembly.instantiate(bytes, imports);
|
return await WebAssembly.instantiate(bytes, imports);
|
||||||
} else {
|
|
||||||
memory = imports.wbg.memory = maybe_memory;
|
|
||||||
const instance = await WebAssembly.instantiate(module, imports);
|
|
||||||
|
|
||||||
if (instance instanceof WebAssembly.Instance) {
|
|
||||||
return { instance, module };
|
|
||||||
} else {
|
} else {
|
||||||
return instance;
|
memory = imports.wbg.memory = maybe_memory;
|
||||||
|
const instance = await WebAssembly.instantiate(module, imports);
|
||||||
|
|
||||||
|
if (instance instanceof WebAssembly.Instance) {
|
||||||
|
return { instance, module };
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init(input, maybe_memory) {
|
async function init(input, maybe_memory) {
|
||||||
if (typeof input === 'undefined') {
|
if (typeof input === 'undefined') {
|
||||||
input = import.meta.url.replace(/\.js$/, '_bg.wasm');
|
input = import.meta.url.replace(/\.js$/, '_bg.wasm');
|
||||||
}
|
}
|
||||||
const imports = {};
|
const imports = {};
|
||||||
imports.wbg = {};
|
imports.wbg = {};
|
||||||
imports.wbg.__wbindgen_module = function () {
|
imports.wbg.__wbindgen_module = function() {
|
||||||
var ret = init.__wbindgen_wasm_module;
|
var ret = init.__wbindgen_wasm_module;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_memory = function () {
|
imports.wbg.__wbindgen_memory = function() {
|
||||||
var ret = wasm.__wbindgen_export_0;
|
var ret = wasm.__wbindgen_export_0;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_of_6510501edc06d65e = function (arg0, arg1) {
|
imports.wbg.__wbg_of_6510501edc06d65e = function(arg0, arg1) {
|
||||||
var ret = Array.of(takeObject(arg0), takeObject(arg1));
|
var ret = Array.of(takeObject(arg0), takeObject(arg1));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_throw = function (arg0, arg1) {
|
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
|
||||||
typeof input === 'string' ||
|
input = fetch(input);
|
||||||
(typeof Request === 'function' && input instanceof Request) ||
|
}
|
||||||
(typeof URL === 'function' && input instanceof URL)
|
|
||||||
) {
|
|
||||||
input = fetch(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { instance, module } = await load(await input, imports, maybe_memory);
|
const { instance, module } = await load(await input, imports, maybe_memory);
|
||||||
|
|
||||||
wasm = instance.exports;
|
wasm = instance.exports;
|
||||||
init.__wbindgen_wasm_module = module;
|
init.__wbindgen_wasm_module = module;
|
||||||
wasm.__wbindgen_start();
|
wasm.__wbindgen_start();
|
||||||
return wasm;
|
return wasm;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default init;
|
export default init;
|
||||||
|
|
||||||
|
|||||||
34
codecs/oxipng/pkg/squoosh_oxipng.d.ts
generated
vendored
@@ -1,18 +1,13 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/**
|
/**
|
||||||
* @param {Uint8Array} data
|
* @param {Uint8Array} data
|
||||||
* @param {number} level
|
* @param {number} level
|
||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
export function optimise(data: Uint8Array, level: number): Uint8Array;
|
export function optimise(data: Uint8Array, level: number): Uint8Array;
|
||||||
|
|
||||||
export type InitInput =
|
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||||
| RequestInfo
|
|
||||||
| URL
|
|
||||||
| Response
|
|
||||||
| BufferSource
|
|
||||||
| WebAssembly.Module;
|
|
||||||
|
|
||||||
export interface InitOutput {
|
export interface InitOutput {
|
||||||
readonly memory: WebAssembly.Memory;
|
readonly memory: WebAssembly.Memory;
|
||||||
@@ -24,13 +19,12 @@ export interface InitOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||||
*
|
*
|
||||||
* @param {InitInput | Promise<InitInput>} module_or_path
|
* @param {InitInput | Promise<InitInput>} module_or_path
|
||||||
*
|
*
|
||||||
* @returns {Promise<InitOutput>}
|
* @returns {Promise<InitOutput>}
|
||||||
*/
|
*/
|
||||||
export default function init(
|
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||||
module_or_path?: InitInput | Promise<InitInput>,
|
|
||||||
): Promise<InitOutput>;
|
|
||||||
158
codecs/oxipng/pkg/squoosh_oxipng.js
generated
@@ -1,126 +1,118 @@
|
|||||||
|
|
||||||
let wasm;
|
let wasm;
|
||||||
|
|
||||||
let cachedTextDecoder = new TextDecoder('utf-8', {
|
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||||
ignoreBOM: true,
|
|
||||||
fatal: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
cachedTextDecoder.decode();
|
cachedTextDecoder.decode();
|
||||||
|
|
||||||
let cachegetUint8Memory0 = null;
|
let cachegetUint8Memory0 = null;
|
||||||
function getUint8Memory0() {
|
function getUint8Memory0() {
|
||||||
if (
|
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
|
||||||
cachegetUint8Memory0 === null ||
|
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
||||||
cachegetUint8Memory0.buffer !== wasm.memory.buffer
|
}
|
||||||
) {
|
return cachegetUint8Memory0;
|
||||||
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
|
||||||
}
|
|
||||||
return cachegetUint8Memory0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStringFromWasm0(ptr, len) {
|
function getStringFromWasm0(ptr, len) {
|
||||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||||
}
|
}
|
||||||
|
|
||||||
let WASM_VECTOR_LEN = 0;
|
let WASM_VECTOR_LEN = 0;
|
||||||
|
|
||||||
function passArray8ToWasm0(arg, malloc) {
|
function passArray8ToWasm0(arg, malloc) {
|
||||||
const ptr = malloc(arg.length * 1);
|
const ptr = malloc(arg.length * 1);
|
||||||
getUint8Memory0().set(arg, ptr / 1);
|
getUint8Memory0().set(arg, ptr / 1);
|
||||||
WASM_VECTOR_LEN = arg.length;
|
WASM_VECTOR_LEN = arg.length;
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachegetInt32Memory0 = null;
|
let cachegetInt32Memory0 = null;
|
||||||
function getInt32Memory0() {
|
function getInt32Memory0() {
|
||||||
if (
|
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
|
||||||
cachegetInt32Memory0 === null ||
|
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
||||||
cachegetInt32Memory0.buffer !== wasm.memory.buffer
|
}
|
||||||
) {
|
return cachegetInt32Memory0;
|
||||||
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
|
||||||
}
|
|
||||||
return cachegetInt32Memory0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArrayU8FromWasm0(ptr, len) {
|
function getArrayU8FromWasm0(ptr, len) {
|
||||||
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
|
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {Uint8Array} data
|
* @param {Uint8Array} data
|
||||||
* @param {number} level
|
* @param {number} level
|
||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
export function optimise(data, level) {
|
export function optimise(data, level) {
|
||||||
try {
|
try {
|
||||||
const retptr = wasm.__wbindgen_export_0.value - 16;
|
const retptr = wasm.__wbindgen_export_0.value - 16;
|
||||||
wasm.__wbindgen_export_0.value = retptr;
|
wasm.__wbindgen_export_0.value = retptr;
|
||||||
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
|
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
|
||||||
var len0 = WASM_VECTOR_LEN;
|
var len0 = WASM_VECTOR_LEN;
|
||||||
wasm.optimise(retptr, ptr0, len0, level);
|
wasm.optimise(retptr, ptr0, len0, level);
|
||||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||||
var v1 = getArrayU8FromWasm0(r0, r1).slice();
|
var v1 = getArrayU8FromWasm0(r0, r1).slice();
|
||||||
wasm.__wbindgen_free(r0, r1 * 1);
|
wasm.__wbindgen_free(r0, r1 * 1);
|
||||||
return v1;
|
return v1;
|
||||||
} finally {
|
} finally {
|
||||||
wasm.__wbindgen_export_0.value += 16;
|
wasm.__wbindgen_export_0.value += 16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load(module, imports) {
|
async function load(module, imports) {
|
||||||
if (typeof Response === 'function' && module instanceof Response) {
|
if (typeof Response === 'function' && module instanceof Response) {
|
||||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
|
||||||
try {
|
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||||
return await WebAssembly.instantiateStreaming(module, imports);
|
try {
|
||||||
} catch (e) {
|
return await WebAssembly.instantiateStreaming(module, imports);
|
||||||
if (module.headers.get('Content-Type') != 'application/wasm') {
|
|
||||||
console.warn(
|
} catch (e) {
|
||||||
'`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n',
|
if (module.headers.get('Content-Type') != 'application/wasm') {
|
||||||
e,
|
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bytes = await module.arrayBuffer();
|
const bytes = await module.arrayBuffer();
|
||||||
return await WebAssembly.instantiate(bytes, imports);
|
return await WebAssembly.instantiate(bytes, imports);
|
||||||
} else {
|
|
||||||
const instance = await WebAssembly.instantiate(module, imports);
|
|
||||||
|
|
||||||
if (instance instanceof WebAssembly.Instance) {
|
|
||||||
return { instance, module };
|
|
||||||
} else {
|
} else {
|
||||||
return instance;
|
|
||||||
|
const instance = await WebAssembly.instantiate(module, imports);
|
||||||
|
|
||||||
|
if (instance instanceof WebAssembly.Instance) {
|
||||||
|
return { instance, module };
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init(input) {
|
async function init(input) {
|
||||||
if (typeof input === 'undefined') {
|
if (typeof input === 'undefined') {
|
||||||
input = import.meta.url.replace(/\.js$/, '_bg.wasm');
|
input = import.meta.url.replace(/\.js$/, '_bg.wasm');
|
||||||
}
|
}
|
||||||
const imports = {};
|
const imports = {};
|
||||||
imports.wbg = {};
|
imports.wbg = {};
|
||||||
imports.wbg.__wbindgen_throw = function (arg0, arg1) {
|
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
|
||||||
typeof input === 'string' ||
|
input = fetch(input);
|
||||||
(typeof Request === 'function' && input instanceof Request) ||
|
}
|
||||||
(typeof URL === 'function' && input instanceof URL)
|
|
||||||
) {
|
|
||||||
input = fetch(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { instance, module } = await load(await input, imports);
|
const { instance, module } = await load(await input, imports);
|
||||||
|
|
||||||
wasm = instance.exports;
|
wasm = instance.exports;
|
||||||
init.__wbindgen_wasm_module = module;
|
init.__wbindgen_wasm_module = module;
|
||||||
|
|
||||||
return wasm;
|
return wasm;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default init;
|
export default init;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use wasm_bindgen::prelude::*;
|
|
||||||
use oxipng::AlphaOptim;
|
use oxipng::AlphaOptim;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
mod malloc_shim;
|
mod malloc_shim;
|
||||||
|
|
||||||
@@ -8,14 +8,14 @@ pub mod parallel;
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn optimise(data: &[u8], level: u8) -> Vec<u8> {
|
pub fn optimise(data: &[u8], level: u8) -> Vec<u8> {
|
||||||
let mut options = oxipng::Options::from_preset(level);
|
let mut options = oxipng::Options::from_preset(level);
|
||||||
options.alphas.insert(AlphaOptim::Black);
|
options.alphas.insert(AlphaOptim::Black);
|
||||||
options.alphas.insert(AlphaOptim::White);
|
options.alphas.insert(AlphaOptim::White);
|
||||||
options.alphas.insert(AlphaOptim::Up);
|
options.alphas.insert(AlphaOptim::Up);
|
||||||
options.alphas.insert(AlphaOptim::Down);
|
options.alphas.insert(AlphaOptim::Down);
|
||||||
options.alphas.insert(AlphaOptim::Left);
|
options.alphas.insert(AlphaOptim::Left);
|
||||||
options.alphas.insert(AlphaOptim::Right);
|
options.alphas.insert(AlphaOptim::Right);
|
||||||
|
|
||||||
options.deflate = oxipng::Deflaters::Libdeflater;
|
options.deflate = oxipng::Deflaters::Libdeflater;
|
||||||
oxipng::optimize_from_memory(data, &options).unwrap_throw()
|
oxipng::optimize_from_memory(data, &options).unwrap_throw()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crossbeam_channel::{Sender, Receiver, bounded};
|
use crossbeam_channel::{bounded, Receiver, Sender};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
@@ -35,7 +35,8 @@ extern "C" {
|
|||||||
// shared memory and blocks the current thread until they're all grabbed.
|
// shared memory and blocks the current thread until they're all grabbed.
|
||||||
// 4) Provide a `worker_initializer` that is expected to be invoked from various workers,
|
// 4) Provide a `worker_initializer` that is expected to be invoked from various workers,
|
||||||
// reads one `threadPtr` from the shared channel and starts running it.
|
// reads one `threadPtr` from the shared channel and starts running it.
|
||||||
static CHANNEL: OnceCell<(Sender<rayon::ThreadBuilder>, Receiver<rayon::ThreadBuilder>)> = OnceCell::new();
|
static CHANNEL: OnceCell<(Sender<rayon::ThreadBuilder>, Receiver<rayon::ThreadBuilder>)> =
|
||||||
|
OnceCell::new();
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn worker_initializer(num: usize) -> JsValue {
|
pub fn worker_initializer(num: usize) -> JsValue {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ RUN wget -qO- https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1/was
|
|||||||
FROM $RUST_IMG AS rust
|
FROM $RUST_IMG AS rust
|
||||||
ARG RUST_IMG
|
ARG RUST_IMG
|
||||||
RUN rustup target add wasm32-unknown-unknown
|
RUN rustup target add wasm32-unknown-unknown
|
||||||
RUN if [[ $RUST_IMG = rustlang/rust:* ]] ; then rustup component add rust-src ; fi
|
RUN case $RUST_IMG in rustlang/rust@*) rustup component add rust-src; esac
|
||||||
COPY --from=wasm-tools /emsdk/upstream/bin/wasm-opt /emsdk/upstream/bin/clang /usr/local/bin/
|
COPY --from=wasm-tools /emsdk/upstream/bin/wasm-opt /emsdk/upstream/bin/clang /usr/local/bin/
|
||||||
COPY --from=wasm-tools /emsdk/upstream/lib/ /usr/local/lib/
|
COPY --from=wasm-tools /emsdk/upstream/lib/ /usr/local/lib/
|
||||||
COPY --from=wasm-tools /emsdk/upstream/emscripten/system/include/libc/ /wasm32/include/
|
COPY --from=wasm-tools /emsdk/upstream/emscripten/system/include/libc/ /wasm32/include/
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ val decode(std::string buffer) {
|
|||||||
int width, height;
|
int width, height;
|
||||||
std::unique_ptr<uint8_t[]> rgba(
|
std::unique_ptr<uint8_t[]> rgba(
|
||||||
WebPDecodeRGBA((const uint8_t*)buffer.c_str(), buffer.size(), &width, &height));
|
WebPDecodeRGBA((const uint8_t*)buffer.c_str(), buffer.size(), &width, &height));
|
||||||
return rgba ? ImageData.new_(Uint8ClampedArray.new_(typed_memory_view(width * height * 4, rgba.get())), width, height) : val::null();
|
return rgba ? ImageData.new_(
|
||||||
|
Uint8ClampedArray.new_(typed_memory_view(width * height * 4, rgba.get())),
|
||||||
|
width, height)
|
||||||
|
: val::null();
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_BINDINGS(my_module) {
|
EMSCRIPTEN_BINDINGS(my_module) {
|
||||||
|
|||||||
@@ -7,7 +7,31 @@ using namespace emscripten;
|
|||||||
|
|
||||||
thread_local const val Uint8Array = val::global("Uint8Array");
|
thread_local const val Uint8Array = val::global("Uint8Array");
|
||||||
|
|
||||||
val encode(std::string image_in, int image_width, int image_height, WP2::EncoderConfig config) {
|
struct WP2Options {
|
||||||
|
float quality;
|
||||||
|
float alpha_quality;
|
||||||
|
int speed;
|
||||||
|
int pass;
|
||||||
|
int uv_mode;
|
||||||
|
float sns;
|
||||||
|
int csp_type;
|
||||||
|
int error_diffusion;
|
||||||
|
bool use_random_matrix;
|
||||||
|
};
|
||||||
|
|
||||||
|
val encode(std::string image_in, int image_width, int image_height, WP2Options options) {
|
||||||
|
WP2::EncoderConfig config = {};
|
||||||
|
|
||||||
|
config.quality = options.quality;
|
||||||
|
config.alpha_quality = options.alpha_quality;
|
||||||
|
config.speed = options.speed;
|
||||||
|
config.pass = options.pass;
|
||||||
|
config.uv_mode = static_cast<WP2::EncoderConfig::UVMode>(options.uv_mode);
|
||||||
|
config.csp_type = static_cast<WP2::Csp>(options.csp_type);
|
||||||
|
config.sns = options.sns;
|
||||||
|
config.error_diffusion = options.error_diffusion;
|
||||||
|
config.use_random_matrix = options.use_random_matrix;
|
||||||
|
|
||||||
uint8_t* image_buffer = (uint8_t*)image_in.c_str();
|
uint8_t* image_buffer = (uint8_t*)image_in.c_str();
|
||||||
WP2::ArgbBuffer src = WP2::ArgbBuffer();
|
WP2::ArgbBuffer src = WP2::ArgbBuffer();
|
||||||
WP2Status status =
|
WP2Status status =
|
||||||
@@ -27,12 +51,16 @@ val encode(std::string image_in, int image_width, int image_height, WP2::Encoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_BINDINGS(my_module) {
|
EMSCRIPTEN_BINDINGS(my_module) {
|
||||||
value_object<WP2::EncoderConfig>("WP2EncoderConfig")
|
value_object<WP2Options>("WP2Options")
|
||||||
.field("quality", &WP2::EncoderConfig::quality)
|
.field("quality", &WP2Options::quality)
|
||||||
.field("alpha_quality", &WP2::EncoderConfig::alpha_quality)
|
.field("alpha_quality", &WP2Options::alpha_quality)
|
||||||
.field("speed", &WP2::EncoderConfig::speed)
|
.field("speed", &WP2Options::speed)
|
||||||
.field("pass", &WP2::EncoderConfig::pass)
|
.field("pass", &WP2Options::pass)
|
||||||
.field("sns", &WP2::EncoderConfig::sns);
|
.field("uv_mode", &WP2Options::uv_mode)
|
||||||
|
.field("csp_type", &WP2Options::csp_type)
|
||||||
|
.field("error_diffusion", &WP2Options::error_diffusion)
|
||||||
|
.field("use_random_matrix", &WP2Options::use_random_matrix)
|
||||||
|
.field("sns", &WP2Options::sns);
|
||||||
|
|
||||||
function("encode", &encode);
|
function("encode", &encode);
|
||||||
}
|
}
|
||||||
|
|||||||
18
codecs/wp2/enc/wp2_enc.d.ts
vendored
@@ -4,6 +4,24 @@ export interface EncodeOptions {
|
|||||||
speed: number;
|
speed: number;
|
||||||
pass: number;
|
pass: number;
|
||||||
sns: number;
|
sns: number;
|
||||||
|
uv_mode: UVMode;
|
||||||
|
csp_type: Csp;
|
||||||
|
error_diffusion: number;
|
||||||
|
use_random_matrix: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum UVMode {
|
||||||
|
UVModeAdapt = 0, // Mix of 420 and 444 (per block)
|
||||||
|
UVMode420, // All blocks 420
|
||||||
|
UVMode444, // All blocks 444
|
||||||
|
UVModeAuto, // Choose any of the above automatically
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum Csp {
|
||||||
|
kYCoCg,
|
||||||
|
kYCbCr,
|
||||||
|
kCustom,
|
||||||
|
kYIQ,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WP2Module extends EmscriptenWasm.Module {
|
export interface WP2Module extends EmscriptenWasm.Module {
|
||||||
|
|||||||
12
codecs/wp2/enc/wp2_enc.js
generated
@@ -576,7 +576,7 @@ var wp2_enc = (function () {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
var ob = {
|
var ob = {
|
||||||
p: function (a, b, c, d) {
|
t: function (a, b, c, d) {
|
||||||
A(
|
A(
|
||||||
'Assertion failed: ' +
|
'Assertion failed: ' +
|
||||||
C(a) +
|
C(a) +
|
||||||
@@ -807,7 +807,7 @@ var wp2_enc = (function () {
|
|||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
c: function (a, b, c, d, e) {
|
d: function (a, b, c, d, e) {
|
||||||
function g(l) {
|
function g(l) {
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
@@ -1000,10 +1000,10 @@ var wp2_enc = (function () {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
q: function (a, b, c, d, e, g) {
|
p: function (a, b, c, d, e, g) {
|
||||||
Fa[a] = { name: U(b), da: Y(c, d), ea: Y(e, g), U: [] };
|
Fa[a] = { name: U(b), da: Y(c, d), ea: Y(e, g), U: [] };
|
||||||
},
|
},
|
||||||
e: function (a, b, c, d, e, g, m, h, k, l) {
|
c: function (a, b, c, d, e, g, m, h, k, l) {
|
||||||
Fa[a].U.push({
|
Fa[a].U.push({
|
||||||
X: U(b),
|
X: U(b),
|
||||||
aa: c,
|
aa: c,
|
||||||
@@ -1034,7 +1034,7 @@ var wp2_enc = (function () {
|
|||||||
m: function (a) {
|
m: function (a) {
|
||||||
4 < a && (X[a].T += 1);
|
4 < a && (X[a].T += 1);
|
||||||
},
|
},
|
||||||
t: function (a, b, c, d) {
|
q: function (a, b, c, d) {
|
||||||
a || W('Cannot use deleted val. handle = ' + a);
|
a || W('Cannot use deleted val. handle = ' + a);
|
||||||
a = X[a].value;
|
a = X[a].value;
|
||||||
var e = ib[b];
|
var e = ib[b];
|
||||||
@@ -1079,7 +1079,7 @@ var wp2_enc = (function () {
|
|||||||
u: function (a, b, c) {
|
u: function (a, b, c) {
|
||||||
D.copyWithin(a, b, b + c);
|
D.copyWithin(a, b, b + c);
|
||||||
},
|
},
|
||||||
d: function (a) {
|
e: function (a) {
|
||||||
a >>>= 0;
|
a >>>= 0;
|
||||||
var b = D.length;
|
var b = D.length;
|
||||||
if (2147483648 < a) return !1;
|
if (2147483648 < a) return !1;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import rollup from 'rollup';
|
import rollup from 'rollup';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
const prefix = 'client-bundle:';
|
const prefix = 'client-bundle:';
|
||||||
const entryPathPlaceholder = 'CLIENT_BUNDLE_PLUGIN_ENTRY_PATH';
|
const entryPathPlaceholder = 'CLIENT_BUNDLE_PLUGIN_ENTRY_PATH';
|
||||||
@@ -119,9 +120,10 @@ export default function (inputOptions, outputOptions, resolveFileUrl) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = entryPointPlaceholderMap.get(num);
|
const id = path.normalize(entryPointPlaceholderMap.get(num));
|
||||||
const clientEntry = clientOutput.find(
|
const clientEntry = clientOutput.find(
|
||||||
(item) => item.facadeModuleId === id,
|
(item) =>
|
||||||
|
item.facadeModuleId && path.normalize(item.facadeModuleId) === id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (property.startsWith(entryPathPlaceholder)) {
|
if (property.startsWith(entryPathPlaceholder)) {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
resolve as resolvePath,
|
resolve as resolvePath,
|
||||||
dirname,
|
dirname,
|
||||||
normalize as nomalizePath,
|
normalize as nomalizePath,
|
||||||
|
sep as pathSep,
|
||||||
|
posix,
|
||||||
} from 'path';
|
} from 'path';
|
||||||
|
|
||||||
import postcss from 'postcss';
|
import postcss from 'postcss';
|
||||||
@@ -39,6 +41,7 @@ const assetRe = new RegExp('/fake/path/to/asset/([^/]+)/', 'g');
|
|||||||
const appendCssModule = '\0appendCss';
|
const appendCssModule = '\0appendCss';
|
||||||
const appendCssSource = `
|
const appendCssSource = `
|
||||||
export default function appendCss(css) {
|
export default function appendCss(css) {
|
||||||
|
if (__PRERENDER__) return;
|
||||||
const style = document.createElement('style');
|
const style = document.createElement('style');
|
||||||
style.textContent = css;
|
style.textContent = css;
|
||||||
document.head.append(style);
|
document.head.append(style);
|
||||||
@@ -172,14 +175,15 @@ export default function (resolveFileUrl) {
|
|||||||
return `export default ${cssStr};`;
|
return `export default ${cssStr};`;
|
||||||
}
|
}
|
||||||
if (id.startsWith(addPrefix)) {
|
if (id.startsWith(addPrefix)) {
|
||||||
const path = id.slice(addPrefix.length);
|
const path = nomalizePath(id.slice(addPrefix.length));
|
||||||
return (
|
return (
|
||||||
`import css from 'css:${path}';\n` +
|
`import css from ${JSON.stringify('css:' + path)};\n` +
|
||||||
`import appendCss from '${appendCssModule}';\n` +
|
`import appendCss from '${appendCssModule}';\n` +
|
||||||
`appendCss(css);\n`
|
`appendCss(css);\n`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (id.endsWith(moduleSuffix)) {
|
if (id.endsWith(moduleSuffix)) {
|
||||||
|
const path = nomalizePath(id.slice(0, -moduleSuffix.length));
|
||||||
if (!pathToResult.has(id)) {
|
if (!pathToResult.has(id)) {
|
||||||
throw Error(`Cannot find ${id} in pathToResult`);
|
throw Error(`Cannot find ${id} in pathToResult`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,17 +19,18 @@ import glob from 'glob';
|
|||||||
const globP = promisify(glob);
|
const globP = promisify(glob);
|
||||||
|
|
||||||
const moduleId = 'initial-css:';
|
const moduleId = 'initial-css:';
|
||||||
|
const initialCssModule = '\0initialCss';
|
||||||
|
|
||||||
export default function initialCssPlugin() {
|
export default function initialCssPlugin() {
|
||||||
return {
|
return {
|
||||||
name: 'initial-css-plugin',
|
name: 'initial-css-plugin',
|
||||||
resolveId(id) {
|
resolveId(id) {
|
||||||
if (id === moduleId) return moduleId;
|
if (id === moduleId) return initialCssModule;
|
||||||
},
|
},
|
||||||
async load(id) {
|
async load(id) {
|
||||||
if (id !== moduleId) return;
|
if (id !== initialCssModule) return;
|
||||||
|
|
||||||
const matches = await globP('shared/initial-app/**/*.css', {
|
const matches = await globP('shared/prerendered-app/**/*.css', {
|
||||||
nodir: true,
|
nodir: true,
|
||||||
cwd: path.join(process.cwd(), 'src'),
|
cwd: path.join(process.cwd(), 'src'),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { posix as pathUtils } from 'path';
|
import { posix as pathUtils, isAbsolute } from 'path';
|
||||||
|
|
||||||
export default function resolveDirs(paths) {
|
export default function resolveDirs(paths) {
|
||||||
const pathBaseDir = paths.map((path) => [
|
const pathBaseDir = paths.map((path) => [
|
||||||
@@ -30,6 +30,7 @@ export default function resolveDirs(paths) {
|
|||||||
if (!resolveResult) {
|
if (!resolveResult) {
|
||||||
throw new Error(`Couldn't find ${'./' + id}`);
|
throw new Error(`Couldn't find ${'./' + id}`);
|
||||||
}
|
}
|
||||||
|
if (isAbsolute(resolveResult.id)) return resolveResult.id;
|
||||||
return pathUtils.resolve(resolveResult.id);
|
return pathUtils.resolve(resolveResult.id);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -127,15 +127,6 @@ export default function simpleTS(mainPath, { noBuild, watch } = {}) {
|
|||||||
relative(process.cwd(), id),
|
relative(process.cwd(), id),
|
||||||
).replace(extRe, '.js');
|
).replace(extRe, '.js');
|
||||||
|
|
||||||
console.log(
|
|
||||||
`simple-ts mapping`,
|
|
||||||
id,
|
|
||||||
'to',
|
|
||||||
newId,
|
|
||||||
'with outDir',
|
|
||||||
config.options.outDir,
|
|
||||||
);
|
|
||||||
|
|
||||||
return fsp.readFile(newId, { encoding: 'utf8' });
|
return fsp.readFile(newId, { encoding: 'utf8' });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
8538
package-lock.json
generated
12
package.json
@@ -6,8 +6,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c && node lib/move-output.js",
|
"build": "rollup -c && node lib/move-output.js",
|
||||||
"debug": "node --inspect-brk node_modules/.bin/rollup -c",
|
"debug": "node --inspect-brk node_modules/.bin/rollup -c",
|
||||||
"dev": "rollup -cw & npm run serve",
|
"dev": "run-p watch serve",
|
||||||
"serve": "serve --config server.json .tmp/build/static"
|
"watch": "rollup -cw",
|
||||||
|
"serve": "serve --config ../../../serve.json .tmp/build/static"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^15.1.0",
|
"@rollup/plugin-commonjs": "^15.1.0",
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
"lint-staged": "^10.5.1",
|
"lint-staged": "^10.5.1",
|
||||||
"lodash.camelcase": "^4.3.0",
|
"lodash.camelcase": "^4.3.0",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
"pointer-tracker": "^2.4.0",
|
"pointer-tracker": "^2.4.0",
|
||||||
"postcss": "^7.0.35",
|
"postcss": "^7.0.35",
|
||||||
"postcss-modules": "^3.2.2",
|
"postcss-modules": "^3.2.2",
|
||||||
@@ -47,9 +49,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,css,json,md,ts,tsx}": [
|
"*.{js,css,json,md,ts,tsx}": "prettier --write",
|
||||||
"prettier --write"
|
"*.{c,h,cpp,hpp}": "clang-format -i",
|
||||||
]
|
"*.rs": "rustfmt"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wasm-feature-detect": "^1.2.9"
|
"wasm-feature-detect": "^1.2.9"
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ function jsFileName(chunkInfo) {
|
|||||||
const parsedPath = path.parse(chunkInfo.facadeModuleId);
|
const parsedPath = path.parse(chunkInfo.facadeModuleId);
|
||||||
if (parsedPath.name !== 'index') return jsPath;
|
if (parsedPath.name !== 'index') return jsPath;
|
||||||
// Come up with a better name than 'index'
|
// Come up with a better name than 'index'
|
||||||
const name = parsedPath.dir.split('/').slice(-1);
|
const name = parsedPath.dir.split(/\\|\//).slice(-1);
|
||||||
return jsPath.replace('[name]', name);
|
return jsPath.replace('[name]', name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"redirects": [{ "source": "/editor", "destination": "/" }]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import type { FileDropEvent } from 'file-drop-element';
|
import type { FileDropEvent } from 'file-drop-element';
|
||||||
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
|
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||||
import type { SnackOptions } from 'shared/initial-app/custom-els/snack-bar';
|
import type { SnackOptions } from 'shared/custom-els/snack-bar';
|
||||||
|
|
||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
|
|
||||||
import { linkRef } from 'shared/initial-app/util';
|
import { linkRef } from 'shared/prerendered-app/util';
|
||||||
import * as style from './style.css';
|
import * as style from './style.css';
|
||||||
import 'add-css:./style.css';
|
import 'add-css:./style.css';
|
||||||
import 'file-drop-element';
|
import 'file-drop-element';
|
||||||
import 'shared/initial-app/custom-els/snack-bar';
|
import 'shared/custom-els/snack-bar';
|
||||||
import Intro from 'shared/initial-app/Intro';
|
import Intro from 'shared/prerendered-app/Intro';
|
||||||
import 'shared/initial-app/custom-els/loading-spinner';
|
import 'shared/custom-els/loading-spinner';
|
||||||
|
|
||||||
const ROUTE_EDITOR = '/editor';
|
const ROUTE_EDITOR = '/editor';
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ export default class App extends Component<Props, State> {
|
|||||||
// really breaks things on Squoosh, as you can easily end up zooming the UI when you mean to
|
// really breaks things on Squoosh, as you can easily end up zooming the UI when you mean to
|
||||||
// zoom the image. Once you've done this, it's really difficult to undo. Anyway, this seems to
|
// zoom the image. Once you've done this, it's really difficult to undo. Anyway, this seems to
|
||||||
// prevent it.
|
// prevent it.
|
||||||
document.body.addEventListener('gesturestart', (event) => {
|
document.body.addEventListener('gesturestart', (event: any) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -115,11 +115,7 @@ export default class App extends Component<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={style.app}>
|
<div class={style.app}>
|
||||||
<file-drop
|
<file-drop onfiledrop={this.onFileDrop} class={style.drop}>
|
||||||
accept="image/*"
|
|
||||||
onfiledrop={this.onFileDrop}
|
|
||||||
class={style.drop}
|
|
||||||
>
|
|
||||||
{showSpinner ? (
|
{showSpinner ? (
|
||||||
<loading-spinner class={style.appLoader} />
|
<loading-spinner class={style.appLoader} />
|
||||||
) : isEditorOpen ? (
|
) : isEditorOpen ? (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// <reference path="../../../shared/initial-app/custom-els/snack-bar/missing-types.d.ts" />
|
/// <reference path="../../../shared/custom-els/snack-bar/missing-types.d.ts" />
|
||||||
/// <reference path="../../../shared/initial-app/custom-els/loading-spinner/missing-types.d.ts" />
|
/// <reference path="../../../shared/custom-els/loading-spinner/missing-types.d.ts" />
|
||||||
import type { FileDropElement, FileDropEvent } from 'file-drop-element';
|
import type { FileDropElement, FileDropEvent } from 'file-drop-element';
|
||||||
|
|
||||||
interface FileDropAttributes extends preact.JSX.HTMLAttributes {
|
interface FileDropAttributes extends preact.JSX.HTMLAttributes {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as style from './style.css';
|
|||||||
import 'add-css:./style.css';
|
import 'add-css:./style.css';
|
||||||
import RangeInputElement from './custom-els/RangeInput';
|
import RangeInputElement from './custom-els/RangeInput';
|
||||||
import './custom-els/RangeInput';
|
import './custom-els/RangeInput';
|
||||||
import { linkRef } from 'shared/initial-app/util';
|
import { linkRef } from 'shared/prerendered-app/util';
|
||||||
|
|
||||||
interface Props extends preact.JSX.HTMLAttributes {}
|
interface Props extends preact.JSX.HTMLAttributes {}
|
||||||
interface State {}
|
interface State {}
|
||||||
|
|||||||
@@ -73,9 +73,14 @@ export default class TwoUp extends HTMLElement {
|
|||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this._childrenChange();
|
this._childrenChange();
|
||||||
|
|
||||||
this._handle.innerHTML = `<div class="${
|
// prettier-ignore
|
||||||
styles.scrubber
|
this._handle.innerHTML =
|
||||||
}">${`<svg viewBox="0 0 27 20" fill="currentColor">${'<path d="M17 19.2l9.5-9.6L16.9 0zM9.6 0L0 9.6l9.6 9.6z"/>'}</svg>`}</div>`;
|
`<div class="${styles.scrubber}">${
|
||||||
|
`<svg viewBox="0 0 27 20">${
|
||||||
|
`<path class="${styles.arrowLeft}" d="M9.6 0L0 9.6l9.6 9.6z"/>` +
|
||||||
|
`<path class="${styles.arrowRight}" d="M17 19.2l9.5-9.6L16.9 0z"/>`
|
||||||
|
}</svg>
|
||||||
|
`}</div>`;
|
||||||
|
|
||||||
if (!this._everConnected) {
|
if (!this._everConnected) {
|
||||||
this._resetPosition();
|
this._resetPosition();
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ two-up {
|
|||||||
display: grid;
|
display: grid;
|
||||||
position: relative;
|
position: relative;
|
||||||
--split-point: 0;
|
--split-point: 0;
|
||||||
--accent-color: #777;
|
--track-color: rgb(0 0 0 / 0.6);
|
||||||
--track-color: var(--accent-color);
|
--thumb-background: var(--black);
|
||||||
--thumb-background: #fff;
|
|
||||||
--thumb-color: var(--accent-color);
|
--thumb-color: var(--accent-color);
|
||||||
--thumb-size: 62px;
|
--thumb-size: 62px;
|
||||||
--bar-size: 6px;
|
--bar-size: 9px;
|
||||||
--bar-touch-size: 30px;
|
--bar-touch-size: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +36,6 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: var(--bar-size);
|
width: var(--bar-size);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
box-shadow: inset calc(var(--bar-size) / 2) 0 0 rgba(0, 0, 0, 0.1),
|
|
||||||
0 1px 4px rgba(0, 0, 0, 0.4);
|
|
||||||
background: var(--track-color);
|
background: var(--track-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,14 +44,11 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform-origin: 50% 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: var(--thumb-size);
|
width: var(--thumb-size);
|
||||||
height: calc(var(--thumb-size) * 0.9);
|
height: calc(var(--thumb-size) * 0.9);
|
||||||
background: var(--thumb-background);
|
background: var(--thumb-background);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: var(--thumb-size);
|
border-radius: var(--thumb-size);
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
color: var(--thumb-color);
|
color: var(--thumb-color);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0 calc(var(--thumb-size) * 0.24);
|
padding: 0 calc(var(--thumb-size) * 0.24);
|
||||||
@@ -64,6 +58,14 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.arrow-left {
|
||||||
|
fill: var(--pink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-right {
|
||||||
|
fill: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
two-up[orientation='vertical'] .two-up-handle {
|
two-up[orientation='vertical'] .two-up-handle {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: var(--bar-touch-size);
|
height: var(--bar-touch-size);
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ import {
|
|||||||
ToggleBackgroundIcon,
|
ToggleBackgroundIcon,
|
||||||
AddIcon,
|
AddIcon,
|
||||||
RemoveIcon,
|
RemoveIcon,
|
||||||
BackIcon,
|
|
||||||
ToggleBackgroundActiveIcon,
|
ToggleBackgroundActiveIcon,
|
||||||
RotateIcon,
|
RotateIcon,
|
||||||
|
MoreIcon,
|
||||||
} from '../../icons';
|
} from '../../icons';
|
||||||
import { twoUpHandle } from './custom-els/TwoUp/styles.css';
|
import { twoUpHandle } from './custom-els/TwoUp/styles.css';
|
||||||
import type { PreprocessorState } from '../../feature-meta';
|
import type { PreprocessorState } from '../../feature-meta';
|
||||||
import { cleanSet } from '../../util/clean-modify';
|
import { cleanSet } from '../../util/clean-modify';
|
||||||
import type { SourceImage } from '../../Compress';
|
import type { SourceImage } from '../../Compress';
|
||||||
import { linkRef } from 'shared/initial-app/util';
|
import { linkRef } from 'shared/prerendered-app/util';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
source?: SourceImage;
|
source?: SourceImage;
|
||||||
@@ -28,7 +28,6 @@ interface Props {
|
|||||||
rightCompressed?: ImageData;
|
rightCompressed?: ImageData;
|
||||||
leftImgContain: boolean;
|
leftImgContain: boolean;
|
||||||
rightImgContain: boolean;
|
rightImgContain: boolean;
|
||||||
onBack: () => void;
|
|
||||||
onPreprocessorChange: (newState: PreprocessorState) => void;
|
onPreprocessorChange: (newState: PreprocessorState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +35,7 @@ interface State {
|
|||||||
scale: number;
|
scale: number;
|
||||||
editingScale: boolean;
|
editingScale: boolean;
|
||||||
altBackground: boolean;
|
altBackground: boolean;
|
||||||
|
menuOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scaleToOpts: ScaleToOpts = {
|
const scaleToOpts: ScaleToOpts = {
|
||||||
@@ -50,6 +50,7 @@ export default class Output extends Component<Props, State> {
|
|||||||
scale: 1,
|
scale: 1,
|
||||||
editingScale: false,
|
editingScale: false,
|
||||||
altBackground: false,
|
altBackground: false,
|
||||||
|
menuOpen: false,
|
||||||
};
|
};
|
||||||
canvasLeft?: HTMLCanvasElement;
|
canvasLeft?: HTMLCanvasElement;
|
||||||
canvasRight?: HTMLCanvasElement;
|
canvasRight?: HTMLCanvasElement;
|
||||||
@@ -162,6 +163,10 @@ export default class Output extends Component<Props, State> {
|
|||||||
this.pinchZoomLeft.scaleTo(this.state.scale / 1.25, scaleToOpts);
|
this.pinchZoomLeft.scaleTo(this.state.scale / 1.25, scaleToOpts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private toggleMenu = () => {
|
||||||
|
this.setState(({ menuOpen }) => ({ menuOpen: !menuOpen }));
|
||||||
|
};
|
||||||
|
|
||||||
private onRotateClick = () => {
|
private onRotateClick = () => {
|
||||||
const { preprocessorState: inputProcessorState } = this.props;
|
const { preprocessorState: inputProcessorState } = this.props;
|
||||||
if (!inputProcessorState) return;
|
if (!inputProcessorState) return;
|
||||||
@@ -255,8 +260,8 @@ export default class Output extends Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render(
|
render(
|
||||||
{ mobileView, leftImgContain, rightImgContain, source, onBack }: Props,
|
{ mobileView, leftImgContain, rightImgContain, source }: Props,
|
||||||
{ scale, editingScale, altBackground }: State,
|
{ scale, editingScale, altBackground, menuOpen }: State,
|
||||||
) {
|
) {
|
||||||
const leftDraw = this.leftDrawable();
|
const leftDraw = this.leftDrawable();
|
||||||
const rightDraw = this.rightDrawable();
|
const rightDraw = this.rightDrawable();
|
||||||
@@ -314,12 +319,6 @@ export default class Output extends Component<Props, State> {
|
|||||||
</pinch-zoom>
|
</pinch-zoom>
|
||||||
</two-up>
|
</two-up>
|
||||||
|
|
||||||
<div class={style.back}>
|
|
||||||
<button class={style.button} onClick={onBack}>
|
|
||||||
<BackIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={style.controls}>
|
<div class={style.controls}>
|
||||||
<div class={style.zoomControls}>
|
<div class={style.zoomControls}>
|
||||||
<button class={style.button} onClick={this.zoomOut}>
|
<button class={style.button} onClick={this.zoomOut}>
|
||||||
@@ -349,26 +348,34 @@ export default class Output extends Component<Props, State> {
|
|||||||
<button class={style.button} onClick={this.zoomIn}>
|
<button class={style.button} onClick={this.zoomIn}>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
<div class={style.buttonsNoWrap}>
|
|
||||||
<button
|
<button
|
||||||
class={style.button}
|
class={`${style.button} ${style.moreButton} ${
|
||||||
onClick={this.onRotateClick}
|
menuOpen ? style.open : ''
|
||||||
title="Rotate image"
|
}`}
|
||||||
|
onClick={this.toggleMenu}
|
||||||
>
|
>
|
||||||
<RotateIcon />
|
<MoreIcon />
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class={`${style.button} ${altBackground ? style.active : ''}`}
|
|
||||||
onClick={this.toggleBackground}
|
|
||||||
title="Change canvas color"
|
|
||||||
>
|
|
||||||
{altBackground ? (
|
|
||||||
<ToggleBackgroundActiveIcon />
|
|
||||||
) : (
|
|
||||||
<ToggleBackgroundIcon />
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
|
<aside class={`${style.menu} ${menuOpen ? style.open : ''}`}>
|
||||||
|
<button
|
||||||
|
class={style.button}
|
||||||
|
onClick={this.onRotateClick}
|
||||||
|
title="Rotate image"
|
||||||
|
>
|
||||||
|
<RotateIcon />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class={`${style.button} ${altBackground ? style.active : ''}`}
|
||||||
|
onClick={this.toggleBackground}
|
||||||
|
title="Change canvas color"
|
||||||
|
>
|
||||||
|
{altBackground ? (
|
||||||
|
<ToggleBackgroundActiveIcon />
|
||||||
|
) : (
|
||||||
|
<ToggleBackgroundIcon />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.output {
|
.output {
|
||||||
composes: abs-fill from '../../../../shared/initial-app/util.css';
|
composes: abs-fill from global;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
@@ -19,12 +19,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.two-up {
|
.two-up {
|
||||||
composes: abs-fill from '../../../../shared/initial-app/util.css';
|
composes: abs-fill from global;
|
||||||
--accent-color: var(--button-fg);
|
--accent-color: var(--button-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pinch-zoom {
|
.pinch-zoom {
|
||||||
composes: abs-fill from '../../../../shared/initial-app/util.css';
|
composes: abs-fill from global;
|
||||||
outline: none;
|
outline: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -54,6 +54,7 @@
|
|||||||
|
|
||||||
/* Allow clicks to fall through to the pinch zoom area */
|
/* Allow clicks to fall through to the pinch zoom area */
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& :not(:last-child) {
|
& :not(:last-child) {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
border-right-width: 0;
|
border-right-width: 0;
|
||||||
@@ -90,46 +92,97 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
background-color: #fff;
|
background-color: rgba(29, 29, 29, 0.92);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
border: 1px solid rgba(0, 0, 0, 0.67);
|
||||||
border-radius: 5px;
|
border-radius: 6px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
height: 36px;
|
height: 39px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
/*
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
height: 48px;
|
height: 39px;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: 0 0 0 2px var(--button-fg);
|
box-shadow: 0 0 0 2px #fff;
|
||||||
outline: none;
|
outline: none;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
color: var(--button-fg);
|
color: #fff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #eee;
|
/* background-color: #eee; */
|
||||||
|
background: rgba(40, 40, 40, 0.92);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: #34b9eb;
|
background: rgba(72, 72, 72, 0.92);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
|
/*
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #32a3ce;
|
background: rgba(72, 72, 72, 0.92);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.moreButton {
|
||||||
|
padding: 0 4px;
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
background: rgba(72, 72, 72, 0.92);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 100%;
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
display: block;
|
||||||
|
animation: menuOpen 500ms ease forwards 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-radius: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #fff;
|
||||||
|
margin: 8px 4px;
|
||||||
|
padding: 10px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes menuOpen {
|
||||||
|
0% {
|
||||||
|
transform: translateY(10px) scale(0.8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom {
|
.zoom {
|
||||||
color: #625e80;
|
color: #939393;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
width: 6em;
|
width: 6em;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
@@ -137,7 +190,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 2px var(--button-fg);
|
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 2px #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,17 +198,10 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
margin: 0 3px 0 0;
|
margin: 0 3px 0 0;
|
||||||
color: #888;
|
color: #fff;
|
||||||
border-bottom: 1px dashed #999;
|
border-bottom: 1px dashed #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
padding: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons-no-wrap {
|
.buttons-no-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
CopyAcrossIcon,
|
CopyAcrossIcon,
|
||||||
CopyAcrossIconProps,
|
CopyAcrossIconProps,
|
||||||
} from 'client/lazy-app/icons';
|
} from 'client/lazy-app/icons';
|
||||||
import 'shared/initial-app/custom-els/loading-spinner';
|
import 'shared/custom-els/loading-spinner';
|
||||||
import { SourceImage } from '../';
|
import { SourceImage } from '../';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@
|
|||||||
.copy-to-other {
|
.copy-to-other {
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
grid-column: copy-button;
|
grid-column: copy-button;
|
||||||
composes: unbutton from '../../../../shared/initial-app/util.css';
|
composes: unbutton from global;
|
||||||
composes: download;
|
composes: download;
|
||||||
|
|
||||||
background: #656565;
|
background: #656565;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import './custom-els/MultiPanel';
|
|||||||
import Results from './Results';
|
import Results from './Results';
|
||||||
import WorkerBridge from '../worker-bridge';
|
import WorkerBridge from '../worker-bridge';
|
||||||
import { resize } from 'features/processors/resize/client';
|
import { resize } from 'features/processors/resize/client';
|
||||||
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
|
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||||
import { CopyAcrossIconProps, ExpandIcon } from '../icons';
|
import { CopyAcrossIconProps, ExpandIcon } from '../icons';
|
||||||
|
|
||||||
export type OutputType = EncoderType | 'identity';
|
export type OutputType = EncoderType | 'identity';
|
||||||
@@ -649,6 +649,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.name === 'AbortError') return;
|
if (err.name === 'AbortError') return;
|
||||||
|
this.setState({ loading: false });
|
||||||
this.props.showSnack(`Preprocessing error: ${err}`);
|
this.props.showSnack(`Preprocessing error: ${err}`);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -772,6 +773,12 @@ export default class Compress extends Component<Props, State> {
|
|||||||
this.activeSideJobs[sideIndex] = undefined;
|
this.activeSideJobs[sideIndex] = undefined;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.name === 'AbortError') return;
|
if (err.name === 'AbortError') return;
|
||||||
|
this.setState((currentState) => {
|
||||||
|
const sides = cleanMerge(currentState.sides, sideIndex, {
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
return { sides };
|
||||||
|
});
|
||||||
this.props.showSnack(`Processing error: ${err}`);
|
this.props.showSnack(`Processing error: ${err}`);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -855,10 +862,22 @@ export default class Compress extends Component<Props, State> {
|
|||||||
rightCompressed={rightImageData}
|
rightCompressed={rightImageData}
|
||||||
leftImgContain={leftImgContain}
|
leftImgContain={leftImgContain}
|
||||||
rightImgContain={rightImgContain}
|
rightImgContain={rightImgContain}
|
||||||
onBack={onBack}
|
|
||||||
preprocessorState={preprocessorState}
|
preprocessorState={preprocessorState}
|
||||||
onPreprocessorChange={this.onPreprocessorChange}
|
onPreprocessorChange={this.onPreprocessorChange}
|
||||||
/>
|
/>
|
||||||
|
<button class={style.back} onClick={onBack}>
|
||||||
|
<svg viewBox="0 0 61 53.3">
|
||||||
|
<title>Back</title>
|
||||||
|
<path
|
||||||
|
class={style.backBlob}
|
||||||
|
d="M0 25.6c-.5-7.1 4.1-14.5 10-19.1S23.4.1 32.2 0c8.8 0 19 1.6 24.4 8s5.6 17.8 1.7 27a29.7 29.7 0 01-20.5 18c-8.4 1.5-17.3-2.6-24.5-8S.5 32.6.1 25.6z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class={style.backX}
|
||||||
|
d="M41.6 17.1l-2-2.1-8.3 8.2-8.2-8.2-2 2 8.2 8.3-8.3 8.2 2.1 2 8.2-8.1 8.3 8.2 2-2-8.2-8.3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
{mobileView ? (
|
{mobileView ? (
|
||||||
<div class={style.options}>
|
<div class={style.options}>
|
||||||
<multi-panel class={style.multiPanel} open-one-only>
|
<multi-panel class={style.multiPanel} open-one-only>
|
||||||
|
|||||||
@@ -73,3 +73,24 @@
|
|||||||
:focus .expand-icon {
|
:focus .expand-icon {
|
||||||
fill: #34b9eb;
|
fill: #34b9eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.back {
|
||||||
|
composes: unbutton from global;
|
||||||
|
position: absolute;
|
||||||
|
top: var(--dist);
|
||||||
|
left: var(--dist);
|
||||||
|
--dist: 14px;
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
width: 58px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-blob {
|
||||||
|
fill: var(--hot-pink);
|
||||||
|
opacity: 0.77;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-x {
|
||||||
|
fill: var(--white);
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ export const RotateIcon = (props: preact.JSX.HTMLAttributes) => (
|
|||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const MoreIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<circle cx="12" cy="6" r="2" fill="#fff" />
|
||||||
|
<circle cx="12" cy="12" r="2" fill="#fff" />
|
||||||
|
<circle cx="12" cy="18" r="2" fill="#fff" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
|
||||||
export const AddIcon = (props: preact.JSX.HTMLAttributes) => (
|
export const AddIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
<Icon {...props}>
|
<Icon {...props}>
|
||||||
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||||
@@ -67,12 +75,6 @@ export const ExpandIcon = (props: preact.JSX.HTMLAttributes) => (
|
|||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const BackIcon = (props: preact.JSX.HTMLAttributes) => (
|
|
||||||
<Icon {...props}>
|
|
||||||
<path d="M20 11H7.8l5.6-5.6L12 4l-8 8 8 8 1.4-1.4L7.8 13H20v-2z" />
|
|
||||||
</Icon>
|
|
||||||
);
|
|
||||||
|
|
||||||
const copyAcrossRotations = {
|
const copyAcrossRotations = {
|
||||||
up: 90,
|
up: 90,
|
||||||
right: 180,
|
right: 180,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
|
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||||
|
|
||||||
import { get, set } from 'idb-keyval';
|
import { get, set } from 'idb-keyval';
|
||||||
|
|
||||||
|
|||||||
2
src/client/missing-types.d.ts
vendored
@@ -11,7 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
/// <reference path="../../missing-types.d.ts" />
|
/// <reference path="../../missing-types.d.ts" />
|
||||||
/// <reference path="../shared/initial-app/Intro/missing-types.d.ts" />
|
/// <reference path="../shared/prerendered-app/Intro/missing-types.d.ts" />
|
||||||
|
|
||||||
interface Navigator {
|
interface Navigator {
|
||||||
readonly standalone: boolean;
|
readonly standalone: boolean;
|
||||||
|
|||||||
@@ -315,9 +315,9 @@ export class Options extends Component<Props, State> {
|
|||||||
value={subsample}
|
value={subsample}
|
||||||
onChange={this._inputChange('subsample', 'number')}
|
onChange={this._inputChange('subsample', 'number')}
|
||||||
>
|
>
|
||||||
<option value="1">4:2:0</option>
|
<option value="1">Half</option>
|
||||||
{/*<option value="2">4:2:2</option>*/}
|
{/*<option value="2">4:2:2</option>*/}
|
||||||
<option value="3">4:4:4</option>
|
<option value="3">Off</option>
|
||||||
</Select>
|
</Select>
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ interface State {
|
|||||||
edgePreservingFilter: number;
|
edgePreservingFilter: number;
|
||||||
lossless: boolean;
|
lossless: boolean;
|
||||||
slightLoss: boolean;
|
slightLoss: boolean;
|
||||||
|
autoEdgePreservingFilter: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxSpeed = 7;
|
const maxSpeed = 7;
|
||||||
@@ -48,9 +49,10 @@ export class Options extends Component<Props, State> {
|
|||||||
effort: maxSpeed - options.speed,
|
effort: maxSpeed - options.speed,
|
||||||
quality: options.quality,
|
quality: options.quality,
|
||||||
progressive: options.progressive,
|
progressive: options.progressive,
|
||||||
edgePreservingFilter: options.epf,
|
edgePreservingFilter: options.epf === -1 ? 2 : options.epf,
|
||||||
lossless: options.quality === 100,
|
lossless: options.quality === 100,
|
||||||
slightLoss: options.lossyPalette,
|
slightLoss: options.lossyPalette,
|
||||||
|
autoEdgePreservingFilter: options.epf === -1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +88,9 @@ export class Options extends Component<Props, State> {
|
|||||||
speed: maxSpeed - optionState.effort,
|
speed: maxSpeed - optionState.effort,
|
||||||
quality: optionState.lossless ? 100 : optionState.quality,
|
quality: optionState.lossless ? 100 : optionState.quality,
|
||||||
progressive: optionState.progressive,
|
progressive: optionState.progressive,
|
||||||
epf: optionState.edgePreservingFilter,
|
epf: optionState.autoEdgePreservingFilter
|
||||||
|
? -1
|
||||||
|
: optionState.edgePreservingFilter,
|
||||||
nearLossless: 0,
|
nearLossless: 0,
|
||||||
lossyPalette: optionState.lossless ? optionState.slightLoss : false,
|
lossyPalette: optionState.lossless ? optionState.slightLoss : false,
|
||||||
};
|
};
|
||||||
@@ -112,6 +116,7 @@ export class Options extends Component<Props, State> {
|
|||||||
edgePreservingFilter,
|
edgePreservingFilter,
|
||||||
lossless,
|
lossless,
|
||||||
slightLoss,
|
slightLoss,
|
||||||
|
autoEdgePreservingFilter,
|
||||||
}: State,
|
}: State,
|
||||||
) {
|
) {
|
||||||
// I'm rendering both lossy and lossless forms, as it becomes much easier when
|
// I'm rendering both lossy and lossless forms, as it becomes much easier when
|
||||||
@@ -152,16 +157,34 @@ export class Options extends Component<Props, State> {
|
|||||||
Quality:
|
Quality:
|
||||||
</Range>
|
</Range>
|
||||||
</div>
|
</div>
|
||||||
<div class={style.optionOneCell}>
|
<label class={style.optionInputFirst}>
|
||||||
<Range
|
<Checkbox
|
||||||
min="0"
|
name="autoEdgeFilter"
|
||||||
max="3"
|
checked={autoEdgePreservingFilter}
|
||||||
value={edgePreservingFilter}
|
onChange={this._inputChange(
|
||||||
onInput={this._inputChange('edgePreservingFilter', 'number')}
|
'autoEdgePreservingFilter',
|
||||||
>
|
'boolean',
|
||||||
Edge preserving filter:
|
)}
|
||||||
</Range>
|
/>
|
||||||
</div>
|
Auto edge filter
|
||||||
|
</label>
|
||||||
|
<Expander>
|
||||||
|
{!autoEdgePreservingFilter && (
|
||||||
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
|
min="0"
|
||||||
|
max="3"
|
||||||
|
value={edgePreservingFilter}
|
||||||
|
onInput={this._inputChange(
|
||||||
|
'edgePreservingFilter',
|
||||||
|
'number',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Edge preserving filter:
|
||||||
|
</Range>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Expander>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Expander>
|
</Expander>
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ export const label = 'JPEG XL (beta)';
|
|||||||
export const mimeType = 'image/jpegxl';
|
export const mimeType = 'image/jpegxl';
|
||||||
export const extension = 'jxl';
|
export const extension = 'jxl';
|
||||||
export const defaultOptions: EncodeOptions = {
|
export const defaultOptions: EncodeOptions = {
|
||||||
speed: 5,
|
speed: 4,
|
||||||
quality: 50,
|
quality: 75,
|
||||||
progressive: false,
|
progressive: false,
|
||||||
epf: 2,
|
epf: -1,
|
||||||
nearLossless: 0,
|
nearLossless: 0,
|
||||||
lossyPalette: false,
|
lossyPalette: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { EncodeOptions } from '../shared/meta';
|
import { EncodeOptions, UVMode, Csp } from '../shared/meta';
|
||||||
|
import { defaultOptions } from '../shared/meta';
|
||||||
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
import { inputFieldValueAsNumber, preventDefault } from 'client/lazy-app/util';
|
import { preventDefault, shallowEqual } from 'client/lazy-app/util';
|
||||||
import * as style from 'client/lazy-app/Compress/Options/style.css';
|
import * as style from 'client/lazy-app/Compress/Options/style.css';
|
||||||
import Range from 'client/lazy-app/Compress/Options/Range';
|
import Range from 'client/lazy-app/Compress/Options/Range';
|
||||||
|
import Select from 'client/lazy-app/Compress/Options/Select';
|
||||||
|
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
|
||||||
|
import Expander from 'client/lazy-app/Compress/Options/Expander';
|
||||||
|
import linkState from 'linkstate';
|
||||||
|
|
||||||
export const encode = (
|
export const encode = (
|
||||||
signal: AbortSignal,
|
signal: AbortSignal,
|
||||||
@@ -18,93 +23,286 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
options: EncodeOptions;
|
||||||
|
effort: number;
|
||||||
|
quality: number;
|
||||||
|
alphaQuality: number;
|
||||||
|
passes: number;
|
||||||
|
sns: number;
|
||||||
|
uvMode: number;
|
||||||
|
lossless: boolean;
|
||||||
|
slightLoss: number;
|
||||||
|
colorSpace: number;
|
||||||
|
errorDiffusion: number;
|
||||||
|
useRandomMatrix: boolean;
|
||||||
showAdvanced: boolean;
|
showAdvanced: boolean;
|
||||||
|
separateAlpha: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Options extends Component<Props, State> {
|
export class Options extends Component<Props, State> {
|
||||||
state: State = {
|
static getDerivedStateFromProps(
|
||||||
showAdvanced: false,
|
props: Props,
|
||||||
};
|
state: State,
|
||||||
|
): Partial<State> | null {
|
||||||
|
if (state.options && shallowEqual(state.options, props.options)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private onChange = (event: Event) => {
|
const { options } = props;
|
||||||
const form = (event.currentTarget as HTMLInputElement).closest(
|
|
||||||
'form',
|
const modifyState: Partial<State> = {
|
||||||
) as HTMLFormElement;
|
options,
|
||||||
const { options } = this.props;
|
effort: options.speed,
|
||||||
const newOptions: EncodeOptions = {
|
alphaQuality: options.alpha_quality,
|
||||||
quality: inputFieldValueAsNumber(form.quality, options.quality),
|
passes: options.pass,
|
||||||
alpha_quality: inputFieldValueAsNumber(
|
sns: options.sns,
|
||||||
form.alpha_quality,
|
uvMode: options.uv_mode,
|
||||||
options.alpha_quality,
|
colorSpace: options.csp_type,
|
||||||
),
|
errorDiffusion: options.error_diffusion,
|
||||||
speed: inputFieldValueAsNumber(form.speed, options.speed),
|
useRandomMatrix: options.use_random_matrix,
|
||||||
pass: inputFieldValueAsNumber(form.pass, options.pass),
|
separateAlpha: options.quality !== options.alpha_quality,
|
||||||
sns: inputFieldValueAsNumber(form.sns, options.sns),
|
|
||||||
};
|
};
|
||||||
this.props.onChange(newOptions);
|
|
||||||
|
// If quality is > 95, it's lossless with slight loss
|
||||||
|
if (options.quality > 95) {
|
||||||
|
modifyState.lossless = true;
|
||||||
|
modifyState.slightLoss = 100 - options.quality;
|
||||||
|
} else {
|
||||||
|
modifyState.quality = options.quality;
|
||||||
|
modifyState.lossless = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifyState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other state is set in getDerivedStateFromProps
|
||||||
|
state: State = {
|
||||||
|
lossless: false,
|
||||||
|
slightLoss: 0,
|
||||||
|
quality: defaultOptions.quality,
|
||||||
|
showAdvanced: false,
|
||||||
|
} as State;
|
||||||
|
|
||||||
|
private _inputChangeCallbacks = new Map<string, (event: Event) => void>();
|
||||||
|
|
||||||
|
private _inputChange = (prop: keyof State, type: 'number' | 'boolean') => {
|
||||||
|
// Cache the callback for performance
|
||||||
|
if (!this._inputChangeCallbacks.has(prop)) {
|
||||||
|
this._inputChangeCallbacks.set(prop, (event: Event) => {
|
||||||
|
const formEl = event.target as HTMLInputElement | HTMLSelectElement;
|
||||||
|
const newVal =
|
||||||
|
type === 'boolean'
|
||||||
|
? 'checked' in formEl
|
||||||
|
? formEl.checked
|
||||||
|
: !!formEl.value
|
||||||
|
: Number(formEl.value);
|
||||||
|
|
||||||
|
const newState: Partial<State> = {
|
||||||
|
[prop]: newVal,
|
||||||
|
};
|
||||||
|
|
||||||
|
const optionState = {
|
||||||
|
...this.state,
|
||||||
|
...newState,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newOptions: EncodeOptions = {
|
||||||
|
speed: optionState.effort,
|
||||||
|
quality: optionState.lossless
|
||||||
|
? 100 - optionState.slightLoss
|
||||||
|
: optionState.quality,
|
||||||
|
alpha_quality: optionState.separateAlpha
|
||||||
|
? optionState.alphaQuality
|
||||||
|
: optionState.quality,
|
||||||
|
pass: optionState.passes,
|
||||||
|
sns: optionState.sns,
|
||||||
|
uv_mode: optionState.uvMode,
|
||||||
|
csp_type: optionState.colorSpace,
|
||||||
|
error_diffusion: optionState.errorDiffusion,
|
||||||
|
use_random_matrix: optionState.useRandomMatrix,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Updating options, so we don't recalculate in getDerivedStateFromProps.
|
||||||
|
newState.options = newOptions;
|
||||||
|
|
||||||
|
this.setState(newState);
|
||||||
|
|
||||||
|
this.props.onChange(newOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._inputChangeCallbacks.get(prop)!;
|
||||||
};
|
};
|
||||||
|
|
||||||
render({ options }: Props) {
|
render(
|
||||||
|
{}: Props,
|
||||||
|
{
|
||||||
|
effort,
|
||||||
|
alphaQuality,
|
||||||
|
passes,
|
||||||
|
quality,
|
||||||
|
sns,
|
||||||
|
uvMode,
|
||||||
|
lossless,
|
||||||
|
slightLoss,
|
||||||
|
colorSpace,
|
||||||
|
errorDiffusion,
|
||||||
|
useRandomMatrix,
|
||||||
|
separateAlpha,
|
||||||
|
showAdvanced,
|
||||||
|
}: State,
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<form class={style.optionsSection} onSubmit={preventDefault}>
|
<form class={style.optionsSection} onSubmit={preventDefault}>
|
||||||
|
<label class={style.optionInputFirst}>
|
||||||
|
<Checkbox
|
||||||
|
checked={lossless}
|
||||||
|
onChange={this._inputChange('lossless', 'boolean')}
|
||||||
|
/>
|
||||||
|
Lossless
|
||||||
|
</label>
|
||||||
|
<Expander>
|
||||||
|
{lossless && (
|
||||||
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
|
min="0"
|
||||||
|
max="5"
|
||||||
|
step="0.1"
|
||||||
|
value={slightLoss}
|
||||||
|
onInput={this._inputChange('slightLoss', 'number')}
|
||||||
|
>
|
||||||
|
Slight loss:
|
||||||
|
</Range>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Expander>
|
||||||
|
<Expander>
|
||||||
|
{!lossless && (
|
||||||
|
<div>
|
||||||
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
|
min="0"
|
||||||
|
max="95"
|
||||||
|
step="0.1"
|
||||||
|
value={quality}
|
||||||
|
onInput={this._inputChange('quality', 'number')}
|
||||||
|
>
|
||||||
|
Quality:
|
||||||
|
</Range>
|
||||||
|
</div>
|
||||||
|
<label class={style.optionInputFirst}>
|
||||||
|
<Checkbox
|
||||||
|
checked={separateAlpha}
|
||||||
|
onChange={this._inputChange('separateAlpha', 'boolean')}
|
||||||
|
/>
|
||||||
|
Separate alpha quality
|
||||||
|
</label>
|
||||||
|
<Expander>
|
||||||
|
{separateAlpha && (
|
||||||
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="1"
|
||||||
|
value={alphaQuality}
|
||||||
|
onInput={this._inputChange('alphaQuality', 'number')}
|
||||||
|
>
|
||||||
|
Alpha Quality:
|
||||||
|
</Range>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Expander>
|
||||||
|
<label class={style.optionInputFirst}>
|
||||||
|
<Checkbox
|
||||||
|
checked={showAdvanced}
|
||||||
|
onChange={linkState(this, 'showAdvanced')}
|
||||||
|
/>
|
||||||
|
Show advanced settings
|
||||||
|
</label>
|
||||||
|
<Expander>
|
||||||
|
{showAdvanced && (
|
||||||
|
<div>
|
||||||
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
|
min="1"
|
||||||
|
max="10"
|
||||||
|
step="1"
|
||||||
|
value={passes}
|
||||||
|
onInput={this._inputChange('passes', 'number')}
|
||||||
|
>
|
||||||
|
Passes:
|
||||||
|
</Range>
|
||||||
|
</div>
|
||||||
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="1"
|
||||||
|
value={sns}
|
||||||
|
onInput={this._inputChange('sns', 'number')}
|
||||||
|
>
|
||||||
|
Spatial noise shaping:
|
||||||
|
</Range>
|
||||||
|
</div>
|
||||||
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
step="1"
|
||||||
|
value={errorDiffusion}
|
||||||
|
onInput={this._inputChange('errorDiffusion', 'number')}
|
||||||
|
>
|
||||||
|
Error diffusion:
|
||||||
|
</Range>
|
||||||
|
</div>
|
||||||
|
<label class={style.optionTextFirst}>
|
||||||
|
Subsample chroma:
|
||||||
|
<Select
|
||||||
|
value={uvMode}
|
||||||
|
onInput={this._inputChange('uvMode', 'number')}
|
||||||
|
>
|
||||||
|
<option value={UVMode.UVModeAuto}>Auto</option>
|
||||||
|
<option value={UVMode.UVModeAdapt}>Vary</option>
|
||||||
|
<option value={UVMode.UVMode420}>Half</option>
|
||||||
|
<option value={UVMode.UVMode444}>Off</option>
|
||||||
|
</Select>
|
||||||
|
</label>
|
||||||
|
<label class={style.optionTextFirst}>
|
||||||
|
Color space:
|
||||||
|
<Select
|
||||||
|
value={colorSpace}
|
||||||
|
onInput={this._inputChange('colorSpace', 'number')}
|
||||||
|
>
|
||||||
|
<option value={Csp.kYCoCg}>YCoCg</option>
|
||||||
|
<option value={Csp.kYCbCr}>YCbCr</option>
|
||||||
|
<option value={Csp.kYIQ}>YIQ</option>
|
||||||
|
</Select>
|
||||||
|
</label>
|
||||||
|
<label class={style.optionInputFirst}>
|
||||||
|
<Checkbox
|
||||||
|
checked={useRandomMatrix}
|
||||||
|
onChange={this._inputChange(
|
||||||
|
'useRandomMatrix',
|
||||||
|
'boolean',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
Random matrix
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Expander>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Expander>
|
||||||
<div class={style.optionOneCell}>
|
<div class={style.optionOneCell}>
|
||||||
<Range
|
<Range
|
||||||
name="quality"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
step="1"
|
|
||||||
value={options.quality}
|
|
||||||
onInput={this.onChange}
|
|
||||||
>
|
|
||||||
Quality:
|
|
||||||
</Range>
|
|
||||||
</div>
|
|
||||||
<div class={style.optionOneCell}>
|
|
||||||
<Range
|
|
||||||
name="alpha_quality"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
step="1"
|
|
||||||
value={options.alpha_quality}
|
|
||||||
onInput={this.onChange}
|
|
||||||
>
|
|
||||||
Alpha Quality:
|
|
||||||
</Range>
|
|
||||||
</div>
|
|
||||||
<div class={style.optionOneCell}>
|
|
||||||
<Range
|
|
||||||
name="speed"
|
|
||||||
min="0"
|
min="0"
|
||||||
max="9"
|
max="9"
|
||||||
step="1"
|
step="1"
|
||||||
value={options.speed}
|
value={effort}
|
||||||
onInput={this.onChange}
|
onInput={this._inputChange('effort', 'number')}
|
||||||
>
|
>
|
||||||
Speed:
|
Effort:
|
||||||
</Range>
|
|
||||||
</div>
|
|
||||||
<div class={style.optionOneCell}>
|
|
||||||
<Range
|
|
||||||
name="pass"
|
|
||||||
min="1"
|
|
||||||
max="10"
|
|
||||||
step="1"
|
|
||||||
value={options.pass}
|
|
||||||
onInput={this.onChange}
|
|
||||||
>
|
|
||||||
Pass:
|
|
||||||
</Range>
|
|
||||||
</div>
|
|
||||||
<div class={style.optionOneCell}>
|
|
||||||
<Range
|
|
||||||
name="sns"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
step="1"
|
|
||||||
value={options.sns}
|
|
||||||
onInput={this.onChange}
|
|
||||||
>
|
|
||||||
Spatial noise shaping:
|
|
||||||
</Range>
|
</Range>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -11,16 +11,21 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import type { EncodeOptions } from 'codecs/wp2/enc/wp2_enc';
|
import type { EncodeOptions } from 'codecs/wp2/enc/wp2_enc';
|
||||||
|
import { UVMode, Csp } from 'codecs/wp2/enc/wp2_enc';
|
||||||
|
|
||||||
export { EncodeOptions };
|
export { EncodeOptions, UVMode, Csp };
|
||||||
|
|
||||||
export const label = 'WebP v2 (unstable)';
|
export const label = 'WebP v2 (unstable)';
|
||||||
export const mimeType = 'image/webp2';
|
export const mimeType = 'image/webp2';
|
||||||
export const extension = 'wp2';
|
export const extension = 'wp2';
|
||||||
export const defaultOptions: EncodeOptions = {
|
export const defaultOptions: EncodeOptions = {
|
||||||
quality: 75,
|
quality: 75,
|
||||||
alpha_quality: 100,
|
alpha_quality: 75,
|
||||||
speed: 5,
|
speed: 5,
|
||||||
pass: 1,
|
pass: 1,
|
||||||
sns: 50,
|
sns: 50,
|
||||||
|
uv_mode: UVMode.UVModeAuto,
|
||||||
|
csp_type: Csp.kYCoCg,
|
||||||
|
error_diffusion: 0,
|
||||||
|
use_random_matrix: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
inputFieldChecked,
|
inputFieldChecked,
|
||||||
} from 'client/lazy-app/util';
|
} from 'client/lazy-app/util';
|
||||||
import * as style from 'client/lazy-app/Compress/Options/style.css';
|
import * as style from 'client/lazy-app/Compress/Options/style.css';
|
||||||
import { linkRef } from 'shared/initial-app/util';
|
import { linkRef } from 'shared/prerendered-app/util';
|
||||||
import Select from 'client/lazy-app/Compress/Options/Select';
|
import Select from 'client/lazy-app/Compress/Options/Select';
|
||||||
import Expander from 'client/lazy-app/Compress/Options/Expander';
|
import Expander from 'client/lazy-app/Compress/Options/Expander';
|
||||||
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
|
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
import 'add-css:./styles.css';
|
||||||
|
|
||||||
// So it doesn't cause an error when running in node
|
// So it doesn't cause an error when running in node
|
||||||
const HTMLEl = ((__PRERENDER__
|
const HTMLEl = ((__PRERENDER__
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as style from './styles.css';
|
import * as style from './styles.css';
|
||||||
|
import 'add-css:./styles.css';
|
||||||
|
|
||||||
// So it doesn't cause an error when running in node
|
// So it doesn't cause an error when running in node
|
||||||
const HTMLEl = ((__PRERENDER__
|
const HTMLEl = ((__PRERENDER__
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,255 +0,0 @@
|
|||||||
import { h, Component } from 'preact';
|
|
||||||
|
|
||||||
import { linkRef } from 'shared/initial-app/util';
|
|
||||||
import '../custom-els/loading-spinner';
|
|
||||||
|
|
||||||
import logo from 'url:./imgs/logo.svg';
|
|
||||||
import largePhoto from 'url:./imgs/demos/demo-large-photo.jpg';
|
|
||||||
import artwork from 'url:./imgs/demos/demo-artwork.jpg';
|
|
||||||
import deviceScreen from 'url:./imgs/demos/demo-device-screen.png';
|
|
||||||
import largePhotoIcon from 'url:./imgs/demos/icon-demo-large-photo.jpg';
|
|
||||||
import artworkIcon from 'url:./imgs/demos/icon-demo-artwork.jpg';
|
|
||||||
import deviceScreenIcon from 'url:./imgs/demos/icon-demo-device-screen.jpg';
|
|
||||||
import logoIcon from 'url:./imgs/demos/icon-demo-logo.png';
|
|
||||||
import * as style from './style.css';
|
|
||||||
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
|
|
||||||
import 'shared/initial-app/custom-els/snack-bar';
|
|
||||||
|
|
||||||
const demos = [
|
|
||||||
{
|
|
||||||
description: 'Large photo (2.8mb)',
|
|
||||||
filename: 'photo.jpg',
|
|
||||||
url: largePhoto,
|
|
||||||
iconUrl: largePhotoIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: 'Artwork (2.9mb)',
|
|
||||||
filename: 'art.jpg',
|
|
||||||
url: artwork,
|
|
||||||
iconUrl: artworkIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: 'Device screen (1.6mb)',
|
|
||||||
filename: 'pixel3.png',
|
|
||||||
url: deviceScreen,
|
|
||||||
iconUrl: deviceScreenIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: 'SVG icon (13k)',
|
|
||||||
filename: 'squoosh.svg',
|
|
||||||
url: logo,
|
|
||||||
iconUrl: logoIcon,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const installButtonSource = 'introInstallButton-Purple';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
onFile?: (file: File) => void;
|
|
||||||
showSnack?: SnackBarElement['showSnackbar'];
|
|
||||||
}
|
|
||||||
interface State {
|
|
||||||
fetchingDemoIndex?: number;
|
|
||||||
beforeInstallEvent?: BeforeInstallPromptEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Intro extends Component<Props, State> {
|
|
||||||
state: State = {};
|
|
||||||
private fileInput?: HTMLInputElement;
|
|
||||||
private installingViaButton = false;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
if (__PRERENDER__) return;
|
|
||||||
// Listen for beforeinstallprompt events, indicating Squoosh is installable.
|
|
||||||
window.addEventListener(
|
|
||||||
'beforeinstallprompt',
|
|
||||||
this.onBeforeInstallPromptEvent,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Listen for the appinstalled event, indicating Squoosh has been installed.
|
|
||||||
window.addEventListener('appinstalled', this.onAppInstalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private resetFileInput = () => {
|
|
||||||
this.fileInput!.value = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
private onFileChange = (event: Event): void => {
|
|
||||||
const fileInput = event.target as HTMLInputElement;
|
|
||||||
const file = fileInput.files && fileInput.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
this.resetFileInput();
|
|
||||||
this.props.onFile!(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
private onButtonClick = () => {
|
|
||||||
this.fileInput!.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
private onDemoClick = async (index: number, event: Event) => {
|
|
||||||
try {
|
|
||||||
this.setState({ fetchingDemoIndex: index });
|
|
||||||
const demo = demos[index];
|
|
||||||
const blob = await fetch(demo.url).then((r) => r.blob());
|
|
||||||
|
|
||||||
// Firefox doesn't like content types like 'image/png; charset=UTF-8', which Webpack's dev
|
|
||||||
// server returns. https://bugzilla.mozilla.org/show_bug.cgi?id=1497925.
|
|
||||||
const type = /[^;]*/.exec(blob.type)![0];
|
|
||||||
const file = new File([blob], demo.filename, { type });
|
|
||||||
this.props.onFile!(file);
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({ fetchingDemoIndex: undefined });
|
|
||||||
this.props.showSnack!("Couldn't fetch demo image");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private onBeforeInstallPromptEvent = (event: BeforeInstallPromptEvent) => {
|
|
||||||
// Don't show the mini-infobar on mobile
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// Save the beforeinstallprompt event so it can be called later.
|
|
||||||
this.setState({ beforeInstallEvent: event });
|
|
||||||
|
|
||||||
// Log the event.
|
|
||||||
const gaEventInfo = {
|
|
||||||
eventCategory: 'pwa-install',
|
|
||||||
eventAction: 'promo-shown',
|
|
||||||
nonInteraction: true,
|
|
||||||
};
|
|
||||||
ga('send', 'event', gaEventInfo);
|
|
||||||
};
|
|
||||||
|
|
||||||
private onInstallClick = async (event: Event) => {
|
|
||||||
// Get the deferred beforeinstallprompt event
|
|
||||||
const beforeInstallEvent = this.state.beforeInstallEvent;
|
|
||||||
// If there's no deferred prompt, bail.
|
|
||||||
if (!beforeInstallEvent) return;
|
|
||||||
|
|
||||||
this.installingViaButton = true;
|
|
||||||
|
|
||||||
// Show the browser install prompt
|
|
||||||
beforeInstallEvent.prompt();
|
|
||||||
|
|
||||||
// Wait for the user to accept or dismiss the install prompt
|
|
||||||
const { outcome } = await beforeInstallEvent.userChoice;
|
|
||||||
// Send the analytics data
|
|
||||||
const gaEventInfo = {
|
|
||||||
eventCategory: 'pwa-install',
|
|
||||||
eventAction: 'promo-clicked',
|
|
||||||
eventLabel: installButtonSource,
|
|
||||||
eventValue: outcome === 'accepted' ? 1 : 0,
|
|
||||||
};
|
|
||||||
ga('send', 'event', gaEventInfo);
|
|
||||||
|
|
||||||
// If the prompt was dismissed, we aren't going to install via the button.
|
|
||||||
if (outcome === 'dismissed') {
|
|
||||||
this.installingViaButton = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAppInstalled = () => {
|
|
||||||
// We don't need the install button, if it's shown
|
|
||||||
this.setState({ beforeInstallEvent: undefined });
|
|
||||||
|
|
||||||
// Don't log analytics if page is not visible
|
|
||||||
if (document.hidden) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to get the install, if it's not set, use 'browser'
|
|
||||||
const source = this.installingViaButton ? installButtonSource : 'browser';
|
|
||||||
ga('send', 'event', 'pwa-install', 'installed', source);
|
|
||||||
|
|
||||||
// Clear the install method property
|
|
||||||
this.installingViaButton = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
render({}: Props, { fetchingDemoIndex, beforeInstallEvent }: State) {
|
|
||||||
return (
|
|
||||||
<div class={style.intro}>
|
|
||||||
<div>
|
|
||||||
<div class={style.logoSizer}>
|
|
||||||
<div class={style.logoContainer}>
|
|
||||||
<img
|
|
||||||
src={logo}
|
|
||||||
class={style.logo}
|
|
||||||
alt="Squoosh"
|
|
||||||
decoding="async"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class={style.openImageGuide}>
|
|
||||||
Drag & drop or{' '}
|
|
||||||
<button class={style.selectButton} onClick={this.onButtonClick}>
|
|
||||||
select an image
|
|
||||||
</button>
|
|
||||||
<input
|
|
||||||
class={style.hide}
|
|
||||||
ref={linkRef(this, 'fileInput')}
|
|
||||||
type="file"
|
|
||||||
onChange={this.onFileChange}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<p>Or try one of these:</p>
|
|
||||||
<ul class={style.demos}>
|
|
||||||
{demos.map((demo, i) => (
|
|
||||||
<li key={demo.url} class={style.demoItem}>
|
|
||||||
<button
|
|
||||||
class={style.demoButton}
|
|
||||||
onClick={this.onDemoClick.bind(this, i)}
|
|
||||||
>
|
|
||||||
<div class={style.demo}>
|
|
||||||
<div class={style.demoImgContainer}>
|
|
||||||
<div class={style.demoImgAspect}>
|
|
||||||
<img
|
|
||||||
class={style.demoIcon}
|
|
||||||
src={demo.iconUrl}
|
|
||||||
alt=""
|
|
||||||
decoding="async"
|
|
||||||
/>
|
|
||||||
{fetchingDemoIndex === i && (
|
|
||||||
<div class={style.demoLoading}>
|
|
||||||
<loading-spinner class={style.demoLoadingSpinner} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class={style.demoDescription}>{demo.description}</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{beforeInstallEvent && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class={style.installButton}
|
|
||||||
onClick={this.onInstallClick}
|
|
||||||
>
|
|
||||||
Install
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<ul class={style.relatedLinks}>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/GoogleChromeLabs/squoosh/">
|
|
||||||
View the code
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/GoogleChromeLabs/squoosh/issues">
|
|
||||||
Report a bug
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/GoogleChromeLabs/squoosh/blob/dev/README.md#privacy">
|
|
||||||
Privacy
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'intro-text';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 300;
|
|
||||||
font-display: block;
|
|
||||||
/* This only contains the chars for "Drag & drop or" */
|
|
||||||
src: url('data:font/woff2;base64,d09GMgABAAAAAAXcAA4AAAAACowAAAWJAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbg2gcMAZgAGwRCAqIQIcnCxYAATYCJAMoBCAFgwAHIBvqCFEU84FMI2Xh/P3g+Tfn532yQ/IgYz4BrJyhtkkZwFBYAZ49sI63e5v/NnqzIfbADyE0qxOqK8ESLoNNdULHihxbW0W86/qHEk4wT/eHShPRZJYYqUGkQdLSWCeSemZBzwpKyX/LRoAhMEQhqCFBw5RHNCc4hbVn35FsxtTXVHYyo7miu5VN2AW1fwzVauRgXnIGo2IWsYdViUoLu6mms5VFAn+SeQ4eBazfj7QodrMt4oyQHaGADEPRpTbDqJaoTENNK6DpOralUszf6gI/QsAhWZSMKVOirikSJxZRLBVD0S4mB0kTBRwopjZ/mt/2/25+bcSipgiHRmwiFI1g+XhwlshyEAsbJzGiGH+U5whHNgiXooplafI1rMFbmIqjGAPhmcSkVFxeu9hw87aXsGyL+dPE05qUpK2WyaVQcZVW+aDmw3aalLJKNmQORcpZYtBIuTrncN4xXoVZY617TBSsx2T1DHgGU6u4etE04wha1GEwjVkEaDttOrl1FCOwUMxgHnuooJo62ukcWEuc1/aT+dZ8b142t5tbzc3mGnP1EJqVTEGMYTjG14YxtGEEG+0E2axhe6Oa1E8UrDHDFjhTRywYNWrU9JHTlw7RmaslkrrGcTJ+znW4EzzP0zovE4Z5d0hqVhBobftBIKkwL09SOv3hhCuv1Dp9taTeCJ2Mj3KDT8iDng5DkWzPw/UdP8idNDkMnUyOwEauwnYLQeLC7GskNe72QKe97AmuA42E5FjfyYTM+HTdQ+Xqb+q4JvptyKZN1w47qMMwL58fyKZM1U6NXgWlOFdxx7DpXHDTz4UB89WMK3HH3uY7mavFopGF+u36lGlqZsL4ugmbqvZxveycMO+a4uyN3o7GT2qdHpfr6W++kNTn1crdx7Z+FW7PfffTmfnXV/2ivsh5UX93zdlzct6QlSuHSumG3oGNNT9/m9yXnDcnKfsmDx8xUaoKi+uvGs99H2ieUJUg8bTnVwQcDd/SPKwYWDUv+QkpT6MulMrcPTXNWYnIowxvoiwnX+opTMkvzOMGgpNpqnK32CNVwCnassw0BwQwTa0rLS3m1DfIoxx5PIE8SvEmSk3pHSWZiRVKjOOQSylJSHGXkhT/u/tg/Vm9UZQcS59TGb1qjcuuT0925iaaU1vaWpZJM4ukqWWlrdWSIcVNlOImvnrzLn53UpnSLzbGT5lUlpTiKiPJFEmyqywFLtOhcaYJkWkaGe/oGBlnmiIiIiKYpqHxLmdaWg5JpxxHSXpajsuVlkPSvb1JelqOC0pubbAn2A2UsDdYmTmjvbVlgTRhVBSSxpbF1nZD+jvkUR4rcJeSFBp2d19SUsVW5DjkUkoSoITHJ7iJEpZnZaL4OiF7g92DN1mz8b1RiM9RDk9ps9pcanamlnj2ftqbJpHJ0wpkRn2+RJ6qsGflpYrPnxG6A4r9zqGY3qCcqDuhsWGQhoXpQ0663cWFM4qNR0Jxj1R0UBT36pahMneH4NYV27jElOeyAAAACACAABy4uvGyOsj21Y9h3gIA3PuxYAYAuC/7vftf7L+PXunMAQDwBQIAAAIA5vR/HwCvOQ//TzLL7cPIHUC0zMI5v7+tHiVfzWOeSrJKZbFabWGNSnJE+jmsnmTjTZm6kBi9r0aLgm8qNk6t67ATuPlEitG+g+E7in1GMYxCxmIF9YzNJK7lRoSPc6PCD+8fxhp+YjdttDNAJw3UUU83M1jFClaylkpkU08NVZqkh0oaaBLPnaCTNhqpoaok2UkPZqy/JyfpKnVLkhrq6KGZCjpZxTJWqN9uJofD5HGMzSXHLaVbOmuTSnOp6cTQgJlaB6oF7RIITul8N+1sYjnL6aJqqoZ2inaxDIY2s2zwlXUs5zj7OPJmAPao+ZhVHy0A')
|
|
||||||
format('woff2');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'intro-text';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: block;
|
|
||||||
/* Only contains the chars for "select an image" */
|
|
||||||
src: url('data:font/woff2;base64,d09GMgABAAAAAAXMAA4AAAAACwQAAAV5AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbg1IcMAZgAGwRCAqJUId/CxoAATYCJAMwBCAFgnAHIBskCcjEh6dNff8Ou/9Tj9VZGnUhJeqFzWGiVVOkxkTthr9f6kWkRdsBbkIj3YuLaloFZWr7aBg22z7IOoWqBWCW5cZU3GBrh0n+dAcBYAUlzYHAzWTYchqKXEyAT0zOLIS1qqm+B8q2ur4OhEMy0PHHUH8KklaSr8T0mp/EU7kRvXlI1E09HXA1qLN8Djxa0AsSDOg3cJARb9mtQJoSK3gvEn372/gcAigg/gOnbsT/MYv491GTReW4rJC5LA+h5FFclF6QQgoZ5Kx7GbsuGeytUgFClkOomY2Gdake3m9HegkHieAx/a0hBTALsy4jvpxBcnFXUnjC+2ZS5zHnDeEaJVwi+ZWqzOm4Uvgy4k6kGv4kFDVkfjk1gkVRRkk2zlo42PBbRJmG30cClJQjak7BnfQqHza4ITKftQZ/ZMUaEiyy1+mYCh4clKhDA5rQglZ0oG9jw+qiNvT+SfxIXCeuFdeIq8VV4gpxyRaGOl0JiChiCocfc7e93DwZIPvWgPiZJLcJugxyjW7UQyl4TJk6dWqYU7Cn0WQiWnNJCdGeprqjW63fpVS3mKy4YGZ6I3ya4nbIVgM1mwkpNEBzixlNxfPmH7owvdE4973OM9quvk11dwvnzDUy/Zn5S5Ywpn/PeqXBQI2m4lna05CRtsI6+GIENjS9K4jWRHUGYA2ozdZm2Smmf0DI3aqpeNbsJfxe7YdFmcZAn5gXLCFa2/Umqiu017APFhMZ0rfQp4sJX0ZrJ+n9UtAljr5VYWb6oj1MrpvX3qe6u8WRJg0bj7aPkDOa7m+E0Oa9Y8eY/gbRbr+efH7hcO49bMd28fbDVHcUmm3XkozQGKjeBHSJ4TQnI879LIFmF2v/BJuEQlffJPfE9oKayS/PsPE44fvM4MsBESxbuEEV39d5pw6oW4vD6S1WQC3UpSbHNbK0Jikl0bphSs+0CGW8Ew4Kzw7zarmcVz873JHTFhKYay18R8vY0ozPiHPAGyROAqlW5fLj5+HbWBn9TpgekKsOy8N+4dlFfL9i/Nk3+gY1bwzZUAvLVNiFpvqHRenetSoVrgn2obGtPsltEVxEeHJAQFhyBIcHT9rDvTJm06e0TLgase2gd2RffGJNg0o1zrdRyi9s1bYE5bi85cK+o/nUwvBR5+jweEBaSMoCub29fEFISmib9Dn5yl5kVFpoGrPQSmZhafQ7WimttNCH7Cktohb6kFpoEfIsdDF7SgvZTbaY3mKFyLXQh+wpzWE3mUNQQkWnR+lgiW9Afkunej49Nz3sYlI8XFTRdkNhUR5d6h4oOpJc8OjcItMXVqoHW2fSW6ycWuiM8NDYoICouLAZ9BYrwwmhycvKlt4Q8hUlCV5nZ7vOm2ut2rizcFpuWpSrT3K1Z3xinbuHnXBTyGAljV/XzHZaNGu6y7vLDziMpIyUdOBBRXXlxznUQiOoheZsZk9njG8er1mNmz2eOCQ3x9BbLP+Zxt+VrbEz9aWxmimRvyl4/sumyoM/nw+LNzV4/uP0/9T/P5f08cvhl38USAS/xTYs2fL/VNFF0vd+SVsRB/khPwW4SCi5SHhx8fDgVPAiAqIJRQL/EuK5bsRzNnAiezD1i7u2VyHHAKRU+E2YUaA5DUsE2ZfbApAmsJcxjBwmQ2Xk4Y2BqZJ+oxSzsNEogz1O3/xkBMKEBHSiC8PoQStaoEIflPCHL/wQBCUKoUITlMhHP+olt5ojykUPOrEQTWgY+f449KMPKnSiB72jGrLQhEZO24925LtbkG5DndTkD2/4um+NQBEyUIJsRBxX24tcDGmbWtGJjq05jhzTaMgCcR+6EA4f+KAXDay5u6RsL7xJsm3w3mzvFvggB8nI/AYBdVnxNvx/2wA=')
|
|
||||||
format('woff2');
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.intro {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 1fr min-content;
|
|
||||||
align-items: center;
|
|
||||||
background: rgba(255, 255, 255, 0.25);
|
|
||||||
text-align: center;
|
|
||||||
font-size: 2rem;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 20px 0 0;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overscroll-behavior: contain;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-container {
|
|
||||||
position: relative;
|
|
||||||
padding-top: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-sizer {
|
|
||||||
width: 90%;
|
|
||||||
max-width: 52vh;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
composes: abs-fill from '../util.css';
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.open-image-guide {
|
|
||||||
font: 300 11vw intro-text, sans-serif;
|
|
||||||
margin-bottom: 0;
|
|
||||||
|
|
||||||
@media (min-width: 460px) {
|
|
||||||
font-size: 50.6px;
|
|
||||||
padding: 0 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-button {
|
|
||||||
composes: unbutton from '../util.css';
|
|
||||||
font-weight: 500;
|
|
||||||
color: #5d509e;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demos {
|
|
||||||
display: block;
|
|
||||||
padding: 0;
|
|
||||||
border-top: 1px solid #e8e8e8;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
@media (min-width: 400px) {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 580px) {
|
|
||||||
border-top: none;
|
|
||||||
width: 523px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 900px) {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
||||||
width: 773px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-item {
|
|
||||||
background: #fff;
|
|
||||||
display: flex;
|
|
||||||
border-bottom: 1px solid #e8e8e8;
|
|
||||||
|
|
||||||
@media (min-width: 580px) {
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-button {
|
|
||||||
composes: unbutton from '../util.css';
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 7px;
|
|
||||||
font-size: 1.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-img-container {
|
|
||||||
overflow: hidden;
|
|
||||||
display: block;
|
|
||||||
width: 47px;
|
|
||||||
background: #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-img-aspect {
|
|
||||||
position: relative;
|
|
||||||
padding-top: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-icon {
|
|
||||||
composes: abs-fill from '../util.css';
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-description {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
text-align: left;
|
|
||||||
flex: 1;
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-loading {
|
|
||||||
composes: abs-fill from '../util.css';
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
animation: fade-in 300ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-loading-spinner {
|
|
||||||
--color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.install-button {
|
|
||||||
composes: unbutton from '../util.css';
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background: #504488;
|
|
||||||
}
|
|
||||||
|
|
||||||
background: #5d509e;
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
color: #fff;
|
|
||||||
padding: 14px;
|
|
||||||
font-size: 1.3rem;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
top: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
|
|
||||||
animation: fade-in 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-links {
|
|
||||||
display: flex;
|
|
||||||
padding: 0;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 1.3rem;
|
|
||||||
|
|
||||||
& li {
|
|
||||||
display: block;
|
|
||||||
border-left: 1px solid #000;
|
|
||||||
padding: 0 0.6em;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& a:link {
|
|
||||||
color: #5d509e;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
.abs-fill {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
contain: strict;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unbutton {
|
|
||||||
cursor: pointer;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font: inherit;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
src/shared/missing-types.d.ts
vendored
@@ -13,3 +13,23 @@
|
|||||||
/// <reference path="../../missing-types.d.ts" />
|
/// <reference path="../../missing-types.d.ts" />
|
||||||
|
|
||||||
declare const __PRERENDER__: boolean;
|
declare const __PRERENDER__: boolean;
|
||||||
|
|
||||||
|
interface ResizeObserverCallback {
|
||||||
|
(entries: ResizeObserverEntry[], observer: ResizeObserver): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResizeObserverEntry {
|
||||||
|
readonly target: Element;
|
||||||
|
readonly contentRect: DOMRectReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResizeObserver {
|
||||||
|
observe(target: Element): void;
|
||||||
|
unobserve(target: Element): void;
|
||||||
|
disconnect(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var ResizeObserver: {
|
||||||
|
prototype: ResizeObserver;
|
||||||
|
new (callback: ResizeObserverCallback): ResizeObserver;
|
||||||
|
};
|
||||||
|
|||||||
401
src/shared/prerendered-app/Intro/blob-anim/index.ts
Normal file
@@ -0,0 +1,401 @@
|
|||||||
|
import * as style from '../style.css';
|
||||||
|
import { startBlobs } from './meta';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control point x,y - point x,y - control point x,y
|
||||||
|
*/
|
||||||
|
export type BlobPoint = [number, number, number, number, number, number];
|
||||||
|
|
||||||
|
const maxPointDistance = 0.25;
|
||||||
|
|
||||||
|
function randomisePoint(point: BlobPoint): BlobPoint {
|
||||||
|
const distance = Math.random() * maxPointDistance;
|
||||||
|
const angle = Math.random() * Math.PI * 2;
|
||||||
|
const xShift = Math.sin(angle) * distance;
|
||||||
|
const yShift = Math.cos(angle) * distance;
|
||||||
|
return [
|
||||||
|
point[0] + xShift,
|
||||||
|
point[1] + yShift,
|
||||||
|
point[2] + xShift,
|
||||||
|
point[3] + yShift,
|
||||||
|
point[4] + xShift,
|
||||||
|
point[5] + yShift,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function easeInOutQuad(x: number): number {
|
||||||
|
return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rand = (min: number, max: number) => Math.random() * (max - min) + min;
|
||||||
|
|
||||||
|
interface CircleBlobPointState {
|
||||||
|
basePoint: BlobPoint;
|
||||||
|
pos: number;
|
||||||
|
duration: number;
|
||||||
|
startPoint: BlobPoint;
|
||||||
|
endPoint: BlobPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Bezier points for a seven point circle, to 3 decimal places */
|
||||||
|
const sevenPointCircle: BlobPoint[] = [
|
||||||
|
[-0.304, -1, 0, -1, 0.304, -1],
|
||||||
|
[0.592, -0.861, 0.782, -0.623, 0.972, -0.386],
|
||||||
|
[1.043, -0.074, 0.975, 0.223, 0.907, 0.519],
|
||||||
|
[0.708, 0.769, 0.434, 0.901, 0.16, 1.033],
|
||||||
|
[-0.16, 1.033, -0.434, 0.901, -0.708, 0.769],
|
||||||
|
[-0.907, 0.519, -0.975, 0.223, -1.043, -0.074],
|
||||||
|
[-0.972, -0.386, -0.782, -0.623, -0.592, -0.861],
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Should it be needed, here's how the above was created:
|
||||||
|
function createBezierCirclePoints(points: number): BlobPoint[] {
|
||||||
|
const anglePerPoint = 360 / points;
|
||||||
|
const matrix = new DOMMatrix();
|
||||||
|
const point = new DOMPoint();
|
||||||
|
const controlDistance = (4 / 3) * Math.tan(Math.PI / (2 * points));
|
||||||
|
return Array.from({ length: points }, (_, i) => {
|
||||||
|
point.x = -controlDistance;
|
||||||
|
point.y = -1;
|
||||||
|
const cp1 = point.matrixTransform(matrix);
|
||||||
|
point.x = 0;
|
||||||
|
point.y = -1;
|
||||||
|
const p = point.matrixTransform(matrix);
|
||||||
|
point.x = controlDistance;
|
||||||
|
point.y = -1;
|
||||||
|
const cp2 = point.matrixTransform(matrix);
|
||||||
|
const basePoint: BlobPoint = [cp1.x, cp1.y, p.x, p.y, cp2.x, cp2.y];
|
||||||
|
matrix.rotateSelf(0, 0, anglePerPoint);
|
||||||
|
return basePoint;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface CircleBlobOptions {
|
||||||
|
minDuration?: number;
|
||||||
|
maxDuration?: number;
|
||||||
|
startPoints?: BlobPoint[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class CircleBlob {
|
||||||
|
private animStates: CircleBlobPointState[];
|
||||||
|
private minDuration: number;
|
||||||
|
private maxDuration: number;
|
||||||
|
private points: BlobPoint[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
basePoints: BlobPoint[],
|
||||||
|
{
|
||||||
|
startPoints = basePoints.map((point) => randomisePoint(point)),
|
||||||
|
minDuration = 4000,
|
||||||
|
maxDuration = 11000,
|
||||||
|
}: CircleBlobOptions = {},
|
||||||
|
) {
|
||||||
|
this.points = startPoints;
|
||||||
|
this.minDuration = minDuration;
|
||||||
|
this.maxDuration = maxDuration;
|
||||||
|
this.animStates = basePoints.map((basePoint, i) => ({
|
||||||
|
basePoint,
|
||||||
|
pos: 0,
|
||||||
|
duration: rand(minDuration, maxDuration),
|
||||||
|
startPoint: startPoints[i],
|
||||||
|
endPoint: randomisePoint(basePoint),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(timeDelta: number): void {
|
||||||
|
this.points = this.animStates.map((animState) => {
|
||||||
|
animState.pos += timeDelta / animState.duration;
|
||||||
|
if (animState.pos >= 1) {
|
||||||
|
animState.startPoint = animState.endPoint;
|
||||||
|
animState.pos = 0;
|
||||||
|
animState.duration = rand(this.minDuration, this.maxDuration);
|
||||||
|
animState.endPoint = randomisePoint(animState.basePoint);
|
||||||
|
}
|
||||||
|
const eased = easeInOutQuad(animState.pos);
|
||||||
|
|
||||||
|
const point = animState.startPoint.map((startPoint, i) => {
|
||||||
|
const endPoint = animState.endPoint[i];
|
||||||
|
return (endPoint - startPoint) * eased + startPoint;
|
||||||
|
}) as BlobPoint;
|
||||||
|
|
||||||
|
return point;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(ctx: CanvasRenderingContext2D) {
|
||||||
|
const points = this.points;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(points[0][2], points[0][3]);
|
||||||
|
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
const nextI = i === points.length - 1 ? 0 : i + 1;
|
||||||
|
ctx.bezierCurveTo(
|
||||||
|
points[i][4],
|
||||||
|
points[i][5],
|
||||||
|
points[nextI][0],
|
||||||
|
points[nextI][1],
|
||||||
|
points[nextI][2],
|
||||||
|
points[nextI][3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const centralBlobsRotationTime = 120000;
|
||||||
|
|
||||||
|
class CentralBlobs {
|
||||||
|
private rotatePos: number = 0;
|
||||||
|
private blobs = Array.from(
|
||||||
|
{ length: 4 },
|
||||||
|
(_, i) => new CircleBlob(sevenPointCircle, { startPoints: startBlobs[i] }),
|
||||||
|
);
|
||||||
|
|
||||||
|
advance(timeDelta: number) {
|
||||||
|
this.rotatePos =
|
||||||
|
(this.rotatePos + timeDelta / centralBlobsRotationTime) % 1;
|
||||||
|
for (const blob of this.blobs) blob.advance(timeDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(ctx: CanvasRenderingContext2D, x: number, y: number, radius: number) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.scale(radius, radius);
|
||||||
|
ctx.rotate(Math.PI * 2 * this.rotatePos);
|
||||||
|
for (const blob of this.blobs) blob.draw(ctx);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bgBlobsMinRadius = 20;
|
||||||
|
const bgBlobsMaxRadius = 60;
|
||||||
|
const bgBlobsMinAlpha = 0.1;
|
||||||
|
const bgBlobsMaxAlpha = 0.8;
|
||||||
|
const bgBlobsGridSize = 200;
|
||||||
|
const bgBlobsMinSpinTime = 20000;
|
||||||
|
const bgBlobsMaxSpinTime = 60000;
|
||||||
|
const bgBlobsMinVelocity = 0.005;
|
||||||
|
const bgBlobsMaxVelocity = 0.02;
|
||||||
|
|
||||||
|
interface BackgroundBlob {
|
||||||
|
blob: CircleBlob;
|
||||||
|
velocity: number;
|
||||||
|
spinTime: number;
|
||||||
|
alpha: number;
|
||||||
|
alphaMultiplier: number;
|
||||||
|
rotatePos: number;
|
||||||
|
radius: number;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bgBlobsAlphaTime = 2000;
|
||||||
|
|
||||||
|
class BackgroundBlobs {
|
||||||
|
private bgBlobs: BackgroundBlob[] = [];
|
||||||
|
private overallAlphaPos = 0;
|
||||||
|
|
||||||
|
constructor(bounds: DOMRect) {
|
||||||
|
for (let x = 0; x < bounds.width; x += bgBlobsGridSize) {
|
||||||
|
for (let y = 0; y < bounds.height; y += bgBlobsGridSize) {
|
||||||
|
this.bgBlobs.push({
|
||||||
|
blob: new CircleBlob(sevenPointCircle, {
|
||||||
|
minDuration: 2000,
|
||||||
|
maxDuration: 5000,
|
||||||
|
}),
|
||||||
|
velocity: rand(bgBlobsMinVelocity, bgBlobsMaxVelocity),
|
||||||
|
alpha:
|
||||||
|
Math.random() ** 3 * (bgBlobsMaxAlpha - bgBlobsMinAlpha) +
|
||||||
|
bgBlobsMinAlpha,
|
||||||
|
alphaMultiplier: 1,
|
||||||
|
spinTime: rand(bgBlobsMinSpinTime, bgBlobsMaxSpinTime),
|
||||||
|
rotatePos: 0,
|
||||||
|
radius:
|
||||||
|
Math.random() ** 3 * (bgBlobsMaxRadius - bgBlobsMinRadius) +
|
||||||
|
bgBlobsMinRadius,
|
||||||
|
x: Math.random() * bgBlobsGridSize + x,
|
||||||
|
y: Math.random() * bgBlobsGridSize + y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(
|
||||||
|
timeDelta: number,
|
||||||
|
bounds: DOMRect,
|
||||||
|
targetX: number,
|
||||||
|
targetY: number,
|
||||||
|
targetRadius: number,
|
||||||
|
) {
|
||||||
|
if (this.overallAlphaPos !== 1) {
|
||||||
|
this.overallAlphaPos = Math.min(
|
||||||
|
1,
|
||||||
|
this.overallAlphaPos + timeDelta / bgBlobsAlphaTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const bgBlob of this.bgBlobs) {
|
||||||
|
bgBlob.blob.advance(timeDelta);
|
||||||
|
let dist = Math.hypot(bgBlob.x - targetX, bgBlob.y - targetY);
|
||||||
|
bgBlob.rotatePos = (bgBlob.rotatePos + timeDelta / bgBlob.spinTime) % 1;
|
||||||
|
const shiftDist = bgBlob.velocity * timeDelta;
|
||||||
|
|
||||||
|
if (dist < 10) {
|
||||||
|
// Move the circle out to a random edge
|
||||||
|
switch (Math.floor(Math.random() * 4)) {
|
||||||
|
case 0: // top
|
||||||
|
bgBlob.x = Math.random() * bounds.width;
|
||||||
|
bgBlob.y = -(bgBlob.radius * (1 + maxPointDistance));
|
||||||
|
break;
|
||||||
|
case 1: // left
|
||||||
|
bgBlob.x = -(bgBlob.radius * (1 + maxPointDistance));
|
||||||
|
bgBlob.y = Math.random() * bounds.height;
|
||||||
|
break;
|
||||||
|
case 2: // bottom
|
||||||
|
bgBlob.x = Math.random() * bounds.width;
|
||||||
|
bgBlob.y = bounds.height + bgBlob.radius * (1 + maxPointDistance);
|
||||||
|
break;
|
||||||
|
case 3: // right
|
||||||
|
bgBlob.x = bounds.width + bgBlob.radius * (1 + maxPointDistance);
|
||||||
|
bgBlob.y = Math.random() * bounds.height;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dist = Math.hypot(bgBlob.x - targetX, bgBlob.y - targetY);
|
||||||
|
const direction = Math.atan2(targetX - bgBlob.x, targetY - bgBlob.y);
|
||||||
|
const xShift = Math.sin(direction) * shiftDist;
|
||||||
|
const yShift = Math.cos(direction) * shiftDist;
|
||||||
|
bgBlob.x += xShift;
|
||||||
|
bgBlob.y += yShift;
|
||||||
|
bgBlob.alphaMultiplier = Math.min(dist / targetRadius, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(ctx: CanvasRenderingContext2D) {
|
||||||
|
const overallAlpha = easeInOutQuad(this.overallAlphaPos);
|
||||||
|
|
||||||
|
for (const bgBlob of this.bgBlobs) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.globalAlpha = bgBlob.alpha * bgBlob.alphaMultiplier * overallAlpha;
|
||||||
|
ctx.translate(bgBlob.x, bgBlob.y);
|
||||||
|
ctx.scale(bgBlob.radius, bgBlob.radius);
|
||||||
|
ctx.rotate(Math.PI * 2 * bgBlob.rotatePos);
|
||||||
|
bgBlob.blob.draw(ctx);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deltaMultiplierStep = 0.01;
|
||||||
|
|
||||||
|
export function startBlobAnim(canvas: HTMLCanvasElement) {
|
||||||
|
let lastTime: number;
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
const centralBlobs = new CentralBlobs();
|
||||||
|
let backgroundBlobs: BackgroundBlobs;
|
||||||
|
const loadImgEl = document.querySelector('.' + style.loadImg)!;
|
||||||
|
let hasFocus = document.hasFocus();
|
||||||
|
let deltaMultiplier = hasFocus ? 1 : 0;
|
||||||
|
let animating = true;
|
||||||
|
|
||||||
|
const visibilityListener = () => {
|
||||||
|
// 'Pause time' while page is hidden
|
||||||
|
if (document.visibilityState === 'visible') lastTime = performance.now();
|
||||||
|
};
|
||||||
|
const focusListener = () => {
|
||||||
|
hasFocus = true;
|
||||||
|
if (!animating) startAnim();
|
||||||
|
};
|
||||||
|
const blurListener = () => {
|
||||||
|
hasFocus = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
new ResizeObserver(() => {
|
||||||
|
// Redraw for new canvas size
|
||||||
|
if (!animating) drawFrame(0);
|
||||||
|
}).observe(canvas);
|
||||||
|
|
||||||
|
addEventListener('focus', focusListener);
|
||||||
|
addEventListener('blur', blurListener);
|
||||||
|
document.addEventListener('visibilitychange', visibilityListener);
|
||||||
|
|
||||||
|
function destruct() {
|
||||||
|
removeEventListener('focus', focusListener);
|
||||||
|
removeEventListener('blur', blurListener);
|
||||||
|
document.removeEventListener('visibilitychange', visibilityListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawFrame(delta: number) {
|
||||||
|
const canvasBounds = canvas.getBoundingClientRect();
|
||||||
|
canvas.width = canvasBounds.width * devicePixelRatio;
|
||||||
|
canvas.height = canvasBounds.height * devicePixelRatio;
|
||||||
|
const loadImgBounds = loadImgEl.getBoundingClientRect();
|
||||||
|
const computedStyles = getComputedStyle(canvas);
|
||||||
|
const blobPink = computedStyles.getPropertyValue('--blob-pink');
|
||||||
|
const loadImgCenterX =
|
||||||
|
loadImgBounds.left - canvasBounds.left + loadImgBounds.width / 2;
|
||||||
|
const loadImgCenterY =
|
||||||
|
loadImgBounds.top - canvasBounds.top + loadImgBounds.height / 2;
|
||||||
|
const loadImgRadius = loadImgBounds.height / 2 / (1 + maxPointDistance);
|
||||||
|
|
||||||
|
ctx.scale(devicePixelRatio, devicePixelRatio);
|
||||||
|
|
||||||
|
if (!backgroundBlobs) backgroundBlobs = new BackgroundBlobs(canvasBounds);
|
||||||
|
backgroundBlobs.advance(
|
||||||
|
delta,
|
||||||
|
canvasBounds,
|
||||||
|
loadImgCenterX,
|
||||||
|
loadImgCenterY,
|
||||||
|
loadImgRadius,
|
||||||
|
);
|
||||||
|
centralBlobs.advance(delta);
|
||||||
|
|
||||||
|
ctx.globalAlpha = Number(
|
||||||
|
computedStyles.getPropertyValue('--center-blob-opacity'),
|
||||||
|
);
|
||||||
|
ctx.fillStyle = blobPink;
|
||||||
|
|
||||||
|
backgroundBlobs.draw(ctx);
|
||||||
|
centralBlobs.draw(ctx, loadImgCenterX, loadImgCenterY, loadImgRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
function frame(time: number) {
|
||||||
|
// Stop the loop if the canvas is gone
|
||||||
|
if (!canvas.isConnected) {
|
||||||
|
destruct();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Be kind: If the window isn't focused, bring the animation to a stop.
|
||||||
|
if (!hasFocus) {
|
||||||
|
// Bring the anim to a slow stop
|
||||||
|
deltaMultiplier = Math.max(0, deltaMultiplier - deltaMultiplierStep);
|
||||||
|
if (deltaMultiplier === 0) {
|
||||||
|
animating = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (deltaMultiplier !== 1) {
|
||||||
|
deltaMultiplier = Math.min(1, deltaMultiplier + deltaMultiplierStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = (time - lastTime) * deltaMultiplier;
|
||||||
|
lastTime = time;
|
||||||
|
|
||||||
|
drawFrame(delta);
|
||||||
|
|
||||||
|
requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAnim() {
|
||||||
|
animating = true;
|
||||||
|
requestAnimationFrame((time: number) => {
|
||||||
|
lastTime = time;
|
||||||
|
frame(time);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startAnim();
|
||||||
|
}
|
||||||
41
src/shared/prerendered-app/Intro/blob-anim/meta.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type { BlobPoint } from '.';
|
||||||
|
|
||||||
|
/** Start points, for the shape we use in prerender */
|
||||||
|
export const startBlobs: BlobPoint[][] = [
|
||||||
|
[
|
||||||
|
[-0.232, -1.029, 0.073, -1.029, 0.377, -1.029],
|
||||||
|
[0.565, -1.098, 0.755, -0.86, 0.945, -0.622],
|
||||||
|
[0.917, -0.01, 0.849, 0.286, 0.782, 0.583],
|
||||||
|
[0.85, 0.687, 0.576, 0.819, 0.302, 0.951],
|
||||||
|
[-0.198, 1.009, -0.472, 0.877, -0.746, 0.745],
|
||||||
|
[-0.98, 0.513, -1.048, 0.216, -1.116, -0.08],
|
||||||
|
[-0.964, -0.395, -0.774, -0.633, -0.584, -0.871],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-0.505, -1.109, -0.201, -1.109, 0.104, -1.109],
|
||||||
|
[0.641, -0.684, 0.831, -0.446, 1.02, -0.208],
|
||||||
|
[1.041, 0.034, 0.973, 0.331, 0.905, 0.628],
|
||||||
|
[0.734, 0.794, 0.46, 0.926, 0.186, 1.058],
|
||||||
|
[-0.135, 0.809, -0.409, 0.677, -0.684, 0.545],
|
||||||
|
[-0.935, 0.404, -1.002, 0.108, -1.07, -0.189],
|
||||||
|
[-0.883, -0.402, -0.693, -0.64, -0.503, -0.878],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-0.376, -1.168, -0.071, -1.168, 0.233, -1.168],
|
||||||
|
[0.732, -0.956, 0.922, -0.718, 1.112, -0.48],
|
||||||
|
[1.173, 0.027, 1.105, 0.324, 1.038, 0.621],
|
||||||
|
[0.707, 0.81, 0.433, 0.943, 0.159, 1.075],
|
||||||
|
[-0.096, 1.135, -0.37, 1.003, -0.644, 0.871],
|
||||||
|
[-0.86, 0.457, -0.927, 0.161, -0.995, -0.136],
|
||||||
|
[-0.87, -0.516, -0.68, -0.754, -0.49, -0.992],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-0.309, -0.998, -0.004, -0.998, 0.3, -0.998],
|
||||||
|
[0.535, -0.852, 0.725, -0.614, 0.915, -0.376],
|
||||||
|
[1.05, -0.09, 0.982, 0.207, 0.915, 0.504],
|
||||||
|
[0.659, 0.807, 0.385, 0.939, 0.111, 1.071],
|
||||||
|
[-0.178, 1.048, -0.452, 0.916, -0.727, 0.784],
|
||||||
|
[-0.942, 0.582, -1.009, 0.285, -1.077, -0.011],
|
||||||
|
[-1.141, -0.335, -0.951, -0.573, -0.761, -0.811],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src/shared/prerendered-app/Intro/imgs/demos/icon-demo-logo.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
1
src/shared/prerendered-app/Intro/imgs/github-logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 29.34 28.61"><path fill="#838383" d="M14.67 0a14.67 14.67 0 00-4.64 28.59c.74.13 1-.32 1-.7l-.02-2.74c-4.08.89-4.94-1.73-4.94-1.73a3.88 3.88 0 00-1.63-2.14c-1.33-.91.1-.9.1-.9A3.08 3.08 0 016.8 21.9a3.12 3.12 0 004.27 1.22 3.12 3.12 0 01.93-1.96c-3.26-.37-6.68-1.63-6.68-7.25a5.68 5.68 0 011.5-3.94 5.27 5.27 0 01.15-3.9S8.2 5.7 11 7.58a13.9 13.9 0 017.34 0c2.8-1.9 4.03-1.5 4.03-1.5a5.27 5.27 0 01.15 3.9 5.67 5.67 0 011.5 3.93c0 5.63-3.42 6.87-6.7 7.24a3.5 3.5 0 011 2.71l-.01 4.03c0 .39.26.85 1 .7A14.67 14.67 0 0014.67 0z"/></svg>
|
||||||
|
After Width: | Height: | Size: 588 B |
1
src/shared/prerendered-app/Intro/imgs/logo-with-text.svg
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
382
src/shared/prerendered-app/Intro/index.tsx
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
import { h, Component } from 'preact';
|
||||||
|
|
||||||
|
import { linkRef } from 'shared/prerendered-app/util';
|
||||||
|
import '../../custom-els/loading-spinner';
|
||||||
|
import logo from 'url:./imgs/logo.svg';
|
||||||
|
import githubLogo from 'url:./imgs/github-logo.svg';
|
||||||
|
import largePhoto from 'url:./imgs/demos/demo-large-photo.jpg';
|
||||||
|
import artwork from 'url:./imgs/demos/demo-artwork.jpg';
|
||||||
|
import deviceScreen from 'url:./imgs/demos/demo-device-screen.png';
|
||||||
|
import largePhotoIcon from 'url:./imgs/demos/icon-demo-large-photo.jpg';
|
||||||
|
import artworkIcon from 'url:./imgs/demos/icon-demo-artwork.jpg';
|
||||||
|
import deviceScreenIcon from 'url:./imgs/demos/icon-demo-device-screen.jpg';
|
||||||
|
import logoIcon from 'url:./imgs/demos/icon-demo-logo.png';
|
||||||
|
import logoWithText from 'url:./imgs/logo-with-text.svg';
|
||||||
|
import * as style from './style.css';
|
||||||
|
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||||
|
import 'shared/custom-els/snack-bar';
|
||||||
|
import { startBlobs } from './blob-anim/meta';
|
||||||
|
|
||||||
|
const demos = [
|
||||||
|
{
|
||||||
|
description: 'Large photo',
|
||||||
|
size: '2.8mb',
|
||||||
|
filename: 'photo.jpg',
|
||||||
|
url: largePhoto,
|
||||||
|
iconUrl: largePhotoIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Artwork',
|
||||||
|
size: '2.9mb',
|
||||||
|
filename: 'art.jpg',
|
||||||
|
url: artwork,
|
||||||
|
iconUrl: artworkIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Device screen',
|
||||||
|
size: '1.6mb',
|
||||||
|
filename: 'pixel3.png',
|
||||||
|
url: deviceScreen,
|
||||||
|
iconUrl: deviceScreenIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'SVG icon',
|
||||||
|
size: '13k',
|
||||||
|
filename: 'squoosh.svg',
|
||||||
|
url: logo,
|
||||||
|
iconUrl: logoIcon,
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const blobAnimImport =
|
||||||
|
!__PRERENDER__ && matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
? undefined
|
||||||
|
: import('./blob-anim');
|
||||||
|
const installButtonSource = 'introInstallButton-Purple';
|
||||||
|
const supportsClipboardAPI =
|
||||||
|
!__PRERENDER__ && navigator.clipboard && navigator.clipboard.read;
|
||||||
|
|
||||||
|
async function getImageClipboardItem(
|
||||||
|
items: ClipboardItem[],
|
||||||
|
): Promise<undefined | Blob> {
|
||||||
|
for (const item of items) {
|
||||||
|
const type = item.types.find((type) => type.startsWith('image/'));
|
||||||
|
if (type) return item.getType(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onFile?: (file: File) => void;
|
||||||
|
showSnack?: SnackBarElement['showSnackbar'];
|
||||||
|
}
|
||||||
|
interface State {
|
||||||
|
fetchingDemoIndex?: number;
|
||||||
|
beforeInstallEvent?: BeforeInstallPromptEvent;
|
||||||
|
showBlobSVG: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Intro extends Component<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
showBlobSVG: true,
|
||||||
|
};
|
||||||
|
private fileInput?: HTMLInputElement;
|
||||||
|
private blobCanvas?: HTMLCanvasElement;
|
||||||
|
private installingViaButton = false;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// Listen for beforeinstallprompt events, indicating Squoosh is installable.
|
||||||
|
window.addEventListener(
|
||||||
|
'beforeinstallprompt',
|
||||||
|
this.onBeforeInstallPromptEvent,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Listen for the appinstalled event, indicating Squoosh has been installed.
|
||||||
|
window.addEventListener('appinstalled', this.onAppInstalled);
|
||||||
|
|
||||||
|
if (blobAnimImport) {
|
||||||
|
blobAnimImport.then((module) => {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
showBlobSVG: false,
|
||||||
|
},
|
||||||
|
() => module.startBlobAnim(this.blobCanvas!),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove this
|
||||||
|
const demo = demos[3];
|
||||||
|
fetch(demo.url)
|
||||||
|
.then((r) => r.blob())
|
||||||
|
.then((blob) =>
|
||||||
|
this.props.onFile!(
|
||||||
|
new File([blob], demo.filename, { type: blob.type }),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener(
|
||||||
|
'beforeinstallprompt',
|
||||||
|
this.onBeforeInstallPromptEvent,
|
||||||
|
);
|
||||||
|
window.removeEventListener('appinstalled', this.onAppInstalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onFileChange = (event: Event): void => {
|
||||||
|
const fileInput = event.target as HTMLInputElement;
|
||||||
|
const file = fileInput.files && fileInput.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
this.fileInput!.value = '';
|
||||||
|
this.props.onFile!(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onOpenClick = () => {
|
||||||
|
this.fileInput!.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
private onDemoClick = async (index: number, event: Event) => {
|
||||||
|
try {
|
||||||
|
this.setState({ fetchingDemoIndex: index });
|
||||||
|
const demo = demos[index];
|
||||||
|
const blob = await fetch(demo.url).then((r) => r.blob());
|
||||||
|
const file = new File([blob], demo.filename, { type: blob.type });
|
||||||
|
this.props.onFile!(file);
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({ fetchingDemoIndex: undefined });
|
||||||
|
this.props.showSnack!("Couldn't fetch demo image");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onBeforeInstallPromptEvent = (event: BeforeInstallPromptEvent) => {
|
||||||
|
// Don't show the mini-infobar on mobile
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Save the beforeinstallprompt event so it can be called later.
|
||||||
|
this.setState({ beforeInstallEvent: event });
|
||||||
|
|
||||||
|
// Log the event.
|
||||||
|
const gaEventInfo = {
|
||||||
|
eventCategory: 'pwa-install',
|
||||||
|
eventAction: 'promo-shown',
|
||||||
|
nonInteraction: true,
|
||||||
|
};
|
||||||
|
ga('send', 'event', gaEventInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onInstallClick = async (event: Event) => {
|
||||||
|
// Get the deferred beforeinstallprompt event
|
||||||
|
const beforeInstallEvent = this.state.beforeInstallEvent;
|
||||||
|
// If there's no deferred prompt, bail.
|
||||||
|
if (!beforeInstallEvent) return;
|
||||||
|
|
||||||
|
this.installingViaButton = true;
|
||||||
|
|
||||||
|
// Show the browser install prompt
|
||||||
|
beforeInstallEvent.prompt();
|
||||||
|
|
||||||
|
// Wait for the user to accept or dismiss the install prompt
|
||||||
|
const { outcome } = await beforeInstallEvent.userChoice;
|
||||||
|
// Send the analytics data
|
||||||
|
const gaEventInfo = {
|
||||||
|
eventCategory: 'pwa-install',
|
||||||
|
eventAction: 'promo-clicked',
|
||||||
|
eventLabel: installButtonSource,
|
||||||
|
eventValue: outcome === 'accepted' ? 1 : 0,
|
||||||
|
};
|
||||||
|
ga('send', 'event', gaEventInfo);
|
||||||
|
|
||||||
|
// If the prompt was dismissed, we aren't going to install via the button.
|
||||||
|
if (outcome === 'dismissed') {
|
||||||
|
this.installingViaButton = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onAppInstalled = () => {
|
||||||
|
// We don't need the install button, if it's shown
|
||||||
|
this.setState({ beforeInstallEvent: undefined });
|
||||||
|
|
||||||
|
// Don't log analytics if page is not visible
|
||||||
|
if (document.hidden) return;
|
||||||
|
|
||||||
|
// Try to get the install, if it's not set, use 'browser'
|
||||||
|
const source = this.installingViaButton ? installButtonSource : 'browser';
|
||||||
|
ga('send', 'event', 'pwa-install', 'installed', source);
|
||||||
|
|
||||||
|
// Clear the install method property
|
||||||
|
this.installingViaButton = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
private onPasteClick = async () => {
|
||||||
|
let clipboardItems: ClipboardItem[];
|
||||||
|
|
||||||
|
try {
|
||||||
|
clipboardItems = await navigator.clipboard.read();
|
||||||
|
} catch (err) {
|
||||||
|
this.props.showSnack!(`No permission to access clipboard`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await getImageClipboardItem(clipboardItems);
|
||||||
|
|
||||||
|
if (!blob) {
|
||||||
|
this.props.showSnack!(`No image found in the clipboard`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onFile!(new File([blob], 'image.unknown'));
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
{}: Props,
|
||||||
|
{ fetchingDemoIndex, beforeInstallEvent, showBlobSVG }: State,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div class={style.intro}>
|
||||||
|
<input
|
||||||
|
class={style.hide}
|
||||||
|
ref={linkRef(this, 'fileInput')}
|
||||||
|
type="file"
|
||||||
|
onChange={this.onFileChange}
|
||||||
|
/>
|
||||||
|
<div class={style.main}>
|
||||||
|
{!__PRERENDER__ && (
|
||||||
|
<canvas
|
||||||
|
ref={linkRef(this, 'blobCanvas')}
|
||||||
|
class={style.blobCanvas}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<h1 class={style.logoContainer}>
|
||||||
|
<img
|
||||||
|
class={style.logo}
|
||||||
|
src={logoWithText}
|
||||||
|
alt="Squoosh"
|
||||||
|
width="539"
|
||||||
|
height="162"
|
||||||
|
/>
|
||||||
|
</h1>
|
||||||
|
<div class={style.loadImg}>
|
||||||
|
{showBlobSVG && (
|
||||||
|
<svg
|
||||||
|
class={style.blobSvg}
|
||||||
|
viewBox="-1.25 -1.25 2.5 2.5"
|
||||||
|
preserveAspectRatio="xMidYMid slice"
|
||||||
|
>
|
||||||
|
{startBlobs.map((points) => (
|
||||||
|
<path
|
||||||
|
d={points
|
||||||
|
.map((point, i) => {
|
||||||
|
const nextI = i === points.length - 1 ? 0 : i + 1;
|
||||||
|
let d = '';
|
||||||
|
if (i === 0) {
|
||||||
|
d += `M${point[2]} ${point[3]}`;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
d +
|
||||||
|
`C${point[4]} ${point[5]} ${points[nextI][0]} ${points[nextI][1]} ${points[nextI][2]} ${points[nextI][3]}`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.join('')}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
class={style.loadImgContent}
|
||||||
|
style={{ visibility: __PRERENDER__ ? 'hidden' : '' }}
|
||||||
|
>
|
||||||
|
<button class={style.loadBtn} onClick={this.onOpenClick}>
|
||||||
|
<svg viewBox="0 0 24 24" class={style.loadIcon}>
|
||||||
|
<path d="M19 7v3h-2V7h-3V5h3V2h2v3h3v2h-3zm-3 4V8h-3V5H5a2 2 0 00-2 2v12c0 1.1.9 2 2 2h12a2 2 0 002-2v-8h-3zM5 19l3-4 2 3 3-4 4 5H5z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<span class={style.dropText}>Drop </span>OR{' '}
|
||||||
|
{supportsClipboardAPI ? (
|
||||||
|
<button class={style.pasteBtn} onClick={this.onPasteClick}>
|
||||||
|
Paste
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
'Paste'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={style.demosContainer}>
|
||||||
|
<svg viewBox="0 0 1920 140" class={style.topWave}>
|
||||||
|
<path
|
||||||
|
d="M1920 0l-107 28c-106 29-320 85-533 93-213 7-427-36-640-50s-427 0-533 7L0 85v171h1920z"
|
||||||
|
class={style.subWave}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M0 129l64-26c64-27 192-81 320-75 128 5 256 69 384 64 128-6 256-80 384-91s256 43 384 70c128 26 256 26 320 26h64v96H0z"
|
||||||
|
class={style.mainWave}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class={style.contentPadding}>
|
||||||
|
<p class={style.demoTitle}>
|
||||||
|
Or <strong>try one</strong> of these:
|
||||||
|
</p>
|
||||||
|
<ul class={style.demos}>
|
||||||
|
{demos.map((demo, i) => (
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="unbutton"
|
||||||
|
onClick={(event) => this.onDemoClick(i, event)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class={style.demoIconContainer}>
|
||||||
|
<img
|
||||||
|
class={style.demoIcon}
|
||||||
|
src={demo.iconUrl}
|
||||||
|
alt={demo.description}
|
||||||
|
/>
|
||||||
|
{fetchingDemoIndex === i && (
|
||||||
|
<div class={style.demoLoader}>
|
||||||
|
<loading-spinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class={style.demoSize}>{demo.size}</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={style.footer}>
|
||||||
|
<svg viewBox="0 0 1920 79" class={style.topWave}>
|
||||||
|
<path
|
||||||
|
d="M0 59l64-11c64-11 192-34 320-43s256-5 384 4 256 23 384 34 256 21 384 14 256-30 320-41l64-11v94H0z"
|
||||||
|
class={style.footerWave}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class={style.contentPadding}>
|
||||||
|
<footer class={style.footerItems}>
|
||||||
|
<a
|
||||||
|
class={style.footerLink}
|
||||||
|
href="https://github.com/GoogleChromeLabs/squoosh/blob/dev/README.md#privacy"
|
||||||
|
>
|
||||||
|
Privacy
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class={style.footerLinkWithLogo}
|
||||||
|
href="https://github.com/GoogleChromeLabs/squoosh"
|
||||||
|
>
|
||||||
|
<img src={githubLogo} alt="" width="10" height="10" />
|
||||||
|
Source on Github
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{beforeInstallEvent && (
|
||||||
|
<button class={style.installBtn} onClick={this.onInstallClick}>
|
||||||
|
Install
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,3 +29,12 @@ interface BeforeInstallPromptEvent extends Event {
|
|||||||
interface WindowEventMap {
|
interface WindowEventMap {
|
||||||
beforeinstallprompt: BeforeInstallPromptEvent;
|
beforeinstallprompt: BeforeInstallPromptEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ClipboardItem {
|
||||||
|
types: string[];
|
||||||
|
getType(type: string): Promise<Blob>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Clipboard {
|
||||||
|
read(): Promise<ClipboardItem[]>;
|
||||||
|
}
|
||||||
243
src/shared/prerendered-app/Intro/style.css
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
.intro {
|
||||||
|
composes: abs-fill from global;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
overflow: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr max-content max-content;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--dark-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-canvas {
|
||||||
|
composes: abs-fill from global;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
min-height: 541px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: max-content max-content;
|
||||||
|
justify-items: center;
|
||||||
|
position: relative;
|
||||||
|
--blob-pink: var(--hot-pink);
|
||||||
|
--center-blob-opacity: 0.3;
|
||||||
|
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
min-height: 688px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
margin: 5rem 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
transform: translate(-1%, 0);
|
||||||
|
width: 189px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-img {
|
||||||
|
position: relative;
|
||||||
|
color: var(--white);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob-svg {
|
||||||
|
composes: abs-fill from global;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
fill: var(--blob-pink);
|
||||||
|
|
||||||
|
& path {
|
||||||
|
opacity: var(--center-blob-opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-img-content {
|
||||||
|
position: relative;
|
||||||
|
--size: 29rem;
|
||||||
|
max-width: var(--size);
|
||||||
|
width: 100vw;
|
||||||
|
height: var(--size);
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: max-content max-content;
|
||||||
|
justify-items: center;
|
||||||
|
align-content: center;
|
||||||
|
gap: 0.7rem;
|
||||||
|
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
--size: 36rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-btn {
|
||||||
|
composes: unbutton from global;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-icon {
|
||||||
|
--size: 5rem;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
fill: var(--white);
|
||||||
|
transform: translate(4.3%, -1%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.paste-btn {
|
||||||
|
composes: unbutton from global;
|
||||||
|
text-decoration: underline;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demos-container {
|
||||||
|
position: relative;
|
||||||
|
background: var(--deep-blue);
|
||||||
|
padding-bottom: 5.2vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-wave {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-wave {
|
||||||
|
fill: var(--deep-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-wave {
|
||||||
|
fill: var(--light-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: relative;
|
||||||
|
background: var(--light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-wave {
|
||||||
|
fill: var(--light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-padding {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-items {
|
||||||
|
display: grid;
|
||||||
|
justify-content: end;
|
||||||
|
grid-auto-columns: max-content;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link-with-logo {
|
||||||
|
composes: footer-link;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.8em max-content;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-btn {
|
||||||
|
composes: unbutton from global;
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
background: var(--deep-blue);
|
||||||
|
border-radius: 0.4em;
|
||||||
|
color: var(--white);
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
animation: fade-in 600ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-title {
|
||||||
|
color: var(--white);
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demos {
|
||||||
|
display: grid;
|
||||||
|
gap: 3rem;
|
||||||
|
justify-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
margin: 3rem auto;
|
||||||
|
--demo-size: 80px;
|
||||||
|
grid-template-columns: repeat(auto-fit, var(--demo-size));
|
||||||
|
|
||||||
|
@media (min-width: 740px) {
|
||||||
|
--demo-size: 100px;
|
||||||
|
gap: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > li {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-size {
|
||||||
|
background: var(--dim-blue);
|
||||||
|
border-radius: 1000px;
|
||||||
|
color: var(--white);
|
||||||
|
width: max-content;
|
||||||
|
padding: 0.5rem 1.2rem;
|
||||||
|
margin: 0.7rem auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-icon-container {
|
||||||
|
border-radius: var(--demo-size);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.demo-icon {
|
||||||
|
width: var(--demo-size);
|
||||||
|
height: var(--demo-size);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.demo-loader {
|
||||||
|
composes: abs-fill from global;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
animation: fade-in 600ms ease-in-out;
|
||||||
|
|
||||||
|
& > loading-spinner {
|
||||||
|
--color: var(--white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-text {
|
||||||
|
@media (max-width: 599px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/shared/prerendered-app/colors.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
html {
|
||||||
|
--pink: #ff3385;
|
||||||
|
--hot-pink: #ff0066;
|
||||||
|
--white: #fff;
|
||||||
|
--black: #000;
|
||||||
|
--blue: #5fb4e4;
|
||||||
|
--dim-blue: #0a7bcc;
|
||||||
|
--deep-blue: #09f;
|
||||||
|
--light-blue: #76c8ff;
|
||||||
|
--light-gray: #eaeaea;
|
||||||
|
--dark-text: #343a3e;
|
||||||
|
|
||||||
|
/* Old stuff: */
|
||||||
|
--gray-dark: rgba(0, 0, 0, 0.8);
|
||||||
|
|
||||||
|
--button-fg-color: 95, 180, 228;
|
||||||
|
--button-fg: rgb(95, 180, 228);
|
||||||
|
|
||||||
|
--negative: rgb(207, 113, 127);
|
||||||
|
--positive: rgb(149, 212, 159);
|
||||||
|
}
|
||||||
24
src/shared/prerendered-app/util.css
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
:global {
|
||||||
|
.abs-fill {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
contain: strict;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unbutton {
|
||||||
|
cursor: pointer;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font: inherit;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/static-build/missing-types.d.ts
vendored
@@ -11,7 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
/// <reference path="../../missing-types.d.ts" />
|
/// <reference path="../../missing-types.d.ts" />
|
||||||
/// <reference path="../shared/initial-app/Intro/missing-types.d.ts" />
|
/// <reference path="../shared/prerendered-app/Intro/missing-types.d.ts" />
|
||||||
|
|
||||||
declare module 'client-bundle:*' {
|
declare module 'client-bundle:*' {
|
||||||
const url: string;
|
const url: string;
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font: 12px/1.3 system-ui, -apple-system, BlinkMacSystemFont, Roboto, Helvetica,
|
font: 12px/1.3 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue',
|
||||||
sans-serif;
|
Helvetica, Arial, 'Lucida Grande', sans-serif;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overscroll-behavior: none;
|
overscroll-behavior: none;
|
||||||
contain: strict;
|
contain: strict;
|
||||||
@@ -34,16 +34,6 @@ body {
|
|||||||
background-size: 20px 20px;
|
background-size: 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
|
||||||
--gray-dark: rgba(0, 0, 0, 0.8);
|
|
||||||
|
|
||||||
--button-fg-color: 95, 180, 228;
|
|
||||||
--button-fg: rgb(95, 180, 228);
|
|
||||||
|
|
||||||
--negative: rgb(207, 113, 127);
|
|
||||||
--positive: rgb(149, 212, 159);
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(#app) {
|
:global(#app) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import initialCss from 'initial-css:';
|
|||||||
import { allSrc } from 'client-bundle:client/initial-app';
|
import { allSrc } from 'client-bundle:client/initial-app';
|
||||||
import favicon from 'url:static-build/assets/favicon.ico';
|
import favicon from 'url:static-build/assets/favicon.ico';
|
||||||
import { escapeStyleScriptContent } from 'static-build/utils';
|
import { escapeStyleScriptContent } from 'static-build/utils';
|
||||||
import Intro from 'shared/initial-app/Intro';
|
import Intro from 'shared/prerendered-app/Intro';
|
||||||
|
|
||||||
interface Props {}
|
interface Props {}
|
||||||
|
|
||||||
|
|||||||