ambient-display 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/client/_app/immutable/assets/0.Dl9__I4E.css +1 -0
- package/build/client/_app/immutable/assets/0.Dl9__I4E.css.br +0 -0
- package/build/client/_app/immutable/assets/0.Dl9__I4E.css.gz +0 -0
- package/build/client/_app/immutable/assets/2.BtzFOBtk.css +1 -0
- package/build/client/_app/immutable/assets/2.BtzFOBtk.css.br +0 -0
- package/build/client/_app/immutable/assets/2.BtzFOBtk.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.BvcZlbFP.css +1 -0
- package/build/client/_app/immutable/assets/3.BvcZlbFP.css.br +0 -0
- package/build/client/_app/immutable/assets/3.BvcZlbFP.css.gz +0 -0
- package/build/client/_app/immutable/assets/LoadingIndicator.D0m6rSKQ.css +1 -0
- package/build/client/_app/immutable/assets/LoadingIndicator.D0m6rSKQ.css.br +0 -0
- package/build/client/_app/immutable/assets/LoadingIndicator.D0m6rSKQ.css.gz +0 -0
- package/build/client/_app/immutable/assets/PlayingTracker.9uM5nyWg.css +1 -0
- package/build/client/_app/immutable/assets/PlayingTracker.9uM5nyWg.css.br +0 -0
- package/build/client/_app/immutable/assets/PlayingTracker.9uM5nyWg.css.gz +0 -0
- package/build/client/_app/immutable/assets/_layout.Dl9__I4E.css +1 -0
- package/build/client/_app/immutable/assets/_layout.Dl9__I4E.css.br +0 -0
- package/build/client/_app/immutable/assets/_layout.Dl9__I4E.css.gz +0 -0
- package/build/client/_app/immutable/assets/_page.BtzFOBtk.css +1 -0
- package/build/client/_app/immutable/assets/_page.BtzFOBtk.css.br +0 -0
- package/build/client/_app/immutable/assets/_page.BtzFOBtk.css.gz +0 -0
- package/build/client/_app/immutable/assets/_page.BvcZlbFP.css +1 -0
- package/build/client/_app/immutable/assets/_page.BvcZlbFP.css.br +0 -0
- package/build/client/_app/immutable/assets/_page.BvcZlbFP.css.gz +0 -0
- package/build/client/_app/immutable/chunks/LoadingIndicator.COVTk436.js +1 -0
- package/build/client/_app/immutable/chunks/LoadingIndicator.COVTk436.js.br +0 -0
- package/build/client/_app/immutable/chunks/LoadingIndicator.COVTk436.js.gz +0 -0
- package/build/client/_app/immutable/chunks/PlayingTracker.Cymhk9j5.js +5 -0
- package/build/client/_app/immutable/chunks/PlayingTracker.Cymhk9j5.js.br +0 -0
- package/build/client/_app/immutable/chunks/PlayingTracker.Cymhk9j5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/disclose-version.N57b1q78.js +1 -0
- package/build/client/_app/immutable/chunks/disclose-version.N57b1q78.js.br +0 -0
- package/build/client/_app/immutable/chunks/disclose-version.N57b1q78.js.gz +0 -0
- package/build/client/_app/immutable/chunks/entry.CTcSu7Oh.js +3 -0
- package/build/client/_app/immutable/chunks/entry.CTcSu7Oh.js.br +0 -0
- package/build/client/_app/immutable/chunks/entry.CTcSu7Oh.js.gz +0 -0
- package/build/client/_app/immutable/chunks/index-client.DV07uIiZ.js +1 -0
- package/build/client/_app/immutable/chunks/index-client.DV07uIiZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/index-client.DV07uIiZ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/index.DFULH2AN.js +1 -0
- package/build/client/_app/immutable/chunks/index.DFULH2AN.js.br +0 -0
- package/build/client/_app/immutable/chunks/index.DFULH2AN.js.gz +0 -0
- package/build/client/_app/immutable/chunks/index.DLR8Bvs6.js +4 -0
- package/build/client/_app/immutable/chunks/index.DLR8Bvs6.js.br +0 -0
- package/build/client/_app/immutable/chunks/index.DLR8Bvs6.js.gz +0 -0
- package/build/client/_app/immutable/chunks/legacy.DZgMwYuQ.js +1 -0
- package/build/client/_app/immutable/chunks/legacy.DZgMwYuQ.js.br +0 -0
- package/build/client/_app/immutable/chunks/legacy.DZgMwYuQ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/props.DYfFZGja.js +1 -0
- package/build/client/_app/immutable/chunks/props.DYfFZGja.js.br +0 -0
- package/build/client/_app/immutable/chunks/props.DYfFZGja.js.gz +0 -0
- package/build/client/_app/immutable/chunks/snippet.BVSCguOu.js +1 -0
- package/build/client/_app/immutable/chunks/snippet.BVSCguOu.js.br +0 -0
- package/build/client/_app/immutable/chunks/snippet.BVSCguOu.js.gz +0 -0
- package/build/client/_app/immutable/chunks/this.EZLWgc5v.js +1 -0
- package/build/client/_app/immutable/chunks/this.EZLWgc5v.js.br +0 -0
- package/build/client/_app/immutable/chunks/this.EZLWgc5v.js.gz +0 -0
- package/build/client/_app/immutable/chunks/utils.BXcQV2KO.js +1 -0
- package/build/client/_app/immutable/chunks/utils.BXcQV2KO.js.br +0 -0
- package/build/client/_app/immutable/chunks/utils.BXcQV2KO.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.DHMJZPqS.js +2 -0
- package/build/client/_app/immutable/entry/app.DHMJZPqS.js.br +0 -0
- package/build/client/_app/immutable/entry/app.DHMJZPqS.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BGJKS9mR.js +1 -0
- package/build/client/_app/immutable/entry/start.BGJKS9mR.js.br +2 -0
- package/build/client/_app/immutable/entry/start.BGJKS9mR.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.DaJoi3FK.js +2 -0
- package/build/client/_app/immutable/nodes/0.DaJoi3FK.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.DaJoi3FK.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.0EwG4xPT.js +1 -0
- package/build/client/_app/immutable/nodes/1.0EwG4xPT.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.0EwG4xPT.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.CIcrK0Mf.js +1 -0
- package/build/client/_app/immutable/nodes/2.CIcrK0Mf.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CIcrK0Mf.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.vzk8SJSG.js +1 -0
- package/build/client/_app/immutable/nodes/3.vzk8SJSG.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.vzk8SJSG.js.gz +0 -0
- package/build/client/_app/version.json +1 -0
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/client/manifest.json.br +0 -0
- package/build/client/manifest.json.gz +0 -0
- package/build/env.js +45 -0
- package/build/handler.js +1375 -0
- package/build/index.js +334 -0
- package/build/server/chunks/0-D_yciAvo.js +91 -0
- package/build/server/chunks/0-D_yciAvo.js.map +1 -0
- package/build/server/chunks/1-BA-qTHOy.js +9 -0
- package/build/server/chunks/1-BA-qTHOy.js.map +1 -0
- package/build/server/chunks/2-DPsIhdeI.js +9 -0
- package/build/server/chunks/2-DPsIhdeI.js.map +1 -0
- package/build/server/chunks/3-CZREXwZu.js +9 -0
- package/build/server/chunks/3-CZREXwZu.js.map +1 -0
- package/build/server/chunks/LoadingIndicator-CTUFEPkL.js +10 -0
- package/build/server/chunks/LoadingIndicator-CTUFEPkL.js.map +1 -0
- package/build/server/chunks/LoadingIndicator.svelte_svelte_type_style_lang-CVdBHA1v.js +172 -0
- package/build/server/chunks/LoadingIndicator.svelte_svelte_type_style_lang-CVdBHA1v.js.map +1 -0
- package/build/server/chunks/PlayingTracker-BIq1bdv_.js +18 -0
- package/build/server/chunks/PlayingTracker-BIq1bdv_.js.map +1 -0
- package/build/server/chunks/_layout.svelte-BoFI04Ng.js +24 -0
- package/build/server/chunks/_layout.svelte-BoFI04Ng.js.map +1 -0
- package/build/server/chunks/_page.svelte-C3Mw7ZhL.js +115 -0
- package/build/server/chunks/_page.svelte-C3Mw7ZhL.js.map +1 -0
- package/build/server/chunks/_page.svelte-CzGyTA7b.js +368 -0
- package/build/server/chunks/_page.svelte-CzGyTA7b.js.map +1 -0
- package/build/server/chunks/error.svelte-BZE1ioPX.js +116 -0
- package/build/server/chunks/error.svelte-BZE1ioPX.js.map +1 -0
- package/build/server/chunks/exports-DAjI6ZSp.js +125 -0
- package/build/server/chunks/exports-DAjI6ZSp.js.map +1 -0
- package/build/server/chunks/index2-BA59f76P.js +1214 -0
- package/build/server/chunks/index2-BA59f76P.js.map +1 -0
- package/build/server/index.js +4868 -0
- package/build/server/index.js.map +1 -0
- package/build/server/manifest.js +50 -0
- package/build/server/manifest.js.map +1 -0
- package/build/shims.js +32 -0
- package/package.json +9 -2
- package/server/index.js +4 -0
- package/server/spotify_auth.json +1 -0
- package/.prettierignore +0 -4
- package/.prettierrc +0 -17
- package/CHANGELOG.md +0 -34
- package/env.template +0 -2
- package/eslint.config.js +0 -24
- package/jsconfig.json +0 -19
- package/screenshot.png +0 -0
- package/src/app.d.ts +0 -13
- package/src/app.html +0 -12
- package/src/lib/actions/qr.svelte.js +0 -23
- package/src/lib/comms.js +0 -25
- package/src/lib/components/AuthenticateTrigger.svelte +0 -74
- package/src/lib/components/Controls.svelte +0 -91
- package/src/lib/components/ImageLoad.svelte +0 -79
- package/src/lib/components/LoadingIndicator.svelte +0 -75
- package/src/lib/components/MediaItem.svelte +0 -75
- package/src/lib/components/PlayingTracker.svelte +0 -94
- package/src/lib/components/ResultsList.svelte +0 -80
- package/src/lib/components/Toast/Item.svelte +0 -71
- package/src/lib/components/Toast/Manager.svelte +0 -34
- package/src/lib/icons/disc.svg +0 -1
- package/src/lib/index.js +0 -1
- package/src/lib/store.js +0 -146
- package/src/lib/styles.scss +0 -166
- package/src/lib/toast.js +0 -57
- package/src/lib/utils.js +0 -723
- package/src/routes/+layout.server.js +0 -25
- package/src/routes/+layout.svelte +0 -72
- package/src/routes/+page.svelte +0 -381
- package/src/routes/player/+page.svelte +0 -294
- package/svelte.config.js +0 -19
- package/tools/BuildManifest.js +0 -87
- package/vite.config.js +0 -46
- /package/{static → build/client}/favicon.ico +0 -0
- /package/{static → build/client}/favicon.png +0 -0
- /package/{static → build/client}/icons/144.favicon.png +0 -0
- /package/{static → build/client}/icons/168.favicon.png +0 -0
- /package/{static → build/client}/icons/192.favicon.png +0 -0
- /package/{static → build/client}/icons/48.favicon.png +0 -0
- /package/{static → build/client}/icons/72.favicon.png +0 -0
- /package/{static → build/client}/icons/96.favicon.png +0 -0
- /package/{static → build/client}/manifest.json +0 -0
package/src/lib/utils.js
DELETED
|
@@ -1,723 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A performant version of a function call, used to keep functions
|
|
3
|
-
* running at 60fps
|
|
4
|
-
*
|
|
5
|
-
* @param {function} cb
|
|
6
|
-
* @returns {function}
|
|
7
|
-
*/
|
|
8
|
-
export const tickUpdate = (cb) => {
|
|
9
|
-
let ticking = false;
|
|
10
|
-
|
|
11
|
-
const update = (e) => {
|
|
12
|
-
cb(e);
|
|
13
|
-
ticking = false;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const requestTick = (e) => {
|
|
17
|
-
if (!ticking) {
|
|
18
|
-
requestAnimationFrame(() => update(e));
|
|
19
|
-
ticking = true;
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
return requestTick;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Clamps a number
|
|
28
|
-
*
|
|
29
|
-
* @param {number} num
|
|
30
|
-
* @param {number} min
|
|
31
|
-
* @param {number} max
|
|
32
|
-
* @returns {number}
|
|
33
|
-
*/
|
|
34
|
-
export const clamp = (num, min, max) => {
|
|
35
|
-
return Math.min(Math.max(num, min), max);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Maps a number from 1 range to another. I cannot believe
|
|
40
|
-
* how much I've used this function in my lifetime
|
|
41
|
-
*
|
|
42
|
-
* @param {number} value
|
|
43
|
-
* @param {number} x1
|
|
44
|
-
* @param {number} y1
|
|
45
|
-
* @param {number} x2
|
|
46
|
-
* @param {number} y2
|
|
47
|
-
* @returns {number}
|
|
48
|
-
*/
|
|
49
|
-
export const mapRange = (value, x1, y1, x2, y2) => ((value - x1) * (y2 - x2)) / (y1 - x1) + x2;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* A simple linear interpolation function
|
|
53
|
-
*
|
|
54
|
-
* @param {number} v0 Lower value
|
|
55
|
-
* @param {number} v1 Target Value
|
|
56
|
-
* @param {*} t
|
|
57
|
-
* @returns
|
|
58
|
-
*/
|
|
59
|
-
export const lerp = (v0, v1, t) => {
|
|
60
|
-
return v0 * (1 - t) + v1 * t;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* A promise version of setTimeout
|
|
65
|
-
*
|
|
66
|
-
* @param {number} time
|
|
67
|
-
* @param {boolean} error
|
|
68
|
-
* @returns {Promise<void>}
|
|
69
|
-
*/
|
|
70
|
-
export const timer = (time = 2000, error = false) => {
|
|
71
|
-
return new Promise((resolve, reject) => {
|
|
72
|
-
setTimeout(() => {
|
|
73
|
-
if (error) {
|
|
74
|
-
reject();
|
|
75
|
-
} else {
|
|
76
|
-
resolve();
|
|
77
|
-
}
|
|
78
|
-
}, time);
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Serialised object of form elements
|
|
84
|
-
*
|
|
85
|
-
* @param {FormData} fd
|
|
86
|
-
* @returns {object}
|
|
87
|
-
*/
|
|
88
|
-
export const formToObject = (fd) => {
|
|
89
|
-
return [...fd.entries()].reduce(
|
|
90
|
-
(prev, curr) => ({
|
|
91
|
-
...prev,
|
|
92
|
-
[curr[0]]: curr[1]
|
|
93
|
-
}),
|
|
94
|
-
{}
|
|
95
|
-
);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Convenience function that registers the 2 window events needed
|
|
100
|
-
* to listen for window resizes
|
|
101
|
-
*
|
|
102
|
-
* @param {function} cb
|
|
103
|
-
* @returns {function} Unlisten
|
|
104
|
-
*/
|
|
105
|
-
export const onWindowResize = (cb) => {
|
|
106
|
-
window.addEventListener('resize', cb, {
|
|
107
|
-
passive: true
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
window.addEventListener('orientationchange', cb, {
|
|
111
|
-
passive: true
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
return () => {
|
|
115
|
-
window.removeEventListener('resize', cb);
|
|
116
|
-
window.removeEventListener('orientationchange', cb);
|
|
117
|
-
};
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Registers a 'bootleg' vh unit, to be used in css like
|
|
122
|
-
* var(--vh), based off of actual dimensions not necessarily what
|
|
123
|
-
* the real css value gives
|
|
124
|
-
*
|
|
125
|
-
* Should be superceded by lvh / dvh / svh
|
|
126
|
-
*
|
|
127
|
-
* @returns {function} Unlisten
|
|
128
|
-
*/
|
|
129
|
-
export const registerBootlegVH = () => {
|
|
130
|
-
// Cached value of window outer height
|
|
131
|
-
let wh = 0;
|
|
132
|
-
|
|
133
|
-
const setVh = () => {
|
|
134
|
-
const nh = window.outerHeight;
|
|
135
|
-
|
|
136
|
-
// If window outer height doesnt match the browser window really has changed
|
|
137
|
-
// not just the nav bar disappearing
|
|
138
|
-
if (wh !== nh) {
|
|
139
|
-
wh = nh;
|
|
140
|
-
|
|
141
|
-
// Set a 'global' property of 1vh to use in your css like
|
|
142
|
-
// calc(var(--vh) * WHATEVER_NUM)
|
|
143
|
-
document.documentElement.style.setProperty('--vh', `${window.innerHeight / 100}px`);
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const cb = tickUpdate(() => {
|
|
148
|
-
setVh();
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
setVh();
|
|
152
|
-
|
|
153
|
-
return onWindowResize(cb);
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Returns a debounced version of the function, so that it runs
|
|
158
|
-
* only after X seconds of not being called
|
|
159
|
-
*
|
|
160
|
-
* @param {function} cb
|
|
161
|
-
* @param {number} time
|
|
162
|
-
* @returns {function}
|
|
163
|
-
*/
|
|
164
|
-
export const debounce = (cb, time = 1000) => {
|
|
165
|
-
let timer = 0;
|
|
166
|
-
|
|
167
|
-
return function () {
|
|
168
|
-
clearTimeout(timer);
|
|
169
|
-
timer = setTimeout(() => {
|
|
170
|
-
cb(...arguments);
|
|
171
|
-
}, time);
|
|
172
|
-
};
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Returns a throttled version of the function, so that it runs only
|
|
177
|
-
* a maximum of X seconds
|
|
178
|
-
*
|
|
179
|
-
* @param {function} cb
|
|
180
|
-
* @param {number} time
|
|
181
|
-
* @returns {function}
|
|
182
|
-
*/
|
|
183
|
-
export const throttle = (cb, time = 1000) => {
|
|
184
|
-
let timer;
|
|
185
|
-
|
|
186
|
-
return function () {
|
|
187
|
-
if (timer) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
timer = setTimeout(() => {
|
|
192
|
-
cb(...arguments);
|
|
193
|
-
timer = undefined;
|
|
194
|
-
}, time);
|
|
195
|
-
};
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* A promise to load an image element
|
|
200
|
-
*
|
|
201
|
-
* @param {string} src
|
|
202
|
-
* @returns {Promise<HTMLImageElement>}
|
|
203
|
-
*/
|
|
204
|
-
export const loadImageElement = (src) => {
|
|
205
|
-
return new Promise((resolve, reject) => {
|
|
206
|
-
const img = new Image();
|
|
207
|
-
img.onload = () => resolve(img);
|
|
208
|
-
img.onerror = () => reject();
|
|
209
|
-
img.src = src;
|
|
210
|
-
});
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* A promise to load an image as a blob url
|
|
215
|
-
*
|
|
216
|
-
* @param {string} src
|
|
217
|
-
* @param {function} onSignal A callback to get an abort signal
|
|
218
|
-
* @returns {Promise<string>}
|
|
219
|
-
*/
|
|
220
|
-
export const loadImage = (src, onSignal = () => false) => {
|
|
221
|
-
const controller = new AbortController();
|
|
222
|
-
const { signal } = controller;
|
|
223
|
-
|
|
224
|
-
onSignal(controller);
|
|
225
|
-
|
|
226
|
-
return fetch(src, { signal })
|
|
227
|
-
.then((resp) => resp.blob())
|
|
228
|
-
.then((blob) => URL.createObjectURL(blob));
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* A function to register a change listener on a media query
|
|
233
|
-
*
|
|
234
|
-
* @param {string} query
|
|
235
|
-
* @param {function} cb
|
|
236
|
-
* @returns {function} Unlisten
|
|
237
|
-
*/
|
|
238
|
-
export const breakpointListen = (query, cb) => {
|
|
239
|
-
const mq = window.matchMedia(query);
|
|
240
|
-
const onCall = (ev) => {
|
|
241
|
-
cb(ev.matches);
|
|
242
|
-
};
|
|
243
|
-
mq.addListener(onCall);
|
|
244
|
-
|
|
245
|
-
cb(mq.matches);
|
|
246
|
-
|
|
247
|
-
return () => mq.removeListener(onCall);
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Adds an event listener and returns a cb to unlisten
|
|
252
|
-
*
|
|
253
|
-
* @param {HTMLElement | Window | Document} el
|
|
254
|
-
* @param {string} evt
|
|
255
|
-
* @param {function} cb
|
|
256
|
-
* @param {object} opts
|
|
257
|
-
* @returns {() => void)} Unlisten
|
|
258
|
-
*/
|
|
259
|
-
export const listenCb = (el, evt, cb, opts = false) => {
|
|
260
|
-
el.addEventListener(evt, cb, opts);
|
|
261
|
-
return () => el.removeEventListener(evt, cb);
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Get random number between 2 numbers
|
|
266
|
-
*
|
|
267
|
-
* @param {any[]} arr
|
|
268
|
-
* @returns {any}
|
|
269
|
-
*/
|
|
270
|
-
export const random = (arr) => {
|
|
271
|
-
return arr[Math.floor(Math.random() * arr.length)];
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Get random number between 2 numbers
|
|
276
|
-
*
|
|
277
|
-
* @param {number} min
|
|
278
|
-
* @param {number} max
|
|
279
|
-
* @returns {number}
|
|
280
|
-
*/
|
|
281
|
-
export const randomBetween = (min, max) => {
|
|
282
|
-
return Math.random() * (max - min) + min;
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Randomises array
|
|
287
|
-
*
|
|
288
|
-
* @param {array} array
|
|
289
|
-
* @returns {array}
|
|
290
|
-
*/
|
|
291
|
-
export const shuffle = (array) => {
|
|
292
|
-
let currentIndex = array.length,
|
|
293
|
-
randomIndex;
|
|
294
|
-
|
|
295
|
-
// While there remain elements to shuffle...
|
|
296
|
-
while (currentIndex != 0) {
|
|
297
|
-
// Pick a remaining element...
|
|
298
|
-
randomIndex = Math.floor(Math.random() * currentIndex);
|
|
299
|
-
currentIndex--;
|
|
300
|
-
|
|
301
|
-
// And swap it with the current element.
|
|
302
|
-
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return array;
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const TWEETS = [() => `Hey this is a tweet, https://mschf.com/`];
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* A function that will randomly return a constructed twitter intent link, to circumvent twitter
|
|
312
|
-
* flagging the same text as spam
|
|
313
|
-
*
|
|
314
|
-
* @param {function[]} options An array of functions that return a string, for the text of the tweet
|
|
315
|
-
* @param {Object} values Values that are passed into the tweet string for variables
|
|
316
|
-
* @returns {string} Twitter intent URL
|
|
317
|
-
*/
|
|
318
|
-
export const tweeter = (options = TWEETS, values = {}) => {
|
|
319
|
-
const generateTweet = () => {
|
|
320
|
-
return TWEETS[Math.floor(Math.random() * TWEETS.length)]();
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
return `https://twitter.com/intent/tweet?text=${encodeURIComponent(generateTweet())}`;
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Utility to help detect low power mode
|
|
328
|
-
*
|
|
329
|
-
* @returns {Promise<Boolean>}
|
|
330
|
-
*/
|
|
331
|
-
export const isLowPower = () => {
|
|
332
|
-
/**
|
|
333
|
-
* Sets up a promise for making sure the video loads before testing.
|
|
334
|
-
* Uses the suspend event which apparently fires regardless for when
|
|
335
|
-
* the video has finished downloading. Then sets a can play event listener
|
|
336
|
-
* to be double sure but also sets a timer, if the timer resolves (which
|
|
337
|
-
* on environments in which low power mode is off, should never happen) it
|
|
338
|
-
* can assume the video can't be played
|
|
339
|
-
*
|
|
340
|
-
* @param {HTMLVideoElement} el
|
|
341
|
-
* @returns
|
|
342
|
-
*/
|
|
343
|
-
const loadVideo = (el) => {
|
|
344
|
-
return new Promise((resolve, reject) => {
|
|
345
|
-
let timer = 0;
|
|
346
|
-
|
|
347
|
-
const onCanPlay = () => {
|
|
348
|
-
clearTimeout(timer);
|
|
349
|
-
|
|
350
|
-
//loadedmetadata not canplay for ios boohoo
|
|
351
|
-
el.removeEventListener('loadedmetadata', onCanPlay);
|
|
352
|
-
resolve(el);
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
const onSuspend = () => {
|
|
356
|
-
el.removeEventListener('suspend', onSuspend);
|
|
357
|
-
|
|
358
|
-
timer = setTimeout(() => {
|
|
359
|
-
reject('too long');
|
|
360
|
-
}, 500);
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
el.addEventListener('suspend', onSuspend);
|
|
364
|
-
el.addEventListener('loadedmetadata', onCanPlay);
|
|
365
|
-
el.load();
|
|
366
|
-
});
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
return new Promise((resolve, reject) => {
|
|
370
|
-
let videoEl = document.querySelector(`#lowpower`);
|
|
371
|
-
|
|
372
|
-
// Create a video element if the test one can't be found already mounted
|
|
373
|
-
if (!videoEl) {
|
|
374
|
-
// console.log("no video test creating");
|
|
375
|
-
videoEl = document.createElement('video');
|
|
376
|
-
videoEl.loop = true;
|
|
377
|
-
videoEl.playsInline = true;
|
|
378
|
-
videoEl.muted = true;
|
|
379
|
-
videoEl.id = `lowpower`;
|
|
380
|
-
// Very specifically dont add autoplay
|
|
381
|
-
// videoEl.autoplay = true;
|
|
382
|
-
|
|
383
|
-
const source = document.createElement('source');
|
|
384
|
-
source.type = `video/mp4`;
|
|
385
|
-
// 5kb
|
|
386
|
-
source.src = `data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACU1tZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2MyByMzA2MCA1ZGI2YWE2IC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyMSAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTIgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAACDmWIhAB//vSj+BTSaXUBsxCW6PzALg74FA/dgccVg8EGt2Axk/JyOG7stC2aelhG3YCVoMYV2/RArmL7h0CSik6m2NszuBRPfgJVVEdSBW9gY0RFBzNtpfAni35C88ncWv2vVr3OgW5fxUvf1kHhSL8kOfc4pR8/DB7NsbDd1WSarzAKL2PFpHv0I8wFitjhss2xi0KGby5UFyRLoTo4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`;
|
|
387
|
-
|
|
388
|
-
videoEl.appendChild(source);
|
|
389
|
-
|
|
390
|
-
document.body.appendChild(videoEl);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Needs to be displayed, some browsers will not play
|
|
394
|
-
// if display is none
|
|
395
|
-
videoEl.style.position = 'fixed';
|
|
396
|
-
videoEl.style.width = `100px`;
|
|
397
|
-
videoEl.style.zIndex = 100;
|
|
398
|
-
// videoEl.style.visibility = "hidden";
|
|
399
|
-
// videoEl.style.opacity = 0;
|
|
400
|
-
|
|
401
|
-
loadVideo(videoEl)
|
|
402
|
-
.then(() => resolve(videoEl))
|
|
403
|
-
.catch((err) => reject(err));
|
|
404
|
-
})
|
|
405
|
-
.then((el) => {
|
|
406
|
-
// Play the video which should be allowed as its
|
|
407
|
-
// muted and can play inline
|
|
408
|
-
return el
|
|
409
|
-
.play()
|
|
410
|
-
.then(() => false)
|
|
411
|
-
.catch((e) => {
|
|
412
|
-
console.error(e);
|
|
413
|
-
return true;
|
|
414
|
-
});
|
|
415
|
-
})
|
|
416
|
-
.catch(() => {
|
|
417
|
-
return true;
|
|
418
|
-
});
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
const LOCAL_STORAGE_PREFIX = 'mschf';
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Convenience feature for grabbing a value from the local storage
|
|
425
|
-
* with a default fallback if not present
|
|
426
|
-
*
|
|
427
|
-
* @param {string} key
|
|
428
|
-
* @param {any} defaultValue
|
|
429
|
-
* @param {function} transform
|
|
430
|
-
* @returns {string|any}
|
|
431
|
-
*/
|
|
432
|
-
export const getPersistedValue = (key, defaultValue, transform = (val) => val) => {
|
|
433
|
-
const item = localStorage.getItem([LOCAL_STORAGE_PREFIX, key].join('-'));
|
|
434
|
-
|
|
435
|
-
return item ? transform(item) : defaultValue;
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Saves a value to local storage
|
|
440
|
-
*
|
|
441
|
-
* @param {string} key
|
|
442
|
-
* @param {string} value
|
|
443
|
-
*/
|
|
444
|
-
export const persistValue = (key, value) => {
|
|
445
|
-
localStorage.setItem([LOCAL_STORAGE_PREFIX, key].join('-'), value);
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Convenience function to remove item with global prefix
|
|
450
|
-
*
|
|
451
|
-
* @param {string} key
|
|
452
|
-
*/
|
|
453
|
-
export const removePersistedValue = (key) => {
|
|
454
|
-
localStorage.removeItem([LOCAL_STORAGE_PREFIX, key].join('-'));
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Turns a ISO-like date string into a readable date format
|
|
459
|
-
*
|
|
460
|
-
* @param {string} dateString
|
|
461
|
-
* @returns {string}
|
|
462
|
-
*/
|
|
463
|
-
export const readableDate = (dateString) => {
|
|
464
|
-
const d = new Date(dateString);
|
|
465
|
-
|
|
466
|
-
return [d.getMonth() + 1, d.getDate()].map((t) => t.toString().padStart(2, '0')).join('.');
|
|
467
|
-
};
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Turns a ISO-like date string into a readable time format
|
|
471
|
-
*
|
|
472
|
-
* @param {string} dateString
|
|
473
|
-
* @returns {string}
|
|
474
|
-
*/
|
|
475
|
-
export const readableTime = (dateString) => {
|
|
476
|
-
const d = new Date(dateString);
|
|
477
|
-
let timeStr = new Intl.DateTimeFormat('en-US', {
|
|
478
|
-
timeZone: 'America/New_York',
|
|
479
|
-
hour: 'numeric',
|
|
480
|
-
hour12: true
|
|
481
|
-
}).format(d);
|
|
482
|
-
return `${timeStr.replace(' ', '')} ET`;
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* A promisified version of a requestAnimationFrame as a utility
|
|
487
|
-
*
|
|
488
|
-
* @returns {Promise<void>}
|
|
489
|
-
*/
|
|
490
|
-
export const singleRaf = () => {
|
|
491
|
-
return new Promise((resolve) => {
|
|
492
|
-
requestAnimationFrame(() => {
|
|
493
|
-
resolve();
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* A double version of the requestAnimationFrame promise
|
|
500
|
-
*
|
|
501
|
-
* @returns {Promise<void>}
|
|
502
|
-
*/
|
|
503
|
-
export const doubleRaf = async () => {
|
|
504
|
-
await singleRaf();
|
|
505
|
-
return singleRaf();
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
const KEYS = {
|
|
509
|
-
ESCAPE: 27
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* A function that registers a key listener for escaping
|
|
514
|
-
*
|
|
515
|
-
* @param {function} onEscape
|
|
516
|
-
* @returns {function} Unlisten
|
|
517
|
-
*/
|
|
518
|
-
export const registerExits = (onEscape) => {
|
|
519
|
-
const cb = (e) => {
|
|
520
|
-
if ([KEYS.ESCAPE].includes(e.keyCode)) {
|
|
521
|
-
onEscape();
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
return listenCb(document, 'keyup', cb);
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* A function for adding a listener to determine if an interaction was made outside
|
|
530
|
-
* an element of its descendents
|
|
531
|
-
*
|
|
532
|
-
* @param {HTMLElement} el
|
|
533
|
-
* @param {function} onClickOutside
|
|
534
|
-
* @param {function} [validator]
|
|
535
|
-
* @returns {function} Unlisten
|
|
536
|
-
*/
|
|
537
|
-
export const clickOutside = (
|
|
538
|
-
el,
|
|
539
|
-
onClickOutside,
|
|
540
|
-
validator // (el: HTMLElement, e: MouseEvent) => boolean
|
|
541
|
-
) => {
|
|
542
|
-
const cb = (e) => {
|
|
543
|
-
if (validator) {
|
|
544
|
-
if (validator(el, e)) {
|
|
545
|
-
onClickOutside();
|
|
546
|
-
}
|
|
547
|
-
} else if (e.target && e.target !== el && !el.contains(e.target)) {
|
|
548
|
-
onClickOutside();
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
const unlisten = listenCb(document, 'click', cb);
|
|
553
|
-
const unregisterExits = registerExits(onClickOutside);
|
|
554
|
-
|
|
555
|
-
return () => {
|
|
556
|
-
unregisterExits();
|
|
557
|
-
unlisten();
|
|
558
|
-
};
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
export const formatCurrency = (val, { division = 1, currency = 'USD', trim = true } = {}) => {
|
|
562
|
-
const formatted = new Intl.NumberFormat('en-US', {
|
|
563
|
-
style: 'currency',
|
|
564
|
-
currency
|
|
565
|
-
}).format(val / division);
|
|
566
|
-
|
|
567
|
-
return formatted.endsWith('.00') && trim ? formatted.split('.').shift() : formatted;
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
export const getBearerToken = (str) => {
|
|
571
|
-
const [header, token] = str.split(' ');
|
|
572
|
-
|
|
573
|
-
return token;
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
export const offscreenCanvasToUrl = async (canvas) => {
|
|
577
|
-
const blob = await canvas.convertToBlob();
|
|
578
|
-
|
|
579
|
-
return new Promise((resolve) => {
|
|
580
|
-
const fr = new FileReader();
|
|
581
|
-
fr.addEventListener('load', () => resolve(fr.result));
|
|
582
|
-
fr.readAsDataURL(blob);
|
|
583
|
-
});
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
/**
|
|
587
|
-
*
|
|
588
|
-
* @param {any} val
|
|
589
|
-
* @param {any[]} options
|
|
590
|
-
* @param {any} defaultVal
|
|
591
|
-
* @returns
|
|
592
|
-
*/
|
|
593
|
-
export function validate(val, options, defaultVal = null) {
|
|
594
|
-
return options.includes(val) ? val : (defaultVal ?? options[0]);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* An array unlisten function
|
|
599
|
-
*
|
|
600
|
-
* @param {function[]} arr
|
|
601
|
-
*/
|
|
602
|
-
export function u(arr) {
|
|
603
|
-
return () => arr.forEach((cb) => cb());
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
const EasingFunctions = Object.freeze({
|
|
607
|
-
// no easing, no acceleration
|
|
608
|
-
linear: (t) => t,
|
|
609
|
-
// accelerating from zero velocity
|
|
610
|
-
easeInQuad: (t) => t * t,
|
|
611
|
-
// decelerating to zero velocity
|
|
612
|
-
easeOutQuad: (t) => t * (2 - t),
|
|
613
|
-
// acceleration until halfway, then deceleration
|
|
614
|
-
easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
|
|
615
|
-
// accelerating from zero velocity
|
|
616
|
-
easeInCubic: (t) => t * t * t,
|
|
617
|
-
// decelerating to zero velocity
|
|
618
|
-
easeOutCubic: (t) => --t * t * t + 1,
|
|
619
|
-
// acceleration until halfway, then deceleration
|
|
620
|
-
easeInOutCubic: (t) => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),
|
|
621
|
-
// accelerating from zero velocity
|
|
622
|
-
easeInQuart: (t) => t * t * t * t,
|
|
623
|
-
// decelerating to zero velocity
|
|
624
|
-
easeOutQuart: (t) => 1 - --t * t * t * t,
|
|
625
|
-
// acceleration until halfway, then deceleration
|
|
626
|
-
easeInOutQuart: (t) => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t),
|
|
627
|
-
// accelerating from zero velocity
|
|
628
|
-
easeInQuint: (t) => t * t * t * t * t,
|
|
629
|
-
// decelerating to zero velocity
|
|
630
|
-
easeOutQuint: (t) => 1 + --t * t * t * t * t,
|
|
631
|
-
// acceleration until halfway, then deceleration
|
|
632
|
-
easeInOutQuint: (t) => (t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t)
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
*
|
|
637
|
-
* @param {number} top
|
|
638
|
-
* @param {{distancePerSecond?: number, easing?: keyof typeof EasingFunctions}} [options]
|
|
639
|
-
* @returns {Promise<void>}
|
|
640
|
-
*/
|
|
641
|
-
export function scrollAtRate(top, { distancePerSecond = 1000, easing = 'linear' } = {}) {
|
|
642
|
-
const windowY = window.scrollY;
|
|
643
|
-
const totalDistance = Math.abs(windowY - top);
|
|
644
|
-
const totalTime = (totalDistance / distancePerSecond) * 1000;
|
|
645
|
-
|
|
646
|
-
return scrollPromise(top, { duration: totalTime, easing });
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
*
|
|
651
|
-
* @param {number} top
|
|
652
|
-
* @param {{duration?: number, easing?: keyof typeof EasingFunctions}} [options]
|
|
653
|
-
* @returns {Promise<void>}
|
|
654
|
-
*/
|
|
655
|
-
export function scrollPromise(top, { duration = 1000, easing = 'linear' } = {}) {
|
|
656
|
-
return new Promise((resolve) => {
|
|
657
|
-
const windowY = window.scrollY;
|
|
658
|
-
const totalTime = duration;
|
|
659
|
-
|
|
660
|
-
const startTime = Date.now();
|
|
661
|
-
|
|
662
|
-
let running = true;
|
|
663
|
-
|
|
664
|
-
window.addEventListener('wheel', () => {
|
|
665
|
-
resolve();
|
|
666
|
-
running = false;
|
|
667
|
-
return;
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
const next = (dt = 0) => {
|
|
671
|
-
if (window.scrollY === top) {
|
|
672
|
-
resolve();
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
const perc = (Date.now() - startTime) / totalTime;
|
|
677
|
-
const place = lerp(windowY, top, EasingFunctions[easing](perc));
|
|
678
|
-
|
|
679
|
-
window.scrollTo(0, place);
|
|
680
|
-
|
|
681
|
-
if (running) {
|
|
682
|
-
requestAnimationFrame(next);
|
|
683
|
-
}
|
|
684
|
-
};
|
|
685
|
-
next();
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
export const scroll_lock = (lock = true) => {
|
|
690
|
-
const { documentElement, body } = document;
|
|
691
|
-
|
|
692
|
-
// RTL <body> scrollbar
|
|
693
|
-
const documentLeft = documentElement.getBoundingClientRect().left;
|
|
694
|
-
const scrollbarX = Math.round(documentLeft) + documentElement.scrollLeft;
|
|
695
|
-
const paddingProp = scrollbarX ? 'paddingLeft' : 'paddingRight';
|
|
696
|
-
|
|
697
|
-
if (lock) {
|
|
698
|
-
body.style[paddingProp] = `${window.innerWidth - documentElement.clientWidth}px`;
|
|
699
|
-
body.style.top = `-${window.scrollY}px`;
|
|
700
|
-
body.style.left = `-${window.scrollX}px`;
|
|
701
|
-
body.style.right = 0;
|
|
702
|
-
body.style.position = 'fixed';
|
|
703
|
-
|
|
704
|
-
body.classList.add('scroll-locked');
|
|
705
|
-
} else {
|
|
706
|
-
if (!body.classList.contains('scroll-locked')) {
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
const currentScrollY = parseInt(body.style.top || '0') * -1;
|
|
711
|
-
const currentScrollX = parseInt(body.style.left || '0') * -1;
|
|
712
|
-
|
|
713
|
-
body.style[paddingProp] = '';
|
|
714
|
-
body.style.position = '';
|
|
715
|
-
body.style.top = '';
|
|
716
|
-
body.style.left = '';
|
|
717
|
-
body.style.right = '';
|
|
718
|
-
|
|
719
|
-
body.classList.remove('scroll-locked');
|
|
720
|
-
|
|
721
|
-
window.scrollTo(currentScrollX, currentScrollY);
|
|
722
|
-
}
|
|
723
|
-
};
|