mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-14 17:49:52 +00:00
First bit of real UI code landed
This commit is contained in:
@@ -101,7 +101,7 @@ export default function (resolveFileUrl) {
|
|||||||
hashToId = new Map();
|
hashToId = new Map();
|
||||||
pathToResult = new Map();
|
pathToResult = new Map();
|
||||||
|
|
||||||
const cssPaths = await globP('src/static-build/**/*.css', {
|
const cssPaths = await globP('src/**/*.css', {
|
||||||
nodir: true,
|
nodir: true,
|
||||||
absolute: true,
|
absolute: true,
|
||||||
});
|
});
|
||||||
@@ -126,11 +126,11 @@ export default function (resolveFileUrl) {
|
|||||||
|
|
||||||
const cssClassExports = Object.entries(moduleJSON).map(
|
const cssClassExports = Object.entries(moduleJSON).map(
|
||||||
([key, val]) =>
|
([key, val]) =>
|
||||||
`export const $${camelCase(key)} = ${JSON.stringify(val)};`,
|
`export const ${camelCase(key)} = ${JSON.stringify(val)};`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const defs = Object.keys(moduleJSON)
|
const defs = Object.keys(moduleJSON)
|
||||||
.map((key) => `export const $${camelCase(key)}: string;`)
|
.map((key) => `export const ${camelCase(key)}: string;`)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
const defPath = path + '.d.ts';
|
const defPath = path + '.d.ts';
|
||||||
|
|||||||
2
missing-types.d.ts
vendored
2
missing-types.d.ts
vendored
@@ -21,3 +21,5 @@ declare module 'omt:*' {
|
|||||||
const value: string;
|
const value: string;
|
||||||
export default value;
|
export default value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare const __PRODUCTION__: boolean;
|
||||||
|
|||||||
72
package-lock.json
generated
72
package-lock.json
generated
@@ -109,9 +109,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@rollup/plugin-commonjs": {
|
"@rollup/plugin-commonjs": {
|
||||||
"version": "15.0.0",
|
"version": "15.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-15.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz",
|
||||||
"integrity": "sha512-8uAdikHqVyrT32w1zB9VhW6uGwGjhKgnDNP4pQJsjdnyF4FgCj6/bmv24c7v2CuKhq32CcyCwRzMPEElaKkn0w==",
|
"integrity": "sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@rollup/pluginutils": "^3.1.0",
|
"@rollup/pluginutils": "^3.1.0",
|
||||||
@@ -145,6 +145,16 @@
|
|||||||
"resolve": "^1.17.0"
|
"resolve": "^1.17.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@rollup/plugin-replace": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-XPmVXZ7IlaoWaJLkSCDaa0Y6uVo5XQYHhiMFzOd5qSv5rE+t/UJToPIOE56flKIxBFQI27ONsxb7dqHnwSsjKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@rollup/pluginutils": "^3.0.8",
|
||||||
|
"magic-string": "^0.25.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@rollup/pluginutils": {
|
"@rollup/pluginutils": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
|
||||||
@@ -195,9 +205,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "14.10.1",
|
"version": "14.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz",
|
||||||
"integrity": "sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ==",
|
"integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/parse-json": {
|
"@types/parse-json": {
|
||||||
@@ -1157,12 +1167,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.1.1",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "^2.1.1"
|
"ms": "2.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dedent": {
|
"dedent": {
|
||||||
@@ -1457,6 +1467,12 @@
|
|||||||
"escape-string-regexp": "^1.0.5"
|
"escape-string-regexp": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"file-drop-element": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-drop-element/-/file-drop-element-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-4T+hoNZR7hMumVcCUbmg2XtjGph15thvsT40+Xu8snMBpnDsRFhBnZ6Nhxbnwot451gg8EfJzQRS+Wmr4j7Ytw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"fill-range": {
|
"fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
@@ -1923,9 +1939,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"version": "10.3.0",
|
"version": "10.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.4.0.tgz",
|
||||||
"integrity": "sha512-an3VgjHqmJk0TORB/sdQl0CTkRg4E5ybYCXTTCSJ5h9jFwZbcgKIx5oVma5e7wp/uKt17s1QYFmYqT9MGVosGw==",
|
"integrity": "sha512-uaiX4U5yERUSiIEQc329vhCTDDwUcSvKdRLsNomkYLRzijk3v8V9GWm2Nz0RMVB87VcuzLvtgy6OsjoH++QHIg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
@@ -2399,9 +2415,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "7.0.32",
|
"version": "7.0.34",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.34.tgz",
|
||||||
"integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
|
"integrity": "sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chalk": "^2.4.2",
|
"chalk": "^2.4.2",
|
||||||
@@ -3063,9 +3079,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"preact": {
|
"preact": {
|
||||||
"version": "10.4.8",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.0.tgz",
|
||||||
"integrity": "sha512-uVLeEAyRsCkUEFhVHlOu17OxcrwC7+hTGZ08kBoLBiGHiZooUZuibQnphgMKftw/rqYntNMyhVCPqQhcyAGHag==",
|
"integrity": "sha512-CuhSq2uq1lUy9442j9Jlucapt8+9SFyNl1+evzbMb8dTF4GCPrc1XMvf9Hai7XbeXG/wIxR0TVhhEFKJ3DkY6Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"preact-render-to-string": {
|
"preact-render-to-string": {
|
||||||
@@ -3078,9 +3094,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz",
|
||||||
"integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==",
|
"integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"pretty-format": {
|
"pretty-format": {
|
||||||
@@ -3225,9 +3241,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rollup": {
|
"rollup": {
|
||||||
"version": "2.26.11",
|
"version": "2.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.26.11.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.28.1.tgz",
|
||||||
"integrity": "sha512-xyfxxhsE6hW57xhfL1I+ixH8l2bdoIMaAecdQiWF3N7IgJEMu99JG+daBiSZQjnBpzFxa0/xZm+3pbCdAQehHw==",
|
"integrity": "sha512-DOtVoqOZt3+FjPJWLU8hDIvBjUylc9s6IZvy76XklxzcLvAQLtVAG/bbhsMhcWnYxC0TKKcf1QQ/tg29zeID0Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fsevents": "~2.1.2"
|
"fsevents": "~2.1.2"
|
||||||
@@ -3812,9 +3828,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz",
|
||||||
"integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==",
|
"integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uniq": {
|
"uniq": {
|
||||||
|
|||||||
18
package.json
18
package.json
@@ -10,29 +10,31 @@
|
|||||||
"serve": "serve --config server.json .tmp/build/static"
|
"serve": "serve --config server.json .tmp/build/static"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^15.0.0",
|
"@rollup/plugin-commonjs": "^15.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^9.0.0",
|
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||||
|
"@rollup/plugin-replace": "^2.3.3",
|
||||||
"@surma/rollup-plugin-off-main-thread": "^1.4.1",
|
"@surma/rollup-plugin-off-main-thread": "^1.4.1",
|
||||||
"@types/node": "^14.10.1",
|
"@types/node": "^14.11.2",
|
||||||
"comlink": "^4.3.0",
|
"comlink": "^4.3.0",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"del": "^5.1.0",
|
"del": "^5.1.0",
|
||||||
|
"file-drop-element": "^1.0.0",
|
||||||
"husky": "^4.3.0",
|
"husky": "^4.3.0",
|
||||||
"lint-staged": "^10.3.0",
|
"lint-staged": "^10.4.0",
|
||||||
"lodash.camelcase": "^4.3.0",
|
"lodash.camelcase": "^4.3.0",
|
||||||
"postcss": "^7.0.32",
|
"postcss": "^7.0.34",
|
||||||
"postcss-import": "^12.0.1",
|
"postcss-import": "^12.0.1",
|
||||||
"postcss-modules": "^3.2.2",
|
"postcss-modules": "^3.2.2",
|
||||||
"postcss-nested": "^4.2.3",
|
"postcss-nested": "^4.2.3",
|
||||||
"postcss-simple-vars": "^5.0.2",
|
"postcss-simple-vars": "^5.0.2",
|
||||||
"postcss-url": "^8.0.0",
|
"postcss-url": "^8.0.0",
|
||||||
"preact": "^10.4.8",
|
"preact": "^10.5.0",
|
||||||
"preact-render-to-string": "^5.1.10",
|
"preact-render-to-string": "^5.1.10",
|
||||||
"prettier": "^2.1.1",
|
"prettier": "^2.1.2",
|
||||||
"rollup": "^2.26.11",
|
"rollup": "^2.28.1",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"serve": "^11.3.2",
|
"serve": "^11.3.2",
|
||||||
"typescript": "^4.0.2"
|
"typescript": "^4.0.3"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import resolve from '@rollup/plugin-node-resolve';
|
|||||||
import commonjs from '@rollup/plugin-commonjs';
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
import { terser } from 'rollup-plugin-terser';
|
import { terser } from 'rollup-plugin-terser';
|
||||||
import OMT from '@surma/rollup-plugin-off-main-thread';
|
import OMT from '@surma/rollup-plugin-off-main-thread';
|
||||||
|
import replace from '@rollup/plugin-replace';
|
||||||
|
|
||||||
import simpleTS from './lib/simple-ts';
|
import simpleTS from './lib/simple-ts';
|
||||||
import clientBundlePlugin from './lib/client-bundle-plugin';
|
import clientBundlePlugin from './lib/client-bundle-plugin';
|
||||||
@@ -46,6 +47,8 @@ export default async function ({ watch }) {
|
|||||||
);
|
);
|
||||||
await del('.tmp/build');
|
await del('.tmp/build');
|
||||||
|
|
||||||
|
const isProduction = !watch;
|
||||||
|
|
||||||
const tsPluginInstance = simpleTS('.', {
|
const tsPluginInstance = simpleTS('.', {
|
||||||
watch,
|
watch,
|
||||||
});
|
});
|
||||||
@@ -84,7 +87,8 @@ export default async function ({ watch }) {
|
|||||||
...commonPlugins(),
|
...commonPlugins(),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
resolve(),
|
resolve(),
|
||||||
terser({ module: true }),
|
replace({ __PRERENDER__: false, __PRODUCTION__: isProduction }),
|
||||||
|
//terser({ module: true }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -99,6 +103,7 @@ export default async function ({ watch }) {
|
|||||||
emitFiles({ include: '**/*', root: path.join(__dirname, 'src', 'copy') }),
|
emitFiles({ include: '**/*', root: path.join(__dirname, 'src', 'copy') }),
|
||||||
nodeExternalPlugin(),
|
nodeExternalPlugin(),
|
||||||
imageWorkerPlugin(),
|
imageWorkerPlugin(),
|
||||||
|
replace({ __PRERENDER__: true, __PRODUCTION__: isProduction }),
|
||||||
runScript(dir + '/index.js'),
|
runScript(dir + '/index.js'),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,51 +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 { wrap } from 'comlink';
|
|
||||||
import workerURL from 'omt:features/worker';
|
|
||||||
import imgURL from 'url:./tmp.png';
|
|
||||||
|
|
||||||
import type { ProcessorWorkerApi } from 'features/worker';
|
|
||||||
const worker = new Worker(workerURL);
|
|
||||||
const api = wrap<ProcessorWorkerApi>(worker);
|
|
||||||
|
|
||||||
async function demo() {
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.src = imgURL;
|
|
||||||
await img.decode();
|
|
||||||
// Make canvas same size as image
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = img.width;
|
|
||||||
canvas.height = img.height;
|
|
||||||
const ctx = canvas.getContext('2d')!;
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
const data = ctx.getImageData(0, 0, img.width, img.height);
|
|
||||||
const result = await api.rotate(data, {
|
|
||||||
rotate: 180,
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
/*const resultUrl = URL.createObjectURL(new Blob([result]));
|
|
||||||
const img = new Image();
|
|
||||||
img.src = resultUrl;
|
|
||||||
document.body.append(img);*/
|
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = result.width;
|
|
||||||
canvas.height = result.height;
|
|
||||||
const ctx = canvas.getContext('2d')!;
|
|
||||||
ctx.putImageData(result, 0, 0);
|
|
||||||
document.body.append(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
demo();
|
|
||||||
137
src/client/initial-app/App/index.tsx
Normal file
137
src/client/initial-app/App/index.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import type { FileDropEvent } from 'file-drop-element';
|
||||||
|
import type SnackBarElement from 'client/initial-app/custom-els/snack-bar';
|
||||||
|
import type { SnackOptions } from 'client/initial-app/custom-els/snack-bar';
|
||||||
|
|
||||||
|
import { h, Component } from 'preact';
|
||||||
|
|
||||||
|
import { linkRef } from 'client/initial-app/util';
|
||||||
|
import * as style from './style.css';
|
||||||
|
import 'file-drop-element';
|
||||||
|
import 'client/initial-app/custom-els/snack-bar';
|
||||||
|
//import Intro from '../intro';
|
||||||
|
import 'client/initial-app/custom-els/loading-spinner';
|
||||||
|
|
||||||
|
const ROUTE_EDITOR = '/editor';
|
||||||
|
|
||||||
|
//const compressPromise = import('../compress');
|
||||||
|
//const swBridgePromise = import('../../lib/sw-bridge');
|
||||||
|
|
||||||
|
function back() {
|
||||||
|
window.history.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
awaitingShareTarget: boolean;
|
||||||
|
file?: File;
|
||||||
|
isEditorOpen: Boolean;
|
||||||
|
Compress?: undefined; // typeof import('../compress').default;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class App extends Component<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
awaitingShareTarget: new URL(location.href).searchParams.has(
|
||||||
|
'share-target',
|
||||||
|
),
|
||||||
|
isEditorOpen: false,
|
||||||
|
file: undefined,
|
||||||
|
Compress: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
snackbar?: SnackBarElement;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
/*compressPromise
|
||||||
|
.then((module) => {
|
||||||
|
this.setState({ Compress: module.default });
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.showSnack('Failed to load app');
|
||||||
|
});
|
||||||
|
|
||||||
|
swBridgePromise.then(async ({ offliner, getSharedImage }) => {
|
||||||
|
offliner(this.showSnack);
|
||||||
|
if (!this.state.awaitingShareTarget) return;
|
||||||
|
const file = await getSharedImage();
|
||||||
|
// Remove the ?share-target from the URL
|
||||||
|
history.replaceState('', '', '/');
|
||||||
|
this.openEditor();
|
||||||
|
this.setState({ file, awaitingShareTarget: false });
|
||||||
|
});*/
|
||||||
|
|
||||||
|
// Since iOS 10, Apple tries to prevent disabling pinch-zoom. This is great in theory, but
|
||||||
|
// really breaks things on Squoosh, as you can easily end up zooming the UI when you mean to
|
||||||
|
// zoom the image. Once you've done this, it's really difficult to undo. Anyway, this seems to
|
||||||
|
// prevent it.
|
||||||
|
document.body.addEventListener('gesturestart', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('popstate', this.onPopState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onFileDrop = ({ files }: FileDropEvent) => {
|
||||||
|
if (!files || files.length === 0) return;
|
||||||
|
const file = files[0];
|
||||||
|
this.openEditor();
|
||||||
|
this.setState({ file });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onIntroPickFile = (file: File) => {
|
||||||
|
this.openEditor();
|
||||||
|
this.setState({ file });
|
||||||
|
};
|
||||||
|
|
||||||
|
private showSnack = (
|
||||||
|
message: string,
|
||||||
|
options: SnackOptions = {},
|
||||||
|
): Promise<string> => {
|
||||||
|
if (!this.snackbar) throw Error('Snackbar missing');
|
||||||
|
return this.snackbar.showSnackbar(message, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onPopState = () => {
|
||||||
|
this.setState({ isEditorOpen: location.pathname === ROUTE_EDITOR });
|
||||||
|
};
|
||||||
|
|
||||||
|
private openEditor = () => {
|
||||||
|
if (this.state.isEditorOpen) return;
|
||||||
|
// Change path, but preserve query string.
|
||||||
|
const editorURL = new URL(location.href);
|
||||||
|
editorURL.pathname = ROUTE_EDITOR;
|
||||||
|
history.pushState(null, '', editorURL.href);
|
||||||
|
this.setState({ isEditorOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
{}: Props,
|
||||||
|
{ file, isEditorOpen, Compress, awaitingShareTarget }: State,
|
||||||
|
) {
|
||||||
|
const showSpinner = awaitingShareTarget || (isEditorOpen && !Compress);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={style.app}>
|
||||||
|
<file-drop
|
||||||
|
accept="image/*"
|
||||||
|
onfiledrop={this.onFileDrop}
|
||||||
|
class={style.drop}
|
||||||
|
>
|
||||||
|
{showSpinner ? (
|
||||||
|
<loading-spinner class={style.appLoader} />
|
||||||
|
) : isEditorOpen ? (
|
||||||
|
Compress &&
|
||||||
|
//<Compress file={file!} showSnack={this.showSnack} onBack={back} />
|
||||||
|
'TODO: uncomment above'
|
||||||
|
) : (
|
||||||
|
//<Intro onFile={this.onIntroPickFile} showSnack={this.showSnack} />
|
||||||
|
'TODO: show intro here'
|
||||||
|
)}
|
||||||
|
<snack-bar ref={linkRef(this, 'snackbar')} />
|
||||||
|
</file-drop>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/client/initial-app/App/style.css
Normal file
68
src/client/initial-app/App/style.css
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
.app {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
contain: strict;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop {
|
||||||
|
overflow: hidden;
|
||||||
|
touch-action: none;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:global {
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
left: 10px;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
border: 2px dashed #fff;
|
||||||
|
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);
|
||||||
|
transition: all 200ms ease-in;
|
||||||
|
transition-property: transform, opacity;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.drop-valid::after {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
transition-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-pair {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-loader {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
--size: 225px;
|
||||||
|
--stroke-width: 26px;
|
||||||
|
}
|
||||||
62
src/client/initial-app/custom-els/loading-spinner/index.ts
Normal file
62
src/client/initial-app/custom-els/loading-spinner/index.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple spinner. This custom element has no JS API. Just put it in the document, and it'll
|
||||||
|
* spin. You can configure the following using CSS custom properties:
|
||||||
|
*
|
||||||
|
* --size: Size of the spinner element (it's always square). Default: 28px.
|
||||||
|
* --color: Color of the spinner. Default: #4285f4.
|
||||||
|
* --stroke-width: Width of the stroke of the spinner. Default: 3px.
|
||||||
|
* --delay: Once the spinner enters the DOM, how long until it shows. This prevents the spinner
|
||||||
|
* appearing on the screen for short operations. Default: 300ms.
|
||||||
|
*/
|
||||||
|
export default class LoadingSpinner extends HTMLElement {
|
||||||
|
private _delayTimeout: number = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// Ideally we'd use shadow DOM here, but we're targeting browsers without shadow DOM support.
|
||||||
|
// You can't set attributes/content in a custom element constructor, so I'm waiting a microtask.
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
this.style.display = 'none';
|
||||||
|
// prettier-ignore
|
||||||
|
this.innerHTML = '' +
|
||||||
|
`<div class="${styles.spinnerContainer}">` +
|
||||||
|
`<div class="${styles.spinnerLayer}">` +
|
||||||
|
`<div class="${styles.spinnerCircleClipper} ${styles.spinnerLeft}">` +
|
||||||
|
`<div class="${styles.spinnerCircle}"></div>` +
|
||||||
|
'</div>' +
|
||||||
|
`<div class="${styles.spinnerGapPatch}">` +
|
||||||
|
`<div class="${styles.spinnerCircle}"></div>` +
|
||||||
|
'</div>' +
|
||||||
|
`<div class="${styles.spinnerCircleClipper} ${styles.spinnerRight}">` +
|
||||||
|
`<div class="${styles.spinnerCircle}"></div>` +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.style.display = 'none';
|
||||||
|
clearTimeout(this._delayTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
const delayStr = getComputedStyle(this).getPropertyValue('--delay').trim();
|
||||||
|
let delayNum = parseFloat(delayStr);
|
||||||
|
|
||||||
|
// If seconds…
|
||||||
|
if (/\ds$/.test(delayStr)) {
|
||||||
|
// Convert to ms.
|
||||||
|
delayNum *= 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._delayTimeout = self.setTimeout(() => {
|
||||||
|
this.style.display = '';
|
||||||
|
}, delayNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('loading-spinner', LoadingSpinner);
|
||||||
13
src/client/initial-app/custom-els/loading-spinner/missing-types.d.ts
vendored
Normal file
13
src/client/initial-app/custom-els/loading-spinner/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
interface LoadingSpinner extends preact.JSX.HTMLAttributes {}
|
||||||
|
|
||||||
|
declare module 'preact' {
|
||||||
|
namespace createElement.JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
'loading-spinner': LoadingSpinner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thing break unless this file is a module.
|
||||||
|
// Don't ask me why. I don't know.
|
||||||
|
export {};
|
||||||
158
src/client/initial-app/custom-els/loading-spinner/styles.css
Normal file
158
src/client/initial-app/custom-els/loading-spinner/styles.css
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
@keyframes spinner-left-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(130deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(130deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner-right-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(-130deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: rotate(5deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(-130deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner-fade-out {
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner-container-rotate {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner-fill-unfill-rotate {
|
||||||
|
12.5% {
|
||||||
|
transform: rotate(135deg);
|
||||||
|
} /* 0.5 * ARCSIZE */
|
||||||
|
25% {
|
||||||
|
transform: rotate(270deg);
|
||||||
|
} /* 1 * ARCSIZE */
|
||||||
|
37.5% {
|
||||||
|
transform: rotate(405deg);
|
||||||
|
} /* 1.5 * ARCSIZE */
|
||||||
|
50% {
|
||||||
|
transform: rotate(540deg);
|
||||||
|
} /* 2 * ARCSIZE */
|
||||||
|
62.5% {
|
||||||
|
transform: rotate(675deg);
|
||||||
|
} /* 2.5 * ARCSIZE */
|
||||||
|
75% {
|
||||||
|
transform: rotate(810deg);
|
||||||
|
} /* 3 * ARCSIZE */
|
||||||
|
87.5% {
|
||||||
|
transform: rotate(945deg);
|
||||||
|
} /* 3.5 * ARCSIZE */
|
||||||
|
to {
|
||||||
|
transform: rotate(1080deg);
|
||||||
|
} /* 4 * ARCSIZE */
|
||||||
|
}
|
||||||
|
|
||||||
|
loading-spinner {
|
||||||
|
--size: 28px;
|
||||||
|
--color: #4285f4;
|
||||||
|
--stroke-width: 3px;
|
||||||
|
--delay: 300ms;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
border-color: var(--color);
|
||||||
|
}
|
||||||
|
|
||||||
|
loading-spinner .spinner-circle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
width: 200%;
|
||||||
|
border-width: var(--stroke-width);
|
||||||
|
border-style: solid;
|
||||||
|
border-color: inherit;
|
||||||
|
border-bottom-color: transparent !important;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Patch the gap that appear between the two adjacent div.circle-clipper while the
|
||||||
|
spinner is rotating (appears on Chrome 38, Safari 7.1, and IE 11).
|
||||||
|
*/
|
||||||
|
loading-spinner .spinner-gap-patch {
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
top: 0;
|
||||||
|
left: 45%;
|
||||||
|
width: 10%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading-spinner .spinner-gap-patch .spinner-circle {
|
||||||
|
width: 1000%;
|
||||||
|
left: -450%;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading-spinner .spinner-circle-clipper {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading-spinner .spinner-left .spinner-circle {
|
||||||
|
border-right-color: transparent !important;
|
||||||
|
transform: rotate(129deg);
|
||||||
|
animation: spinner-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading-spinner .spinner-right .spinner-circle {
|
||||||
|
left: -100%;
|
||||||
|
border-left-color: transparent !important;
|
||||||
|
transform: rotate(-129deg);
|
||||||
|
animation: spinner-right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite
|
||||||
|
both;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading-spinner.spinner-fadeout {
|
||||||
|
animation: spinner-fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading-spinner .spinner-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-color: inherit;
|
||||||
|
|
||||||
|
/* duration: 360 * ARCTIME / (ARCSTARTROT + (360-ARCSIZE)) */
|
||||||
|
animation: spinner-container-rotate 1568ms linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading-spinner .spinner-layer {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-color: inherit;
|
||||||
|
/* durations: 4 * ARCTIME */
|
||||||
|
animation: spinner-fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1)
|
||||||
|
infinite both;
|
||||||
|
}
|
||||||
16
src/client/initial-app/custom-els/missing-types.d.ts
vendored
Normal file
16
src/client/initial-app/custom-els/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type { FileDropElement, FileDropEvent } from 'file-drop-element';
|
||||||
|
|
||||||
|
interface FileDropAttributes extends preact.JSX.HTMLAttributes {
|
||||||
|
accept?: string;
|
||||||
|
onfiledrop?: ((this: FileDropElement, ev: FileDropEvent) => any) | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'preact' {
|
||||||
|
namespace createElement.JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
'file-drop': FileDropAttributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
95
src/client/initial-app/custom-els/snack-bar/index.ts
Normal file
95
src/client/initial-app/custom-els/snack-bar/index.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import * as style from './styles.css';
|
||||||
|
|
||||||
|
export interface SnackOptions {
|
||||||
|
timeout?: number;
|
||||||
|
actions?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSnack(
|
||||||
|
message: string,
|
||||||
|
options: SnackOptions,
|
||||||
|
): [Element, Promise<string>] {
|
||||||
|
const { timeout = 0, actions = ['dismiss'] } = options;
|
||||||
|
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = style.snackbar;
|
||||||
|
el.setAttribute('aria-live', 'assertive');
|
||||||
|
el.setAttribute('aria-atomic', 'true');
|
||||||
|
el.setAttribute('aria-hidden', 'false');
|
||||||
|
|
||||||
|
const text = document.createElement('div');
|
||||||
|
text.className = style.text;
|
||||||
|
text.textContent = message;
|
||||||
|
el.appendChild(text);
|
||||||
|
|
||||||
|
const result = new Promise<string>((resolve) => {
|
||||||
|
let timeoutId: number;
|
||||||
|
|
||||||
|
// Add action buttons
|
||||||
|
for (const action of actions) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = style.button;
|
||||||
|
button.textContent = action;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
resolve(action);
|
||||||
|
});
|
||||||
|
el.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add timeout
|
||||||
|
if (timeout) {
|
||||||
|
timeoutId = self.setTimeout(() => resolve(''), timeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [el, result];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SnackBarElement extends HTMLElement {
|
||||||
|
private _snackbars: [
|
||||||
|
string,
|
||||||
|
SnackOptions,
|
||||||
|
(action: Promise<string>) => void,
|
||||||
|
][] = [];
|
||||||
|
private _processingQueue = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a snackbar. Returns a promise for the name of the action clicked, or an empty string if no
|
||||||
|
* action is clicked.
|
||||||
|
*/
|
||||||
|
showSnackbar(message: string, options: SnackOptions = {}): Promise<string> {
|
||||||
|
return new Promise<string>((resolve) => {
|
||||||
|
this._snackbars.push([message, options, resolve]);
|
||||||
|
if (!this._processingQueue) this._processQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _processQueue() {
|
||||||
|
this._processingQueue = true;
|
||||||
|
|
||||||
|
while (this._snackbars[0]) {
|
||||||
|
const [message, options, resolver] = this._snackbars[0];
|
||||||
|
const [el, result] = createSnack(message, options);
|
||||||
|
// Pass the result back to the original showSnackbar call.
|
||||||
|
resolver(result);
|
||||||
|
this.appendChild(el);
|
||||||
|
|
||||||
|
// Wait for the user to click an action, or for the snack to timeout.
|
||||||
|
await result;
|
||||||
|
|
||||||
|
// Transition the snack away.
|
||||||
|
el.setAttribute('aria-hidden', 'true');
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
el.addEventListener('animationend', () => resolve());
|
||||||
|
});
|
||||||
|
el.remove();
|
||||||
|
|
||||||
|
this._snackbars.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._processingQueue = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('snack-bar', SnackBarElement);
|
||||||
15
src/client/initial-app/custom-els/snack-bar/missing-types.d.ts
vendored
Normal file
15
src/client/initial-app/custom-els/snack-bar/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { SnackOptions } from '.';
|
||||||
|
|
||||||
|
interface SnackBarAttributes extends preact.JSX.HTMLAttributes {
|
||||||
|
showSnackbar?: (options: SnackOptions) => Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'preact' {
|
||||||
|
namespace createElement.JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
'snack-bar': SnackBarAttributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
105
src/client/initial-app/custom-els/snack-bar/styles.css
Normal file
105
src/client/initial-app/custom-els/snack-bar/styles.css
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
snack-bar {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snackbar {
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 24px;
|
||||||
|
width: 344px;
|
||||||
|
margin-left: -172px;
|
||||||
|
background: #2a2a2a;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
|
||||||
|
transform-origin: center;
|
||||||
|
color: #eee;
|
||||||
|
z-index: 100;
|
||||||
|
cursor: default;
|
||||||
|
will-change: transform;
|
||||||
|
animation: snackbar-show 300ms ease forwards 1;
|
||||||
|
}
|
||||||
|
.snackbar[aria-hidden='true'] {
|
||||||
|
animation: snackbar-hide 300ms ease forwards 1;
|
||||||
|
}
|
||||||
|
@keyframes snackbar-show {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes snackbar-hide {
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.snackbar {
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
position: relative;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
padding: 8px;
|
||||||
|
height: 36px;
|
||||||
|
margin: auto 8px auto -8px;
|
||||||
|
min-width: 5em;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: lightgreen;
|
||||||
|
font-weight: inherit;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
font-size: 100%;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: background-color 200ms ease;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
.button:focus:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 120%;
|
||||||
|
height: 0;
|
||||||
|
padding: 0 0 120%;
|
||||||
|
margin: -60% 0 0 -60%;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 50%;
|
||||||
|
transform-origin: center;
|
||||||
|
will-change: transform;
|
||||||
|
animation: focus-ring 300ms ease-out forwards 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
@keyframes focus-ring {
|
||||||
|
from {
|
||||||
|
transform: scale(0.01);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/client/initial-app/index.tsx
Normal file
44
src/client/initial-app/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* 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 { h, render } from 'preact';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
const root = document.getElementById('app') as HTMLElement;
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
if (!__PRODUCTION__) await import('preact/debug');
|
||||||
|
render(<App />, root);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
||||||
|
// Analytics
|
||||||
|
{
|
||||||
|
// Determine the current display mode.
|
||||||
|
const displayMode =
|
||||||
|
navigator.standalone ||
|
||||||
|
window.matchMedia('(display-mode: standalone)').matches
|
||||||
|
? 'standalone'
|
||||||
|
: 'browser';
|
||||||
|
|
||||||
|
// Setup analytics
|
||||||
|
window.ga = window.ga || ((...args) => (ga.q = ga.q || []).push(args));
|
||||||
|
ga('create', 'UA-128752250-1', 'auto');
|
||||||
|
ga('set', 'transport', 'beacon');
|
||||||
|
ga('set', 'dimension1', displayMode);
|
||||||
|
ga('send', 'pageview', '/index.html', { title: 'Squoosh' });
|
||||||
|
// Load the GA script
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = 'https://www.google-analytics.com/analytics.js';
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}
|
||||||
15
src/client/initial-app/util.ts
Normal file
15
src/client/initial-app/util.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/** Creates a function ref that assigns its value to a given property of an object.
|
||||||
|
* @example
|
||||||
|
* // element is stored as `this.foo` when rendered.
|
||||||
|
* <div ref={linkRef(this, 'foo')} />
|
||||||
|
*/
|
||||||
|
export function linkRef<T>(obj: any, name: string) {
|
||||||
|
const refName = `$$ref_${name}`;
|
||||||
|
let ref = obj[refName];
|
||||||
|
if (!ref) {
|
||||||
|
ref = obj[refName] = (c: T) => {
|
||||||
|
obj[name] = c;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
11
src/client/missing-types.d.ts
vendored
11
src/client/missing-types.d.ts
vendored
@@ -11,3 +11,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
/// <reference path="../../missing-types.d.ts" />
|
/// <reference path="../../missing-types.d.ts" />
|
||||||
|
|
||||||
|
declare var ga: {
|
||||||
|
(...args: any[]): void;
|
||||||
|
q: any[];
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Navigator {
|
||||||
|
readonly standalone: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'preact/debug' {}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 90 KiB |
BIN
src/static-build/assets/favicon.ico
Normal file
BIN
src/static-build/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -1,44 +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 { h, FunctionalComponent, RenderableProps } from 'preact';
|
|
||||||
import styles from 'css-bundle:./all.css';
|
|
||||||
import clientBundleURL, { imports } from 'client-bundle:client/index.tsx';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BasePage: FunctionalComponent<Props> = ({
|
|
||||||
children,
|
|
||||||
title,
|
|
||||||
}: RenderableProps<Props>) => {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>{title ? `${title} - ` : ''}Squoosh</title>
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, minimum-scale=1.0"
|
|
||||||
></meta>
|
|
||||||
<link rel="stylesheet" href={styles} />
|
|
||||||
<script src={clientBundleURL} defer />
|
|
||||||
{imports.map((v) => (
|
|
||||||
<link rel="preload" as="script" href={v} />
|
|
||||||
))}
|
|
||||||
</head>
|
|
||||||
<body>{children}</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BasePage;
|
|
||||||
@@ -11,11 +11,40 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { h, FunctionalComponent } from 'preact';
|
import { h, FunctionalComponent } from 'preact';
|
||||||
import BasePage from 'static-build/components/base';
|
|
||||||
|
|
||||||
const IndexPage: FunctionalComponent<{}> = () => (
|
import styles from 'css-bundle:./all.css';
|
||||||
<BasePage>
|
import clientBundleURL, { imports } from 'client-bundle:client/initial-app';
|
||||||
<h1>Hi</h1>
|
import favicon from 'url:static-build/assets/favicon.ico';
|
||||||
</BasePage>
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
const Index: FunctionalComponent<Props> = () => (
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Squoosh</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Compress and compare images with different codecs, right in your browser"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
||||||
|
/>
|
||||||
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<link rel="shortcut icon" href={favicon} />
|
||||||
|
<meta name="theme-color" content="#f78f21" />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<link rel="stylesheet" href={styles} />
|
||||||
|
<script src={clientBundleURL} defer />
|
||||||
|
{imports.map((v) => (
|
||||||
|
<link rel="preload" as="script" href={v} />
|
||||||
|
))}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app" />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
);
|
);
|
||||||
export default IndexPage;
|
|
||||||
|
export default Index;
|
||||||
|
|||||||
Reference in New Issue
Block a user