Compare commits

..

1 Commits

Author SHA1 Message Date
Ingvar Stepanyan
67ec215622 Rebuilt JXL is different for some reason 2020-12-01 19:04:00 +00:00
106 changed files with 998 additions and 5542 deletions

View File

@@ -1,3 +1,3 @@
#!/bin/sh -e
docker build -t squoosh-cpp - < ../cpp.Dockerfile
docker run -it --rm -v $PWD:/src squoosh-cpp "$@"
docker run -it --rm -v $PWD:/src squoosh-cpp

View File

@@ -1,37 +1,18 @@
CODEC_URL = https://gitlab.com/wg1/jpeg-xl.git
CODEC_VERSION = v0.1
CODEC_DIR = node_modules/jxl
CODEC_BUILD_ROOT := $(CODEC_DIR)/build
CODEC_MT_BUILD_DIR := $(CODEC_BUILD_ROOT)/mt
CODEC_MT_SIMD_BUILD_DIR := $(CODEC_BUILD_ROOT)/mt-simd
CODEC_BUILD_DIR := $(CODEC_DIR)/build
CODEC_OUT := $(CODEC_BUILD_DIR)/lib/libjxl.a
OUT_JS = enc/jxl_enc.js enc/jxl_enc_mt.js enc/jxl_enc_mt_simd.js dec/jxl_dec.js
OUT_JS = enc/jxl_enc.js dec/jxl_dec.js
OUT_WASM = $(OUT_JS:.js=.wasm)
OUT_WORKER = $(OUT_JS:.js=.worker.js)
.PHONY: all clean
all: $(OUT_JS)
# Define dependencies for all variations of build artifacts.
$(filter enc/%,$(OUT_JS)): enc/jxl_enc.cpp
$(filter dec/%,$(OUT_JS)): dec/jxl_dec.cpp
# For single-threaded build, we compile with threads enabled, but then just don't use them nor link them in.
enc/jxl_enc.js enc/jxl_enc_mt.js dec/jxl_dec.js: CODEC_BUILD_DIR:=$(CODEC_MT_BUILD_DIR)
enc/jxl_enc_mt_simd.js: CODEC_BUILD_DIR:=$(CODEC_MT_SIMD_BUILD_DIR)
enc/jxl_enc.js dec/jxl_dec.js: $(CODEC_MT_BUILD_DIR)/lib/libjxl.a
enc/jxl_enc_mt.js: $(CODEC_MT_BUILD_DIR)/lib/libjxl.a $(CODEC_MT_BUILD_DIR)/lib/libjxl_threads.a
enc/jxl_enc_mt_simd.js: $(CODEC_MT_SIMD_BUILD_DIR)/lib/libjxl.a $(CODEC_MT_SIMD_BUILD_DIR)/lib/libjxl_threads.a
# Compile multithreaded wrappers with -pthread.
enc/jxl_enc_mt.js enc/jxl_enc_mt_simd.js: CXXFLAGS+=-pthread
$(OUT_JS):
%.js: %.cpp $(LIBAOM_OUT) $(CODEC_OUT)
$(CXX) \
$(CXXFLAGS) \
$(LDFLAGS) \
-I $(CODEC_DIR) \
-I $(CODEC_DIR)/lib \
-I $(CODEC_DIR)/lib/include \
@@ -40,6 +21,8 @@ $(OUT_JS):
-I $(CODEC_DIR)/third_party/skcms \
-I $(CODEC_DIR)/third_party/brunsli \
-I $(CODEC_DIR)/third_party/brunsli/c/include \
${CXXFLAGS} \
${LDFLAGS} \
--bind \
--closure 1 \
-s ALLOW_MEMORY_GROWTH=1 \
@@ -59,31 +42,18 @@ $(OUT_JS):
$(CODEC_BUILD_DIR)/third_party/libskcms.a \
$(CODEC_BUILD_DIR)/third_party/highway/libhwy.a
%/lib/libjxl.a: %/Makefile
$(MAKE) -C $(<D) jxl-static
$(CODEC_OUT): $(CODEC_DIR)/CMakeLists.txt
mkdir -p $(CODEC_BUILD_DIR)
cd $(CODEC_BUILD_DIR) && \
emcmake cmake ../ && \
$(MAKE) jxl-static
%/lib/libjxl_threads.a: %/Makefile
$(MAKE) -C $(<D) jxl_threads-static
$(CODEC_DIR)/CMakeLists.txt: $(CODEC_DIR)
# Enable SIMD on a SIMD build.
$(CODEC_MT_SIMD_BUILD_DIR)/Makefile: CXXFLAGS+=-msimd128
%/Makefile: $(CODEC_DIR)/CMakeLists.txt
emcmake cmake \
$(CMAKE_FLAGS) \
-DBUILD_SHARED_LIBS=0 \
-DJPEGXL_ENABLE_BENCHMARK=0 \
-DJPEGXL_ENABLE_EXAMPLES=0 \
-DBUILD_TESTING=0 \
-B $(@D) \
$(<D)
$(CODEC_DIR)/CMakeLists.txt:
mkdir -p $(@D)
git clone $(CODEC_URL) --recursive -j`nproc` --depth 1 --branch $(CODEC_VERSION) $(@D)
$(CODEC_DIR):
mkdir -p $@
git clone $(CODEC_URL) --recursive -j`nproc` --depth 1 --branch $(CODEC_VERSION) $@
clean:
$(RM) $(OUT_JS) $(OUT_WASM) $(OUT_WORKER)
$(RM) $(OUT_JS) $(OUT_WASM)
$(MAKE) -C $(CODEC_BUILD_DIR) clean
$(MAKE) -C $(CODEC_MT_BUILD_DIR) clean
$(MAKE) -C $(CODEC_MT_SIMD_BUILD_DIR) clean

