您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Display a thermometer widget with the temperature, time, and humidity of the current location of the neal.fun/internet-roadtrip vehicle
// ==UserScript== // @name Internet Roadtrip - Thermometer // @description Display a thermometer widget with the temperature, time, and humidity of the current location of the neal.fun/internet-roadtrip vehicle // @namespace me.netux.site/user-scripts/internet-roadtrip/thermometer // @icon https://cloudy.netux.site/neal_internet_roadtrip/Thermometer%20logo.png // @match https://neal.fun/* // @author Netux // @require https://cdn.jsdelivr.net/npm/[email protected] // @require https://cdn.jsdelivr.net/npm/@violentmonkey/[email protected] // @grant GM.deleteValue // @grant GM.getValue // @grant GM.info // @grant GM.setValue // @grant GM.xmlHttpRequest // @grant GM_addStyle // @grant GM_fetch // @version 2.0.0 // ==/UserScript== (async function (IRF, dom) { 'use strict'; const IS_DEV = false; const equalFn = (a, b) => a === b; const $PROXY = Symbol("solid-proxy"); const SUPPORTS_PROXY = typeof Proxy === "function"; const $TRACK = Symbol("solid-track"); const signalOptions = { equals: equalFn }; let runEffects = runQueue; const STALE = 1; const PENDING = 2; const UNOWNED = { owned: null, cleanups: null, context: null, owner: null }; var Owner = null; let Transition = null; let ExternalSourceConfig = null; let Listener = null; let Updates = null; let Effects = null; let ExecCount = 0; function createRoot(fn, detachedOwner) { const listener = Listener, owner = Owner, unowned = fn.length === 0, current = detachedOwner === undefined ? owner : detachedOwner, root = unowned ? UNOWNED : { owned: null, cleanups: null, context: current ? current.context : null, owner: current }, updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root))); Owner = root; Listener = null; try { return runUpdates(updateFn, true); } finally { Listener = listener; Owner = owner; } } function createSignal(value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const s = { value, observers: null, observerSlots: null, comparator: options.equals || undefined }; const setter = value => { if (typeof value === "function") { value = value(s.value); } return writeSignal(s, value); }; return [readSignal.bind(s), setter]; } function createRenderEffect(fn, value, options) { const c = createComputation(fn, value, false, STALE); updateComputation(c); } function createEffect(fn, value, options) { runEffects = runUserEffects; const c = createComputation(fn, value, false, STALE); if (!options || !options.render) c.user = true; Effects ? Effects.push(c) : updateComputation(c); } function createMemo(fn, value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const c = createComputation(fn, value, true, 0); c.observers = null; c.observerSlots = null; c.comparator = options.equals || undefined; updateComputation(c); return readSignal.bind(c); } function untrack(fn) { if (Listener === null) return fn(); const listener = Listener; Listener = null; try { if (ExternalSourceConfig) ; return fn(); } finally { Listener = listener; } } function onMount(fn) { createEffect(() => untrack(fn)); } function onCleanup(fn) { if (Owner === null) ;else if (Owner.cleanups === null) Owner.cleanups = [fn];else Owner.cleanups.push(fn); return fn; } function getOwner() { return Owner; } function runWithOwner(o, fn) { const prev = Owner; const prevListener = Listener; Owner = o; Listener = null; try { return runUpdates(fn, true); } catch (err) { handleError(err); } finally { Owner = prev; Listener = prevListener; } } function createContext(defaultValue, options) { const id = Symbol("context"); return { id, Provider: createProvider(id), defaultValue }; } function useContext(context) { let value; return Owner && Owner.context && (value = Owner.context[context.id]) !== undefined ? value : context.defaultValue; } function children(fn) { const children = createMemo(fn); const memo = createMemo(() => resolveChildren(children())); memo.toArray = () => { const c = memo(); return Array.isArray(c) ? c : c != null ? [c] : []; }; return memo; } function readSignal() { if (this.sources && (this.state)) { if ((this.state) === STALE) updateComputation(this);else { const updates = Updates; Updates = null; runUpdates(() => lookUpstream(this), false); Updates = updates; } } if (Listener) { const sSlot = this.observers ? this.observers.length : 0; if (!Listener.sources) { Listener.sources = [this]; Listener.sourceSlots = [sSlot]; } else { Listener.sources.push(this); Listener.sourceSlots.push(sSlot); } if (!this.observers) { this.observers = [Listener]; this.observerSlots = [Listener.sources.length - 1]; } else { this.observers.push(Listener); this.observerSlots.push(Listener.sources.length - 1); } } return this.value; } function writeSignal(node, value, isComp) { let current = node.value; if (!node.comparator || !node.comparator(current, value)) { node.value = value; if (node.observers && node.observers.length) { runUpdates(() => { for (let i = 0; i < node.observers.length; i += 1) { const o = node.observers[i]; const TransitionRunning = Transition && Transition.running; if (TransitionRunning && Transition.disposed.has(o)) ; if (TransitionRunning ? !o.tState : !o.state) { if (o.pure) Updates.push(o);else Effects.push(o); if (o.observers) markDownstream(o); } if (!TransitionRunning) o.state = STALE; } if (Updates.length > 10e5) { Updates = []; if (IS_DEV) ; throw new Error(); } }, false); } } return value; } function updateComputation(node) { if (!node.fn) return; cleanNode(node); const time = ExecCount; runComputation(node, node.value, time); } function runComputation(node, value, time) { let nextValue; const owner = Owner, listener = Listener; Listener = Owner = node; try { nextValue = node.fn(value); } catch (err) { if (node.pure) { { node.state = STALE; node.owned && node.owned.forEach(cleanNode); node.owned = null; } } node.updatedAt = time + 1; return handleError(err); } finally { Listener = listener; Owner = owner; } if (!node.updatedAt || node.updatedAt <= time) { if (node.updatedAt != null && "observers" in node) { writeSignal(node, nextValue); } else node.value = nextValue; node.updatedAt = time; } } function createComputation(fn, init, pure, state = STALE, options) { const c = { fn, state: state, updatedAt: null, owned: null, sources: null, sourceSlots: null, cleanups: null, value: init, owner: Owner, context: Owner ? Owner.context : null, pure }; if (Owner === null) ;else if (Owner !== UNOWNED) { { if (!Owner.owned) Owner.owned = [c];else Owner.owned.push(c); } } return c; } function runTop(node) { if ((node.state) === 0) return; if ((node.state) === PENDING) return lookUpstream(node); if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node); const ancestors = [node]; while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) { if (node.state) ancestors.push(node); } for (let i = ancestors.length - 1; i >= 0; i--) { node = ancestors[i]; if ((node.state) === STALE) { updateComputation(node); } else if ((node.state) === PENDING) { const updates = Updates; Updates = null; runUpdates(() => lookUpstream(node, ancestors[0]), false); Updates = updates; } } } function runUpdates(fn, init) { if (Updates) return fn(); let wait = false; if (!init) Updates = []; if (Effects) wait = true;else Effects = []; ExecCount++; try { const res = fn(); completeUpdates(wait); return res; } catch (err) { if (!wait) Effects = null; Updates = null; handleError(err); } } function completeUpdates(wait) { if (Updates) { runQueue(Updates); Updates = null; } if (wait) return; const e = Effects; Effects = null; if (e.length) runUpdates(() => runEffects(e), false); } function runQueue(queue) { for (let i = 0; i < queue.length; i++) runTop(queue[i]); } function runUserEffects(queue) { let i, userLength = 0; for (i = 0; i < queue.length; i++) { const e = queue[i]; if (!e.user) runTop(e);else queue[userLength++] = e; } for (i = 0; i < userLength; i++) runTop(queue[i]); } function lookUpstream(node, ignore) { node.state = 0; for (let i = 0; i < node.sources.length; i += 1) { const source = node.sources[i]; if (source.sources) { const state = source.state; if (state === STALE) { if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount)) runTop(source); } else if (state === PENDING) lookUpstream(source, ignore); } } } function markDownstream(node) { for (let i = 0; i < node.observers.length; i += 1) { const o = node.observers[i]; if (!o.state) { o.state = PENDING; if (o.pure) Updates.push(o);else Effects.push(o); o.observers && markDownstream(o); } } } function cleanNode(node) { let i; if (node.sources) { while (node.sources.length) { const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers; if (obs && obs.length) { const n = obs.pop(), s = source.observerSlots.pop(); if (index < obs.length) { n.sourceSlots[s] = index; obs[index] = n; source.observerSlots[index] = s; } } } } if (node.tOwned) { for (i = node.tOwned.length - 1; i >= 0; i--) cleanNode(node.tOwned[i]); delete node.tOwned; } if (node.owned) { for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]); node.owned = null; } if (node.cleanups) { for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i](); node.cleanups = null; } node.state = 0; } function castError(err) { if (err instanceof Error) return err; return new Error(typeof err === "string" ? err : "Unknown error", { cause: err }); } function handleError(err, owner = Owner) { const error = castError(err); throw error; } function resolveChildren(children) { if (typeof children === "function" && !children.length) return resolveChildren(children()); if (Array.isArray(children)) { const results = []; for (let i = 0; i < children.length; i++) { const result = resolveChildren(children[i]); Array.isArray(result) ? results.push.apply(results, result) : results.push(result); } return results; } return children; } function createProvider(id, options) { return function provider(props) { let res; createRenderEffect(() => res = untrack(() => { Owner.context = { ...Owner.context, [id]: props.value }; return children(() => props.children); }), undefined); return res; }; } const FALLBACK = Symbol("fallback"); function dispose(d) { for (let i = 0; i < d.length; i++) d[i](); } function mapArray(list, mapFn, options = {}) { let items = [], mapped = [], disposers = [], len = 0, indexes = mapFn.length > 1 ? [] : null; onCleanup(() => dispose(disposers)); return () => { let newItems = list() || [], newLen = newItems.length, i, j; newItems[$TRACK]; return untrack(() => { let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item; if (newLen === 0) { if (len !== 0) { dispose(disposers); disposers = []; items = []; mapped = []; len = 0; indexes && (indexes = []); } if (options.fallback) { items = [FALLBACK]; mapped[0] = createRoot(disposer => { disposers[0] = disposer; return options.fallback(); }); len = 1; } } else if (len === 0) { mapped = new Array(newLen); for (j = 0; j < newLen; j++) { items[j] = newItems[j]; mapped[j] = createRoot(mapper); } len = newLen; } else { temp = new Array(newLen); tempdisposers = new Array(newLen); indexes && (tempIndexes = new Array(newLen)); for (start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++); for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) { temp[newEnd] = mapped[end]; tempdisposers[newEnd] = disposers[end]; indexes && (tempIndexes[newEnd] = indexes[end]); } newIndices = new Map(); newIndicesNext = new Array(newEnd + 1); for (j = newEnd; j >= start; j--) { item = newItems[j]; i = newIndices.get(item); newIndicesNext[j] = i === undefined ? -1 : i; newIndices.set(item, j); } for (i = start; i <= end; i++) { item = items[i]; j = newIndices.get(item); if (j !== undefined && j !== -1) { temp[j] = mapped[i]; tempdisposers[j] = disposers[i]; indexes && (tempIndexes[j] = indexes[i]); j = newIndicesNext[j]; newIndices.set(item, j); } else disposers[i](); } for (j = start; j < newLen; j++) { if (j in temp) { mapped[j] = temp[j]; disposers[j] = tempdisposers[j]; if (indexes) { indexes[j] = tempIndexes[j]; indexes[j](j); } } else mapped[j] = createRoot(mapper); } mapped = mapped.slice(0, len = newLen); items = newItems.slice(0); } return mapped; }); function mapper(disposer) { disposers[j] = disposer; if (indexes) { const [s, set] = createSignal(j); indexes[j] = set; return mapFn(newItems[j], s); } return mapFn(newItems[j]); } }; } function indexArray(list, mapFn, options = {}) { let items = [], mapped = [], disposers = [], signals = [], len = 0, i; onCleanup(() => dispose(disposers)); return () => { const newItems = list() || [], newLen = newItems.length; newItems[$TRACK]; return untrack(() => { if (newLen === 0) { if (len !== 0) { dispose(disposers); disposers = []; items = []; mapped = []; len = 0; signals = []; } if (options.fallback) { items = [FALLBACK]; mapped[0] = createRoot(disposer => { disposers[0] = disposer; return options.fallback(); }); len = 1; } return mapped; } if (items[0] === FALLBACK) { disposers[0](); disposers = []; items = []; mapped = []; len = 0; } for (i = 0; i < newLen; i++) { if (i < items.length && items[i] !== newItems[i]) { signals[i](() => newItems[i]); } else if (i >= items.length) { mapped[i] = createRoot(mapper); } } for (; i < items.length; i++) { disposers[i](); } len = signals.length = disposers.length = newLen; items = newItems.slice(0); return mapped = mapped.slice(0, len); }); function mapper(disposer) { disposers[i] = disposer; const [s, set] = createSignal(newItems[i]); signals[i] = set; return mapFn(s, i); } }; } function createComponent(Comp, props) { return untrack(() => Comp(props || {})); } function trueFn() { return true; } const propTraps = { get(_, property, receiver) { if (property === $PROXY) return receiver; return _.get(property); }, has(_, property) { if (property === $PROXY) return true; return _.has(property); }, set: trueFn, deleteProperty: trueFn, getOwnPropertyDescriptor(_, property) { return { configurable: true, enumerable: true, get() { return _.get(property); }, set: trueFn, deleteProperty: trueFn }; }, ownKeys(_) { return _.keys(); } }; function resolveSource(s) { return !(s = typeof s === "function" ? s() : s) ? {} : s; } function resolveSources() { for (let i = 0, length = this.length; i < length; ++i) { const v = this[i](); if (v !== undefined) return v; } } function mergeProps(...sources) { let proxy = false; for (let i = 0; i < sources.length; i++) { const s = sources[i]; proxy = proxy || !!s && $PROXY in s; sources[i] = typeof s === "function" ? (proxy = true, createMemo(s)) : s; } if (SUPPORTS_PROXY && proxy) { return new Proxy({ get(property) { for (let i = sources.length - 1; i >= 0; i--) { const v = resolveSource(sources[i])[property]; if (v !== undefined) return v; } }, has(property) { for (let i = sources.length - 1; i >= 0; i--) { if (property in resolveSource(sources[i])) return true; } return false; }, keys() { const keys = []; for (let i = 0; i < sources.length; i++) keys.push(...Object.keys(resolveSource(sources[i]))); return [...new Set(keys)]; } }, propTraps); } const sourcesMap = {}; const defined = Object.create(null); for (let i = sources.length - 1; i >= 0; i--) { const source = sources[i]; if (!source) continue; const sourceKeys = Object.getOwnPropertyNames(source); for (let i = sourceKeys.length - 1; i >= 0; i--) { const key = sourceKeys[i]; if (key === "__proto__" || key === "constructor") continue; const desc = Object.getOwnPropertyDescriptor(source, key); if (!defined[key]) { defined[key] = desc.get ? { enumerable: true, configurable: true, get: resolveSources.bind(sourcesMap[key] = [desc.get.bind(source)]) } : desc.value !== undefined ? desc : undefined; } else { const sources = sourcesMap[key]; if (sources) { if (desc.get) sources.push(desc.get.bind(source));else if (desc.value !== undefined) sources.push(() => desc.value); } } } } const target = {}; const definedKeys = Object.keys(defined); for (let i = definedKeys.length - 1; i >= 0; i--) { const key = definedKeys[i], desc = defined[key]; if (desc && desc.get) Object.defineProperty(target, key, desc);else target[key] = desc ? desc.value : undefined; } return target; } function splitProps(props, ...keys) { if (SUPPORTS_PROXY && $PROXY in props) { const blocked = new Set(keys.length > 1 ? keys.flat() : keys[0]); const res = keys.map(k => { return new Proxy({ get(property) { return k.includes(property) ? props[property] : undefined; }, has(property) { return k.includes(property) && property in props; }, keys() { return k.filter(property => property in props); } }, propTraps); }); res.push(new Proxy({ get(property) { return blocked.has(property) ? undefined : props[property]; }, has(property) { return blocked.has(property) ? false : property in props; }, keys() { return Object.keys(props).filter(k => !blocked.has(k)); } }, propTraps)); return res; } const otherObject = {}; const objects = keys.map(() => ({})); for (const propName of Object.getOwnPropertyNames(props)) { const desc = Object.getOwnPropertyDescriptor(props, propName); const isDefaultDesc = !desc.get && !desc.set && desc.enumerable && desc.writable && desc.configurable; let blocked = false; let objectIndex = 0; for (const k of keys) { if (k.includes(propName)) { blocked = true; isDefaultDesc ? objects[objectIndex][propName] = desc.value : Object.defineProperty(objects[objectIndex], propName, desc); } ++objectIndex; } if (!blocked) { isDefaultDesc ? otherObject[propName] = desc.value : Object.defineProperty(otherObject, propName, desc); } } return [...objects, otherObject]; } const narrowedError = name => `Stale read from <${name}>.`; function For(props) { const fallback = "fallback" in props && { fallback: () => props.fallback }; return createMemo(mapArray(() => props.each, props.children, fallback || undefined)); } function Index(props) { const fallback = "fallback" in props && { fallback: () => props.fallback }; return createMemo(indexArray(() => props.each, props.children, fallback || undefined)); } function Show(props) { const keyed = props.keyed; const conditionValue = createMemo(() => props.when, undefined, undefined); const condition = keyed ? conditionValue : createMemo(conditionValue, undefined, { equals: (a, b) => !a === !b }); return createMemo(() => { const c = condition(); if (c) { const child = props.children; const fn = typeof child === "function" && child.length > 0; return fn ? untrack(() => child(keyed ? c : () => { if (!untrack(condition)) throw narrowedError("Show"); return conditionValue(); })) : child; } return props.fallback; }, undefined, undefined); } const booleans = ["allowfullscreen", "async", "alpha", "autofocus", "autoplay", "checked", "controls", "default", "disabled", "formnovalidate", "hidden", "indeterminate", "inert", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "seamless", "selected", "adauctionheaders", "browsingtopics", "credentialless", "defaultchecked", "defaultmuted", "defaultselected", "defer", "disablepictureinpicture", "disableremoteplayback", "preservespitch", "shadowrootclonable", "shadowrootcustomelementregistry", "shadowrootdelegatesfocus", "shadowrootserializable", "sharedstoragewritable" ]; const Properties = /*#__PURE__*/new Set([ "className", "value", "readOnly", "noValidate", "formNoValidate", "isMap", "noModule", "playsInline", "adAuctionHeaders", "allowFullscreen", "browsingTopics", "defaultChecked", "defaultMuted", "defaultSelected", "disablePictureInPicture", "disableRemotePlayback", "preservesPitch", "shadowRootClonable", "shadowRootCustomElementRegistry", "shadowRootDelegatesFocus", "shadowRootSerializable", "sharedStorageWritable", ...booleans]); const ChildProperties = /*#__PURE__*/new Set(["innerHTML", "textContent", "innerText", "children"]); const Aliases = /*#__PURE__*/Object.assign(Object.create(null), { className: "class", htmlFor: "for" }); const PropAliases = /*#__PURE__*/Object.assign(Object.create(null), { class: "className", novalidate: { $: "noValidate", FORM: 1 }, formnovalidate: { $: "formNoValidate", BUTTON: 1, INPUT: 1 }, ismap: { $: "isMap", IMG: 1 }, nomodule: { $: "noModule", SCRIPT: 1 }, playsinline: { $: "playsInline", VIDEO: 1 }, readonly: { $: "readOnly", INPUT: 1, TEXTAREA: 1 }, adauctionheaders: { $: "adAuctionHeaders", IFRAME: 1 }, allowfullscreen: { $: "allowFullscreen", IFRAME: 1 }, browsingtopics: { $: "browsingTopics", IMG: 1 }, defaultchecked: { $: "defaultChecked", INPUT: 1 }, defaultmuted: { $: "defaultMuted", AUDIO: 1, VIDEO: 1 }, defaultselected: { $: "defaultSelected", OPTION: 1 }, disablepictureinpicture: { $: "disablePictureInPicture", VIDEO: 1 }, disableremoteplayback: { $: "disableRemotePlayback", AUDIO: 1, VIDEO: 1 }, preservespitch: { $: "preservesPitch", AUDIO: 1, VIDEO: 1 }, shadowrootclonable: { $: "shadowRootClonable", TEMPLATE: 1 }, shadowrootdelegatesfocus: { $: "shadowRootDelegatesFocus", TEMPLATE: 1 }, shadowrootserializable: { $: "shadowRootSerializable", TEMPLATE: 1 }, sharedstoragewritable: { $: "sharedStorageWritable", IFRAME: 1, IMG: 1 } }); function getPropAlias(prop, tagName) { const a = PropAliases[prop]; return typeof a === "object" ? a[tagName] ? a["$"] : undefined : a; } const DelegatedEvents = /*#__PURE__*/new Set(["beforeinput", "click", "dblclick", "contextmenu", "focusin", "focusout", "input", "keydown", "keyup", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "pointerdown", "pointermove", "pointerout", "pointerover", "pointerup", "touchend", "touchmove", "touchstart"]); const SVGElements = /*#__PURE__*/new Set([ "altGlyph", "altGlyphDef", "altGlyphItem", "animate", "animateColor", "animateMotion", "animateTransform", "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "ellipse", "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence", "filter", "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignObject", "g", "glyph", "glyphRef", "hkern", "image", "line", "linearGradient", "marker", "mask", "metadata", "missing-glyph", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect", "set", "stop", "svg", "switch", "symbol", "text", "textPath", "tref", "tspan", "use", "view", "vkern"]); const SVGNamespace = { xlink: "http://www.w3.org/1999/xlink", xml: "http://www.w3.org/XML/1998/namespace" }; const memo = fn => createMemo(() => fn()); function reconcileArrays(parentNode, a, b) { let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = a[aEnd - 1].nextSibling, map = null; while (aStart < aEnd || bStart < bEnd) { if (a[aStart] === b[bStart]) { aStart++; bStart++; continue; } while (a[aEnd - 1] === b[bEnd - 1]) { aEnd--; bEnd--; } if (aEnd === aStart) { const node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after; while (bStart < bEnd) parentNode.insertBefore(b[bStart++], node); } else if (bEnd === bStart) { while (aStart < aEnd) { if (!map || !map.has(a[aStart])) a[aStart].remove(); aStart++; } } else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) { const node = a[--aEnd].nextSibling; parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling); parentNode.insertBefore(b[--bEnd], node); a[aEnd] = b[bEnd]; } else { if (!map) { map = new Map(); let i = bStart; while (i < bEnd) map.set(b[i], i++); } const index = map.get(a[aStart]); if (index != null) { if (bStart < index && index < bEnd) { let i = aStart, sequence = 1, t; while (++i < aEnd && i < bEnd) { if ((t = map.get(a[i])) == null || t !== index + sequence) break; sequence++; } if (sequence > index - bStart) { const node = a[aStart]; while (bStart < index) parentNode.insertBefore(b[bStart++], node); } else parentNode.replaceChild(b[bStart++], a[aStart++]); } else aStart++; } else a[aStart++].remove(); } } } const $$EVENTS = "_$DX_DELEGATE"; function render(code, element, init, options = {}) { let disposer; createRoot(dispose => { disposer = dispose; element === document ? code() : insert(element, code(), element.firstChild ? null : undefined, init); }, options.owner); return () => { disposer(); element.textContent = ""; }; } function template(html, isImportNode, isSVG, isMathML) { let node; const create = () => { const t = document.createElement("template"); t.innerHTML = html; return t.content.firstChild; }; const fn = () => (node || (node = create())).cloneNode(true); fn.cloneNode = fn; return fn; } function delegateEvents(eventNames, document = window.document) { const e = document[$$EVENTS] || (document[$$EVENTS] = new Set()); for (let i = 0, l = eventNames.length; i < l; i++) { const name = eventNames[i]; if (!e.has(name)) { e.add(name); document.addEventListener(name, eventHandler); } } } function setAttribute(node, name, value) { if (value == null) node.removeAttribute(name);else node.setAttribute(name, value); } function setAttributeNS(node, namespace, name, value) { if (value == null) node.removeAttributeNS(namespace, name);else node.setAttributeNS(namespace, name, value); } function setBoolAttribute(node, name, value) { value ? node.setAttribute(name, "") : node.removeAttribute(name); } function className(node, value) { if (value == null) node.removeAttribute("class");else node.className = value; } function addEventListener(node, name, handler, delegate) { if (delegate) { if (Array.isArray(handler)) { node[`$$${name}`] = handler[0]; node[`$$${name}Data`] = handler[1]; } else node[`$$${name}`] = handler; } else if (Array.isArray(handler)) { const handlerFn = handler[0]; node.addEventListener(name, handler[0] = e => handlerFn.call(node, handler[1], e)); } else node.addEventListener(name, handler, typeof handler !== "function" && handler); } function classList(node, value, prev = {}) { const classKeys = Object.keys(value || {}), prevKeys = Object.keys(prev); let i, len; for (i = 0, len = prevKeys.length; i < len; i++) { const key = prevKeys[i]; if (!key || key === "undefined" || value[key]) continue; toggleClassKey(node, key, false); delete prev[key]; } for (i = 0, len = classKeys.length; i < len; i++) { const key = classKeys[i], classValue = !!value[key]; if (!key || key === "undefined" || prev[key] === classValue || !classValue) continue; toggleClassKey(node, key, true); prev[key] = classValue; } return prev; } function style(node, value, prev) { if (!value) return prev ? setAttribute(node, "style") : value; const nodeStyle = node.style; if (typeof value === "string") return nodeStyle.cssText = value; typeof prev === "string" && (nodeStyle.cssText = prev = undefined); prev || (prev = {}); value || (value = {}); let v, s; for (s in prev) { value[s] == null && nodeStyle.removeProperty(s); delete prev[s]; } for (s in value) { v = value[s]; if (v !== prev[s]) { nodeStyle.setProperty(s, v); prev[s] = v; } } return prev; } function setStyleProperty(node, name, value) { value != null ? node.style.setProperty(name, value) : node.style.removeProperty(name); } function spread(node, props = {}, isSVG, skipChildren) { const prevProps = {}; if (!skipChildren) { createRenderEffect(() => prevProps.children = insertExpression(node, props.children, prevProps.children)); } createRenderEffect(() => typeof props.ref === "function" && use(props.ref, node)); createRenderEffect(() => assign(node, props, isSVG, true, prevProps, true)); return prevProps; } function use(fn, element, arg) { return untrack(() => fn(element, arg)); } function insert(parent, accessor, marker, initial) { if (marker !== undefined && !initial) initial = []; if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker); createRenderEffect(current => insertExpression(parent, accessor(), current, marker), initial); } function assign(node, props, isSVG, skipChildren, prevProps = {}, skipRef = false) { props || (props = {}); for (const prop in prevProps) { if (!(prop in props)) { if (prop === "children") continue; prevProps[prop] = assignProp(node, prop, null, prevProps[prop], isSVG, skipRef, props); } } for (const prop in props) { if (prop === "children") { continue; } const value = props[prop]; prevProps[prop] = assignProp(node, prop, value, prevProps[prop], isSVG, skipRef, props); } } function toPropertyName(name) { return name.toLowerCase().replace(/-([a-z])/g, (_, w) => w.toUpperCase()); } function toggleClassKey(node, key, value) { const classNames = key.trim().split(/\s+/); for (let i = 0, nameLen = classNames.length; i < nameLen; i++) node.classList.toggle(classNames[i], value); } function assignProp(node, prop, value, prev, isSVG, skipRef, props) { let isCE, isProp, isChildProp, propAlias, forceProp; if (prop === "style") return style(node, value, prev); if (prop === "classList") return classList(node, value, prev); if (value === prev) return prev; if (prop === "ref") { if (!skipRef) value(node); } else if (prop.slice(0, 3) === "on:") { const e = prop.slice(3); prev && node.removeEventListener(e, prev, typeof prev !== "function" && prev); value && node.addEventListener(e, value, typeof value !== "function" && value); } else if (prop.slice(0, 10) === "oncapture:") { const e = prop.slice(10); prev && node.removeEventListener(e, prev, true); value && node.addEventListener(e, value, true); } else if (prop.slice(0, 2) === "on") { const name = prop.slice(2).toLowerCase(); const delegate = DelegatedEvents.has(name); if (!delegate && prev) { const h = Array.isArray(prev) ? prev[0] : prev; node.removeEventListener(name, h); } if (delegate || value) { addEventListener(node, name, value, delegate); delegate && delegateEvents([name]); } } else if (prop.slice(0, 5) === "attr:") { setAttribute(node, prop.slice(5), value); } else if (prop.slice(0, 5) === "bool:") { setBoolAttribute(node, prop.slice(5), value); } else if ((forceProp = prop.slice(0, 5) === "prop:") || (isChildProp = ChildProperties.has(prop)) || !isSVG && ((propAlias = getPropAlias(prop, node.tagName)) || (isProp = Properties.has(prop))) || (isCE = node.nodeName.includes("-") || "is" in props)) { if (forceProp) { prop = prop.slice(5); isProp = true; } if (prop === "class" || prop === "className") className(node, value);else if (isCE && !isProp && !isChildProp) node[toPropertyName(prop)] = value;else node[propAlias || prop] = value; } else { const ns = isSVG && prop.indexOf(":") > -1 && SVGNamespace[prop.split(":")[0]]; if (ns) setAttributeNS(node, ns, prop, value);else setAttribute(node, Aliases[prop] || prop, value); } return value; } function eventHandler(e) { let node = e.target; const key = `$$${e.type}`; const oriTarget = e.target; const oriCurrentTarget = e.currentTarget; const retarget = value => Object.defineProperty(e, "target", { configurable: true, value }); const handleNode = () => { const handler = node[key]; if (handler && !node.disabled) { const data = node[`${key}Data`]; data !== undefined ? handler.call(node, data, e) : handler.call(node, e); if (e.cancelBubble) return; } node.host && typeof node.host !== "string" && !node.host._$host && node.contains(e.target) && retarget(node.host); return true; }; const walkUpTree = () => { while (handleNode() && (node = node._$host || node.parentNode || node.host)); }; Object.defineProperty(e, "currentTarget", { configurable: true, get() { return node || document; } }); if (e.composedPath) { const path = e.composedPath(); retarget(path[0]); for (let i = 0; i < path.length - 2; i++) { node = path[i]; if (!handleNode()) break; if (node._$host) { node = node._$host; walkUpTree(); break; } if (node.parentNode === oriCurrentTarget) { break; } } } else walkUpTree(); retarget(oriTarget); } function insertExpression(parent, value, current, marker, unwrapArray) { while (typeof current === "function") current = current(); if (value === current) return current; const t = typeof value, multi = marker !== undefined; parent = multi && current[0] && current[0].parentNode || parent; if (t === "string" || t === "number") { if (t === "number") { value = value.toString(); if (value === current) return current; } if (multi) { let node = current[0]; if (node && node.nodeType === 3) { node.data !== value && (node.data = value); } else node = document.createTextNode(value); current = cleanChildren(parent, current, marker, node); } else { if (current !== "" && typeof current === "string") { current = parent.firstChild.data = value; } else current = parent.textContent = value; } } else if (value == null || t === "boolean") { current = cleanChildren(parent, current, marker); } else if (t === "function") { createRenderEffect(() => { let v = value(); while (typeof v === "function") v = v(); current = insertExpression(parent, v, current, marker); }); return () => current; } else if (Array.isArray(value)) { const array = []; const currentArray = current && Array.isArray(current); if (normalizeIncomingArray(array, value, current, unwrapArray)) { createRenderEffect(() => current = insertExpression(parent, array, current, marker, true)); return () => current; } if (array.length === 0) { current = cleanChildren(parent, current, marker); if (multi) return current; } else if (currentArray) { if (current.length === 0) { appendNodes(parent, array, marker); } else reconcileArrays(parent, current, array); } else { current && cleanChildren(parent); appendNodes(parent, array); } current = array; } else if (value.nodeType) { if (Array.isArray(current)) { if (multi) return current = cleanChildren(parent, current, marker, value); cleanChildren(parent, current, null, value); } else if (current == null || current === "" || !parent.firstChild) { parent.appendChild(value); } else parent.replaceChild(value, parent.firstChild); current = value; } else ; return current; } function normalizeIncomingArray(normalized, array, current, unwrap) { let dynamic = false; for (let i = 0, len = array.length; i < len; i++) { let item = array[i], prev = current && current[normalized.length], t; if (item == null || item === true || item === false) ; else if ((t = typeof item) === "object" && item.nodeType) { normalized.push(item); } else if (Array.isArray(item)) { dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic; } else if (t === "function") { if (unwrap) { while (typeof item === "function") item = item(); dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic; } else { normalized.push(item); dynamic = true; } } else { const value = String(item); if (prev && prev.nodeType === 3 && prev.data === value) normalized.push(prev);else normalized.push(document.createTextNode(value)); } } return dynamic; } function appendNodes(parent, array, marker = null) { for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker); } function cleanChildren(parent, current, marker, replacement) { if (marker === undefined) return parent.textContent = ""; const node = replacement || document.createTextNode(""); if (current.length) { let inserted = false; for (let i = current.length - 1; i >= 0; i--) { const el = current[i]; if (node !== el) { const isParent = el.parentNode === parent; if (!inserted && !i) isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker);else isParent && el.remove(); } else inserted = true; } } else parent.insertBefore(node, marker); return [node]; } const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; function createElement(tagName, isSVG = false, is = undefined) { return isSVG ? document.createElementNS(SVG_NAMESPACE, tagName) : document.createElement(tagName, { is }); } function Portal(props) { const { useShadow } = props, marker = document.createTextNode(""), mount = () => props.mount || document.body, owner = getOwner(); let content; createEffect(() => { content || (content = runWithOwner(owner, () => createMemo(() => props.children))); const el = mount(); if (el instanceof HTMLHeadElement) { const [clean, setClean] = createSignal(false); const cleanup = () => setClean(true); createRoot(dispose => insert(el, () => !clean() ? content() : dispose(), null)); onCleanup(cleanup); } else { const container = createElement(props.isSVG ? "g" : "div", props.isSVG), renderRoot = useShadow && container.attachShadow ? container.attachShadow({ mode: "open" }) : container; Object.defineProperty(container, "_$host", { get() { return marker.parentNode; }, configurable: true }); insert(renderRoot, content); el.appendChild(container); props.ref && props.ref(container); onCleanup(() => el.removeChild(container)); } }, undefined, { render: true }); return marker; } function createDynamic(component, props) { const cached = createMemo(component); return createMemo(() => { const component = cached(); switch (typeof component) { case "function": return untrack(() => component(props)); case "string": const isSvg = SVGElements.has(component); const el = createElement(component, isSvg, props.is); spread(el, props, isSvg); return el; } }); } function Dynamic(props) { const [, others] = splitProps(props, ["component"]); return createDynamic(() => props.component, others); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } const [panel, setPanel] = createSignal(); const [loading, setLoading] = createSignal(false); const [forecast, setForecast] = createSignal(null); const [error, setError] = createSignal(null); function setForecastLoading() { setLoading(true); } function setForecastLoadSuccess(forecast) { setLoading(false); setForecast(forecast); setError(null); } function setForecastLoadFailure(error) { setLoading(false); setError(error); } const zeroOne = (value, min, max) => (value - min) / (max - min); function GM_fetch(details) { return new Promise((resolve, reject) => { GM.xmlHttpRequest(_extends({}, details, { onload: response => resolve(response), onerror: err => reject(err) })); }); } const waitForCoordinatesToBeSetAtLeastOnce = IRF.vdom.container.then(containerVDOM => { return new Promise(resolve => { containerVDOM.state.changeStop = new Proxy(containerVDOM.state.changeStop, { apply(ogChangeStop, thisArg, args) { const returnedValue = ogChangeStop.apply(thisArg, args); resolve(); return returnedValue; } }); }); }); const offsetTimezone = (date, utcOffsetSeconds) => { const localUtcOffsetUtcSeconds = -date.getTimezoneOffset() * 60; const result = new Date(date); result.setSeconds(result.getSeconds() - localUtcOffsetUtcSeconds + utcOffsetSeconds); return result; }; const MOD_NAME = 'Thermometer'; const MOD_DOM_SAFE_PREFIX = 'thermometer-'; const MOD_LOG_PREFIX = `[${MOD_NAME}]`; const RAD_TO_DEG = 180 / Math.PI; const TEMPERATURE_UNITS = { celsius: { label: 'Celsius', unit: '°C', fromCelsius: celsius => celsius, toCelsius: celsius => celsius }, fahrenheit: { label: 'Fahrenheit', unit: '°F', fromCelsius: celsius => celsius * 1.8 + 32, toCelsius: fahrenheit => (fahrenheit - 32) / 1.8 }, felsius: { label: 'Felsius (xkcd #1923)', unit: '°Є', fromCelsius: celsius => 7 * celsius / 5 + 16, toCelsius: felsius => 5 * (felsius - 16) / 7 }, kelvin: { label: 'Kelvin', unit: 'K', fromCelsius: celsius => celsius + 273, toCelsius: kelvin => kelvin - 273 } }; async function fetchForecast([latitude, longitude]) { const forecastApiUrl = `https://api.open-meteo.com/v1/forecast?${[`latitude=${latitude}`, `longitude=${longitude}`, `current=${['temperature_2m', 'relative_humidity_2m'].join(',')}`, `temperature_unit=celsius`, `timezone=auto`].join('&')}`; const { status, response: forecastStr } = await GM_fetch({ url: forecastApiUrl, headers: { 'content-type': 'application/json' }, timeout: 10000 }); if (status !== 200) { throw new Error(`Got a ${status} status code when requesting forecast information`); } if (forecastStr == null) { throw new Error(`For some reason the forecast information was nullish`); } let forecast; try { forecast = JSON.parse(forecastStr); } catch (error) { throw new Error(`Could not parse forecast JSON: ${error.toString()}`); } return forecast; } /* #region Widget Position */ const DEFAULT_WIDGET_POSITION = { x: 0.5, y: 0.5 }; /* #endregion Overlay Position */ /* #region Default Temperature Gradient */ const DEFAULT_TEMPERATURE_GRADIENT = [{ temperatureCelsius: -20, color: '#f5f5f5' }, { temperatureCelsius: -10, color: '#82cdff' }, { temperatureCelsius: 0, color: '#0c9eff' }, { temperatureCelsius: 15, color: '#043add' }, { temperatureCelsius: 18.5, color: '#c0c23d' }, { temperatureCelsius: 22.5, color: '#ffd86d' }, { temperatureCelsius: 27.5, color: '#ffa538' }, { temperatureCelsius: 32.5, color: '#c92626' }, { temperatureCelsius: 40, color: '#6a0b39' }]; const { min: DEFAULT_TEMPERATURE_GRADIENT_MIN_TEMPERATURE, max: DEFAULT_TEMPERATURE_GRADIENT_MAX_TEMPERATURE } = DEFAULT_TEMPERATURE_GRADIENT.reduce(({ min: previousMin, max: previousMax }, { temperatureCelsius }) => ({ min: Math.min(temperatureCelsius, previousMin), max: Math.max(temperatureCelsius, previousMax) }), { min: 0, max: 0 }); const getDefaultTemperatureGradientSettings = () => ({ temperatureGradient: DEFAULT_TEMPERATURE_GRADIENT.map(({ temperatureCelsius, color }) => ({ percent: zeroOne(temperatureCelsius, DEFAULT_TEMPERATURE_GRADIENT_MIN_TEMPERATURE, DEFAULT_TEMPERATURE_GRADIENT_MAX_TEMPERATURE), color })), temperatureGradientMinCelsius: DEFAULT_TEMPERATURE_GRADIENT_MIN_TEMPERATURE, temperatureGradientMaxCelsius: DEFAULT_TEMPERATURE_GRADIENT_MAX_TEMPERATURE }); /* #endregion Default Temperature Gradient */ const [settings, setSettings] = createSignal(_extends({ widgetPosition: DEFAULT_WIDGET_POSITION, showClock: true, time24Hours: true, timeIncludeSeconds: false, showTemperature: true, temperatureUnit: 'celsius' }, getDefaultTemperatureGradientSettings(), { showRelativeHumidity: true })); // Load { const newSettings = _extends({}, settings()); for (const key in settings()) { newSettings[key] = await( GM.getValue(key, newSettings[key])); } // Migration from <2.0.0, when the widget was refered to as an overlay { const noValue = Symbol(); const hasWidgetPositionValue = (await( GM.getValue('widgetPosition', noValue))) === noValue; const oldOverlayPositionValue = await( GM.getValue('overlayPosition', noValue)); if (!hasWidgetPositionValue && oldOverlayPositionValue !== noValue) { newSettings.widgetPosition = oldOverlayPositionValue; await( GM.deleteValue('overlayPosition')); } } // Migration from <=1.2.2, when the widget position was null by default if (newSettings.widgetPosition == null) { newSettings.widgetPosition = DEFAULT_WIDGET_POSITION; } setSettings(newSettings); } // Save async function saveSettings(change) { if (typeof change === 'function') { setSettings(change); } else if (typeof change === 'object') { setSettings(prevSettings => _extends({}, prevSettings, change)); } const currentSettings = settings(); for (const key in currentSettings) { await GM.setValue(key, currentSettings[key]); } } /* * This whole file is heavily based on https://github.com/violentmonkey/vm-ui/blob/00592622a01e48a4ac27a743254d82b1ebcd6d02/src/util/movable.ts * with the following modifications: * - Make Movable an EventTarget * - Add move-start, moving, and move-end events to Movable * - Replaces the old onMoved callback in MovableOptions * - Add handler elements system to Movable * - Add methods to retrieve and set position of the Movable * - Add support for touch events */ class Movable extends EventTarget { constructor(el, options) { super(); this.el = null; this.options = null; this.dragging = null; this.isTouchEvent = e => e.type.startsWith('touch'); this.getEventPointerPosition = e => { if (this.isTouchEvent(e)) { const { clientX, clientY } = e.touches[this.touchIdentifier]; return { clientX, clientY }; } else { const { clientX, clientY } = e; return { clientX, clientY }; } }; this.onMouseDown = e => { if (this.isTouchEvent(e)) { var _e$changedTouches; this.touchIdentifier = (_e$changedTouches = e.changedTouches) == null || (_e$changedTouches = _e$changedTouches[0]) == null ? void 0 : _e$changedTouches.identifier; } const { handlerElements = [] } = this.options; if (handlerElements.length > 0 && !handlerElements.some(handlerEl => e.target === handlerEl || handlerEl.contains(e.target))) { return; } e.preventDefault(); e.stopPropagation(); const { x, y } = this.el.getBoundingClientRect(); const { clientX, clientY } = this.getEventPointerPosition(e); this.dragging = { x: clientX - x, y: clientY - y }; document.addEventListener('mousemove', this.onMouseMove); document.addEventListener('touchmove', this.onMouseMove); document.addEventListener('mouseup', this.onMouseUp); document.addEventListener('touchend', this.onMouseUp); this.dispatchEvent(new Event('move-start')); }; this.onMouseMove = e => { if (this.isTouchEvent(e) && this.touchIdentifier != null && !Array.from(e.changedTouches).some(touch => this.touchIdentifier === touch.identifier)) return; if (!this.dragging) return; const { x, y } = this.dragging; const { clientX, clientY } = this.getEventPointerPosition(e); this.setPosition(clientX - x, clientY - y); }; this.onMouseUp = e => { if (this.isTouchEvent(e) && this.touchIdentifier != null && !Array.from(e.changedTouches).some(touch => this.touchIdentifier === touch.identifier)) return; this.dragging = null; this.touchIdentifier = null; document.removeEventListener('mousemove', this.onMouseMove); document.removeEventListener('touchmove', this.onMouseMove); document.removeEventListener('mouseup', this.onMouseUp); document.removeEventListener('touchend', this.onMouseUp); this.dispatchEvent(new Event('move-end')); }; this.el = el; this.setOptions(options); } setOptions(options) { this.options = _extends({}, Movable.defaultOptions, options); } applyOptions(newOptions) { this.options = _extends({}, this.options, newOptions); } enable() { this.el.addEventListener('mousedown', this.onMouseDown); this.el.addEventListener('touchstart', this.onMouseDown); } disable() { this.dragging = undefined; this.el.removeEventListener('mousedown', this.onMouseDown); this.el.removeEventListener('touchstart', this.onMouseDown); document.removeEventListener('mousemove', this.onMouseMove); document.removeEventListener('touchmove', this.onMouseUp); document.removeEventListener('mouseup', this.onMouseUp); document.removeEventListener('touchend', this.onMouseUp); } setPosition(x, y) { const { origin } = this.options; const { offsetWidth: width, offsetHeight: height } = this.el; const { clientWidth, clientHeight } = document.documentElement; const left = Math.max(0, Math.min(x, clientWidth - width)); const top = Math.max(0, Math.min(y, clientHeight - height)); const position = { top: 'auto', left: 'auto', right: 'auto', bottom: 'auto' }; if (origin.x === 'start' || origin.x === 'auto' && left + left + width < clientWidth) { position.left = `${left}px`; } else { position.right = `${clientWidth - left - width}px`; } if (origin.y === 'start' || origin.y === 'auto' && top + top + height < clientHeight) { position.top = `${top}px`; } else { position.bottom = `${clientHeight - top - height}px`; } Object.assign(this.el.style, position); this.dispatchEvent(new Event('moving')); } getPosition() { const { left, top } = this.el.getBoundingClientRect(); return { left, top }; } } Movable.defaultOptions = { origin: { x: 'auto', y: 'auto' } }; const shadowRootContext = createContext(null); var ShadowRooted = props => { props = mergeProps({ hostTag: 'div' }, props); let hostEl; const [shadowRoot, setShadowRoot] = createSignal(null); onMount(() => { setShadowRoot(hostEl.attachShadow({ mode: 'open' })); }); return [createComponent(Dynamic, { get component() { return props.hostTag; }, style: { display: 'contents' }, ref(r$) { var _ref$ = hostEl; typeof _ref$ === "function" ? _ref$(r$) : hostEl = r$; } }), createComponent(shadowRootContext.Provider, { value: shadowRoot, get children() { return createComponent(Show, { get when() { return (shadowRoot == null ? void 0 : shadowRoot()) != null; }, get children() { return createComponent(Portal, { get mount() { return shadowRoot(); }, get children() { return props.children; } }); } }); } })]; }; /* * Based on VM UI's VM.getPanel() https://github.com/violentmonkey/vm-ui/blob/00592622a01e48a4ac27a743254d82b1ebcd6d02/src/panel/index.tsx#L71 */ function makePanel(options) { var _options$zIndex; const hostEl = dom.hm(`${MOD_DOM_SAFE_PREFIX}host`, {}); const shadowRoot = hostEl.attachShadow({ mode: 'open' }); shadowRoot.append(dom.hm('style', {}, ` ${MOD_DOM_SAFE_PREFIX}wrapper { position: fixed; z-index: ${(_options$zIndex = options.zIndex) != null ? _options$zIndex : Number.MAX_SAFE_INTEGER - 1}; pointer-events: none; & > * { pointer-events: initial; } } `)); const wrapperEl = dom.hm(`${MOD_DOM_SAFE_PREFIX}wrapper`, {}); wrapperEl.style.pointerEvents = 'none'; shadowRoot.append(wrapperEl); const movable = new Movable(wrapperEl); const panel = { hostEl, wrapperEl, movable, show() { document.body.append(hostEl); }, hide() { hostEl.remove(); } }; render(() => { return createComponent(shadowRootContext.Provider, { value: () => shadowRoot, get children() { return createComponent(options.element, { panel: panel }); } }); }, panel.wrapperEl); return panel; } var styles$6 = {"container":"thermometer-ThermometerWidget-container","graphic":"thermometer-ThermometerWidget-graphic","is-being-grabbed":"thermometer-ThermometerWidget-is-being-grabbed","info":"thermometer-ThermometerWidget-info","error":"thermometer-ThermometerWidget-error","is-info-on-the-right":"thermometer-ThermometerWidget-is-info-on-the-right"}; var stylesheet$9=".thermometer-ThermometerWidget-container {\n /* Theme: transparent */\n background-color: transparent;\n border: none;\n box-shadow: none;\n\n /* Fix for loading indicator making the container super big */\n width: fit-content;\n height: fit-content;\n\n padding: 0;\n display: flex;\n flex-direction: row;\n}\n\n .thermometer-ThermometerWidget-container > * {\n pointer-events: initial;\n }\n\n .thermometer-ThermometerWidget-container .thermometer-ThermometerWidget-graphic {\n cursor: grab;\n }\n\n .thermometer-ThermometerWidget-is-being-grabbed .thermometer-ThermometerWidget-container .thermometer-ThermometerWidget-graphic {\n cursor: grabbing;\n }\n\n .thermometer-ThermometerWidget-container .thermometer-ThermometerWidget-info {\n min-width: 8ch;\n margin-block: 0.5rem;\n font-family: 'Roboto', sans-serif;\n color: white;\n text-shadow:\n 0px 1px 2px black,\n 0px -1px 2px black,\n 1px 0px 2px black,\n -1px 0px 2px black;\n text-align: right;\n }\n\n .thermometer-ThermometerWidget-container .thermometer-ThermometerWidget-info :is(p, h1, h2, h3, h4, h5, h6) {\n margin: 0;\n }\n\n .thermometer-ThermometerWidget-container .thermometer-ThermometerWidget-error {\n color: #ff4141;\n }\n\n .thermometer-ThermometerWidget-container.thermometer-ThermometerWidget-is-info-on-the-right {\n flex-direction: row-reverse;\n }\n\n .thermometer-ThermometerWidget-container.thermometer-ThermometerWidget-is-info-on-the-right .thermometer-ThermometerWidget-info {\n text-align: left;\n }\n"; const temperatureGradientCanvasCtx = document.createElement('canvas').getContext('2d', { willReadFrequently: true }); const temperatureGradientCanvas = temperatureGradientCanvasCtx.canvas; const [temperatureGradientHasRedrawn, setTemperatureGradientHasRedrawn] = createSignal(false); const pingTemperatureGradientHasRedrawn = () => { // Abusing Solid's signal system since 2025 setTemperatureGradientHasRedrawn(true); setTemperatureGradientHasRedrawn(false); }; const [temperatureGradientArray, setTemperatureGradientArray] = createSignal(null); createEffect(() => { if (temperatureGradientArray()) { pingTemperatureGradientHasRedrawn(); } }); function redrawTemperatureCanvas() { const canvasWidth = Math.abs(settings().temperatureGradientMaxCelsius - settings().temperatureGradientMinCelsius); temperatureGradientCanvasCtx.canvas.width = canvasWidth; temperatureGradientCanvasCtx.canvas.height = 1; const temperatureCanvasGradient = temperatureGradientCanvasCtx.createLinearGradient(0, 0, temperatureGradientCanvasCtx.canvas.width, 0); for (const { percent, color } of settings().temperatureGradient) { temperatureCanvasGradient.addColorStop(percent, color); } temperatureGradientCanvasCtx.fillStyle = temperatureCanvasGradient; temperatureGradientCanvasCtx.fillRect(0, 0, temperatureGradientCanvasCtx.canvas.width, temperatureGradientCanvasCtx.canvas.height); } createEffect(() => { var _settings; if (((_settings = settings()) == null ? void 0 : _settings.temperatureGradient) != null) { redrawTemperatureCanvas(); setTemperatureGradientArray(temperatureGradientCanvasCtx.getImageData(0, 0, temperatureGradientCanvasCtx.canvas.width, 1).data); } }); function sampleTemperatureGradient(temperatureCelsius) { const percent = zeroOne(temperatureCelsius, settings().temperatureGradientMinCelsius, settings().temperatureGradientMaxCelsius); const clampedPercent = Math.max(0, Math.min(percent, 1)); const x = Math.floor(clampedPercent * (temperatureGradientCanvasCtx.canvas.width - 1)); const i = x * 4; const rgb = temperatureGradientArray().slice(i, i + 3); return '#' + [...rgb].map(c => c.toString(16).padStart(2, '0')).join(''); } const temperatureAtGradient = x => settings().temperatureGradientMinCelsius + x * (settings().temperatureGradientMaxCelsius - settings().temperatureGradientMinCelsius); const existingStyleSheetsPerRealm = new Map(); const useRealm = () => { const closestShadowRoot = useContext(shadowRootContext); return () => closestShadowRoot == null ? document : closestShadowRoot(); }; var SingleInstanceStyle = props => { const mountRealm = useRealm(); const mount = () => { const realm = mountRealm(); if (realm instanceof Document) { return realm.head; } else if (realm instanceof ShadowRoot) { return realm; } else { throw new Error('Could not determine mount point for SingleInstanceStylesheet'); } }; const injectIfFirst = () => { if (!existingStyleSheetsPerRealm.has(mountRealm())) { existingStyleSheetsPerRealm.set(mountRealm(), new Map()); } const realmExistingStyleSheets = existingStyleSheetsPerRealm.get(mountRealm()); if (!realmExistingStyleSheets.has(props.key)) { const [usageCount, setUsageCount] = createSignal(0); realmExistingStyleSheets.set(props.key, { styleEl: dom.hm('style', { 'data-single-instance-style-key': props.key }, props.children), usageCount, setUsageCount }); } const instance = realmExistingStyleSheets.get(props.key); instance.setUsageCount(count => count + 1); if (!instance.styleEl.isConnected) { mount().append(instance.styleEl); } }; const cleanUpForThisInstance = () => { const realmExistingStyleSheets = existingStyleSheetsPerRealm.get(mountRealm()); if (!realmExistingStyleSheets) { return; } const instance = realmExistingStyleSheets.get(props.key); if (!instance) { return; } instance.setUsageCount(count => count - 1); if (instance.usageCount() <= 0 && instance.styleEl.isConnected) { instance.styleEl.remove(); realmExistingStyleSheets.delete(props.key); } }; createEffect(() => { if (mountRealm() == null) { return; } injectIfFirst(); }); onCleanup(cleanUpForThisInstance); return null; }; var styles$5 = {"graphic":"thermometer-ThermometerGraphic-graphic","fill":"thermometer-ThermometerGraphic-fill","bottom-fill":"thermometer-ThermometerGraphic-bottom-fill","is-busy":"thermometer-ThermometerGraphic-is-busy"}; var stylesheet$8=".thermometer-ThermometerGraphic-graphic {\n padding: 0.5rem;\n align-self: center;\n}\n\n .thermometer-ThermometerGraphic-graphic .thermometer-ThermometerGraphic-fill,\n .thermometer-ThermometerGraphic-graphic .thermometer-ThermometerGraphic-bottom-fill {\n transition:\n height 1s linear,\n color 1s linear;\n }\n\n .thermometer-ThermometerGraphic-is-busy .thermometer-ThermometerGraphic-graphic {\n animation: 1s linear infinite alternate thermometer-ThermometerGraphic-busy;\n }\n\n .thermometer-ThermometerGraphic-is-busy .thermometer-ThermometerGraphic-graphic .thermometer-ThermometerGraphic-fill,\n .thermometer-ThermometerGraphic-is-busy .thermometer-ThermometerGraphic-graphic .thermometer-ThermometerGraphic-bottom-fill {\n transition: none;\n }\n\n@keyframes thermometer-ThermometerGraphic-busy {\n 0% {\n color: lightgray;\n }\n 100% {\n color: darkgray;\n }\n}\n"; var _tmpl$$6 = /*#__PURE__*/template(`<svg width=22 height=100 viewBox="0 0 22 100"xmlns=http://www.w3.org/2000/svg><rect id=background width=9.67380575 height=84.6300375 x=5.841754249999994 y=1.9680687500000005 rx=4.5632865016684 style="fill:rgb(206 251 250 / 50%);fill-opacity:1;opacity:1;stroke:none;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-width:2"></rect><rect id=fill width=9.67380575 x=5.841754249999994 y=14 rx=4.5632865016684 style=fill:currentcolor;fill-opacity:1;opacity:1;stroke:none;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-width:2;rotate:180deg;transform-origin:center></rect><g><ellipse id=bottom-fill cx=10.81210005114999 cy=89.515713524548 rx=9.596004408359999 ry=9.3861523188054 style=opacity:1;fill:currentcolor;fill-opacity:1;stroke-width:2.98623;stroke-linecap:round;stroke-linejoin:round></ellipse><path id=shimmer d="M 3.68829 88.8769 C 3.68829 88.8769 3.94517 91.2262 5.70701 93.1668 C 7.46886 95.1075 9.59383 95.4109 9.59383 95.4109"style=fill:none;opacity:0.75;stroke:#ffffff;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;stroke-width:1.5></path></g><path id=outline d="M 10.6362 1.09869 C 7.89534 1.09869 5.69054 3.76255 5.69055 7.07108 L 5.69055 80.5387 C 2.8139 82.2685 0.910186 85.417 0.910186 89.0143 C 0.910186 94.4748 5.34273 98.9026 10.8115 98.9026 C 16.2803 98.9026 20.7148 94.4748 20.7148 89.0143 C 20.7148 85.2876 18.6337 82.0426 15.5838 80.3576 L 15.5838 7.07108 C 15.5838 3.76255 13.377 1.09869 10.6362 1.09869 Z"style=display:inline;fill:none;stroke:#000000;stroke-dasharray:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:2>`); var ThermometerGraphic = props => { const GRAPHIC_MAX_HEIGHT = 84.6300375; const [mainProps, restProps] = splitProps(props, ['busy', 'fillPercent', 'color']); return [createComponent(SingleInstanceStyle, { key: "ThermometerGraphic", children: stylesheet$8 }), (() => { var _el$ = _tmpl$$6(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.nextSibling, _el$5 = _el$4.firstChild; spread(_el$, mergeProps({ get style() { return { color: !mainProps.busy ? mainProps.color : null }; } }, restProps, { get classList() { var _restProps$class$spli, _restProps$class; return _extends({}, Object.fromEntries((_restProps$class$spli = (_restProps$class = restProps['class']) == null ? void 0 : _restProps$class.split(' ').map(cls => [cls, true])) != null ? _restProps$class$spli : []), { [styles$5['graphic']]: true, [styles$5['is-busy']]: mainProps.busy }); } }), true, true); createRenderEffect(_p$ => { var _v$ = styles$5['fill'], _v$2 = mainProps.fillPercent * GRAPHIC_MAX_HEIGHT, _v$3 = styles$5['bottom-fill']; _v$ !== _p$.e && setAttribute(_el$3, "class", _p$.e = _v$); _v$2 !== _p$.t && setAttribute(_el$3, "height", _p$.t = _v$2); _v$3 !== _p$.a && setAttribute(_el$5, "class", _p$.a = _v$3); return _p$; }, { e: undefined, t: undefined, a: undefined }); return _el$; })()]; }; function _classPrivateFieldBase(e, t) { if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance"); return e; } var id = 0; function _classPrivateFieldKey(e) { return "__private_" + id++ + "_" + e; } var _interval = /*#__PURE__*/_classPrivateFieldKey("interval"); var _mousePositionInsidePet = /*#__PURE__*/_classPrivateFieldKey("mousePositionInsidePet"); var _lastMousePositionInsidePet = /*#__PURE__*/_classPrivateFieldKey("lastMousePositionInsidePet"); var _lastAngle = /*#__PURE__*/_classPrivateFieldKey("lastAngle"); var _scratchCounter = /*#__PURE__*/_classPrivateFieldKey("scratchCounter"); var _samplesBeingPet = /*#__PURE__*/_classPrivateFieldKey("samplesBeingPet"); var _lastEventDispatchWasPettingStart = /*#__PURE__*/_classPrivateFieldKey("lastEventDispatchWasPettingStart"); var _handlePetElMouseMove = /*#__PURE__*/_classPrivateFieldKey("handlePetElMouseMove"); var _handlePetElMouseLeave = /*#__PURE__*/_classPrivateFieldKey("handlePetElMouseLeave"); var _sample = /*#__PURE__*/_classPrivateFieldKey("sample"); class Pet extends EventTarget { constructor(petEl, options) { super(); Object.defineProperty(this, _sample, { value: _sample2 }); Object.defineProperty(this, _interval, { writable: true, value: null }); Object.defineProperty(this, _mousePositionInsidePet, { writable: true, value: null }); Object.defineProperty(this, _lastMousePositionInsidePet, { writable: true, value: null }); Object.defineProperty(this, _lastAngle, { writable: true, value: null }); Object.defineProperty(this, _scratchCounter, { writable: true, value: 0 }); Object.defineProperty(this, _samplesBeingPet, { writable: true, value: 0 }); Object.defineProperty(this, _lastEventDispatchWasPettingStart, { writable: true, value: false }); Object.defineProperty(this, _handlePetElMouseMove, { writable: true, value: event => { const { clientX, clientY } = event; _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet] = { x: clientX, y: clientY }; } }); Object.defineProperty(this, _handlePetElMouseLeave, { writable: true, value: _event => { _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet] = null; } }); this.petEl = petEl; this.options = options; this.start(); } start() { _classPrivateFieldBase(this, _interval)[_interval] = setInterval(_classPrivateFieldBase(this, _sample)[_sample].bind(this), this.options.sampleRate); _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet] = null; this.petEl.addEventListener('mousemove', _classPrivateFieldBase(this, _handlePetElMouseMove)[_handlePetElMouseMove].bind(this)); this.petEl.addEventListener('mouseleave', _classPrivateFieldBase(this, _handlePetElMouseLeave)[_handlePetElMouseLeave].bind(this)); } stop() { clearInterval(_classPrivateFieldBase(this, _interval)[_interval]); _classPrivateFieldBase(this, _interval)[_interval] = null; this.petEl.removeEventListener('mousemove', _classPrivateFieldBase(this, _handlePetElMouseMove)[_handlePetElMouseMove]); this.petEl.removeEventListener('mouseleave', _classPrivateFieldBase(this, _handlePetElMouseLeave)[_handlePetElMouseLeave]); } } function _sample2() { if (_classPrivateFieldBase(this, _lastMousePositionInsidePet)[_lastMousePositionInsidePet] != null && _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet] != null) { const normalizedX = _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet].x - _classPrivateFieldBase(this, _lastMousePositionInsidePet)[_lastMousePositionInsidePet].x; const normalizedY = _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet].y - _classPrivateFieldBase(this, _lastMousePositionInsidePet)[_lastMousePositionInsidePet].y; const angle = Math.atan2(normalizedX, normalizedY) * RAD_TO_DEG; if (_classPrivateFieldBase(this, _lastAngle)[_lastAngle] != null) { const anglesDistance = (_classPrivateFieldBase(this, _lastAngle)[_lastAngle] - angle + 180) % 360 - 180; const absAnglesDistance = Math.abs(anglesDistance); // console.debug(MOD_LOG_PREFIX, 'absAnglesDistance:', absAnglesDistance); if (absAnglesDistance > this.options.angleMin + 180 && absAnglesDistance < this.options.angleMax + 180) { _classPrivateFieldBase(this, _scratchCounter)[_scratchCounter] = Math.min(_classPrivateFieldBase(this, _scratchCounter)[_scratchCounter] + 1, this.options.max); } const isBeingPet = _classPrivateFieldBase(this, _scratchCounter)[_scratchCounter] > this.options.threshold; if (isBeingPet) { _classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] = Math.min(_classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] + 1, this.options.activation); } else { _classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] = Math.max(0, _classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] - 1); } // console.debug(MOD_LOG_PREFIX, 'isBeingPet:', isBeingPet); // console.debug(MOD_LOG_PREFIX, 'samplesBeingPet:', this.#samplesBeingPet); if (isBeingPet && _classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] >= this.options.activation) { if (!_classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart]) { this.dispatchEvent(new Event('petting-start')); _classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart] = true; } } else { if (_classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart]) { this.dispatchEvent(new Event('petting-end')); _classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart] = false; } } } _classPrivateFieldBase(this, _lastAngle)[_lastAngle] = angle; } else { _classPrivateFieldBase(this, _lastAngle)[_lastAngle] = null; _classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] = Math.max(0, _classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] - 0.75); if (_classPrivateFieldBase(this, _samplesBeingPet)[_samplesBeingPet] === 0 && _classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart]) { this.dispatchEvent(new Event('petting-end')); _classPrivateFieldBase(this, _lastEventDispatchWasPettingStart)[_lastEventDispatchWasPettingStart] = false; } } _classPrivateFieldBase(this, _lastMousePositionInsidePet)[_lastMousePositionInsidePet] = _classPrivateFieldBase(this, _mousePositionInsidePet)[_mousePositionInsidePet]; _classPrivateFieldBase(this, _scratchCounter)[_scratchCounter] = Math.max(0, _classPrivateFieldBase(this, _scratchCounter)[_scratchCounter] - this.options.neglect); } var styles$4 = {"petting-heart":"thermometer-heart-petting-heart"}; var stylesheet$7="@keyframes thermometer-heart-petting-heart-movement {\n 0% {\n translate: 0 0;\n rotate: 0deg;\n }\n 25% {\n translate: -10% 0;\n rotate: 25deg;\n }\n 50% {\n translate: 0% 0;\n rotate: 0deg;\n }\n 75% {\n translate: 10% 0;\n rotate: -25deg;\n }\n}\n\n/* Based on https://css-tricks.com/hearts-in-html-and-css/#aa-css-shape */\n.thermometer-heart-petting-heart {\n --size: 10px;\n\n position: fixed;\n background-color: red;\n margin: 0 calc(var(--size)/3);\n width: var(--size);\n aspect-ratio: 1;\n display: inline-block;\n transform: translate(-50%, -50%) rotate(-45deg);\n animation: thermometer-heart-petting-heart-movement 2s linear infinite;\n z-index: 0;\n}\n.thermometer-heart-petting-heart::before,\n .thermometer-heart-petting-heart::after {\n content: '';\n position: absolute;\n width: var(--size);\n aspect-ratio: 1;\n border-radius: 50%;\n background-color: red;\n }\n.thermometer-heart-petting-heart::before {\n top: calc(var(--size)/-2);\n left: 0;\n }\n.thermometer-heart-petting-heart::after {\n left: calc(var(--size)/2);\n top: 0;\n }\n"; class Heart { constructor({ initialPosX, initialPosY, velocityX, velocityY, maxLifetime }) { this.el = document.createElement('div'); this.el.classList.add(styles$4['petting-heart']); document.body.append(this.el); this.maxLifetime = maxLifetime; this.lifetime = this.maxLifetime; this.x = initialPosX; this.y = initialPosY; this.velocityX = velocityX; this.velocityY = velocityY; this.lastUpdateTimestamp = Date.now(); this.update(); } update() { this.lifetime -= Date.now() - this.lastUpdateTimestamp; if (this.lifetime <= 0) { this.el.remove(); return; } this.x += this.velocityX; this.y += this.velocityY; this.el.style.left = `${this.x}px`; this.el.style.top = `${this.y}px`; this.el.style.scale = (this.lifetime / this.maxLifetime).toString(10); this.el.style.opacity = (1.5 * this.lifetime / this.maxLifetime).toString(10); this.lastUpdateTimestamp = Date.now(); requestAnimationFrame(this.update.bind(this)); } } GM_addStyle(stylesheet$7); var _tmpl$$5 = /*#__PURE__*/template(`<div><h2>:(</h2><p>Contact @netux about this`), _tmpl$2$2 = /*#__PURE__*/template(`<p>`), _tmpl$3$2 = /*#__PURE__*/template(`<p>T: `), _tmpl$4$2 = /*#__PURE__*/template(`<p>H: <!>%`), _tmpl$5$1 = /*#__PURE__*/template(`<div><div>`); const CLOCK_EMOJIS_MAP = new Map(); { const firstOClockEmojiCodePoint = '🕐'.codePointAt(0); // one-oclock const lastOClockEmojiCodePoint = '🕛'.codePointAt(0); // twelve-oclock for (let i = 0; i <= lastOClockEmojiCodePoint - firstOClockEmojiCodePoint; i++) { CLOCK_EMOJIS_MAP.set((i + 1) % 12, String.fromCodePoint(firstOClockEmojiCodePoint + i)); } const firstThirtyEmojiCodePoint = '🕜'.codePointAt(0); const lastThirtyEmojiCodePoint = '🕧'.codePointAt(0); for (let i = 0; i <= lastThirtyEmojiCodePoint - firstThirtyEmojiCodePoint; i++) { CLOCK_EMOJIS_MAP.set((i + 1.5) % 12, String.fromCodePoint(firstThirtyEmojiCodePoint + i)); } } const useCurrentDate = updateEveryMs => { const [date, setDate] = createSignal(new Date()); const updateInterval = setInterval(() => setDate(new Date()), updateEveryMs); onCleanup(() => { clearInterval(updateInterval); }); return date; }; const ForecastZoneDateClock = props => { const currentDate = useCurrentDate(1000); const forecastZoneDate = createMemo(() => offsetTimezone(currentDate(), props.forecast.utc_offset_seconds)); const forecastZoneDateEmoji = () => { const decimalHour = forecastZoneDate().getHours() % 12 + (forecastZoneDate().getMinutes() >= 30 ? 0.5 : 0); return CLOCK_EMOJIS_MAP.get(decimalHour) || '⏰'; }; const prettyForecastZoneDate = createMemo(() => { const date = forecastZoneDate(); const pad = n => n.toString().padStart(2, '0'); if (settings().time24Hours) { return `${pad(date.getHours())}:${pad(date.getMinutes())}${settings().timeIncludeSeconds ? `:${pad(date.getSeconds())}` : ''}`; } else { return `${pad(date.getHours() % 12)}:${pad(date.getMinutes())}${settings().timeIncludeSeconds ? `:${pad(date.getSeconds())}` : ''} ${date.getHours() >= 12 ? 'PM' : 'AM'}`; } }); return [memo(forecastZoneDateEmoji), " ", memo(prettyForecastZoneDate)]; }; var Thermometer = ({ panel }) => { let graphicEl; const [isBeingGrabbed, setIsBeingGrabbed] = createSignal(false); const [isInfoOnTheRight, setIsInfoOnTheRight] = createSignal(false); const onPanelMoving = () => { const { left, width } = panel.wrapperEl.getBoundingClientRect(); setIsInfoOnTheRight(left + width / 2 < window.innerWidth / 2); }; const onPanelMoveStart = () => { setIsBeingGrabbed(true); }; const onPanelMoveEnd = async () => { setIsBeingGrabbed(false); const { left, top } = panel.movable.getPosition(); await saveSettings(prevSettings => { return _extends({}, prevSettings, { widgetPosition: { x: left / window.innerWidth, y: top / window.innerHeight } }); }); }; const setupPet = () => { const pet = new Pet(graphicEl, { sampleRate: 100, max: 10, threshold: 2.5, activation: 3, neglect: 0.5, angleMin: -20, angleMax: 20 }); let spawnHeartsInterval = null; pet.addEventListener('petting-start', () => { let spawnNextHeartGoingRight = false; spawnHeartsInterval = setInterval(() => { const boundingBox = pet.petEl.getBoundingClientRect(); new Heart({ initialPosX: boundingBox.left + boundingBox.width * (spawnNextHeartGoingRight ? 2 / 3 : 1 / 3), initialPosY: boundingBox.top + boundingBox.height / 3, velocityX: 0.25 * (spawnNextHeartGoingRight ? 1 : -1), velocityY: -0.5, maxLifetime: 5000 }); spawnNextHeartGoingRight = !spawnNextHeartGoingRight; }, 500); }); pet.addEventListener('petting-end', () => { clearInterval(spawnHeartsInterval); spawnHeartsInterval = null; }); }; onMount(() => { panel.movable.addEventListener('move-start', onPanelMoveStart); panel.movable.addEventListener('moving', onPanelMoving); panel.movable.addEventListener('move-end', onPanelMoveEnd); panel.movable.applyOptions({ handlerElements: [graphicEl] }); panel.movable.enable(); setupPet(); }); onCleanup(() => { panel.movable.disable(); panel.movable.removeEventListener('move-start', onPanelMoveStart); panel.movable.removeEventListener('moving', onPanelMoving); panel.movable.removeEventListener('move-end', onPanelMoveEnd); }); createEffect(prevWidgetPosition => { const newWidgetPosition = settings().widgetPosition; if (newWidgetPosition.x !== prevWidgetPosition.x || newWidgetPosition.y !== prevWidgetPosition.y) { panel.movable.setPosition(newWidgetPosition.x * window.innerWidth, newWidgetPosition.y * window.innerHeight); } return newWidgetPosition; }, settings().widgetPosition); const userTemperatureUnit = createMemo(() => TEMPERATURE_UNITS[settings().temperatureUnit]); const graphicFillPercent = createMemo(prev => { const forecast$1 = forecast(); if (!forecast$1) { return prev; } const temperatureGradientLength = Math.abs(settings().temperatureGradientMaxCelsius - settings().temperatureGradientMinCelsius); return forecast$1.current.temperature_2m / temperatureGradientLength; }, 0); const currentTemperatureCelsius = createMemo(previousTemperatureCelsius => { const forecast$1 = forecast(); if (!forecast$1) { // Preserve previous temperature return previousTemperatureCelsius; } return forecast$1.current.temperature_2m; }, null); const graphicColor = createMemo(() => { if (!(currentTemperatureCelsius() || temperatureGradientHasRedrawn())) { return null; } return sampleTemperatureGradient(currentTemperatureCelsius()); }); return (() => { var _el$ = _tmpl$5$1(), _el$2 = _el$.firstChild; insert(_el$, createComponent(SingleInstanceStyle, { key: "ThermometerWidget", children: stylesheet$9 }), _el$2); insert(_el$2, createComponent(Show, { get when() { return error(); }, get children() { var _el$3 = _tmpl$$5(); createRenderEffect(() => className(_el$3, styles$6['error'])); return _el$3; } }), null); insert(_el$2, createComponent(Show, { get when() { return memo(() => !!!loading())() && forecast() != null; }, get children() { return [createComponent(Show, { get when() { return settings().showClock; }, get children() { var _el$4 = _tmpl$2$2(); insert(_el$4, createComponent(ForecastZoneDateClock, { get forecast() { return forecast(); } })); return _el$4; } }), createComponent(Show, { get when() { return settings().showTemperature; }, get children() { var _el$5 = _tmpl$3$2(); _el$5.firstChild; insert(_el$5, () => userTemperatureUnit().fromCelsius(forecast().current.temperature_2m), null); insert(_el$5, () => userTemperatureUnit().unit, null); return _el$5; } }), createComponent(Show, { get when() { return settings().showRelativeHumidity; }, get children() { var _el$8 = _tmpl$4$2(), _el$9 = _el$8.firstChild, _el$1 = _el$9.nextSibling; _el$1.nextSibling; insert(_el$8, () => forecast().current.relative_humidity_2m, _el$1); return _el$8; } })]; } }), null); insert(_el$, createComponent(ThermometerGraphic, { get busy() { return loading(); }, get fillPercent() { return graphicFillPercent(); }, get color() { return graphicColor(); }, get ["class"]() { return styles$6['graphic']; }, ref(r$) { var _ref$ = graphicEl; typeof _ref$ === "function" ? _ref$(r$) : graphicEl = r$; } }), null); createRenderEffect(_p$ => { var _v$ = { [styles$6['container']]: true, [styles$6['is-info-on-the-right']]: isInfoOnTheRight(), [styles$6['is-being-grabbed']]: isBeingGrabbed() }, _v$2 = styles$6['info']; _p$.e = classList(_el$, _v$, _p$.e); _v$2 !== _p$.t && className(_el$2, _p$.t = _v$2); return _p$; }, { e: undefined, t: undefined }); return _el$; })(); }; var irfTabStyles = {"tab-content":"thermometer-irf-tab-tab-content"}; var stylesheet$6=".thermometer-irf-tab-tab-content *,\n .thermometer-irf-tab-tab-content *::before,\n .thermometer-irf-tab-tab-content *::after {\n box-sizing: border-box;\n }\n"; var styles$3 = {"field-group":"thermometer-SettingsFieldGroup-field-group","field-group__label-container":"thermometer-SettingsFieldGroup-field-group__label-container","field-group__input-container":"thermometer-SettingsFieldGroup-field-group__input-container"}; var stylesheet$5=".thermometer-SettingsFieldGroup-field-group {\n margin-block: 1rem;\n gap: 0.25rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n .thermometer-SettingsFieldGroup-field-group label > small {\n color: lightgray;\n display: block;\n }\n\n .thermometer-SettingsFieldGroup-field-group .thermometer-SettingsFieldGroup-field-group__label-container,\n .thermometer-SettingsFieldGroup-field-group .thermometer-SettingsFieldGroup-field-group__input-container {\n width: 100%;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n align-items: center;\n gap: 1ch;\n }\n\n .thermometer-SettingsFieldGroup-field-group .thermometer-SettingsFieldGroup-field-group__input-container {\n justify-content: end;\n white-space: nowrap;\n }\n"; var _tmpl$$4 = /*#__PURE__*/template(`<div><div><label></label></div><div>`); var SettingsFieldGroup = props => { return [createComponent(SingleInstanceStyle, { key: "SettingsFieldGroup", children: stylesheet$5 }), (() => { var _el$ = _tmpl$$4(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$2.nextSibling; insert(_el$3, () => props.label); insert(_el$4, () => props.children); createRenderEffect(_p$ => { var _v$ = styles$3['field-group'], _v$2 = styles$3['field-group__label-container'], _v$3 = props.id, _v$4 = styles$3['field-group__input-container']; _v$ !== _p$.e && className(_el$, _p$.e = _v$); _v$2 !== _p$.t && className(_el$2, _p$.t = _v$2); _v$3 !== _p$.a && setAttribute(_el$3, "for", _p$.a = _v$3); _v$4 !== _p$.o && className(_el$4, _p$.o = _v$4); return _p$; }, { e: undefined, t: undefined, a: undefined, o: undefined }); return _el$; })()]; }; var styles$2 = {"gradient-field-group":"thermometer-SettingsGradientFieldGroup-gradient-field-group","label":"thermometer-SettingsGradientFieldGroup-label","gradient-range":"thermometer-SettingsGradientFieldGroup-gradient-range","gradient-range__input-container":"thermometer-SettingsGradientFieldGroup-gradient-range__input-container","gradient-container":"thermometer-SettingsGradientFieldGroup-gradient-container","stops-container":"thermometer-SettingsGradientFieldGroup-stops-container"}; var stylesheet$4=".thermometer-SettingsGradientFieldGroup-gradient-field-group {\n position: relative;\n margin-block: 1rem;\n gap: 0.25rem;\n display: flex;\n justify-content: space-between;\n flex-direction: column;\n}\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-label {\n width: 100%;\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n align-items: center;\n gap: 1ch;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group canvas {\n width: 100%;\n height: 20px;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-range {\n --inputs-size: 8ch;\n --inputs-size-padding: 3ch;\n --inputs-full-size: calc(var(--inputs-size) + var(--inputs-size-padding));\n\n display: flex;\n justify-content: space-between;\n\n background:\n /* Dotted line */\n repeating-linear-gradient(to right, #b9b9b973 0 3px, transparent 6px 9px),\n \n linear-gradient(\n to right,\n black 0%,\n black var(--inputs-full-size),\n transparent calc(var(--inputs-full-size) + 10%),\n transparent calc(90% - var(--inputs-full-size)),\n black calc(100% - var(--inputs-full-size)),\n black 100%\n );\n background-size: auto 3px;\n background-blend-mode: multiply;\n background-repeat: repeat-x;\n background-position: center;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-range .thermometer-SettingsGradientFieldGroup-gradient-range__input-container {\n display: flex;\n align-items: center;\n gap: 0.5ch;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-range .thermometer-SettingsGradientFieldGroup-gradient-range__input-container input {\n width: var(--inputs-size);\n text-align: right;\n\n -moz-appearance: textfield;\n appearance: textfield;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-range .thermometer-SettingsGradientFieldGroup-gradient-range__input-container input::-webkit-inner-spin-button,\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-range .thermometer-SettingsGradientFieldGroup-gradient-range__input-container input::-webkit-inner-spin-button {\n display: none;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-gradient-container {\n position: relative;\n padding-top: 0.33rem;\n margin-inline: 0.5rem;\n cursor: pointer;\n }\n\n .thermometer-SettingsGradientFieldGroup-gradient-field-group .thermometer-SettingsGradientFieldGroup-stops-container {\n --stop-height: 0.85rem;\n --stop-tip-height: 7px;\n --stop-border-size: 2px;\n\n position: relative;\n min-height: calc(var(--stop-tip-height) + var(--stop-height) + var(--stop-border-size)*2);\n }\n"; var styles$1 = {"gradient-marker":"thermometer-GradientMarker-gradient-marker","gradient-marker--current":"thermometer-GradientMarker-gradient-marker--current","gradient-marker--cursor":"thermometer-GradientMarker-gradient-marker--cursor"}; var stylesheet$3=".thermometer-GradientMarker-gradient-marker {\n --marker-color: pink;\n\n position: absolute;\n top: 0;\n translate: -50% -133%;\n text-align: center;\n white-space: nowrap;\n font-size: 75%;\n text-shadow: 0 0 3px black;\n pointer-events: none;\n}\n\n .thermometer-GradientMarker-gradient-marker.thermometer-GradientMarker-gradient-marker--current {\n --marker-color: gray;\n }\n\n .thermometer-GradientMarker-gradient-marker.thermometer-GradientMarker-gradient-marker--cursor {\n --marker-color: red;\n }\n\n .thermometer-GradientMarker-gradient-marker::after {\n content: '';\n position: absolute;\n translate: -50% 100%;\n left: 50%;\n bottom: 0;\n width: 1rem;\n height: 0.5rem;\n background-color: var(--marker-color);\n clip-path: polygon(50% 100%, 0 0, 100% 0);\n }\n"; var _tmpl$$3 = /*#__PURE__*/template(`<span>`); let GradientMarkerType = /*#__PURE__*/function (GradientMarkerType) { GradientMarkerType["CURRENT"] = "current"; GradientMarkerType["CURSOR"] = "cursor"; return GradientMarkerType; }({}); var GradientMarker = props => { const temperaturePercent = () => zeroOne(props.temperatureCelsius, settings().temperatureGradientMinCelsius, settings().temperatureGradientMaxCelsius); const prettyTemperatureValue = value => { switch (props.type) { case GradientMarkerType.CURSOR: { // Round to closest multiple of 0.5 return (Math.round(value * 2) / 2).toFixed(1); } default: { return Math.round(value).toFixed(0); } } }; const userTemperatureUnit = () => TEMPERATURE_UNITS[settings().temperatureUnit]; return [createComponent(SingleInstanceStyle, { key: "GradientMarker", children: stylesheet$3 }), (() => { var _el$ = _tmpl$$3(); insert(_el$, () => prettyTemperatureValue(userTemperatureUnit().fromCelsius(props.temperatureCelsius)), null); insert(_el$, () => userTemperatureUnit().unit, null); createRenderEffect(_p$ => { var _v$ = { [styles$1['gradient-marker']]: true, [styles$1[`gradient-marker--${props.type}`]]: true }, _v$2 = `${temperaturePercent() * 100}%`; _p$.e = classList(_el$, _v$, _p$.e); _v$2 !== _p$.t && setStyleProperty(_el$, "left", _p$.t = _v$2); return _p$; }, { e: undefined, t: undefined }); return _el$; })()]; }; var styles = {"gradient-stop":"thermometer-GradientColorStop-gradient-stop"}; var stylesheet$2=".thermometer-GradientColorStop-gradient-stop {\n position: absolute;\n background-color: transparent;\n translate: -50% 0;\n height: var(--stop-height);\n aspect-ratio: 0.9;\n display: inline-block;\n border-radius: 4px 4px 2px 2px;\n border: var(--stop-border-size) solid white;\n margin-top: var(--stop-tip-height);\n cursor: grab;\n}\n\n .thermometer-GradientColorStop-gradient-stop::before {\n content: '';\n background-color: white;\n width: var(--stop-tip-height);\n aspect-ratio: 1;\n position: absolute;\n top: 0;\n left: 50%;\n translate: -50% -100%;\n clip-path: polygon(50% 0, 100% 100%, 0 100%);\n }\n\n .thermometer-GradientColorStop-gradient-stop input[type='color'] {\n pointer-events: none;\n width: 1px;\n aspect-ratio: 1;\n opacity: 0.01;\n }\n"; var _tmpl$$2 = /*#__PURE__*/template(`<div><input type=color>`); var GradientColorStop = props => { let stopEl; let colorInputEl; let draggingState = null; const onMouseDown = event => { event.preventDefault(); if (event.button !== 0 /* left click */) { return; } draggingState = { startPos: { x: event.clientX, y: event.clientY } }; }; const onDocumentMouseUp = event => { if (draggingState == null) { return; } event.preventDefault(); if (Math.abs(draggingState.startPos.x - event.clientX) + Math.abs(draggingState.startPos.y - event.clientY) < 5) { colorInputEl.click(); } draggingState = null; }; const onDocumentMouseMove = event => { if (draggingState == null) { return; } event.preventDefault(); const gradientStopBoundingBox = stopEl.parentElement.getBoundingClientRect(); const percent = (event.clientX - gradientStopBoundingBox.left) / gradientStopBoundingBox.width; const clampedPercent = Math.max(0, Math.min(percent, 1)); props.onMove(clampedPercent); }; const onContextMenu = event => { event.preventDefault(); props.onDelete(); }; const onColorInputChange = event => { event.preventDefault(); props.onChange(colorInputEl.value); }; onMount(() => { document.addEventListener('mouseup', onDocumentMouseUp); document.addEventListener('mousemove', onDocumentMouseMove); }); onCleanup(() => { document.removeEventListener('mouseup', onDocumentMouseUp); document.removeEventListener('mousemove', onDocumentMouseMove); }); return [createComponent(SingleInstanceStyle, { key: "GradientColorStop", children: stylesheet$2 }), (() => { var _el$ = _tmpl$$2(), _el$2 = _el$.firstChild; var _ref$ = stopEl; typeof _ref$ === "function" ? use(_ref$, _el$) : stopEl = _el$; addEventListener(_el$, "contextmenu", onContextMenu); addEventListener(_el$, "mousedown", onMouseDown); var _ref$2 = colorInputEl; typeof _ref$2 === "function" ? use(_ref$2, _el$2) : colorInputEl = _el$2; addEventListener(_el$2, "input", onColorInputChange); createRenderEffect(_p$ => { var _v$ = styles['gradient-stop'], _v$2 = `${props.percent * 100}%`, _v$3 = props.color, _v$4 = Math.round(props.percent * 100); _v$ !== _p$.e && className(_el$, _p$.e = _v$); _v$2 !== _p$.t && setStyleProperty(_el$, "left", _p$.t = _v$2); _v$3 !== _p$.a && setStyleProperty(_el$, "background-color", _p$.a = _v$3); _v$4 !== _p$.o && setStyleProperty(_el$, "z-index", _p$.o = _v$4); return _p$; }, { e: undefined, t: undefined, a: undefined, o: undefined }); createRenderEffect(() => _el$2.value = props.color); return _el$; })()]; }; var _tmpl$$1 = /*#__PURE__*/template(`<div><input type=number>`), _tmpl$2$1 = /*#__PURE__*/template(`<div>`), _tmpl$3$1 = /*#__PURE__*/template(`<div><div>`), _tmpl$4$1 = /*#__PURE__*/template(`<div><div><label>Temperature Gradient</label><button><img src=https://www.svgrepo.com/show/511181/undo.svg>`); const GradientRangeInput = props => { const userTemperatureUnit = () => TEMPERATURE_UNITS[settings().temperatureUnit]; const onChange = event => { const numberValue = parseFloat(event.currentTarget.value); if (Number.isNaN(numberValue)) { return; } props.onChange(userTemperatureUnit().toCelsius(numberValue)); }; return (() => { var _el$ = _tmpl$$1(), _el$2 = _el$.firstChild; addEventListener(_el$2, "change", onChange); insert(_el$, () => userTemperatureUnit().unit, null); createRenderEffect(_p$ => { var _v$ = styles$2['gradient-range__input-container'], _v$2 = props.id; _v$ !== _p$.e && className(_el$, _p$.e = _v$); _v$2 !== _p$.t && setAttribute(_el$2, "id", _p$.t = _v$2); return _p$; }, { e: undefined, t: undefined }); createRenderEffect(() => _el$2.value = userTemperatureUnit().fromCelsius(props.valueCelsius)); return _el$; })(); }; const GradientRange = () => { const onChangeMin = newMinTemperatureCelsius => saveSettings(previousSettings => _extends({}, previousSettings, { temperatureGradientMinCelsius: Math.min(newMinTemperatureCelsius, previousSettings.temperatureGradientMaxCelsius), temperatureGradientMaxCelsius: Math.max(newMinTemperatureCelsius, previousSettings.temperatureGradientMaxCelsius) })); const onChangeMax = newMaxTemperatureCelsius => saveSettings(previousSettings => _extends({}, previousSettings, { temperatureGradientMinCelsius: Math.min(previousSettings.temperatureGradientMinCelsius, newMaxTemperatureCelsius), temperatureGradientMaxCelsius: Math.max(previousSettings.temperatureGradientMinCelsius, newMaxTemperatureCelsius) })); return (() => { var _el$3 = _tmpl$2$1(); insert(_el$3, createComponent(GradientRangeInput, { id: `${MOD_DOM_SAFE_PREFIX}gradient-temperature-min`, get valueCelsius() { return settings().temperatureGradientMinCelsius; }, onChange: onChangeMin }), null); insert(_el$3, createComponent(GradientRangeInput, { id: `${MOD_DOM_SAFE_PREFIX}gradient-temperature-max`, get valueCelsius() { return settings().temperatureGradientMaxCelsius; }, onChange: onChangeMax }), null); createRenderEffect(() => className(_el$3, styles$2['gradient-range'])); return _el$3; })(); }; const GradientContainer = () => { const [hoveringTemperature, setHoveringTemperature] = createSignal(null); const onGradientContainerMouseMove = event => { const { left: containerClientX, width: containerWidth } = event.currentTarget.getBoundingClientRect(); const containerOffsetX = event.clientX - containerClientX; const percent = containerOffsetX / containerWidth; setHoveringTemperature(temperatureAtGradient(percent)); }; const onGradientContainerMouseLeave = () => { setHoveringTemperature(null); }; const onGradientContainerDoubleClick = event => { event.preventDefault(); event.stopPropagation(); const percent = event.offsetX / event.currentTarget.clientWidth; const color = sampleTemperatureGradient(temperatureAtGradient(percent)); const newStop = { percent, color }; saveSettings(previousSettings => _extends({}, previousSettings, { temperatureGradient: previousSettings.temperatureGradient.concat(newStop) })); }; return (() => { var _el$4 = _tmpl$3$1(), _el$5 = _el$4.firstChild; addEventListener(_el$4, "dblclick", onGradientContainerDoubleClick); addEventListener(_el$4, "mouseleave", onGradientContainerMouseLeave); addEventListener(_el$4, "mousemove", { handleEvent: onGradientContainerMouseMove, capture: true }); insert(_el$4, createComponent(Show, { get when() { return forecast(); }, get children() { return createComponent(GradientMarker, { get type() { return GradientMarkerType.CURRENT; }, get temperatureCelsius() { return forecast().current.temperature_2m; } }); } }), _el$5); insert(_el$4, createComponent(Show, { get when() { return hoveringTemperature(); }, get children() { return createComponent(GradientMarker, { get type() { return GradientMarkerType.CURSOR; }, get temperatureCelsius() { return hoveringTemperature(); } }); } }), _el$5); insert(_el$4, temperatureGradientCanvas, _el$5); insert(_el$5, createComponent(Index, { get each() { return settings().temperatureGradient; }, children: (stop, index) => { const onMove = newPercent => { saveSettings(previousSettings => _extends({}, previousSettings, { temperatureGradient: previousSettings.temperatureGradient.with(index, _extends({}, stop(), { percent: newPercent })) })); }; const onColorChange = newColor => { saveSettings(previousSettings => _extends({}, previousSettings, { temperatureGradient: previousSettings.temperatureGradient.with(index, _extends({}, stop(), { color: newColor })) })); }; const onDelete = () => { saveSettings(previousSettings => _extends({}, previousSettings, { temperatureGradient: previousSettings.temperatureGradient.toSpliced(index, 1) })); }; return createComponent(GradientColorStop, { get color() { return stop().color; }, get percent() { return stop().percent; }, onMove: onMove, onChange: onColorChange, onDelete: onDelete }); } })); createRenderEffect(_p$ => { var _v$3 = styles$2['gradient-container'], _v$4 = styles$2['stops-container']; _v$3 !== _p$.e && className(_el$4, _p$.e = _v$3); _v$4 !== _p$.t && className(_el$5, _p$.t = _v$4); return _p$; }, { e: undefined, t: undefined }); return _el$4; })(); }; var SettingsGradientFieldGroup = () => { const id = `${MOD_DOM_SAFE_PREFIX}temperature-gradient`; const onResetButtonClick = () => { if (!confirm(['This will reset the thermometer gradient range and color stops to their default values.', 'Are you sure you want to continue?'].join('\n'))) { return; } saveSettings(previousSettings => _extends({}, previousSettings, getDefaultTemperatureGradientSettings())); }; return (() => { var _el$6 = _tmpl$4$1(), _el$7 = _el$6.firstChild, _el$8 = _el$7.firstChild, _el$9 = _el$8.nextSibling; insert(_el$6, createComponent(SingleInstanceStyle, { key: "SettingsGradientFieldGroup", children: stylesheet$4 }), _el$7); setAttribute(_el$8, "for", id); addEventListener(_el$9, "click", onResetButtonClick); insert(_el$6, createComponent(GradientRange, {}), null); insert(_el$6, createComponent(GradientContainer, {}), null); createRenderEffect(_p$ => { var _v$5 = styles$2['gradient-field-group'], _v$6 = styles$2['label']; _v$5 !== _p$.e && className(_el$6, _p$.e = _v$5); _v$6 !== _p$.t && className(_el$7, _p$.t = _v$6); return _p$; }, { e: undefined, t: undefined }); return _el$6; })(); }; var irfPanelDesignStyles = {"toggle":"thermometer-irf-panel-design-toggle"}; var stylesheet$1="/*\n * Copied from <https://raw.githubusercontent.com/Mikarific/InternetRoadtripFramework/c8fbacda2f65d7ff76b9ad94d155ad2c20829abd/src/lib/panel.module.css>.\n * We cannot use IRF.ui.panel.styles.* because those classes are not accessible from inside the shadow root we create for our settings panel.\n *\n * TODO(netux): Consider a better solution for IRF 0.5.0-beta?\n */\n\n.thermometer-irf-panel-design-toggle {\n --toggle-offset: -1.5rem;\n width: 3rem;\n height: 1.5rem;\n margin: 0;\n box-shadow:\n inset var(--toggle-offset) 0 0 2px #000,\n inset 0 0 0 2px #000;\n transition-property: box-shadow;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n transition-duration: 200ms;\n transition-timing-function: cubic-bezier(0, 0, 0.2, 1);\n -webkit-appearance: none;\n appearance: none;\n border: 1px solid #848e95;\n border-radius: 9999px;\n background: #848e95;\n cursor: pointer;\n}\n.thermometer-irf-panel-design-toggle:checked {\n --toggle-offset: 1.5rem;\n background: #fff;\n border-color: #fff;\n}\n.thermometer-irf-panel-design-toggle:disabled {\n cursor: not-allowed;\n opacity: 0.3;\n}\n\n.thermometer-irf-panel-design-slider {\n width: 100%;\n height: 1.5rem;\n margin: 0;\n border-radius: 0.75rem;\n background-color: #0000;\n border: none;\n overflow: hidden;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n cursor: pointer;\n vertical-align: middle;\n}\n\n.thermometer-irf-panel-design-slider:focus {\n outline: none;\n}\n.thermometer-irf-panel-design-slider:focus-visible {\n outline-offset: 2px;\n outline: 2px solid;\n}\n.thermometer-irf-panel-design-slider::-webkit-slider-runnable-track {\n background-color: #41474b;\n border-radius: 0.5rem;\n width: 100%;\n height: 0.75rem;\n}\n@media (forced-colors: active) {\n .thermometer-irf-panel-design-slider::-webkit-slider-runnable-track {\n border: 1px solid;\n }\n .thermometer-irf-panel-design-slider::-moz-range-track {\n border: 1px solid;\n }\n}\n.thermometer-irf-panel-design-slider::-webkit-slider-thumb {\n box-sizing: border-box;\n border-radius: 0.75rem;\n height: 1.5rem;\n width: 1.5rem;\n border: 0.25rem solid;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n color: #fff;\n box-shadow:\n 0 0 0 2rem #000 inset,\n -100.75rem 0 0 100rem;\n background-color: #fff;\n position: relative;\n top: 50%;\n transform: translateY(-50%);\n}\n.thermometer-irf-panel-design-slider::-moz-range-track {\n background-color: #41474b;\n border-radius: 0.5rem;\n width: 100%;\n height: 0.75rem;\n}\n.thermometer-irf-panel-design-slider::-moz-range-thumb {\n box-sizing: border-box;\n border-radius: 0.75rem;\n height: 1.5rem;\n width: 1.5rem;\n border: 0.25rem solid;\n color: #fff;\n box-shadow:\n 0 0 0 2rem #000 inset,\n -100.75rem 0 0 100rem;\n background-color: #fff;\n position: relative;\n top: 50%;\n}\n.thermometer-irf-panel-design-slider:disabled {\n cursor: not-allowed;\n opacity: 0.3;\n}\n"; var stylesheet="input:is(:not([type]), [type='text'], [type='number']),\nselect {\n --padding-inline: 0.5rem;\n --border-radius: 0.8rem;\n --border-color: #848e95;\n\n width: calc(100% - var(--padding-inline)*2);\n min-height: 1.5rem;\n margin: 0;\n padding-inline: var(--padding-inline);\n color: white;\n background: transparent;\n border: 1px solid var(--border-color);\n font-size: 100%;\n border-radius: var(--border-radius);\n}\n\noption {\n background-color: black;\n}\n\n@supports (appearance: base-select) {\n select,\n ::picker(select) {\n appearance: base-select;\n font-size: 0.9rem;\n }\n\n select::picker-icon {\n scale: 0.9;\n margin-right: 0.125rem;\n }\n\n select:open {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-color: transparent;\n }\n\n ::picker(select) {\n margin-top: -1px;\n border: none;\n background-color: black;\n border-bottom-left-radius: var(--border-radius);\n border-bottom-right-radius: var(--border-radius);\n border: 1px solid var(--border-color);\n border-top-color: transparent;\n }\n\n option {\n color: white;\n background-color: black;\n }\n\n option::checkmark {\n display: none;\n }\n\n option:checked {\n background-color: rgb(255 255 255 / 10%);\n }\n\n option:hover {\n background-color: rgb(255 255 255 / 20%);\n }\n}\n\nbutton {\n padding: 0.25rem;\n margin-left: 0.125rem;\n gap: 0.25rem;\n cursor: pointer;\n border: none;\n font-size: 0.9rem;\n align-items: center;\n justify-content: center;\n background-color: white;\n display: inline-flex;\n}\n\nbutton:hover {\n background-color: #d3d3d3;\n }\n\nbutton > img {\n width: 1rem;\n aspect-ratio: 1;\n }\n"; var _tmpl$ = /*#__PURE__*/template(`<select>`), _tmpl$2 = /*#__PURE__*/template(`<option>`), _tmpl$3 = /*#__PURE__*/template(`<input type=checkbox>`), _tmpl$4 = /*#__PURE__*/template(`<h2>Clock`), _tmpl$5 = /*#__PURE__*/template(`<h2>Temperature`), _tmpl$6 = /*#__PURE__*/template(`<h2>Relative Humidity`), _tmpl$7 = /*#__PURE__*/template(`<button>Reset widget position`); const SettingsTemperatureUnitFieldGroup = () => { const label = 'Temperature Unit'; const id = `${MOD_DOM_SAFE_PREFIX}temperature-unit`; const onChange = async event => { await saveSettings({ temperatureUnit: event.currentTarget.value }); }; return createComponent(SettingsFieldGroup, { id: id, label: label, get children() { var _el$ = _tmpl$(); addEventListener(_el$, "change", onChange); setAttribute(_el$, "id", id); insert(_el$, createComponent(For, { get each() { return Object.entries(TEMPERATURE_UNITS); }, children: ([value, { label }]) => (() => { var _el$2 = _tmpl$2(); _el$2.value = value; insert(_el$2, label); return _el$2; })() })); createRenderEffect(() => _el$.value = settings().temperatureUnit); return _el$; } }); }; const SettingsToggleFieldGroup = function (props) { props = mergeProps({ inverted: false, disabled: false }, props); const onChange = async event => { const checked = event.currentTarget.checked; await saveSettings({ [props.field]: props.inverted ? !checked : checked }); }; return createComponent(SettingsFieldGroup, { get id() { return props.id; }, get label() { return props.label; }, get children() { var _el$3 = _tmpl$3(); addEventListener(_el$3, "change", onChange); createRenderEffect(_p$ => { var _v$ = props.id, _v$2 = irfPanelDesignStyles['toggle'], _v$3 = props.disabled; _v$ !== _p$.e && setAttribute(_el$3, "id", _p$.e = _v$); _v$2 !== _p$.t && className(_el$3, _p$.t = _v$2); _v$3 !== _p$.a && (_el$3.disabled = _p$.a = _v$3); return _p$; }, { e: undefined, t: undefined, a: undefined }); createRenderEffect(() => _el$3.checked = props.inverted ? !settings()[props.field] : settings()[props.field]); return _el$3; } }); }; var SettingsTab = () => { return [createComponent(SingleInstanceStyle, { key: "SettingsTab", children: stylesheet }), createComponent(SingleInstanceStyle, { key: "IRFTabExported", children: stylesheet$1 }), _tmpl$4(), createComponent(SettingsToggleFieldGroup, { id: `${MOD_DOM_SAFE_PREFIX}show-clock`, label: "Show Clock", field: "showClock" }), createComponent(SettingsToggleFieldGroup, { id: `${MOD_DOM_SAFE_PREFIX}time-24-hour`, label: "Use AM/PM", field: "time24Hours", inverted: true, get disabled() { return !settings().showClock; } }), createComponent(SettingsToggleFieldGroup, { id: `${MOD_DOM_SAFE_PREFIX}time-include-seconds`, label: "Show Seconds", field: "timeIncludeSeconds", get disabled() { return !settings().showClock; } }), _tmpl$5(), createComponent(SettingsToggleFieldGroup, { id: `${MOD_DOM_SAFE_PREFIX}show-temperature`, label: "Show Temperature", field: "showTemperature" }), createComponent(SettingsTemperatureUnitFieldGroup, {}), createComponent(SettingsGradientFieldGroup, {}), _tmpl$6(), createComponent(SettingsToggleFieldGroup, { id: `${MOD_DOM_SAFE_PREFIX}show-relative-humidity`, label: "Show Relative Humidity", field: "showRelativeHumidity" }), (() => { var _el$7 = _tmpl$7(); addEventListener(_el$7, "click", async () => { await saveSettings({ widgetPosition: DEFAULT_WIDGET_POSITION }); }); return _el$7; })()]; }; function createWidget() { const panel = makePanel({ element: Thermometer, zIndex: 200 }); setPanel(panel); panel.show(); panel.movable.setPosition(settings().widgetPosition.x * window.innerWidth, settings().widgetPosition.y * window.innerHeight); } function createIrfTab() { const irfTab = IRF.ui.panel.createTabFor(_extends({}, GM.info, { script: _extends({}, GM.info.script, { name: MOD_NAME }) }), { tabName: MOD_NAME, className: irfTabStyles['tab-content'], style: stylesheet$6 }); render( // <ShadowRooted> is a workaround for IRF not having per-tab stylesheet isolation. // TODO(netux): remove <ShadowRooted> for IRF v0.5.0-beta () => createComponent(ShadowRooted, { get children() { return createComponent(SettingsTab, {}); } }), irfTab.container); } async function tickForecast() { const containerVDOM = await IRF.vdom.container; setForecastLoading(); let forecast; try { forecast = await fetchForecast([containerVDOM.state.currentCoords.lat, containerVDOM.state.currentCoords.lng]); } catch (error) { setForecastLoadFailure(error); console.error(MOD_LOG_PREFIX, 'Could not fetch forecast', error); return; } console.debug(MOD_LOG_PREFIX, 'New forecast received:', forecast); setForecastLoadSuccess(forecast); } createWidget(); createIrfTab(); waitForCoordinatesToBeSetAtLeastOnce.then(() => { setInterval(tickForecast, 15 * 60000 /* every 15 minutes */); tickForecast(); }); })(IRF, VM);