mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-12 00:37:19 +00:00
Compare commits
22 Commits
update-res
...
v1.10.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
273ed917c2 | ||
|
|
acc912d33f | ||
|
|
8313246fd1 | ||
|
|
6ad28c0b5c | ||
|
|
c76dabf063 | ||
|
|
e6d8bac9c5 | ||
|
|
42e43730c8 | ||
|
|
5c17fba349 | ||
|
|
85eb94b725 | ||
|
|
4fa73be842 | ||
|
|
7c89d09139 | ||
|
|
079e56f1e1 | ||
|
|
6065ceabfe | ||
|
|
265e6db2bd | ||
|
|
eeb3d3562a | ||
|
|
2d73c24e09 | ||
|
|
f4a16022ef | ||
|
|
12153c72dc | ||
|
|
62c53c9fed | ||
|
|
53a38b2ba1 | ||
|
|
22b7e36c01 | ||
|
|
a0e6a377cd |
@@ -10,6 +10,7 @@ Google Analytics is used to record the following:
|
|||||||
* [Basic visit data](https://support.google.com/analytics/answer/6004245?ref_topic=2919631).
|
* [Basic visit data](https://support.google.com/analytics/answer/6004245?ref_topic=2919631).
|
||||||
* Before and after image size once an image is downloaded. These values are rounded to the nearest
|
* Before and after image size once an image is downloaded. These values are rounded to the nearest
|
||||||
kilobyte.
|
kilobyte.
|
||||||
|
* If install is available, when Squoosh is installed, and what method was used to install Squoosh.
|
||||||
|
|
||||||
Image compression is handled locally; no additional data is sent to the server.
|
Image compression is handled locally; no additional data is sent to the server.
|
||||||
|
|
||||||
|
|||||||
@@ -11,25 +11,25 @@ crate-type = ["cdylib"]
|
|||||||
default = ["console_error_panic_hook", "wee_alloc"]
|
default = ["console_error_panic_hook", "wee_alloc"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cfg-if = "0.1.10"
|
cfg-if = "0.1.2"
|
||||||
wasm-bindgen = "0.2.63"
|
wasm-bindgen = "0.2.38"
|
||||||
resize = "0.4.0"
|
resize = "0.3.0"
|
||||||
|
|
||||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
# logging them with `console.error`. This is great for development, but requires
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||||
# code size when deploying.
|
# code size when deploying.
|
||||||
console_error_panic_hook = { version = "0.1.6", optional = true }
|
console_error_panic_hook = { version = "0.1.1", optional = true }
|
||||||
|
|
||||||
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||||
# compared to the default allocator's ~10K. It is slower than the default
|
# compared to the default allocator's ~10K. It is slower than the default
|
||||||
# allocator, however.
|
# allocator, however.
|
||||||
#
|
#
|
||||||
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
||||||
wee_alloc = { version = "0.4.5", optional = true }
|
wee_alloc = { version = "0.4.2", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = "0.3.13"
|
wasm-bindgen-test = "0.2"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
# Tell `rustc` to optimize for small code size.
|
# Tell `rustc` to optimize for small code size.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ RUN rustup target add wasm32-unknown-unknown
|
|||||||
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
|
|
||||||
RUN mkdir /opt/wabt && \
|
RUN mkdir /opt/wabt && \
|
||||||
curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.15/wabt-1.0.15-linux.tar.gz | tar -xzf - -C /opt/wabt --strip 1
|
curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.11/wabt-1.0.11-linux.tar.gz | tar -xzf - -C /opt/wabt --strip 1
|
||||||
|
|
||||||
ENV PATH="/opt/wabt:${PATH}"
|
ENV PATH="/opt/wabt:${PATH}"
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|||||||
@@ -11,5 +11,5 @@
|
|||||||
],
|
],
|
||||||
"module": "resize.js",
|
"module": "resize.js",
|
||||||
"types": "resize.d.ts",
|
"types": "resize.d.ts",
|
||||||
"sideEffects": false
|
"sideEffects": "false"
|
||||||
}
|
}
|
||||||
1
codecs/resize/pkg/resize.d.ts
vendored
1
codecs/resize/pkg/resize.d.ts
vendored
@@ -1,5 +1,4 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
|
||||||
/**
|
/**
|
||||||
* @param {Uint8Array} input_image
|
* @param {Uint8Array} input_image
|
||||||
* @param {number} input_width
|
* @param {number} input_width
|
||||||
|
|||||||
@@ -1,2 +1,50 @@
|
|||||||
import * as wasm from "./resize_bg.wasm";
|
import * as wasm from './resize_bg.wasm';
|
||||||
export * from "./resize_bg.js";
|
|
||||||
|
let cachegetUint8Memory = null;
|
||||||
|
function getUint8Memory() {
|
||||||
|
if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetUint8Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
let WASM_VECTOR_LEN = 0;
|
||||||
|
|
||||||
|
function passArray8ToWasm(arg) {
|
||||||
|
const ptr = wasm.__wbindgen_malloc(arg.length * 1);
|
||||||
|
getUint8Memory().set(arg, ptr / 1);
|
||||||
|
WASM_VECTOR_LEN = arg.length;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachegetInt32Memory = null;
|
||||||
|
function getInt32Memory() {
|
||||||
|
if (cachegetInt32Memory === null || cachegetInt32Memory.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetInt32Memory = new Int32Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetInt32Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArrayU8FromWasm(ptr, len) {
|
||||||
|
return getUint8Memory().subarray(ptr / 1, ptr / 1 + len);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} input_image
|
||||||
|
* @param {number} input_width
|
||||||
|
* @param {number} input_height
|
||||||
|
* @param {number} output_width
|
||||||
|
* @param {number} output_height
|
||||||
|
* @param {number} typ_idx
|
||||||
|
* @param {boolean} premultiply
|
||||||
|
* @param {boolean} color_space_conversion
|
||||||
|
* @returns {Uint8Array}
|
||||||
|
*/
|
||||||
|
export function resize(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion) {
|
||||||
|
const retptr = 8;
|
||||||
|
const ret = wasm.resize(retptr, passArray8ToWasm(input_image), WASM_VECTOR_LEN, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion);
|
||||||
|
const memi32 = getInt32Memory();
|
||||||
|
const v0 = getArrayU8FromWasm(memi32[retptr / 4 + 0], memi32[retptr / 4 + 1]).slice();
|
||||||
|
wasm.__wbindgen_free(memi32[retptr / 4 + 0], memi32[retptr / 4 + 1] * 1);
|
||||||
|
return v0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
3
codecs/resize/pkg/resize_bg.d.ts
vendored
3
codecs/resize/pkg/resize_bg.d.ts
vendored
@@ -1,6 +1,5 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
|
||||||
export const memory: WebAssembly.Memory;
|
export const memory: WebAssembly.Memory;
|
||||||
export function resize(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number): void;
|
|
||||||
export function __wbindgen_malloc(a: number): number;
|
export function __wbindgen_malloc(a: number): number;
|
||||||
export function __wbindgen_free(a: number, b: number): void;
|
export function __wbindgen_free(a: number, b: number): void;
|
||||||
|
export function resize(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number): void;
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import * as wasm from './resize_bg.wasm';
|
|
||||||
|
|
||||||
let cachegetUint8Memory0 = null;
|
|
||||||
function getUint8Memory0() {
|
|
||||||
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
|
|
||||||
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
|
||||||
}
|
|
||||||
return cachegetUint8Memory0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let WASM_VECTOR_LEN = 0;
|
|
||||||
|
|
||||||
function passArray8ToWasm0(arg, malloc) {
|
|
||||||
const ptr = malloc(arg.length * 1);
|
|
||||||
getUint8Memory0().set(arg, ptr / 1);
|
|
||||||
WASM_VECTOR_LEN = arg.length;
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cachegetInt32Memory0 = null;
|
|
||||||
function getInt32Memory0() {
|
|
||||||
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
|
|
||||||
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
|
||||||
}
|
|
||||||
return cachegetInt32Memory0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getArrayU8FromWasm0(ptr, len) {
|
|
||||||
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param {Uint8Array} input_image
|
|
||||||
* @param {number} input_width
|
|
||||||
* @param {number} input_height
|
|
||||||
* @param {number} output_width
|
|
||||||
* @param {number} output_height
|
|
||||||
* @param {number} typ_idx
|
|
||||||
* @param {boolean} premultiply
|
|
||||||
* @param {boolean} color_space_conversion
|
|
||||||
* @returns {Uint8Array}
|
|
||||||
*/
|
|
||||||
export function resize(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion) {
|
|
||||||
var ptr0 = passArray8ToWasm0(input_image, wasm.__wbindgen_malloc);
|
|
||||||
var len0 = WASM_VECTOR_LEN;
|
|
||||||
wasm.resize(8, ptr0, len0, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion);
|
|
||||||
var r0 = getInt32Memory0()[8 / 4 + 0];
|
|
||||||
var r1 = getInt32Memory0()[8 / 4 + 1];
|
|
||||||
var v1 = getArrayU8FromWasm0(r0, r1).slice();
|
|
||||||
wasm.__wbindgen_free(r0, r1 * 1);
|
|
||||||
return v1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Binary file not shown.
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "squoosh",
|
"name": "squoosh",
|
||||||
"version": "1.9.1",
|
"version": "1.10.4",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -10240,7 +10240,7 @@
|
|||||||
},
|
},
|
||||||
"os-locale": {
|
"os-locale": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
|
||||||
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
|
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -12869,7 +12869,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "squoosh",
|
"name": "squoosh",
|
||||||
"version": "1.9.1",
|
"version": "1.10.4",
|
||||||
"license": "apache-2.0",
|
"license": "apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack-dev-server --host 0.0.0.0 --hot",
|
"start": "webpack-dev-server --host 0.0.0.0 --hot",
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ export default class WebPEncoderOptions extends Component<Props, State> {
|
|||||||
value={options.sns_strength}
|
value={options.sns_strength}
|
||||||
onInput={this.onChange}
|
onInput={this.onChange}
|
||||||
>
|
>
|
||||||
Spacial noise shaping:
|
Spatial noise shaping:
|
||||||
</Range>
|
</Range>
|
||||||
</div>
|
</div>
|
||||||
<label class={style.optionTextFirst}>
|
<label class={style.optionTextFirst}>
|
||||||
|
|||||||
@@ -41,17 +41,31 @@ const demos = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const installButtonSource = 'introInstallButton';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onFile: (file: File | Fileish) => void;
|
onFile: (file: File | Fileish) => void;
|
||||||
showSnack: SnackBarElement['showSnackbar'];
|
showSnack: SnackBarElement['showSnackbar'];
|
||||||
}
|
}
|
||||||
interface State {
|
interface State {
|
||||||
fetchingDemoIndex?: number;
|
fetchingDemoIndex?: number;
|
||||||
|
beforeInstallEvent?: BeforeInstallPromptEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Intro extends Component<Props, State> {
|
export default class Intro extends Component<Props, State> {
|
||||||
state: State = {};
|
state: State = {};
|
||||||
private fileInput?: HTMLInputElement;
|
private fileInput?: HTMLInputElement;
|
||||||
|
private installingViaButton = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
private resetFileInput() {
|
private resetFileInput() {
|
||||||
@@ -90,7 +104,52 @@ export default class Intro extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ }: Props, { fetchingDemoIndex }: State) {
|
@bind
|
||||||
|
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.
|
||||||
|
ga('send', 'event', 'pwa-install', 'available');
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
private async onInstallClick(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;
|
||||||
|
ga('send', 'event', 'pwa-install', installButtonSource, outcome);
|
||||||
|
|
||||||
|
// If the prompt was dismissed, we aren't going to install via the button.
|
||||||
|
if (outcome === 'dismissed') {
|
||||||
|
this.installingViaButton = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
private onAppInstalled() {
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
this.installingViaButton = false;
|
||||||
|
// We don't need the install button, if it's shown
|
||||||
|
this.setState({ beforeInstallEvent: undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
render({ }: Props, { fetchingDemoIndex, beforeInstallEvent }: State) {
|
||||||
return (
|
return (
|
||||||
<div class={style.intro}>
|
<div class={style.intro}>
|
||||||
<div>
|
<div>
|
||||||
@@ -120,7 +179,7 @@ export default class Intro extends Component<Props, State> {
|
|||||||
<img class={style.demoIcon} src={demo.iconUrl} alt="" decoding="async" />
|
<img class={style.demoIcon} src={demo.iconUrl} alt="" decoding="async" />
|
||||||
{fetchingDemoIndex === i &&
|
{fetchingDemoIndex === i &&
|
||||||
<div class={style.demoLoading}>
|
<div class={style.demoLoading}>
|
||||||
<loading-spinner class={style.demoLoadingSpinner}/>
|
<loading-spinner class={style.demoLoadingSpinner} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -132,6 +191,15 @@ export default class Intro extends Component<Props, State> {
|
|||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{beforeInstallEvent &&
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={style.installButton}
|
||||||
|
onClick={this.onInstallClick}
|
||||||
|
>
|
||||||
|
Install
|
||||||
|
</button>
|
||||||
|
}
|
||||||
<ul class={style.relatedLinks}>
|
<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/">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/issues">Report a bug</a></li>
|
||||||
|
|||||||
32
src/components/intro/missing-types.d.ts
vendored
Normal file
32
src/components/intro/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* The BeforeInstallPromptEvent is fired at the Window.onbeforeinstallprompt handler
|
||||||
|
* before a user is prompted to "install" a web site to a home screen on mobile.
|
||||||
|
*/
|
||||||
|
interface BeforeInstallPromptEvent extends Event {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of DOMString items containing the platforms on which the event was dispatched.
|
||||||
|
* This is provided for user agents that want to present a choice of versions to the user such as,
|
||||||
|
* for example, "web" or "play" which would allow the user to chose between a web version or
|
||||||
|
* an Android version.
|
||||||
|
*/
|
||||||
|
readonly platforms: Array<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Promise that resolves to a DOMString containing either "accepted" or "dismissed".
|
||||||
|
*/
|
||||||
|
readonly userChoice: Promise<{
|
||||||
|
outcome: 'accepted' | 'dismissed',
|
||||||
|
platform: string
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows a developer to show the install prompt at a time of their own choosing.
|
||||||
|
* This method returns a Promise.
|
||||||
|
*/
|
||||||
|
prompt(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WindowEventMap {
|
||||||
|
"beforeinstallprompt": BeforeInstallPromptEvent;
|
||||||
|
}
|
||||||
@@ -170,6 +170,30 @@
|
|||||||
--color: #fff;
|
--color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.install-button {
|
||||||
|
composes: unbutton from '../../lib/util.scss';
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
padding: 14px;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
|
||||||
|
animation: fade-in .3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
.related-links {
|
.related-links {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
10
src/index.ts
10
src/index.ts
@@ -13,11 +13,19 @@ if (!('customElements' in self)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof PRERENDER === 'undefined') {
|
if (typeof PRERENDER === 'undefined') {
|
||||||
|
// Determine the current display mode.
|
||||||
|
let displayMode = 'browser';
|
||||||
|
const mqStandAlone = '(display-mode: standalone)';
|
||||||
|
if (navigator.standalone || window.matchMedia(mqStandAlone).matches) {
|
||||||
|
displayMode = 'standalone';
|
||||||
|
}
|
||||||
|
// Setup analytics
|
||||||
window.ga = window.ga || ((...args) => (ga.q = ga.q || []).push(args));
|
window.ga = window.ga || ((...args) => (ga.q = ga.q || []).push(args));
|
||||||
ga('create', 'UA-128752250-1', 'auto');
|
ga('create', 'UA-128752250-1', 'auto');
|
||||||
ga('set', 'transport', 'beacon');
|
ga('set', 'transport', 'beacon');
|
||||||
|
ga('set', 'dimension1', displayMode);
|
||||||
ga('send', 'pageview');
|
ga('send', 'pageview');
|
||||||
// Load the GA script
|
// Load the GA script
|
||||||
const s = document.createElement('script');
|
const s = document.createElement('script');
|
||||||
s.src = 'https://www.google-analytics.com/analytics.js';
|
s.src = 'https://www.google-analytics.com/analytics.js';
|
||||||
document.head!.appendChild(s);
|
document.head!.appendChild(s);
|
||||||
|
|||||||
4
src/missing-types.d.ts
vendored
4
src/missing-types.d.ts
vendored
@@ -39,3 +39,7 @@ declare var ga: {
|
|||||||
(...args: any[]): void;
|
(...args: any[]): void;
|
||||||
q: any[];
|
q: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface Navigator {
|
||||||
|
readonly standalone: boolean;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user