1241
codecs/jxl/dec/jxl_dec.js generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -3,7 +3,6 @@
#include "lib/jxl/enc_file.h"
#include "lib/jxl/external_image.h"
#include "lib/jxl/base/thread_pool_internal.h"
using namespace emscripten;
@@ -26,11 +25,6 @@ val encode(std::string image, int width, int height, JXLOptions options) {
jxl::CodecInOut io;
jxl::PaddedBytes bytes;
jxl::ImageBundle* main = &io.Main();
jxl::ThreadPoolInternal *pool_ptr = nullptr;
#ifdef __EMSCRIPTEN_PTHREADS__
jxl::ThreadPoolInternal pool;
pool_ptr = &pool;
#endif
cparams.epf = options.epf;
cparams.speed_tier = static_cast<jxl::SpeedTier>(options.speed);
@@ -103,14 +97,14 @@ val encode(std::string image, int width, int height, JXLOptions options) {
jxl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(image.data()), image.size()), width,
height, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/true,
/*alpha_is_premultiplied=*/false, /*bits_per_alpha=*/8, /*bits_per_sample=*/8,
/*big_endian=*/false, /*flipped_y=*/false, pool_ptr, main);
/*big_endian=*/false, /*flipped_y=*/false, /*pool=*/nullptr, main);
if (!result) {
return val::null();
}
auto js_result = val::null();
if (EncodeFile(cparams, &io, &passes_enc_state, &bytes, /*aux=*/nullptr, pool_ptr)) {
if (EncodeFile(cparams, &io, &passes_enc_state, &bytes)) {
js_result = Uint8Array.new_(typed_memory_view(bytes.size(), bytes.data()));
}

Binary file not shown.

View File

@@ -1 +0,0 @@
export { default } from './jxl_enc';

View File

@@ -1,111 +0,0 @@
var jxl_enc_mt = (function() {
var _scriptDir = import.meta.url;
return (
function(jxl_enc_mt) {
jxl_enc_mt = jxl_enc_mt || {};
function e(){m.buffer!=n&&u(m.buffer);return aa}function v(){m.buffer!=n&&u(m.buffer);return ba}function x(){m.buffer!=n&&u(m.buffer);return ca}function ea(){m.buffer!=n&&u(m.buffer);return fa}function A(){m.buffer!=n&&u(m.buffer);return ha}function C(){m.buffer!=n&&u(m.buffer);return ia}function ja(){m.buffer!=n&&u(m.buffer);return ka}function la(){m.buffer!=n&&u(m.buffer);return ma}var D;D||(D=typeof jxl_enc_mt !== 'undefined' ? jxl_enc_mt : {});var na,oa;
D.ready=new Promise(function(a,b){na=a;oa=b});var E={},F;for(F in D)D.hasOwnProperty(F)&&(E[F]=D[F]);var pa="./this.program",G=D.ENVIRONMENT_IS_PTHREAD||!1;G&&(n=D.buffer);var H="";function qa(a){return D.locateFile?D.locateFile(a,H):H+a}var ra;H=self.location.href;_scriptDir&&(H=_scriptDir);0!==H.indexOf("blob:")?H=H.substr(0,H.lastIndexOf("/")+1):H="";ra=function(a){var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)};
var sa=D.print||console.log.bind(console),J=D.printErr||console.warn.bind(console);for(F in E)E.hasOwnProperty(F)&&(D[F]=E[F]);E=null;D.thisProgram&&(pa=D.thisProgram);var ta;D.wasmBinary&&(ta=D.wasmBinary);var noExitRuntime;D.noExitRuntime&&(noExitRuntime=D.noExitRuntime);"object"!==typeof WebAssembly&&K("no native wasm support detected");var m,ua,threadInfoStruct=0,selfThreadId=0,va=!1;function wa(a,b){a||K("Assertion failed: "+b)}
function xa(a,b,c){c=b+c;for(var d="";!(b>=c);){var f=a[b++];if(!f)break;if(f&128){var g=a[b++]&63;if(192==(f&224))d+=String.fromCharCode((f&31)<<6|g);else{var l=a[b++]&63;f=224==(f&240)?(f&15)<<12|g<<6|l:(f&7)<<18|g<<12|l<<6|a[b++]&63;65536>f?d+=String.fromCharCode(f):(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else d+=String.fromCharCode(f)}return d}function L(a,b){return a?xa(v(),a,b):""}
function ya(a,b,c,d){if(0<d){d=c+d-1;for(var f=0;f<a.length;++f){var g=a.charCodeAt(f);if(55296<=g&&57343>=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}if(127>=g){if(c>=d)break;b[c++]=g}else{if(2047>=g){if(c+1>=d)break;b[c++]=192|g>>6}else{if(65535>=g){if(c+2>=d)break;b[c++]=224|g>>12}else{if(c+3>=d)break;b[c++]=240|g>>18;b[c++]=128|g>>12&63}b[c++]=128|g>>6&63}b[c++]=128|g&63}}b[c]=0}}function za(a,b,c){ya(a,v(),b,c)}
function Aa(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++c)&1023);127>=d?++b:b=2047>=d?b+2:65535>=d?b+3:b+4}return b}function Ba(a,b){for(var c=0,d="";;){var f=x()[a+2*c>>1];if(0==f||c==b/2)return d;++c;d+=String.fromCharCode(f)}}function Ca(a,b,c){void 0===c&&(c=2147483647);if(2>c)return 0;c-=2;var d=b;c=c<2*a.length?c/2:a.length;for(var f=0;f<c;++f){var g=a.charCodeAt(f);x()[b>>1]=g;b+=2}x()[b>>1]=0;return b-d}
function Da(a){return 2*a.length}function Ea(a,b){for(var c=0,d="";!(c>=b/4);){var f=A()[a+4*c>>2];if(0==f)break;++c;65536<=f?(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023)):d+=String.fromCharCode(f)}return d}function Fa(a,b,c){void 0===c&&(c=2147483647);if(4>c)return 0;var d=b;c=d+c-4;for(var f=0;f<a.length;++f){var g=a.charCodeAt(f);if(55296<=g&&57343>=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}A()[b>>2]=g;b+=4;if(b+4>c)break}A()[b>>2]=0;return b-d}
function Ga(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&++c;b+=4}return b}function Ha(a,b){e().set(a,b)}var n,aa,ba,ca,fa,ha,ia,ka,ma;function u(a){n=a;D.HEAP8=aa=new Int8Array(a);D.HEAP16=ca=new Int16Array(a);D.HEAP32=ha=new Int32Array(a);D.HEAPU8=ba=new Uint8Array(a);D.HEAPU16=fa=new Uint16Array(a);D.HEAPU32=ia=new Uint32Array(a);D.HEAPF32=ka=new Float32Array(a);D.HEAPF64=ma=new Float64Array(a)}var Ia=D.INITIAL_MEMORY||16777216;
if(G)m=D.wasmMemory,n=D.buffer;else if(D.wasmMemory)m=D.wasmMemory;else if(m=new WebAssembly.Memory({initial:Ia/65536,maximum:32768,shared:!0}),!(m.buffer instanceof SharedArrayBuffer))throw J("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"),Error("bad memory");m&&(n=m.buffer);Ia=n.byteLength;u(n);var M,Ja=[],Ka=[],La=[],Ma=[];
function Na(){var a=D.preRun.shift();Ja.unshift(a)}var N=0,Oa=null,Pa=null;D.preloadedImages={};D.preloadedAudios={};function K(a){if(D.onAbort)D.onAbort(a);G&&console.error("Pthread aborting at "+Error().stack);J(a);va=!0;a=new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");oa(a);throw a;}function Qa(){var a=O;return String.prototype.startsWith?a.startsWith("data:application/octet-stream;base64,"):0===a.indexOf("data:application/octet-stream;base64,")}var O="jxl_enc_mt.wasm";
Qa()||(O=qa(O));function Ra(){try{if(ta)return new Uint8Array(ta);if(ra)return ra(O);throw"both async and sync fetching of the wasm failed";}catch(a){K(a)}}function Sa(){return ta||"function"!==typeof fetch?Promise.resolve().then(Ra):fetch(O,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+O+"'";return a.arrayBuffer()}).catch(function(){return Ra()})}var Ua={60005:function(a,b){setTimeout(function(){Ta(a,b)},0)},60083:function(){throw"Canceled!";}};
function Va(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(D);else{var c=b.Bb;"number"===typeof c?void 0===b.Va?M.get(c)():M.get(c)(b.Va):c(void 0===b.Va?null:b.Va)}}}function Wa(a,b,c){var d;-1!=a.indexOf("j")?d=c&&c.length?D["dynCall_"+a].apply(null,[b].concat(c)):D["dynCall_"+a].call(null,b):d=M.get(b).apply(null,c);return d}D.dynCall=Wa;var P=0,Xa=0,Ya=0;function Za(a,b,c){P=a|0;Ya=b|0;Xa=c|0}D.registerPthreadPtr=Za;
function $a(a,b){if(0>=a||a>e().length||a&1||0>b)return-28;if(0==b)return 0;2147483647<=b&&(b=Infinity);var c=Atomics.load(A(),Q.rb>>2),d=0;if(c==a&&Atomics.compareExchange(A(),Q.rb>>2,c,0)==c&&(--b,d=1,0>=b))return 1;a=Atomics.notify(A(),a>>2,b);if(0<=a)return a+d;throw"Atomics.notify returned an unexpected value "+a;}D._emscripten_futex_wake=$a;
function ab(a){if(G)throw"Internal Error! cleanupThread() can only ever be called from main application thread!";if(!a)throw"Internal Error! Null pthread_ptr in cleanupThread!";A()[a+12>>2]=0;(a=Q.Oa[a])&&Q.bb(a.worker)}
var Q={ic:1,pc:{ub:0,vb:0},Ma:[],Qa:[],Jb:function(){for(var a=navigator.hardwareConcurrency,b=0;b<a;++b)Q.lb()},Kb:function(){Q.Ka=R(232);for(var a=0;58>a;++a)C()[Q.Ka/4+a]=0;A()[Q.Ka+12>>2]=Q.Ka;a=Q.Ka+156;A()[a>>2]=a;var b=R(512);for(a=0;128>a;++a)C()[b/4+a]=0;Atomics.store(C(),Q.Ka+104>>2,b);Atomics.store(C(),Q.Ka+40>>2,Q.Ka);Atomics.store(C(),Q.Ka+44>>2,42);Q.pb();Za(Q.Ka,!1,1);bb(Q.Ka)},Lb:function(){Q.pb();na(D);Q.receiveObjectTransfer=Q.Qb;Q.setThreadStatus=Q.Rb;Q.threadCancel=Q.ac;Q.threadExit=
Q.bc},pb:function(){Q.rb=cb},Oa:{},kb:[],Rb:function(){},tb:function(){for(;0<Q.kb.length;)Q.kb.pop()();G&&threadInfoStruct&&db()},bc:function(a){var b=P|0;b&&(Atomics.store(C(),b+4>>2,a),Atomics.store(C(),b+0>>2,1),Atomics.store(C(),b+60>>2,1),Atomics.store(C(),b+64>>2,0),Q.tb(),$a(b+0,2147483647),Za(0,0,0),threadInfoStruct=0,G&&postMessage({cmd:"exit"}))},ac:function(){Q.tb();Atomics.store(C(),threadInfoStruct+4>>2,-1);Atomics.store(C(),threadInfoStruct+0>>2,1);$a(threadInfoStruct+0,2147483647);
threadInfoStruct=selfThreadId=0;Za(0,0,0);postMessage({cmd:"cancelDone"})},wc:function(){for(var a in Q.Oa){var b=Q.Oa[a];b&&b.worker&&Q.bb(b.worker)}Q.Oa={};for(a=0;a<Q.Ma.length;++a){var c=Q.Ma[a];c.terminate()}Q.Ma=[];for(a=0;a<Q.Qa.length;++a)c=Q.Qa[a],b=c.La,Q.ib(b),c.terminate();Q.Qa=[]},ib:function(a){if(a){if(a.threadInfoStruct){var b=A()[a.threadInfoStruct+104>>2];A()[a.threadInfoStruct+104>>2]=0;S(b);S(a.threadInfoStruct)}a.threadInfoStruct=0;a.gb&&a.Ra&&S(a.Ra);a.Ra=0;a.worker&&(a.worker.La=
null)}},bb:function(a){delete Q.Oa[a.La.wb];Q.Ma.push(a);Q.Qa.splice(Q.Qa.indexOf(a),1);Q.ib(a.La);a.La=void 0},Qb:function(){},qb:function(a,b){a.onmessage=function(c){var d=c.data,f=d.cmd;a.La&&(Q.hb=a.La.threadInfoStruct);if(d.targetThread&&d.targetThread!=(P|0)){var g=Q.Oa[d.vc];g?g.worker.postMessage(c.data,d.transferList):console.error('Internal error! Worker sent a message "'+f+'" to target pthread '+d.targetThread+", but that thread no longer exists!")}else if("processQueuedMainThreadWork"===
f)eb();else if("spawnThread"===f)fb(c.data);else if("cleanupThread"===f)ab(d.thread);else if("killThread"===f){c=d.thread;if(G)throw"Internal Error! killThread() can only ever be called from main application thread!";if(!c)throw"Internal Error! Null pthread_ptr in killThread!";A()[c+12>>2]=0;c=Q.Oa[c];c.worker.terminate();Q.ib(c);Q.Qa.splice(Q.Qa.indexOf(c.worker),1);c.worker.La=void 0}else if("cancelThread"===f){c=d.thread;if(G)throw"Internal Error! cancelThread() can only ever be called from main application thread!";
if(!c)throw"Internal Error! Null pthread_ptr in cancelThread!";Q.Oa[c].worker.postMessage({cmd:"cancel"})}else"loaded"===f?(a.loaded=!0,b&&b(a),a.Xa&&(a.Xa(),delete a.Xa)):"print"===f?sa("Thread "+d.threadId+": "+d.text):"printErr"===f?J("Thread "+d.threadId+": "+d.text):"alert"===f?alert("Thread "+d.threadId+": "+d.text):"exit"===f?a.La&&Atomics.load(C(),a.La.wb+68>>2)&&Q.bb(a):"cancelDone"===f?Q.bb(a):"objectTransfer"!==f&&("setimmediate"===c.data.target?a.postMessage(c.data):J("worker sent an unknown command "+
f));Q.hb=void 0};a.onerror=function(c){J("pthread sent an error! "+c.filename+":"+c.lineno+": "+c.message)};a.postMessage({cmd:"load",urlOrBlob:D.mainScriptUrlOrBlob||_scriptDir,wasmMemory:m,wasmModule:ua})},lb:function(){var a=qa("jxl_enc_mt.worker.js");Q.Ma.push(new Worker(a))},Cb:function(){0==Q.Ma.length&&(Q.lb(),Q.qb(Q.Ma[0]));return 0<Q.Ma.length?Q.Ma.pop():null},jc:function(a){for(a=performance.now()+a;performance.now()<a;);}};D.establishStackSpace=function(a){gb(a)};D.getNoExitRuntime=function(){return noExitRuntime};
var hb;hb=G?function(){return performance.now()-D.__performance_now_clock_drift}:function(){return performance.now()};function ib(a,b){Q.kb.push(function(){M.get(a)(b)})}function jb(a){this.Wa=a-16;this.Wb=function(b){A()[this.Wa+8>>2]=b};this.Tb=function(b){A()[this.Wa+0>>2]=b};this.Ub=function(){A()[this.Wa+4>>2]=0};this.Sb=function(){var b=0;e()[this.Wa+12>>0]=b};this.Vb=function(){var b=0;e()[this.Wa+13>>0]=b};this.Hb=function(b,c){this.Wb(b);this.Tb(c);this.Ub();this.Sb();this.Vb()}}
function kb(){return 0<kb.xb}var lb={};function mb(a){for(;a.length;){var b=a.pop();a.pop()(b)}}function nb(a){return this.fromWireType(C()[a>>2])}var T={},U={},ob={};function pb(a){if(void 0===a)return"_unknown";a=a.replace(/[^a-zA-Z0-9_]/g,"$");var b=a.charCodeAt(0);return 48<=b&&57>=b?"_"+a:a}function qb(a,b){a=pb(a);return(new Function("body","return function "+a+'() {\n "use strict"; return body.apply(this, arguments);\n};\n'))(b)}
function rb(a){var b=Error,c=qb(a,function(d){this.name=a;this.message=d;d=Error(d).stack;void 0!==d&&(this.stack=this.toString()+"\n"+d.replace(/^Error(:[^\n]*)?\n/,""))});c.prototype=Object.create(b.prototype);c.prototype.constructor=c;c.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message};return c}var sb=void 0;
function tb(a,b,c){function d(k){k=c(k);if(k.length!==a.length)throw new sb("Mismatched type converter count");for(var q=0;q<a.length;++q)V(a[q],k[q])}a.forEach(function(k){ob[k]=b});var f=Array(b.length),g=[],l=0;b.forEach(function(k,q){U.hasOwnProperty(k)?f[q]=U[k]:(g.push(k),T.hasOwnProperty(k)||(T[k]=[]),T[k].push(function(){f[q]=U[k];++l;l===g.length&&d(f)}))});0===g.length&&d(f)}
function ub(a){switch(a){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+a);}}var vb=void 0;function W(a){for(var b="";v()[a];)b+=vb[v()[a++]];return b}var wb=void 0;function X(a){throw new wb(a);}
function V(a,b,c){c=c||{};if(!("argPackAdvance"in b))throw new TypeError("registerType registeredInstance requires argPackAdvance");var d=b.name;a||X('type "'+d+'" must have a positive integer typeid pointer');if(U.hasOwnProperty(a)){if(c.Gb)return;X("Cannot register type '"+d+"' twice")}U[a]=b;delete ob[a];T.hasOwnProperty(a)&&(b=T[a],delete T[a],b.forEach(function(f){f()}))}var xb=[],Y=[{},{value:void 0},{value:null},{value:!0},{value:!1}];
function yb(a){4<a&&0===--Y[a].jb&&(Y[a]=void 0,xb.push(a))}function zb(a){switch(a){case void 0:return 1;case null:return 2;case !0:return 3;case !1:return 4;default:var b=xb.length?xb.pop():Y.length;Y[b]={jb:1,value:a};return b}}function Ab(a){if(null===a)return"null";var b=typeof a;return"object"===b||"array"===b||"function"===b?a.toString():""+a}
function Bb(a,b){switch(b){case 2:return function(c){return this.fromWireType(ja()[c>>2])};case 3:return function(c){return this.fromWireType(la()[c>>3])};default:throw new TypeError("Unknown float type: "+a);}}function Cb(a){var b=Function;if(!(b instanceof Function))throw new TypeError("new_ called with constructor type "+typeof b+" which is not a function");var c=qb(b.name||"unknownFunctionName",function(){});c.prototype=b.prototype;c=new c;a=b.apply(c,a);return a instanceof Object?a:c}
function Db(a,b){var c=D;if(void 0===c[a].Na){var d=c[a];c[a]=function(){c[a].Na.hasOwnProperty(arguments.length)||X("Function '"+b+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+c[a].Na+")!");return c[a].Na[arguments.length].apply(this,arguments)};c[a].Na=[];c[a].Na[d.yb]=d}}
function Eb(a,b,c){D.hasOwnProperty(a)?((void 0===c||void 0!==D[a].Na&&void 0!==D[a].Na[c])&&X("Cannot register public name '"+a+"' twice"),Db(a,a),D.hasOwnProperty(c)&&X("Cannot register multiple overloads of a function with the same number of arguments ("+c+")!"),D[a].Na[c]=b):(D[a]=b,void 0!==c&&(D[a].sc=c))}function Fb(a,b){for(var c=[],d=0;d<a;d++)c.push(A()[(b>>2)+d]);return c}
function Gb(a,b){wa(0<=a.indexOf("j"),"getDynCaller should only be called with i64 sigs");var c=[];return function(){c.length=arguments.length;for(var d=0;d<arguments.length;d++)c[d]=arguments[d];return Wa(a,b,c)}}function Hb(a,b){a=W(a);var c=-1!=a.indexOf("j")?Gb(a,b):M.get(b);"function"!==typeof c&&X("unknown function pointer with signature "+a+": "+b);return c}var Ib=void 0;function Jb(a){a=Kb(a);var b=W(a);S(a);return b}
function Lb(a,b){function c(g){f[g]||U[g]||(ob[g]?ob[g].forEach(c):(d.push(g),f[g]=!0))}var d=[],f={};b.forEach(c);throw new Ib(a+": "+d.map(Jb).join([", "]));}function Mb(a,b,c){switch(b){case 0:return c?function(d){return e()[d]}:function(d){return v()[d]};case 1:return c?function(d){return x()[d>>1]}:function(d){return ea()[d>>1]};case 2:return c?function(d){return A()[d>>2]}:function(d){return C()[d>>2]};default:throw new TypeError("Unknown integer type: "+a);}}var Nb={};
function Ob(){return"object"===typeof globalThis?globalThis:Function("return this")()}function Pb(a,b){var c=U[a];void 0===c&&X(b+" has unknown type "+Jb(a));return c}var Qb={};function Rb(a,b,c){if(0>=a||a>e().length||a&1)return-28;a=Atomics.wait(A(),a>>2,b,c);if("timed-out"===a)return-73;if("not-equal"===a)return-6;if("ok"===a)return 0;throw"Atomics.wait returned an unexpected value "+a;}
function Z(a,b){for(var c=arguments.length-2,d=Sb(),f=Tb(8*c),g=f>>3,l=0;l<c;l++)la()[g+l]=arguments[2+l];c=Ub(a,c,f,b);gb(d);return c}var Vb=[],Wb=[],Xb=[0,"undefined"!==typeof document?document:0,"undefined"!==typeof window?window:0];function Yb(a){a=2<a?L(a):a;return Xb[a]||("undefined"!==typeof document?document.querySelector(a):void 0)}
function Zb(a,b,c){var d=Yb(a);if(!d)return-4;d.ab&&(A()[d.ab>>2]=b,A()[d.ab+4>>2]=c);if(d.sb||!d.lc)d.sb&&(d=d.sb),a=!1,d.$a&&d.$a.Za&&(a=d.$a.Za.getParameter(2978),a=0===a[0]&&0===a[1]&&a[2]===d.width&&a[3]===d.height),d.width=b,d.height=c,a&&d.$a.Za.viewport(0,0,b,c);else{if(d.ab){d=A()[d.ab+8>>2];a=a?L(a):"";var f=Sb(),g=Tb(12),l=0;if(a){l=Aa(a)+1;var k=R(l);za(a,k,l);l=k}A()[g>>2]=l;A()[g+4>>2]=b;A()[g+8>>2]=c;$b(0,d,657457152,0,l,g);gb(f);return 1}return-4}return 0}
function ac(a,b,c){return G?Z(2,1,a,b,c):Zb(a,b,c)}function bc(a){var b=a.getExtension("ANGLE_instanced_arrays");b&&(a.vertexAttribDivisor=function(c,d){b.vertexAttribDivisorANGLE(c,d)},a.drawArraysInstanced=function(c,d,f,g){b.drawArraysInstancedANGLE(c,d,f,g)},a.drawElementsInstanced=function(c,d,f,g,l){b.drawElementsInstancedANGLE(c,d,f,g,l)})}
function cc(a){var b=a.getExtension("OES_vertex_array_object");b&&(a.createVertexArray=function(){return b.createVertexArrayOES()},a.deleteVertexArray=function(c){b.deleteVertexArrayOES(c)},a.bindVertexArray=function(c){b.bindVertexArrayOES(c)},a.isVertexArray=function(c){return b.isVertexArrayOES(c)})}function dc(a){var b=a.getExtension("WEBGL_draw_buffers");b&&(a.drawBuffers=function(c,d){b.drawBuffersWEBGL(c,d)})}
function ec(a){a||(a=fc);if(!a.Ib){a.Ib=!0;var b=a.Za;bc(b);cc(b);dc(b);b.mc=b.getExtension("EXT_disjoint_timer_query");b.rc=b.getExtension("WEBGL_multi_draw");var c="OES_texture_float OES_texture_half_float OES_standard_derivatives OES_vertex_array_object WEBGL_compressed_texture_s3tc WEBGL_depth_texture OES_element_index_uint EXT_texture_filter_anisotropic EXT_frag_depth WEBGL_draw_buffers ANGLE_instanced_arrays OES_texture_float_linear OES_texture_half_float_linear EXT_blend_minmax EXT_shader_texture_lod EXT_texture_norm16 WEBGL_compressed_texture_pvrtc EXT_color_buffer_half_float WEBGL_color_buffer_float EXT_sRGB WEBGL_compressed_texture_etc1 EXT_disjoint_timer_query WEBGL_compressed_texture_etc WEBGL_compressed_texture_astc EXT_color_buffer_float WEBGL_compressed_texture_s3tc_srgb EXT_disjoint_timer_query_webgl2 WEBKIT_WEBGL_compressed_texture_pvrtc".split(" ");
(b.getSupportedExtensions()||[]).forEach(function(d){-1!=c.indexOf(d)&&b.getExtension(d)})}}var fc,gc=["default","low-power","high-performance"],hc={};function ic(){if(!jc){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:pa||"./this.program"},b;for(b in hc)a[b]=hc[b];var c=[];for(b in a)c.push(b+"="+a[b]);jc=c}return jc}var jc,kc=[null,[],[]];
function lc(a){return G?Z(3,1,a):0}function mc(a,b,c,d,f){if(G)return Z(4,1,a,b,c,d,f)}function nc(a,b,c,d){if(G)return Z(5,1,a,b,c,d);for(var f=0,g=0;g<c;g++){for(var l=A()[b+8*g>>2],k=A()[b+(8*g+4)>>2],q=0;q<k;q++){var t=v()[l+q],r=kc[a];0===t||10===t?((1===a?sa:J)(xa(r,0)),r.length=0):r.push(t)}f+=k}A()[d>>2]=f;return 0}
function fb(a){if(G)throw"Internal Error! spawnThread() can only ever be called from main application thread!";var b=Q.Cb();if(void 0!==b.La)throw"Internal error!";if(!a.Sa)throw"Internal error, no pthread ptr!";Q.Qa.push(b);for(var c=R(512),d=0;128>d;++d)A()[c+4*d>>2]=0;var f=a.Ra+a.Ta;d=Q.Oa[a.Sa]={worker:b,Ra:a.Ra,Ta:a.Ta,gb:a.gb,wb:a.Sa,threadInfoStruct:a.Sa};var g=d.threadInfoStruct>>2;Atomics.store(C(),g,0);Atomics.store(C(),g+1,0);Atomics.store(C(),g+2,0);Atomics.store(C(),g+17,a.mb);Atomics.store(C(),
g+26,c);Atomics.store(C(),g+12,0);Atomics.store(C(),g+10,d.threadInfoStruct);Atomics.store(C(),g+11,42);Atomics.store(C(),g+27,a.Ta);Atomics.store(C(),g+21,a.Ta);Atomics.store(C(),g+20,f);Atomics.store(C(),g+29,f);Atomics.store(C(),g+30,a.mb);Atomics.store(C(),g+32,a.ub);Atomics.store(C(),g+33,a.vb);c=oc()+40;Atomics.store(C(),g+44,c);b.La=d;var l={cmd:"run",start_routine:a.$b,arg:a.Va,threadInfoStruct:a.Sa,selfThreadId:a.Sa,parentThreadId:a.Nb,stackBase:a.Ra,stackSize:a.Ta};b.Xa=function(){l.time=
performance.now();b.postMessage(l,a.hc)};b.loaded&&(b.Xa(),delete b.Xa)}function pc(){return P|0}D._pthread_self=pc;
function qc(a,b){if(!a)return J("pthread_join attempted on a null thread pointer!"),71;if(G&&selfThreadId==a)return J("PThread "+a+" is attempting to join to itself!"),16;if(!G&&Q.Ka==a)return J("Main thread "+a+" is attempting to join to itself!"),16;if(A()[a+12>>2]!==a)return J("pthread_join attempted on thread "+a+", which does not point to a valid thread, or does not exist anymore!"),71;if(Atomics.load(C(),a+68>>2))return J("Attempted to join thread "+a+", which was already detached!"),28;for(;;){var c=
Atomics.load(C(),a+0>>2);if(1==c)return c=Atomics.load(C(),a+4>>2),b&&(A()[b>>2]=c),Atomics.store(C(),a+68>>2,1),G?postMessage({cmd:"cleanupThread",thread:a}):ab(a),0;if(G&&threadInfoStruct&&!Atomics.load(C(),threadInfoStruct+60>>2)&&2==Atomics.load(C(),threadInfoStruct+0>>2))throw"Canceled!";G||eb();Rb(a+0,c,G?100:1)}}function rc(a){return 0===a%4&&(0!==a%100||0===a%400)}function sc(a,b){for(var c=0,d=0;d<=b;c+=a[d++]);return c}
var tc=[31,29,31,30,31,30,31,31,30,31,30,31],uc=[31,28,31,30,31,30,31,31,30,31,30,31];function vc(a,b){for(a=new Date(a.getTime());0<b;){var c=a.getMonth(),d=(rc(a.getFullYear())?tc:uc)[c];if(b>d-a.getDate())b-=d-a.getDate()+1,a.setDate(1),11>c?a.setMonth(c+1):(a.setMonth(0),a.setFullYear(a.getFullYear()+1));else{a.setDate(a.getDate()+b);break}}return a}
function wc(a,b,c,d){function f(h,p,y){for(h="number"===typeof h?h.toString():h||"";h.length<p;)h=y[0]+h;return h}function g(h,p){return f(h,p,"0")}function l(h,p){function y(I){return 0>I?-1:0<I?1:0}var B;0===(B=y(h.getFullYear()-p.getFullYear()))&&0===(B=y(h.getMonth()-p.getMonth()))&&(B=y(h.getDate()-p.getDate()));return B}function k(h){switch(h.getDay()){case 0:return new Date(h.getFullYear()-1,11,29);case 1:return h;case 2:return new Date(h.getFullYear(),0,3);case 3:return new Date(h.getFullYear(),
0,2);case 4:return new Date(h.getFullYear(),0,1);case 5:return new Date(h.getFullYear()-1,11,31);case 6:return new Date(h.getFullYear()-1,11,30)}}function q(h){h=vc(new Date(h.Ja+1900,0,1),h.fb);var p=new Date(h.getFullYear()+1,0,4),y=k(new Date(h.getFullYear(),0,4));p=k(p);return 0>=l(y,h)?0>=l(p,h)?h.getFullYear()+1:h.getFullYear():h.getFullYear()-1}var t=A()[d+40>>2];d={ec:A()[d>>2],dc:A()[d+4>>2],cb:A()[d+8>>2],Ya:A()[d+12>>2],Ua:A()[d+16>>2],Ja:A()[d+20>>2],eb:A()[d+24>>2],fb:A()[d+28>>2],xc:A()[d+
32>>2],cc:A()[d+36>>2],fc:t?L(t):""};c=L(c);t={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var r in t)c=c.replace(new RegExp(r,"g"),t[r]);var w="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),
z="January February March April May June July August September October November December".split(" ");t={"%a":function(h){return w[h.eb].substring(0,3)},"%A":function(h){return w[h.eb]},"%b":function(h){return z[h.Ua].substring(0,3)},"%B":function(h){return z[h.Ua]},"%C":function(h){return g((h.Ja+1900)/100|0,2)},"%d":function(h){return g(h.Ya,2)},"%e":function(h){return f(h.Ya,2," ")},"%g":function(h){return q(h).toString().substring(2)},"%G":function(h){return q(h)},"%H":function(h){return g(h.cb,
2)},"%I":function(h){h=h.cb;0==h?h=12:12<h&&(h-=12);return g(h,2)},"%j":function(h){return g(h.Ya+sc(rc(h.Ja+1900)?tc:uc,h.Ua-1),3)},"%m":function(h){return g(h.Ua+1,2)},"%M":function(h){return g(h.dc,2)},"%n":function(){return"\n"},"%p":function(h){return 0<=h.cb&&12>h.cb?"AM":"PM"},"%S":function(h){return g(h.ec,2)},"%t":function(){return"\t"},"%u":function(h){return h.eb||7},"%U":function(h){var p=new Date(h.Ja+1900,0,1),y=0===p.getDay()?p:vc(p,7-p.getDay());h=new Date(h.Ja+1900,h.Ua,h.Ya);return 0>
l(y,h)?g(Math.ceil((31-y.getDate()+(sc(rc(h.getFullYear())?tc:uc,h.getMonth()-1)-31)+h.getDate())/7),2):0===l(y,p)?"01":"00"},"%V":function(h){var p=new Date(h.Ja+1901,0,4),y=k(new Date(h.Ja+1900,0,4));p=k(p);var B=vc(new Date(h.Ja+1900,0,1),h.fb);return 0>l(B,y)?"53":0>=l(p,B)?"01":g(Math.ceil((y.getFullYear()<h.Ja+1900?h.fb+32-y.getDate():h.fb+1-y.getDate())/7),2)},"%w":function(h){return h.eb},"%W":function(h){var p=new Date(h.Ja,0,1),y=1===p.getDay()?p:vc(p,0===p.getDay()?1:7-p.getDay()+1);h=
new Date(h.Ja+1900,h.Ua,h.Ya);return 0>l(y,h)?g(Math.ceil((31-y.getDate()+(sc(rc(h.getFullYear())?tc:uc,h.getMonth()-1)-31)+h.getDate())/7),2):0===l(y,p)?"01":"00"},"%y":function(h){return(h.Ja+1900).toString().substring(2)},"%Y":function(h){return h.Ja+1900},"%z":function(h){h=h.cc;var p=0<=h;h=Math.abs(h)/60;return(p?"+":"-")+String("0000"+(h/60*100+h%60)).slice(-4)},"%Z":function(h){return h.fc},"%%":function(){return"%"}};for(r in t)0<=c.indexOf(r)&&(c=c.replace(new RegExp(r,"g"),t[r](d)));r=
xc(c);if(r.length>b)return 0;Ha(r,a);return r.length-1}
function yc(a){if(G)return Z(6,1,a);switch(a){case 30:return 16384;case 85:return 131072;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:case 79:return 200809;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;
case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1E3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:return"object"===
typeof navigator?navigator.hardwareConcurrency||1:1}A()[zc()>>2]=28;return-1}G||Q.Jb();sb=D.InternalError=rb("InternalError");for(var Ac=Array(256),Bc=0;256>Bc;++Bc)Ac[Bc]=String.fromCharCode(Bc);vb=Ac;wb=D.BindingError=rb("BindingError");D.count_emval_handles=function(){for(var a=0,b=5;b<Y.length;++b)void 0!==Y[b]&&++a;return a};D.get_first_emval=function(){for(var a=5;a<Y.length;++a)if(void 0!==Y[a])return Y[a];return null};Ib=D.UnboundTypeError=rb("UnboundTypeError");
var Cc=[null,function(a,b){if(G)return Z(1,1,a,b)},ac,lc,mc,nc,yc];function xc(a){var b=Array(Aa(a)+1);ya(a,b,0,b.length);return b}G||Ka.push({Bb:function(){Dc()}});
var Gc={i:function(a,b,c,d){K("Assertion failed: "+L(a)+", at: "+[b?L(b):"unknown filename",c,d?L(d):"unknown function"])},K:function(a){return R(a+16)+16},X:function(a,b){return ib(a,b)},I:function(a,b,c){(new jb(a)).Hb(b,c);"uncaught_exception"in kb?kb.xb++:kb.xb=1;throw a;},w:function(a){var b=lb[a];delete lb[a];var c=b.Ob,d=b.Pb,f=b.ob,g=f.map(function(l){return l.Fb}).concat(f.map(function(l){return l.Yb}));tb([a],g,function(l){var k={};f.forEach(function(q,t){var r=l[t],w=q.Db,z=q.Eb,h=l[t+
f.length],p=q.Xb,y=q.Zb;k[q.Ab]={read:function(B){return r.fromWireType(w(z,B))},write:function(B,I){var da=[];p(y,B,h.toWireType(da,I));mb(da)}}});return[{name:b.name,fromWireType:function(q){var t={},r;for(r in k)t[r]=k[r].read(q);d(q);return t},toWireType:function(q,t){for(var r in k)if(!(r in t))throw new TypeError('Missing field: "'+r+'"');var w=c();for(r in k)k[r].write(w,t[r]);null!==q&&q.push(d,w);return w},argPackAdvance:8,readValueFromPointer:nb,Pa:d}]})},S:function(a,b,c,d,f){var g=ub(c);
b=W(b);V(a,{name:b,fromWireType:function(l){return!!l},toWireType:function(l,k){return k?d:f},argPackAdvance:8,readValueFromPointer:function(l){if(1===c)var k=e();else if(2===c)k=x();else if(4===c)k=A();else throw new TypeError("Unknown boolean type size: "+b);return this.fromWireType(k[l>>g])},Pa:null})},R:function(a,b){b=W(b);V(a,{name:b,fromWireType:function(c){var d=Y[c].value;yb(c);return d},toWireType:function(c,d){return zb(d)},argPackAdvance:8,readValueFromPointer:nb,Pa:null})},t:function(a,
b,c){c=ub(c);b=W(b);V(a,{name:b,fromWireType:function(d){return d},toWireType:function(d,f){if("number"!==typeof f&&"boolean"!==typeof f)throw new TypeError('Cannot convert "'+Ab(f)+'" to '+this.name);return f},argPackAdvance:8,readValueFromPointer:Bb(b,c),Pa:null})},v:function(a,b,c,d,f,g){var l=Fb(b,c);a=W(a);f=Hb(d,f);Eb(a,function(){Lb("Cannot call "+a+" due to unbound types",l)},b-1);tb([],l,function(k){var q=a,t=a;k=[k[0],null].concat(k.slice(1));var r=f,w=k.length;2>w&&X("argTypes array size mismatch! Must at least get return value and 'this' types!");
for(var z=null!==k[1]&&!1,h=!1,p=1;p<k.length;++p)if(null!==k[p]&&void 0===k[p].Pa){h=!0;break}var y="void"!==k[0].name,B="",I="";for(p=0;p<w-2;++p)B+=(0!==p?", ":"")+"arg"+p,I+=(0!==p?", ":"")+"arg"+p+"Wired";t="return function "+pb(t)+"("+B+") {\nif (arguments.length !== "+(w-2)+") {\nthrowBindingError('function "+t+" called with ' + arguments.length + ' arguments, expected "+(w-2)+" args!');\n}\n";h&&(t+="var destructors = [];\n");var da=h?"destructors":"null";B="throwBindingError invoker fn runDestructors retType classParam".split(" ");
r=[X,r,g,mb,k[0],k[1]];z&&(t+="var thisWired = classParam.toWireType("+da+", this);\n");for(p=0;p<w-2;++p)t+="var arg"+p+"Wired = argType"+p+".toWireType("+da+", arg"+p+"); // "+k[p+2].name+"\n",B.push("argType"+p),r.push(k[p+2]);z&&(I="thisWired"+(0<I.length?", ":"")+I);t+=(y?"var rv = ":"")+"invoker(fn"+(0<I.length?", ":"")+I+");\n";if(h)t+="runDestructors(destructors);\n";else for(p=z?1:2;p<k.length;++p)w=1===p?"thisWired":"arg"+(p-2)+"Wired",null!==k[p].Pa&&(t+=w+"_dtor("+w+"); // "+k[p].name+
"\n",B.push(w+"_dtor"),r.push(k[p].Pa));y&&(t+="var ret = retType.fromWireType(rv);\nreturn ret;\n");B.push(t+"}\n");k=Cb(B).apply(null,r);p=b-1;if(!D.hasOwnProperty(q))throw new sb("Replacing nonexistant public symbol");void 0!==D[q].Na&&void 0!==p?D[q].Na[p]=k:(D[q]=k,D[q].yb=p);return[]})},j:function(a,b,c,d,f){function g(t){return t}b=W(b);-1===f&&(f=4294967295);var l=ub(c);if(0===d){var k=32-8*c;g=function(t){return t<<k>>>k}}var q=-1!=b.indexOf("unsigned");V(a,{name:b,fromWireType:g,toWireType:function(t,
r){if("number"!==typeof r&&"boolean"!==typeof r)throw new TypeError('Cannot convert "'+Ab(r)+'" to '+this.name);if(r<d||r>f)throw new TypeError('Passing a number "'+Ab(r)+'" from JS side to C/C++ side to an argument of type "'+b+'", which is outside the valid range ['+d+", "+f+"]!");return q?r>>>0:r|0},argPackAdvance:8,readValueFromPointer:Mb(b,l,0!==d),Pa:null})},h:function(a,b,c){function d(g){g>>=2;var l=C();return new f(n,l[g+1],l[g])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,
Uint32Array,Float32Array,Float64Array][b];c=W(c);V(a,{name:c,fromWireType:d,argPackAdvance:8,readValueFromPointer:d},{Gb:!0})},u:function(a,b){b=W(b);var c="std::string"===b;V(a,{name:b,fromWireType:function(d){var f=C()[d>>2];if(c)for(var g=d+4,l=0;l<=f;++l){var k=d+4+l;if(l==f||0==v()[k]){g=L(g,k-g);if(void 0===q)var q=g;else q+=String.fromCharCode(0),q+=g;g=k+1}}else{q=Array(f);for(l=0;l<f;++l)q[l]=String.fromCharCode(v()[d+4+l]);q=q.join("")}S(d);return q},toWireType:function(d,f){f instanceof
ArrayBuffer&&(f=new Uint8Array(f));var g="string"===typeof f;g||f instanceof Uint8Array||f instanceof Uint8ClampedArray||f instanceof Int8Array||X("Cannot pass non-string to std::string");var l=(c&&g?function(){return Aa(f)}:function(){return f.length})(),k=R(4+l+1);C()[k>>2]=l;if(c&&g)za(f,k+4,l+1);else if(g)for(g=0;g<l;++g){var q=f.charCodeAt(g);255<q&&(S(k),X("String has UTF-16 code units that do not fit in 8 bits"));v()[k+4+g]=q}else for(g=0;g<l;++g)v()[k+4+g]=f[g];null!==d&&d.push(S,k);return k},
argPackAdvance:8,readValueFromPointer:nb,Pa:function(d){S(d)}})},q:function(a,b,c){c=W(c);if(2===b){var d=Ba;var f=Ca;var g=Da;var l=function(){return ea()};var k=1}else 4===b&&(d=Ea,f=Fa,g=Ga,l=function(){return C()},k=2);V(a,{name:c,fromWireType:function(q){for(var t=C()[q>>2],r=l(),w,z=q+4,h=0;h<=t;++h){var p=q+4+h*b;if(h==t||0==r[p>>k])z=d(z,p-z),void 0===w?w=z:(w+=String.fromCharCode(0),w+=z),z=p+b}S(q);return w},toWireType:function(q,t){"string"!==typeof t&&X("Cannot pass non-string to C++ string type "+
c);var r=g(t),w=R(4+r+b);C()[w>>2]=r>>k;f(t,w+4,r+b);null!==q&&q.push(S,w);return w},argPackAdvance:8,readValueFromPointer:nb,Pa:function(q){S(q)}})},x:function(a,b,c,d,f,g){lb[a]={name:W(b),Ob:Hb(c,d),Pb:Hb(f,g),ob:[]}},l:function(a,b,c,d,f,g,l,k,q,t){lb[a].ob.push({Ab:W(b),Fb:c,Db:Hb(d,f),Eb:g,Yb:l,Xb:Hb(k,q),Zb:t})},T:function(a,b){b=W(b);V(a,{oc:!0,name:b,argPackAdvance:0,fromWireType:function(){},toWireType:function(){}})},H:function(a,b){if(a==b)postMessage({cmd:"processQueuedMainThreadWork"});
else if(G)postMessage({targetThread:a,cmd:"processThreadQueue"});else{a=(a=Q.Oa[a])&&a.worker;if(!a)return;a.postMessage({cmd:"processThreadQueue"})}return 1},m:yb,W:function(a){if(0===a)return zb(Ob());var b=Nb[a];a=void 0===b?W(a):b;return zb(Ob()[a])},V:function(a){4<a&&(Y[a].jb+=1)},y:function(a,b,c,d){a||X("Cannot use deleted val. handle = "+a);a=Y[a].value;var f=Qb[b];if(!f){f="";for(var g=0;g<b;++g)f+=(0!==g?", ":"")+"arg"+g;var l="return function emval_allocator_"+b+"(constructor, argTypes, args) {\n";
for(g=0;g<b;++g)l+="var argType"+g+" = requireRegisteredType(Module['HEAP32'][(argTypes >>> 2) + "+g+'], "parameter '+g+'");\nvar arg'+g+" = argType"+g+".readValueFromPointer(args);\nargs += argType"+g+"['argPackAdvance'];\n";f=(new Function("requireRegisteredType","Module","__emval_register",l+("var obj = new constructor("+f+");\nreturn __emval_register(obj);\n}\n")))(Pb,D,zb);Qb[b]=f}return f(a,c,d)},b:function(){K()},n:function(a,b,c){Wb.length=0;var d;for(c>>=2;d=v()[b++];)(d=105>d)&&c&1&&c++,
Wb.push(d?la()[c++>>1]:A()[c]),++c;return Ua[a].apply(null,Wb)},J:function(){},r:function(){},f:Rb,g:$a,d:hb,p:function(){return Ya|0},o:function(){return Xa|0},C:function(a,b,c){v().copyWithin(a,b,b+c)},E:function(a,b,c){Vb.length=b;c>>=3;for(var d=0;d<b;d++)Vb[d]=la()[c+d];return(0>a?Ua[-a-1]:Cc[a]).apply(null,Vb)},k:function(a){a>>>=0;var b=v().length;if(a<=b||2147483648<a)return!1;for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);d=Math.max(16777216,a,d);0<d%65536&&(d+=65536-d%
65536);a:{try{m.grow(Math.min(2147483648,d)-n.byteLength+65535>>>16);u(m.buffer);var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1},F:function(a,b,c){return Yb(a)?Zb(a,b,c):ac(a,b,c)},e:function(){},G:function(a,b){var c={};b>>=2;c.alpha=!!A()[b];c.depth=!!A()[b+1];c.stencil=!!A()[b+2];c.antialias=!!A()[b+3];c.premultipliedAlpha=!!A()[b+4];c.preserveDrawingBuffer=!!A()[b+5];var d=A()[b+6];c.powerPreference=gc[d];c.failIfMajorPerformanceCaveat=!!A()[b+7];c.Mb=A()[b+8];c.qc=A()[b+9];c.nb=A()[b+
10];c.zb=A()[b+11];c.tc=A()[b+12];c.uc=A()[b+13];a=Yb(a);!a||c.zb?c=0:(a=a.getContext("webgl",c))?(b=R(8),A()[b+4>>2]=P|0,d={nc:b,attributes:c,version:c.Mb,Za:a},a.canvas&&(a.canvas.$a=d),("undefined"===typeof c.nb||c.nb)&&ec(d),c=b):c=0;return c},O:function(a,b){var c=0;ic().forEach(function(d,f){var g=b+c;f=A()[a+4*f>>2]=g;for(g=0;g<d.length;++g)e()[f++>>0]=d.charCodeAt(g);e()[f>>0]=0;c+=d.length+1});return 0},P:function(a,b){var c=ic();A()[a>>2]=c.length;var d=0;c.forEach(function(f){d+=f.length+
1});A()[b>>2]=d;return 0},Q:lc,z:mc,s:nc,B:function(){Q.Kb()},a:m||D.wasmMemory,D:ib,U:function(a,b,c,d){if("undefined"===typeof SharedArrayBuffer)return J("Current environment does not support SharedArrayBuffer, pthreads are not available!"),6;if(!a)return J("pthread_create called with a null thread pointer!"),28;var f=[];if(G&&0===f.length)return Ec(687865856,a,b,c,d);var g=0,l=0,k=0,q=0;if(b){var t=A()[b>>2];t+=81920;g=A()[b+8>>2];l=0!==A()[b+12>>2];if(0===A()[b+16>>2]){var r=A()[b+20>>2],w=A()[b+
24>>2];k=b+20;q=b+24;var z=Q.hb?Q.hb:P|0;if(k||q)if(z)if(A()[z+12>>2]!==z)J("pthread_getschedparam attempted on thread "+z+", which does not point to a valid thread, or does not exist anymore!");else{var h=Atomics.load(C(),z+108+20>>2);z=Atomics.load(C(),z+108+24>>2);k&&(A()[k>>2]=h);q&&(A()[q>>2]=z)}else J("pthread_getschedparam called with a null thread pointer!");k=A()[b+20>>2];q=A()[b+24>>2];A()[b+20>>2]=r;A()[b+24>>2]=w}else k=A()[b+20>>2],q=A()[b+24>>2]}else t=2097152;(b=0==g)?g=Fc(16,t):(g-=
t,wa(0<g));r=R(232);for(w=0;58>w;++w)C()[(r>>2)+w]=0;A()[a>>2]=r;A()[r+12>>2]=r;a=r+156;A()[a>>2]=a;c={Ra:g,Ta:t,gb:b,ub:k,vb:q,mb:l,$b:c,Sa:r,Nb:P|0,Va:d,hc:f};G?(c.kc="spawnThread",postMessage(c,f)):fb(c);return 0},L:function(a,b){return qc(a,b)},c:pc,A:function(){},N:function(a,b,c,d){return wc(a,b,c,d)},M:yc};
(function(){function a(f,g){D.asm=f.exports;M=D.asm.Y;ua=g;if(!G){var l=Q.Ma.length;Q.Ma.forEach(function(k){Q.qb(k,function(){if(!--l&&(N--,D.monitorRunDependencies&&D.monitorRunDependencies(N),0==N&&(null!==Oa&&(clearInterval(Oa),Oa=null),Pa))){var q=Pa;Pa=null;q()}})})}}function b(f){a(f.instance,f.module)}function c(f){return Sa().then(function(g){return WebAssembly.instantiate(g,d)}).then(f,function(g){J("failed to asynchronously prepare wasm: "+g);K(g)})}var d={a:Gc};G||(wa(!G,"addRunDependency cannot be used in a pthread worker"),
N++,D.monitorRunDependencies&&D.monitorRunDependencies(N));if(D.instantiateWasm)try{return D.instantiateWasm(d,a)}catch(f){return J("Module.instantiateWasm callback failed with error: "+f),!1}(function(){return ta||"function"!==typeof WebAssembly.instantiateStreaming||Qa()||"function"!==typeof fetch?c(b):fetch(O,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,d).then(b,function(g){J("wasm streaming compile failed: "+g);J("falling back to ArrayBuffer instantiation");
return c(b)})})})().catch(oa);return{}})();var Dc=D.___wasm_call_ctors=function(){return(Dc=D.___wasm_call_ctors=D.asm.Z).apply(null,arguments)},R=D._malloc=function(){return(R=D._malloc=D.asm._).apply(null,arguments)},S=D._free=function(){return(S=D._free=D.asm.$).apply(null,arguments)},zc=D.___errno_location=function(){return(zc=D.___errno_location=D.asm.aa).apply(null,arguments)},Kb=D.___getTypeName=function(){return(Kb=D.___getTypeName=D.asm.ba).apply(null,arguments)};
D.___embind_register_native_and_builtin_types=function(){return(D.___embind_register_native_and_builtin_types=D.asm.ca).apply(null,arguments)};D.___em_js__initPthreadsJS=function(){return(D.___em_js__initPthreadsJS=D.asm.da).apply(null,arguments)};
var oc=D._emscripten_get_global_libc=function(){return(oc=D._emscripten_get_global_libc=D.asm.ea).apply(null,arguments)},Sb=D.stackSave=function(){return(Sb=D.stackSave=D.asm.fa).apply(null,arguments)},gb=D.stackRestore=function(){return(gb=D.stackRestore=D.asm.ga).apply(null,arguments)},Tb=D.stackAlloc=function(){return(Tb=D.stackAlloc=D.asm.ha).apply(null,arguments)},Fc=D._memalign=function(){return(Fc=D._memalign=D.asm.ia).apply(null,arguments)};
D._emscripten_main_browser_thread_id=function(){return(D._emscripten_main_browser_thread_id=D.asm.ja).apply(null,arguments)};var db=D.___pthread_tsd_run_dtors=function(){return(db=D.___pthread_tsd_run_dtors=D.asm.ka).apply(null,arguments)},eb=D._emscripten_main_thread_process_queued_calls=function(){return(eb=D._emscripten_main_thread_process_queued_calls=D.asm.la).apply(null,arguments)};
D._emscripten_current_thread_process_queued_calls=function(){return(D._emscripten_current_thread_process_queued_calls=D.asm.ma).apply(null,arguments)};var bb=D._emscripten_register_main_browser_thread_id=function(){return(bb=D._emscripten_register_main_browser_thread_id=D.asm.na).apply(null,arguments)},Ta=D._do_emscripten_dispatch_to_thread=function(){return(Ta=D._do_emscripten_dispatch_to_thread=D.asm.oa).apply(null,arguments)};
D._emscripten_async_run_in_main_thread=function(){return(D._emscripten_async_run_in_main_thread=D.asm.pa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread=function(){return(D._emscripten_sync_run_in_main_thread=D.asm.qa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_0=function(){return(D._emscripten_sync_run_in_main_thread_0=D.asm.ra).apply(null,arguments)};
D._emscripten_sync_run_in_main_thread_1=function(){return(D._emscripten_sync_run_in_main_thread_1=D.asm.sa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_2=function(){return(D._emscripten_sync_run_in_main_thread_2=D.asm.ta).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_xprintf_varargs=function(){return(D._emscripten_sync_run_in_main_thread_xprintf_varargs=D.asm.ua).apply(null,arguments)};
D._emscripten_sync_run_in_main_thread_3=function(){return(D._emscripten_sync_run_in_main_thread_3=D.asm.va).apply(null,arguments)};var Ec=D._emscripten_sync_run_in_main_thread_4=function(){return(Ec=D._emscripten_sync_run_in_main_thread_4=D.asm.wa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_5=function(){return(D._emscripten_sync_run_in_main_thread_5=D.asm.xa).apply(null,arguments)};
D._emscripten_sync_run_in_main_thread_6=function(){return(D._emscripten_sync_run_in_main_thread_6=D.asm.ya).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_7=function(){return(D._emscripten_sync_run_in_main_thread_7=D.asm.za).apply(null,arguments)};
var Ub=D._emscripten_run_in_main_runtime_thread_js=function(){return(Ub=D._emscripten_run_in_main_runtime_thread_js=D.asm.Aa).apply(null,arguments)},$b=D.__emscripten_call_on_thread=function(){return($b=D.__emscripten_call_on_thread=D.asm.Ba).apply(null,arguments)};D._emscripten_tls_init=function(){return(D._emscripten_tls_init=D.asm.Ca).apply(null,arguments)};D.dynCall_viijii=function(){return(D.dynCall_viijii=D.asm.Da).apply(null,arguments)};
D.dynCall_iiji=function(){return(D.dynCall_iiji=D.asm.Ea).apply(null,arguments)};D.dynCall_jiji=function(){return(D.dynCall_jiji=D.asm.Fa).apply(null,arguments)};D.dynCall_iiiiiijj=function(){return(D.dynCall_iiiiiijj=D.asm.Ga).apply(null,arguments)};D.dynCall_iiiiij=function(){return(D.dynCall_iiiiij=D.asm.Ha).apply(null,arguments)};D.dynCall_iiiiijj=function(){return(D.dynCall_iiiiijj=D.asm.Ia).apply(null,arguments)};var cb=D._main_thread_futex=3067912;D.PThread=Q;D.PThread=Q;D._pthread_self=pc;
D.wasmMemory=m;D.ExitStatus=Hc;var Ic;function Hc(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}Pa=function Jc(){Ic||Kc();Ic||(Pa=Jc)};
function Kc(){function a(){if(!Ic&&(Ic=!0,D.calledRun=!0,!va)){Va(Ka);G||Va(La);na(D);if(D.onRuntimeInitialized)D.onRuntimeInitialized();if(!G){if(D.postRun)for("function"==typeof D.postRun&&(D.postRun=[D.postRun]);D.postRun.length;){var b=D.postRun.shift();Ma.unshift(b)}Va(Ma)}}}if(!(0<N)){if(!G){if(D.preRun)for("function"==typeof D.preRun&&(D.preRun=[D.preRun]);D.preRun.length;)Na();Va(Ja)}0<N||(D.setStatus?(D.setStatus("Running..."),setTimeout(function(){setTimeout(function(){D.setStatus("")},
1);a()},1)):a())}}D.run=Kc;if(D.preInit)for("function"==typeof D.preInit&&(D.preInit=[D.preInit]);0<D.preInit.length;)D.preInit.pop()();G||(noExitRuntime=!0);G?Q.Lb():Kc();
return jxl_enc_mt.ready
}
);
})();
export default jxl_enc_mt;

Binary file not shown.

View File

@@ -1 +0,0 @@
var threadInfoStruct=0;var selfThreadId=0;var parentThreadId=0;var initializedJS=false;var Module={};function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:selfThreadId})}var err=threadPrintErr;this.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);Module["wasmModule"]=null;receiveInstance(instance);return instance.exports};this.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;import(e.data.urlOrBlob).then(function(jxl_enc_mt){return jxl_enc_mt.default(Module)}).then(function(instance){Module=instance;postMessage({"cmd":"loaded"})})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;threadInfoStruct=e.data.threadInfoStruct;Module["registerPthreadPtr"](threadInfoStruct,/*isMainBrowserThread=*/0,/*isMainRuntimeThread=*/0);selfThreadId=e.data.selfThreadId;parentThreadId=e.data.parentThreadId;var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["_emscripten_tls_init"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].setThreadStatus(Module["_pthread_self"](),1);if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["dynCall"]("ii",e.data.start_routine,[e.data.arg]);if(!Module["getNoExitRuntime"]())Module["PThread"].threadExit(result)}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){Atomics.store(Module["HEAPU32"],(threadInfoStruct+4)>>/*C_STRUCTS.pthread.threadExitCode*/2,(ex instanceof Module["ExitStatus"])?ex.status:-2);/*A custom entry specific to Emscripten denoting that the thread crashed.*/Atomics.store(Module["HEAPU32"],(threadInfoStruct+0)>>/*C_STRUCTS.pthread.threadStatus*/2,1);Module["_emscripten_futex_wake"](threadInfoStruct+0,/*C_STRUCTS.pthread.threadStatus*/2147483647);if(!(ex instanceof Module["ExitStatus"]))throw ex}}}else if(e.data.cmd==="cancel"){if(threadInfoStruct){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(threadInfoStruct){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};

View File

@@ -1 +0,0 @@
export { default } from './jxl_enc';

View File

@@ -1,112 +0,0 @@
var jxl_enc_mt_simd = (function() {
var _scriptDir = import.meta.url;
return (
function(jxl_enc_mt_simd) {
jxl_enc_mt_simd = jxl_enc_mt_simd || {};
function e(){m.buffer!=n&&u(m.buffer);return aa}function v(){m.buffer!=n&&u(m.buffer);return ba}function x(){m.buffer!=n&&u(m.buffer);return ca}function ea(){m.buffer!=n&&u(m.buffer);return fa}function A(){m.buffer!=n&&u(m.buffer);return ha}function C(){m.buffer!=n&&u(m.buffer);return ia}function ja(){m.buffer!=n&&u(m.buffer);return ka}function la(){m.buffer!=n&&u(m.buffer);return ma}var D;D||(D=typeof jxl_enc_mt_simd !== 'undefined' ? jxl_enc_mt_simd : {});var na,oa;
D.ready=new Promise(function(a,b){na=a;oa=b});var E={},F;for(F in D)D.hasOwnProperty(F)&&(E[F]=D[F]);var pa="./this.program",G=D.ENVIRONMENT_IS_PTHREAD||!1;G&&(n=D.buffer);var H="";function qa(a){return D.locateFile?D.locateFile(a,H):H+a}var ra;H=self.location.href;_scriptDir&&(H=_scriptDir);0!==H.indexOf("blob:")?H=H.substr(0,H.lastIndexOf("/")+1):H="";ra=function(a){var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)};
var sa=D.print||console.log.bind(console),J=D.printErr||console.warn.bind(console);for(F in E)E.hasOwnProperty(F)&&(D[F]=E[F]);E=null;D.thisProgram&&(pa=D.thisProgram);var ta;D.wasmBinary&&(ta=D.wasmBinary);var noExitRuntime;D.noExitRuntime&&(noExitRuntime=D.noExitRuntime);"object"!==typeof WebAssembly&&K("no native wasm support detected");var m,ua,threadInfoStruct=0,selfThreadId=0,va=!1;function wa(a,b){a||K("Assertion failed: "+b)}
function xa(a,b,c){c=b+c;for(var d="";!(b>=c);){var f=a[b++];if(!f)break;if(f&128){var g=a[b++]&63;if(192==(f&224))d+=String.fromCharCode((f&31)<<6|g);else{var l=a[b++]&63;f=224==(f&240)?(f&15)<<12|g<<6|l:(f&7)<<18|g<<12|l<<6|a[b++]&63;65536>f?d+=String.fromCharCode(f):(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else d+=String.fromCharCode(f)}return d}function L(a,b){return a?xa(v(),a,b):""}
function ya(a,b,c,d){if(0<d){d=c+d-1;for(var f=0;f<a.length;++f){var g=a.charCodeAt(f);if(55296<=g&&57343>=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}if(127>=g){if(c>=d)break;b[c++]=g}else{if(2047>=g){if(c+1>=d)break;b[c++]=192|g>>6}else{if(65535>=g){if(c+2>=d)break;b[c++]=224|g>>12}else{if(c+3>=d)break;b[c++]=240|g>>18;b[c++]=128|g>>12&63}b[c++]=128|g>>6&63}b[c++]=128|g&63}}b[c]=0}}function za(a,b,c){ya(a,v(),b,c)}
function Aa(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++c)&1023);127>=d?++b:b=2047>=d?b+2:65535>=d?b+3:b+4}return b}function Ba(a,b){for(var c=0,d="";;){var f=x()[a+2*c>>1];if(0==f||c==b/2)return d;++c;d+=String.fromCharCode(f)}}function Ca(a,b,c){void 0===c&&(c=2147483647);if(2>c)return 0;c-=2;var d=b;c=c<2*a.length?c/2:a.length;for(var f=0;f<c;++f){var g=a.charCodeAt(f);x()[b>>1]=g;b+=2}x()[b>>1]=0;return b-d}
function Da(a){return 2*a.length}function Ea(a,b){for(var c=0,d="";!(c>=b/4);){var f=A()[a+4*c>>2];if(0==f)break;++c;65536<=f?(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023)):d+=String.fromCharCode(f)}return d}function Fa(a,b,c){void 0===c&&(c=2147483647);if(4>c)return 0;var d=b;c=d+c-4;for(var f=0;f<a.length;++f){var g=a.charCodeAt(f);if(55296<=g&&57343>=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}A()[b>>2]=g;b+=4;if(b+4>c)break}A()[b>>2]=0;return b-d}
function Ga(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&++c;b+=4}return b}function Ha(a,b){e().set(a,b)}var n,aa,ba,ca,fa,ha,ia,ka,ma;function u(a){n=a;D.HEAP8=aa=new Int8Array(a);D.HEAP16=ca=new Int16Array(a);D.HEAP32=ha=new Int32Array(a);D.HEAPU8=ba=new Uint8Array(a);D.HEAPU16=fa=new Uint16Array(a);D.HEAPU32=ia=new Uint32Array(a);D.HEAPF32=ka=new Float32Array(a);D.HEAPF64=ma=new Float64Array(a)}var Ia=D.INITIAL_MEMORY||16777216;
if(G)m=D.wasmMemory,n=D.buffer;else if(D.wasmMemory)m=D.wasmMemory;else if(m=new WebAssembly.Memory({initial:Ia/65536,maximum:32768,shared:!0}),!(m.buffer instanceof SharedArrayBuffer))throw J("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"),Error("bad memory");m&&(n=m.buffer);Ia=n.byteLength;u(n);var M,Ja=[],Ka=[],La=[],Ma=[];
function Na(){var a=D.preRun.shift();Ja.unshift(a)}var N=0,Oa=null,Pa=null;D.preloadedImages={};D.preloadedAudios={};function K(a){if(D.onAbort)D.onAbort(a);G&&console.error("Pthread aborting at "+Error().stack);J(a);va=!0;a=new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");oa(a);throw a;}function Qa(){var a=O;return String.prototype.startsWith?a.startsWith("data:application/octet-stream;base64,"):0===a.indexOf("data:application/octet-stream;base64,")}var O="jxl_enc_mt_simd.wasm";
Qa()||(O=qa(O));function Ra(){try{if(ta)return new Uint8Array(ta);if(ra)return ra(O);throw"both async and sync fetching of the wasm failed";}catch(a){K(a)}}function Sa(){return ta||"function"!==typeof fetch?Promise.resolve().then(Ra):fetch(O,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+O+"'";return a.arrayBuffer()}).catch(function(){return Ra()})}var Ua={60053:function(a,b){setTimeout(function(){Ta(a,b)},0)},60131:function(){throw"Canceled!";}};
function Va(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(D);else{var c=b.Bb;"number"===typeof c?void 0===b.Va?M.get(c)():M.get(c)(b.Va):c(void 0===b.Va?null:b.Va)}}}function Wa(a,b,c){var d;-1!=a.indexOf("j")?d=c&&c.length?D["dynCall_"+a].apply(null,[b].concat(c)):D["dynCall_"+a].call(null,b):d=M.get(b).apply(null,c);return d}D.dynCall=Wa;var P=0,Xa=0,Ya=0;function Za(a,b,c){P=a|0;Ya=b|0;Xa=c|0}D.registerPthreadPtr=Za;
function $a(a,b){if(0>=a||a>e().length||a&1||0>b)return-28;if(0==b)return 0;2147483647<=b&&(b=Infinity);var c=Atomics.load(A(),Q.rb>>2),d=0;if(c==a&&Atomics.compareExchange(A(),Q.rb>>2,c,0)==c&&(--b,d=1,0>=b))return 1;a=Atomics.notify(A(),a>>2,b);if(0<=a)return a+d;throw"Atomics.notify returned an unexpected value "+a;}D._emscripten_futex_wake=$a;
function ab(a){if(G)throw"Internal Error! cleanupThread() can only ever be called from main application thread!";if(!a)throw"Internal Error! Null pthread_ptr in cleanupThread!";A()[a+12>>2]=0;(a=Q.Oa[a])&&Q.bb(a.worker)}
var Q={ic:1,pc:{ub:0,vb:0},Ma:[],Qa:[],Jb:function(){for(var a=navigator.hardwareConcurrency,b=0;b<a;++b)Q.lb()},Kb:function(){Q.Ka=R(232);for(var a=0;58>a;++a)C()[Q.Ka/4+a]=0;A()[Q.Ka+12>>2]=Q.Ka;a=Q.Ka+156;A()[a>>2]=a;var b=R(512);for(a=0;128>a;++a)C()[b/4+a]=0;Atomics.store(C(),Q.Ka+104>>2,b);Atomics.store(C(),Q.Ka+40>>2,Q.Ka);Atomics.store(C(),Q.Ka+44>>2,42);Q.pb();Za(Q.Ka,!1,1);bb(Q.Ka)},Lb:function(){Q.pb();na(D);Q.receiveObjectTransfer=Q.Qb;Q.setThreadStatus=Q.Rb;Q.threadCancel=Q.ac;Q.threadExit=
Q.bc},pb:function(){Q.rb=cb},Oa:{},kb:[],Rb:function(){},tb:function(){for(;0<Q.kb.length;)Q.kb.pop()();G&&threadInfoStruct&&db()},bc:function(a){var b=P|0;b&&(Atomics.store(C(),b+4>>2,a),Atomics.store(C(),b+0>>2,1),Atomics.store(C(),b+60>>2,1),Atomics.store(C(),b+64>>2,0),Q.tb(),$a(b+0,2147483647),Za(0,0,0),threadInfoStruct=0,G&&postMessage({cmd:"exit"}))},ac:function(){Q.tb();Atomics.store(C(),threadInfoStruct+4>>2,-1);Atomics.store(C(),threadInfoStruct+0>>2,1);$a(threadInfoStruct+0,2147483647);
threadInfoStruct=selfThreadId=0;Za(0,0,0);postMessage({cmd:"cancelDone"})},wc:function(){for(var a in Q.Oa){var b=Q.Oa[a];b&&b.worker&&Q.bb(b.worker)}Q.Oa={};for(a=0;a<Q.Ma.length;++a){var c=Q.Ma[a];c.terminate()}Q.Ma=[];for(a=0;a<Q.Qa.length;++a)c=Q.Qa[a],b=c.La,Q.ib(b),c.terminate();Q.Qa=[]},ib:function(a){if(a){if(a.threadInfoStruct){var b=A()[a.threadInfoStruct+104>>2];A()[a.threadInfoStruct+104>>2]=0;S(b);S(a.threadInfoStruct)}a.threadInfoStruct=0;a.gb&&a.Ra&&S(a.Ra);a.Ra=0;a.worker&&(a.worker.La=
null)}},bb:function(a){delete Q.Oa[a.La.wb];Q.Ma.push(a);Q.Qa.splice(Q.Qa.indexOf(a),1);Q.ib(a.La);a.La=void 0},Qb:function(){},qb:function(a,b){a.onmessage=function(c){var d=c.data,f=d.cmd;a.La&&(Q.hb=a.La.threadInfoStruct);if(d.targetThread&&d.targetThread!=(P|0)){var g=Q.Oa[d.vc];g?g.worker.postMessage(c.data,d.transferList):console.error('Internal error! Worker sent a message "'+f+'" to target pthread '+d.targetThread+", but that thread no longer exists!")}else if("processQueuedMainThreadWork"===
f)eb();else if("spawnThread"===f)fb(c.data);else if("cleanupThread"===f)ab(d.thread);else if("killThread"===f){c=d.thread;if(G)throw"Internal Error! killThread() can only ever be called from main application thread!";if(!c)throw"Internal Error! Null pthread_ptr in killThread!";A()[c+12>>2]=0;c=Q.Oa[c];c.worker.terminate();Q.ib(c);Q.Qa.splice(Q.Qa.indexOf(c.worker),1);c.worker.La=void 0}else if("cancelThread"===f){c=d.thread;if(G)throw"Internal Error! cancelThread() can only ever be called from main application thread!";
if(!c)throw"Internal Error! Null pthread_ptr in cancelThread!";Q.Oa[c].worker.postMessage({cmd:"cancel"})}else"loaded"===f?(a.loaded=!0,b&&b(a),a.Xa&&(a.Xa(),delete a.Xa)):"print"===f?sa("Thread "+d.threadId+": "+d.text):"printErr"===f?J("Thread "+d.threadId+": "+d.text):"alert"===f?alert("Thread "+d.threadId+": "+d.text):"exit"===f?a.La&&Atomics.load(C(),a.La.wb+68>>2)&&Q.bb(a):"cancelDone"===f?Q.bb(a):"objectTransfer"!==f&&("setimmediate"===c.data.target?a.postMessage(c.data):J("worker sent an unknown command "+
f));Q.hb=void 0};a.onerror=function(c){J("pthread sent an error! "+c.filename+":"+c.lineno+": "+c.message)};a.postMessage({cmd:"load",urlOrBlob:D.mainScriptUrlOrBlob||_scriptDir,wasmMemory:m,wasmModule:ua})},lb:function(){var a=qa("jxl_enc_mt_simd.worker.js");Q.Ma.push(new Worker(a))},Cb:function(){0==Q.Ma.length&&(Q.lb(),Q.qb(Q.Ma[0]));return 0<Q.Ma.length?Q.Ma.pop():null},jc:function(a){for(a=performance.now()+a;performance.now()<a;);}};D.establishStackSpace=function(a){gb(a)};
D.getNoExitRuntime=function(){return noExitRuntime};var hb;hb=G?function(){return performance.now()-D.__performance_now_clock_drift}:function(){return performance.now()};function ib(a,b){Q.kb.push(function(){M.get(a)(b)})}
function jb(a){this.Wa=a-16;this.Wb=function(b){A()[this.Wa+8>>2]=b};this.Tb=function(b){A()[this.Wa+0>>2]=b};this.Ub=function(){A()[this.Wa+4>>2]=0};this.Sb=function(){var b=0;e()[this.Wa+12>>0]=b};this.Vb=function(){var b=0;e()[this.Wa+13>>0]=b};this.Hb=function(b,c){this.Wb(b);this.Tb(c);this.Ub();this.Sb();this.Vb()}}function kb(){return 0<kb.xb}var lb={};function mb(a){for(;a.length;){var b=a.pop();a.pop()(b)}}function nb(a){return this.fromWireType(C()[a>>2])}var T={},U={},ob={};
function pb(a){if(void 0===a)return"_unknown";a=a.replace(/[^a-zA-Z0-9_]/g,"$");var b=a.charCodeAt(0);return 48<=b&&57>=b?"_"+a:a}function qb(a,b){a=pb(a);return(new Function("body","return function "+a+'() {\n "use strict"; return body.apply(this, arguments);\n};\n'))(b)}
function rb(a){var b=Error,c=qb(a,function(d){this.name=a;this.message=d;d=Error(d).stack;void 0!==d&&(this.stack=this.toString()+"\n"+d.replace(/^Error(:[^\n]*)?\n/,""))});c.prototype=Object.create(b.prototype);c.prototype.constructor=c;c.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message};return c}var sb=void 0;
function tb(a,b,c){function d(k){k=c(k);if(k.length!==a.length)throw new sb("Mismatched type converter count");for(var q=0;q<a.length;++q)V(a[q],k[q])}a.forEach(function(k){ob[k]=b});var f=Array(b.length),g=[],l=0;b.forEach(function(k,q){U.hasOwnProperty(k)?f[q]=U[k]:(g.push(k),T.hasOwnProperty(k)||(T[k]=[]),T[k].push(function(){f[q]=U[k];++l;l===g.length&&d(f)}))});0===g.length&&d(f)}
function ub(a){switch(a){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+a);}}var vb=void 0;function W(a){for(var b="";v()[a];)b+=vb[v()[a++]];return b}var wb=void 0;function X(a){throw new wb(a);}
function V(a,b,c){c=c||{};if(!("argPackAdvance"in b))throw new TypeError("registerType registeredInstance requires argPackAdvance");var d=b.name;a||X('type "'+d+'" must have a positive integer typeid pointer');if(U.hasOwnProperty(a)){if(c.Gb)return;X("Cannot register type '"+d+"' twice")}U[a]=b;delete ob[a];T.hasOwnProperty(a)&&(b=T[a],delete T[a],b.forEach(function(f){f()}))}var xb=[],Y=[{},{value:void 0},{value:null},{value:!0},{value:!1}];
function yb(a){4<a&&0===--Y[a].jb&&(Y[a]=void 0,xb.push(a))}function zb(a){switch(a){case void 0:return 1;case null:return 2;case !0:return 3;case !1:return 4;default:var b=xb.length?xb.pop():Y.length;Y[b]={jb:1,value:a};return b}}function Ab(a){if(null===a)return"null";var b=typeof a;return"object"===b||"array"===b||"function"===b?a.toString():""+a}
function Bb(a,b){switch(b){case 2:return function(c){return this.fromWireType(ja()[c>>2])};case 3:return function(c){return this.fromWireType(la()[c>>3])};default:throw new TypeError("Unknown float type: "+a);}}function Cb(a){var b=Function;if(!(b instanceof Function))throw new TypeError("new_ called with constructor type "+typeof b+" which is not a function");var c=qb(b.name||"unknownFunctionName",function(){});c.prototype=b.prototype;c=new c;a=b.apply(c,a);return a instanceof Object?a:c}
function Db(a,b){var c=D;if(void 0===c[a].Na){var d=c[a];c[a]=function(){c[a].Na.hasOwnProperty(arguments.length)||X("Function '"+b+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+c[a].Na+")!");return c[a].Na[arguments.length].apply(this,arguments)};c[a].Na=[];c[a].Na[d.yb]=d}}
function Eb(a,b,c){D.hasOwnProperty(a)?((void 0===c||void 0!==D[a].Na&&void 0!==D[a].Na[c])&&X("Cannot register public name '"+a+"' twice"),Db(a,a),D.hasOwnProperty(c)&&X("Cannot register multiple overloads of a function with the same number of arguments ("+c+")!"),D[a].Na[c]=b):(D[a]=b,void 0!==c&&(D[a].sc=c))}function Fb(a,b){for(var c=[],d=0;d<a;d++)c.push(A()[(b>>2)+d]);return c}
function Gb(a,b){wa(0<=a.indexOf("j"),"getDynCaller should only be called with i64 sigs");var c=[];return function(){c.length=arguments.length;for(var d=0;d<arguments.length;d++)c[d]=arguments[d];return Wa(a,b,c)}}function Hb(a,b){a=W(a);var c=-1!=a.indexOf("j")?Gb(a,b):M.get(b);"function"!==typeof c&&X("unknown function pointer with signature "+a+": "+b);return c}var Ib=void 0;function Jb(a){a=Kb(a);var b=W(a);S(a);return b}
function Lb(a,b){function c(g){f[g]||U[g]||(ob[g]?ob[g].forEach(c):(d.push(g),f[g]=!0))}var d=[],f={};b.forEach(c);throw new Ib(a+": "+d.map(Jb).join([", "]));}function Mb(a,b,c){switch(b){case 0:return c?function(d){return e()[d]}:function(d){return v()[d]};case 1:return c?function(d){return x()[d>>1]}:function(d){return ea()[d>>1]};case 2:return c?function(d){return A()[d>>2]}:function(d){return C()[d>>2]};default:throw new TypeError("Unknown integer type: "+a);}}var Nb={};
function Ob(){return"object"===typeof globalThis?globalThis:Function("return this")()}function Pb(a,b){var c=U[a];void 0===c&&X(b+" has unknown type "+Jb(a));return c}var Qb={};function Rb(a,b,c){if(0>=a||a>e().length||a&1)return-28;a=Atomics.wait(A(),a>>2,b,c);if("timed-out"===a)return-73;if("not-equal"===a)return-6;if("ok"===a)return 0;throw"Atomics.wait returned an unexpected value "+a;}
function Z(a,b){for(var c=arguments.length-2,d=Sb(),f=Tb(8*c),g=f>>3,l=0;l<c;l++)la()[g+l]=arguments[2+l];c=Ub(a,c,f,b);gb(d);return c}var Vb=[],Wb=[],Xb=[0,"undefined"!==typeof document?document:0,"undefined"!==typeof window?window:0];function Yb(a){a=2<a?L(a):a;return Xb[a]||("undefined"!==typeof document?document.querySelector(a):void 0)}
function Zb(a,b,c){var d=Yb(a);if(!d)return-4;d.ab&&(A()[d.ab>>2]=b,A()[d.ab+4>>2]=c);if(d.sb||!d.lc)d.sb&&(d=d.sb),a=!1,d.$a&&d.$a.Za&&(a=d.$a.Za.getParameter(2978),a=0===a[0]&&0===a[1]&&a[2]===d.width&&a[3]===d.height),d.width=b,d.height=c,a&&d.$a.Za.viewport(0,0,b,c);else{if(d.ab){d=A()[d.ab+8>>2];a=a?L(a):"";var f=Sb(),g=Tb(12),l=0;if(a){l=Aa(a)+1;var k=R(l);za(a,k,l);l=k}A()[g>>2]=l;A()[g+4>>2]=b;A()[g+8>>2]=c;$b(0,d,657457152,0,l,g);gb(f);return 1}return-4}return 0}
function ac(a,b,c){return G?Z(2,1,a,b,c):Zb(a,b,c)}function bc(a){var b=a.getExtension("ANGLE_instanced_arrays");b&&(a.vertexAttribDivisor=function(c,d){b.vertexAttribDivisorANGLE(c,d)},a.drawArraysInstanced=function(c,d,f,g){b.drawArraysInstancedANGLE(c,d,f,g)},a.drawElementsInstanced=function(c,d,f,g,l){b.drawElementsInstancedANGLE(c,d,f,g,l)})}
function cc(a){var b=a.getExtension("OES_vertex_array_object");b&&(a.createVertexArray=function(){return b.createVertexArrayOES()},a.deleteVertexArray=function(c){b.deleteVertexArrayOES(c)},a.bindVertexArray=function(c){b.bindVertexArrayOES(c)},a.isVertexArray=function(c){return b.isVertexArrayOES(c)})}function dc(a){var b=a.getExtension("WEBGL_draw_buffers");b&&(a.drawBuffers=function(c,d){b.drawBuffersWEBGL(c,d)})}
function ec(a){a||(a=fc);if(!a.Ib){a.Ib=!0;var b=a.Za;bc(b);cc(b);dc(b);b.mc=b.getExtension("EXT_disjoint_timer_query");b.rc=b.getExtension("WEBGL_multi_draw");var c="OES_texture_float OES_texture_half_float OES_standard_derivatives OES_vertex_array_object WEBGL_compressed_texture_s3tc WEBGL_depth_texture OES_element_index_uint EXT_texture_filter_anisotropic EXT_frag_depth WEBGL_draw_buffers ANGLE_instanced_arrays OES_texture_float_linear OES_texture_half_float_linear EXT_blend_minmax EXT_shader_texture_lod EXT_texture_norm16 WEBGL_compressed_texture_pvrtc EXT_color_buffer_half_float WEBGL_color_buffer_float EXT_sRGB WEBGL_compressed_texture_etc1 EXT_disjoint_timer_query WEBGL_compressed_texture_etc WEBGL_compressed_texture_astc EXT_color_buffer_float WEBGL_compressed_texture_s3tc_srgb EXT_disjoint_timer_query_webgl2 WEBKIT_WEBGL_compressed_texture_pvrtc".split(" ");
(b.getSupportedExtensions()||[]).forEach(function(d){-1!=c.indexOf(d)&&b.getExtension(d)})}}var fc,gc=["default","low-power","high-performance"],hc={};function ic(){if(!jc){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:pa||"./this.program"},b;for(b in hc)a[b]=hc[b];var c=[];for(b in a)c.push(b+"="+a[b]);jc=c}return jc}var jc,kc=[null,[],[]];
function lc(a){return G?Z(3,1,a):0}function mc(a,b,c,d,f){if(G)return Z(4,1,a,b,c,d,f)}function nc(a,b,c,d){if(G)return Z(5,1,a,b,c,d);for(var f=0,g=0;g<c;g++){for(var l=A()[b+8*g>>2],k=A()[b+(8*g+4)>>2],q=0;q<k;q++){var t=v()[l+q],r=kc[a];0===t||10===t?((1===a?sa:J)(xa(r,0)),r.length=0):r.push(t)}f+=k}A()[d>>2]=f;return 0}
function fb(a){if(G)throw"Internal Error! spawnThread() can only ever be called from main application thread!";var b=Q.Cb();if(void 0!==b.La)throw"Internal error!";if(!a.Sa)throw"Internal error, no pthread ptr!";Q.Qa.push(b);for(var c=R(512),d=0;128>d;++d)A()[c+4*d>>2]=0;var f=a.Ra+a.Ta;d=Q.Oa[a.Sa]={worker:b,Ra:a.Ra,Ta:a.Ta,gb:a.gb,wb:a.Sa,threadInfoStruct:a.Sa};var g=d.threadInfoStruct>>2;Atomics.store(C(),g,0);Atomics.store(C(),g+1,0);Atomics.store(C(),g+2,0);Atomics.store(C(),g+17,a.mb);Atomics.store(C(),
g+26,c);Atomics.store(C(),g+12,0);Atomics.store(C(),g+10,d.threadInfoStruct);Atomics.store(C(),g+11,42);Atomics.store(C(),g+27,a.Ta);Atomics.store(C(),g+21,a.Ta);Atomics.store(C(),g+20,f);Atomics.store(C(),g+29,f);Atomics.store(C(),g+30,a.mb);Atomics.store(C(),g+32,a.ub);Atomics.store(C(),g+33,a.vb);c=oc()+40;Atomics.store(C(),g+44,c);b.La=d;var l={cmd:"run",start_routine:a.$b,arg:a.Va,threadInfoStruct:a.Sa,selfThreadId:a.Sa,parentThreadId:a.Nb,stackBase:a.Ra,stackSize:a.Ta};b.Xa=function(){l.time=
performance.now();b.postMessage(l,a.hc)};b.loaded&&(b.Xa(),delete b.Xa)}function pc(){return P|0}D._pthread_self=pc;
function qc(a,b){if(!a)return J("pthread_join attempted on a null thread pointer!"),71;if(G&&selfThreadId==a)return J("PThread "+a+" is attempting to join to itself!"),16;if(!G&&Q.Ka==a)return J("Main thread "+a+" is attempting to join to itself!"),16;if(A()[a+12>>2]!==a)return J("pthread_join attempted on thread "+a+", which does not point to a valid thread, or does not exist anymore!"),71;if(Atomics.load(C(),a+68>>2))return J("Attempted to join thread "+a+", which was already detached!"),28;for(;;){var c=
Atomics.load(C(),a+0>>2);if(1==c)return c=Atomics.load(C(),a+4>>2),b&&(A()[b>>2]=c),Atomics.store(C(),a+68>>2,1),G?postMessage({cmd:"cleanupThread",thread:a}):ab(a),0;if(G&&threadInfoStruct&&!Atomics.load(C(),threadInfoStruct+60>>2)&&2==Atomics.load(C(),threadInfoStruct+0>>2))throw"Canceled!";G||eb();Rb(a+0,c,G?100:1)}}function rc(a){return 0===a%4&&(0!==a%100||0===a%400)}function sc(a,b){for(var c=0,d=0;d<=b;c+=a[d++]);return c}
var tc=[31,29,31,30,31,30,31,31,30,31,30,31],uc=[31,28,31,30,31,30,31,31,30,31,30,31];function vc(a,b){for(a=new Date(a.getTime());0<b;){var c=a.getMonth(),d=(rc(a.getFullYear())?tc:uc)[c];if(b>d-a.getDate())b-=d-a.getDate()+1,a.setDate(1),11>c?a.setMonth(c+1):(a.setMonth(0),a.setFullYear(a.getFullYear()+1));else{a.setDate(a.getDate()+b);break}}return a}
function wc(a,b,c,d){function f(h,p,y){for(h="number"===typeof h?h.toString():h||"";h.length<p;)h=y[0]+h;return h}function g(h,p){return f(h,p,"0")}function l(h,p){function y(I){return 0>I?-1:0<I?1:0}var B;0===(B=y(h.getFullYear()-p.getFullYear()))&&0===(B=y(h.getMonth()-p.getMonth()))&&(B=y(h.getDate()-p.getDate()));return B}function k(h){switch(h.getDay()){case 0:return new Date(h.getFullYear()-1,11,29);case 1:return h;case 2:return new Date(h.getFullYear(),0,3);case 3:return new Date(h.getFullYear(),
0,2);case 4:return new Date(h.getFullYear(),0,1);case 5:return new Date(h.getFullYear()-1,11,31);case 6:return new Date(h.getFullYear()-1,11,30)}}function q(h){h=vc(new Date(h.Ja+1900,0,1),h.fb);var p=new Date(h.getFullYear()+1,0,4),y=k(new Date(h.getFullYear(),0,4));p=k(p);return 0>=l(y,h)?0>=l(p,h)?h.getFullYear()+1:h.getFullYear():h.getFullYear()-1}var t=A()[d+40>>2];d={ec:A()[d>>2],dc:A()[d+4>>2],cb:A()[d+8>>2],Ya:A()[d+12>>2],Ua:A()[d+16>>2],Ja:A()[d+20>>2],eb:A()[d+24>>2],fb:A()[d+28>>2],xc:A()[d+
32>>2],cc:A()[d+36>>2],fc:t?L(t):""};c=L(c);t={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var r in t)c=c.replace(new RegExp(r,"g"),t[r]);var w="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),
z="January February March April May June July August September October November December".split(" ");t={"%a":function(h){return w[h.eb].substring(0,3)},"%A":function(h){return w[h.eb]},"%b":function(h){return z[h.Ua].substring(0,3)},"%B":function(h){return z[h.Ua]},"%C":function(h){return g((h.Ja+1900)/100|0,2)},"%d":function(h){return g(h.Ya,2)},"%e":function(h){return f(h.Ya,2," ")},"%g":function(h){return q(h).toString().substring(2)},"%G":function(h){return q(h)},"%H":function(h){return g(h.cb,
2)},"%I":function(h){h=h.cb;0==h?h=12:12<h&&(h-=12);return g(h,2)},"%j":function(h){return g(h.Ya+sc(rc(h.Ja+1900)?tc:uc,h.Ua-1),3)},"%m":function(h){return g(h.Ua+1,2)},"%M":function(h){return g(h.dc,2)},"%n":function(){return"\n"},"%p":function(h){return 0<=h.cb&&12>h.cb?"AM":"PM"},"%S":function(h){return g(h.ec,2)},"%t":function(){return"\t"},"%u":function(h){return h.eb||7},"%U":function(h){var p=new Date(h.Ja+1900,0,1),y=0===p.getDay()?p:vc(p,7-p.getDay());h=new Date(h.Ja+1900,h.Ua,h.Ya);return 0>
l(y,h)?g(Math.ceil((31-y.getDate()+(sc(rc(h.getFullYear())?tc:uc,h.getMonth()-1)-31)+h.getDate())/7),2):0===l(y,p)?"01":"00"},"%V":function(h){var p=new Date(h.Ja+1901,0,4),y=k(new Date(h.Ja+1900,0,4));p=k(p);var B=vc(new Date(h.Ja+1900,0,1),h.fb);return 0>l(B,y)?"53":0>=l(p,B)?"01":g(Math.ceil((y.getFullYear()<h.Ja+1900?h.fb+32-y.getDate():h.fb+1-y.getDate())/7),2)},"%w":function(h){return h.eb},"%W":function(h){var p=new Date(h.Ja,0,1),y=1===p.getDay()?p:vc(p,0===p.getDay()?1:7-p.getDay()+1);h=
new Date(h.Ja+1900,h.Ua,h.Ya);return 0>l(y,h)?g(Math.ceil((31-y.getDate()+(sc(rc(h.getFullYear())?tc:uc,h.getMonth()-1)-31)+h.getDate())/7),2):0===l(y,p)?"01":"00"},"%y":function(h){return(h.Ja+1900).toString().substring(2)},"%Y":function(h){return h.Ja+1900},"%z":function(h){h=h.cc;var p=0<=h;h=Math.abs(h)/60;return(p?"+":"-")+String("0000"+(h/60*100+h%60)).slice(-4)},"%Z":function(h){return h.fc},"%%":function(){return"%"}};for(r in t)0<=c.indexOf(r)&&(c=c.replace(new RegExp(r,"g"),t[r](d)));r=
xc(c);if(r.length>b)return 0;Ha(r,a);return r.length-1}
function yc(a){if(G)return Z(6,1,a);switch(a){case 30:return 16384;case 85:return 131072;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:case 79:return 200809;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;
case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1E3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:return"object"===
typeof navigator?navigator.hardwareConcurrency||1:1}A()[zc()>>2]=28;return-1}G||Q.Jb();sb=D.InternalError=rb("InternalError");for(var Ac=Array(256),Bc=0;256>Bc;++Bc)Ac[Bc]=String.fromCharCode(Bc);vb=Ac;wb=D.BindingError=rb("BindingError");D.count_emval_handles=function(){for(var a=0,b=5;b<Y.length;++b)void 0!==Y[b]&&++a;return a};D.get_first_emval=function(){for(var a=5;a<Y.length;++a)if(void 0!==Y[a])return Y[a];return null};Ib=D.UnboundTypeError=rb("UnboundTypeError");
var Cc=[null,function(a,b){if(G)return Z(1,1,a,b)},ac,lc,mc,nc,yc];function xc(a){var b=Array(Aa(a)+1);ya(a,b,0,b.length);return b}G||Ka.push({Bb:function(){Dc()}});
var Gc={i:function(a,b,c,d){K("Assertion failed: "+L(a)+", at: "+[b?L(b):"unknown filename",c,d?L(d):"unknown function"])},K:function(a){return R(a+16)+16},X:function(a,b){return ib(a,b)},I:function(a,b,c){(new jb(a)).Hb(b,c);"uncaught_exception"in kb?kb.xb++:kb.xb=1;throw a;},w:function(a){var b=lb[a];delete lb[a];var c=b.Ob,d=b.Pb,f=b.ob,g=f.map(function(l){return l.Fb}).concat(f.map(function(l){return l.Yb}));tb([a],g,function(l){var k={};f.forEach(function(q,t){var r=l[t],w=q.Db,z=q.Eb,h=l[t+
f.length],p=q.Xb,y=q.Zb;k[q.Ab]={read:function(B){return r.fromWireType(w(z,B))},write:function(B,I){var da=[];p(y,B,h.toWireType(da,I));mb(da)}}});return[{name:b.name,fromWireType:function(q){var t={},r;for(r in k)t[r]=k[r].read(q);d(q);return t},toWireType:function(q,t){for(var r in k)if(!(r in t))throw new TypeError('Missing field: "'+r+'"');var w=c();for(r in k)k[r].write(w,t[r]);null!==q&&q.push(d,w);return w},argPackAdvance:8,readValueFromPointer:nb,Pa:d}]})},S:function(a,b,c,d,f){var g=ub(c);
b=W(b);V(a,{name:b,fromWireType:function(l){return!!l},toWireType:function(l,k){return k?d:f},argPackAdvance:8,readValueFromPointer:function(l){if(1===c)var k=e();else if(2===c)k=x();else if(4===c)k=A();else throw new TypeError("Unknown boolean type size: "+b);return this.fromWireType(k[l>>g])},Pa:null})},R:function(a,b){b=W(b);V(a,{name:b,fromWireType:function(c){var d=Y[c].value;yb(c);return d},toWireType:function(c,d){return zb(d)},argPackAdvance:8,readValueFromPointer:nb,Pa:null})},t:function(a,
b,c){c=ub(c);b=W(b);V(a,{name:b,fromWireType:function(d){return d},toWireType:function(d,f){if("number"!==typeof f&&"boolean"!==typeof f)throw new TypeError('Cannot convert "'+Ab(f)+'" to '+this.name);return f},argPackAdvance:8,readValueFromPointer:Bb(b,c),Pa:null})},v:function(a,b,c,d,f,g){var l=Fb(b,c);a=W(a);f=Hb(d,f);Eb(a,function(){Lb("Cannot call "+a+" due to unbound types",l)},b-1);tb([],l,function(k){var q=a,t=a;k=[k[0],null].concat(k.slice(1));var r=f,w=k.length;2>w&&X("argTypes array size mismatch! Must at least get return value and 'this' types!");
for(var z=null!==k[1]&&!1,h=!1,p=1;p<k.length;++p)if(null!==k[p]&&void 0===k[p].Pa){h=!0;break}var y="void"!==k[0].name,B="",I="";for(p=0;p<w-2;++p)B+=(0!==p?", ":"")+"arg"+p,I+=(0!==p?", ":"")+"arg"+p+"Wired";t="return function "+pb(t)+"("+B+") {\nif (arguments.length !== "+(w-2)+") {\nthrowBindingError('function "+t+" called with ' + arguments.length + ' arguments, expected "+(w-2)+" args!');\n}\n";h&&(t+="var destructors = [];\n");var da=h?"destructors":"null";B="throwBindingError invoker fn runDestructors retType classParam".split(" ");
r=[X,r,g,mb,k[0],k[1]];z&&(t+="var thisWired = classParam.toWireType("+da+", this);\n");for(p=0;p<w-2;++p)t+="var arg"+p+"Wired = argType"+p+".toWireType("+da+", arg"+p+"); // "+k[p+2].name+"\n",B.push("argType"+p),r.push(k[p+2]);z&&(I="thisWired"+(0<I.length?", ":"")+I);t+=(y?"var rv = ":"")+"invoker(fn"+(0<I.length?", ":"")+I+");\n";if(h)t+="runDestructors(destructors);\n";else for(p=z?1:2;p<k.length;++p)w=1===p?"thisWired":"arg"+(p-2)+"Wired",null!==k[p].Pa&&(t+=w+"_dtor("+w+"); // "+k[p].name+
"\n",B.push(w+"_dtor"),r.push(k[p].Pa));y&&(t+="var ret = retType.fromWireType(rv);\nreturn ret;\n");B.push(t+"}\n");k=Cb(B).apply(null,r);p=b-1;if(!D.hasOwnProperty(q))throw new sb("Replacing nonexistant public symbol");void 0!==D[q].Na&&void 0!==p?D[q].Na[p]=k:(D[q]=k,D[q].yb=p);return[]})},j:function(a,b,c,d,f){function g(t){return t}b=W(b);-1===f&&(f=4294967295);var l=ub(c);if(0===d){var k=32-8*c;g=function(t){return t<<k>>>k}}var q=-1!=b.indexOf("unsigned");V(a,{name:b,fromWireType:g,toWireType:function(t,
r){if("number"!==typeof r&&"boolean"!==typeof r)throw new TypeError('Cannot convert "'+Ab(r)+'" to '+this.name);if(r<d||r>f)throw new TypeError('Passing a number "'+Ab(r)+'" from JS side to C/C++ side to an argument of type "'+b+'", which is outside the valid range ['+d+", "+f+"]!");return q?r>>>0:r|0},argPackAdvance:8,readValueFromPointer:Mb(b,l,0!==d),Pa:null})},h:function(a,b,c){function d(g){g>>=2;var l=C();return new f(n,l[g+1],l[g])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,
Uint32Array,Float32Array,Float64Array][b];c=W(c);V(a,{name:c,fromWireType:d,argPackAdvance:8,readValueFromPointer:d},{Gb:!0})},u:function(a,b){b=W(b);var c="std::string"===b;V(a,{name:b,fromWireType:function(d){var f=C()[d>>2];if(c)for(var g=d+4,l=0;l<=f;++l){var k=d+4+l;if(l==f||0==v()[k]){g=L(g,k-g);if(void 0===q)var q=g;else q+=String.fromCharCode(0),q+=g;g=k+1}}else{q=Array(f);for(l=0;l<f;++l)q[l]=String.fromCharCode(v()[d+4+l]);q=q.join("")}S(d);return q},toWireType:function(d,f){f instanceof
ArrayBuffer&&(f=new Uint8Array(f));var g="string"===typeof f;g||f instanceof Uint8Array||f instanceof Uint8ClampedArray||f instanceof Int8Array||X("Cannot pass non-string to std::string");var l=(c&&g?function(){return Aa(f)}:function(){return f.length})(),k=R(4+l+1);C()[k>>2]=l;if(c&&g)za(f,k+4,l+1);else if(g)for(g=0;g<l;++g){var q=f.charCodeAt(g);255<q&&(S(k),X("String has UTF-16 code units that do not fit in 8 bits"));v()[k+4+g]=q}else for(g=0;g<l;++g)v()[k+4+g]=f[g];null!==d&&d.push(S,k);return k},
argPackAdvance:8,readValueFromPointer:nb,Pa:function(d){S(d)}})},q:function(a,b,c){c=W(c);if(2===b){var d=Ba;var f=Ca;var g=Da;var l=function(){return ea()};var k=1}else 4===b&&(d=Ea,f=Fa,g=Ga,l=function(){return C()},k=2);V(a,{name:c,fromWireType:function(q){for(var t=C()[q>>2],r=l(),w,z=q+4,h=0;h<=t;++h){var p=q+4+h*b;if(h==t||0==r[p>>k])z=d(z,p-z),void 0===w?w=z:(w+=String.fromCharCode(0),w+=z),z=p+b}S(q);return w},toWireType:function(q,t){"string"!==typeof t&&X("Cannot pass non-string to C++ string type "+
c);var r=g(t),w=R(4+r+b);C()[w>>2]=r>>k;f(t,w+4,r+b);null!==q&&q.push(S,w);return w},argPackAdvance:8,readValueFromPointer:nb,Pa:function(q){S(q)}})},x:function(a,b,c,d,f,g){lb[a]={name:W(b),Ob:Hb(c,d),Pb:Hb(f,g),ob:[]}},l:function(a,b,c,d,f,g,l,k,q,t){lb[a].ob.push({Ab:W(b),Fb:c,Db:Hb(d,f),Eb:g,Yb:l,Xb:Hb(k,q),Zb:t})},T:function(a,b){b=W(b);V(a,{oc:!0,name:b,argPackAdvance:0,fromWireType:function(){},toWireType:function(){}})},H:function(a,b){if(a==b)postMessage({cmd:"processQueuedMainThreadWork"});
else if(G)postMessage({targetThread:a,cmd:"processThreadQueue"});else{a=(a=Q.Oa[a])&&a.worker;if(!a)return;a.postMessage({cmd:"processThreadQueue"})}return 1},m:yb,W:function(a){if(0===a)return zb(Ob());var b=Nb[a];a=void 0===b?W(a):b;return zb(Ob()[a])},V:function(a){4<a&&(Y[a].jb+=1)},y:function(a,b,c,d){a||X("Cannot use deleted val. handle = "+a);a=Y[a].value;var f=Qb[b];if(!f){f="";for(var g=0;g<b;++g)f+=(0!==g?", ":"")+"arg"+g;var l="return function emval_allocator_"+b+"(constructor, argTypes, args) {\n";
for(g=0;g<b;++g)l+="var argType"+g+" = requireRegisteredType(Module['HEAP32'][(argTypes >>> 2) + "+g+'], "parameter '+g+'");\nvar arg'+g+" = argType"+g+".readValueFromPointer(args);\nargs += argType"+g+"['argPackAdvance'];\n";f=(new Function("requireRegisteredType","Module","__emval_register",l+("var obj = new constructor("+f+");\nreturn __emval_register(obj);\n}\n")))(Pb,D,zb);Qb[b]=f}return f(a,c,d)},b:function(){K()},n:function(a,b,c){Wb.length=0;var d;for(c>>=2;d=v()[b++];)(d=105>d)&&c&1&&c++,
Wb.push(d?la()[c++>>1]:A()[c]),++c;return Ua[a].apply(null,Wb)},J:function(){},r:function(){},f:Rb,g:$a,d:hb,p:function(){return Ya|0},o:function(){return Xa|0},C:function(a,b,c){v().copyWithin(a,b,b+c)},E:function(a,b,c){Vb.length=b;c>>=3;for(var d=0;d<b;d++)Vb[d]=la()[c+d];return(0>a?Ua[-a-1]:Cc[a]).apply(null,Vb)},k:function(a){a>>>=0;var b=v().length;if(a<=b||2147483648<a)return!1;for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);d=Math.max(16777216,a,d);0<d%65536&&(d+=65536-d%
65536);a:{try{m.grow(Math.min(2147483648,d)-n.byteLength+65535>>>16);u(m.buffer);var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1},F:function(a,b,c){return Yb(a)?Zb(a,b,c):ac(a,b,c)},e:function(){},G:function(a,b){var c={};b>>=2;c.alpha=!!A()[b];c.depth=!!A()[b+1];c.stencil=!!A()[b+2];c.antialias=!!A()[b+3];c.premultipliedAlpha=!!A()[b+4];c.preserveDrawingBuffer=!!A()[b+5];var d=A()[b+6];c.powerPreference=gc[d];c.failIfMajorPerformanceCaveat=!!A()[b+7];c.Mb=A()[b+8];c.qc=A()[b+9];c.nb=A()[b+
10];c.zb=A()[b+11];c.tc=A()[b+12];c.uc=A()[b+13];a=Yb(a);!a||c.zb?c=0:(a=a.getContext("webgl",c))?(b=R(8),A()[b+4>>2]=P|0,d={nc:b,attributes:c,version:c.Mb,Za:a},a.canvas&&(a.canvas.$a=d),("undefined"===typeof c.nb||c.nb)&&ec(d),c=b):c=0;return c},O:function(a,b){var c=0;ic().forEach(function(d,f){var g=b+c;f=A()[a+4*f>>2]=g;for(g=0;g<d.length;++g)e()[f++>>0]=d.charCodeAt(g);e()[f>>0]=0;c+=d.length+1});return 0},P:function(a,b){var c=ic();A()[a>>2]=c.length;var d=0;c.forEach(function(f){d+=f.length+
1});A()[b>>2]=d;return 0},Q:lc,z:mc,s:nc,B:function(){Q.Kb()},a:m||D.wasmMemory,D:ib,U:function(a,b,c,d){if("undefined"===typeof SharedArrayBuffer)return J("Current environment does not support SharedArrayBuffer, pthreads are not available!"),6;if(!a)return J("pthread_create called with a null thread pointer!"),28;var f=[];if(G&&0===f.length)return Ec(687865856,a,b,c,d);var g=0,l=0,k=0,q=0;if(b){var t=A()[b>>2];t+=81920;g=A()[b+8>>2];l=0!==A()[b+12>>2];if(0===A()[b+16>>2]){var r=A()[b+20>>2],w=A()[b+
24>>2];k=b+20;q=b+24;var z=Q.hb?Q.hb:P|0;if(k||q)if(z)if(A()[z+12>>2]!==z)J("pthread_getschedparam attempted on thread "+z+", which does not point to a valid thread, or does not exist anymore!");else{var h=Atomics.load(C(),z+108+20>>2);z=Atomics.load(C(),z+108+24>>2);k&&(A()[k>>2]=h);q&&(A()[q>>2]=z)}else J("pthread_getschedparam called with a null thread pointer!");k=A()[b+20>>2];q=A()[b+24>>2];A()[b+20>>2]=r;A()[b+24>>2]=w}else k=A()[b+20>>2],q=A()[b+24>>2]}else t=2097152;(b=0==g)?g=Fc(16,t):(g-=
t,wa(0<g));r=R(232);for(w=0;58>w;++w)C()[(r>>2)+w]=0;A()[a>>2]=r;A()[r+12>>2]=r;a=r+156;A()[a>>2]=a;c={Ra:g,Ta:t,gb:b,ub:k,vb:q,mb:l,$b:c,Sa:r,Nb:P|0,Va:d,hc:f};G?(c.kc="spawnThread",postMessage(c,f)):fb(c);return 0},L:function(a,b){return qc(a,b)},c:pc,A:function(){},N:function(a,b,c,d){return wc(a,b,c,d)},M:yc};
(function(){function a(f,g){D.asm=f.exports;M=D.asm.Y;ua=g;if(!G){var l=Q.Ma.length;Q.Ma.forEach(function(k){Q.qb(k,function(){if(!--l&&(N--,D.monitorRunDependencies&&D.monitorRunDependencies(N),0==N&&(null!==Oa&&(clearInterval(Oa),Oa=null),Pa))){var q=Pa;Pa=null;q()}})})}}function b(f){a(f.instance,f.module)}function c(f){return Sa().then(function(g){return WebAssembly.instantiate(g,d)}).then(f,function(g){J("failed to asynchronously prepare wasm: "+g);K(g)})}var d={a:Gc};G||(wa(!G,"addRunDependency cannot be used in a pthread worker"),
N++,D.monitorRunDependencies&&D.monitorRunDependencies(N));if(D.instantiateWasm)try{return D.instantiateWasm(d,a)}catch(f){return J("Module.instantiateWasm callback failed with error: "+f),!1}(function(){return ta||"function"!==typeof WebAssembly.instantiateStreaming||Qa()||"function"!==typeof fetch?c(b):fetch(O,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,d).then(b,function(g){J("wasm streaming compile failed: "+g);J("falling back to ArrayBuffer instantiation");
return c(b)})})})().catch(oa);return{}})();var Dc=D.___wasm_call_ctors=function(){return(Dc=D.___wasm_call_ctors=D.asm.Z).apply(null,arguments)},R=D._malloc=function(){return(R=D._malloc=D.asm._).apply(null,arguments)},S=D._free=function(){return(S=D._free=D.asm.$).apply(null,arguments)},zc=D.___errno_location=function(){return(zc=D.___errno_location=D.asm.aa).apply(null,arguments)},Kb=D.___getTypeName=function(){return(Kb=D.___getTypeName=D.asm.ba).apply(null,arguments)};
D.___embind_register_native_and_builtin_types=function(){return(D.___embind_register_native_and_builtin_types=D.asm.ca).apply(null,arguments)};D.___em_js__initPthreadsJS=function(){return(D.___em_js__initPthreadsJS=D.asm.da).apply(null,arguments)};
var oc=D._emscripten_get_global_libc=function(){return(oc=D._emscripten_get_global_libc=D.asm.ea).apply(null,arguments)},Sb=D.stackSave=function(){return(Sb=D.stackSave=D.asm.fa).apply(null,arguments)},gb=D.stackRestore=function(){return(gb=D.stackRestore=D.asm.ga).apply(null,arguments)},Tb=D.stackAlloc=function(){return(Tb=D.stackAlloc=D.asm.ha).apply(null,arguments)},Fc=D._memalign=function(){return(Fc=D._memalign=D.asm.ia).apply(null,arguments)};
D._emscripten_main_browser_thread_id=function(){return(D._emscripten_main_browser_thread_id=D.asm.ja).apply(null,arguments)};var db=D.___pthread_tsd_run_dtors=function(){return(db=D.___pthread_tsd_run_dtors=D.asm.ka).apply(null,arguments)},eb=D._emscripten_main_thread_process_queued_calls=function(){return(eb=D._emscripten_main_thread_process_queued_calls=D.asm.la).apply(null,arguments)};
D._emscripten_current_thread_process_queued_calls=function(){return(D._emscripten_current_thread_process_queued_calls=D.asm.ma).apply(null,arguments)};var bb=D._emscripten_register_main_browser_thread_id=function(){return(bb=D._emscripten_register_main_browser_thread_id=D.asm.na).apply(null,arguments)},Ta=D._do_emscripten_dispatch_to_thread=function(){return(Ta=D._do_emscripten_dispatch_to_thread=D.asm.oa).apply(null,arguments)};
D._emscripten_async_run_in_main_thread=function(){return(D._emscripten_async_run_in_main_thread=D.asm.pa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread=function(){return(D._emscripten_sync_run_in_main_thread=D.asm.qa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_0=function(){return(D._emscripten_sync_run_in_main_thread_0=D.asm.ra).apply(null,arguments)};
D._emscripten_sync_run_in_main_thread_1=function(){return(D._emscripten_sync_run_in_main_thread_1=D.asm.sa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_2=function(){return(D._emscripten_sync_run_in_main_thread_2=D.asm.ta).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_xprintf_varargs=function(){return(D._emscripten_sync_run_in_main_thread_xprintf_varargs=D.asm.ua).apply(null,arguments)};
D._emscripten_sync_run_in_main_thread_3=function(){return(D._emscripten_sync_run_in_main_thread_3=D.asm.va).apply(null,arguments)};var Ec=D._emscripten_sync_run_in_main_thread_4=function(){return(Ec=D._emscripten_sync_run_in_main_thread_4=D.asm.wa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_5=function(){return(D._emscripten_sync_run_in_main_thread_5=D.asm.xa).apply(null,arguments)};
D._emscripten_sync_run_in_main_thread_6=function(){return(D._emscripten_sync_run_in_main_thread_6=D.asm.ya).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_7=function(){return(D._emscripten_sync_run_in_main_thread_7=D.asm.za).apply(null,arguments)};
var Ub=D._emscripten_run_in_main_runtime_thread_js=function(){return(Ub=D._emscripten_run_in_main_runtime_thread_js=D.asm.Aa).apply(null,arguments)},$b=D.__emscripten_call_on_thread=function(){return($b=D.__emscripten_call_on_thread=D.asm.Ba).apply(null,arguments)};D._emscripten_tls_init=function(){return(D._emscripten_tls_init=D.asm.Ca).apply(null,arguments)};D.dynCall_viijii=function(){return(D.dynCall_viijii=D.asm.Da).apply(null,arguments)};
D.dynCall_iiji=function(){return(D.dynCall_iiji=D.asm.Ea).apply(null,arguments)};D.dynCall_jiji=function(){return(D.dynCall_jiji=D.asm.Fa).apply(null,arguments)};D.dynCall_iiiiiijj=function(){return(D.dynCall_iiiiiijj=D.asm.Ga).apply(null,arguments)};D.dynCall_iiiiij=function(){return(D.dynCall_iiiiij=D.asm.Ha).apply(null,arguments)};D.dynCall_iiiiijj=function(){return(D.dynCall_iiiiijj=D.asm.Ia).apply(null,arguments)};var cb=D._main_thread_futex=3067960;D.PThread=Q;D.PThread=Q;D._pthread_self=pc;
D.wasmMemory=m;D.ExitStatus=Hc;var Ic;function Hc(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}Pa=function Jc(){Ic||Kc();Ic||(Pa=Jc)};
function Kc(){function a(){if(!Ic&&(Ic=!0,D.calledRun=!0,!va)){Va(Ka);G||Va(La);na(D);if(D.onRuntimeInitialized)D.onRuntimeInitialized();if(!G){if(D.postRun)for("function"==typeof D.postRun&&(D.postRun=[D.postRun]);D.postRun.length;){var b=D.postRun.shift();Ma.unshift(b)}Va(Ma)}}}if(!(0<N)){if(!G){if(D.preRun)for("function"==typeof D.preRun&&(D.preRun=[D.preRun]);D.preRun.length;)Na();Va(Ja)}0<N||(D.setStatus?(D.setStatus("Running..."),setTimeout(function(){setTimeout(function(){D.setStatus("")},
1);a()},1)):a())}}D.run=Kc;if(D.preInit)for("function"==typeof D.preInit&&(D.preInit=[D.preInit]);0<D.preInit.length;)D.preInit.pop()();G||(noExitRuntime=!0);G?Q.Lb():Kc();
return jxl_enc_mt_simd.ready
}
);
})();
export default jxl_enc_mt_simd;

Binary file not shown.

View File

@@ -1 +0,0 @@
var threadInfoStruct=0;var selfThreadId=0;var parentThreadId=0;var initializedJS=false;var Module={};function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:selfThreadId})}var err=threadPrintErr;this.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);Module["wasmModule"]=null;receiveInstance(instance);return instance.exports};this.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;import(e.data.urlOrBlob).then(function(jxl_enc_mt_simd){return jxl_enc_mt_simd.default(Module)}).then(function(instance){Module=instance;postMessage({"cmd":"loaded"})})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;threadInfoStruct=e.data.threadInfoStruct;Module["registerPthreadPtr"](threadInfoStruct,/*isMainBrowserThread=*/0,/*isMainRuntimeThread=*/0);selfThreadId=e.data.selfThreadId;parentThreadId=e.data.parentThreadId;var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["_emscripten_tls_init"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].setThreadStatus(Module["_pthread_self"](),1);if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["dynCall"]("ii",e.data.start_routine,[e.data.arg]);if(!Module["getNoExitRuntime"]())Module["PThread"].threadExit(result)}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){Atomics.store(Module["HEAPU32"],(threadInfoStruct+4)>>/*C_STRUCTS.pthread.threadExitCode*/2,(ex instanceof Module["ExitStatus"])?ex.status:-2);/*A custom entry specific to Emscripten denoting that the thread crashed.*/Atomics.store(Module["HEAPU32"],(threadInfoStruct+0)>>/*C_STRUCTS.pthread.threadStatus*/2,1);Module["_emscripten_futex_wake"](threadInfoStruct+0,/*C_STRUCTS.pthread.threadStatus*/2147483647);if(!(ex instanceof Module["ExitStatus"]))throw ex}}}else if(e.data.cmd==="cancel"){if(threadInfoStruct){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(threadInfoStruct){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};

View File

@@ -41,7 +41,6 @@ const assetRe = new RegExp('/fake/path/to/asset/([^/]+)/', 'g');
const appendCssModule = '\0appendCss';
const appendCssSource = `
export default function appendCss(css) {
if (__PRERENDER__) return;
const style = document.createElement('style');
style.textContent = css;
document.head.append(style);
@@ -90,7 +89,7 @@ export default function (resolveFileUrl) {
}),
postCSSUrl({
url: ({ relativePath, url }) => {
if (/^((https?|data):|#)/.test(url)) return url;
if (/^(https?|data):/.test(url)) return url;
const parsedPath = parsePath(relativePath);
const source = readFileSync(
resolvePath(dirname(path), relativePath),

View File

@@ -30,7 +30,7 @@ export default function initialCssPlugin() {
async load(id) {
if (id !== initialCssModule) return;
const matches = await globP('shared/prerendered-app/**/*.css', {
const matches = await globP('shared/initial-app/**/*.css', {
nodir: true,
cwd: path.join(process.cwd(), 'src'),
});

12
package-lock.json generated
View File

@@ -182,12 +182,6 @@
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true
},
"@types/dedent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.0.tgz",
"integrity": "sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==",
"dev": true
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
@@ -6036,9 +6030,9 @@
"dev": true
},
"preact": {
"version": "10.5.7",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.7.tgz",
"integrity": "sha512-4oEpz75t/0UNcwmcsjk+BIcDdk68oao+7kxcpc1hQPNs2Oo3ZL9xFz8UBf350mxk/VEdD41L5b4l2dE3Ug3RYg==",
"version": "10.5.5",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.5.tgz",
"integrity": "sha512-5ONLNH1SXMzzbQoExZX4TELemNt+TEDb622xXFNfZngjjM9qtrzseJt+EfiUu4TZ6EJ95X5sE1ES4yqHFSIdhg==",
"dev": true
},
"preact-render-to-string": {

View File

@@ -15,11 +15,9 @@
"@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-replace": "^2.3.4",
"@surma/rollup-plugin-off-main-thread": "^1.4.2",
"@types/dedent": "^0.7.0",
"@types/node": "^14.14.7",
"comlink": "^4.3.0",
"cssnano": "^4.1.10",
"dedent": "^0.7.0",
"del": "^5.1.0",
"file-drop-element": "^1.0.1",
"husky": "^4.3.0",
@@ -35,7 +33,7 @@
"postcss-nested": "^4.2.3",
"postcss-simple-vars": "^5.0.2",
"postcss-url": "^8.0.0",
"preact": "^10.5.7",
"preact": "^10.5.5",
"preact-render-to-string": "^5.1.11",
"prettier": "^2.1.2",
"pretty-bytes": "^5.4.1",

View File

@@ -1,16 +1,16 @@
import type { FileDropEvent } from 'file-drop-element';
import type SnackBarElement from 'shared/custom-els/snack-bar';
import type { SnackOptions } from 'shared/custom-els/snack-bar';
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
import type { SnackOptions } from 'shared/initial-app/custom-els/snack-bar';
import { h, Component } from 'preact';
import { linkRef } from 'shared/prerendered-app/util';
import { linkRef } from 'shared/initial-app/util';
import * as style from './style.css';
import 'add-css:./style.css';
import 'file-drop-element';
import 'shared/custom-els/snack-bar';
import Intro from 'shared/prerendered-app/Intro';
import 'shared/custom-els/loading-spinner';
import 'shared/initial-app/custom-els/snack-bar';
import Intro from 'shared/initial-app/Intro';
import 'shared/initial-app/custom-els/loading-spinner';
const ROUTE_EDITOR = '/editor';

View File

@@ -24,8 +24,8 @@
right: 10px;
bottom: 10px;
border: 2px dashed #fff;
background-color: rgba(0, 0, 0, 0.1);
border-color: var(--pink);
background-color: rgba(88, 116, 88, 0.2);
border-color: rgba(65, 129, 65, 0.5);
border-radius: 10px;
opacity: 0;
transform: scale(0.95);

View File

@@ -1,5 +1,5 @@
/// <reference path="../../../shared/custom-els/snack-bar/missing-types.d.ts" />
/// <reference path="../../../shared/custom-els/loading-spinner/missing-types.d.ts" />
/// <reference path="../../../shared/initial-app/custom-els/snack-bar/missing-types.d.ts" />
/// <reference path="../../../shared/initial-app/custom-els/loading-spinner/missing-types.d.ts" />
import type { FileDropElement, FileDropEvent } from 'file-drop-element';
interface FileDropAttributes extends preact.JSX.HTMLAttributes {

View File

@@ -1,37 +0,0 @@
import { h, Component, createRef } from 'preact';
import { drawDataToCanvas } from '../util';
export interface CanvasImageProps
extends h.JSX.HTMLAttributes<HTMLCanvasElement> {
image?: ImageData;
}
export default class CanvasImage extends Component<CanvasImageProps> {
canvas = createRef<HTMLCanvasElement>();
componentDidUpdate(prevProps: CanvasImageProps) {
if (this.props.image !== prevProps.image) {
this.draw(this.props.image);
}
}
componentDidMount() {
if (this.props.image) {
this.draw(this.props.image);
}
}
draw(image?: ImageData) {
const canvas = this.canvas.current;
if (!canvas) return;
if (!image) canvas.getContext('2d');
else drawDataToCanvas(canvas, image);
}
render({ image, ...props }: CanvasImageProps) {
return (
<canvas
ref={this.canvas}
width={image?.width}
height={image?.height}
{...props}
/>
);
}
}

View File

@@ -1,54 +0,0 @@
import {
Component,
cloneElement,
createRef,
toChildArray,
ComponentChildren,
RefObject,
} from 'preact';
interface Props {
children: ComponentChildren;
onClick?(e: MouseEvent | KeyboardEvent): void;
}
export class ClickOutsideDetector extends Component<Props> {
private _roots: RefObject<Element>[] = [];
private handleClick = (e: MouseEvent) => {
let target = e.target as Node;
// check if the click came from within any of our child elements:
for (const { current: root } of this._roots) {
if (root && (root === target || root.contains(target))) return;
}
const { onClick } = this.props;
if (onClick) onClick(e);
};
private handleKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
const { onClick } = this.props;
if (onClick) onClick(e);
}
};
componentDidMount() {
addEventListener('click', this.handleClick, { passive: true });
addEventListener('keydown', this.handleKey, { passive: true });
}
componentWillUnmount() {
removeEventListener('click', this.handleClick);
removeEventListener('keydown', this.handleKey);
}
render({ children }: Props) {
this._roots = [];
return toChildArray(children).map((child) => {
if (typeof child !== 'object') return child;
const ref = createRef();
this._roots.push(ref);
return cloneElement(child, { ref });
});
}
}

View File

@@ -1,84 +0,0 @@
import { h, cloneElement, Component, VNode, createRef, ComponentChildren, ComponentProps } from "preact";
import { ClickOutsideDetector } from "../ClickOutsideDetector";
import * as style from './style.css';
import 'add-css:./style.css';
type Anchor = 'left' | 'right' | 'top' | 'bottom';
interface Props extends ComponentProps<'aside'> {
showing?: boolean;
direction?: 'up' | 'down';
anchor?: Anchor | Anchor[];
toggle?: VNode;
children?: ComponentChildren;
}
interface State {
showing: boolean;
}
export default class Flyout extends Component<Props, State> {
state = {
showing: this.props.showing === true
};
private menu = createRef<HTMLElement>();
private hide = () => {
this.setState({ showing: false });
};
private toggle = () => {
this.setState({ showing: !this.state.showing });
};
componentWillReceiveProps({ showing }: Props) {
if (showing !== this.props.showing) {
this.setState({ showing });
}
}
componentDidUpdate(prevProps: Props, prevState: State) {
if (this.state.showing && !prevState.showing) {
const menu = this.menu.current;
if (menu) {
let toFocus = menu.firstElementChild;
for (let child of menu.children) {
if (child.hasAttribute('autofocus')) {
toFocus = child;
break;
}
}
// @ts-ignore-next
if (toFocus) toFocus.focus();
}
}
}
render({ direction, anchor, toggle, children, ...props }: Props, { showing }: State) {
const toggleProps = {
flyoutOpen: showing,
onClick: this.toggle
};
const anchorText = Array.isArray(anchor) ? anchor.join(' ') : anchor;
return (
<span class={style.wrap} data-flyout-open={showing ? '' : undefined}>
<ClickOutsideDetector onClick={this.hide}>
{toggle && cloneElement(toggle, toggleProps)}
<aside
{...props}
ref={this.menu}
hidden={!showing}
data-anchor={anchorText}
data-direction={direction}
>
{children}
</aside>
</ClickOutsideDetector>
</span>
);
}
}

View File

@@ -1,70 +0,0 @@
.wrap {
display: inline;
position: relative;
}
.wrap > aside:last-of-type {
display: inline-block;
position: absolute;
left: 0;
top: 100%;
z-index: 100;
display: flex;
flex-wrap: nowrap;
flex-direction: column;
align-items: flex-start;
overflow: visible;
outline: none;
will-change: transform, opacity;
animation: menuOpen 350ms ease forwards 1;
--flyout-offset-y: -20px;
&[hidden] {
display: none;
}
/* align to the right edge */
&[data-anchor*='right'] {
left: 100%;
}
/* open to the left */
&[data-direction*='left'] {
right: 0;
left: auto;
&[anchor*='right'] {
right: 100%;
}
}
/* align to the top edge */
&[data-anchor*='top'] {
top: 0;
}
/* open to the left */
&[data-direction*='up'] {
bottom: 100%;
top: auto;
flex-direction: column-reverse;
--flyout-offset-y: 20px;
&[data-anchor*='bottom'] {
bottom: 0;
}
}
/*
@media (min-width: 860px) {
flex-direction: column-reverse;
top: auto;
bottom: 100%;
}
*/
}
@keyframes menuOpen {
0% {
transform: translateY(var(--flyout-offset-y, 0));
opacity: 0;
}
}

View File

@@ -18,5 +18,5 @@
}
.checked {
fill: var(--main-theme-color);
fill: #34b9eb;
}

View File

@@ -28,7 +28,7 @@ function getPrescision(value: string): number {
class RangeInputElement extends HTMLElement {
private _input: HTMLInputElement;
private _valueDisplay?: HTMLSpanElement;
private _valueDisplay?: HTMLDivElement;
private _ignoreChange = false;
static get observedAttributes() {
@@ -66,13 +66,13 @@ class RangeInputElement extends HTMLElement {
this.innerHTML =
`<div class="${style.thumbWrapper}">` +
`<div class="${style.thumb}"></div>` +
`<div class="${style.valueDisplay}"><svg width="32" height="62"><path d="M27.3 27.3C25 29.6 17 35.8 17 43v3c0 3 2.5 5 3.2 5.8a6 6 0 1 1-8.5 0C12.6 51 15 49 15 46v-3c0-7.2-8-13.4-10.3-15.7A16 16 0 0 1 16 0a16 16 0 0 1 11.3 27.3z"/></svg><span></span></div>` +
`<div class="${style.valueDisplay}"></div>` +
'</div>';
this.insertBefore(this._input, this.firstChild);
this._valueDisplay = this.querySelector(
'.' + style.valueDisplay + ' > span',
) as HTMLSpanElement;
'.' + style.valueDisplay,
) as HTMLDivElement;
// Set inline styles (this is useful when used with frameworks which might clear inline styles)
this._update();
}

View File

@@ -23,8 +23,10 @@ range-input::before {
width: 100%;
height: 2px;
border-radius: 1px;
background: linear-gradient(var(--main-theme-color), var(--main-theme-color))
0 / var(--value-percent, 0%) 100% no-repeat var(--medium-light-gray);
box-shadow: 0 -0.5px 0 rgba(0, 0, 0, 0.3),
inset 0 0.5px 0 rgba(255, 255, 255, 0.2), 0 0.5px 0 rgba(255, 255, 255, 0.3);
background: linear-gradient(#34b9eb, #218ab1) 0 / var(--value-percent, 0%)
100% no-repeat #eee;
}
.input {
@@ -39,12 +41,14 @@ range-input::before {
pointer-events: none;
position: absolute;
bottom: 3px;
left: 0;
left: var(--value-percent, 0%);
margin-left: -6px;
background: var(--main-theme-color);
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><circle cx="5" cy="5" r="1" fill="%235D509E" /></svg>')
center no-repeat #34b9eb;
border-radius: 50%;
width: 12px;
height: 12px;
box-shadow: 0 0.5px 2px rgba(0, 0, 0, 0.3);
}
.thumb-wrapper {
@@ -54,19 +58,21 @@ range-input::before {
bottom: 0;
height: 0;
overflow: visible;
transform: translate(var(--value-percent, 0%), 0);
}
.value-display {
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="62" fill="none"><path fill="%2334B9EB" d="M27.3 27.3C25 29.6 17 35.8 17 43v3c0 3 2.5 5 3.2 5.8a6 6 0 1 1-8.5 0C12.6 51 15 49 15 46v-3c0-7.2-8-13.4-10.3-15.7A16 16 0 0 1 16 0a16 16 0 0 1 11.3 27.3z"/><circle cx="16" cy="56" r="1" fill="%235D509E"/></svg>')
top center no-repeat;
position: absolute;
box-sizing: border-box;
left: 0;
left: var(--value-percent, 0%);
bottom: 3px;
width: 32px;
height: 62px;
text-align: center;
padding: 8px 3px 0;
margin: 0 0 0 -16px;
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3));
transform-origin: 50% 90%;
opacity: 0.0001;
transform: scale(0.2);
@@ -80,19 +86,6 @@ range-input::before {
will-change: transform;
pointer-events: none;
overflow: hidden;
> svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
fill: var(--main-theme-color);
}
> span {
position: relative;
}
}
.touch-active + .thumb-wrapper .value-display {

View File

@@ -3,7 +3,7 @@ import * as style from './style.css';
import 'add-css:./style.css';
import RangeInputElement from './custom-els/RangeInput';
import './custom-els/RangeInput';
import { linkRef } from 'shared/prerendered-app/util';
import { linkRef } from 'shared/initial-app/util';
interface Props extends preact.JSX.HTMLAttributes {}
interface State {}

View File

@@ -33,7 +33,6 @@
box-sizing: border-box;
text-decoration: underline;
text-decoration-style: dotted;
text-decoration-color: var(--main-theme-color);
text-underline-position: under;
width: 48px;
position: relative;

View File

@@ -1,21 +0,0 @@
import { h, Component } from 'preact';
import * as style from './style.css';
import 'add-css:./style.css';
import { Arrow } from '../../../icons';
interface Props extends preact.JSX.HTMLAttributes {}
interface State {}
export default class Revealer extends Component<Props, State> {
render(props: Props) {
return (
<div class={style.checkbox}>
{/* @ts-ignore - TS bug https://github.com/microsoft/TypeScript/issues/16019 */}
<input class={style.realCheckbox} type="checkbox" {...props} />
<div class={style.arrow}>
<Arrow />
</div>
</div>
);
}
}

View File

@@ -1,29 +0,0 @@
.checkbox {
display: inline-block;
position: relative;
}
.arrow {
width: 10px;
height: 10px;
fill: var(--white);
transition: transform 200ms ease;
transform: rotate(-90deg);
svg {
width: 100%;
height: 100%;
display: block;
}
}
.real-checkbox {
top: 0;
position: absolute;
opacity: 0;
pointer-events: none;
&:checked + .arrow {
transform: none;
}
}

View File

@@ -1,7 +1,6 @@
import { h, Component } from 'preact';
import * as style from './style.css';
import 'add-css:./style.css';
import { Arrow } from 'client/lazy-app/icons';
interface Props extends preact.JSX.HTMLAttributes {
large?: boolean;
@@ -19,9 +18,9 @@ export default class Select extends Component<Props, State> {
class={`${style.builtinSelect} ${large ? style.large : ''}`}
{...otherProps}
/>
<div class={style.arrow}>
<Arrow />
</div>
<svg class={style.arrow} viewBox="0 0 10 5">
<path d="M0 0l5 5 5-5z" />
</svg>
</div>
);
}

View File

@@ -3,12 +3,10 @@
}
.builtin-select {
background: var(--black);
background: #2f2f2f;
border-radius: 4px;
font: inherit;
padding: 7px 0;
padding-right: 25px;
padding-left: 10px;
padding: 4px 25px 4px 10px;
-webkit-appearance: none;
-moz-appearance: none;
border: none;
@@ -27,7 +25,7 @@
.large {
padding: 10px 35px 10px 10px;
background: var(--dark-gray);
background: #151515;
& .arrow {
right: 13px;

View File

@@ -1,22 +0,0 @@
import { h, Component } from 'preact';
import * as style from './style.css';
import 'add-css:./style.css';
interface Props extends preact.JSX.HTMLAttributes {}
interface State {}
export default class Toggle extends Component<Props, State> {
render(props: Props) {
return (
<div class={style.checkbox}>
{/* @ts-ignore - TS bug https://github.com/microsoft/TypeScript/issues/16019 */}
<input class={style.realCheckbox} type="checkbox" {...props} />
<div class={style.track}>
<div class={style.thumbTrack}>
<div class={style.thumb}></div>
</div>
</div>
</div>
);
}
}

View File

@@ -1,55 +0,0 @@
.checkbox {
display: inline-block;
position: relative;
}
.track {
--thumb-size: 14px;
background: var(--black);
border-radius: 1000px;
width: 24px;
padding: 3px calc(var(--thumb-size) / 2 + 3px);
}
.thumb {
position: relative;
width: var(--thumb-size);
height: var(--thumb-size);
background: var(--less-light-gray);
border-radius: 100%;
transform: translateX(calc(var(--thumb-size) / -2));
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--main-theme-color);
opacity: 0;
transition: opacity 200ms ease;
}
}
.thumb-track {
transition: transform 200ms ease;
}
.real-checkbox {
top: 0;
position: absolute;
opacity: 0;
pointer-events: none;
&:checked + .track {
.thumb-track {
transform: translateX(100%);
}
.thumb::before {
opacity: 1;
}
}
}

View File

@@ -14,20 +14,18 @@ import {
} from '../../feature-meta';
import Expander from './Expander';
import Checkbox from './Checkbox';
import Toggle from './Toggle';
import Select from './Select';
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
interface Props {
index: 0 | 1;
mobileView: boolean;
source?: SourceImage;
encoderState?: EncoderState;
processorState: ProcessorState;
onEncoderTypeChange(index: 0 | 1, newType: OutputType): void;
onEncoderOptionsChange(index: 0 | 1, newOptions: EncoderOptions): void;
onProcessorOptionsChange(index: 0 | 1, newOptions: ProcessorState): void;
onEncoderTypeChange(newType: OutputType): void;
onEncoderOptionsChange(newOptions: EncoderOptions): void;
onProcessorOptionsChange(newOptions: ProcessorState): void;
}
interface State {
@@ -75,7 +73,7 @@ export default class Options extends Component<Props, State> {
// The select element only has values matching encoder types,
// so 'as' is safe here.
const type = el.value as OutputType;
this.props.onEncoderTypeChange(this.props.index, type);
this.props.onEncoderTypeChange(type);
};
private onProcessorEnabledChange = (event: Event) => {
@@ -83,31 +81,24 @@ export default class Options extends Component<Props, State> {
const processor = el.name.split('.')[0] as keyof ProcessorState;
this.props.onProcessorOptionsChange(
this.props.index,
cleanSet(this.props.processorState, `${processor}.enabled`, el.checked),
);
};
private onQuantizerOptionsChange = (opts: ProcessorOptions['quantize']) => {
this.props.onProcessorOptionsChange(
this.props.index,
cleanMerge(this.props.processorState, 'quantize', opts),
);
};
private onResizeOptionsChange = (opts: ProcessorOptions['resize']) => {
this.props.onProcessorOptionsChange(
this.props.index,
cleanMerge(this.props.processorState, 'resize', opts),
);
};
private onEncoderOptionsChange = (newOptions: EncoderOptions) => {
this.props.onEncoderOptionsChange(this.props.index, newOptions);
};
render(
{ source, encoderState, processorState }: Props,
{ source, encoderState, processorState, onEncoderOptionsChange }: Props,
{ supportedEncoderMap }: State,
) {
const encoder = encoderState && encoderMap[encoderState.type];
@@ -115,24 +106,18 @@ export default class Options extends Component<Props, State> {
encoder && 'Options' in encoder ? encoder.Options : undefined;
return (
<div
class={
style.optionsScroller +
' ' +
(encoderState ? '' : style.originalImage)
}
>
<div class={style.optionsScroller}>
<Expander>
{!encoderState ? null : (
<div>
<h3 class={style.optionsTitle}>Edit</h3>
<label class={style.sectionEnabler}>
Resize
<Toggle
<Checkbox
name="resize.enable"
checked={!!processorState.resize.enabled}
onChange={this.onProcessorEnabledChange}
/>
Resize
</label>
<Expander>
{processorState.resize.enabled ? (
@@ -147,12 +132,12 @@ export default class Options extends Component<Props, State> {
</Expander>
<label class={style.sectionEnabler}>
Reduce palette
<Toggle
<Checkbox
name="quantize.enable"
checked={!!processorState.quantize.enabled}
onChange={this.onProcessorEnabledChange}
/>
Reduce palette
</label>
<Expander>
{processorState.quantize.enabled ? (
@@ -195,7 +180,7 @@ export default class Options extends Component<Props, State> {
// the correct type, but typescript isn't smart enough.
encoderState!.options as any
}
onChange={this.onEncoderOptionsChange}
onChange={onEncoderOptionsChange}
/>
)}
</Expander>

View File

@@ -3,73 +3,56 @@
overflow-y: auto;
-webkit-overflow-scrolling: touch;
--horizontal-padding: 15px;
border-radius: var(--scroller-radius);
}
.options-title {
background-color: var(--main-theme-color);
color: var(--header-text-color);
background: rgba(0, 0, 0, 0.9);
margin: 0;
padding: 10px var(--horizontal-padding);
font-weight: bold;
font-weight: normal;
font-size: 1.4rem;
border-bottom: 1px solid var(--off-black);
transition: all 300ms ease-in-out;
transition-property: background-color, color;
}
.original-image .options-title {
background-color: var(--black);
color: var(--white);
border-bottom: 1px solid #000;
}
.option-text-first {
display: grid;
align-items: center;
grid-template-columns: 87px 1fr;
gap: 0.7em;
grid-gap: 0.7em;
padding: 10px var(--horizontal-padding);
}
.option-toggle {
cursor: pointer;
display: grid;
align-items: center;
grid-template-columns: 1fr auto;
gap: 0.7em;
padding: 10px var(--horizontal-padding);
}
.option-reveal {
composes: option-toggle;
grid-template-columns: auto 1fr;
gap: 1em;
}
.option-one-cell {
display: grid;
grid-template-columns: 1fr;
padding: 10px var(--horizontal-padding);
}
.option-input-first,
.section-enabler {
composes: option-toggle;
background: var(--dark-gray);
padding: 15px var(--horizontal-padding);
border-bottom: 1px solid var(--off-black);
cursor: pointer;
display: grid;
align-items: center;
grid-template-columns: auto 1fr;
grid-gap: 0.7em;
padding: 10px var(--horizontal-padding);
}
.section-enabler {
background: rgba(0, 0, 0, 0.8);
}
.options-section {
background: var(--off-black);
background: rgba(0, 0, 0, 0.7);
}
.text-field {
background: var(--white);
color: var(--black);
background: #fff;
color: #000;
font: inherit;
border: none;
padding: 6px 0 6px 10px;
padding: 2px 0 2px 10px;
width: 100%;
box-sizing: border-box;
border-radius: 4px;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);
}

View File

@@ -73,14 +73,9 @@ export default class TwoUp extends HTMLElement {
connectedCallback() {
this._childrenChange();
// prettier-ignore
this._handle.innerHTML =
`<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>`;
this._handle.innerHTML = `<div class="${
styles.scrubber
}">${`<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>`;
if (!this._everConnected) {
this._resetPosition();

View File

@@ -2,11 +2,12 @@ two-up {
display: grid;
position: relative;
--split-point: 0;
--track-color: rgb(0 0 0 / 0.6);
--thumb-background: var(--black);
--accent-color: #777;
--track-color: var(--accent-color);
--thumb-background: #fff;
--thumb-color: var(--accent-color);
--thumb-size: 62px;
--bar-size: 9px;
--bar-size: 6px;
--bar-touch-size: 30px;
}
@@ -36,6 +37,8 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
height: 100%;
width: var(--bar-size);
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);
}
@@ -44,11 +47,14 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
position: absolute;
top: 50%;
left: 50%;
transform-origin: 50% 50%;
transform: translate(-50%, -50%);
width: var(--thumb-size);
height: var(--thumb-size);
height: calc(var(--thumb-size) * 0.9);
background: var(--thumb-background);
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: var(--thumb-size);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
color: var(--thumb-color);
box-sizing: border-box;
padding: 0 calc(var(--thumb-size) * 0.24);
@@ -58,14 +64,6 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
flex: 1;
}
.arrow-left {
fill: var(--pink);
}
.arrow-right {
fill: var(--blue);
}
two-up[orientation='vertical'] .two-up-handle {
width: auto;
height: var(--bar-touch-size);

View File

@@ -1,4 +1,4 @@
import { h, createRef, Component, Fragment } from 'preact';
import { h, Component } from 'preact';
import type PinchZoom from './custom-els/PinchZoom';
import type { ScaleToOpts } from './custom-els/PinchZoom';
import './custom-els/PinchZoom';
@@ -10,36 +10,32 @@ import {
ToggleBackgroundIcon,
AddIcon,
RemoveIcon,
BackIcon,
ToggleBackgroundActiveIcon,
RotateIcon,
MoreIcon,
} from '../../icons';
import { twoUpHandle } from './custom-els/TwoUp/styles.css';
import type { PreprocessorState } from '../../feature-meta';
import { cleanSet } from '../../util/clean-modify';
import type { SourceImage } from '../../Compress';
import { linkRef } from 'shared/prerendered-app/util';
import Flyout from '../Flyout';
import { linkRef } from 'shared/initial-app/util';
interface Props {
source?: SourceImage;
preprocessorState?: PreprocessorState;
hidden?: boolean;
mobileView: boolean;
leftCompressed?: ImageData;
rightCompressed?: ImageData;
leftImgContain: boolean;
rightImgContain: boolean;
onPreprocessorChange?: (newState: PreprocessorState) => void;
onShowPreprocessorTransforms?: () => void;
onToggleBackground?: () => void;
onBack: () => void;
onPreprocessorChange: (newState: PreprocessorState) => void;
}
interface State {
scale: number;
editingScale: boolean;
altBackground: boolean;
transform: boolean;
menuOpen: boolean;
}
const scaleToOpts: ScaleToOpts = {
@@ -54,15 +50,12 @@ export default class Output extends Component<Props, State> {
scale: 1,
editingScale: false,
altBackground: false,
transform: false,
menuOpen: false,
};
canvasLeft?: HTMLCanvasElement;
canvasRight?: HTMLCanvasElement;
pinchZoomLeft?: PinchZoom;
pinchZoomRight?: PinchZoom;
scaleInput?: HTMLInputElement;
flyout = createRef<Flyout>();
retargetedEvents = new WeakSet<Event>();
componentDidMount() {
@@ -153,6 +146,12 @@ export default class Output extends Component<Props, State> {
return props.rightCompressed || (props.source && props.source.preprocessed);
}
private toggleBackground = () => {
this.setState({
altBackground: !this.state.altBackground,
});
};
private zoomIn = () => {
if (!this.pinchZoomLeft) throw Error('Missing pinch-zoom element');
this.pinchZoomLeft.scaleTo(this.state.scale * 1.25, scaleToOpts);
@@ -163,36 +162,17 @@ export default class Output extends Component<Props, State> {
this.pinchZoomLeft.scaleTo(this.state.scale / 1.25, scaleToOpts);
};
private fitToViewport = () => {
if (!this.pinchZoomLeft) throw Error('Missing pinch-zoom element');
const img = this.props.source?.preprocessed;
if (!img) return;
const scale = Number(
Math.min(
(window.innerWidth - 20) / img.width,
(window.innerHeight - 20) / img.height,
).toFixed(2),
private onRotateClick = () => {
const { preprocessorState: inputProcessorState } = this.props;
if (!inputProcessorState) return;
const newState = cleanSet(
inputProcessorState,
'rotate.rotate',
(inputProcessorState.rotate.rotate + 90) % 360,
);
this.pinchZoomLeft.scaleTo(Number(scale.toFixed(2)), scaleToOpts);
this.recenter();
// this.hideMenu();
};
private zoomTo2x = () => {
if (!this.pinchZoomLeft) throw Error('Missing pinch-zoom element');
this.pinchZoomLeft.scaleTo(0.5, scaleToOpts);
this.recenter();
};
private recenter = () => {
const img = this.props.source?.preprocessed;
if (!img || !this.pinchZoomLeft) return;
let scale = this.pinchZoomLeft.scale;
this.pinchZoomLeft.setTransform({
x: (img.width - img.width * scale) / 2,
y: (img.height - img.height * scale) / 2,
allowChangeEvent: true,
});
this.props.onPreprocessorChange(newState);
};
private onScaleValueFocus = () => {
@@ -275,16 +255,8 @@ export default class Output extends Component<Props, State> {
};
render(
{
source,
mobileView,
hidden,
leftImgContain,
rightImgContain,
onShowPreprocessorTransforms,
onToggleBackground,
}: Props,
{ scale, editingScale }: State,
{ mobileView, leftImgContain, rightImgContain, source, onBack }: Props,
{ scale, editingScale, altBackground }: State,
) {
const leftDraw = this.leftDrawable();
const rightDraw = this.rightDrawable();
@@ -292,60 +264,63 @@ export default class Output extends Component<Props, State> {
const originalImage = source && source.preprocessed;
return (
<Fragment>
<div class={style.output} hidden={hidden}>
<two-up
legacy-clip-compat
class={style.twoUp}
orientation={mobileView ? 'vertical' : 'horizontal'}
// Event redirecting. See onRetargetableEvent.
onTouchStartCapture={this.onRetargetableEvent}
onTouchEndCapture={this.onRetargetableEvent}
onTouchMoveCapture={this.onRetargetableEvent}
onPointerDownCapture={this.onRetargetableEvent}
onMouseDownCapture={this.onRetargetableEvent}
onWheelCapture={this.onRetargetableEvent}
<div
class={`${style.output} ${altBackground ? style.altBackground : ''}`}
>
<two-up
legacy-clip-compat
class={style.twoUp}
orientation={mobileView ? 'vertical' : 'horizontal'}
// Event redirecting. See onRetargetableEvent.
onTouchStartCapture={this.onRetargetableEvent}
onTouchEndCapture={this.onRetargetableEvent}
onTouchMoveCapture={this.onRetargetableEvent}
onPointerDownCapture={this.onRetargetableEvent}
onMouseDownCapture={this.onRetargetableEvent}
onWheelCapture={this.onRetargetableEvent}
>
<pinch-zoom
class={style.pinchZoom}
onChange={this.onPinchZoomLeftChange}
ref={linkRef(this, 'pinchZoomLeft')}
>
<pinch-zoom
class={style.pinchZoom}
onChange={this.onPinchZoomLeftChange}
ref={linkRef(this, 'pinchZoomLeft')}
>
<canvas
class={style.pinchTarget}
ref={linkRef(this, 'canvasLeft')}
width={leftDraw && leftDraw.width}
height={leftDraw && leftDraw.height}
style={{
width: originalImage ? originalImage.width : '',
height: originalImage ? originalImage.height : '',
objectFit: leftImgContain ? 'contain' : undefined,
}}
/>
</pinch-zoom>
<pinch-zoom
class={style.pinchZoom}
ref={linkRef(this, 'pinchZoomRight')}
>
<canvas
class={style.pinchTarget}
ref={linkRef(this, 'canvasRight')}
width={rightDraw && rightDraw.width}
height={rightDraw && rightDraw.height}
style={{
width: originalImage ? originalImage.width : '',
height: originalImage ? originalImage.height : '',
objectFit: rightImgContain ? 'contain' : undefined,
}}
/>
</pinch-zoom>
</two-up>
<canvas
class={style.pinchTarget}
ref={linkRef(this, 'canvasLeft')}
width={leftDraw && leftDraw.width}
height={leftDraw && leftDraw.height}
style={{
width: originalImage ? originalImage.width : '',
height: originalImage ? originalImage.height : '',
objectFit: leftImgContain ? 'contain' : '',
}}
/>
</pinch-zoom>
<pinch-zoom
class={style.pinchZoom}
ref={linkRef(this, 'pinchZoomRight')}
>
<canvas
class={style.pinchTarget}
ref={linkRef(this, 'canvasRight')}
width={rightDraw && rightDraw.width}
height={rightDraw && rightDraw.height}
style={{
width: originalImage ? originalImage.width : '',
height: originalImage ? originalImage.height : '',
objectFit: rightImgContain ? 'contain' : '',
}}
/>
</pinch-zoom>
</two-up>
<div class={style.back}>
<button class={style.button} onClick={onBack}>
<BackIcon />
</button>
</div>
<div
class={style.controls}
hidden={hidden}
>
<div class={style.controls}>
<div class={style.zoomControls}>
<button class={style.button} onClick={this.zoomOut}>
<RemoveIcon />
@@ -374,39 +349,29 @@ export default class Output extends Component<Props, State> {
<button class={style.button} onClick={this.zoomIn}>
<AddIcon />
</button>
<Flyout
class={style.menu}
showing={hidden ? false : undefined}
anchor="right"
direction={mobileView ? 'down' : 'up'}
toggle={
<button class={`${style.button} ${style.moreButton}`}>
<MoreIcon />
</button>
}
</div>
<div class={style.buttonsNoWrap}>
<button
class={style.button}
onClick={this.onRotateClick}
title="Rotate image"
>
<button class={style.button} onClick={onShowPreprocessorTransforms}>
<RotateIcon /> Rotate & Transform
</button>
<button class={style.button} onClick={this.fitToViewport}>
Fit to viewport
</button>
<button class={style.button} onClick={this.zoomTo2x}>
Simulate retina
</button>
<button class={style.button} onClick={this.recenter}>
Re-center
</button>
<button class={style.button} onClick={onToggleBackground}>
<RotateIcon />
</button>
<button
class={`${style.button} ${altBackground ? style.active : ''}`}
onClick={this.toggleBackground}
title="Change canvas color"
>
{altBackground ? (
<ToggleBackgroundActiveIcon />
) : (
<ToggleBackgroundIcon />
{' '}
Change canvas color
</button>
</Flyout>
)}
</button>
</div>
</div>
</Fragment>
</div>
);
}
}

View File

@@ -1,18 +1,30 @@
.output {
display: contents;
composes: abs-fill from '../../../../shared/initial-app/util.css';
&[hidden] {
display: none;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #000;
opacity: 0;
transition: opacity 500ms ease;
}
&.alt-background::before {
opacity: 0.6;
}
}
.two-up {
composes: abs-fill from global;
composes: abs-fill from '../../../../shared/initial-app/util.css';
--accent-color: var(--button-fg);
}
.pinch-zoom {
composes: abs-fill from global;
composes: abs-fill from '../../../../shared/initial-app/util.css';
outline: none;
display: flex;
justify-content: center;
@@ -29,55 +41,42 @@
}
.controls {
position: absolute;
display: flex;
justify-content: center;
flex-wrap: wrap;
grid-area: header;
align-self: center;
padding: 9px 66px;
/* Had to disable containment because of the overflow menu. */
/*
contain: content;
top: 0;
left: 0;
right: 0;
padding: 9px 84px;
overflow: hidden;
*/
transition: transform 500ms ease;
flex-wrap: wrap;
contain: content;
/* Allow clicks to fall through to the pinch zoom area */
pointer-events: none;
& > * {
pointer-events: auto;
}
@media (min-width: 860px) {
padding: 9px;
top: auto;
left: 320px;
right: 320px;
bottom: 0;
flex-wrap: wrap-reverse;
grid-area: viewportOpts;
align-self: end;
}
&[hidden] {
visibility: visible;
transform: translateY(-200%);
@media (min-width: 860px) {
transform: translateY(200%);
}
}
}
.zoom-controls {
display: flex;
position: relative;
z-index: 100;
& > :not(:first-child) {
& :not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
margin-left: 0;
}
& > :not(:nth-last-child(2)) {
& :not(:last-child) {
margin-right: 0;
border-right-width: 0;
border-top-right-radius: 0;
@@ -91,72 +90,72 @@
align-items: center;
box-sizing: border-box;
margin: 4px;
background-color: rgba(29, 29, 29, 0.92);
border: 1px solid rgba(0, 0, 0, 0.67);
border-radius: 6px;
line-height: 1.1;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 5px;
line-height: 1;
white-space: nowrap;
height: 39px;
height: 36px;
padding: 0 8px;
font-size: 1.2rem;
cursor: pointer;
@media (min-width: 600px) {
height: 48px;
padding: 0 16px;
}
&:focus {
/* box-shadow: 0 0 0 2px var(--hot-pink); */
box-shadow: 0 0 0 2px #fff;
box-shadow: 0 0 0 2px var(--button-fg);
outline: none;
z-index: 1;
}
}
.button {
color: #fff;
color: var(--button-fg);
&:hover {
background: rgba(50, 50, 50, 0.92);
background-color: #eee;
}
&.active {
background: rgba(72, 72, 72, 0.92);
background: #34b9eb;
color: #fff;
&:hover {
background: #32a3ce;
}
}
}
.zoom {
color: #625e80;
cursor: text;
width: 7rem;
width: 6em;
font: inherit;
text-align: center;
justify-content: center;
&:focus {
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 2px #fff;
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 2px var(--button-fg);
}
}
span.zoom {
color: #939393;
font-size: 0.8rem;
line-height: 1.2;
font-weight: 100;
}
input.zoom {
font-size: 1.2rem;
letter-spacing: 0.05rem;
font-weight: 700;
text-indent: 3px;
color: #fff;
}
.zoom-value {
position: relative;
top: 1px;
margin: 0 3px 0 0;
padding: 0 2px;
font-size: 1.2rem;
letter-spacing: 0.05rem;
font-weight: 700;
color: #fff;
color: #888;
border-bottom: 1px dashed #999;
}
.back {
position: absolute;
top: 0;
left: 0;
padding: 9px;
}
.buttons-no-wrap {
display: flex;
pointer-events: none;
@@ -165,64 +164,3 @@ input.zoom {
pointer-events: auto;
}
}
/** Three-dot menu */
.moreButton {
padding: 0 4px;
& > svg {
transform-origin: center;
transition: transform 200ms ease;
}
}
[data-flyout-open] {
.moreButton {
background: rgba(82, 82, 82, 0.92);
& > svg {
transform: rotate(180deg);
}
}
&:before {
content: '';
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(50, 50, 50, 0.4);
backdrop-filter: blur(2px) contrast(70%);
animation: menuShimFadeIn 350ms ease forwards 1;
will-change: opacity;
z-index: -1;
}
}
@keyframes menuShimFadeIn {
0% {
opacity: 0;
}
}
.menu {
button {
margin: 8px 0;
border-radius: 2rem;
padding: 0 16px;
& > svg {
position: relative;
left: -6px;
}
}
h5 {
text-transform: uppercase;
font-size: 0.8rem;
color: #fff;
margin: 8px 4px;
padding: 10px 0 0;
}
}

View File

@@ -8,7 +8,7 @@ import {
CopyAcrossIcon,
CopyAcrossIconProps,
} from 'client/lazy-app/icons';
import 'shared/custom-els/loading-spinner';
import 'shared/initial-app/custom-els/loading-spinner';
import { SourceImage } from '../';
interface Props {

View File

@@ -124,7 +124,7 @@
.copy-to-other {
grid-row: 1;
grid-column: copy-button;
composes: unbutton from global;
composes: unbutton from '../../../../shared/initial-app/util.css';
composes: download;
background: #656565;

View File

@@ -1,388 +0,0 @@
import { h, Component, ComponentChildren } from 'preact';
import * as style from './style.css';
import 'add-css:./style.css';
import { shallowEqual } from 'client/lazy-app/util';
export interface CropBox {
left: number;
top: number;
right: number;
bottom: number;
}
// Minimum CropBox size
const MIN_SIZE = 2;
export interface Props {
size: { width: number; height: number };
scale?: number;
lockAspect?: boolean;
crop: CropBox;
onChange?(crop: CropBox): void;
}
type Edge = keyof CropBox;
interface PointerTrack {
x: number;
y: number;
edges: { edge: Edge; value: number }[];
}
interface State {
crop: CropBox;
pan: boolean;
}
export default class Cropper extends Component<Props, State> {
private pointers = new Map<number, PointerTrack>();
state = {
crop: this.normalizeCrop({ ...this.props.crop }),
pan: false,
};
shouldComponentUpdate(nextProps: Props, nextState: State) {
if (!shallowEqual(nextState, this.state)) return true;
const { size, scale, lockAspect, crop } = this.props;
return (
size.width !== nextProps.size.width ||
size.height !== nextProps.size.height ||
scale !== nextProps.scale ||
lockAspect !== nextProps.lockAspect ||
!shallowEqual(crop, nextProps.crop)
);
}
componentWillReceiveProps({ crop }: Props, nextState: State) {
const current = nextState.crop || this.state.crop;
if (crop !== this.props.crop && !shallowEqual(crop, current)) {
// this.setState({ crop: nextProps.crop });
this.setCrop(crop);
}
}
private normalizeCrop(crop: CropBox) {
crop.left = Math.round(Math.max(0, crop.left));
crop.top = Math.round(Math.max(0, crop.top));
crop.right = Math.round(Math.max(0, crop.right));
crop.bottom = Math.round(Math.max(0, crop.bottom));
return crop;
}
private setCrop(cropUpdate: Partial<CropBox>) {
const crop = this.normalizeCrop({ ...this.state.crop, ...cropUpdate });
// ignore crop updates that normalize to the same values
const old = this.state.crop;
if (
crop.left === old.left &&
crop.right === old.right &&
crop.top === old.top &&
crop.bottom === old.bottom
) {
return;
}
// crop.left = Math.max(0, crop.left) | 0;
// crop.top = Math.max(0, crop.top) | 0;
// crop.right = Math.max(0, crop.right) | 0;
// crop.bottom = Math.max(0, crop.bottom) | 0;
this.setState({ crop });
if (this.props.onChange) {
this.props.onChange(crop);
}
}
private onPointerDown = (event: PointerEvent) => {
if (event.button !== 0 || this.state.pan) return;
const target = event.target as SVGElement;
const edgeAttr = target.getAttribute('data-edge');
if (edgeAttr) {
event.stopPropagation();
event.preventDefault();
const edges = edgeAttr.split(/ *, */) as Edge[];
// console.log(this.props.lockAspect);
if (this.props.lockAspect && edges.length === 1) return;
this.pointers.set(event.pointerId, {
x: event.x,
y: event.y,
edges: edges.map((edge) => ({ edge, value: this.state.crop[edge] })),
});
target.setPointerCapture(event.pointerId);
}
};
private onPointerMove = (event: PointerEvent) => {
const target = event.target as SVGElement;
const down = this.pointers.get(event.pointerId);
if (down && target.hasPointerCapture(event.pointerId)) {
const { size } = this.props;
const oldCrop = this.state.crop;
const aspect =
(size.width - oldCrop.left - oldCrop.right) /
(size.height - oldCrop.top - oldCrop.bottom);
const scale = this.props.scale || 1;
let dx = (event.x - down.x) / scale;
let dy = (event.y - down.y) / scale;
// console.log(this.props.lockAspect, aspect);
if (this.props.lockAspect) {
const dir = (dx + dy) / 2;
dx = dir * aspect;
dy = dir / aspect;
}
const crop: Partial<CropBox> = {};
for (const { edge, value } of down.edges) {
let edgeValue = value;
switch (edge) {
case 'left':
edgeValue += dx;
break;
case 'right':
edgeValue -= dx;
break;
case 'top':
edgeValue += dy;
break;
case 'bottom':
edgeValue -= dy;
break;
}
crop[edge] = edgeValue;
}
// Prevent MOVE from resizing the cropbox:
if (crop.left && crop.right) {
if (crop.left < 0) crop.right += crop.left;
if (crop.right < 0) crop.left += crop.right;
} else {
// enforce minimum 1px cropbox width
if (crop.left)
crop.left = Math.min(
crop.left,
size.width - oldCrop.right - MIN_SIZE,
);
if (crop.right)
crop.right = Math.min(
crop.right,
size.width - oldCrop.left - MIN_SIZE,
);
}
if (crop.top && crop.bottom) {
if (crop.top < 0) crop.bottom += crop.top;
if (crop.bottom < 0) crop.top += crop.bottom;
} else {
// enforce minimum 1px cropbox height
if (crop.top)
crop.top = Math.min(
crop.top,
size.height - oldCrop.bottom - MIN_SIZE,
);
if (crop.bottom)
crop.bottom = Math.min(
crop.bottom,
size.height - oldCrop.top - MIN_SIZE,
);
}
this.setCrop(crop);
event.stopPropagation();
event.preventDefault();
}
};
private onPointerUp = (event: PointerEvent) => {
const target = event.target as SVGElement;
const down = this.pointers.get(event.pointerId);
if (down && target.hasPointerCapture(event.pointerId)) {
this.onPointerMove(event);
target.releasePointerCapture(event.pointerId);
event.stopPropagation();
event.preventDefault();
this.pointers.delete(event.pointerId);
}
};
private onKeyDown = (event: KeyboardEvent) => {
if (event.key === ' ') {
if (!this.state.pan) {
this.setState({ pan: true });
}
event.preventDefault();
}
};
private onKeyUp = (event: KeyboardEvent) => {
if (event.key === ' ') this.setState({ pan: false });
};
componentDidMount() {
addEventListener('keydown', this.onKeyDown);
addEventListener('keyup', this.onKeyUp);
}
componentWillUnmount() {
addEventListener('keydown', this.onKeyDown);
addEventListener('keyup', this.onKeyUp);
}
render({ size, scale }: Props, { crop, pan }: State) {
const x = crop.left;
const y = crop.top;
const width = size.width - crop.left - crop.right;
const height = size.height - crop.top - crop.bottom;
// const x = crop.left.toFixed(2);
// const y = crop.top.toFixed(2);
// const width = (size.width - crop.left - crop.right).toFixed(2);
// const height = (size.height - crop.top - crop.bottom).toFixed(2);
return (
<svg
class={`${style.cropper} ${pan ? style.pan : ''}`}
width={size.width + 20}
height={size.height + 20}
viewBox={`-10 -10 ${size.width + 20} ${size.height + 20}`}
style={{
// this is hack to force style invalidation in Chrome
zoom: (scale || 1).toFixed(3),
}}
onPointerDown={this.onPointerDown}
onPointerMove={this.onPointerMove}
onPointerUp={this.onPointerUp}
>
<defs>
{/*
<clipPath id="bg">
<rect x={x} y={y} width={width} height={height} />
</clipPath>
*/}
{/*
<filter id="shadow" x="-2" y="-2" width="4" height="4">
<feDropShadow
dx="0"
dy="0.5"
stdDeviation="1.5"
flood-color="#000"
/>
</filter>
<filter id="shadow2" x="-2" y="-2" width="4" height="4">
<feDropShadow
dx="0"
dy="0.25"
stdDeviation="0.5"
flood-color="rgba(0,0,0,0.5)"
/>
</filter>
*/}
</defs>
<rect
class={style.background}
width={size.width}
height={size.height}
// mask="url(#bg)"
// clip-path="url(#bg)"
// style={{
// clipPath: `polygon(0 0, 0 100%, 100% 100%, 100% 0, 0 0, ${x}px ${y}px, ${x+width}px ${y}px, ${x+width}px ${y+height}px, ${x}px ${y+height}px, ${x}px ${y}px)`
// }}
clip-path={`polygon(0 0, 0 100%, 100% 100%, 100% 0, 0 0, ${x}px ${y}px, ${
x + width
}px ${y}px, ${x + width}px ${y + height}px, ${x}px ${
y + height
}px, ${x}px ${y}px)`}
/>
<svg x={x} y={y} width={width} height={height}>
<Freezer>
<rect
id="box"
class={style.cropbox}
data-edge="left,right,top,bottom"
width="100%"
height="100%"
// filter="url(#shadow2)"
/>
<rect class={style.edge} data-edge="top" width="100%" />
<rect class={style.edge} data-edge="bottom" width="100%" y="100%" />
<rect class={style.edge} data-edge="left" height="100%" />
<rect class={style.edge} data-edge="right" height="100%" x="100%" />
<circle
class={style.corner}
data-edge="left,top"
// filter="url(#shadow)"
/>
<circle
class={style.corner}
data-edge="right,top"
cx="100%"
// filter="url(#shadow)"
/>
<circle
class={style.corner}
data-edge="right,bottom"
cx="100%"
cy="100%"
// filter="url(#shadow)"
/>
<circle
class={style.corner}
data-edge="left,bottom"
cy="100%"
// filter="url(#shadow)"
/>
</Freezer>
</svg>
{/*
<rect
id="box"
class={style.cropbox}
data-edge="left,right,top,bottom"
x={x}
y={y}
width={width}
height={height}
/>
<rect
class={`${style.edge} ${style.top}`}
data-edge="top"
x={x}
y={y}
width={width}
/>
<rect
class={`${style.edge} ${style.bottom}`}
data-edge="bottom"
x={x}
y={size.height - crop.bottom}
width={width}
/>
<rect
class={`${style.edge} ${style.left}`}
data-edge="left"
x={x}
y={y}
height={height}
/>
<rect
class={`${style.edge} ${style.right}`}
data-edge="right"
x={size.width - crop.right}
y={y}
height={height}
/>
*/}
</svg>
);
}
}
interface FreezerProps {
children: ComponentChildren;
}
class Freezer extends Component<FreezerProps> {
shouldComponentUpdate() {
return false;
}
render({ children }: FreezerProps) {
return children;
}
}

View File

@@ -1,119 +0,0 @@
.cropper {
position: absolute;
left: calc(-10px / var(--scale, 1));
top: calc(-10px / var(--scale, 1));
right: calc(-10px / var(--scale, 1));
bottom: calc(-10px / var(--scale, 1));
shape-rendering: crispedges;
overflow: hidden;
contain: strict;
transform-origin: 0 0;
transform: scale(calc(1 / var(--scale))) !important;
zoom: var(--scale, 1);
&.pan {
cursor: grabbing;
& * {
pointer-events: none;
}
}
& > svg {
margin: -10px;
padding: 10px;
overflow: visible;
contain: strict;
/* overflow: visible; */
}
}
.background {
pointer-events: none;
fill: rgba(0, 0, 0, 0.25);
}
.cropbox {
fill: none;
stroke: white;
stroke-width: calc(1.5 / var(--scale, 1));
stroke-dasharray: calc(5 / var(--scale, 1)), calc(5 / var(--scale, 1));
stroke-dashoffset: 50%;
/* Accept pointer input even though this is unpainted transparent */
pointer-events: all;
cursor: move;
/* animation: ants 1s linear forwards infinite; */
}
/*
@keyframes ants {
0% { stroke-dashoffset: 0; }
100% { stroke-dashoffset: -12; }
}
*/
.edge {
fill: #aaa;
opacity: 0;
transition: opacity 250ms ease;
z-index: 2;
pointer-events: all;
--edge-width: calc(10px / var(--scale, 1));
@media (max-width: 779px) {
--edge-width: calc(20px / var(--scale, 1));
fill: rgba(0, 0, 0, 0.01);
}
&[data-edge='left'],
&[data-edge='right'] {
cursor: ew-resize;
transform: translate(calc(var(--edge-width, 10px) / -2), 0);
width: var(--edge-width, 10px);
}
&[data-edge='top'],
&[data-edge='bottom'] {
cursor: ns-resize;
transform: translate(0, calc(var(--edge-width, 10px) / -2));
height: var(--edge-width, 10px);
}
&:hover,
&:active {
opacity: 0.1;
transition: none;
}
}
.corner {
r: calc(4 / var(--scale, 1));
stroke-width: calc(4 / var(--scale, 1));
stroke: rgba(225, 225, 225, 0.01);
fill: white;
shape-rendering: geometricprecision;
pointer-events: all;
transition: fill 250ms ease, stroke 250ms ease;
&:hover,
&:active {
stroke: rgba(225, 225, 225, 0.5);
transition: none;
}
@media (max-width: 779px) {
r: calc(10 / var(--scale, 1));
stroke-width: calc(2 / var(--scale, 1));
}
&[data-edge='left,top'] {
cursor: nw-resize;
}
&[data-edge='right,top'] {
cursor: ne-resize;
}
&[data-edge='right,bottom'] {
cursor: se-resize;
}
&[data-edge='left,bottom'] {
cursor: sw-resize;
}
}

View File

@@ -1,617 +0,0 @@
import {
h,
Component,
Fragment,
createRef,
FunctionComponent,
ComponentChildren,
} from 'preact';
import type {
default as PinchZoom,
ScaleToOpts,
} from '../Output/custom-els/PinchZoom';
import '../Output/custom-els/PinchZoom';
import * as style from './style.css';
import 'add-css:./style.css';
import {
AddIcon,
CheckmarkIcon,
CompareIcon,
FlipHorizontallyIcon,
FlipVerticallyIcon,
RemoveIcon,
RotateClockwiseIcon,
RotateCounterClockwiseIcon,
SwapIcon,
} from '../../icons';
import { cleanSet } from '../../util/clean-modify';
import type { SourceImage } from '../../Compress';
import { PreprocessorState } from 'client/lazy-app/feature-meta';
import Cropper, { CropBox } from './Cropper';
import CanvasImage from '../CanvasImage';
import Expander from '../Options/Expander';
import Select from '../Options/Select';
import Checkbox from '../Options/Checkbox';
const ROTATE_ORIENTATIONS = [0, 90, 180, 270] as const;
const cropPresets = {
square: {
name: 'Square',
ratio: 1,
},
'4:3': {
name: '4:3',
ratio: 4 / 3,
},
'16:9': {
name: '16:9',
ratio: 16 / 9,
},
'16:10': {
name: '16:10',
ratio: 16 / 10,
},
};
type CropPresetId = keyof typeof cropPresets;
interface Props {
source: SourceImage;
preprocessorState: PreprocessorState;
mobileView: boolean;
onCancel?(): void;
onSave?(e: { preprocessorState: PreprocessorState }): void;
}
interface State {
scale: number;
editingScale: boolean;
rotate: typeof ROTATE_ORIENTATIONS[number];
// crop: false | CropBox;
crop: CropBox;
cropPreset: keyof typeof cropPresets | undefined;
lockAspect: boolean;
flip: PreprocessorState['flip'];
}
const scaleToOpts: ScaleToOpts = {
originX: '50%',
originY: '50%',
relativeTo: 'container',
allowChangeEvent: true,
};
export default class Transform extends Component<Props, State> {
state: State = {
scale: 1,
editingScale: false,
cropPreset: undefined,
lockAspect: false,
...this.fromPreprocessorState(this.props.preprocessorState),
};
pinchZoom = createRef<PinchZoom>();
scaleInput = createRef<HTMLInputElement>();
// static getDerivedStateFromProps({ source, preprocessorState }: Props) {
// return {
// rotate: preprocessorState.rotate.rotate || 0,
// crop: preprocessorState.crop || false,
// flip: preprocessorState.flip || { horizontal: false, vertical: false },
// };
// }
componentWillReceiveProps(
{ source, preprocessorState }: Props,
{ crop, cropPreset }: State,
) {
if (preprocessorState !== this.props.preprocessorState) {
this.setState(this.fromPreprocessorState(preprocessorState));
}
const { width, height } = source.decoded;
const cropWidth = width - crop.left - crop.right;
const cropHeight = height - crop.top - crop.bottom;
for (const [id, preset] of Object.entries(cropPresets)) {
if (cropHeight * preset.ratio === cropWidth) {
if (cropPreset !== id) {
this.setState({ cropPreset: id as CropPresetId });
}
break;
}
}
}
private fromPreprocessorState(preprocessorState?: PreprocessorState) {
const state: Pick<State, 'rotate' | 'crop' | 'flip'> = {
rotate: preprocessorState ? preprocessorState.rotate.rotate : 0,
crop: Object.assign(
{
left: 0,
right: 0,
top: 0,
bottom: 0,
},
(preprocessorState && preprocessorState.crop) || {},
),
flip: Object.assign(
{
horizontal: false,
vertical: false,
},
(preprocessorState && preprocessorState.flip) || {},
),
};
return state;
}
private save = () => {
const { preprocessorState, onSave } = this.props;
const { rotate, crop, flip } = this.state;
let newState = cleanSet(preprocessorState, 'rotate.rotate', rotate);
newState = cleanSet(newState, 'crop', crop);
newState = cleanSet(newState, 'flip', flip);
console.log('u', JSON.parse(JSON.stringify(newState)));
if (onSave) onSave({ preprocessorState: newState });
};
private cancel = () => {
const { onCancel, onSave } = this.props;
if (onCancel) onCancel();
else if (onSave)
onSave({ preprocessorState: this.props.preprocessorState });
};
// private fitToViewport = () => {
// const pinchZoom = this.pinchZoom.current;
// const img = this.props.source?.preprocessed;
// if (!img || !pinchZoom) return;
// const scale = Number(Math.min(
// (window.innerWidth - 20) / img.width,
// (window.innerHeight - 20) / img.height
// ).toFixed(2));
// pinchZoom.scaleTo(Number(scale.toFixed(2)), { allowChangeEvent: true });
// this.recenter();
// };
// private recenter = () => {
// const pinchZoom = this.pinchZoom.current;
// const img = this.props.source?.preprocessed;
// if (!img || !pinchZoom) return;
// pinchZoom.setTransform({
// x: (img.width - img.width * pinchZoom.scale) / 2,
// y: (img.height - img.height * pinchZoom.scale) / 2,
// allowChangeEvent: true
// });
// };
private zoomIn = () => {
if (!this.pinchZoom.current) throw Error('Missing pinch-zoom element');
this.pinchZoom.current.scaleTo(this.state.scale * 1.25, scaleToOpts);
};
private zoomOut = () => {
if (!this.pinchZoom.current) throw Error('Missing pinch-zoom element');
this.pinchZoom.current.scaleTo(this.state.scale / 1.25, scaleToOpts);
};
private onScaleValueFocus = () => {
this.setState({ editingScale: true }, () => {
if (this.scaleInput.current) {
// Firefox unfocuses the input straight away unless I force a style
// calculation here. I have no idea why, but it's late and I'm quite
// tired.
getComputedStyle(this.scaleInput.current).transform;
this.scaleInput.current.focus();
}
});
};
private onScaleInputBlur = () => {
this.setState({ editingScale: false });
};
private onScaleInputChanged = (event: Event) => {
const target = event.target as HTMLInputElement;
const percent = parseFloat(target.value);
if (isNaN(percent)) return;
if (!this.pinchZoom.current) throw Error('Missing pinch-zoom element');
this.pinchZoom.current.scaleTo(percent / 100, scaleToOpts);
};
private onPinchZoomChange = () => {
if (!this.pinchZoom.current) throw Error('Missing pinch-zoom element');
this.setState({
scale: this.pinchZoom.current.scale,
});
};
private onCropChange = (crop: CropBox) => {
this.setState({ crop });
};
private onCropPresetChange = (event: Event) => {
const { value } = event.target as HTMLSelectElement;
// @ts-ignore-next
const cropPreset = cropPresets[value];
this.setState({
cropPreset,
lockAspect: true,
});
};
private swapCropDimensions = () => {
const { width, height } = this.props.source.decoded;
let { left, right, top, bottom } = this.state.crop;
const cropWidth = width - left - right;
const cropHeight = height - top - bottom;
const centerX = left - right;
const centerY = top - bottom;
const crop = {
top: (width - cropWidth) / 2 + centerY / 2,
bottom: (width - cropWidth) / 2 - centerY / 2,
left: (height - cropHeight) / 2 + centerX / 2,
right: (height - cropHeight) / 2 - centerX / 2,
};
this.setCrop(crop);
};
private setCrop(crop: CropBox) {
if (crop.top < 0) {
crop.bottom += crop.top;
crop.top = 0;
}
if (crop.bottom < 0) {
crop.top += crop.bottom;
crop.bottom = 0;
}
if (crop.left < 0) {
crop.right += crop.left;
crop.left = 0;
}
if (crop.right < 0) {
crop.left += crop.right;
crop.right = 0;
}
this.setState({ crop });
}
// yeah these could just += 90
private rotateClockwise = () => {
let { rotate, crop } = this.state;
this.setState({
rotate: ((rotate + 90) % 360) as typeof ROTATE_ORIENTATIONS[number],
crop: {
top: crop.left,
left: crop.bottom,
bottom: crop.right,
right: crop.top,
},
});
};
private rotateCounterClockwise = () => {
let { rotate, crop } = this.state;
this.setState({
rotate: (rotate
? rotate - 90
: 270) as typeof ROTATE_ORIENTATIONS[number],
crop: {
top: crop.right,
right: crop.bottom,
bottom: crop.left,
left: crop.top,
},
});
};
private flipHorizontally = () => {
const { horizontal, vertical } = this.state.flip;
this.setState({ flip: { horizontal: !horizontal, vertical } });
};
private flipVertically = () => {
const { horizontal, vertical } = this.state.flip;
this.setState({ flip: { horizontal, vertical: !vertical } });
};
// private update = (event: Event) => {
// const { name, value } = event.target as HTMLInputElement;
// const state = cleanSet(this.state, name, value);
// this.setState(state);
// };
private toggleLockAspect = () => {
this.setState({ lockAspect: !this.state.lockAspect });
};
private setCropWidth = (
event: preact.JSX.TargetedEvent<HTMLInputElement, Event>,
) => {
const { width, height } = this.props.source.decoded;
const newWidth = Math.min(width, parseInt(event.currentTarget.value, 10));
let { top, right, bottom, left } = this.state.crop;
const aspect = (width - left - right) / (height - top - bottom);
right = width - newWidth - left;
if (this.state.lockAspect) {
const newHeight = newWidth / aspect;
if (newHeight > height) return;
bottom = height - newHeight - top;
}
this.setCrop({ top, right, bottom, left });
};
private setCropHeight = (
event: preact.JSX.TargetedEvent<HTMLInputElement, Event>,
) => {
const { width, height } = this.props.source.decoded;
const newHeight = Math.min(height, parseInt(event.currentTarget.value, 10));
let { top, right, bottom, left } = this.state.crop;
const aspect = (width - left - right) / (height - top - bottom);
bottom = height - newHeight - top;
if (this.state.lockAspect) {
const newWidth = newHeight * aspect;
if (newWidth > width) return;
right = width - newWidth - left;
}
this.setCrop({ top, right, bottom, left });
};
// private onRotateClick = () => {
// const { preprocessorState: inputProcessorState } = this.props;
// if (!inputProcessorState) return;
// const newState = cleanSet(
// inputProcessorState,
// 'rotate.rotate',
// (inputProcessorState.rotate.rotate + 90) % 360,
// );
// this.props.onPreprocessorChange(newState);
// };
render(
{ mobileView, source }: Props,
{ scale, editingScale, rotate, flip, crop, cropPreset, lockAspect }: State,
) {
const image = source.decoded;
const width = source.decoded.width - crop.left - crop.right;
const height = source.decoded.height - crop.top - crop.bottom;
let transform =
`rotate(${rotate}deg) ` +
`scale(${flip.horizontal ? -1 : 1}, ${flip.vertical ? -1 : 1})`;
return (
<Fragment>
<CancelButton onClick={this.cancel} />
<SaveButton onClick={this.save} />
<div class={style.transform}>
<pinch-zoom
class={style.pinchZoom}
onChange={this.onPinchZoomChange}
ref={this.pinchZoom}
>
{/* <Backdrop width={image.width} height={image.height} /> */}
<div
class={style.wrap}
style={{
width: image.width,
height: image.height,
}}
>
<CanvasImage
class={style.pinchTarget}
image={image}
style={{ transform }}
/>
{crop && (
<Cropper
size={{ width: image.width, height: image.height }}
scale={scale}
lockAspect={lockAspect}
crop={crop}
onChange={this.onCropChange}
/>
)}
</div>
</pinch-zoom>
</div>
<div class={style.controls}>
<div class={style.zoomControls}>
<button class={style.button} onClick={this.zoomOut}>
<RemoveIcon />
</button>
{editingScale ? (
<input
type="number"
step="1"
min="1"
max="1000000"
ref={this.scaleInput}
class={style.zoom}
value={Math.round(scale * 100)}
onInput={this.onScaleInputChanged}
onBlur={this.onScaleInputBlur}
/>
) : (
<span
class={style.zoom}
tabIndex={0}
onFocus={this.onScaleValueFocus}
>
<span class={style.zoomValue}>{Math.round(scale * 100)}</span>%
</span>
)}
<button class={style.button} onClick={this.zoomIn}>
<AddIcon />
</button>
</div>
</div>
<div class={style.options}>
<h3 class={style.optionsTitle}>Modify Source</h3>
<div class={style.optionsSection}>
<h4 class={style.optionsSectionTitle}>Crop</h4>
<div class={style.optionOneCell}>
<Select
large
value={cropPreset}
onChange={this.onCropPresetChange}
>
<option value="">Custom</option>
{Object.entries(cropPresets).map(([type, preset]) => (
<option value={type}>{preset.name}</option>
))}
</Select>
</div>
<label class={style.optionCheckbox}>
<Checkbox checked={lockAspect} onClick={this.toggleLockAspect} />
Lock aspect-ratio
</label>
<div class={style.optionsDimensions}>
<input
type="number"
name="width"
value={width}
title="Crop width"
onInput={this.setCropWidth}
/>
<button
class={style.optionsButton}
title="swap"
onClick={this.swapCropDimensions}
>
<SwapIcon />
</button>
<input
type="number"
name="height"
value={height}
title="Crop height"
onInput={this.setCropHeight}
/>
</div>
<div class={style.optionButtonRow}>
Flip
<button
class={style.optionsButton}
data-active={flip.vertical}
title="Flip vertically"
onClick={this.flipVertically}
>
<FlipVerticallyIcon />
</button>
<button
class={style.optionsButton}
data-active={flip.horizontal}
title="Flip horizontally"
onClick={this.flipHorizontally}
>
<FlipHorizontallyIcon />
</button>
</div>
<div class={style.optionButtonRow}>
Rotate
<button
class={style.optionsButton}
title="Rotate clockwise"
onClick={this.rotateClockwise}
>
<RotateClockwiseIcon />
</button>
<button
class={style.optionsButton}
title="Rotate counter-clockwise"
onClick={this.rotateCounterClockwise}
>
<RotateCounterClockwiseIcon />
</button>
</div>
</div>
</div>
</Fragment>
);
}
}
const CancelButton = ({ onClick }: { onClick: () => void }) => (
<button class={style.cancel} onClick={onClick}>
<svg viewBox="0 0 80 80" width="80" height="80">
<path d="M8.06 40.98c-.53-7.1 4.05-14.52 9.98-19.1s13.32-6.35 22.13-6.43c8.84-.12 19.12 1.51 24.4 7.97s5.6 17.74 1.68 26.97c-3.89 9.26-11.97 16.45-20.46 18-8.43 1.55-17.28-2.62-24.5-8.08S8.54 48.08 8.07 40.98z" />
</svg>
<CompareIcon class={style.icon} />
<span>Cancel</span>
</button>
);
const SaveButton = ({ onClick }: { onClick: () => void }) => (
<button class={style.save} onClick={onClick}>
<svg viewBox="0 0 89 87" width="89" height="87">
<path
fill="#0c99ff"
opacity=".7"
d="M27.3 71.9c-8-4-15.6-12.3-16.9-21-1.2-8.7 4-17.8 10.5-26s14.4-15.6 24-16 21.2 6 28.6 16.5c7.4 10.5 10.8 25 6.6 34S64.1 71.7 54 73.5c-10.2 2-18.7 2.3-26.7-1.6z"
/>
<path
fill="#0c99ff"
opacity=".7"
d="M14.6 24.8c4.3-7.8 13-15 21.8-15.7 8.7-.8 17.5 4.8 25.4 11.8 7.8 6.9 14.8 15.2 14.8 24.9s-7.2 20.7-18 27.6c-10.9 6.8-25.6 9.5-34.3 4.8S13 61.6 11.6 51.4c-1.3-10.3-1.3-18.8 3-26.6z"
/>
</svg>
<CheckmarkIcon class={style.icon} />
</button>
);
interface BackdropProps {
width: number;
height: number;
}
class Backdrop extends Component<BackdropProps> {
shouldComponentUpdate({ width, height }: BackdropProps) {
return width !== this.props.width || height !== this.props.height;
}
/** @TODO this could at least use clip-path */
render({ width, height }: BackdropProps) {
const transform =
`transform-origin: 50% 50%; transform: translate(var(--x), var(--y)) ` +
`translate(-${width / 2}px, -${height / 2}px) ` +
`scale(calc(var(--scale, 1) * 0.99999));`;
return (
<svg
class={style.backdrop}
preserveAspectRatio="xMidYMid meet"
width="100%"
height="100%"
shape-rendering="optimizeSpeed"
>
<mask id="bgmask">
<rect width="100%" height="100%" fill="white" />
<rect
style={transform}
width={width}
height={height}
x="50%"
y="50%"
fill="black"
/>
</mask>
<rect
class={style.backdropArea}
width="100%"
height="100%"
mask="url(#bgmask)"
/>
</svg>
);
}
}

View File

@@ -1,351 +0,0 @@
.transform {
display: block;
}
.wrap {
overflow: visible;
/*
& > canvas {
transition: transform 150ms ease;
}
*/
}
.backdrop {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
transform: none !important;
will-change: initial !important;
contain: strict;
& * {
contain: strict;
}
/* background: rgba(255, 0, 0, 0.5); */
}
.backdropArea {
fill: rgba(0, 0, 0, 0.25);
}
.pinch-zoom {
composes: abs-fill from global;
outline: none;
display: flex;
justify-content: center;
align-items: center;
}
.pinch-target {
/* This fixes a severe painting bug in Chrome.
* We should try to remove this once the issue is fixed.
* https://bugs.chromium.org/p/chromium/issues/detail?id=870222#c10 */
will-change: auto;
/* Prevent the image becoming misshapen due to default flexbox layout. */
flex-shrink: 0;
}
.cancel,
.save {
composes: unbutton from global;
position: absolute;
padding: 0;
z-index: 2;
}
.save {
position: absolute;
right: 0;
bottom: 0;
display: grid;
align-items: center;
justify-items: center;
& > * {
grid-area: 1/1/1/1;
fill: #fff;
}
}
/* @TODO use grid */
.cancel {
fill: rgba(0, 0, 0, 0.4);
& > svg:not(.icon) {
display: block;
margin: -8px 0;
width: 80px;
height: 80px;
}
& > .icon {
position: absolute;
left: 28px;
top: 22px;
fill: #fff;
}
& > span {
display: inline-block;
padding: 4px 10px;
border-radius: 1rem;
background: rgba(0, 0, 0, 0.4);
font-size: 80%;
color: #fff;
}
&:hover,
&:focus {
fill: rgba(0, 0, 0, 0.9);
& > span {
background: rgba(0, 0, 0, 0.9);
}
}
}
.options {
position: fixed;
right: 0;
bottom: 78px;
color: #fff;
font-size: 1.2rem;
display: flex;
flex-flow: column;
max-width: 250px;
margin: 0;
width: calc(100% - 60px);
max-height: 100%;
overflow: hidden;
align-self: end;
border-radius: var(--options-radius) 0 0 var(--options-radius);
animation: slideInFromRight 500ms ease-out forwards 1;
--horizontal-padding: 15px;
--main-theme-color: var(--blue);
}
@keyframes slideInFromRight {
0% {
transform: translateX(100%);
}
}
.options-title {
background-color: var(--main-theme-color);
color: var(--dark-text);
margin: 0;
padding: 10px var(--horizontal-padding);
font-weight: bold;
font-size: 1.4rem;
border-bottom: 1px solid var(--off-black);
}
.options-section {
padding: 5px 0;
background: var(--off-black);
}
.options-section-title {
font: inherit;
margin: 0;
padding: 5px var(--horizontal-padding);
}
.option-base {
display: grid;
gap: 0.7em;
align-items: center;
padding: 5px var(--horizontal-padding);
}
.options-button {
composes: unbutton from global;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
border: 1px solid var(--dark-gray);
color: var(--white);
&:hover,
&:focus {
background-color: var(--off-black);
border-color: var(--med-gray);
}
&[data-active] {
background-color: var(--dark-gray);
border-color: var(--med-gray);
}
}
.options-dimensions {
composes: option-base;
grid-template-columns: 1fr 0fr 1fr;
input {
background: var(--white);
color: var(--black);
font: inherit;
border: none;
width: 100%;
padding: 4px;
box-sizing: border-box;
border-radius: 4px;
}
}
.option-one-cell {
composes: option-base;
grid-template-columns: 1fr;
}
.option-button-row {
composes: option-base;
grid-template-columns: 1fr auto auto;
}
.option-checkbox {
composes: option-base;
grid-template-columns: auto 1fr;
}
/** Zoom controls */
.controls {
position: absolute;
display: flex;
justify-content: center;
top: 0;
left: 0;
right: 0;
padding: 9px 84px;
flex-wrap: wrap;
/* Allow clicks to fall through to the pinch zoom area */
pointer-events: none;
& > * {
pointer-events: auto;
}
@media (min-width: 860px) {
padding: 9px;
top: auto;
left: 320px;
right: 320px;
bottom: 0;
flex-wrap: wrap-reverse;
}
}
.zoom-controls {
display: flex;
position: relative;
z-index: 100;
& > :not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
margin-left: 0;
}
& > :not(:last-child) {
margin-right: 0;
border-right-width: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
.button,
.zoom {
display: flex;
align-items: center;
box-sizing: border-box;
margin: 4px;
background-color: rgba(29, 29, 29, 0.92);
border: 1px solid rgba(0, 0, 0, 0.67);
border-radius: 6px;
line-height: 1.1;
white-space: nowrap;
height: 39px;
padding: 0 8px;
font-size: 1.2rem;
cursor: pointer;
/*
@media (min-width: 600px) {
height: 39px;
padding: 0 16px;
}
*/
&:focus {
/* box-shadow: 0 0 0 2px var(--hot-pink); */
box-shadow: 0 0 0 2px #fff;
outline: none;
z-index: 1;
}
}
.button {
color: #fff;
&:hover {
background: rgba(50, 50, 50, 0.92);
}
&.active {
background: rgba(72, 72, 72, 0.92);
color: #fff;
}
}
.zoom {
cursor: text;
width: 7rem;
font: inherit;
text-align: center;
justify-content: center;
&:focus {
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 2px #fff;
}
}
span.zoom {
color: #939393;
font-size: 0.8rem;
line-height: 1.2;
font-weight: 100;
}
input.zoom {
font-size: 1.2rem;
letter-spacing: 0.05rem;
font-weight: 700;
text-indent: 3px;
color: #fff;
}
.zoom-value {
margin: 0 3px 0 0;
padding: 0 2px;
font-size: 1.2rem;
letter-spacing: 0.05rem;
font-weight: 700;
color: #fff;
border-bottom: 1px dashed #999;
}
.buttons-no-wrap {
display: flex;
pointer-events: none;
& > * {
pointer-events: auto;
}
}

View File

@@ -30,9 +30,8 @@ import './custom-els/MultiPanel';
import Results from './Results';
import WorkerBridge from '../worker-bridge';
import { resize } from 'features/processors/resize/client';
import type SnackBarElement from 'shared/custom-els/snack-bar';
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
import { CopyAcrossIconProps, ExpandIcon } from '../icons';
import Transform from './Transform';
export type OutputType = EncoderType | 'identity';
@@ -69,11 +68,8 @@ interface State {
sides: [Side, Side];
/** Source image load */
loading: boolean;
/** Showing preprocessor transformations modal */
transform: boolean;
error?: string;
mobileView: boolean;
altBackground: boolean;
preprocessorState: PreprocessorState;
encodedPreprocessorState?: PreprocessorState;
}
@@ -129,18 +125,13 @@ async function preprocessImage(
): Promise<ImageData> {
assertSignal(signal);
let processedData = data;
const { rotate, flip, crop } = preprocessorState;
if (rotate.rotate !== 0) {
processedData = await workerBridge.rotate(signal, processedData, rotate);
}
if (flip && (flip.horizontal || flip.vertical)) {
processedData = await workerBridge.flip(signal, processedData, flip);
}
if (crop && (crop.left || crop.top || crop.right || crop.bottom)) {
processedData = await workerBridge.crop(signal, processedData, crop);
if (preprocessorState.rotate.rotate !== 0) {
processedData = await workerBridge.rotate(
signal,
processedData,
preprocessorState.rotate,
);
}
return processedData;
@@ -282,9 +273,6 @@ export default class Compress extends Component<Props, State> {
state: State = {
source: undefined,
loading: false,
/** @TODO remove this */
// transform: true,
transform: false,
preprocessorState: defaultPreprocessorState,
sides: [
{
@@ -306,7 +294,6 @@ export default class Compress extends Component<Props, State> {
},
],
mobileView: this.widthQuery.matches,
altBackground: false,
};
private readonly encodeCache = new ResultCache();
@@ -332,13 +319,7 @@ export default class Compress extends Component<Props, State> {
this.setState({ mobileView: this.widthQuery.matches });
};
private toggleBackground = () => {
this.setState({
altBackground: !this.state.altBackground,
});
};
private onEncoderTypeChange = (index: 0 | 1, newType: OutputType): void => {
private onEncoderTypeChange(index: 0 | 1, newType: OutputType): void {
this.setState({
sides: cleanSet(
this.state.sides,
@@ -351,12 +332,12 @@ export default class Compress extends Component<Props, State> {
},
),
});
};
}
private onProcessorOptionsChange = (
private onProcessorOptionsChange(
index: 0 | 1,
options: ProcessorState,
): void => {
): void {
this.setState({
sides: cleanSet(
this.state.sides,
@@ -364,12 +345,9 @@ export default class Compress extends Component<Props, State> {
options,
),
});
};
}
private onEncoderOptionsChange = (
index: 0 | 1,
options: EncoderOptions,
): void => {
private onEncoderOptionsChange(index: 0 | 1, options: EncoderOptions): void {
this.setState({
sides: cleanSet(
this.state.sides,
@@ -377,21 +355,7 @@ export default class Compress extends Component<Props, State> {
options,
),
});
};
private showPreprocessorTransforms = () => {
this.setState({ transform: true });
};
private onTransformUpdated = ({
preprocessorState,
}: { preprocessorState?: PreprocessorState } = {}) => {
console.log('onTransformUpdated', preprocessorState);
if (preprocessorState) {
this.onPreprocessorChange(preprocessorState);
}
this.setState({ transform: false });
};
}
componentWillReceiveProps(nextProps: Props): void {
if (nextProps.file !== this.props.file) {
@@ -823,31 +787,29 @@ export default class Compress extends Component<Props, State> {
render(
{ onBack }: Props,
{
loading,
sides,
source,
mobileView,
altBackground,
transform,
preprocessorState,
}: State,
{ loading, sides, source, mobileView, preprocessorState }: State,
) {
const [leftSide, rightSide] = sides;
const [leftImageData, rightImageData] = sides.map((i) => i.data);
transform = (source && source.decoded && transform) || false;
const options = sides.map((side, index) => (
<Options
index={index as 0 | 1}
source={source}
mobileView={mobileView}
processorState={side.latestSettings.processorState}
encoderState={side.latestSettings.encoderState}
onEncoderTypeChange={this.onEncoderTypeChange}
onEncoderOptionsChange={this.onEncoderOptionsChange}
onProcessorOptionsChange={this.onProcessorOptionsChange}
onEncoderTypeChange={this.onEncoderTypeChange.bind(
this,
index as 0 | 1,
)}
onEncoderOptionsChange={this.onEncoderOptionsChange.bind(
this,
index as 0 | 1,
)}
onProcessorOptionsChange={this.onProcessorOptionsChange.bind(
this,
index as 0 | 1,
)}
/>
));
@@ -892,68 +854,39 @@ export default class Compress extends Component<Props, State> {
rightDisplaySettings.processorState.resize.fitMethod === 'contain';
return (
<div
class={`${style.compress} ${transform ? style.transforming : ''} ${
altBackground ? style.altBackground : ''
}`}
>
<div class={style.compress}>
<Output
hidden={transform}
source={source}
mobileView={mobileView}
leftCompressed={leftImageData}
rightCompressed={rightImageData}
leftImgContain={leftImgContain}
rightImgContain={rightImgContain}
onBack={onBack}
preprocessorState={preprocessorState}
onPreprocessorChange={this.onPreprocessorChange}
onShowPreprocessorTransforms={this.showPreprocessorTransforms}
onToggleBackground={this.toggleBackground}
/>
<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 ? (
<div class={style.options}>
<multi-panel class={style.multiPanel} open-one-only>
{results[0]}
<div class={style.options1Theme}>{options[0]}</div>
{options[0]}
{results[1]}
<div class={style.options2Theme}>{options[1]}</div>
{options[1]}
</multi-panel>
</div>
) : (
[
<div class={style.options1} key="options1">
<div class={style.options} key="options0">
{options[0]}
{results[0]}
</div>,
<div class={style.options2} key="options2">
<div class={style.options} key="options1">
{options[1]}
{results[1]}
</div>,
]
)}
{transform && (
<Transform
mobileView={mobileView}
source={source!}
preprocessorState={preprocessorState!}
onSave={this.onTransformUpdated}
onCancel={this.onTransformUpdated}
/>
)}
</div>
);
}

View File

@@ -3,110 +3,39 @@
height: 100%;
contain: strict;
display: grid;
grid-template-rows: max-content 1fr;
grid-template-areas:
'header'
'opts';
--options-radius: 7px;
align-items: end;
align-content: end;
grid-template-rows: 1fr auto;
@media (min-width: 600px) {
grid-template-rows: max-content 1fr;
grid-template-columns: max-content 1fr max-content;
grid-template-areas:
'header header header'
'optsLeft viewportOpts optsRight';
}
/* darker squares background */
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #000;
opacity: 0.8;
transition: opacity 500ms ease;
}
&.alt-background::before {
opacity: 0;
}
/* transformation is modal and we sweep away the comparison UI */
&.transforming {
& > .options {
transform: translateX(-100%);
}
& > .options + .options {
transform: translateX(100%);
}
& > .back {
display: none;
}
& > :first-child {
display: none;
}
grid-template-columns: 1fr auto;
grid-template-rows: 100%;
}
}
.options {
position: relative;
color: #fff;
opacity: 0.9;
font-size: 1.2rem;
display: flex;
flex-flow: column;
max-width: 400px;
margin: 0 auto;
width: calc(100% - 60px);
max-height: 100%;
max-height: calc(100% - 104px);
overflow: hidden;
align-self: end;
grid-area: opts;
transition: transform 500ms ease;
@media (min-width: 600px) {
max-height: calc(100% - 75px);
width: 300px;
margin: 0;
}
}
.options-1-theme {
--main-theme-color: var(--pink);
--header-text-color: var(--white);
--scroller-radius: var(--options-radius) var(--options-radius) 0 0;
@media (min-width: 600px) {
--scroller-radius: 0 var(--options-radius) var(--options-radius) 0;
@media (min-width: 860px) {
max-height: calc(100% - 40px);
}
}
.options-2-theme {
--main-theme-color: var(--blue);
--header-text-color: var(--dark-text);
--scroller-radius: var(--options-radius) var(--options-radius) 0 0;
@media (min-width: 600px) {
--scroller-radius: var(--options-radius) 0 0 var(--options-radius);
}
}
.options-1 {
composes: options;
composes: options-1-theme;
grid-area: optsLeft;
}
.options-2 {
composes: options;
composes: options-2-theme;
grid-area: optsRight;
}
.multi-panel {
position: relative;
display: flex;
@@ -144,33 +73,3 @@
:focus .expand-icon {
fill: #34b9eb;
}
.back {
composes: unbutton from global;
position: relative;
grid-area: header;
margin: 9px;
justify-self: start;
align-self: start;
& > svg {
width: 47px;
}
@media (min-width: 600px) {
margin: 14px;
& > svg {
width: 58px;
}
}
}
.back-blob {
fill: var(--hot-pink);
opacity: 0.77;
}
.back-x {
fill: var(--white);
}

View File

@@ -11,50 +11,6 @@ const Icon = (props: preact.JSX.HTMLAttributes) => (
/>
);
export const SwapIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<path d="M9.01 14H2v2h7.01v3L13 15l-3.99-4zm5.98-1v-3H22V8h-7.01V5L11 9z" />
</Icon>
);
export const FlipVerticallyIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<path d="M21 9V7h-2v2zM9 5V3H7v2zM5 21h14a2 2 0 002-2v-4h-2v4H5v-4H3v4a2 2 0 002 2zM3 5h2V3a2 2 0 00-2 2zm20 8v-2H1v2zm-6-8V3h-2v2zM5 9V7H3v2zm8-4V3h-2v2zm8 0a2 2 0 00-2-2v2z" />
</Icon>
);
export const FlipHorizontallyIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<path d="M15 21h2v-2h-2zm4-12h2V7h-2zM3 5v14a2 2 0 002 2h4v-2H5V5h4V3H5a2 2 0 00-2 2zm16-2v2h2a2 2 0 00-2-2zm-8 20h2V1h-2zm8-6h2v-2h-2zM15 5h2V3h-2zm4 8h2v-2h-2zm0 8a2 2 0 002-2h-2z" />
</Icon>
);
export const RotateClockwiseIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<path d="M16.05 5.34l-5.2-5.2v3.5a9.12 9.12 0 000 18.1v-2.3a6.84 6.84 0 010-13.5v4.47zm5 6.22a9.03 9.03 0 00-1.85-4.44l-1.62 1.62a6.63 6.63 0 011.16 2.82zm-7.91 7.87v2.31a9.05 9.05 0 004.45-1.84l-1.64-1.64a6.6 6.6 0 01-2.81 1.18zm4.44-2.76l1.62 1.61a9.03 9.03 0 001.85-4.44h-2.3a6.73 6.73 0 01-1.17 2.83z" />
</Icon>
);
export const RotateCounterClockwiseIcon = (
props: preact.JSX.HTMLAttributes,
) => (
<Icon {...props}>
<path d="M7.95 5.34l5.19-5.2v3.5a9.12 9.12 0 010 18.1v-2.3a6.84 6.84 0 000-13.5v4.47zm-5 6.22A9.03 9.03 0 014.8 7.12l1.62 1.62a6.63 6.63 0 00-1.17 2.82zm7.9 7.87v2.31A9.05 9.05 0 016.4 19.9l1.65-1.64a6.6 6.6 0 002.8 1.17zm-4.43-2.76L4.8 18.28a9.03 9.03 0 01-1.85-4.44h2.3a6.73 6.73 0 001.17 2.83z" />
</Icon>
);
export const CheckmarkIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<path d="M9.76 17.56l-4.55-4.55-1.52 1.52 6.07 6.08 13-13.02-1.51-1.52z" />
</Icon>
);
export const CompareIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<path d="M9.77 1.94h-5.6a2.24 2.24 0 00-2.22 2.25v15.65a2.24 2.24 0 002.24 2.23h5.59v2.24h2.23V-.31H9.78zm0 16.77h-5.6l5.6-6.7zM19.83 1.94h-5.6v2.25h5.6v14.53l-5.6-6.7v10.05h5.6a2.24 2.24 0 002.23-2.23V4.18a2.24 2.24 0 00-2.23-2.24z" />
</Icon>
);
export const DownloadIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<path d="M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-7h-2zm-6 .7l2.6-2.6 1.4 1.4-5 5-5-5 1.4-1.4 2.6 2.6V3h2z" />
@@ -81,14 +37,6 @@ export const RotateIcon = (props: preact.JSX.HTMLAttributes) => (
</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) => (
<Icon {...props}>
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
@@ -119,10 +67,10 @@ export const ExpandIcon = (props: preact.JSX.HTMLAttributes) => (
</Icon>
);
export const Arrow = () => (
<svg viewBox="0 -1.95 9.8 9.8">
<path d="M8.2.2a1 1 0 011.4 1.4l-4 4a1 1 0 01-1.4 0l-4-4A1 1 0 011.6.2l3.3 3.3L8.2.2z" />
</svg>
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 = {

View File

@@ -1,4 +1,4 @@
import type SnackBarElement from 'shared/custom-els/snack-bar';
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
import { get, set } from 'idb-keyval';

View File

@@ -11,7 +11,7 @@
* limitations under the License.
*/
/// <reference path="../../missing-types.d.ts" />
/// <reference path="../shared/prerendered-app/Intro/missing-types.d.ts" />
/// <reference path="../shared/initial-app/Intro/missing-types.d.ts" />
interface Navigator {
readonly standalone: boolean;
@@ -25,3 +25,23 @@ declare module 'service-worker:*' {
}
declare module 'preact/debug' {}
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;
};

14
src/copy/_headers Normal file
View File

@@ -0,0 +1,14 @@
/*
Cache-Control: no-cache
/c/*
Cache-Control: max-age=31536000
# COOP+COEP for WebAssembly threads.
/*
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
# Origin trial for WebAssembly SIMD.
# ATTENTION: This one is configured for dev--squoosh.netlify.app preview.
# For production squoosh.app, replace with AgoKiDqjr0GVPtrwV/vuVlrrSvbDa5Yb99s+q66ly816DrrAQ8Cdas33NgDtmhxM4BtDP9PEdyuxHPyTQHD5ZAcAAABUeyJvcmlnaW4iOiJodHRwczovL3NxdW9vc2guYXBwOjQ0MyIsImZlYXR1cmUiOiJXZWJBc3NlbWJseVNpbWQiLCJleHBpcnkiOjE2MDg2NzI5OTR9.
Origin-Trial: As3b1fXjclhF8ZgvUkIqOo3r1/Jqvx0mNuT6Ilgb7SdpeJnV8lUdYr7i+OKgCmcVTWkqjkF23LJ+xZ111VYMEQIAAABheyJvcmlnaW4iOiJodHRwczovL2Rldi0tc3F1b29zaC5uZXRsaWZ5LmFwcDo0NDMiLCJmZWF0dXJlIjoiV2ViQXNzZW1ibHlTaW1kIiwiZXhwaXJ5IjoxNjA5NDI4Nzk4fQ==

View File

@@ -1 +1,2 @@
/editor / 301
/index.html / 301
/* /index.html 301

View File

@@ -8,7 +8,6 @@ import Expander from 'client/lazy-app/Compress/Options/Expander';
import Select from 'client/lazy-app/Compress/Options/Select';
import Range from 'client/lazy-app/Compress/Options/Range';
import linkState from 'linkstate';
import Revealer from 'client/lazy-app/Compress/Options/Revealer';
export const encode = (
signal: AbortSignal,
@@ -210,12 +209,12 @@ export class Options extends Component<Props, State> {
) {
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<label class={style.optionToggle}>
Lossless
<label class={style.optionInputFirst}>
<Checkbox
checked={lossless}
onChange={this._inputChange('lossless', 'boolean')}
/>
Lossless
</label>
<Expander>
{!lossless && (
@@ -243,22 +242,22 @@ export class Options extends Component<Props, State> {
</div>
)}
</Expander>
<label class={style.optionToggle}>
Separate alpha quality
<label class={style.optionInputFirst}>
<Checkbox
checked={separateAlpha}
onChange={this._inputChange('separateAlpha', 'boolean')}
/>
Separate alpha quality
</label>
<Expander>
{separateAlpha && (
<div>
<label class={style.optionToggle}>
Lossless alpha
<label class={style.optionInputFirst}>
<Checkbox
checked={losslessAlpha}
onChange={this._inputChange('losslessAlpha', 'boolean')}
/>
Lossless alpha
</label>
<Expander>
{!losslessAlpha && (
@@ -289,23 +288,23 @@ export class Options extends Component<Props, State> {
</div>
)}
</Expander>
<label class={style.optionReveal}>
<Revealer
<label class={style.optionInputFirst}>
<Checkbox
checked={showAdvanced}
onChange={linkState(this, 'showAdvanced')}
/>
Advanced settings
Show advanced settings
</label>
<Expander>
{showAdvanced && (
<div>
{/*<label class={style.optionToggle}>
Grayscale
{/*<label class={style.optionInputFirst}>
<Checkbox
data-set-state="grayscale"
checked={grayscale}
onChange={this._inputChange('grayscale', 'boolean')}
/>
Grayscale
</label>*/}
<Expander>
{!grayscale && !lossless && (

View File

@@ -123,23 +123,23 @@ export class Options extends Component<Props, State> {
// gathering the data.
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<label class={style.optionToggle}>
Lossless
<label class={style.optionInputFirst}>
<Checkbox
name="lossless"
checked={lossless}
onChange={this._inputChange('lossless', 'boolean')}
/>
Lossless
</label>
<Expander>
{lossless && (
<label class={style.optionToggle}>
Slight loss
<label class={style.optionInputFirst}>
<Checkbox
name="slightLoss"
checked={slightLoss}
onChange={this._inputChange('slightLoss', 'boolean')}
/>
Slight loss
</label>
)}
</Expander>
@@ -157,8 +157,7 @@ export class Options extends Component<Props, State> {
Quality:
</Range>
</div>
<label class={style.optionToggle}>
Auto edge filter
<label class={style.optionInputFirst}>
<Checkbox
name="autoEdgeFilter"
checked={autoEdgePreservingFilter}
@@ -167,6 +166,7 @@ export class Options extends Component<Props, State> {
'boolean',
)}
/>
Auto edge filter
</label>
<Expander>
{!autoEdgePreservingFilter && (
@@ -188,13 +188,13 @@ export class Options extends Component<Props, State> {
</div>
)}
</Expander>
<label class={style.optionToggle}>
Progressive rendering
<label class={style.optionInputFirst}>
<Checkbox
name="progressive"
checked={progressive}
onChange={this._inputChange('progressive', 'boolean')}
/>
Progressive rendering
</label>
<div class={style.optionOneCell}>
<Range

View File

@@ -10,56 +10,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { JXLModule } from 'codecs/jxl/enc/jxl_enc';
import jxlEncoder, { JXLModule } from 'codecs/jxl/enc/jxl_enc';
import wasmUrl from 'url:codecs/jxl/enc/jxl_enc.wasm';
import { initEmscriptenModule } from 'features/worker-utils';
import type { EncodeOptions } from '../shared/meta';
import { initEmscriptenModule } from 'features/worker-utils';
import { threads, simd } from 'wasm-feature-detect';
import wasmUrl from 'url:codecs/jxl/enc/jxl_enc.wasm';
import wasmUrlWithMT from 'url:codecs/jxl/enc/jxl_enc_mt.wasm';
import workerUrl from 'omt:codecs/jxl/enc/jxl_enc_mt.worker.js';
import wasmUrlWithMTAndSIMD from 'url:codecs/jxl/enc/jxl_enc_mt_simd.wasm';
import workerUrlWithSIMD from 'omt:codecs/jxl/enc/jxl_enc_mt_simd.worker.js';
let emscriptenModule: Promise<JXLModule>;
async function init() {
if (await threads()) {
if (await simd()) {
const jxlEncoder = await import('codecs/jxl/enc/jxl_enc_mt_simd');
return initEmscriptenModule(
jxlEncoder.default,
wasmUrlWithMTAndSIMD,
workerUrlWithSIMD,
);
}
const jxlEncoder = await import('codecs/jxl/enc/jxl_enc_mt');
return initEmscriptenModule(
jxlEncoder.default,
wasmUrlWithMT,
workerUrl,
);
}
const jxlEncoder = await import('codecs/jxl/enc/jxl_enc');
return initEmscriptenModule(
jxlEncoder.default,
wasmUrl,
);
}
export default async function encode(
data: ImageData,
options: EncodeOptions,
): Promise<ArrayBuffer> {
if (!emscriptenModule) emscriptenModule = init();
if (!emscriptenModule) {
emscriptenModule = initEmscriptenModule(jxlEncoder, wasmUrl);
}
const module = await emscriptenModule;
const result = module.encode(data.data, data.width, data.height, options);
if (!result) throw new Error('Encoding error.');
return result.buffer;
// wasm cant run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
return result.buffer as ArrayBuffer;
}

View File

@@ -12,7 +12,6 @@ import Range from 'client/lazy-app/Compress/Options/Range';
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
import Expander from 'client/lazy-app/Compress/Options/Expander';
import Select from 'client/lazy-app/Compress/Options/Select';
import Revealer from 'client/lazy-app/Compress/Options/Revealer';
export function encode(
signal: AbortSignal,
@@ -117,12 +116,12 @@ export class Options extends Component<Props, State> {
Quality:
</Range>
</div>
<label class={style.optionReveal}>
<Revealer
<label class={style.optionInputFirst}>
<Checkbox
checked={showAdvanced}
onChange={linkState(this, 'showAdvanced')}
/>
Advanced settings
Show advanced settings
</label>
<Expander>
{showAdvanced ? (
@@ -142,13 +141,13 @@ export class Options extends Component<Props, State> {
<Expander>
{options.color_space === MozJpegColorSpace.YCbCr ? (
<div>
<label class={style.optionToggle}>
Auto subsample chroma
<label class={style.optionInputFirst}>
<Checkbox
name="auto_subsample"
checked={options.auto_subsample}
onChange={this.onChange}
/>
Auto subsample chroma
</label>
<Expander>
{options.auto_subsample ? null : (
@@ -165,13 +164,13 @@ export class Options extends Component<Props, State> {
</div>
)}
</Expander>
<label class={style.optionToggle}>
Separate chroma quality
<label class={style.optionInputFirst}>
<Checkbox
name="separate_chroma_quality"
checked={options.separate_chroma_quality}
onChange={this.onChange}
/>
Separate chroma quality
</label>
<Expander>
{options.separate_chroma_quality ? (
@@ -191,35 +190,35 @@ export class Options extends Component<Props, State> {
</div>
) : null}
</Expander>
<label class={style.optionToggle}>
Pointless spec compliance
<label class={style.optionInputFirst}>
<Checkbox
name="baseline"
checked={options.baseline}
onChange={this.onChange}
/>
Pointless spec compliance
</label>
<Expander>
{options.baseline ? null : (
<label class={style.optionToggle}>
Progressive rendering
<label class={style.optionInputFirst}>
<Checkbox
name="progressive"
checked={options.progressive}
onChange={this.onChange}
/>
Progressive rendering
</label>
)}
</Expander>
<Expander>
{options.baseline ? (
<label class={style.optionToggle}>
Optimize Huffman table
<label class={style.optionInputFirst}>
<Checkbox
name="optimize_coding"
checked={options.optimize_coding}
onChange={this.onChange}
/>
Optimize Huffman table
</label>
) : null}
</Expander>
@@ -252,33 +251,33 @@ export class Options extends Component<Props, State> {
<option value="8">Peterson et al</option>
</Select>
</label>
<label class={style.optionToggle}>
Trellis multipass
<label class={style.optionInputFirst}>
<Checkbox
name="trellis_multipass"
checked={options.trellis_multipass}
onChange={this.onChange}
/>
Trellis multipass
</label>
<Expander>
{options.trellis_multipass ? (
<label class={style.optionToggle}>
Optimize zero block runs
<label class={style.optionInputFirst}>
<Checkbox
name="trellis_opt_zero"
checked={options.trellis_opt_zero}
onChange={this.onChange}
/>
Optimize zero block runs
</label>
) : null}
</Expander>
<label class={style.optionToggle}>
Optimize after trellis quantization
<label class={style.optionInputFirst}>
<Checkbox
name="trellis_opt_table"
checked={options.trellis_opt_table}
onChange={this.onChange}
/>
Optimize after trellis quantization
</label>
<div class={style.optionOneCell}>
<Range

View File

@@ -12,7 +12,6 @@ import Range from 'client/lazy-app/Compress/Options/Range';
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
import Expander from 'client/lazy-app/Compress/Options/Expander';
import Select from 'client/lazy-app/Compress/Options/Select';
import Revealer from 'client/lazy-app/Compress/Options/Revealer';
export const encode = (
signal: AbortSignal,
@@ -180,8 +179,7 @@ export class Options extends Component<Props, State> {
Slight loss:
</Range>
</div>
<label class={style.optionToggle}>
Discrete tone image
<label class={style.optionInputFirst}>
{/*
Although there are 3 different kinds of image hint, webp only
seems to do something with the 'graph' type, and I don't really
@@ -192,6 +190,7 @@ export class Options extends Component<Props, State> {
checked={options.image_hint === WebPImageHint.WEBP_HINT_GRAPH}
onChange={this.onChange}
/>
Discrete tone image
</label>
</div>
);
@@ -225,23 +224,23 @@ export class Options extends Component<Props, State> {
Quality:
</Range>
</div>
<label class={style.optionReveal}>
<Revealer
<label class={style.optionInputFirst}>
<Checkbox
checked={showAdvanced}
onChange={linkState(this, 'showAdvanced')}
/>
Advanced settings
Show advanced settings
</label>
<Expander>
{showAdvanced ? (
<div>
<label class={style.optionToggle}>
Compress alpha
<label class={style.optionInputFirst}>
<Checkbox
name="alpha_compression"
checked={!!options.alpha_compression}
onChange={this.onChange}
/>
Compress alpha
</label>
<div class={style.optionOneCell}>
<Range
@@ -265,13 +264,13 @@ export class Options extends Component<Props, State> {
Alpha filter quality:
</Range>
</div>
<label class={style.optionToggle}>
Auto adjust filter strength
<label class={style.optionInputFirst}>
<Checkbox
name="autofilter"
checked={!!options.autofilter}
onChange={this.onChange}
/>
Auto adjust filter strength
</label>
<Expander>
{options.autofilter ? null : (
@@ -288,13 +287,13 @@ export class Options extends Component<Props, State> {
</div>
)}
</Expander>
<label class={style.optionToggle}>
Strong filter
<label class={style.optionInputFirst}>
<Checkbox
name="filter_type"
checked={!!options.filter_type}
onChange={this.onChange}
/>
Strong filter
</label>
<div class={style.optionOneCell}>
<Range
@@ -307,13 +306,13 @@ export class Options extends Component<Props, State> {
Filter sharpness:
</Range>
</div>
<label class={style.optionToggle}>
Sharp RGBYUV conversion
<label class={style.optionInputFirst}>
<Checkbox
name="use_sharp_yuv"
checked={!!options.use_sharp_yuv}
onChange={this.onChange}
/>
Sharp RGBYUV conversion
</label>
<div class={style.optionOneCell}>
<Range
@@ -383,24 +382,24 @@ export class Options extends Component<Props, State> {
// gathering the data.
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<label class={style.optionToggle}>
Lossless
<label class={style.optionInputFirst}>
<Checkbox
name="lossless"
checked={!!options.lossless}
onChange={this.onChange}
/>
Lossless
</label>
{options.lossless
? this._losslessSpecificOptions(options)
: this._lossySpecificOptions(options)}
<label class={style.optionToggle}>
Preserve transparent data
<label class={style.optionInputFirst}>
<Checkbox
name="exact"
checked={!!options.exact}
onChange={this.onChange}
/>
Preserve transparent data
</label>
</form>
);

View File

@@ -9,7 +9,6 @@ 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';
import Revealer from 'client/lazy-app/Compress/Options/Revealer';
export const encode = (
signal: AbortSignal,
@@ -155,12 +154,12 @@ export class Options extends Component<Props, State> {
) {
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<label class={style.optionToggle}>
Lossless
<label class={style.optionInputFirst}>
<Checkbox
checked={lossless}
onChange={this._inputChange('lossless', 'boolean')}
/>
Lossless
</label>
<Expander>
{lossless && (
@@ -191,12 +190,12 @@ export class Options extends Component<Props, State> {
Quality:
</Range>
</div>
<label class={style.optionToggle}>
Separate alpha quality
<label class={style.optionInputFirst}>
<Checkbox
checked={separateAlpha}
onChange={this._inputChange('separateAlpha', 'boolean')}
/>
Separate alpha quality
</label>
<Expander>
{separateAlpha && (
@@ -213,12 +212,12 @@ export class Options extends Component<Props, State> {
</div>
)}
</Expander>
<label class={style.optionReveal}>
<Revealer
<label class={style.optionInputFirst}>
<Checkbox
checked={showAdvanced}
onChange={linkState(this, 'showAdvanced')}
/>
Advanced settings
Show advanced settings
</label>
<Expander>
{showAdvanced && (
@@ -279,8 +278,7 @@ export class Options extends Component<Props, State> {
<option value={Csp.kYIQ}>YIQ</option>
</Select>
</label>
<label class={style.optionToggle}>
Random matrix
<label class={style.optionInputFirst}>
<Checkbox
checked={useRandomMatrix}
onChange={this._inputChange(
@@ -288,6 +286,7 @@ export class Options extends Component<Props, State> {
'boolean',
)}
/>
Random matrix
</label>
</div>
)}

View File

@@ -1,25 +0,0 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Options {
left: number;
right: number;
top: number;
bottom: number;
}
export const defaultOptions: Options = {
left: 0,
right: 0,
top: 0,
bottom: 0,
};

View File

@@ -1,13 +0,0 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference path="../../../../../missing-types.d.ts" />

View File

@@ -1,63 +0,0 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Options } from '../shared/meta';
export default async function crop(
data: ImageData,
opts: Options,
): Promise<ImageData> {
const { left, top, right, bottom } = opts;
const source = data.data;
const { width, height } = data;
const cols = width * 4;
const newWidth = width - left - right;
const newHeight = height - top - bottom;
const len = newWidth * newHeight * 4;
const pixels = new Uint8ClampedArray(len);
for (let y = 0; y < newHeight; y++) {
for (let x = 0; x < newWidth; x++) {
let i = y * cols + x * 4;
let j = (top + y) * cols + (left + x) * 4;
pixels[i] = source[j];
pixels[i + 1] = source[j + 1];
pixels[i + 2] = source[j + 2];
pixels[i + 3] = source[j + 3];
}
}
// let sourceX = left;
// let sourceY = top;
// let x = 0;
// let y = 0;
// let i = 0;
// while (i < len) {
// let from = sourceY * cols + sourceX * 4;
// pixels[i++] = source[from++];
// pixels[i++] = source[from++];
// pixels[i++] = source[from++];
// pixels[i++] = source[from];
// if (++x === newWidth) {
// x = 0;
// y++;
// sourceX = left;
// sourceY++;
// }
// }
return new ImageData(pixels, newWidth, newHeight);
}

View File

@@ -1,13 +0,0 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference path="../../../../../missing-types.d.ts" />

View File

@@ -1,21 +0,0 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Options {
horizontal: boolean;
vertical: boolean;
}
export const defaultOptions: Options = {
horizontal: false,
vertical: false,
};

View File

@@ -1,13 +0,0 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference path="../../../../../missing-types.d.ts" />

View File

@@ -1,68 +0,0 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Options } from '../shared/meta';
export default async function flip(
data: ImageData,
opts: Options,
): Promise<ImageData> {
const { vertical, horizontal } = opts;
const source = data.data;
const len = source.length;
const pixels = new Uint8ClampedArray(len);
const { width, height } = data;
let i = 0;
let x = 0;
let y = 0;
const cols = width * 4;
while (i < len) {
let from = vertical ? (height - y) * cols + x * 4 : i;
if (horizontal) from = from - x * 4 + cols - x * 4; // todo: reduce
pixels[i++] = source[from++];
pixels[i++] = source[from++];
pixels[i++] = source[from++];
pixels[i++] = source[from];
if (++x === width) {
x = 0;
y++;
}
}
/*
function swap(a: number, b: number) {
let tmp = pixels[a];
pixels[a] = pixels[b];
pixels[b] = tmp;
}
function swapRgba(a: number, b: number) {
swap(a, b);
swap(a+1, b+1);
swap(a+2, b+2);
swap(a+3, b+3);
}
const COLS = data.width * 4;
// for (let y = 0, y2 = (data.height - 1); y < y2; y+=4, y2-=4) {
for (let y = 0; y < data.height; y++) {
for (let x = 0, x2 = COLS - 4; x < x2; x+=4, x2-=4) {
const offsetX = y * COLS;
const offsetY = (opts.vertical ? (data.height - y) : y) * COLS;
const flippedX = opts.horizontal ? x2 : x;
swapRgba(offsetX + x, offsetY + x2);
}
}
*/
return new ImageData(pixels, data.width, data.height);
}

View File

@@ -1,13 +0,0 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference path="../../../../../missing-types.d.ts" />

View File

@@ -22,7 +22,7 @@ import {
inputFieldChecked,
} from 'client/lazy-app/util';
import * as style from 'client/lazy-app/Compress/Options/style.css';
import { linkRef } from 'shared/prerendered-app/util';
import { linkRef } from 'shared/initial-app/util';
import Select from 'client/lazy-app/Compress/Options/Select';
import Expander from 'client/lazy-app/Compress/Options/Expander';
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
@@ -285,33 +285,33 @@ export class Options extends Component<Props, State> {
</label>
<Expander>
{isWorkerOptions(options) ? (
<label class={style.optionToggle}>
Premultiply alpha channel
<label class={style.optionInputFirst}>
<Checkbox
name="premultiply"
checked={options.premultiply}
onChange={this.onChange}
/>
Premultiply alpha channel
</label>
) : null}
{isWorkerOptions(options) ? (
<label class={style.optionToggle}>
Linear RGB
<label class={style.optionInputFirst}>
<Checkbox
name="linearRGB"
checked={options.linearRGB}
onChange={this.onChange}
/>
Linear RGB
</label>
) : null}
</Expander>
<label class={style.optionToggle}>
Maintain aspect ratio
<label class={style.optionInputFirst}>
<Checkbox
name="maintainAspect"
checked={maintainAspect}
onChange={linkState(this, 'maintainAspect')}
/>
Maintain aspect ratio
</label>
<Expander>
{maintainAspect ? null : (

View File

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,255 @@
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 &amp; 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>
);
}
}

View File

@@ -29,12 +29,3 @@ interface BeforeInstallPromptEvent extends Event {
interface WindowEventMap {
beforeinstallprompt: BeforeInstallPromptEvent;
}
interface ClipboardItem {
types: string[];
getType(type: string): Promise<Blob>;
}
interface Clipboard {
read(): Promise<ClipboardItem[]>;
}

View File

@@ -0,0 +1,228 @@
@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;
}
}
}

View File

@@ -1,5 +1,4 @@
import * as styles from './styles.css';
import 'add-css:./styles.css';
// So it doesn't cause an error when running in node
const HTMLEl = ((__PRERENDER__

View File

@@ -1,5 +1,4 @@
import * as style from './styles.css';
import 'add-css:./styles.css';
// So it doesn't cause an error when running in node
const HTMLEl = ((__PRERENDER__

View File

@@ -0,0 +1,22 @@
.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;
}
}

View File

@@ -13,24 +13,3 @@
/// <reference path="../../missing-types.d.ts" />
declare const __PRERENDER__: boolean;
type 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;
};

View File

@@ -1,417 +0,0 @@
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;
}
function easeInExpo(x: number): number {
return x === 0 ? 0 : Math.pow(2, 10 * x - 10);
}
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 = 7;
const bgBlobsMaxRadius = 60;
const bgBlobsMinAlpha = 0.2;
const bgBlobsMaxAlpha = 0.8;
const bgBlobsPerPx = 0.000025;
const bgBlobsMinSpinTime = 20000;
const bgBlobsMaxSpinTime = 60000;
const bgBlobsMinVelocity = 0.0015;
const bgBlobsMaxVelocity = 0.007;
const gravityVelocityMultiplier = 15;
const gravityStartDistance = 300;
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) {
const blobs = Math.round(bounds.width * bounds.height * bgBlobsPerPx);
this.bgBlobs = Array.from({ length: blobs }, () => {
const radiusPos = easeInExpo(Math.random());
return {
blob: new CircleBlob(sevenPointCircle, {
minDuration: 2000,
maxDuration: 5000,
}),
// Velocity is based on the size
velocity:
(1 - radiusPos) * (bgBlobsMaxVelocity - bgBlobsMinVelocity) +
bgBlobsMinVelocity,
alpha:
Math.random() ** 3 * (bgBlobsMaxAlpha - bgBlobsMinAlpha) +
bgBlobsMinAlpha,
alphaMultiplier: 1,
spinTime: rand(bgBlobsMinSpinTime, bgBlobsMaxSpinTime),
rotatePos: 0,
radius:
radiusPos * (bgBlobsMaxRadius - bgBlobsMinRadius) + bgBlobsMinRadius,
x: Math.random() * bounds.width,
y: Math.random() * bounds.height,
};
});
}
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;
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 velocity =
dist > gravityStartDistance
? bgBlob.velocity
: ((1 - dist / gravityStartDistance) *
(gravityVelocityMultiplier - 1) +
1) *
bgBlob.velocity;
const shiftDist = velocity * timeDelta;
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();
}

View File

@@ -1,41 +0,0 @@
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],
],
];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 588 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,382 +0,0 @@
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' : undefined }}
>
<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>
);
}
}

View File

@@ -1,243 +0,0 @@
.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(--dim-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;
}
}

Some files were not shown because too many files have changed in this diff Show More