Mark up YouTube videos and quickly generate clipped webms.
// modules are defined as an array
// [ module function, map of requires ]
//
// map of requires is short require name -> numeric require
//
// anything defined in a previous bundle is accessed via the
// orig method which is the require for previous bundles
(function (
modules,
entry,
mainEntry,
parcelRequireName,
externals,
distDir,
publicUrl,
devServer
) {
/* eslint-disable no-undef */
var globalObject =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {};
/* eslint-enable no-undef */
// Save the require from previous bundle to this closure if any
var previousRequire =
typeof globalObject[parcelRequireName] === 'function' &&
globalObject[parcelRequireName];
var importMap = previousRequire.i || {};
var cache = previousRequire.cache || {};
// Do not use `require` to prevent Webpack from trying to bundle this call
var nodeRequire =
typeof module !== 'undefined' &&
typeof module.require === 'function' &&
module.require.bind(module);
function newRequire(name, jumped) {
if (!cache[name]) {
if (!modules[name]) {
if (externals[name]) {
return externals[name];
}
// if we cannot find the module within our internal map or
// cache jump to the current global require ie. the last bundle
// that was added to the page.
var currentRequire =
typeof globalObject[parcelRequireName] === 'function' &&
globalObject[parcelRequireName];
if (!jumped && currentRequire) {
return currentRequire(name, true);
}
// If there are other bundles on this page the require from the
// previous one is saved to 'previousRequire'. Repeat this as
// many times as there are bundles until the module is found or
// we exhaust the require chain.
if (previousRequire) {
return previousRequire(name, true);
}
// Try the node require function if it exists.
if (nodeRequire && typeof name === 'string') {
return nodeRequire(name);
}
var err = new Error("Cannot find module '" + name + "'");
err.code = 'MODULE_NOT_FOUND';
throw err;
}
localRequire.resolve = resolve;
localRequire.cache = {};
var module = (cache[name] = new newRequire.Module(name));
modules[name][0].call(
module.exports,
localRequire,
module,
module.exports,
globalObject
);
}
return cache[name].exports;
function localRequire(x) {
var res = localRequire.resolve(x);
if (res === false) {
return {};
}
// Synthesize a module to follow re-exports.
if (Array.isArray(res)) {
var m = {__esModule: true};
res.forEach(function (v) {
var key = v[0];
var id = v[1];
var exp = v[2] || v[0];
var x = newRequire(id);
if (key === '*') {
Object.keys(x).forEach(function (key) {
if (
key === 'default' ||
key === '__esModule' ||
Object.prototype.hasOwnProperty.call(m, key)
) {
return;
}
Object.defineProperty(m, key, {
enumerable: true,
get: function () {
return x[key];
},
});
});
} else if (exp === '*') {
Object.defineProperty(m, key, {
enumerable: true,
value: x,
});
} else {
Object.defineProperty(m, key, {
enumerable: true,
get: function () {
if (exp === 'default') {
return x.__esModule ? x.default : x;
}
return x[exp];
},
});
}
});
return m;
}
return newRequire(res);
}
function resolve(x) {
var id = modules[name][1][x];
return id != null ? id : x;
}
}
function Module(moduleName) {
this.id = moduleName;
this.bundle = newRequire;
this.require = nodeRequire;
this.exports = {};
}
newRequire.isParcelRequire = true;
newRequire.Module = Module;
newRequire.modules = modules;
newRequire.cache = cache;
newRequire.parent = previousRequire;
newRequire.distDir = distDir;
newRequire.publicUrl = publicUrl;
newRequire.devServer = devServer;
newRequire.i = importMap;
newRequire.register = function (id, exports) {
modules[id] = [
function (require, module) {
module.exports = exports;
},
{},
];
};
// Only insert newRequire.load when it is actually used.
// The code in this file is linted against ES5, so dynamic import is not allowed.
// INSERT_LOAD_HERE
Object.defineProperty(newRequire, 'root', {
get: function () {
return globalObject[parcelRequireName];
},
});
globalObject[parcelRequireName] = newRequire;
for (var i = 0; i < entry.length; i++) {
newRequire(entry[i]);
}
if (mainEntry) {
// Expose entry point to Node, AMD or browser globals
// Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
var mainExports = newRequire(mainEntry);
// CommonJS
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = mainExports;
// RequireJS
} else if (typeof define === 'function' && define.amd) {
define(function () {
return mainExports;
});
}
}
})({"6vE65":[function(require,module,exports,__globalThis) {
// BANNER GUARD
// ==UserScript==
// BANNER GUARD
// @locale english
// @name yt_clipper
// @version 5.44.0
// @description Mark up YouTube videos and quickly generate clipped webms.
// @author elwm
// @namespace https://github.com/exwm
// @homepage https://github.com/exwm/yt_clipper
// @supportURL https://github.com/exwm/yt_clipper/issues
// @icon https://raw.githubusercontent.com/exwm/yt_clipper/master/assets/image/pepe-clipper.gif
// @license MIT
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.min.js
// @require https://cdn.jsdelivr.net/gh/exwm/Chart.js@141fe542034bc127b0a932de25d0c4f351f3bce1/dist/Chart.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js
// @require https://cdn.jsdelivr.net/gh/exwm/chartjs-plugin-zoom@b1adf6115d5816cabf0d82fba87950a32f7f965e/dist/chartjs-plugin-zoom.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/chartjs-plugin-datalabels.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/chartjs-plugin-style.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/chartjs-plugin-annotation.min.js
// @run-at document-end
// @match http*://*.youtube.com/*
// @match http*://*.vlive.tv/video/*
// @match http*://*.vlive.tv/post/*
// @match http*://weverse.io/*
// @match https*://tv.naver.com/*
// @match https*://*.afreecatv.com/*
// @match https*://exwm.github.io/yt_clipper/*
// @noframes
// dummy grant to enable sandboxing
// @grant GM_getValue
// BANNER GUARD
// ==/UserScript==
// BANNER GUARD
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "shortcutsTableStyle", ()=>shortcutsTableStyle);
parcelHelpers.export(exports, "platform", ()=>platform);
parcelHelpers.export(exports, "selectors", ()=>selectors);
parcelHelpers.export(exports, "initOnceCalled", ()=>initOnceCalled);
parcelHelpers.export(exports, "shortcutRegistry", ()=>shortcutRegistry);
parcelHelpers.export(exports, "commandPalette", ()=>commandPalette);
parcelHelpers.export(exports, "initShortcutSystem", ()=>initShortcutSystem);
parcelHelpers.export(exports, "isTheatreMode", ()=>isTheatreMode);
parcelHelpers.export(exports, "updateSettingsEditorHook", ()=>updateSettingsEditorHook);
var _fs = require("fs");
var _immer = require("immer");
var _misc = require("./actions/misc");
var _featureFlags = require("./feature-flags");
var _common = require("./platforms/blockers/common");
var _youtube = require("./platforms/blockers/youtube");
var _platforms = require("./platforms/platforms");
var _chartJsDragDataPlugin = require("./ui/chart/chart.js-drag-data-plugin");
var _chartutil = require("./ui/chart/chartutil");
var _util = require("./util/util");
var _cropPreview = require("./crop/crop-preview");
var _commandPalette = require("../command-palette");
var _shortcutDefinitions = require("./shortcut-definitions");
var _appState = require("./appState");
var _frameCapture = require("./frame-capture");
var _videoRotation = require("./video-rotation");
var _previewToggles = require("./preview-toggles");
var _speed = require("./speed");
var _saveLoad = require("./save-load");
var _cropUtils = require("./crop-utils");
var _globalSettingsEditor = require("./features/settings/global-settings-editor");
var _bootstrap = require("./bootstrap");
var _navigation = require("./navigation");
var _navigation1 = require("./platforms/navigation");
var _cropOverlay = require("./crop-overlay");
var _markers = require("./markers");
var _charts = require("./charts");
var _urlSanitizer = require("./util/url-sanitizer");
var _videoUtil = require("./util/videoUtil");
var _hintsBar = require("./features/hints-bar/hints-bar");
var _hoverRegion = require("./features/hints-bar/hover-region");
var _settingsEditor = require("./features/settings/settings-editor");
var _markerSettingsEditor = require("./features/settings/marker-settings-editor");
const ytClipperCSS = ":root {\n --lighter-grey: rgb(235, 235, 235);\n --light-grey: rgb(210, 210, 210);\n --med-light-grey: rgb(160, 160, 160);\n --med-grey: rgb(110, 110, 110);\n --med-dark-grey: rgb(90, 90, 90);\n --dark-grey: rgb(40, 40, 40);\n --darker-grey: rgb(10, 10, 10);\n --bright-red: rgb(255, 0, 0);\n --dark-red: rgb(50, 0, 0);\n --marker-pair-editor-accent: rgb(245, 118, 0);\n --global-editor-accent: rgb(245, 0, 0);\n --inherited-accent: dimgrey;\n}\n\n@keyframes valid-input {\n 0% {\n background-color: tomato;\n }\n 100% {\n background-color: lightgreen;\n }\n}\n\n@keyframes invalid-input {\n 0% {\n background-color: lightgreen;\n }\n 100% {\n background-color: tomato;\n }\n}\n\n@keyframes flash {\n 0% {\n opacity: 1;\n }\n 85% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n }\n}\n\n.msg-div,\n.long-msg-div,\n.long-msg-div input:not([type='file']) {\n display: inline-block;\n margin: 4px;\n padding: 4px;\n color: var(--light-grey);\n background: var(--dark-grey);\n box-shadow: 2px 2px 3px 0px var(--darker-grey);\n border-radius: 2px;\n border-color: var(--med-grey);\n border-width: 1px 0px 0px 1px;\n font-size: 10pt;\n font-weight: 500;\n}\n\n.long-msg-div {\n display: block;\n}\n\n.flash-div {\n animation-name: flash;\n animation-duration: 5s;\n animation-fill-mode: forwards;\n border-left: 3px solid transparent;\n}\n\n.flash-info {\n border-left-color: #6cb7ff;\n}\n\n.flash-success {\n border-left-color: #3ac36a;\n background: linear-gradient(to right, rgba(58, 195, 106, 0.18), var(--dark-grey) 60%);\n}\n\n.flash-warn {\n border-left-color: #f5a623;\n background: linear-gradient(to right, rgba(245, 166, 35, 0.2), var(--dark-grey) 60%);\n}\n\n.flash-warn .flash-msg {\n color: #ffc661 !important;\n font-weight: 600;\n}\n\n.flash-error {\n border-left-color: var(--bright-red);\n background: linear-gradient(to right, rgba(237, 28, 63, 0.28), var(--dark-grey) 60%);\n box-shadow: 2px 2px 3px 0px var(--darker-grey), 0 0 0 1px rgba(237, 28, 63, 0.4);\n}\n\n.flash-error .flash-msg {\n color: #ff6b85 !important;\n font-weight: 700;\n}\n\n.flash-count {\n margin-left: 4px;\n padding: 1px 6px;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.15);\n color: var(--lighter-grey);\n font-size: 9pt;\n font-weight: 700;\n}\n\n#ytc-stale-video-banner {\n display: flex;\n align-items: center;\n gap: 12px;\n margin: 8px 4px;\n padding: 10px 14px;\n background: linear-gradient(to right, rgba(237, 28, 63, 0.28), var(--dark-grey) 60%);\n border: 1px solid rgba(237, 28, 63, 0.6);\n border-left: 4px solid var(--bright-red);\n border-radius: 3px;\n color: var(--lighter-grey);\n font-size: 11pt;\n box-shadow: 2px 2px 3px 0px var(--darker-grey), 0 0 0 1px rgba(237, 28, 63, 0.4);\n position: relative;\n z-index: 100;\n}\n\n.ytc-stale-banner-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n flex-shrink: 0;\n border-radius: 50%;\n background: var(--bright-red);\n color: #fff;\n font-weight: 900;\n font-size: 13pt;\n line-height: 1;\n}\n\n.ytc-stale-banner-text {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.ytc-stale-banner-text strong {\n color: #ff6b85;\n font-weight: 700;\n}\n\n.ytc-stale-banner-videoid {\n display: inline-block;\n padding: 1px 6px;\n background: rgba(0, 0, 0, 0.45);\n border: 1px solid rgba(237, 28, 63, 0.5);\n border-radius: 3px;\n color: #ffb3c0;\n font-family: 'Consolas', 'Courier New', monospace;\n font-size: 10pt;\n font-weight: 600;\n}\n\n.marker {\n width: 1px;\n height: 14px;\n}\n\n.start-marker {\n fill: lime;\n pointer-events: none;\n}\n\n.end-marker {\n fill: gold;\n pointer-events: visibleFill;\n}\n\n.end-marker:hover {\n fill: var(--bright-red);\n}\n\n.selected-marker-overlay {\n fill: black;\n width: 1px;\n height: 8px;\n pointer-events: none;\n}\n.selected-marker-overlay-hidden {\n fill-opacity: 0.3;\n}\n\n#settings-editor-div {\n display: flex;\n}\n\n.settings-editor-panel {\n display: inline;\n flex-grow: 1;\n color: var(--med-grey);\n font-size: 12pt;\n margin: 3px;\n padding: 2px 6px 0px 4px;\n border: 2px solid var(--med-grey);\n border-radius: 5px;\n background: var(--dark-red);\n}\n\n.settings-editor-panel > legend {\n display: block;\n width: fit-content;\n font-size: 12pt;\n text-shadow:\n -1px -1px 0 black,\n 1px -1px black,\n -1px 1px 0 black,\n 1px 1px 0 black,\n -1px 0px 0px black,\n 0px -1px 0px black,\n 1px 0px 0px black,\n 0px 1px 0px black;\n padding: 0px 4px 0px 4px;\n margin-left: 12px;\n}\n\n.settings-editor-input-div {\n display: inline-block;\n color: grey;\n font-size: 11.5pt;\n margin: 0px 0px 6px 2px;\n padding: 4px 10px 4px 4px;\n background: var(--dark-grey);\n box-shadow: 2px 2px 3px 0px var(--darker-grey);\n border-radius: 2px;\n border-color: var(--med-grey);\n border-style: solid;\n border-width: 1px 0px 0px 1px;\n vertical-align: top;\n}\n\n.settings-editor-input-div span {\n display: block;\n color: var(--light-grey);\n margin-bottom: 2px;\n}\n\n.settings-editor-input-div select option {\n color: black;\n}\n\n.settings-editor-input-div select option:first-child {\n color: dimgrey;\n}\n\n.multi-input-div {\n display: inline-flex;\n width: fit-content;\n}\n\n.settings-editor-input-div > div {\n display: inline-block;\n margin-right: 6px;\n}\n.settings-editor-input-div > div:last-of-type {\n margin-right: -4px;\n}\n\n.settings-editor-input-div select,\n.settings-editor-input-div input:not([type='radio']),\n#marker-pair-number-input {\n display: block;\n font-weight: bold;\n background: var(--lighter-grey);\n width: 100%;\n border: none;\n box-shadow: #151515 2px 2px 2px 0px;\n}\n\n.settings-editor-input-div input:not([type='radio']),\n#marker-pair-number-input {\n border-radius: 5px;\n padding-right: 4px;\n padding-left: 2px;\n}\n\n.settings-info-display span,\n#global-settings-rotate label,\n#merge-list-div input,\n#global-settings-rotate input,\n#marker-pair-number-input {\n display: inline;\n width: auto;\n}\n\n#marker-pair-number-input {\n vertical-align: top;\n border: 1px solid black;\n}\n\n.settings-editor-input-div input:valid,\n#marker-pair-number-input:valid {\n animation-name: valid-input;\n animation-duration: 1s;\n animation-fill-mode: forwards;\n}\n\n.settings-editor-input-div input:invalid,\n#marker-pair-number-input:invalid {\n animation-name: invalid-input;\n animation-duration: 1s;\n animation-fill-mode: forwards;\n}\n\n.fps-mul-stepper {\n display: flex;\n align-items: center;\n gap: 2px;\n}\n\n#title-suffix-input {\n background-color: lightgreen;\n min-width: 20em;\n text-align: right;\n}\n\n#title-prefix-input {\n text-align: right;\n}\n\n.fps-mul-stepper input[type='number'] {\n width: auto;\n}\n\n.fps-mul-input {\n width: 3.5em !important;\n}\n\n.fps-mul-suffix {\n color: var(--med-light-grey);\n font-size: 9pt;\n font-weight: bold;\n white-space: nowrap;\n}\n\n.fps-mul-step-btn {\n background: var(--lighter-grey);\n color: #333;\n border: 1px solid var(--med-grey);\n border-radius: 999px;\n padding: 0 5px;\n cursor: pointer;\n font-weight: bold;\n font-size: 10pt;\n line-height: 1.6;\n box-shadow: #151515 1px 1px 2px 0px;\n}\n\n.fps-mul-step-btn:hover {\n background: #fff;\n}\n\n.marker-pair-settings-editor-highlighted-div {\n border: 2px solid var(--marker-pair-editor-accent) !important;\n}\n\n.global-settings-editor-highlighted-div {\n border: 2px solid var(--global-editor-accent) !important;\n}\n\n.marker-pair-settings-editor-highlighted-label {\n color: var(--marker-pair-editor-accent) !important;\n}\n\n.global-settings-editor-highlighted-label {\n color: var(--global-editor-accent) !important;\n}\n\n.inherited-settings-highlighted-label {\n color: var(--inherited-accent);\n}\n\n#markers-svg,\n#selected-marker-pair-overlay,\n#start-marker-numberings,\n#end-marker-numberings {\n font-size: 6.5pt;\n width: 100%;\n height: 300%;\n top: -5px;\n position: absolute;\n z-index: 99;\n paint-order: stroke;\n stroke: rgba(0, 0, 0, 0.25);\n stroke-width: 2px;\n}\n\n#selected-marker-pair-overlay {\n pointer-events: none;\n}\n\n#start-marker-numberings {\n top: -19px;\n}\n\n#end-marker-numberings {\n top: 5px;\n}\n\n.markerNumbering {\n pointer-events: visibleFill;\n user-select: none;\n}\n.markerNumbering:hover {\n fill: var(--bright-red);\n cursor: pointer;\n}\n\n.startMarkerNumbering {\n fill: lime;\n}\n\n.endMarkerNumbering {\n fill: gold;\n}\n\n#crop-div {\n pointer-events: none;\n z-index: 10;\n}\n\n#begin-crop-preview-div {\n pointer-events: none;\n z-index: 11;\n}\n\n#crop-svg,\n#begin-crop-preview-svg {\n width: 100%;\n height: 100%;\n top: 0px;\n position: absolute;\n}\n\n#cropChartCanvas {\n background-color: rgb(24, 24, 24);\n}\n\n#shortcutsTableToggleButton {\n cursor: pointer;\n position: relative;\n float: left;\n}\n\n@keyframes ytc-palette-pulse {\n 0%, 100% {\n filter: drop-shadow(0 0 0 rgba(255, 120, 140, 0)) brightness(1);\n }\n 50% {\n filter: drop-shadow(0 0 2px rgba(255, 120, 140, 0.45)) brightness(1.12);\n }\n}\n\n#shortcutsTableToggleButton.yt-clipper-palette-button svg {\n transform: scale(1.35);\n transform-origin: center center;\n transition: transform 0.15s;\n}\n\n#shortcutsTableToggleButton.yt-clipper-palette-button svg path {\n fill: #ff6b85 !important;\n transition: fill 0.15s;\n}\n\n/* #shortcutsTableToggleButton.yt-clipper-palette-button {\n animation: ytc-palette-pulse 3.6s ease-in-out infinite;\n} */\n\n#shortcutsTableToggleButton.yt-clipper-palette-button:hover {\n animation: none;\n filter: drop-shadow(0 0 4px rgba(255, 120, 140, 0.8)) brightness(1.25);\n}\n\n#shortcutsTableToggleButton.yt-clipper-palette-button:hover svg path {\n fill: #fff !important;\n}\n\n#shortcutsTableToggleButton.yt-clipper-palette-button:hover svg {\n transform: scale(1.5);\n}\n\n/* Hints bar toggle button \u2014 white tint so `currentColor` propagates to\n the inner SVG. Sizing/positioning is handled per-platform below. */\n#hintsBarToggleButton.yt-clipper-hints-bar-button {\n color: #fff;\n}\n\n/* Centers the icon inside the button slot. */\n.yt-clipper-hints-bar-icon-wrap {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Hints bar icon \u2014 match the visual icon size of the scissors (command\n palette) button and the native YouTube player-rail glyphs. The\n Blender STATUSBAR svg fills its viewBox densely, so the 26 px\n `renderUIIcon` size reads as oversized in the .ytp-button slot. The\n yt_clipper platform CSS overrides this with a smaller scale to fit\n the vjs control bar; this rule sets the default for YouTube and the\n other host platforms. */\n#hintsBarToggleButton.yt-clipper-hints-bar-button svg {\n transform: scale(0.65);\n transform-origin: center center;\n transition: transform 0.15s;\n}\n\n#hintsBarToggleButton.yt-clipper-hints-bar-button:hover svg {\n transform: scale(0.73);\n}\n\n.ytc-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.8);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 9999;\n}\n\n.ytc-modal.hidden {\n display: none;\n}\n\n.ytc-modal-content {\n position: relative;\n width: 90vw;\n height: 90vh;\n background: black;\n border-radius: 0px;\n overflow: hidden;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n.ytc-canvas-wrapper {\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n#ytc-zoom-canvas, #ytc-zoom-canvas-webgl {\n width: 100%;\n height: 100%;\n object-fit: contain; /* Maintain aspect ratio */\n background: black; /* Letterboxing */\n}\n\n.ytc-share-modal {\n z-index: 2147483000;\n}\n\n.ytc-share-modal-box {\n width: min(90vw, 900px);\n max-height: 85vh;\n background: var(--dark-grey);\n color: var(--light-grey);\n border: 1px solid var(--med-grey);\n border-radius: 4px;\n box-shadow: 4px 4px 12px 0px var(--darker-grey);\n padding: 14px 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n font-size: 10pt;\n font-weight: 500;\n}\n\n.ytc-share-modal-title {\n color: var(--lighter-grey);\n font-size: 13pt;\n font-weight: 700;\n}\n\n.ytc-share-modal-warning {\n padding: 8px 10px;\n border-left: 3px solid #f5a623;\n background: linear-gradient(to right, rgba(245, 166, 35, 0.2), var(--dark-grey) 60%);\n color: #ffc661;\n font-weight: 600;\n font-size: 10pt;\n}\n\n.ytc-share-modal-findings {\n padding: 8px 10px;\n border-left: 3px solid #ef4444;\n background: linear-gradient(to right, rgba(239, 68, 68, 0.2), var(--dark-grey) 60%);\n color: #fca5a5;\n font-size: 10pt;\n}\n\n.ytc-share-modal-findings-header {\n font-weight: 600;\n margin-bottom: 4px;\n}\n\n.ytc-share-modal-findings-callout {\n margin: 8px 0;\n padding: 8px 12px;\n background: rgba(245, 166, 35, 0.18);\n border-left: 4px solid #f5a623;\n border-radius: 2px;\n color: #ffd894;\n font-size: 11pt;\n font-weight: 700;\n letter-spacing: 0.2px;\n}\n\n.ytc-share-modal-findings ul {\n margin: 4px 0 0 0;\n padding-left: 18px;\n}\n\n.ytc-share-modal-findings li {\n margin: 2px 0;\n}\n\n.ytc-share-modal-findings code {\n color: #fff;\n font-family: 'Consolas', 'Menlo', 'Monaco', monospace;\n font-size: 10pt;\n font-weight: 600;\n background: rgba(255, 255, 255, 0.08);\n padding: 1px 4px;\n border-radius: 3px;\n}\n\n.ytc-share-modal-parse-issues {\n padding: 8px 10px;\n border-left: 3px solid #60a5fa;\n background: linear-gradient(to right, rgba(96, 165, 250, 0.15), var(--dark-grey) 60%);\n color: var(--light-grey);\n font-size: 10pt;\n}\n\n.ytc-share-modal-parse-issues-header {\n font-weight: 600;\n margin-bottom: 4px;\n}\n\n.ytc-share-modal-parse-issues ul {\n margin: 4px 0 0 0;\n padding-left: 18px;\n}\n\n.ytc-share-modal-parse-issues li {\n margin: 2px 0;\n}\n\n.ytc-share-modal-parse-issues code {\n color: #fff;\n font-family: 'Consolas', 'Menlo', 'Monaco', monospace;\n font-size: 10pt;\n font-weight: 600;\n background: rgba(255, 255, 255, 0.08);\n padding: 1px 4px;\n border-radius: 3px;\n}\n\n.ytc-share-modal-json {\n flex: 1;\n min-height: 12em;\n max-height: 50vh;\n margin: 0;\n padding: 10px 12px;\n overflow: auto;\n background: var(--darker-grey);\n border: 1px solid var(--med-grey);\n border-radius: 2px;\n color: var(--light-grey);\n font-family: 'Consolas', 'Menlo', 'Monaco', monospace;\n font-size: 10pt;\n white-space: pre;\n tab-size: 2;\n}\n\n.ytc-share-modal-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.ytc-share-modal-actions input[type='button'] {\n padding: 5px 14px;\n background: var(--dark-grey);\n color: var(--light-grey);\n border: 1px solid var(--med-grey);\n border-radius: 2px;\n font-size: 10pt;\n font-weight: 500;\n cursor: pointer;\n}\n\n.ytc-share-modal-actions input[type='button']:hover {\n background: var(--med-grey);\n color: var(--lighter-grey);\n}\n\n.ytc-share-modal-actions input[type='button'].ytc-share-modal-copy {\n margin-right: auto;\n}\n\n.ytc-share-modal-actions input[type='button'].ytc-share-modal-load {\n border-color: #ef4444;\n color: #fca5a5;\n font-weight: 600;\n}\n\n.ytc-share-modal-actions input[type='button'].ytc-share-modal-load:hover {\n background: rgba(239, 68, 68, 0.2);\n color: #fecaca;\n}\n\n.ytc-share-modal-actions input[type='button'].ytc-share-modal-cancel {\n border-color: #f5a623;\n color: #ffc661;\n font-weight: 700;\n}\n\n.ytc-share-modal-actions input[type='button'].ytc-share-modal-cancel:hover {\n background: rgba(245, 166, 35, 0.25);\n color: #ffd894;\n}\n";
const shortcutsTableStyle = ":root {\n --lighter-grey: rgb(235, 235, 235);\n --light-grey: rgb(210, 210, 210);\n --med-grey: rgb(110, 110, 110);\n --dark-grey: rgb(40, 40, 40);\n --darker-grey: rgb(10, 10, 10);\n --bright-red: rgb(237, 28, 63);\n --marker-pair-editor-accent: rgb(245, 118, 0);\n --essential-red: rgb(200, 40, 40);\n}\n\n#shortcutsTableContainer {\n text-align: center;\n position: relative;\n}\n\n#shortcutsTableContainer table {\n text-align: left;\n line-height: 32px;\n border-collapse: separate;\n border-spacing: 0;\n border: 2px solid var(--bright-red);\n width: 640px;\n margin: 8px auto;\n border-radius: 0.25rem;\n font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n font-size: 13px;\n font-weight: 500;\n background-color: #222;\n color: #ddd;\n}\n\n#shortcutsTableContainer thead tr:first-child {\n background: linear-gradient(135deg, var(--bright-red), rgb(140, 10, 35));\n color: #fff;\n border: none;\n}\n\n#shortcutsTableContainer thead tr:first-child th {\n font-size: 17px;\n font-weight: 800;\n text-transform: uppercase;\n letter-spacing: 0.12em;\n padding: 8px 10px;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n border-bottom: 2px solid rgba(255, 255, 255, 0.15);\n}\n\n#shortcutsTableContainer thead tr:nth-child(2) {\n text-align: center;\n}\n\n#shortcutsTableContainer tr:nth-child(even) {\n background-color: #333;\n}\n\n#shortcutsTableContainer th:first-child,\ntd:first-child {\n padding: 0 5px;\n}\n\n#shortcutsTableContainer th {\n font-size: 14px;\n font-weight: 600;\n}\n\n#shortcutsTableContainer thead tr:last-child th {\n border-bottom: 3px solid #777;\n}\n\n#shortcutsTableContainer tbody tr:hover {\n background-color: #444;\n}\n\n#shortcutsTableContainer tbody tr:first-child {\n border-bottom: 1px solid #666;\n border-right: 2px solid #666;\n}\n\n#shortcutsTableContainer td {\n border-bottom: 1px solid #666;\n}\n\n#shortcutsTableContainer td:first-child {\n border-right: 2px solid #666;\n}\n\n#shortcutsTableContainer td:last-child {\n text-align: right;\n padding-right: 5px;\n}\n\n#shortcutsTableContainer kbd {\n border: 1px solid #999;\n border-radius: 2px;\n font-weight: bold;\n padding: 2px 4px;\n margin: 1px;\n background-color: #e0e0e0;\n color: #333;\n}\n\n#shortcutsTableContainer tr.essential-row {\n color: var(--bright-red);\n}\n\n#shortcutsTableContainer h2 {\n display: inline-block;\n color: var(--lighter-grey);\n font-size: 22px;\n font-weight: 800;\n text-transform: uppercase;\n letter-spacing: 0.15em;\n margin: 28px auto 12px;\n padding: 10px 28px;\n background: linear-gradient(135deg, var(--bright-red), rgb(140, 10, 35));\n border-radius: 6px;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.55), 0 0 0 2px rgba(255, 255, 255, 0.08) inset;\n text-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);\n}\n";
const platform = (0, _platforms.getPlatform)();
const selectors = (0, _platforms.videoPlatformDataRecords)[platform].selectors;
loadytClipper();
async function loadytClipper() {
console.log('Loading yt_clipper markup script...');
await (0, _bootstrap.resolvePlayerAndVideo)();
(0, _appState.appState).isReady = true;
(0, _navigation.startNavigationWatcher)();
}
let initOnceCalled = false;
function initOnce() {
if (initOnceCalled) return;
initOnceCalled = true;
init();
}
function init() {
//immer
(0, _immer.enableAllPlugins)();
// Install lit-html URL sanitizer as defense-in-depth against dangerous URL
// schemes (javascript:/data:/vbscript:) in template bindings. Must run
// before any render() call — lit-html captures the factory at template
// instantiation time.
(0, _urlSanitizer.installLitUrlSanitizer)();
//yt-clipper
(0, _util.injectCSS)(ytClipperCSS, 'yt-clipper-css');
(0, _util.injectCSS)((0, _platforms.videoPlatformDataRecords)[platform].css, 'platform-css');
initHooks();
initVideoInfo();
initObservers();
(0, _markers.initMarkersContainer)();
(0, _charts.initChartHooks)();
addForeignEventListeners();
// Order matters: the command-palette button must be injected first so
// the hints-bar injection can position itself directly afterend of it
// (see injectToggleHintsBarButton). The left-to-right result is
// [command palette] [hints bar] across every platform.
(0, _settingsEditor.injectToggleCommandPaletteButton)();
(0, _settingsEditor.injectToggleHintsBarButton)();
(0, _cropOverlay.addCropMouseManipulationListener)();
(0, _videoUtil.addScrubVideoHandler)();
(0, _markers.loopMarkerPair)();
}
let shortcutRegistry = null;
let hotkeyEngine = null;
let commandPalette = null;
function initShortcutSystem() {
if (shortcutRegistry) return;
shortcutRegistry = new (0, _commandPalette.ShortcutRegistry)();
shortcutRegistry.registerAll((0, _shortcutDefinitions.createShortcutDefinitions)({
showShortcutsReference: ()=>{
(0, _settingsEditor.toggleShortcutsTable)();
},
addMarker: ()=>{
// During an active crop manipulation (pan-drag OR resize) with
// the crop chart visible, plain `A` reroutes to "add crop
// point" so the user can drop keyframes one-handed without
// involving Alt — Alt + A still works globally but mixes
// awkwardly with the Alt-Y pan lock during pan-drag. Outside
// this context, `A` is its usual "add marker" hotkey.
if ((0, _cropOverlay.isMouseManipulatingCrop) && (0, _cropOverlay.cropManipulationKind) != null && (0, _appState.appState).isCurrentChartVisible && (0, _charts.chartState).currentChartInput?.type === 'crop') (0, _chartutil.addChartPoint)();
else (0, _markers.addMarker)();
},
moveMarkerToCurrentTime: (which)=>{
const marker = which === 'start' ? (0, _markers.getActiveStartMarker)() : (0, _markers.getActiveEndMarker)();
if (marker) (0, _markers.moveMarker)(marker);
},
addChartPoint: ()=>{
(0, _chartutil.addChartPoint)();
},
duplicateSelectedMarkerPair: ()=>{
(0, _markers.duplicateSelectedMarkerPair)();
},
saveMarkersAndSettings: ()=>{
(0, _saveLoad.saveMarkersAndSettings)();
},
copyMarkersToClipboard: ()=>{
(0, _util.copyToClipboard)((0, _saveLoad.getClipperInputJSON)());
},
copyShareableUrl: ()=>{
(0, _saveLoad.copyShareableUrl)();
},
toggleForceSetSpeed: ()=>{
(0, _speed.toggleForceSetSpeed)();
},
cycleForceSetSpeedValueDown: ()=>{
(0, _speed.cycleForceSetSpeedValueDown)();
},
updateAllMarkerPairSpeedsToDefault: ()=>{
(0, _speed.updateAllMarkerPairSpeeds)((0, _appState.appState).settings.newMarkerSpeed, (0, _charts.renderSpeedAndCropUI));
},
captureFrame: ()=>(0, _frameCapture.captureFrame)(),
saveCapturedFrames: ()=>{
(0, _frameCapture.saveCapturedFrames)();
},
toggleGlobalSettingsEditor: ()=>{
(0, _globalSettingsEditor.toggleGlobalSettingsEditor)();
},
toggleMarkerPairOverridesEditor: ()=>{
(0, _settingsEditor.toggleMarkerPairOverridesEditor)();
},
toggleMarkerPairSpeedPreview: ()=>{
(0, _speed.toggleMarkerPairSpeedPreview)();
},
toggleMarkerPairLoop: ()=>{
(0, _speed.toggleMarkerPairLoop)();
},
toggleGammaPreview: ()=>{
(0, _previewToggles.toggleGammaPreview)();
},
toggleFadeLoopPreview: ()=>{
(0, _previewToggles.toggleFadeLoopPreview)();
},
toggleCropChartLooping: ()=>{
(0, _charts.toggleCropChartLooping)();
},
toggleAllPreviews: ()=>{
(0, _previewToggles.toggleAllPreviews)();
},
toggleMarkersDataCommands: ()=>{
(0, _saveLoad.toggleMarkersDataCommands)();
},
toggleSpeedChart: ()=>{
(0, _charts.toggleChart)((0, _charts.chartState).speedChartInput);
},
toggleChartLoop: ()=>{
(0, _charts.toggleChartLoop)();
},
toggleCropChart: ()=>{
(0, _charts.toggleChart)((0, _charts.chartState).cropChartInput);
},
undoMarker: ()=>{
(0, _markers.undoMarker)();
},
redoMarker: ()=>{
(0, _markers.redoMarker)();
},
undoMarkerPairChange: ()=>{
(0, _markers.undoRedoMarkerPairChange)('undo');
},
redoMarkerPairChange: ()=>{
(0, _markers.undoRedoMarkerPairChange)('redo');
},
deleteMarkerPair: ()=>{
(0, _markers.deleteMarkerPair)();
},
drawCrop: ()=>{
(0, _cropOverlay.drawCrop)();
},
toggleArrowKeyCropAdjustment: ()=>{
(0, _settingsEditor.toggleArrowKeyCropAdjustment)();
},
updateAllMarkerPairCropsToDefault: ()=>{
(0, _cropUtils.updateAllMarkerPairCrops)((0, _appState.appState).settings.newMarkerCrop);
},
cycleCropDimOpacity: ()=>{
(0, _cropOverlay.cycleCropDimOpacity)();
},
toggleCropCrossHair: ()=>{
(0, _cropOverlay.toggleCropCrossHair)();
},
toggleCropPreviewModal: ()=>{
(0, _cropPreview.toggleCropPreview)('modal');
},
toggleCropPreviewPopOut: ()=>{
(0, _cropPreview.toggleCropPreview)('pop-out');
},
rotateVideoClock: ()=>{
(0, _videoRotation.rotateVideo)('clock');
},
rotateVideoCClock: ()=>{
(0, _videoRotation.rotateVideo)('cclock');
},
toggleBigVideoPreviews: ()=>{
(0, _videoRotation.toggleBigVideoPreviews)();
},
flashNotTheatreMode: ()=>{
(0, _util.flashMessage)('Please switch to theater mode to rotate video.', 'red');
},
flattenVRVideo: ()=>{
(0, _misc.flattenVRVideo)((0, _appState.appState).hooks.videoContainer, (0, _appState.appState).video);
},
jumpToNearestMarkerOrPair: (e)=>{
(0, _markers.jumpToNearestMarkerOrPair)(e, e.code);
},
togglePrevSelectedMarkerPair: ()=>{
(0, _markers.togglePrevSelectedMarkerPair)();
},
toggleAutoHideUnselectedMarkerPairs: (e)=>{
(0, _markerSettingsEditor.toggleAutoHideUnselectedMarkerPairs)(e);
},
toggleHintsBar: ()=>{
(0, _hintsBar.toggleHintsBar)();
},
isMarkerHotkeysEnabled: ()=>(0, _appState.appState).markerHotkeysEnabled,
isTheatreMode: ()=>isTheatreMode(),
isArrowKeyCropAdjustmentDisabled: ()=>!(0, _settingsEditor.arrowKeyCropAdjustmentEnabled),
hasMarkerPairs: ()=>(0, _appState.appState).markerPairs.length > 0,
isInCropManipulationWithCropChart: ()=>(0, _cropOverlay.isMouseManipulatingCrop) && (0, _cropOverlay.cropManipulationKind) != null && (0, _appState.appState).isCurrentChartVisible && (0, _charts.chartState).currentChartInput?.type === 'crop'
}));
hotkeyEngine = new (0, _commandPalette.HotkeyEngine)(shortcutRegistry);
hotkeyEngine.setBlocker((e)=>{
(0, _util.blockEvent)(e);
});
hotkeyEngine.setEnabled((0, _appState.appState).isHotkeysEnabled);
(0, _hintsBar.mountHintsBar)(shortcutRegistry);
let wasPausedBeforeOpen = false;
commandPalette = new (0, _commandPalette.CommandPalette)(shortcutRegistry, {
zIndex: 99999,
onOpenReference: ()=>{
(0, _settingsEditor.toggleShortcutsTable)();
},
onOpen: ()=>{
wasPausedBeforeOpen = (0, _appState.appState).video.paused;
if (!wasPausedBeforeOpen) (0, _appState.appState).video.pause();
},
onClose: ()=>{
if (!wasPausedBeforeOpen) (0, _appState.appState).video.play();
}
});
}
function hotkeys(e) {
if (!(0, _appState.appState).isReady) {
console.log('yt_clipper not yet ready to process hotkeys.');
return;
}
if (!e.ctrlKey && e.shiftKey && e.altKey && e.code === 'KeyA') {
if (0, _navigation1.isStaleVideo) {
(0, _util.flashMessage)('Video changed since yt_clipper loaded. Reload the page to continue clipping.', 'red');
return;
}
(0, _appState.appState).isHotkeysEnabled = !(0, _appState.appState).isHotkeysEnabled;
initOnce();
initShortcutSystem();
hotkeyEngine?.setEnabled((0, _appState.appState).isHotkeysEnabled);
if ((0, _appState.appState).isHotkeysEnabled) {
(0, _settingsEditor.showCommandPaletteToggleButton)();
(0, _settingsEditor.showHintsBarToggleButton)();
(0, _hintsBar.setHintsBarEnabled)(true);
(0, _common.enableCommonBlockers)();
if (platform === (0, _platforms.VideoPlatforms).youtube) (0, _youtube.enableYTBlockers)();
(0, _util.flashMessage)('Enabled Hotkeys', 'green');
if ((0, _featureFlags.featureFlags).shareLink) (0, _saveLoad.tryLoadSharedMarkers)();
} else {
(0, _settingsEditor.hideCommandPaletteToggleButton)();
(0, _settingsEditor.hideHintsBarToggleButton)();
(0, _hintsBar.setHintsBarEnabled)(false);
(0, _common.disableCommonBlockers)();
if (platform === (0, _platforms.VideoPlatforms).youtube) (0, _youtube.disableYTBlockers)();
(0, _util.flashMessage)('Disabled Hotkeys', 'red');
}
return;
}
if (!(0, _appState.appState).isHotkeysEnabled) return;
if (!e.ctrlKey && e.shiftKey && !e.altKey && e.code === 'KeyE') {
(0, _util.blockEvent)(e);
commandPalette?.toggle();
return;
}
if (commandPalette?.isOpen()) return;
hotkeyEngine?.dispatch(e);
}
function addEventListeners() {
document.addEventListener('keydown', hotkeys, true);
document.addEventListener('keydown', (0, _cropOverlay.addCropHoverListener), true);
document.addEventListener('keyup', (0, _cropOverlay.removeCropHoverListener), true);
document.body.addEventListener('wheel', mouseWheelFrameSkipHandler);
document.body.addEventListener('mousedown', changeMouseWheelFrameSkipRateHandler);
document.body.addEventListener('wheel', (0, _markers.moveMarkerByFrameHandler));
document.body.addEventListener('wheel', (0, _charts.selectCropPointWithMouseWheel), {
passive: false
});
document.body.addEventListener('wheel', (0, _charts.inheritCropPointCrop), {
passive: false
});
}
function initHooks() {
(0, _appState.appState).hooks = (0, _platforms.getVideoPlatformHooks)(selectors);
(0, _util.setFlashMessageHook)((0, _appState.appState).hooks.flashMessage);
updateSettingsEditorHook();
(0, _appState.appState).hooks.progressBar.removeAttribute('draggable');
attachVideoHoverDetector((0, _appState.appState).hooks.cropMouseManipulation);
(0, _hoverRegion.registerHoverRegion)((0, _appState.appState).hooks.progressBar, 'progress-bar');
}
/** Custom hover detector for the video area: distinguishes "inside the crop
* rectangle" (region: 'crop') from "elsewhere on the video" (region:
* 'video') so the hints bar can scope crop-manipulation chips precisely. */ function attachVideoHoverDetector(el) {
let inside = false;
let currentRegion = null;
const sync = (next)=>{
if (next === currentRegion) return;
currentRegion = next;
(0, _hoverRegion.setHoveredRegion)(next);
};
el.addEventListener('mouseenter', ()=>{
inside = true;
});
el.addEventListener('mouseleave', ()=>{
inside = false;
sync(null);
});
el.addEventListener('mousemove', (e)=>{
if (!inside) return;
const overCrop = (0, _appState.appState).isCropOverlayVisible && (0, _cropOverlay.isMouseInsideCrop)(e.clientX, e.clientY);
sync(overCrop ? 'crop' : 'video');
});
}
function isTheatreMode() {
if (platform === (0, _platforms.VideoPlatforms).youtube) return (0, _appState.appState).hooks.theaterModeIndicator.theater;
else if (platform === (0, _platforms.VideoPlatforms).yt_clipper) return true;
}
function initVideoInfo() {
(0, _appState.appState).videoInfo.aspectRatio = (0, _appState.appState).video.videoWidth / (0, _appState.appState).video.videoHeight;
(0, _appState.appState).videoInfo.isVerticalVideo = (0, _appState.appState).videoInfo.aspectRatio <= 1;
const url = window.location.origin + window.location.pathname;
(0, _appState.appState).videoInfo.videoUrl = url;
(0, _appState.appState).videoInfo.fps = (0, _videoUtil.getFPS)();
(0, _appState.appState).video.seekTo = (time)=>(0, _appState.appState).video.currentTime = time;
(0, _appState.appState).video.getCurrentTime = ()=>{
return (0, _appState.appState).video.currentTime;
};
if (platform === (0, _platforms.VideoPlatforms).youtube) {
const playerData = (0, _appState.appState).player.getVideoData();
(0, _appState.appState).videoInfo.id = playerData.video_id;
(0, _appState.appState).videoInfo.videoUrl += '?v=' + (0, _appState.appState).videoInfo.id;
(0, _appState.appState).videoInfo.title = playerData.title;
(0, _appState.appState).video.seekTo = (time)=>{
(0, _appState.appState).player.seekTo(time);
};
} else if (platform === (0, _platforms.VideoPlatforms).vlive) {
const location1 = window.location;
const preloadedState = unsafeWindow.__PRELOADED_STATE__;
const videoParams = preloadedState?.postDetail?.post?.officialVideo;
(0, _appState.appState).videoInfo.id = videoParams?.videoSeq;
(0, _appState.appState).videoInfo.title = videoParams?.title;
if (location1.pathname.includes('video')) {
(0, _appState.appState).videoInfo.id ??= location1.pathname.split('/')[2];
(0, _appState.appState).videoInfo.title ??= document.querySelector('[class*="video_title"]')?.textContent;
}
} else if (platform === (0, _platforms.VideoPlatforms).naver_tv) {
(0, _appState.appState).videoInfo.id = location.pathname.split('/')[2];
(0, _appState.appState).videoInfo.title = document.querySelector('h2[class*=ArticleSection_article_title]')?.textContent;
} else if (platform === (0, _platforms.VideoPlatforms).weverse) {
(0, _appState.appState).videoInfo.title = document.querySelector('h2[class*=TitleView_title]')?.textContent;
if (location.pathname.includes('media') || location.pathname.includes('live')) (0, _appState.appState).videoInfo.id ??= location.pathname.split('/')[3];
} else if (platform === (0, _platforms.VideoPlatforms).yt_clipper) {
(0, _appState.appState).videoInfo.id = 'unknown';
const videoTile = document.querySelector('#ytc-video-title');
(0, _appState.appState).videoInfo.title = videoTile?.textContent;
} else if (platform === (0, _platforms.VideoPlatforms).afreecatv) {
(0, _appState.appState).videoInfo.id = location.pathname.split('/')[2];
(0, _appState.appState).videoInfo.title = document.querySelector('div[class~=broadcast_title]')?.textContent;
(0, _appState.appState).video.getCurrentTime = ()=>{
return unsafeWindow.vodCore.playerController._playingTime;
};
(0, _appState.appState).video.seekTo = (time)=>{
unsafeWindow.vodCore.seek(time);
};
}
if ((0, _appState.appState).videoInfo.id == null) {
(0, _util.flashMessage)('Could not get video ID.', 'red');
throw new Error('Could not get video ID.');
}
}
function initObservers() {
new ResizeObserver((0, _cropOverlay.resizeCropOverlay)).observe((0, _appState.appState).hooks.videoContainer);
if (platform === (0, _platforms.VideoPlatforms).afreecatv) (0, _util.observeVideoElementChange)((0, _appState.appState).hooks.videoContainer, (addedNodes)=>{
(0, _appState.appState).video = addedNodes[0];
(0, _appState.appState).video.classList.add('yt-clipper-video');
initVideoInfo();
});
}
function updateSettingsEditorHook() {
if (isTheatreMode()) (0, _appState.appState).settingsEditorHook = (0, _appState.appState).hooks.settingsEditorTheater;
else (0, _appState.appState).settingsEditorHook = (0, _appState.appState).hooks.settingsEditor;
}
addEventListeners();
let mouseWheelFrameSkipRate = 1;
function mouseWheelFrameSkipHandler(event) {
if ((0, _appState.appState).isHotkeysEnabled && !event.ctrlKey && !event.altKey && event.shiftKey && Math.abs(event.deltaY) > 0) {
const fps = (0, _videoUtil.getFPS)();
if (event.deltaY < 0) (0, _util.seekBySafe)((0, _appState.appState).video, mouseWheelFrameSkipRate / fps);
else if (event.deltaY > 0) (0, _util.seekBySafe)((0, _appState.appState).video, -mouseWheelFrameSkipRate / fps);
}
}
function changeMouseWheelFrameSkipRateHandler(event) {
if ((0, _appState.appState).isHotkeysEnabled && !event.ctrlKey && !event.altKey && event.shiftKey && event.button == 1) {
event.preventDefault();
mouseWheelFrameSkipRate += 1;
if (mouseWheelFrameSkipRate > 4) mouseWheelFrameSkipRate = 1;
(0, _util.flashMessage)(`Mouse wheel frame skip rate set to ${mouseWheelFrameSkipRate}`, 'green');
}
}
function addForeignEventListeners() {
const selectors = [
'input[type="text"',
'textarea'
];
selectors.forEach((selector)=>{
const inputs = document.querySelectorAll(selector);
for (const input of Array.from(inputs))if ((0, _appState.appState).isHotkeysEnabled) {
input.addEventListener('focus', ()=>(0, _appState.appState).isHotkeysEnabled = false, {
capture: true
});
input.addEventListener('blur', ()=>(0, _appState.appState).isHotkeysEnabled = true, {
capture: true
});
}
});
}
},{"fs":"1LEPD","immer":"R6AMM","./actions/misc":"lOXYn","./feature-flags":"e23zj","./platforms/blockers/common":"kg6wl","./platforms/blockers/youtube":"lEer5","./platforms/platforms":"1kR7r","./ui/chart/chart.js-drag-data-plugin":"1JMOe","./ui/chart/chartutil":"AvPxz","./util/util":"99arg","./crop/crop-preview":"9T0zg","../command-palette":"5ubiV","./shortcut-definitions":"2Fvl0","./appState":"g0AlP","./frame-capture":"5uOzY","./video-rotation":"iAvvX","./preview-toggles":"9Q63h","./speed":"6CgFD","./save-load":"3FwNw","./crop-utils":"k2gwb","./features/settings/global-settings-editor":"koJFH","./bootstrap":"bwm01","./navigation":"13t4D","./platforms/navigation":"l9QMf","./crop-overlay":"6s727","./markers":"EQEoZ","./charts":"hBxwj","./util/url-sanitizer":"96bfD","./util/videoUtil":"93pN0","./features/hints-bar/hints-bar":"4nJHa","./features/hints-bar/hover-region":"l2Fo9","./features/settings/settings-editor":"jDViX","./features/settings/marker-settings-editor":"ZduPU","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"1LEPD":[function(require,module,exports,__globalThis) {
"use strict";
},{}],"R6AMM":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "Immer", ()=>un);
parcelHelpers.export(exports, "applyPatches", ()=>pn);
parcelHelpers.export(exports, "castDraft", ()=>K);
parcelHelpers.export(exports, "castImmutable", ()=>$);
parcelHelpers.export(exports, "createDraft", ()=>ln);
parcelHelpers.export(exports, "current", ()=>R);
parcelHelpers.export(exports, "enableAllPlugins", ()=>J);
parcelHelpers.export(exports, "enableES5", ()=>F);
parcelHelpers.export(exports, "enableMapSet", ()=>C);
parcelHelpers.export(exports, "enablePatches", ()=>T);
parcelHelpers.export(exports, "finishDraft", ()=>dn);
parcelHelpers.export(exports, "freeze", ()=>d);
parcelHelpers.export(exports, "immerable", ()=>L);
parcelHelpers.export(exports, "isDraft", ()=>r);
parcelHelpers.export(exports, "isDraftable", ()=>t);
parcelHelpers.export(exports, "nothing", ()=>H);
parcelHelpers.export(exports, "original", ()=>e);
parcelHelpers.export(exports, "produce", ()=>fn);
parcelHelpers.export(exports, "produceWithPatches", ()=>cn);
parcelHelpers.export(exports, "setAutoFreeze", ()=>sn);
parcelHelpers.export(exports, "setUseProxies", ()=>vn);
function n(n) {
for(var r = arguments.length, t = Array(r > 1 ? r - 1 : 0), e = 1; e < r; e++)t[e - 1] = arguments[e];
var i, o;
throw Error("[Immer] minified error nr: " + n + (t.length ? " " + t.map(function(n) {
return "'" + n + "'";
}).join(",") : "") + ". Find the full error at: https://bit.ly/3cXEKWf");
}
function r(n) {
return !!n && !!n[Q];
}
function t(n) {
var r;
return !!n && (function(n) {
if (!n || "object" != typeof n) return !1;
var r = Object.getPrototypeOf(n);
if (null === r) return !0;
var t = Object.hasOwnProperty.call(r, "constructor") && r.constructor;
return t === Object || "function" == typeof t && Function.toString.call(t) === Z;
}(n) || Array.isArray(n) || !!n[L] || !!(null === (r = n.constructor) || void 0 === r ? void 0 : r[L]) || s(n) || v(n));
}
function e(t) {
return r(t) || n(23, t), t[Q].t;
}
function i(n, r, t) {
void 0 === t && (t = !1), 0 === o(n) ? (t ? Object.keys : nn)(n).forEach(function(e) {
t && "symbol" == typeof e || r(e, n[e], n);
}) : n.forEach(function(t, e) {
return r(e, t, n);
});
}
function o(n) {
var r = n[Q];
return r ? r.i > 3 ? r.i - 4 : r.i : Array.isArray(n) ? 1 : s(n) ? 2 : v(n) ? 3 : 0;
}
function u(n, r) {
return 2 === o(n) ? n.has(r) : Object.prototype.hasOwnProperty.call(n, r);
}
function a(n, r) {
return 2 === o(n) ? n.get(r) : n[r];
}
function f(n, r, t) {
var e = o(n);
2 === e ? n.set(r, t) : 3 === e ? n.add(t) : n[r] = t;
}
function c(n, r) {
return n === r ? 0 !== n || 1 / n == 1 / r : n != n && r != r;
}
function s(n) {
return X && n instanceof Map;
}
function v(n) {
return q && n instanceof Set;
}
function p(n) {
return n.o || n.t;
}
function l(n) {
if (Array.isArray(n)) return Array.prototype.slice.call(n);
var r = rn(n);
delete r[Q];
for(var t = nn(r), e = 0; e < t.length; e++){
var i = t[e], o = r[i];
!1 === o.writable && (o.writable = !0, o.configurable = !0), (o.get || o.set) && (r[i] = {
configurable: !0,
writable: !0,
enumerable: o.enumerable,
value: n[i]
});
}
return Object.create(Object.getPrototypeOf(n), r);
}
function d(n, e) {
return void 0 === e && (e = !1), y(n) || r(n) || !t(n) || (o(n) > 1 && (n.set = n.add = n.clear = n.delete = h), Object.freeze(n), e && i(n, function(n, r) {
return d(r, !0);
}, !0)), n;
}
function h() {
n(2);
}
function y(n) {
return null == n || "object" != typeof n || Object.isFrozen(n);
}
function b(r) {
var t = tn[r];
return t || n(18, r), t;
}
function m(n, r) {
tn[n] || (tn[n] = r);
}
function _() {
return U;
}
function j(n, r) {
r && (b("Patches"), n.u = [], n.s = [], n.v = r);
}
function g(n) {
O(n), n.p.forEach(S), n.p = null;
}
function O(n) {
n === U && (U = n.l);
}
function w(n) {
return U = {
p: [],
l: U,
h: n,
m: !0,
_: 0
};
}
function S(n) {
var r = n[Q];
0 === r.i || 1 === r.i ? r.j() : r.g = !0;
}
function P(r, e) {
e._ = e.p.length;
var i = e.p[0], o = void 0 !== r && r !== i;
return e.h.O || b("ES5").S(e, r, o), o ? (i[Q].P && (g(e), n(4)), t(r) && (r = M(e, r), e.l || x(e, r)), e.u && b("Patches").M(i[Q].t, r, e.u, e.s)) : r = M(e, i, []), g(e), e.u && e.v(e.u, e.s), r !== H ? r : void 0;
}
function M(n, r, t) {
if (y(r)) return r;
var e = r[Q];
if (!e) return i(r, function(i, o) {
return A(n, e, r, i, o, t);
}, !0), r;
if (e.A !== n) return r;
if (!e.P) return x(n, e.t, !0), e.t;
if (!e.I) {
e.I = !0, e.A._--;
var o = 4 === e.i || 5 === e.i ? e.o = l(e.k) : e.o, u = o, a = !1;
3 === e.i && (u = new Set(o), o.clear(), a = !0), i(u, function(r, i) {
return A(n, e, o, r, i, t, a);
}), x(n, o, !1), t && n.u && b("Patches").N(e, t, n.u, n.s);
}
return e.o;
}
function A(e, i, o, a, c, s, v) {
if (r(c)) {
var p = M(e, c, s && i && 3 !== i.i && !u(i.R, a) ? s.concat(a) : void 0);
if (f(o, a, p), !r(p)) return;
e.m = !1;
} else v && o.add(c);
if (t(c) && !y(c)) {
if (!e.h.D && e._ < 1) return;
M(e, c), i && i.A.l || x(e, c);
}
}
function x(n, r, t) {
void 0 === t && (t = !1), !n.l && n.h.D && n.m && d(r, t);
}
function z(n, r) {
var t = n[Q];
return (t ? p(t) : n)[r];
}
function I(n, r) {
if (r in n) for(var t = Object.getPrototypeOf(n); t;){
var e = Object.getOwnPropertyDescriptor(t, r);
if (e) return e;
t = Object.getPrototypeOf(t);
}
}
function k(n) {
n.P || (n.P = !0, n.l && k(n.l));
}
function E(n) {
n.o || (n.o = l(n.t));
}
function N(n, r, t) {
var e = s(r) ? b("MapSet").F(r, t) : v(r) ? b("MapSet").T(r, t) : n.O ? function(n, r) {
var t = Array.isArray(n), e = {
i: t ? 1 : 0,
A: r ? r.A : _(),
P: !1,
I: !1,
R: {},
l: r,
t: n,
k: null,
o: null,
j: null,
C: !1
}, i = e, o = en;
t && (i = [
e
], o = on);
var u = Proxy.revocable(i, o), a = u.revoke, f = u.proxy;
return e.k = f, e.j = a, f;
}(r, t) : b("ES5").J(r, t);
return (t ? t.A : _()).p.push(e), e;
}
function R(e) {
return r(e) || n(22, e), function n(r) {
if (!t(r)) return r;
var e, u = r[Q], c = o(r);
if (u) {
if (!u.P && (u.i < 4 || !b("ES5").K(u))) return u.t;
u.I = !0, e = D(r, c), u.I = !1;
} else e = D(r, c);
return i(e, function(r, t) {
u && a(u.t, r) === t || f(e, r, n(t));
}), 3 === c ? new Set(e) : e;
}(e);
}
function D(n, r) {
switch(r){
case 2:
return new Map(n);
case 3:
return Array.from(n);
}
return l(n);
}
function F() {
function t(n, r) {
var t = s[n];
return t ? t.enumerable = r : s[n] = t = {
configurable: !0,
enumerable: r,
get: function() {
var r = this[Q];
return en.get(r, n);
},
set: function(r) {
var t = this[Q];
en.set(t, n, r);
}
}, t;
}
function e(n) {
for(var r = n.length - 1; r >= 0; r--){
var t = n[r][Q];
if (!t.P) switch(t.i){
case 5:
a(t) && k(t);
break;
case 4:
o(t) && k(t);
}
}
}
function o(n) {
for(var r = n.t, t = n.k, e = nn(t), i = e.length - 1; i >= 0; i--){
var o = e[i];
if (o !== Q) {
var a = r[o];
if (void 0 === a && !u(r, o)) return !0;
var f = t[o], s = f && f[Q];
if (s ? s.t !== a : !c(f, a)) return !0;
}
}
var v = !!r[Q];
return e.length !== nn(r).length + (v ? 0 : 1);
}
function a(n) {
var r = n.k;
if (r.length !== n.t.length) return !0;
var t = Object.getOwnPropertyDescriptor(r, r.length - 1);
if (t && !t.get) return !0;
for(var e = 0; e < r.length; e++)if (!r.hasOwnProperty(e)) return !0;
return !1;
}
function f(r) {
r.g && n(3, JSON.stringify(p(r)));
}
var s = {};
m("ES5", {
J: function(n, r) {
var e = Array.isArray(n), i = function(n, r) {
if (n) {
for(var e = Array(r.length), i = 0; i < r.length; i++)Object.defineProperty(e, "" + i, t(i, !0));
return e;
}
var o = rn(r);
delete o[Q];
for(var u = nn(o), a = 0; a < u.length; a++){
var f = u[a];
o[f] = t(f, n || !!o[f].enumerable);
}
return Object.create(Object.getPrototypeOf(r), o);
}(e, n), o = {
i: e ? 5 : 4,
A: r ? r.A : _(),
P: !1,
I: !1,
R: {},
l: r,
t: n,
k: i,
o: null,
g: !1,
C: !1
};
return Object.defineProperty(i, Q, {
value: o,
writable: !0
}), i;
},
S: function(n, t, o) {
o ? r(t) && t[Q].A === n && e(n.p) : (n.u && function n(r) {
if (r && "object" == typeof r) {
var t = r[Q];
if (t) {
var e = t.t, o = t.k, f = t.R, c = t.i;
if (4 === c) i(o, function(r) {
r !== Q && (void 0 !== e[r] || u(e, r) ? f[r] || n(o[r]) : (f[r] = !0, k(t)));
}), i(e, function(n) {
void 0 !== o[n] || u(o, n) || (f[n] = !1, k(t));
});
else if (5 === c) {
if (a(t) && (k(t), f.length = !0), o.length < e.length) for(var s = o.length; s < e.length; s++)f[s] = !1;
else for(var v = e.length; v < o.length; v++)f[v] = !0;
for(var p = Math.min(o.length, e.length), l = 0; l < p; l++)o.hasOwnProperty(l) || (f[l] = !0), void 0 === f[l] && n(o[l]);
}
}
}
}(n.p[0]), e(n.p));
},
K: function(n) {
return 4 === n.i ? o(n) : a(n);
}
});
}
function T() {
function e(n) {
if (!t(n)) return n;
if (Array.isArray(n)) return n.map(e);
if (s(n)) return new Map(Array.from(n.entries()).map(function(n) {
return [
n[0],
e(n[1])
];
}));
if (v(n)) return new Set(Array.from(n).map(e));
var r = Object.create(Object.getPrototypeOf(n));
for(var i in n)r[i] = e(n[i]);
return u(n, L) && (r[L] = n[L]), r;
}
function f(n) {
return r(n) ? e(n) : n;
}
var c = "add";
m("Patches", {
$: function(r, t) {
return t.forEach(function(t) {
for(var i = t.path, u = t.op, f = r, s = 0; s < i.length - 1; s++){
var v = o(f), p = i[s];
"string" != typeof p && "number" != typeof p && (p = "" + p), 0 !== v && 1 !== v || "__proto__" !== p && "constructor" !== p || n(24), "function" == typeof f && "prototype" === p && n(24), "object" != typeof (f = a(f, p)) && n(15, i.join("/"));
}
var l = o(f), d = e(t.value), h = i[i.length - 1];
switch(u){
case "replace":
switch(l){
case 2:
return f.set(h, d);
case 3:
n(16);
default:
return f[h] = d;
}
case c:
switch(l){
case 1:
return "-" === h ? f.push(d) : f.splice(h, 0, d);
case 2:
return f.set(h, d);
case 3:
return f.add(d);
default:
return f[h] = d;
}
case "remove":
switch(l){
case 1:
return f.splice(h, 1);
case 2:
return f.delete(h);
case 3:
return f.delete(t.value);
default:
return delete f[h];
}
default:
n(17, u);
}
}), r;
},
N: function(n, r, t, e) {
switch(n.i){
case 0:
case 4:
case 2:
return function(n, r, t, e) {
var o = n.t, s = n.o;
i(n.R, function(n, i) {
var v = a(o, n), p = a(s, n), l = i ? u(o, n) ? "replace" : c : "remove";
if (v !== p || "replace" !== l) {
var d = r.concat(n);
t.push("remove" === l ? {
op: l,
path: d
} : {
op: l,
path: d,
value: p
}), e.push(l === c ? {
op: "remove",
path: d
} : "remove" === l ? {
op: c,
path: d,
value: f(v)
} : {
op: "replace",
path: d,
value: f(v)
});
}
});
}(n, r, t, e);
case 5:
case 1:
return function(n, r, t, e) {
var i = n.t, o = n.R, u = n.o;
if (u.length < i.length) {
var a = [
u,
i
];
i = a[0], u = a[1];
var s = [
e,
t
];
t = s[0], e = s[1];
}
for(var v = 0; v < i.length; v++)if (o[v] && u[v] !== i[v]) {
var p = r.concat([
v
]);
t.push({
op: "replace",
path: p,
value: f(u[v])
}), e.push({
op: "replace",
path: p,
value: f(i[v])
});
}
for(var l = i.length; l < u.length; l++){
var d = r.concat([
l
]);
t.push({
op: c,
path: d,
value: f(u[l])
});
}
i.length < u.length && e.push({
op: "replace",
path: r.concat([
"length"
]),
value: i.length
});
}(n, r, t, e);
case 3:
return function(n, r, t, e) {
var i = n.t, o = n.o, u = 0;
i.forEach(function(n) {
if (!o.has(n)) {
var i = r.concat([
u
]);
t.push({
op: "remove",
path: i,
value: n
}), e.unshift({
op: c,
path: i,
value: n
});
}
u++;
}), u = 0, o.forEach(function(n) {
if (!i.has(n)) {
var o = r.concat([
u
]);
t.push({
op: c,
path: o,
value: n
}), e.unshift({
op: "remove",
path: o,
value: n
});
}
u++;
});
}(n, r, t, e);
}
},
M: function(n, r, t, e) {
t.push({
op: "replace",
path: [],
value: r === H ? void 0 : r
}), e.push({
op: "replace",
path: [],
value: n
});
}
});
}
function C() {
function r(n, r) {
function t() {
this.constructor = n;
}
a(n, r), n.prototype = (t.prototype = r.prototype, new t);
}
function e(n) {
n.o || (n.R = new Map, n.o = new Map(n.t));
}
function o(n) {
n.o || (n.o = new Set, n.t.forEach(function(r) {
if (t(r)) {
var e = N(n.A.h, r, n);
n.p.set(r, e), n.o.add(e);
} else n.o.add(r);
}));
}
function u(r) {
r.g && n(3, JSON.stringify(p(r)));
}
var a = function(n, r) {
return (a = Object.setPrototypeOf || ({
__proto__: []
}) instanceof Array && function(n, r) {
n.__proto__ = r;
} || function(n, r) {
for(var t in r)r.hasOwnProperty(t) && (n[t] = r[t]);
})(n, r);
}, f = function() {
function n(n, r) {
return this[Q] = {
i: 2,
l: r,
A: r ? r.A : _(),
P: !1,
I: !1,
o: void 0,
R: void 0,
t: n,
k: this,
C: !1,
g: !1
}, this;
}
r(n, Map);
var o = n.prototype;
return Object.defineProperty(o, "size", {
get: function() {
return p(this[Q]).size;
}
}), o.has = function(n) {
return p(this[Q]).has(n);
}, o.set = function(n, r) {
var t = this[Q];
return u(t), p(t).has(n) && p(t).get(n) === r || (e(t), k(t), t.R.set(n, !0), t.o.set(n, r), t.R.set(n, !0)), this;
}, o.delete = function(n) {
if (!this.has(n)) return !1;
var r = this[Q];
return u(r), e(r), k(r), r.t.has(n) ? r.R.set(n, !1) : r.R.delete(n), r.o.delete(n), !0;
}, o.clear = function() {
var n = this[Q];
u(n), p(n).size && (e(n), k(n), n.R = new Map, i(n.t, function(r) {
n.R.set(r, !1);
}), n.o.clear());
}, o.forEach = function(n, r) {
var t = this;
p(this[Q]).forEach(function(e, i) {
n.call(r, t.get(i), i, t);
});
}, o.get = function(n) {
var r = this[Q];
u(r);
var i = p(r).get(n);
if (r.I || !t(i)) return i;
if (i !== r.t.get(n)) return i;
var o = N(r.A.h, i, r);
return e(r), r.o.set(n, o), o;
}, o.keys = function() {
return p(this[Q]).keys();
}, o.values = function() {
var n, r = this, t = this.keys();
return (n = {})[V] = function() {
return r.values();
}, n.next = function() {
var n = t.next();
return n.done ? n : {
done: !1,
value: r.get(n.value)
};
}, n;
}, o.entries = function() {
var n, r = this, t = this.keys();
return (n = {})[V] = function() {
return r.entries();
}, n.next = function() {
var n = t.next();
if (n.done) return n;
var e = r.get(n.value);
return {
done: !1,
value: [
n.value,
e
]
};
}, n;
}, o[V] = function() {
return this.entries();
}, n;
}(), c = function() {
function n(n, r) {
return this[Q] = {
i: 3,
l: r,
A: r ? r.A : _(),
P: !1,
I: !1,
o: void 0,
t: n,
k: this,
p: new Map,
g: !1,
C: !1
}, this;
}
r(n, Set);
var t = n.prototype;
return Object.defineProperty(t, "size", {
get: function() {
return p(this[Q]).size;
}
}), t.has = function(n) {
var r = this[Q];
return u(r), r.o ? !!r.o.has(n) || !(!r.p.has(n) || !r.o.has(r.p.get(n))) : r.t.has(n);
}, t.add = function(n) {
var r = this[Q];
return u(r), this.has(n) || (o(r), k(r), r.o.add(n)), this;
}, t.delete = function(n) {
if (!this.has(n)) return !1;
var r = this[Q];
return u(r), o(r), k(r), r.o.delete(n) || !!r.p.has(n) && r.o.delete(r.p.get(n));
}, t.clear = function() {
var n = this[Q];
u(n), p(n).size && (o(n), k(n), n.o.clear());
}, t.values = function() {
var n = this[Q];
return u(n), o(n), n.o.values();
}, t.entries = function() {
var n = this[Q];
return u(n), o(n), n.o.entries();
}, t.keys = function() {
return this.values();
}, t[V] = function() {
return this.values();
}, t.forEach = function(n, r) {
for(var t = this.values(), e = t.next(); !e.done;)n.call(r, e.value, e.value, this), e = t.next();
}, n;
}();
m("MapSet", {
F: function(n, r) {
return new f(n, r);
},
T: function(n, r) {
return new c(n, r);
}
});
}
function J() {
F(), C(), T();
}
function K(n) {
return n;
}
function $(n) {
return n;
}
var G, U, W = "undefined" != typeof Symbol && "symbol" == typeof Symbol("x"), X = "undefined" != typeof Map, q = "undefined" != typeof Set, B = "undefined" != typeof Proxy && void 0 !== Proxy.revocable && "undefined" != typeof Reflect, H = W ? Symbol.for("immer-nothing") : ((G = {})["immer-nothing"] = !0, G), L = W ? Symbol.for("immer-draftable") : "__$immer_draftable", Q = W ? Symbol.for("immer-state") : "__$immer_state", V = "undefined" != typeof Symbol && Symbol.iterator || "@@iterator", Y = {
0: "Illegal state",
1: "Immer drafts cannot have computed properties",
2: "This object has been frozen and should not be mutated",
3: function(n) {
return "Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? " + n;
},
4: "An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.",
5: "Immer forbids circular references",
6: "The first or second argument to `produce` must be a function",
7: "The third argument to `produce` must be a function or undefined",
8: "First argument to `createDraft` must be a plain object, an array, or an immerable object",
9: "First argument to `finishDraft` must be a draft returned by `createDraft`",
10: "The given draft is already finalized",
11: "Object.defineProperty() cannot be used on an Immer draft",
12: "Object.setPrototypeOf() cannot be used on an Immer draft",
13: "Immer only supports deleting array indices",
14: "Immer only supports setting array indices and the 'length' property",
15: function(n) {
return "Cannot apply patch, path doesn't resolve: " + n;
},
16: 'Sets cannot have "replace" patches.',
17: function(n) {
return "Unsupported patch operation: " + n;
},
18: function(n) {
return "The plugin for '" + n + "' has not been loaded into Immer. To enable the plugin, import and call `enable" + n + "()` when initializing your application.";
},
20: "Cannot use proxies if Proxy, Proxy.revocable or Reflect are not available",
21: function(n) {
return "produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '" + n + "'";
},
22: function(n) {
return "'current' expects a draft, got: " + n;
},
23: function(n) {
return "'original' expects a draft, got: " + n;
},
24: "Patching reserved attributes like __proto__, prototype and constructor is not allowed"
}, Z = "" + Object.prototype.constructor, nn = "undefined" != typeof Reflect && Reflect.ownKeys ? Reflect.ownKeys : void 0 !== Object.getOwnPropertySymbols ? function(n) {
return Object.getOwnPropertyNames(n).concat(Object.getOwnPropertySymbols(n));
} : Object.getOwnPropertyNames, rn = Object.getOwnPropertyDescriptors || function(n) {
var r = {};
return nn(n).forEach(function(t) {
r[t] = Object.getOwnPropertyDescriptor(n, t);
}), r;
}, tn = {}, en = {
get: function(n, r) {
if (r === Q) return n;
var e = p(n);
if (!u(e, r)) return function(n, r, t) {
var e, i = I(r, t);
return i ? "value" in i ? i.value : null === (e = i.get) || void 0 === e ? void 0 : e.call(n.k) : void 0;
}(n, e, r);
var i = e[r];
return n.I || !t(i) ? i : i === z(n.t, r) ? (E(n), n.o[r] = N(n.A.h, i, n)) : i;
},
has: function(n, r) {
return r in p(n);
},
ownKeys: function(n) {
return Reflect.ownKeys(p(n));
},
set: function(n, r, t) {
var e = I(p(n), r);
if (null == e ? void 0 : e.set) return e.set.call(n.k, t), !0;
if (!n.P) {
var i = z(p(n), r), o = null == i ? void 0 : i[Q];
if (o && o.t === t) return n.o[r] = t, n.R[r] = !1, !0;
if (c(t, i) && (void 0 !== t || u(n.t, r))) return !0;
E(n), k(n);
}
return n.o[r] === t && (void 0 !== t || r in n.o) || Number.isNaN(t) && Number.isNaN(n.o[r]) || (n.o[r] = t, n.R[r] = !0), !0;
},
deleteProperty: function(n, r) {
return void 0 !== z(n.t, r) || r in n.t ? (n.R[r] = !1, E(n), k(n)) : delete n.R[r], n.o && delete n.o[r], !0;
},
getOwnPropertyDescriptor: function(n, r) {
var t = p(n), e = Reflect.getOwnPropertyDescriptor(t, r);
return e ? {
writable: !0,
configurable: 1 !== n.i || "length" !== r,
enumerable: e.enumerable,
value: t[r]
} : e;
},
defineProperty: function() {
n(11);
},
getPrototypeOf: function(n) {
return Object.getPrototypeOf(n.t);
},
setPrototypeOf: function() {
n(12);
}
}, on = {};
i(en, function(n, r) {
on[n] = function() {
return arguments[0] = arguments[0][0], r.apply(this, arguments);
};
}), on.deleteProperty = function(r, t) {
return on.set.call(this, r, t, void 0);
}, on.set = function(r, t, e) {
return en.set.call(this, r[0], t, e, r[0]);
};
var un = function() {
function e(r) {
var e = this;
this.O = B, this.D = !0, this.produce = function(r, i, o) {
if ("function" == typeof r && "function" != typeof i) {
var u = i;
i = r;
var a = e;
return function(n) {
var r = this;
void 0 === n && (n = u);
for(var t = arguments.length, e = Array(t > 1 ? t - 1 : 0), o = 1; o < t; o++)e[o - 1] = arguments[o];
return a.produce(n, function(n) {
var t;
return (t = i).call.apply(t, [
r,
n
].concat(e));
});
};
}
var f;
if ("function" != typeof i && n(6), void 0 !== o && "function" != typeof o && n(7), t(r)) {
var c = w(e), s = N(e, r, void 0), v = !0;
try {
f = i(s), v = !1;
} finally{
v ? g(c) : O(c);
}
return "undefined" != typeof Promise && f instanceof Promise ? f.then(function(n) {
return j(c, o), P(n, c);
}, function(n) {
throw g(c), n;
}) : (j(c, o), P(f, c));
}
if (!r || "object" != typeof r) {
if (void 0 === (f = i(r)) && (f = r), f === H && (f = void 0), e.D && d(f, !0), o) {
var p = [], l = [];
b("Patches").M(r, f, p, l), o(p, l);
}
return f;
}
n(21, r);
}, this.produceWithPatches = function(n, r) {
if ("function" == typeof n) return function(r) {
for(var t = arguments.length, i = Array(t > 1 ? t - 1 : 0), o = 1; o < t; o++)i[o - 1] = arguments[o];
return e.produceWithPatches(r, function(r) {
return n.apply(void 0, [
r
].concat(i));
});
};
var t, i, o = e.produce(n, r, function(n, r) {
t = n, i = r;
});
return "undefined" != typeof Promise && o instanceof Promise ? o.then(function(n) {
return [
n,
t,
i
];
}) : [
o,
t,
i
];
}, "boolean" == typeof (null == r ? void 0 : r.useProxies) && this.setUseProxies(r.useProxies), "boolean" == typeof (null == r ? void 0 : r.autoFreeze) && this.setAutoFreeze(r.autoFreeze);
}
var i = e.prototype;
return i.createDraft = function(e) {
t(e) || n(8), r(e) && (e = R(e));
var i = w(this), o = N(this, e, void 0);
return o[Q].C = !0, O(i), o;
}, i.finishDraft = function(r, t) {
var e = r && r[Q];
var i = e.A;
return j(i, t), P(void 0, i);
}, i.setAutoFreeze = function(n) {
this.D = n;
}, i.setUseProxies = function(r) {
r && !B && n(20), this.O = r;
}, i.applyPatches = function(n, t) {
var e;
for(e = t.length - 1; e >= 0; e--){
var i = t[e];
if (0 === i.path.length && "replace" === i.op) {
n = i.value;
break;
}
}
e > -1 && (t = t.slice(e + 1));
var o = b("Patches").$;
return r(n) ? o(n, t) : this.produce(n, function(n) {
return o(n, t);
});
}, e;
}(), an = new un, fn = an.produce, cn = an.produceWithPatches.bind(an), sn = an.setAutoFreeze.bind(an), vn = an.setUseProxies.bind(an), pn = an.applyPatches.bind(an), ln = an.createDraft.bind(an), dn = an.finishDraft.bind(an);
exports.default = fn;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"6ky3m":[function(require,module,exports,__globalThis) {
exports.interopDefault = function(a) {
return a && a.__esModule ? a : {
default: a
};
};
exports.defineInteropFlag = function(a) {
Object.defineProperty(a, '__esModule', {
value: true
});
};
exports.exportAll = function(source, dest) {
Object.keys(source).forEach(function(key) {
if (key === 'default' || key === '__esModule' || Object.prototype.hasOwnProperty.call(dest, key)) return;
Object.defineProperty(dest, key, {
enumerable: true,
get: function() {
return source[key];
}
});
});
return dest;
};
exports.export = function(dest, destName, get) {
Object.defineProperty(dest, destName, {
enumerable: true,
get: get
});
};
},{}],"lOXYn":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "flattenVRVideo", ()=>flattenVRVideo);
var _util = require("../util/util");
function flattenVRVideo(videoContainer, video) {
let isVRVideo = true;
const VRCanvas = videoContainer.getElementsByClassName('webgl')[0];
VRCanvas != null ? (0, _util.deleteElement)(VRCanvas) : isVRVideo = false;
const VRControl = document.getElementsByClassName('ytp-webgl-spherical-control')[0];
VRControl != null ? (0, _util.deleteElement)(VRControl) : isVRVideo = false;
if (isVRVideo) {
videoContainer.style.cursor = 'auto';
video.style.display = 'block';
(0, _util.flashMessage)('Flattened VR video.', 'green');
} else (0, _util.flashMessage)('Not a VR video or already flattened.', 'red');
}
},{"../util/util":"99arg","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"99arg":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "assertDefined", ()=>assertDefined);
parcelHelpers.export(exports, "setFlashMessageHook", ()=>setFlashMessageHook);
parcelHelpers.export(exports, "flashMessage", ()=>flashMessage);
parcelHelpers.export(exports, "retryUntilTruthyResult", ()=>retryUntilTruthyResult);
parcelHelpers.export(exports, "sleep", ()=>sleep);
parcelHelpers.export(exports, "injectCSS", ()=>injectCSS);
parcelHelpers.export(exports, "deleteElement", ()=>deleteElement);
parcelHelpers.export(exports, "querySelectors", ()=>querySelectors);
parcelHelpers.export(exports, "once", ()=>once);
parcelHelpers.export(exports, "setAttributes", ()=>setAttributes);
parcelHelpers.export(exports, "copyToClipboard", ()=>copyToClipboard);
parcelHelpers.export(exports, "getRounder", ()=>getRounder);
parcelHelpers.export(exports, "roundValue", ()=>roundValue);
parcelHelpers.export(exports, "speedRounder", ()=>speedRounder);
parcelHelpers.export(exports, "timeRounder", ()=>timeRounder);
parcelHelpers.export(exports, "clampNumber", ()=>clampNumber);
parcelHelpers.export(exports, "toHHMMSS", ()=>toHHMMSS);
parcelHelpers.export(exports, "toHHMMSSTrimmed", ()=>toHHMMSSTrimmed);
parcelHelpers.export(exports, "mod", ()=>mod);
parcelHelpers.export(exports, "bsearch", ()=>bsearch);
parcelHelpers.export(exports, "getEasedValue", ()=>getEasedValue);
parcelHelpers.export(exports, "seekToSafe", ()=>seekToSafe);
parcelHelpers.export(exports, "seekBySafe", ()=>seekBySafe);
parcelHelpers.export(exports, "blockEvent", ()=>blockEvent);
parcelHelpers.export(exports, "getCropString", ()=>getCropString);
parcelHelpers.export(exports, "ternaryToString", ()=>ternaryToString);
parcelHelpers.export(exports, "arrayEquals", ()=>arrayEquals);
parcelHelpers.export(exports, "getOutputDuration", ()=>getOutputDuration);
parcelHelpers.export(exports, "onLoadVideoPage", ()=>onLoadVideoPage);
parcelHelpers.export(exports, "observeVideoElementChange", ()=>observeVideoElementChange);
parcelHelpers.export(exports, "parseTimeStringToSeconds", ()=>parseTimeStringToSeconds);
parcelHelpers.export(exports, "getVideoDuration", ()=>getVideoDuration);
parcelHelpers.export(exports, "injectProgressBar", ()=>injectProgressBar);
var _litHtml = require("lit-html");
var _platforms = require("../platforms/platforms");
var _appState = require("../appState");
function assertDefined(value, msg) {
if (value == null) throw new Error(msg ?? 'Expected value to be defined');
}
let flashMessageHook;
function setFlashMessageHook(hook) {
flashMessageHook = hook;
}
const SEVERITY_DEFAULT_LIFETIME = {
info: 3000,
success: 3000,
warn: 6000,
error: 12000
};
const SEVERITY_ICON = {
info: '',
success: '\u2713 ',
warn: '\u26A0 ',
error: '\u2716 '
};
const activeFlashes = new Map();
function inferSeverityFromColor(color) {
const c = color.toLowerCase().trim();
if (c === 'red' || c === 'crimson' || c === 'tomato' || c.includes('rgb(237')) return 'error';
if (c === 'orange' || c === 'yellow' || c === 'gold' || c === 'darkorange') return 'warn';
if (c === 'green' || c === 'lime' || c === 'lightgreen' || c === 'limegreen') return 'success';
return 'info';
}
function flashMessage(msg, color, lifetime, severity) {
if (!flashMessageHook) return;
const sev = severity ?? inferSeverityFromColor(color);
const life = lifetime ?? SEVERITY_DEFAULT_LIFETIME[sev];
const key = sev + '\u0000' + msg;
const existing = activeFlashes.get(key);
if (existing) {
existing.count += 1;
existing.countEl.textContent = ' \u00D7' + existing.count;
existing.countEl.style.display = 'inline';
existing.div.classList.remove('flash-div');
existing.div.offsetWidth;
existing.div.classList.add('flash-div');
existing.div.style.animationDuration = life + 'ms';
clearTimeout(existing.timeoutId);
existing.timeoutId = window.setTimeout(()=>{
deleteElement(existing.div);
activeFlashes.delete(key);
}, life);
return;
}
const flashDiv = document.createElement('div');
flashDiv.className = 'msg-div flash-div flash-' + sev;
flashDiv.style.animationDuration = life + 'ms';
const textSpan = document.createElement('span');
textSpan.className = 'flash-msg';
textSpan.textContent = SEVERITY_ICON[sev] + msg;
flashDiv.appendChild(textSpan);
const countEl = document.createElement('span');
countEl.className = 'flash-count';
countEl.style.display = 'none';
flashDiv.appendChild(countEl);
flashMessageHook.insertAdjacentElement('beforebegin', flashDiv);
const timeoutId = window.setTimeout(()=>{
deleteElement(flashDiv);
activeFlashes.delete(key);
}, life);
activeFlashes.set(key, {
div: flashDiv,
countEl,
timeoutId,
count: 1,
lifetime: life
});
}
async function retryUntilTruthyResult(fn, wait = 200) {
let result = fn();
while(!result){
console.debug(`Retrying function: ${fn.name || 'arrow'} with body ${fn.toString()} because result was ${String(result)}`);
result = fn();
await sleep(wait);
}
return result;
}
function sleep(ms) {
return new Promise((resolve)=>setTimeout(resolve, ms));
}
function injectCSS(css, id) {
const style = document.createElement('style');
style.setAttribute('id', id);
style.textContent = css;
document.body.appendChild(style);
return style;
}
function deleteElement(elem) {
if (elem?.parentElement) elem.parentElement.removeChild(elem);
}
function querySelectors(selectors, root = document) {
const elements = {};
for(const key in selectors)// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
elements[key] = root.querySelector(selectors[key]);
return elements;
}
function once(fn, context) {
let result = null;
return function(...args) {
if (fn) {
result = fn.apply(context ?? this, args);
fn = null;
}
return result;
};
}
function setAttributes(el, attrs) {
Object.keys(attrs).forEach((key)=>{
el.setAttribute(key, attrs[key]);
});
}
function copyToClipboard(str) {
const el = document.createElement('textarea');
el.value = str;
document.body.appendChild(el);
el.select();
document.execCommand('copy'); // eslint-disable-line @typescript-eslint/no-deprecated
document.body.removeChild(el);
}
function getRounder(multiple, precision) {
return (value)=>{
const roundedValue = Math.round(value / multiple) * multiple;
const roundedValueFixedPrecision = +roundedValue.toFixed(precision);
return roundedValueFixedPrecision;
};
}
function roundValue(value, multiple, precision) {
return getRounder(multiple, precision)(value);
}
const speedRounder = getRounder(5e-2, 2);
const timeRounder = getRounder(1e-6, 6);
function clampNumber(number, min, max) {
return Math.max(min, Math.min(number, max));
}
function toHHMMSS(seconds) {
return new Date(seconds * 1000).toISOString().substring(11, 23);
}
function toHHMMSSTrimmed(seconds) {
return toHHMMSS(seconds).replace(/(00:)+(.*)/, '$2');
}
function mod(dividend, divisor) {
return (dividend % divisor + divisor) % divisor;
}
function bsearch(haystack, needle, comparator, lowParam, highParam) {
let mid, cmp;
let low = lowParam ?? 0;
low = low | 0;
if (low < 0 || low >= haystack.length) throw new RangeError('invalid lower bound');
let high = highParam ?? haystack.length - 1;
high = high | 0;
if (high < low || high >= haystack.length) throw new RangeError('invalid upper bound');
while(low <= high){
// The naive `low + high >>> 1` could fail for array lengths > 2**31
// because `>>>` converts its operands to int32. `low + (high - low >>> 1)`
// works for array lengths <= 2**32-1 which is also Javascript's max array
// length.
mid = low + (high - low >>> 1);
cmp = +comparator(haystack[mid], needle, mid, haystack);
// Too low.
if (cmp < 0.0) low = mid + 1;
else if (cmp > 0.0) high = mid - 1;
else return [
mid,
mid
];
}
// Key not found.
return [
high,
low
];
}
function getEasedValue(easingFunc, startValue, endValue, startTime, endTime, currentTime) {
const elapsed = currentTime - startTime;
const duration = endTime - startTime;
const valueDelta = endValue - startValue;
const easedValuePercentage = easingFunc(elapsed / duration);
const easedValue = startValue + valueDelta * easedValuePercentage;
return easedValue;
}
function seekToSafe(video, newTime) {
newTime = clampNumber(newTime, 0, video.duration);
if (!isNaN(newTime) && video.getCurrentTime() != newTime && !video.seeking) try {
video.seekTo(newTime);
} catch (e) {
console.error(e);
}
}
function seekBySafe(video, timeDelta) {
const newTime = video.getCurrentTime() + timeDelta;
seekToSafe(video, newTime);
}
function blockEvent(e) {
e.preventDefault();
e.stopImmediatePropagation();
}
function getCropString(x, y, w, h) {
return `${x}:${y}:${w}:${h}`;
}
function ternaryToString(ternary, def) {
if (ternary == null) return def ?? '(Disabled)';
else if (ternary) return '(Enabled)';
else if (!ternary) return '(Disabled)';
else return null;
}
function arrayEquals(a, b) {
return a.length === b.length && a.every((v, i)=>v === b[i]);
}
function getOutputDuration(speedMap, fps = 30) {
let outputDuration = 0;
const frameDur = 1 / fps;
const nSects = speedMap.length - 1;
// Account for marker pair start time as trim filter sets start time to ~0
const speedMapStartTime = speedMap[0].x;
// Account for first input frame delay due to potentially imprecise trim
const startt = Math.ceil(speedMapStartTime / frameDur) * frameDur - speedMapStartTime;
for(let sect = 0; sect < nSects; ++sect){
const left = speedMap[sect];
const right = speedMap[sect + 1];
const startSpeed = left.y;
const endSpeed = right.y;
const speedChange = endSpeed - startSpeed;
const sectStart = left.x - speedMapStartTime - startt;
let sectEnd = right.x - speedMapStartTime - startt;
// Account for last input frame delay due to potentially imprecise trim
if (sect === nSects - 1) {
sectEnd = Math.floor(right.x / frameDur) * frameDur;
// When trim is frame-precise, the frame that begins at the marker pair end time is not included
if (right.x - sectEnd < 1e-10) sectEnd = sectEnd - frameDur;
sectEnd = sectEnd - speedMapStartTime - startt;
sectEnd = Math.floor(sectEnd * 1000000) / 1000000;
}
const sectDuration = sectEnd - sectStart;
if (sectDuration === 0) continue;
const m = speedChange / sectDuration;
const b = startSpeed - m * sectStart;
if (speedChange === 0) outputDuration += sectDuration / endSpeed;
else // Integrate the reciprocal of the linear time vs speed function for the current section
outputDuration += 1 / m * (Math.log(Math.abs(m * sectEnd + b)) - Math.log(Math.abs(m * sectStart + b)));
}
// Each output frame time is rounded to the nearest multiple of a frame's duration at the given fps
outputDuration = Math.round(outputDuration / frameDur) * frameDur;
// The last included frame is held for a single frame's duration
outputDuration += frameDur;
outputDuration = Math.round(outputDuration * 1000) / 1000;
return outputDuration;
}
async function onLoadVideoPage(callback) {
const ytdapp = await retryUntilTruthyResult(()=>document.getElementsByTagName('ytd-app')[0]);
if (ytdapp.hasAttribute('is-watch-page')) {
console.log('watch page loaded');
callback();
return;
}
const observer = new MutationObserver((mutationList)=>{
mutationList.forEach((mutation)=>{
if (mutation.type === 'attributes' && mutation.attributeName === 'is-watch-page' && ytdapp.hasAttribute('is-watch-page')) {
console.log('watch page loaded');
observer.disconnect();
callback();
}
});
});
const config = {
attributeFilter: [
'is-watch-page'
]
};
console.log(`Waiting for video page load before calling ${callback.name}`);
observer.observe(ytdapp, config);
}
function observeVideoElementChange(videoContainer, callback) {
const observer = new MutationObserver((mutationList)=>{
mutationList.forEach((mutation)=>{
if (mutation.type === 'childList') {
console.log('observed mutation in video container', mutation);
callback(mutation.addedNodes);
}
});
});
console.log(`Watching for changes to video container nodes. callback=${callback.name}`);
observer.observe(videoContainer, {
childList: true
});
}
function parseTimeStringToSeconds(timeString) {
const [hours, minutes, seconds] = timeString.split(':').map(Number);
return hours * 3600 + minutes * 60 + seconds;
}
let videoDuration = NaN;
function getVideoDuration(platform, video) {
if (!Number.isNaN(videoDuration)) return videoDuration;
if (platform === (0, _platforms.VideoPlatforms).afreecatv) {
let duration = 0;
for (const videoPart of unsafeWindow.vodCore.playerController.fileItems)duration += videoPart.duration;
videoDuration = duration;
} else videoDuration = video.duration;
return videoDuration;
}
function injectProgressBar(color, tag) {
const progressDiv = document.createElement('div');
progressDiv.setAttribute('class', 'msg-div');
progressDiv.addEventListener('done', ()=>{
progressDiv.setAttribute('class', 'msg-div flash-div');
setTimeout(()=>{
deleteElement(progressDiv);
}, 2500);
});
(0, _litHtml.render)((0, _litHtml.html)`<span class="flash-msg" style="color:${color}"> ${tag} Zipping Progress: 0%</span>`, progressDiv);
(0, _appState.appState).hooks.frameCapturerProgressBar.insertAdjacentElement('beforebegin', progressDiv);
return progressDiv;
}
},{"lit-html":"9fQBw","../platforms/platforms":"1kR7r","../appState":"g0AlP","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"9fQBw":[function(require,module,exports,__globalThis) {
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "_$LH", ()=>j);
parcelHelpers.export(exports, "html", ()=>b);
parcelHelpers.export(exports, "mathml", ()=>T);
parcelHelpers.export(exports, "noChange", ()=>E);
parcelHelpers.export(exports, "nothing", ()=>A);
parcelHelpers.export(exports, "render", ()=>D);
parcelHelpers.export(exports, "svg", ()=>w);
const t = globalThis, i = (t)=>t, s = t.trustedTypes, e = s ? s.createPolicy("lit-html", {
createHTML: (t)=>t
}) : void 0, h = "$lit$", o = `lit$${Math.random().toFixed(9).slice(2)}$`, n = "?" + o, r = `<${n}>`, l = document, c = ()=>l.createComment(""), a = (t)=>null === t || "object" != typeof t && "function" != typeof t, u = Array.isArray, d = (t)=>u(t) || "function" == typeof t?.[Symbol.iterator], f = "[ \t\n\f\r]", v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, _ = /-->/g, m = />/g, p = RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`, "g"), g = /'/g, $ = /"/g, y = /^(?:script|style|textarea|title)$/i, x = (t)=>(i, ...s)=>({
_$litType$: t,
strings: i,
values: s
}), b = x(1), w = x(2), T = x(3), E = Symbol.for("lit-noChange"), A = Symbol.for("lit-nothing"), C = new WeakMap, P = l.createTreeWalker(l, 129);
function V(t, i) {
if (!u(t) || !t.hasOwnProperty("raw")) throw Error("invalid template strings array");
return void 0 !== e ? e.createHTML(i) : i;
}
const N = (t, i)=>{
const s = t.length - 1, e = [];
let n, l = 2 === i ? "<svg>" : 3 === i ? "<math>" : "", c = v;
for(let i = 0; i < s; i++){
const s = t[i];
let a, u, d = -1, f = 0;
for(; f < s.length && (c.lastIndex = f, u = c.exec(s), null !== u);)f = c.lastIndex, c === v ? "!--" === u[1] ? c = _ : void 0 !== u[1] ? c = m : void 0 !== u[2] ? (y.test(u[2]) && (n = RegExp("</" + u[2], "g")), c = p) : void 0 !== u[3] && (c = p) : c === p ? ">" === u[0] ? (c = n ?? v, d = -1) : void 0 === u[1] ? d = -2 : (d = c.lastIndex - u[2].length, a = u[1], c = void 0 === u[3] ? p : '"' === u[3] ? $ : g) : c === $ || c === g ? c = p : c === _ || c === m ? c = v : (c = p, n = void 0);
const x = c === p && t[i + 1].startsWith("/>") ? " " : "";
l += c === v ? s + r : d >= 0 ? (e.push(a), s.slice(0, d) + h + s.slice(d) + o + x) : s + o + (-2 === d ? i : x);
}
return [
V(t, l + (t[s] || "<?>") + (2 === i ? "</svg>" : 3 === i ? "</math>" : "")),
e
];
};
class S {
constructor({ strings: t, _$litType$: i }, e){
let r;
this.parts = [];
let l = 0, a = 0;
const u = t.length - 1, d = this.parts, [f, v] = N(t, i);
if (this.el = S.createElement(f, e), P.currentNode = this.el.content, 2 === i || 3 === i) {
const t = this.el.content.firstChild;
t.replaceWith(...t.childNodes);
}
for(; null !== (r = P.nextNode()) && d.length < u;){
if (1 === r.nodeType) {
if (r.hasAttributes()) for (const t of r.getAttributeNames())if (t.endsWith(h)) {
const i = v[a++], s = r.getAttribute(t).split(o), e = /([.?@])?(.*)/.exec(i);
d.push({
type: 1,
index: l,
name: e[2],
strings: s,
ctor: "." === e[1] ? I : "?" === e[1] ? L : "@" === e[1] ? z : H
}), r.removeAttribute(t);
} else t.startsWith(o) && (d.push({
type: 6,
index: l
}), r.removeAttribute(t));
if (y.test(r.tagName)) {
const t = r.textContent.split(o), i = t.length - 1;
if (i > 0) {
r.textContent = s ? s.emptyScript : "";
for(let s = 0; s < i; s++)r.append(t[s], c()), P.nextNode(), d.push({
type: 2,
index: ++l
});
r.append(t[i], c());
}
}
} else if (8 === r.nodeType) {
if (r.data === n) d.push({
type: 2,
index: l
});
else {
let t = -1;
for(; -1 !== (t = r.data.indexOf(o, t + 1));)d.push({
type: 7,
index: l
}), t += o.length - 1;
}
}
l++;
}
}
static createElement(t, i) {
const s = l.createElement("template");
return s.innerHTML = t, s;
}
}
function M(t, i, s = t, e) {
if (i === E) return i;
let h = void 0 !== e ? s._$Co?.[e] : s._$Cl;
const o = a(i) ? void 0 : i._$litDirective$;
return h?.constructor !== o && (h?._$AO?.(!1), void 0 === o ? h = void 0 : (h = new o(t), h._$AT(t, s, e)), void 0 !== e ? (s._$Co ??= [])[e] = h : s._$Cl = h), void 0 !== h && (i = M(t, h._$AS(t, i.values), h, e)), i;
}
class R {
constructor(t, i){
this._$AV = [], this._$AN = void 0, this._$AD = t, this._$AM = i;
}
get parentNode() {
return this._$AM.parentNode;
}
get _$AU() {
return this._$AM._$AU;
}
u(t) {
const { el: { content: i }, parts: s } = this._$AD, e = (t?.creationScope ?? l).importNode(i, !0);
P.currentNode = e;
let h = P.nextNode(), o = 0, n = 0, r = s[0];
for(; void 0 !== r;){
if (o === r.index) {
let i;
2 === r.type ? i = new k(h, h.nextSibling, this, t) : 1 === r.type ? i = new r.ctor(h, r.name, r.strings, this, t) : 6 === r.type && (i = new Z(h, this, t)), this._$AV.push(i), r = s[++n];
}
o !== r?.index && (h = P.nextNode(), o++);
}
return P.currentNode = l, e;
}
p(t) {
let i = 0;
for (const s of this._$AV)void 0 !== s && (void 0 !== s.strings ? (s._$AI(t, s, i), i += s.strings.length - 2) : s._$AI(t[i])), i++;
}
}
class k {
get _$AU() {
return this._$AM?._$AU ?? this._$Cv;
}
constructor(t, i, s, e){
this.type = 2, this._$AH = A, this._$AN = void 0, this._$AA = t, this._$AB = i, this._$AM = s, this.options = e, this._$Cv = e?.isConnected ?? !0;
}
get parentNode() {
let t = this._$AA.parentNode;
const i = this._$AM;
return void 0 !== i && 11 === t?.nodeType && (t = i.parentNode), t;
}
get startNode() {
return this._$AA;
}
get endNode() {
return this._$AB;
}
_$AI(t, i = this) {
t = M(this, t, i), a(t) ? t === A || null == t || "" === t ? (this._$AH !== A && this._$AR(), this._$AH = A) : t !== this._$AH && t !== E && this._(t) : void 0 !== t._$litType$ ? this.$(t) : void 0 !== t.nodeType ? this.T(t) : d(t) ? this.k(t) : this._(t);
}
O(t) {
return this._$AA.parentNode.insertBefore(t, this._$AB);
}
T(t) {
this._$AH !== t && (this._$AR(), this._$AH = this.O(t));
}
_(t) {
this._$AH !== A && a(this._$AH) ? this._$AA.nextSibling.data = t : this.T(l.createTextNode(t)), this._$AH = t;
}
$(t) {
const { values: i, _$litType$: s } = t, e = "number" == typeof s ? this._$AC(t) : (void 0 === s.el && (s.el = S.createElement(V(s.h, s.h[0]), this.options)), s);
if (this._$AH?._$AD === e) this._$AH.p(i);
else {
const t = new R(e, this), s = t.u(this.options);
t.p(i), this.T(s), this._$AH = t;
}
}
_$AC(t) {
let i = C.get(t.strings);
return void 0 === i && C.set(t.strings, i = new S(t)), i;
}
k(t) {
u(this._$AH) || (this._$AH = [], this._$AR());
const i = this._$AH;
let s, e = 0;
for (const h of t)e === i.length ? i.push(s = new k(this.O(c()), this.O(c()), this, this.options)) : s = i[e], s._$AI(h), e++;
e < i.length && (this._$AR(s && s._$AB.nextSibling, e), i.length = e);
}
_$AR(t = this._$AA.nextSibling, s) {
for(this._$AP?.(!1, !0, s); t !== this._$AB;){
const s = i(t).nextSibling;
i(t).remove(), t = s;
}
}
setConnected(t) {
void 0 === this._$AM && (this._$Cv = t, this._$AP?.(t));
}
}
class H {
get tagName() {
return this.element.tagName;
}
get _$AU() {
return this._$AM._$AU;
}
constructor(t, i, s, e, h){
this.type = 1, this._$AH = A, this._$AN = void 0, this.element = t, this.name = i, this._$AM = e, this.options = h, s.length > 2 || "" !== s[0] || "" !== s[1] ? (this._$AH = Array(s.length - 1).fill(new String), this.strings = s) : this._$AH = A;
}
_$AI(t, i = this, s, e) {
const h = this.strings;
let o = !1;
if (void 0 === h) t = M(this, t, i, 0), o = !a(t) || t !== this._$AH && t !== E, o && (this._$AH = t);
else {
const e = t;
let n, r;
for(t = h[0], n = 0; n < h.length - 1; n++)r = M(this, e[s + n], i, n), r === E && (r = this._$AH[n]), o ||= !a(r) || r !== this._$AH[n], r === A ? t = A : t !== A && (t += (r ?? "") + h[n + 1]), this._$AH[n] = r;
}
o && !e && this.j(t);
}
j(t) {
t === A ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? "");
}
}
class I extends H {
constructor(){
super(...arguments), this.type = 3;
}
j(t) {
this.element[this.name] = t === A ? void 0 : t;
}
}
class L extends H {
constructor(){
super(...arguments), this.type = 4;
}
j(t) {
this.element.toggleAttribute(this.name, !!t && t !== A);
}
}
class z extends H {
constructor(t, i, s, e, h){
super(t, i, s, e, h), this.type = 5;
}
_$AI(t, i = this) {
if ((t = M(this, t, i, 0) ?? A) === E) return;
const s = this._$AH, e = t === A && s !== A || t.capture !== s.capture || t.once !== s.once || t.passive !== s.passive, h = t !== A && (s === A || e);
e && this.element.removeEventListener(this.name, this, s), h && this.element.addEventListener(this.name, this, t), this._$AH = t;
}
handleEvent(t) {
"function" == typeof this._$AH ? this._$AH.call(this.options?.host ?? this.element, t) : this._$AH.handleEvent(t);
}
}
class Z {
constructor(t, i, s){
this.element = t, this.type = 6, this._$AN = void 0, this._$AM = i, this.options = s;
}
get _$AU() {
return this._$AM._$AU;
}
_$AI(t) {
M(this, t);
}
}
const j = {
M: h,
P: o,
A: n,
C: 1,
L: N,
R,
D: d,
V: M,
I: k,
H,
N: L,
U: z,
B: I,
F: Z
}, B = t.litHtmlPolyfillSupport;
B?.(S, k), (t.litHtmlVersions ??= []).push("3.3.2");
const D = (t, i, s)=>{
const e = s?.renderBefore ?? i;
let h = e._$litPart$;
if (void 0 === h) {
const t = s?.renderBefore ?? null;
e._$litPart$ = h = new k(i.insertBefore(c(), t), t, void 0, s ?? {});
}
return h._$AI(t), h;
};
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"1kR7r":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "VideoPlatforms", ()=>VideoPlatforms);
parcelHelpers.export(exports, "getVideoPlatformHooks", ()=>getVideoPlatformHooks);
parcelHelpers.export(exports, "getPlatform", ()=>getPlatform);
parcelHelpers.export(exports, "videoPlatformDataRecords", ()=>videoPlatformDataRecords);
var _util = require("../util/util");
var _fs = require("fs");
var _navigation = require("./navigation");
const youtubeCSS = '';
const vliveCSS = "#shortcutsTableToggleButton {\n width: 30px;\n top: -7px;\n left: 10px;\n}\n\n#markers-upload-div input[type='file'],\n#settings-editor-div input[type='checkbox'],\n#settings-editor-div input[type='radio'] {\n position: static;\n width: auto;\n height: auto;\n opacity: 1;\n appearance: auto;\n -webkit-appearance: auto;\n -moz-appearance: auto;\n}\n\n._click_zone[data-title-container] {\n pointer-events: none !important;\n}\n\ndiv[class*='layout_main'] {\n width: 100% !important;\n margin: 5px auto 0px;\n}\ndiv[class*='layout_content'] {\n width: 98% !important;\n margin-left: 1%;\n}\ndiv[class*='lnb'] {\n display: none;\n}\n\n@media (min-width: 1024px) {\n div[class*='snb'] {\n display: none;\n }\n}\n";
const naver_tvCSS = "#shortcutsTableToggleButton {\n width: 40px;\n display: inline-block;\n background-color: transparent;\n border: transparent;\n top: -3px;\n}\n\n#markers-div {\n height: 11px;\n}\n\n#markers-svg,\n#selected-marker-pair-overlay,\n#start-marker-numberings,\n#end-marker-numberings {\n font-size: 6.5pt;\n width: 100%;\n height: 300%;\n top: 2px;\n position: absolute;\n z-index: 99;\n paint-order: stroke;\n stroke: rgba(0, 0, 0, 0.25);\n stroke-width: 2px;\n}\n\n#start-marker-numberings {\n top: -13px;\n height: 13px;\n}\n\n#end-marker-numberings {\n top: 13px;\n height: 13px;\n}\n";
const weverseCSS = "#shortcutsTableToggleButton {\n width: 40px;\n display: inline-block;\n background-color: transparent;\n border: transparent;\n}\n\n#markers-svg,\n#selected-marker-pair-overlay,\n#start-marker-numberings,\n#end-marker-numberings {\n font-size: 6.5pt;\n width: 100%;\n height: 100%;\n top: 2px;\n position: absolute;\n z-index: 99;\n paint-order: stroke;\n stroke: rgba(0, 0, 0, 0.25);\n stroke-width: 2px;\n}\n\n.slider {\n padding-bottom: 15px !important;\n}\n\n\n#start-marker-numberings {\n top: -13px;\n}\n\n#end-marker-numberings {\n top: 13px;\n}\n\n.pzp-pc__bottom-buttons {\n top: 8px;\n}\n";
const afreecatvCSS = "#shortcutsTableToggleButton {\n width: 40px;\n display: inline-block;\n background-color: transparent;\n border: transparent;\n top: -3px;\n}\n\n#markers-div {\n height: 14px;\n position: relative;\n top: -6px;\n}\n\n#markers-svg,\n#selected-marker-pair-overlay,\n#start-marker-numberings,\n#end-marker-numberings {\n font-size: 6.5pt;\n width: 100%;\n height: 300%;\n top: 2px;\n z-index: 99;\n position: absolute;\n paint-order: stroke;\n stroke: rgba(0, 0, 0, 0.25);\n stroke-width: 2px;\n}\n\n\n\n.progress {\n padding-bottom: 25px !important;\n}\n\n#start-marker-numberings {\n top: -20px;\n height: 13px;\n}\n\n#end-marker-numberings {\n top: 8px;\n height: 13px;\n}\n";
const ytclipperCSS = "#markers-svg,\n#selected-marker-pair-overlay,\n#start-marker-numberings,\n#end-marker-numberings {\n font-size: 6.5pt;\n width: 100%;\n height: 20px;\n top: 8px;\n z-index: 99;\n position: absolute;\n paint-order: stroke;\n stroke: rgba(0, 0, 0, 0.25);\n stroke-width: 2px;\n pointer-events: none;\n}\n\n#start-marker-numberings {\n top: -5px;\n height: 13px;\n}\n\n#end-marker-numberings {\n top: 20px;\n height: 13px;\n}\n\n#shortcutsTableToggleButton {\n width: 40px;\n /* Override the shared CSS's `float: left` \u2014 on the yt_clipper player\n page the toggle buttons live directly inside the Video.js\n `.vjs-control-bar` (a flex row), not inside YouTube's nested\n `.ytp-right-controls`. Floating yanks them out of the flex row and\n piles them on the LEFT edge of the bar. */\n float: none;\n /* Push the toggle-button cluster (scissors \u2192 hints bar \u2192 fullscreen)\n to the right end of the control bar. Video.js's default control flow\n doesn't anchor anything to the right, so without this auto margin\n our buttons sit immediately after the default controls (Play / Time\n / Progress / etc.) \u2014 i.e. on the LEFT side. The auto margin absorbs\n all leftover horizontal space before the scissors, shoving the whole\n trailing cluster to the right edge. */\n margin-left: auto;\n}\n\n/* Hints bar toggle button on the yt_clipper generic player page. Matches\n the scissors button's sizing/placement so the two read as a coherent\n pair next to the fullscreen control. */\n#hintsBarToggleButton.yt-clipper-hints-bar-button {\n cursor: pointer;\n position: relative;\n width: 40px;\n height: 100%;\n padding: 0;\n background: transparent;\n border: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: #fff;\n transition: filter 0.15s, transform 0.15s;\n}\n\n/* Scale the SVG down on yt_clipper specifically \u2014 Blender's STATUSBAR\n glyph fills its viewBox densely, so the 26 px size used on YouTube\n reads as oversized in the smaller vjs control bar. 0.6\xd7 brings it in\n line visually with the scissors button next to it. */\n#hintsBarToggleButton.yt-clipper-hints-bar-button svg {\n transform: scale(0.6);\n transform-origin: center center;\n transition: transform 0.15s;\n}\n\n#hintsBarToggleButton.yt-clipper-hints-bar-button:hover {\n filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.55)) brightness(1.2);\n}\n\n#hintsBarToggleButton.yt-clipper-hints-bar-button:hover svg {\n transform: scale(0.7);\n}\n";
var VideoPlatforms = /*#__PURE__*/ function(VideoPlatforms) {
VideoPlatforms["youtube"] = "youtube";
VideoPlatforms["vlive"] = "vlive";
VideoPlatforms["weverse"] = "weverse";
VideoPlatforms["naver_tv"] = "naver_tv";
VideoPlatforms["afreecatv"] = "afreecatv";
VideoPlatforms["yt_clipper"] = "ytc_generic";
return VideoPlatforms;
}({});
function getVideoPlatformHooks(selectors) {
return (0, _util.querySelectors)(selectors);
}
function getPlatform() {
const host = window.location.hostname;
if (host.includes('youtube')) return "youtube";
else if (host.includes('vlive')) return "vlive";
else if (host.includes('weverse')) return "weverse";
else if (host.includes('tv.naver')) return "naver_tv";
else if (host.includes('afreecatv.com')) return "afreecatv";
else if (host.includes('exwm.github.io') || host.includes('127.0.0.1') || host.includes('localhost')) return "ytc_generic";
else return "youtube";
}
const youtubeSelectors = {
playerContainer: '#ytd-player #container',
player: '#movie_player',
videoContainer: '#ytd-player #container',
video: 'video',
markersDiv: '.ytp-progress-bar',
theaterModeIndicator: 'ytd-watch-flexy',
progressBar: '.ytp-progress-bar',
settingsEditor: '#below',
settingsEditorTheater: '#full-bleed-container',
shortcutsTable: '#below',
frameCapturerProgressBar: '#below',
flashMessage: '#below',
cropOverlay: '.html5-video-container',
cropMouseManipulation: '.html5-video-container',
speedChartContainer: '.html5-video-container',
cropChartContainer: '#columns',
markerNumberingsDiv: '.ytp-progress-bar',
controls: '.ytp-chrome-bottom',
controlsGradient: '.ytp-gradient-bottom',
shortcutsTableButton: '.ytp-right-controls',
playerClickZone: '.html5-video-container'
};
const vliveSelectors = {
playerContainer: 'div[class*=player_area]',
player: 'div[id$="videoArea"]',
videoContainer: 'div[id$="videoArea"]',
video: 'video',
progressBar: '.u_rmc_progress_bar',
markersDiv: '.u_rmc_progress_bar',
theaterModeIndicator: 'placeholder',
settingsEditor: 'div[class*=player_area]',
settingsEditorTheater: 'div[class*=player_area]',
shortcutsTable: '[class*="video_title"]',
frameCapturerProgressBar: '[class*="video_title"]',
flashMessage: '[class*="video_title"]',
cropOverlay: 'div[id$="videoArea"]',
cropMouseManipulation: '._click_zone[data-video-overlay]',
speedChartContainer: '._click_zone[data-video-overlay]',
cropChartContainer: '[class*="video_title"]',
markerNumberingsDiv: '.u_rmc_progress_bar_container',
controls: '.u_rmcplayer_control',
controlsGradient: '.u_rmcplayer_control_bg._click_zone',
shortcutsTableButton: 'div[class*=video_content]',
playerClickZone: '._click_zone[data-video-overlay]'
};
const naver_tvSelectors = {
playerContainer: 'div[class=webplayer-internal-source-shadow]',
player: 'div[class=webplayer-internal-source-wrapper]',
playerClickZone: '.webplayer-internal-source-wrapper',
videoContainer: 'div[class=webplayer-internal-source-wrapper]',
video: 'video',
progressBar: '.pzp-pc__progress-slider',
markersDiv: '.pzp-ui-slider__wrap',
markerNumberingsDiv: '.pzp-ui-slider__wrap',
theaterModeIndicator: 'placeholder',
settingsEditor: 'div[class*=ArticleSection_article_section]',
settingsEditorTheater: 'div[class*=ArticleSection_article_section]',
shortcutsTable: 'div[class*=ArticleSection_article_section]',
frameCapturerProgressBar: 'div[class*=ArticleSection_article_section]',
flashMessage: 'div[class*=ArticleSection_article_section]',
cropOverlay: '.webplayer-internal-source-wrapper',
cropMouseManipulation: '.webplayer-internal-source-wrapper',
speedChartContainer: '.webplayer-internal-video',
cropChartContainer: 'div[class*=ArticleSection_article_section]',
controls: '.pzp-pc__bottom',
controlsGradient: '.pzp-pc__bottom-shadow',
shortcutsTableButton: '.pzp-pc__bottom-buttons-right'
};
const weverseSelectors = {
playerContainer: 'div[class=webplayer-internal-source-shadow]',
player: 'div[class=webplayer-internal-source-wrapper]',
playerClickZone: '.webplayer-internal-source-wrapper',
videoContainer: 'div[class=webplayer-internal-source-wrapper]',
video: 'video',
progressBar: '.pzp-pc__progress-slider',
markersDiv: '.pzp-pc__progress-slider',
markerNumberingsDiv: '.pzp-pc__progress-slider',
theaterModeIndicator: 'placeholder',
settingsEditor: 'div[class*=HeaderView_container]',
settingsEditorTheater: 'div[class*=HeaderView_container]',
shortcutsTable: 'div[class*="HeaderView_container"]',
frameCapturerProgressBar: 'div[class*="HeaderView_container"]',
flashMessage: 'div[class*="HeaderView_container"]',
cropOverlay: '.webplayer-internal-source-wrapper',
cropMouseManipulation: '.webplayer-internal-source-wrapper',
speedChartContainer: '.webplayer-internal-video',
cropChartContainer: 'div[class*="HeaderView_container"]',
controls: '.pzp-pc__bottom-buttons',
controlsGradient: '.pzp-pc__bottom-buttons',
shortcutsTableButton: '.pzp-pc__bottom-buttons-right'
};
const afreecaPlayerItemListSelector = 'div[class~=player_item_list]';
const afreecatvSelectors = {
playerContainer: 'div[class~=htmlplayer_wrap]',
player: 'div[id=afreecatv_player]',
playerClickZone: 'div[id=afreecatv_player]',
videoContainer: 'div[id=videoLayer]',
video: 'video[id=video]',
progressBar: 'div[class~=progress_track]',
markersDiv: 'div[class~=progress_track]',
markerNumberingsDiv: 'div[class~=progress_track]',
theaterModeIndicator: 'placeholder',
settingsEditor: afreecaPlayerItemListSelector,
settingsEditorTheater: afreecaPlayerItemListSelector,
shortcutsTable: afreecaPlayerItemListSelector,
frameCapturerProgressBar: afreecaPlayerItemListSelector,
flashMessage: afreecaPlayerItemListSelector,
cropOverlay: 'div[id=afreecatv_player]',
cropMouseManipulation: 'div[id=afreecatv_player]',
speedChartContainer: 'div[id=videoLayer]',
cropChartContainer: afreecaPlayerItemListSelector,
controls: 'div[class~=ctrl]',
controlsGradient: 'div[class~=ctrl]',
shortcutsTableButton: 'div[class~=right_ctrl]'
};
const ytclipperSelectors = {
playerContainer: 'div[id=ytc-media-player-container]',
player: '#my-video',
playerClickZone: 'div[id=ytc-media-player-container]',
videoContainer: 'div[id=ytc-media-player-container]',
video: 'video',
progressBar: '.vjs-progress-control',
markersDiv: '.vjs-progress-control',
markerNumberingsDiv: '.vjs-progress-control',
theaterModeIndicator: 'placeholder',
settingsEditor: '#ytc-editor',
settingsEditorTheater: '#ytc-editor',
shortcutsTable: '#ytc-editor',
frameCapturerProgressBar: '#ytc-editor',
flashMessage: '#ytc-editor',
cropOverlay: '#my-video',
cropMouseManipulation: '#my-video',
speedChartContainer: 'video',
cropChartContainer: '#ytc-editor',
controls: '.vjs-control-bar',
controlsGradient: '.vjs-control-bar',
shortcutsTableButton: '.vjs-fullscreen-control'
};
const youtubeData = {
selectors: youtubeSelectors,
css: youtubeCSS,
createNavObserver: (0, _navigation.createYouTubeNavObserver)
};
const vliveData = {
selectors: vliveSelectors,
css: vliveCSS,
createNavObserver: (0, _navigation.createHistoryApiNavObserver)
};
const weverseData = {
selectors: weverseSelectors,
css: weverseCSS,
createNavObserver: (0, _navigation.createHistoryApiNavObserver)
};
const naver_tvData = {
selectors: naver_tvSelectors,
css: naver_tvCSS,
createNavObserver: (0, _navigation.createHistoryApiNavObserver)
};
const afreecaData = {
selectors: afreecatvSelectors,
css: afreecatvCSS,
createNavObserver: (0, _navigation.createHistoryApiNavObserver)
};
const ytclipperData = {
selectors: ytclipperSelectors,
css: ytclipperCSS,
createNavObserver: (0, _navigation.createNoopNavObserver)
};
const videoPlatformDataRecords = {
["youtube"]: youtubeData,
["weverse"]: weverseData,
["vlive"]: vliveData,
["naver_tv"]: naver_tvData,
["afreecatv"]: afreecaData,
["ytc_generic"]: ytclipperData
};
},{"../util/util":"99arg","fs":"1LEPD","./navigation":"l9QMf","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"l9QMf":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "createYouTubeNavObserver", ()=>createYouTubeNavObserver);
parcelHelpers.export(exports, "createHistoryApiNavObserver", ()=>createHistoryApiNavObserver);
parcelHelpers.export(exports, "createNoopNavObserver", ()=>createNoopNavObserver);
parcelHelpers.export(exports, "isStaleVideo", ()=>isStaleVideo);
parcelHelpers.export(exports, "setIsStaleVideo", ()=>setIsStaleVideo);
parcelHelpers.export(exports, "getCurrentPageVideoID", ()=>getCurrentPageVideoID);
var _appState = require("../appState");
var _platforms = require("./platforms");
function createYouTubeNavObserver() {
let handler = null;
return {
start (onNavigate) {
handler = ()=>{
onNavigate();
};
document.addEventListener('yt-navigate-finish', handler);
},
stop () {
if (handler) {
document.removeEventListener('yt-navigate-finish', handler);
handler = null;
}
}
};
}
function createHistoryApiNavObserver() {
let userHandler = null;
let eventHandler = null;
let popstateHandler = null;
return {
start (onNavigate) {
userHandler = onNavigate;
installHistoryApiHook();
eventHandler = ()=>userHandler?.();
popstateHandler = ()=>userHandler?.();
window.addEventListener(LOCATION_CHANGE_EVENT, eventHandler);
window.addEventListener('popstate', popstateHandler);
},
stop () {
if (eventHandler) window.removeEventListener(LOCATION_CHANGE_EVENT, eventHandler);
if (popstateHandler) window.removeEventListener('popstate', popstateHandler);
eventHandler = null;
popstateHandler = null;
userHandler = null;
}
};
}
function createNoopNavObserver() {
return {
start () {},
stop () {}
};
}
const LOCATION_CHANGE_EVENT = 'ytc-locationchange';
let historyApiHookInstalled = false;
function installHistoryApiHook() {
if (historyApiHookInstalled) return;
historyApiHookInstalled = true;
const origPushState = history.pushState;
const origReplaceState = history.replaceState;
let lastHref = location.href;
const dispatchIfChanged = ()=>{
if (location.href !== lastHref) {
lastHref = location.href;
window.dispatchEvent(new Event(LOCATION_CHANGE_EVENT));
}
};
history.pushState = function(...args) {
const ret = origPushState.apply(this, args);
dispatchIfChanged();
return ret;
};
history.replaceState = function(...args) {
const ret = origReplaceState.apply(this, args);
dispatchIfChanged();
return ret;
};
}
let isStaleVideo = false;
function setIsStaleVideo(value) {
isStaleVideo = value;
}
function getCurrentPageVideoID() {
const platform = (0, _platforms.getPlatform)();
try {
if (platform === (0, _platforms.VideoPlatforms).youtube) {
const data = (0, _appState.appState).player?.getVideoData?.();
return data?.video_id ?? null;
} else if (platform === (0, _platforms.VideoPlatforms).vlive) {
const preloadedState = window.unsafeWindow?.__PRELOADED_STATE__;
const videoParams = preloadedState?.postDetail?.post?.officialVideo;
let id = videoParams?.videoSeq;
if (id == null && location.pathname.includes('video')) id = location.pathname.split('/')[2];
return id ?? null;
} else if (platform === (0, _platforms.VideoPlatforms).naver_tv) return location.pathname.split('/')[2] ?? null;
else if (platform === (0, _platforms.VideoPlatforms).weverse) {
if (location.pathname.includes('media') || location.pathname.includes('live')) return location.pathname.split('/')[3] ?? null;
return null;
} else if (platform === (0, _platforms.VideoPlatforms).yt_clipper) return 'unknown';
else if (platform === (0, _platforms.VideoPlatforms).afreecatv) return location.pathname.split('/')[2] ?? null;
} catch (e) {
console.error('yt_clipper: failed to read current page video id', e);
}
return null;
}
},{"../appState":"g0AlP","./platforms":"1kR7r","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"g0AlP":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "__version__", ()=>__version__);
parcelHelpers.export(exports, "appState", ()=>appState);
const __version__ = '5.44.0';
const appState = {
player: null,
video: null,
hooks: {},
settingsEditorHook: null,
markersSvg: null,
markersDiv: null,
markerNumberingsDiv: null,
selectedMarkerPairOverlay: null,
startMarkerNumberings: null,
endMarkerNumberings: null,
prevSelectedEndMarker: null,
markerPairs: [],
markerPairsHistory: [],
prevSelectedMarkerPairIndex: null,
settings: null,
videoInfo: {},
rotation: 0,
startTime: 0.0,
isReady: false,
isNextMarkerStart: true,
isHotkeysEnabled: false,
markerHotkeysEnabled: false,
isSettingsEditorOpen: false,
wasGlobalSettingsEditorOpen: false,
isCropOverlayVisible: false,
isCurrentChartVisible: false,
isChartEnabled: false,
isAutoHideUnselectedMarkerPairsOn: false,
isGammaPreviewOn: false,
isCropChartLoopingOn: false,
isAllPreviewsOn: false,
currentCropPointIndex: 0,
// Speed module shared state
speedInputLabel: null,
minterpFpsMulSuffixSpan: null,
speedInput: null,
easingMode: 'linear',
forceSetSpeedValue: 1,
isForceSetSpeedOn: false,
cropInputLabel: null,
cropInput: null,
cropAspectRatioSpan: null,
enableZoomPanInput: null
};
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"e23zj":[function(require,module,exports,__globalThis) {
/**
* Runtime feature flags for the markup userscript.
*
* Flip a flag to enable / disable a feature without removing its code
* from the bundle. Each flag is read at runtime by its consumer (a
* shortcut's `guard` callback, a conditional call site, etc.), so
* toggling means a one-line source edit and a rebundle — the script's
* surface area changes but no code disappears.
*
* Add a flag when a feature is wired up end-to-end but not yet ready to
* expose by default — typically because its data format is still in
* flux, the UX needs more iteration, or the implementation is gated on
* an external dependency that isn't deployed.
*/ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "featureFlags", ()=>featureFlags);
const featureFlags = {
/** Shareable-URL save / load.
* - Anchors the Shift+S "Share" chip in the Data group of the hints
* bar (`copyShareableUrl` shortcut).
* - Auto-loads markers from a `?share=...` URL fragment on init
* via `tryLoadSharedMarkers()`.
* Off by default — the embedded-markers URL format is still
* iterating and shared links from one bundle version may not parse
* cleanly in another. */ shareLink: false
};
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"kg6wl":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "hideElements", ()=>hideElements);
parcelHelpers.export(exports, "showElements", ()=>showElements);
parcelHelpers.export(exports, "enableCommonBlockers", ()=>enableCommonBlockers);
parcelHelpers.export(exports, "disableCommonBlockers", ()=>disableCommonBlockers);
function hideElements(...selectors) {
for (const sel of selectors){
const el = document.querySelector(sel);
if (el) el.style.display = 'none';
}
}
function showElements(...selectors) {
for (const sel of selectors){
const el = document.querySelector(sel);
if (el) el.style.removeProperty('display');
}
}
function enableCommonBlockers() {
enablePreventMouseZoom();
enablePreventSpaceScroll();
}
function disableCommonBlockers() {
disablePreventMouseZoom();
disablePreventSpaceScroll();
}
function enablePreventMouseZoom() {
window.addEventListener('mousewheel', stopWheelZoom, {
passive: false
});
window.addEventListener('DOMMouseScroll', stopWheelZoom, {
passive: false
});
}
function disablePreventMouseZoom() {
window.removeEventListener('mousewheel', stopWheelZoom);
window.removeEventListener('DOMMouseScroll', stopWheelZoom);
}
function stopWheelZoom(e) {
if (e.ctrlKey || e.shiftKey || e.altKey) e.preventDefault();
}
function enablePreventSpaceScroll() {
window.addEventListener('keydown', preventSpaceScrollHandler);
}
function disablePreventSpaceScroll() {
window.removeEventListener('keydown', preventSpaceScrollHandler);
}
function preventSpaceScrollHandler(e) {
if (e.code === 'Space' && e.target == document.body && !e.ctrlKey && !e.shiftKey && !e.altKey) e.preventDefault();
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"lEer5":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "enableYTBlockers", ()=>enableYTBlockers);
parcelHelpers.export(exports, "disableYTBlockers", ()=>disableYTBlockers);
var _common = require("./common");
const hiddenOnActivation = [
'.ytp-overlays-container'
];
function enableYTBlockers() {
enablePreventSideBarPull();
enablePreventAltDefault();
(0, _common.hideElements)(...hiddenOnActivation);
}
function disableYTBlockers() {
disablePreventSideBarPull();
disablePreventAltDefault();
(0, _common.showElements)(...hiddenOnActivation);
}
function enablePreventAltDefault() {
window.addEventListener('keyup', preventAltDefaultHandler, true);
}
function disablePreventAltDefault() {
window.removeEventListener('keyup', preventAltDefaultHandler, true);
}
function enablePreventSideBarPull() {
const sideBar = document.getElementById('contentContainer');
const sideBarContent = document.getElementById('guide-content');
if (sideBarContent) sideBarContent.style.pointerEvents = 'auto';
if (sideBar != null) sideBar.style.pointerEvents = 'none';
}
function disablePreventSideBarPull() {
const sideBar = document.getElementById('contentContainer');
if (sideBar != null) sideBar.style.removeProperty('pointer-events');
}
function preventAltDefaultHandler(e) {
if (e.code === 'AltLeft' && !e.ctrlKey && !e.shiftKey) e.preventDefault();
}
},{"./common":"kg6wl","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"1JMOe":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "createRounder", ()=>createRounder);
parcelHelpers.export(exports, "roundValue", ()=>roundValue);
var _chartJs = require("chart.js");
var _chartJsDefault = parcelHelpers.interopDefault(_chartJs);
var _d3Drag = require("d3-drag");
var _d3Selection = require("d3-selection");
var _immer = require("immer");
var _appState = require("../../appState");
let element, scale, scaleX, radar;
function getElement(chartInstance, callback) {
return ()=>{
if (0, _d3Selection.event) {
const e = (0, _d3Selection.event).sourceEvent;
element = chartInstance.getElementAtEvent(e)[0];
radar = chartInstance.config.type == 'radar';
const scaleName = radar ? '_scale' : '_yScale';
if (element) {
if (chartInstance.data.datasets[element._datasetIndex].dragData === false || element[scaleName].options.dragData === false) {
element = null;
return;
}
scale = element[scaleName].id;
if (element._xScale) scaleX = element._xScale.id;
if (typeof callback === 'function' && element) {
const datasetIndex = element._datasetIndex;
const index = element._index;
const value = chartInstance.data.datasets[datasetIndex].data[index];
if (callback(e, chartInstance, element, value) === false) element = null;
}
}
}
};
}
function createRounder(multiple, precision) {
return (value)=>{
const roundedValue = Math.round(value / multiple) * multiple;
const roundedValueFixedPrecision = +roundedValue.toFixed(precision);
return roundedValueFixedPrecision;
};
}
function roundValue(value, multiple, precision) {
return createRounder(multiple, precision)(value);
}
function updateData(chartInstance, callback) {
return ()=>{
if (element && (0, _d3Selection.event)) {
const e = (0, _d3Selection.event).sourceEvent;
const datasetIndex = element._datasetIndex;
const index = element._index;
const roundMultipleX = chartInstance.options.dragDataRoundMultipleX;
const roundPrecisionX = chartInstance.options.dragDataRoundPrecisionX;
const roundMultipleY = chartInstance.options.dragDataRoundMultipleY;
const roundPrecisionY = chartInstance.options.dragDataRoundPrecisionY;
const roundX = createRounder(roundMultipleX, roundPrecisionX);
const roundY = createRounder(roundMultipleY, roundPrecisionY);
let x;
let y;
const initialState = chartInstance.data.datasets[datasetIndex].data;
const dataRef = (0, _immer.createDraft)(initialState);
let datumRef = dataRef[index];
let proposedDatum = {
x: datumRef.x,
y: datumRef.y
};
if (radar) {
let v;
if (e.touches) {
x = e.touches[0].clientX - chartInstance.canvas.getBoundingClientRect().left;
y = e.touches[0].clientY - chartInstance.canvas.getBoundingClientRect().top;
} else {
x = e.clientX - chartInstance.canvas.getBoundingClientRect().left;
y = e.clientY - chartInstance.canvas.getBoundingClientRect().top;
}
const rScale = chartInstance.scales[scale];
const d = Math.sqrt(Math.pow(x - rScale.xCenter, 2) + Math.pow(y - rScale.yCenter, 2));
const scalingFactor = rScale.drawingArea / (rScale.max - rScale.min);
if (rScale.options.ticks.reverse) v = rScale.max - d / scalingFactor;
else v = rScale.min + d / scalingFactor;
v = roundValue(v, chartInstance.options.dragDataRound, 2);
v = Math.min(v, chartInstance.scale.max);
v = Math.max(v, chartInstance.scale.min);
proposedDatum = v;
} else {
if (e.touches) {
x = chartInstance.scales[scaleX].getValueForPixel(e.touches[0].clientX - chartInstance.canvas.getBoundingClientRect().left);
y = chartInstance.scales[scale].getValueForPixel(e.touches[0].clientY - chartInstance.canvas.getBoundingClientRect().top);
} else {
x = chartInstance.scales[scaleX].getValueForPixel(e.clientX - chartInstance.canvas.getBoundingClientRect().left);
y = chartInstance.scales[scale].getValueForPixel(e.clientY - chartInstance.canvas.getBoundingClientRect().top);
}
x = roundX(x);
y = roundY(y);
x = Math.min(x, chartInstance.scales[scaleX].max);
x = Math.max(x, chartInstance.scales[scaleX].min);
y = Math.min(y, chartInstance.scales[scale].max);
y = Math.max(y, chartInstance.scales[scale].min);
proposedDatum.x = x;
if (datumRef.y !== undefined) proposedDatum.y = y;
else proposedDatum = y;
}
let shouldChartUpdateX = chartInstance.options.dragX && datumRef.x !== undefined;
let shouldChartUpdateY = chartInstance.options.dragY;
let shouldChartUpdate;
if (typeof callback === 'function') {
shouldChartUpdate = callback(e, chartInstance, datasetIndex, index, datumRef, proposedDatum);
shouldChartUpdateX = shouldChartUpdateX && shouldChartUpdate.dragX;
shouldChartUpdateY = shouldChartUpdateY && shouldChartUpdate.dragY;
}
if (shouldChartUpdateX !== false) datumRef.x = proposedDatum.x;
if (shouldChartUpdateY !== false) {
if (datumRef.y !== undefined) datumRef.y = proposedDatum.y;
else datumRef = proposedDatum; // eslint-disable-line no-useless-assignment
}
const newState = (0, _immer.finishDraft)(dataRef);
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
chartInstance.data.datasets[datasetIndex].data = newState;
shouldChartUpdate.chartType === 'crop' ? markerPair.cropMap = newState : markerPair.speedMap = newState;
if (shouldChartUpdateX !== false || shouldChartUpdateY !== false) chartInstance.update(0);
}
};
}
function dragEndCallback(chartInstance, callback) {
return ()=>{
try {
if (typeof callback === 'function' && element) {
const e = (0, _d3Selection.event).sourceEvent;
const datasetIndex = element._datasetIndex;
const index = element._index;
const value = chartInstance.data.datasets[datasetIndex].data[index];
return callback(e, chartInstance, datasetIndex, index, value);
}
} finally{
// Drop references to the dragged datum and its scales so the
// plugin's module globals don't keep them alive between drags.
// Without this, `element` retains a pointer to the chart's last
// dragged element until the next `start` event overwrites it,
// which both leaks memory and could let a stale value leak into
// `updateData` if `event` were somehow re-entered.
element = null;
scale = null;
scaleX = null;
radar = null;
}
};
}
const ChartJSdragDataPlugin = {
beforeDatasetsUpdate: function(chartInstance) {
if (chartInstance.options.dragData) (0, _d3Selection.select)(chartInstance.chart.canvas).call((0, _d3Drag.drag)().container(chartInstance.chart.canvas).on('start', getElement(chartInstance, chartInstance.options.onDragStart)).on('drag', updateData(chartInstance, chartInstance.options.onDrag)).on('end', dragEndCallback(chartInstance, chartInstance.options.onDragEnd)));
}
};
(0, _chartJsDefault.default).pluginService.register(ChartJSdragDataPlugin);
exports.default = ChartJSdragDataPlugin;
},{"chart.js":"kS68a","d3-drag":[["drag","8rFxr","default"]],"d3-selection":[["event","7QdqF"],["select","7dO6e","default"]],"immer":"R6AMM","../../appState":"g0AlP","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"kS68a":[function(require,module,exports,__globalThis) {
module.exports = Chart;
},{}],"8rFxr":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
var filter = defaultFilter, container = defaultContainer, subject = defaultSubject, touchable = defaultTouchable, gestures = {}, listeners = (0, _d3Dispatch.dispatch)("start", "drag", "end"), active = 0, mousedownx, mousedowny, mousemoving, touchending, clickDistance2 = 0;
function drag(selection) {
selection.on("mousedown.drag", mousedowned).filter(touchable).on("touchstart.drag", touchstarted).on("touchmove.drag", touchmoved).on("touchend.drag touchcancel.drag", touchended).style("touch-action", "none").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)");
}
function mousedowned() {
if (touchending || !filter.apply(this, arguments)) return;
var gesture = beforestart("mouse", container.apply(this, arguments), (0, _d3Selection.mouse), this, arguments);
if (!gesture) return;
(0, _d3Selection.select)((0, _d3Selection.event).view).on("mousemove.drag", mousemoved, true).on("mouseup.drag", mouseupped, true);
(0, _nodragJsDefault.default)((0, _d3Selection.event).view);
(0, _noeventJs.nopropagation)();
mousemoving = false;
mousedownx = (0, _d3Selection.event).clientX;
mousedowny = (0, _d3Selection.event).clientY;
gesture("start");
}
function mousemoved() {
(0, _noeventJsDefault.default)();
if (!mousemoving) {
var dx = (0, _d3Selection.event).clientX - mousedownx, dy = (0, _d3Selection.event).clientY - mousedowny;
mousemoving = dx * dx + dy * dy > clickDistance2;
}
gestures.mouse("drag");
}
function mouseupped() {
(0, _d3Selection.select)((0, _d3Selection.event).view).on("mousemove.drag mouseup.drag", null);
(0, _nodragJs.yesdrag)((0, _d3Selection.event).view, mousemoving);
(0, _noeventJsDefault.default)();
gestures.mouse("end");
}
function touchstarted() {
if (!filter.apply(this, arguments)) return;
var touches = (0, _d3Selection.event).changedTouches, c = container.apply(this, arguments), n = touches.length, i, gesture;
for(i = 0; i < n; ++i)if (gesture = beforestart(touches[i].identifier, c, (0, _d3Selection.touch), this, arguments)) {
(0, _noeventJs.nopropagation)();
gesture("start");
}
}
function touchmoved() {
var touches = (0, _d3Selection.event).changedTouches, n = touches.length, i, gesture;
for(i = 0; i < n; ++i)if (gesture = gestures[touches[i].identifier]) {
(0, _noeventJsDefault.default)();
gesture("drag");
}
}
function touchended() {
var touches = (0, _d3Selection.event).changedTouches, n = touches.length, i, gesture;
if (touchending) clearTimeout(touchending);
touchending = setTimeout(function() {
touchending = null;
}, 500); // Ghost clicks are delayed!
for(i = 0; i < n; ++i)if (gesture = gestures[touches[i].identifier]) {
(0, _noeventJs.nopropagation)();
gesture("end");
}
}
function beforestart(id, container, point, that, args) {
var p = point(container, id), s, dx, dy, sublisteners = listeners.copy();
if (!(0, _d3Selection.customEvent)(new (0, _eventJsDefault.default)(drag, "beforestart", s, id, active, p[0], p[1], 0, 0, sublisteners), function() {
if (((0, _d3Selection.event).subject = s = subject.apply(that, args)) == null) return false;
dx = s.x - p[0] || 0;
dy = s.y - p[1] || 0;
return true;
})) return;
return function gesture(type) {
var p0 = p, n;
switch(type){
case "start":
gestures[id] = gesture, n = active++;
break;
case "end":
delete gestures[id], --active; // nobreak
case "drag":
p = point(container, id), n = active;
break;
}
(0, _d3Selection.customEvent)(new (0, _eventJsDefault.default)(drag, type, s, id, n, p[0] + dx, p[1] + dy, p[0] - p0[0], p[1] - p0[1], sublisteners), sublisteners.apply, sublisteners, [
type,
that,
args
]);
};
}
drag.filter = function(_) {
return arguments.length ? (filter = typeof _ === "function" ? _ : (0, _constantJsDefault.default)(!!_), drag) : filter;
};
drag.container = function(_) {
return arguments.length ? (container = typeof _ === "function" ? _ : (0, _constantJsDefault.default)(_), drag) : container;
};
drag.subject = function(_) {
return arguments.length ? (subject = typeof _ === "function" ? _ : (0, _constantJsDefault.default)(_), drag) : subject;
};
drag.touchable = function(_) {
return arguments.length ? (touchable = typeof _ === "function" ? _ : (0, _constantJsDefault.default)(!!_), drag) : touchable;
};
drag.on = function() {
var value = listeners.on.apply(listeners, arguments);
return value === listeners ? drag : value;
};
drag.clickDistance = function(_) {
return arguments.length ? (clickDistance2 = (_ = +_) * _, drag) : Math.sqrt(clickDistance2);
};
return drag;
});
var _d3Dispatch = require("d3-dispatch");
var _d3Selection = require("d3-selection");
var _nodragJs = require("./nodrag.js");
var _nodragJsDefault = parcelHelpers.interopDefault(_nodragJs);
var _noeventJs = require("./noevent.js");
var _noeventJsDefault = parcelHelpers.interopDefault(_noeventJs);
var _constantJs = require("./constant.js");
var _constantJsDefault = parcelHelpers.interopDefault(_constantJs);
var _eventJs = require("./event.js");
var _eventJsDefault = parcelHelpers.interopDefault(_eventJs);
// Ignore right-click, since that should open the context menu.
function defaultFilter() {
return !(0, _d3Selection.event).ctrlKey && !(0, _d3Selection.event).button;
}
function defaultContainer() {
return this.parentNode;
}
function defaultSubject(d) {
return d == null ? {
x: (0, _d3Selection.event).x,
y: (0, _d3Selection.event).y
} : d;
}
function defaultTouchable() {
return navigator.maxTouchPoints || "ontouchstart" in this;
}
},{"d3-dispatch":[["dispatch","9QtTQ","default"]],"d3-selection":[["customEvent","7QdqF"],["event","7QdqF"],["mouse","e7xv3","default"],["select","7dO6e","default"],["touch","1y2wU","default"]],"./nodrag.js":"Zk6MG","./noevent.js":"7bjIj","./constant.js":"e8QfU","./event.js":"cnUOG","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"9QtTQ":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var noop = {
value: function() {}
};
function dispatch() {
for(var i = 0, n = arguments.length, _ = {}, t; i < n; ++i){
if (!(t = arguments[i] + "") || t in _ || /[\s.]/.test(t)) throw new Error("illegal type: " + t);
_[t] = [];
}
return new Dispatch(_);
}
function Dispatch(_) {
this._ = _;
}
function parseTypenames(typenames, types) {
return typenames.trim().split(/^|\s+/).map(function(t) {
var name = "", i = t.indexOf(".");
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
return {
type: t,
name: name
};
});
}
Dispatch.prototype = dispatch.prototype = {
constructor: Dispatch,
on: function(typename, callback) {
var _ = this._, T = parseTypenames(typename + "", _), t, i = -1, n = T.length;
// If no callback was specified, return the callback of the given type and name.
if (arguments.length < 2) {
while(++i < n)if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;
return;
}
// If a type was specified, set the callback for the given type and name.
// Otherwise, if a null callback was specified, remove callbacks of the given name.
if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);
while(++i < n){
if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);
else if (callback == null) for(t in _)_[t] = set(_[t], typename.name, null);
}
return this;
},
copy: function() {
var copy = {}, _ = this._;
for(var t in _)copy[t] = _[t].slice();
return new Dispatch(copy);
},
call: function(type, that) {
if ((n = arguments.length - 2) > 0) for(var args = new Array(n), i = 0, n, t; i < n; ++i)args[i] = arguments[i + 2];
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
for(t = this._[type], i = 0, n = t.length; i < n; ++i)t[i].value.apply(that, args);
},
apply: function(type, that, args) {
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
for(var t = this._[type], i = 0, n = t.length; i < n; ++i)t[i].value.apply(that, args);
}
};
function get(type, name) {
for(var i = 0, n = type.length, c; i < n; ++i){
if ((c = type[i]).name === name) return c.value;
}
}
function set(type, name, callback) {
for(var i = 0, n = type.length; i < n; ++i)if (type[i].name === name) {
type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1));
break;
}
if (callback != null) type.push({
name: name,
value: callback
});
return type;
}
exports.default = dispatch;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"7QdqF":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "event", ()=>event);
parcelHelpers.export(exports, "default", ()=>function(typename, value, capture) {
var typenames = parseTypenames(typename + ""), i, n = typenames.length, t;
if (arguments.length < 2) {
var on = this.node().__on;
if (on) for(var j = 0, m = on.length, o; j < m; ++j)for(i = 0, o = on[j]; i < n; ++i){
if ((t = typenames[i]).type === o.type && t.name === o.name) return o.value;
}
return;
}
on = value ? onAdd : onRemove;
if (capture == null) capture = false;
for(i = 0; i < n; ++i)this.each(on(typenames[i], value, capture));
return this;
});
parcelHelpers.export(exports, "customEvent", ()=>customEvent);
var filterEvents = {};
var event = null;
if (typeof document !== "undefined") {
var element = document.documentElement;
if (!("onmouseenter" in element)) filterEvents = {
mouseenter: "mouseover",
mouseleave: "mouseout"
};
}
function filterContextListener(listener, index, group) {
listener = contextListener(listener, index, group);
return function(event) {
var related = event.relatedTarget;
if (!related || related !== this && !(related.compareDocumentPosition(this) & 8)) listener.call(this, event);
};
}
function contextListener(listener, index, group) {
return function(event1) {
var event0 = event; // Events can be reentrant (e.g., focus).
event = event1;
try {
listener.call(this, this.__data__, index, group);
} finally{
event = event0;
}
};
}
function parseTypenames(typenames) {
return typenames.trim().split(/^|\s+/).map(function(t) {
var name = "", i = t.indexOf(".");
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
return {
type: t,
name: name
};
});
}
function onRemove(typename) {
return function() {
var on = this.__on;
if (!on) return;
for(var j = 0, i = -1, m = on.length, o; j < m; ++j)if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) this.removeEventListener(o.type, o.listener, o.capture);
else on[++i] = o;
if (++i) on.length = i;
else delete this.__on;
};
}
function onAdd(typename, value, capture) {
var wrap = filterEvents.hasOwnProperty(typename.type) ? filterContextListener : contextListener;
return function(d, i, group) {
var on = this.__on, o, listener = wrap(value, i, group);
if (on) {
for(var j = 0, m = on.length; j < m; ++j)if ((o = on[j]).type === typename.type && o.name === typename.name) {
this.removeEventListener(o.type, o.listener, o.capture);
this.addEventListener(o.type, o.listener = listener, o.capture = capture);
o.value = value;
return;
}
}
this.addEventListener(typename.type, listener, capture);
o = {
type: typename.type,
name: typename.name,
value: value,
listener: listener,
capture: capture
};
if (!on) this.__on = [
o
];
else on.push(o);
};
}
function customEvent(event1, listener, that, args) {
var event0 = event;
event1.sourceEvent = event;
event = event1;
try {
return listener.apply(that, args);
} finally{
event = event0;
}
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"e7xv3":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(node) {
var event = (0, _sourceEventDefault.default)();
if (event.changedTouches) event = event.changedTouches[0];
return (0, _pointDefault.default)(node, event);
});
var _sourceEvent = require("./sourceEvent");
var _sourceEventDefault = parcelHelpers.interopDefault(_sourceEvent);
var _point = require("./point");
var _pointDefault = parcelHelpers.interopDefault(_point);
},{"./sourceEvent":"8962w","./point":"i2cnY","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"8962w":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
var current = (0, _on.event), source;
while(source = current.sourceEvent)current = source;
return current;
});
var _on = require("./selection/on");
},{"./selection/on":"7QdqF","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"i2cnY":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(node, event) {
var svg = node.ownerSVGElement || node;
if (svg.createSVGPoint) {
var point = svg.createSVGPoint();
point.x = event.clientX, point.y = event.clientY;
point = point.matrixTransform(node.getScreenCTM().inverse());
return [
point.x,
point.y
];
}
var rect = node.getBoundingClientRect();
return [
event.clientX - rect.left - node.clientLeft,
event.clientY - rect.top - node.clientTop
];
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"7dO6e":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(selector) {
return typeof selector === "string" ? new (0, _index.Selection)([
[
document.querySelector(selector)
]
], [
document.documentElement
]) : new (0, _index.Selection)([
[
selector
]
], (0, _index.root));
});
var _index = require("./selection/index");
},{"./selection/index":"3jECc","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"3jECc":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "root", ()=>root);
parcelHelpers.export(exports, "Selection", ()=>Selection);
var _select = require("./select");
var _selectDefault = parcelHelpers.interopDefault(_select);
var _selectAll = require("./selectAll");
var _selectAllDefault = parcelHelpers.interopDefault(_selectAll);
var _filter = require("./filter");
var _filterDefault = parcelHelpers.interopDefault(_filter);
var _data = require("./data");
var _dataDefault = parcelHelpers.interopDefault(_data);
var _enter = require("./enter");
var _enterDefault = parcelHelpers.interopDefault(_enter);
var _exit = require("./exit");
var _exitDefault = parcelHelpers.interopDefault(_exit);
var _join = require("./join");
var _joinDefault = parcelHelpers.interopDefault(_join);
var _merge = require("./merge");
var _mergeDefault = parcelHelpers.interopDefault(_merge);
var _order = require("./order");
var _orderDefault = parcelHelpers.interopDefault(_order);
var _sort = require("./sort");
var _sortDefault = parcelHelpers.interopDefault(_sort);
var _call = require("./call");
var _callDefault = parcelHelpers.interopDefault(_call);
var _nodes = require("./nodes");
var _nodesDefault = parcelHelpers.interopDefault(_nodes);
var _node = require("./node");
var _nodeDefault = parcelHelpers.interopDefault(_node);
var _size = require("./size");
var _sizeDefault = parcelHelpers.interopDefault(_size);
var _empty = require("./empty");
var _emptyDefault = parcelHelpers.interopDefault(_empty);
var _each = require("./each");
var _eachDefault = parcelHelpers.interopDefault(_each);
var _attr = require("./attr");
var _attrDefault = parcelHelpers.interopDefault(_attr);
var _style = require("./style");
var _styleDefault = parcelHelpers.interopDefault(_style);
var _property = require("./property");
var _propertyDefault = parcelHelpers.interopDefault(_property);
var _classed = require("./classed");
var _classedDefault = parcelHelpers.interopDefault(_classed);
var _text = require("./text");
var _textDefault = parcelHelpers.interopDefault(_text);
var _html = require("./html");
var _htmlDefault = parcelHelpers.interopDefault(_html);
var _raise = require("./raise");
var _raiseDefault = parcelHelpers.interopDefault(_raise);
var _lower = require("./lower");
var _lowerDefault = parcelHelpers.interopDefault(_lower);
var _append = require("./append");
var _appendDefault = parcelHelpers.interopDefault(_append);
var _insert = require("./insert");
var _insertDefault = parcelHelpers.interopDefault(_insert);
var _remove = require("./remove");
var _removeDefault = parcelHelpers.interopDefault(_remove);
var _clone = require("./clone");
var _cloneDefault = parcelHelpers.interopDefault(_clone);
var _datum = require("./datum");
var _datumDefault = parcelHelpers.interopDefault(_datum);
var _on = require("./on");
var _onDefault = parcelHelpers.interopDefault(_on);
var _dispatch = require("./dispatch");
var _dispatchDefault = parcelHelpers.interopDefault(_dispatch);
var root = [
null
];
function Selection(groups, parents) {
this._groups = groups;
this._parents = parents;
}
function selection() {
return new Selection([
[
document.documentElement
]
], root);
}
Selection.prototype = selection.prototype = {
constructor: Selection,
select: (0, _selectDefault.default),
selectAll: (0, _selectAllDefault.default),
filter: (0, _filterDefault.default),
data: (0, _dataDefault.default),
enter: (0, _enterDefault.default),
exit: (0, _exitDefault.default),
join: (0, _joinDefault.default),
merge: (0, _mergeDefault.default),
order: (0, _orderDefault.default),
sort: (0, _sortDefault.default),
call: (0, _callDefault.default),
nodes: (0, _nodesDefault.default),
node: (0, _nodeDefault.default),
size: (0, _sizeDefault.default),
empty: (0, _emptyDefault.default),
each: (0, _eachDefault.default),
attr: (0, _attrDefault.default),
style: (0, _styleDefault.default),
property: (0, _propertyDefault.default),
classed: (0, _classedDefault.default),
text: (0, _textDefault.default),
html: (0, _htmlDefault.default),
raise: (0, _raiseDefault.default),
lower: (0, _lowerDefault.default),
append: (0, _appendDefault.default),
insert: (0, _insertDefault.default),
remove: (0, _removeDefault.default),
clone: (0, _cloneDefault.default),
datum: (0, _datumDefault.default),
on: (0, _onDefault.default),
dispatch: (0, _dispatchDefault.default)
};
exports.default = selection;
},{"./select":"2UL9b","./selectAll":"Gyzh0","./filter":"cdoc4","./data":"8mXSq","./enter":"4gnL7","./exit":"7MjAO","./join":"5pvZ0","./merge":"gtCuS","./order":"kDp8G","./sort":"kpVdV","./call":"iHWbx","./nodes":"4ujY4","./node":"2xSCI","./size":"cXiKc","./empty":"8jhUp","./each":"gBDNU","./attr":"ijmec","./style":"7IIUD","./property":"eAZmL","./classed":"akczj","./text":"4SPlq","./html":"46ETY","./raise":"5L2a7","./lower":"6hwkt","./append":"5t2Uy","./insert":"k6Ecq","./remove":"iWeo0","./clone":"gawOJ","./datum":"fLeKH","./on":"7QdqF","./dispatch":"kUHse","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"2UL9b":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(select) {
if (typeof select !== "function") select = (0, _selectorDefault.default)(select);
for(var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j){
for(var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i)if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
if ("__data__" in node) subnode.__data__ = node.__data__;
subgroup[i] = subnode;
}
}
return new (0, _index.Selection)(subgroups, this._parents);
});
var _index = require("./index");
var _selector = require("../selector");
var _selectorDefault = parcelHelpers.interopDefault(_selector);
},{"./index":"3jECc","../selector":"43ZJy","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"43ZJy":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(selector) {
return selector == null ? none : function() {
return this.querySelector(selector);
};
});
function none() {}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"Gyzh0":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(select) {
if (typeof select !== "function") select = (0, _selectorAllDefault.default)(select);
for(var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j){
for(var group = groups[j], n = group.length, node, i = 0; i < n; ++i)if (node = group[i]) {
subgroups.push(select.call(node, node.__data__, i, group));
parents.push(node);
}
}
return new (0, _index.Selection)(subgroups, parents);
});
var _index = require("./index");
var _selectorAll = require("../selectorAll");
var _selectorAllDefault = parcelHelpers.interopDefault(_selectorAll);
},{"./index":"3jECc","../selectorAll":"8Z6rM","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"8Z6rM":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(selector) {
return selector == null ? empty : function() {
return this.querySelectorAll(selector);
};
});
function empty() {
return [];
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"cdoc4":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(match) {
if (typeof match !== "function") match = (0, _matcherDefault.default)(match);
for(var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j){
for(var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i)if ((node = group[i]) && match.call(node, node.__data__, i, group)) subgroup.push(node);
}
return new (0, _index.Selection)(subgroups, this._parents);
});
var _index = require("./index");
var _matcher = require("../matcher");
var _matcherDefault = parcelHelpers.interopDefault(_matcher);
},{"./index":"3jECc","../matcher":"780Tc","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"780Tc":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(selector) {
return function() {
return this.matches(selector);
};
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"8mXSq":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(value, key) {
if (!value) {
data = new Array(this.size()), j = -1;
this.each(function(d) {
data[++j] = d;
});
return data;
}
var bind = key ? bindKey : bindIndex, parents = this._parents, groups = this._groups;
if (typeof value !== "function") value = (0, _constantDefault.default)(value);
for(var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j){
var parent = parents[j], group = groups[j], groupLength = group.length, data = value.call(parent, parent && parent.__data__, j, parents), dataLength = data.length, enterGroup = enter[j] = new Array(dataLength), updateGroup = update[j] = new Array(dataLength), exitGroup = exit[j] = new Array(groupLength);
bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);
// Now connect the enter nodes to their following update node, such that
// appendChild can insert the materialized enter node before this node,
// rather than at the end of the parent node.
for(var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0)if (previous = enterGroup[i0]) {
if (i0 >= i1) i1 = i0 + 1;
while(!(next = updateGroup[i1]) && ++i1 < dataLength);
previous._next = next || null;
}
}
update = new (0, _index.Selection)(update, parents);
update._enter = enter;
update._exit = exit;
return update;
});
var _index = require("./index");
var _enter = require("./enter");
var _constant = require("../constant");
var _constantDefault = parcelHelpers.interopDefault(_constant);
var keyPrefix = "$"; // Protect against keys like “__proto__”.
function bindIndex(parent, group, enter, update, exit, data) {
var i = 0, node, groupLength = group.length, dataLength = data.length;
// Put any non-null nodes that fit into update.
// Put any null nodes into enter.
// Put any remaining data into enter.
for(; i < dataLength; ++i)if (node = group[i]) {
node.__data__ = data[i];
update[i] = node;
} else enter[i] = new (0, _enter.EnterNode)(parent, data[i]);
// Put any non-null nodes that don’t fit into exit.
for(; i < groupLength; ++i)if (node = group[i]) exit[i] = node;
}
function bindKey(parent, group, enter, update, exit, data, key) {
var i, node, nodeByKeyValue = {}, groupLength = group.length, dataLength = data.length, keyValues = new Array(groupLength), keyValue;
// Compute the key for each node.
// If multiple nodes have the same key, the duplicates are added to exit.
for(i = 0; i < groupLength; ++i)if (node = group[i]) {
keyValues[i] = keyValue = keyPrefix + key.call(node, node.__data__, i, group);
if (keyValue in nodeByKeyValue) exit[i] = node;
else nodeByKeyValue[keyValue] = node;
}
// Compute the key for each datum.
// If there a node associated with this key, join and add it to update.
// If there is not (or the key is a duplicate), add it to enter.
for(i = 0; i < dataLength; ++i){
keyValue = keyPrefix + key.call(parent, data[i], i, data);
if (node = nodeByKeyValue[keyValue]) {
update[i] = node;
node.__data__ = data[i];
nodeByKeyValue[keyValue] = null;
} else enter[i] = new (0, _enter.EnterNode)(parent, data[i]);
}
// Add any remaining nodes that were not bound to data to exit.
for(i = 0; i < groupLength; ++i)if ((node = group[i]) && nodeByKeyValue[keyValues[i]] === node) exit[i] = node;
}
},{"./index":"3jECc","./enter":"4gnL7","../constant":"9ITZr","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"4gnL7":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
return new (0, _index.Selection)(this._enter || this._groups.map((0, _sparseDefault.default)), this._parents);
});
parcelHelpers.export(exports, "EnterNode", ()=>EnterNode);
var _sparse = require("./sparse");
var _sparseDefault = parcelHelpers.interopDefault(_sparse);
var _index = require("./index");
function EnterNode(parent, datum) {
this.ownerDocument = parent.ownerDocument;
this.namespaceURI = parent.namespaceURI;
this._next = null;
this._parent = parent;
this.__data__ = datum;
}
EnterNode.prototype = {
constructor: EnterNode,
appendChild: function(child) {
return this._parent.insertBefore(child, this._next);
},
insertBefore: function(child, next) {
return this._parent.insertBefore(child, next);
},
querySelector: function(selector) {
return this._parent.querySelector(selector);
},
querySelectorAll: function(selector) {
return this._parent.querySelectorAll(selector);
}
};
},{"./sparse":"acE4J","./index":"3jECc","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"acE4J":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(update) {
return new Array(update.length);
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"9ITZr":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(x) {
return function() {
return x;
};
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"7MjAO":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
return new (0, _index.Selection)(this._exit || this._groups.map((0, _sparseDefault.default)), this._parents);
});
var _sparse = require("./sparse");
var _sparseDefault = parcelHelpers.interopDefault(_sparse);
var _index = require("./index");
},{"./sparse":"acE4J","./index":"3jECc","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"5pvZ0":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(onenter, onupdate, onexit) {
var enter = this.enter(), update = this, exit = this.exit();
enter = typeof onenter === "function" ? onenter(enter) : enter.append(onenter + "");
if (onupdate != null) update = onupdate(update);
if (onexit == null) exit.remove();
else onexit(exit);
return enter && update ? enter.merge(update).order() : update;
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"gtCuS":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(selection) {
for(var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j){
for(var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i)if (node = group0[i] || group1[i]) merge[i] = node;
}
for(; j < m0; ++j)merges[j] = groups0[j];
return new (0, _index.Selection)(merges, this._parents);
});
var _index = require("./index");
},{"./index":"3jECc","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"kDp8G":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
for(var groups = this._groups, j = -1, m = groups.length; ++j < m;){
for(var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;)if (node = group[i]) {
if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next);
next = node;
}
}
return this;
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"kpVdV":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(compare) {
if (!compare) compare = ascending;
function compareNode(a, b) {
return a && b ? compare(a.__data__, b.__data__) : !a - !b;
}
for(var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j){
for(var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i)if (node = group[i]) sortgroup[i] = node;
sortgroup.sort(compareNode);
}
return new (0, _index.Selection)(sortgroups, this._parents).order();
});
var _index = require("./index");
function ascending(a, b) {
return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
}
},{"./index":"3jECc","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"iHWbx":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
var callback = arguments[0];
arguments[0] = this;
callback.apply(null, arguments);
return this;
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"4ujY4":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
var nodes = new Array(this.size()), i = -1;
this.each(function() {
nodes[++i] = this;
});
return nodes;
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"2xSCI":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
for(var groups = this._groups, j = 0, m = groups.length; j < m; ++j)for(var group = groups[j], i = 0, n = group.length; i < n; ++i){
var node = group[i];
if (node) return node;
}
return null;
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"cXiKc":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
var size = 0;
this.each(function() {
++size;
});
return size;
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"8jhUp":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
return !this.node();
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"gBDNU":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(callback) {
for(var groups = this._groups, j = 0, m = groups.length; j < m; ++j){
for(var group = groups[j], i = 0, n = group.length, node; i < n; ++i)if (node = group[i]) callback.call(node, node.__data__, i, group);
}
return this;
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"ijmec":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(name, value) {
var fullname = (0, _namespaceDefault.default)(name);
if (arguments.length < 2) {
var node = this.node();
return fullname.local ? node.getAttributeNS(fullname.space, fullname.local) : node.getAttribute(fullname);
}
return this.each((value == null ? fullname.local ? attrRemoveNS : attrRemove : typeof value === "function" ? fullname.local ? attrFunctionNS : attrFunction : fullname.local ? attrConstantNS : attrConstant)(fullname, value));
});
var _namespace = require("../namespace");
var _namespaceDefault = parcelHelpers.interopDefault(_namespace);
function attrRemove(name) {
return function() {
this.removeAttribute(name);
};
}
function attrRemoveNS(fullname) {
return function() {
this.removeAttributeNS(fullname.space, fullname.local);
};
}
function attrConstant(name, value) {
return function() {
this.setAttribute(name, value);
};
}
function attrConstantNS(fullname, value) {
return function() {
this.setAttributeNS(fullname.space, fullname.local, value);
};
}
function attrFunction(name, value) {
return function() {
var v = value.apply(this, arguments);
if (v == null) this.removeAttribute(name);
else this.setAttribute(name, v);
};
}
function attrFunctionNS(fullname, value) {
return function() {
var v = value.apply(this, arguments);
if (v == null) this.removeAttributeNS(fullname.space, fullname.local);
else this.setAttributeNS(fullname.space, fullname.local, v);
};
}
},{"../namespace":"1n29w","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"1n29w":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(name) {
var prefix = name += "", i = prefix.indexOf(":");
if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
return (0, _namespacesDefault.default).hasOwnProperty(prefix) ? {
space: (0, _namespacesDefault.default)[prefix],
local: name
} : name;
});
var _namespaces = require("./namespaces");
var _namespacesDefault = parcelHelpers.interopDefault(_namespaces);
},{"./namespaces":"dROOu","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"dROOu":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "xhtml", ()=>xhtml);
var xhtml = "http://www.w3.org/1999/xhtml";
exports.default = {
svg: "http://www.w3.org/2000/svg",
xhtml: xhtml,
xlink: "http://www.w3.org/1999/xlink",
xml: "http://www.w3.org/XML/1998/namespace",
xmlns: "http://www.w3.org/2000/xmlns/"
};
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"7IIUD":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(name, value, priority) {
return arguments.length > 1 ? this.each((value == null ? styleRemove : typeof value === "function" ? styleFunction : styleConstant)(name, value, priority == null ? "" : priority)) : styleValue(this.node(), name);
});
parcelHelpers.export(exports, "styleValue", ()=>styleValue);
var _window = require("../window");
var _windowDefault = parcelHelpers.interopDefault(_window);
function styleRemove(name) {
return function() {
this.style.removeProperty(name);
};
}
function styleConstant(name, value, priority) {
return function() {
this.style.setProperty(name, value, priority);
};
}
function styleFunction(name, value, priority) {
return function() {
var v = value.apply(this, arguments);
if (v == null) this.style.removeProperty(name);
else this.style.setProperty(name, v, priority);
};
}
function styleValue(node, name) {
return node.style.getPropertyValue(name) || (0, _windowDefault.default)(node).getComputedStyle(node, null).getPropertyValue(name);
}
},{"../window":"c6eEL","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"c6eEL":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(node) {
return node.ownerDocument && node.ownerDocument.defaultView // node is a Node
|| node.document && node // node is a Window
|| node.defaultView; // node is a Document
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"eAZmL":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(name, value) {
return arguments.length > 1 ? this.each((value == null ? propertyRemove : typeof value === "function" ? propertyFunction : propertyConstant)(name, value)) : this.node()[name];
});
function propertyRemove(name) {
return function() {
delete this[name];
};
}
function propertyConstant(name, value) {
return function() {
this[name] = value;
};
}
function propertyFunction(name, value) {
return function() {
var v = value.apply(this, arguments);
if (v == null) delete this[name];
else this[name] = v;
};
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"akczj":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(name, value) {
var names = classArray(name + "");
if (arguments.length < 2) {
var list = classList(this.node()), i = -1, n = names.length;
while(++i < n)if (!list.contains(names[i])) return false;
return true;
}
return this.each((typeof value === "function" ? classedFunction : value ? classedTrue : classedFalse)(names, value));
});
function classArray(string) {
return string.trim().split(/^|\s+/);
}
function classList(node) {
return node.classList || new ClassList(node);
}
function ClassList(node) {
this._node = node;
this._names = classArray(node.getAttribute("class") || "");
}
ClassList.prototype = {
add: function(name) {
var i = this._names.indexOf(name);
if (i < 0) {
this._names.push(name);
this._node.setAttribute("class", this._names.join(" "));
}
},
remove: function(name) {
var i = this._names.indexOf(name);
if (i >= 0) {
this._names.splice(i, 1);
this._node.setAttribute("class", this._names.join(" "));
}
},
contains: function(name) {
return this._names.indexOf(name) >= 0;
}
};
function classedAdd(node, names) {
var list = classList(node), i = -1, n = names.length;
while(++i < n)list.add(names[i]);
}
function classedRemove(node, names) {
var list = classList(node), i = -1, n = names.length;
while(++i < n)list.remove(names[i]);
}
function classedTrue(names) {
return function() {
classedAdd(this, names);
};
}
function classedFalse(names) {
return function() {
classedRemove(this, names);
};
}
function classedFunction(names, value) {
return function() {
(value.apply(this, arguments) ? classedAdd : classedRemove)(this, names);
};
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"4SPlq":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(value) {
return arguments.length ? this.each(value == null ? textRemove : (typeof value === "function" ? textFunction : textConstant)(value)) : this.node().textContent;
});
function textRemove() {
this.textContent = "";
}
function textConstant(value) {
return function() {
this.textContent = value;
};
}
function textFunction(value) {
return function() {
var v = value.apply(this, arguments);
this.textContent = v == null ? "" : v;
};
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"46ETY":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(value) {
return arguments.length ? this.each(value == null ? htmlRemove : (typeof value === "function" ? htmlFunction : htmlConstant)(value)) : this.node().innerHTML;
});
function htmlRemove() {
this.innerHTML = "";
}
function htmlConstant(value) {
return function() {
this.innerHTML = value;
};
}
function htmlFunction(value) {
return function() {
var v = value.apply(this, arguments);
this.innerHTML = v == null ? "" : v;
};
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"5L2a7":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
return this.each(raise);
});
function raise() {
if (this.nextSibling) this.parentNode.appendChild(this);
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"6hwkt":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
return this.each(lower);
});
function lower() {
if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild);
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"5t2Uy":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(name) {
var create = typeof name === "function" ? name : (0, _creatorDefault.default)(name);
return this.select(function() {
return this.appendChild(create.apply(this, arguments));
});
});
var _creator = require("../creator");
var _creatorDefault = parcelHelpers.interopDefault(_creator);
},{"../creator":"cNDKh","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"cNDKh":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(name) {
var fullname = (0, _namespaceDefault.default)(name);
return (fullname.local ? creatorFixed : creatorInherit)(fullname);
});
var _namespace = require("./namespace");
var _namespaceDefault = parcelHelpers.interopDefault(_namespace);
var _namespaces = require("./namespaces");
function creatorInherit(name) {
return function() {
var document = this.ownerDocument, uri = this.namespaceURI;
return uri === (0, _namespaces.xhtml) && document.documentElement.namespaceURI === (0, _namespaces.xhtml) ? document.createElement(name) : document.createElementNS(uri, name);
};
}
function creatorFixed(fullname) {
return function() {
return this.ownerDocument.createElementNS(fullname.space, fullname.local);
};
}
},{"./namespace":"1n29w","./namespaces":"dROOu","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"k6Ecq":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(name, before) {
var create = typeof name === "function" ? name : (0, _creatorDefault.default)(name), select = before == null ? constantNull : typeof before === "function" ? before : (0, _selectorDefault.default)(before);
return this.select(function() {
return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null);
});
});
var _creator = require("../creator");
var _creatorDefault = parcelHelpers.interopDefault(_creator);
var _selector = require("../selector");
var _selectorDefault = parcelHelpers.interopDefault(_selector);
function constantNull() {
return null;
}
},{"../creator":"cNDKh","../selector":"43ZJy","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"iWeo0":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function() {
return this.each(remove);
});
function remove() {
var parent = this.parentNode;
if (parent) parent.removeChild(this);
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"gawOJ":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(deep) {
return this.select(deep ? selection_cloneDeep : selection_cloneShallow);
});
function selection_cloneShallow() {
var clone = this.cloneNode(false), parent = this.parentNode;
return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
}
function selection_cloneDeep() {
var clone = this.cloneNode(true), parent = this.parentNode;
return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"fLeKH":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(value) {
return arguments.length ? this.property("__data__", value) : this.node().__data__;
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"kUHse":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(type, params) {
return this.each((typeof params === "function" ? dispatchFunction : dispatchConstant)(type, params));
});
var _window = require("../window");
var _windowDefault = parcelHelpers.interopDefault(_window);
function dispatchEvent(node, type, params) {
var window = (0, _windowDefault.default)(node), event = window.CustomEvent;
if (typeof event === "function") event = new event(type, params);
else {
event = window.document.createEvent("Event");
if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail;
else event.initEvent(type, false, false);
}
node.dispatchEvent(event);
}
function dispatchConstant(type, params) {
return function() {
return dispatchEvent(this, type, params);
};
}
function dispatchFunction(type, params) {
return function() {
return dispatchEvent(this, type, params.apply(this, arguments));
};
}
},{"../window":"c6eEL","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"1y2wU":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(node, touches, identifier) {
if (arguments.length < 3) identifier = touches, touches = (0, _sourceEventDefault.default)().changedTouches;
for(var i = 0, n = touches ? touches.length : 0, touch; i < n; ++i){
if ((touch = touches[i]).identifier === identifier) return (0, _pointDefault.default)(node, touch);
}
return null;
});
var _sourceEvent = require("./sourceEvent");
var _sourceEventDefault = parcelHelpers.interopDefault(_sourceEvent);
var _point = require("./point");
var _pointDefault = parcelHelpers.interopDefault(_point);
},{"./sourceEvent":"8962w","./point":"i2cnY","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"Zk6MG":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(view) {
var root = view.document.documentElement, selection = (0, _d3Selection.select)(view).on("dragstart.drag", (0, _noeventJsDefault.default), true);
if ("onselectstart" in root) selection.on("selectstart.drag", (0, _noeventJsDefault.default), true);
else {
root.__noselect = root.style.MozUserSelect;
root.style.MozUserSelect = "none";
}
});
parcelHelpers.export(exports, "yesdrag", ()=>yesdrag);
var _d3Selection = require("d3-selection");
var _noeventJs = require("./noevent.js");
var _noeventJsDefault = parcelHelpers.interopDefault(_noeventJs);
function yesdrag(view, noclick) {
var root = view.document.documentElement, selection = (0, _d3Selection.select)(view).on("dragstart.drag", null);
if (noclick) {
selection.on("click.drag", (0, _noeventJsDefault.default), true);
setTimeout(function() {
selection.on("click.drag", null);
}, 0);
}
if ("onselectstart" in root) selection.on("selectstart.drag", null);
else {
root.style.MozUserSelect = root.__noselect;
delete root.__noselect;
}
}
},{"d3-selection":[["select","7dO6e","default"]],"./noevent.js":"7bjIj","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"7bjIj":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "nopropagation", ()=>nopropagation);
parcelHelpers.export(exports, "default", ()=>function() {
(0, _d3Selection.event).preventDefault();
(0, _d3Selection.event).stopImmediatePropagation();
});
var _d3Selection = require("d3-selection");
function nopropagation() {
(0, _d3Selection.event).stopImmediatePropagation();
}
},{"d3-selection":"7QdqF","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"e8QfU":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>function(x) {
return function() {
return x;
};
});
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"cnUOG":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>DragEvent);
function DragEvent(target, type, subject, id, active, x, y, dx, dy, dispatch) {
this.target = target;
this.type = type;
this.subject = subject;
this.identifier = id;
this.active = active;
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this._ = dispatch;
}
DragEvent.prototype.on = function() {
var value = this._.on.apply(this._, arguments);
return value === this._ ? this : value;
};
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"AvPxz":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "sortX", ()=>(0, _chartPrimitives.sortX));
parcelHelpers.export(exports, "lightgrey", ()=>(0, _chartPrimitives.lightgrey));
parcelHelpers.export(exports, "medgrey", ()=>(0, _chartPrimitives.medgrey));
parcelHelpers.export(exports, "grey", ()=>(0, _chartPrimitives.grey));
parcelHelpers.export(exports, "cubicInOutTension", ()=>(0, _chartPrimitives.cubicInOutTension));
parcelHelpers.export(exports, "roundX", ()=>(0, _chartPrimitives.roundX));
parcelHelpers.export(exports, "roundY", ()=>(0, _chartPrimitives.roundY));
parcelHelpers.export(exports, "getInputUpdater", ()=>(0, _chartPrimitives.getInputUpdater));
parcelHelpers.export(exports, "addChartPoint", ()=>addChartPoint);
parcelHelpers.export(exports, "stretchPointMap", ()=>stretchPointMap);
parcelHelpers.export(exports, "shrinkPointMap", ()=>shrinkPointMap);
parcelHelpers.export(exports, "updateCharts", ()=>updateCharts);
parcelHelpers.export(exports, "rerenderCurrentChart", ()=>rerenderCurrentChart);
var _lodashClonedeep = require("lodash.clonedeep");
var _lodashClonedeepDefault = parcelHelpers.interopDefault(_lodashClonedeep);
var _appState = require("../../appState");
var _cropUtils = require("../../crop-utils");
var _util = require("../../util/util");
var _charts = require("../../charts");
var _crop = require("../../crop/crop");
var _speed = require("../../speed");
var _chartPrimitives = require("./chartPrimitives");
function addChartPoint() {
if ((0, _appState.appState).isChartEnabled && (0, _appState.appState).isCurrentChartVisible) {
// eslint-disable-next-line @typescript-eslint/no-require-imports -- lazy import to break circular dependency with scatterChartSpec
const { addSpeedPoint, addCropPoint } = require("a5d1a3e926713a7c");
(0, _util.assertDefined)((0, _charts.chartState).currentChartInput, 'currentChartInput must be defined');
if ((0, _charts.chartState).currentChartInput.type == 'speed') addSpeedPoint.call((0, _charts.chartState).currentChartInput.chart, (0, _appState.appState).video.getCurrentTime(), 1);
else if ((0, _charts.chartState).currentChartInput.type == 'crop') addCropPoint.call((0, _charts.chartState).currentChartInput.chart, (0, _appState.appState).video.getCurrentTime());
}
}
function stretchPointMap(_draft, pointMap, pointType, toTime, type) {
const maxIndex = pointMap.length - 1;
const [sectStart, sectEnd] = type === 'start' ? [
0,
1
] : [
maxIndex - 1,
maxIndex
];
const leftPoint = pointMap[sectStart];
const rightPoint = pointMap[sectEnd];
const targetPoint = type === 'start' ? leftPoint : rightPoint;
const isSectionStatic = pointType === 'crop' ? (0, _cropUtils.cropStringsEqual)(leftPoint.crop, rightPoint.crop) : leftPoint.y === rightPoint.y;
if (isSectionStatic) targetPoint.x = toTime;
else {
const targetPointCopy = (0, _lodashClonedeepDefault.default)(targetPoint);
targetPointCopy.x = toTime;
type === 'start' ? pointMap.unshift(targetPointCopy) : pointMap.push(targetPointCopy);
}
return pointMap;
}
function shrinkPointMap(draft, pointMap, pointType, toTime, type) {
const maxIndex = pointMap.length - 1;
const searchPoint = {
x: toTime,
y: 0,
crop: ''
};
let [sectStart] = (0, _util.bsearch)(pointMap, searchPoint, (0, _chartPrimitives.sortX));
let sectEnd;
if (sectStart <= 0) {
sectStart = 0;
sectEnd = 1;
} else if (sectStart >= maxIndex) {
sectStart = maxIndex - 1;
sectEnd = maxIndex;
} else sectEnd = sectStart + 1;
const leftPoint = pointMap[sectStart];
const rightPoint = pointMap[sectEnd];
const targetPointIndex = type === 'start' ? sectStart : sectEnd;
const targetPoint = pointMap[targetPointIndex];
if (pointType === 'crop') {
const toCropString = (0, _charts.getInterpolatedCrop)(leftPoint, rightPoint, toTime);
const [x, y, w, h] = (0, _cropUtils.getCropComponents)(targetPoint.crop);
const toCrop = new (0, _crop.Crop)(x, y, w, h, (0, _appState.appState).settings.cropResWidth, (0, _appState.appState).settings.cropResHeight);
toCrop.setCropStringSafe(toCropString, draft.enableZoomPan);
targetPoint.crop = toCrop.cropString;
(0, _cropUtils.setAspectRatioForAllPoints)(toCrop.aspectRatio, pointMap, pointMap, targetPointIndex);
if (type === 'start') draft.crop = toCrop.cropString;
} else {
const speed = (0, _speed.getInterpolatedSpeed)(leftPoint, rightPoint, toTime);
targetPoint.y = speed;
if (type === 'start') draft.speed = speed;
}
targetPoint.x = toTime;
pointMap = pointMap.filter((point)=>{
const keepPoint = point === targetPoint || (type === 'start' ? point.x > toTime : point.x < toTime);
return keepPoint;
});
return pointMap;
}
function updateCharts(markerPair, rerender = true) {
const speedChart = (0, _charts.chartState).speedChartInput.chart;
if (speedChart) {
(0, _util.assertDefined)(speedChart.config.data, 'speedChart config data must be defined');
(0, _util.assertDefined)(speedChart.config.data.datasets, 'speedChart config datasets must be defined');
speedChart.config.data.datasets[0].data = markerPair.speedMap;
(0, _charts.updateChartBounds)(speedChart.config, markerPair.start, markerPair.end);
}
const cropChart = (0, _charts.chartState).cropChartInput.chart;
if (cropChart) {
(0, _util.assertDefined)(cropChart.config.data, 'cropChart config data must be defined');
(0, _util.assertDefined)(cropChart.config.data.datasets, 'cropChart config datasets must be defined');
cropChart.config.data.datasets[0].data = markerPair.cropMap;
(0, _charts.updateChartBounds)(cropChart.config, markerPair.start, markerPair.end);
}
if (rerender) rerenderCurrentChart();
}
function rerenderCurrentChart() {
if ((0, _appState.appState).isCurrentChartVisible && (0, _charts.chartState).currentChartInput?.chart) (0, _charts.chartState).currentChartInput.chart.update();
}
},{"lodash.clonedeep":"hwif0","../../appState":"g0AlP","../../crop-utils":"k2gwb","../../util/util":"99arg","../../charts":"hBxwj","../../crop/crop":"axPMI","../../speed":"6CgFD","./chartPrimitives":"hk4AN","a5d1a3e926713a7c":"4kM90","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"hwif0":[function(require,module,exports,__globalThis) {
/**
* lodash (Custom Build) <https://lodash.com/>
* Build: `lodash modularize exports="npm" -o ./`
* Copyright jQuery Foundation and other contributors <https://jquery.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/ /** Used as the size to enable large array optimizations. */ var global = arguments[3];
var LARGE_ARRAY_SIZE = 200;
/** Used to stand-in for `undefined` hash values. */ var HASH_UNDEFINED = '__lodash_hash_undefined__';
/** Used as references for various `Number` constants. */ var MAX_SAFE_INTEGER = 9007199254740991;
/** `Object#toString` result references. */ var argsTag = '[object Arguments]', arrayTag = '[object Array]', boolTag = '[object Boolean]', dateTag = '[object Date]', errorTag = '[object Error]', funcTag = '[object Function]', genTag = '[object GeneratorFunction]', mapTag = '[object Map]', numberTag = '[object Number]', objectTag = '[object Object]', promiseTag = '[object Promise]', regexpTag = '[object RegExp]', setTag = '[object Set]', stringTag = '[object String]', symbolTag = '[object Symbol]', weakMapTag = '[object WeakMap]';
var arrayBufferTag = '[object ArrayBuffer]', dataViewTag = '[object DataView]', float32Tag = '[object Float32Array]', float64Tag = '[object Float64Array]', int8Tag = '[object Int8Array]', int16Tag = '[object Int16Array]', int32Tag = '[object Int32Array]', uint8Tag = '[object Uint8Array]', uint8ClampedTag = '[object Uint8ClampedArray]', uint16Tag = '[object Uint16Array]', uint32Tag = '[object Uint32Array]';
/**
* Used to match `RegExp`
* [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
*/ var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
/** Used to match `RegExp` flags from their coerced string values. */ var reFlags = /\w*$/;
/** Used to detect host constructors (Safari). */ var reIsHostCtor = /^\[object .+?Constructor\]$/;
/** Used to detect unsigned integer values. */ var reIsUint = /^(?:0|[1-9]\d*)$/;
/** Used to identify `toStringTag` values supported by `_.clone`. */ var cloneableTags = {};
cloneableTags[argsTag] = cloneableTags[arrayTag] = cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = cloneableTags[boolTag] = cloneableTags[dateTag] = cloneableTags[float32Tag] = cloneableTags[float64Tag] = cloneableTags[int8Tag] = cloneableTags[int16Tag] = cloneableTags[int32Tag] = cloneableTags[mapTag] = cloneableTags[numberTag] = cloneableTags[objectTag] = cloneableTags[regexpTag] = cloneableTags[setTag] = cloneableTags[stringTag] = cloneableTags[symbolTag] = cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
cloneableTags[errorTag] = cloneableTags[funcTag] = cloneableTags[weakMapTag] = false;
/** Detect free variable `global` from Node.js. */ var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
/** Detect free variable `self`. */ var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
/** Used as a reference to the global object. */ var root = freeGlobal || freeSelf || Function('return this')();
/** Detect free variable `exports`. */ var freeExports = exports && !exports.nodeType && exports;
/** Detect free variable `module`. */ var freeModule = freeExports && true && module && !module.nodeType && module;
/** Detect the popular CommonJS extension `module.exports`. */ var moduleExports = freeModule && freeModule.exports === freeExports;
/**
* Adds the key-value `pair` to `map`.
*
* @private
* @param {Object} map The map to modify.
* @param {Array} pair The key-value pair to add.
* @returns {Object} Returns `map`.
*/ function addMapEntry(map, pair) {
// Don't return `map.set` because it's not chainable in IE 11.
map.set(pair[0], pair[1]);
return map;
}
/**
* Adds `value` to `set`.
*
* @private
* @param {Object} set The set to modify.
* @param {*} value The value to add.
* @returns {Object} Returns `set`.
*/ function addSetEntry(set, value) {
// Don't return `set.add` because it's not chainable in IE 11.
set.add(value);
return set;
}
/**
* A specialized version of `_.forEach` for arrays without support for
* iteratee shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns `array`.
*/ function arrayEach(array, iteratee) {
var index = -1, length = array ? array.length : 0;
while(++index < length){
if (iteratee(array[index], index, array) === false) break;
}
return array;
}
/**
* Appends the elements of `values` to `array`.
*
* @private
* @param {Array} array The array to modify.
* @param {Array} values The values to append.
* @returns {Array} Returns `array`.
*/ function arrayPush(array, values) {
var index = -1, length = values.length, offset = array.length;
while(++index < length)array[offset + index] = values[index];
return array;
}
/**
* A specialized version of `_.reduce` for arrays without support for
* iteratee shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @param {*} [accumulator] The initial value.
* @param {boolean} [initAccum] Specify using the first element of `array` as
* the initial value.
* @returns {*} Returns the accumulated value.
*/ function arrayReduce(array, iteratee, accumulator, initAccum) {
var index = -1, length = array ? array.length : 0;
if (initAccum && length) accumulator = array[++index];
while(++index < length)accumulator = iteratee(accumulator, array[index], index, array);
return accumulator;
}
/**
* The base implementation of `_.times` without support for iteratee shorthands
* or max array length checks.
*
* @private
* @param {number} n The number of times to invoke `iteratee`.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the array of results.
*/ function baseTimes(n, iteratee) {
var index = -1, result = Array(n);
while(++index < n)result[index] = iteratee(index);
return result;
}
/**
* Gets the value at `key` of `object`.
*
* @private
* @param {Object} [object] The object to query.
* @param {string} key The key of the property to get.
* @returns {*} Returns the property value.
*/ function getValue(object, key) {
return object == null ? undefined : object[key];
}
/**
* Checks if `value` is a host object in IE < 9.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a host object, else `false`.
*/ function isHostObject(value) {
// Many host objects are `Object` objects that can coerce to strings
// despite having improperly defined `toString` methods.
var result = false;
if (value != null && typeof value.toString != 'function') try {
result = !!(value + '');
} catch (e) {}
return result;
}
/**
* Converts `map` to its key-value pairs.
*
* @private
* @param {Object} map The map to convert.
* @returns {Array} Returns the key-value pairs.
*/ function mapToArray(map) {
var index = -1, result = Array(map.size);
map.forEach(function(value, key) {
result[++index] = [
key,
value
];
});
return result;
}
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/ function overArg(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
/**
* Converts `set` to an array of its values.
*
* @private
* @param {Object} set The set to convert.
* @returns {Array} Returns the values.
*/ function setToArray(set) {
var index = -1, result = Array(set.size);
set.forEach(function(value) {
result[++index] = value;
});
return result;
}
/** Used for built-in method references. */ var arrayProto = Array.prototype, funcProto = Function.prototype, objectProto = Object.prototype;
/** Used to detect overreaching core-js shims. */ var coreJsData = root['__core-js_shared__'];
/** Used to detect methods masquerading as native. */ var maskSrcKey = function() {
var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
return uid ? 'Symbol(src)_1.' + uid : '';
}();
/** Used to resolve the decompiled source of functions. */ var funcToString = funcProto.toString;
/** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/ var objectToString = objectProto.toString;
/** Used to detect if a method is native. */ var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$');
/** Built-in value references. */ var Buffer = moduleExports ? root.Buffer : undefined, Symbol = root.Symbol, Uint8Array = root.Uint8Array, getPrototype = overArg(Object.getPrototypeOf, Object), objectCreate = Object.create, propertyIsEnumerable = objectProto.propertyIsEnumerable, splice = arrayProto.splice;
/* Built-in method references for those with the same name as other `lodash` methods. */ var nativeGetSymbols = Object.getOwnPropertySymbols, nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, nativeKeys = overArg(Object.keys, Object);
/* Built-in method references that are verified to be native. */ var DataView = getNative(root, 'DataView'), Map = getNative(root, 'Map'), Promise = getNative(root, 'Promise'), Set = getNative(root, 'Set'), WeakMap = getNative(root, 'WeakMap'), nativeCreate = getNative(Object, 'create');
/** Used to detect maps, sets, and weakmaps. */ var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map), promiseCtorString = toSource(Promise), setCtorString = toSource(Set), weakMapCtorString = toSource(WeakMap);
/** Used to convert symbols to primitives and strings. */ var symbolProto = Symbol ? Symbol.prototype : undefined, symbolValueOf = symbolProto ? symbolProto.valueOf : undefined;
/**
* Creates a hash object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/ function Hash(entries) {
var index = -1, length = entries ? entries.length : 0;
this.clear();
while(++index < length){
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the hash.
*
* @private
* @name clear
* @memberOf Hash
*/ function hashClear() {
this.__data__ = nativeCreate ? nativeCreate(null) : {};
}
/**
* Removes `key` and its value from the hash.
*
* @private
* @name delete
* @memberOf Hash
* @param {Object} hash The hash to modify.
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ function hashDelete(key) {
return this.has(key) && delete this.__data__[key];
}
/**
* Gets the hash value for `key`.
*
* @private
* @name get
* @memberOf Hash
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/ function hashGet(key) {
var data = this.__data__;
if (nativeCreate) {
var result = data[key];
return result === HASH_UNDEFINED ? undefined : result;
}
return hasOwnProperty.call(data, key) ? data[key] : undefined;
}
/**
* Checks if a hash value for `key` exists.
*
* @private
* @name has
* @memberOf Hash
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ function hashHas(key) {
var data = this.__data__;
return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
}
/**
* Sets the hash `key` to `value`.
*
* @private
* @name set
* @memberOf Hash
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the hash instance.
*/ function hashSet(key, value) {
var data = this.__data__;
data[key] = nativeCreate && value === undefined ? HASH_UNDEFINED : value;
return this;
}
// Add methods to `Hash`.
Hash.prototype.clear = hashClear;
Hash.prototype['delete'] = hashDelete;
Hash.prototype.get = hashGet;
Hash.prototype.has = hashHas;
Hash.prototype.set = hashSet;
/**
* Creates an list cache object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/ function ListCache(entries) {
var index = -1, length = entries ? entries.length : 0;
this.clear();
while(++index < length){
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the list cache.
*
* @private
* @name clear
* @memberOf ListCache
*/ function listCacheClear() {
this.__data__ = [];
}
/**
* Removes `key` and its value from the list cache.
*
* @private
* @name delete
* @memberOf ListCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ function listCacheDelete(key) {
var data = this.__data__, index = assocIndexOf(data, key);
if (index < 0) return false;
var lastIndex = data.length - 1;
if (index == lastIndex) data.pop();
else splice.call(data, index, 1);
return true;
}
/**
* Gets the list cache value for `key`.
*
* @private
* @name get
* @memberOf ListCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/ function listCacheGet(key) {
var data = this.__data__, index = assocIndexOf(data, key);
return index < 0 ? undefined : data[index][1];
}
/**
* Checks if a list cache value for `key` exists.
*
* @private
* @name has
* @memberOf ListCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ function listCacheHas(key) {
return assocIndexOf(this.__data__, key) > -1;
}
/**
* Sets the list cache `key` to `value`.
*
* @private
* @name set
* @memberOf ListCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the list cache instance.
*/ function listCacheSet(key, value) {
var data = this.__data__, index = assocIndexOf(data, key);
if (index < 0) data.push([
key,
value
]);
else data[index][1] = value;
return this;
}
// Add methods to `ListCache`.
ListCache.prototype.clear = listCacheClear;
ListCache.prototype['delete'] = listCacheDelete;
ListCache.prototype.get = listCacheGet;
ListCache.prototype.has = listCacheHas;
ListCache.prototype.set = listCacheSet;
/**
* Creates a map cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/ function MapCache(entries) {
var index = -1, length = entries ? entries.length : 0;
this.clear();
while(++index < length){
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the map.
*
* @private
* @name clear
* @memberOf MapCache
*/ function mapCacheClear() {
this.__data__ = {
'hash': new Hash,
'map': new (Map || ListCache),
'string': new Hash
};
}
/**
* Removes `key` and its value from the map.
*
* @private
* @name delete
* @memberOf MapCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ function mapCacheDelete(key) {
return getMapData(this, key)['delete'](key);
}
/**
* Gets the map value for `key`.
*
* @private
* @name get
* @memberOf MapCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/ function mapCacheGet(key) {
return getMapData(this, key).get(key);
}
/**
* Checks if a map value for `key` exists.
*
* @private
* @name has
* @memberOf MapCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ function mapCacheHas(key) {
return getMapData(this, key).has(key);
}
/**
* Sets the map `key` to `value`.
*
* @private
* @name set
* @memberOf MapCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the map cache instance.
*/ function mapCacheSet(key, value) {
getMapData(this, key).set(key, value);
return this;
}
// Add methods to `MapCache`.
MapCache.prototype.clear = mapCacheClear;
MapCache.prototype['delete'] = mapCacheDelete;
MapCache.prototype.get = mapCacheGet;
MapCache.prototype.has = mapCacheHas;
MapCache.prototype.set = mapCacheSet;
/**
* Creates a stack cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/ function Stack(entries) {
this.__data__ = new ListCache(entries);
}
/**
* Removes all key-value entries from the stack.
*
* @private
* @name clear
* @memberOf Stack
*/ function stackClear() {
this.__data__ = new ListCache;
}
/**
* Removes `key` and its value from the stack.
*
* @private
* @name delete
* @memberOf Stack
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ function stackDelete(key) {
return this.__data__['delete'](key);
}
/**
* Gets the stack value for `key`.
*
* @private
* @name get
* @memberOf Stack
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/ function stackGet(key) {
return this.__data__.get(key);
}
/**
* Checks if a stack value for `key` exists.
*
* @private
* @name has
* @memberOf Stack
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ function stackHas(key) {
return this.__data__.has(key);
}
/**
* Sets the stack `key` to `value`.
*
* @private
* @name set
* @memberOf Stack
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the stack cache instance.
*/ function stackSet(key, value) {
var cache = this.__data__;
if (cache instanceof ListCache) {
var pairs = cache.__data__;
if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) {
pairs.push([
key,
value
]);
return this;
}
cache = this.__data__ = new MapCache(pairs);
}
cache.set(key, value);
return this;
}
// Add methods to `Stack`.
Stack.prototype.clear = stackClear;
Stack.prototype['delete'] = stackDelete;
Stack.prototype.get = stackGet;
Stack.prototype.has = stackHas;
Stack.prototype.set = stackSet;
/**
* Creates an array of the enumerable property names of the array-like `value`.
*
* @private
* @param {*} value The value to query.
* @param {boolean} inherited Specify returning inherited property names.
* @returns {Array} Returns the array of property names.
*/ function arrayLikeKeys(value, inherited) {
// Safari 8.1 makes `arguments.callee` enumerable in strict mode.
// Safari 9 makes `arguments.length` enumerable in strict mode.
var result = isArray(value) || isArguments(value) ? baseTimes(value.length, String) : [];
var length = result.length, skipIndexes = !!length;
for(var key in value)if ((inherited || hasOwnProperty.call(value, key)) && !(skipIndexes && (key == 'length' || isIndex(key, length)))) result.push(key);
return result;
}
/**
* Assigns `value` to `key` of `object` if the existing value is not equivalent
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* for equality comparisons.
*
* @private
* @param {Object} object The object to modify.
* @param {string} key The key of the property to assign.
* @param {*} value The value to assign.
*/ function assignValue(object, key, value) {
var objValue = object[key];
if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || value === undefined && !(key in object)) object[key] = value;
}
/**
* Gets the index at which the `key` is found in `array` of key-value pairs.
*
* @private
* @param {Array} array The array to inspect.
* @param {*} key The key to search for.
* @returns {number} Returns the index of the matched value, else `-1`.
*/ function assocIndexOf(array, key) {
var length = array.length;
while(length--){
if (eq(array[length][0], key)) return length;
}
return -1;
}
/**
* The base implementation of `_.assign` without support for multiple sources
* or `customizer` functions.
*
* @private
* @param {Object} object The destination object.
* @param {Object} source The source object.
* @returns {Object} Returns `object`.
*/ function baseAssign(object, source) {
return object && copyObject(source, keys(source), object);
}
/**
* The base implementation of `_.clone` and `_.cloneDeep` which tracks
* traversed objects.
*
* @private
* @param {*} value The value to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @param {boolean} [isFull] Specify a clone including symbols.
* @param {Function} [customizer] The function to customize cloning.
* @param {string} [key] The key of `value`.
* @param {Object} [object] The parent object of `value`.
* @param {Object} [stack] Tracks traversed objects and their clone counterparts.
* @returns {*} Returns the cloned value.
*/ function baseClone(value, isDeep, isFull, customizer, key, object, stack) {
var result;
if (customizer) result = object ? customizer(value, key, object, stack) : customizer(value);
if (result !== undefined) return result;
if (!isObject(value)) return value;
var isArr = isArray(value);
if (isArr) {
result = initCloneArray(value);
if (!isDeep) return copyArray(value, result);
} else {
var tag = getTag(value), isFunc = tag == funcTag || tag == genTag;
if (isBuffer(value)) return cloneBuffer(value, isDeep);
if (tag == objectTag || tag == argsTag || isFunc && !object) {
if (isHostObject(value)) return object ? value : {};
result = initCloneObject(isFunc ? {} : value);
if (!isDeep) return copySymbols(value, baseAssign(result, value));
} else {
if (!cloneableTags[tag]) return object ? value : {};
result = initCloneByTag(value, tag, baseClone, isDeep);
}
}
// Check for circular references and return its corresponding clone.
stack || (stack = new Stack);
var stacked = stack.get(value);
if (stacked) return stacked;
stack.set(value, result);
if (!isArr) var props = isFull ? getAllKeys(value) : keys(value);
arrayEach(props || value, function(subValue, key) {
if (props) {
key = subValue;
subValue = value[key];
}
// Recursively populate clone (susceptible to call stack limits).
assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack));
});
return result;
}
/**
* The base implementation of `_.create` without support for assigning
* properties to the created object.
*
* @private
* @param {Object} prototype The object to inherit from.
* @returns {Object} Returns the new object.
*/ function baseCreate(proto) {
return isObject(proto) ? objectCreate(proto) : {};
}
/**
* The base implementation of `getAllKeys` and `getAllKeysIn` which uses
* `keysFunc` and `symbolsFunc` to get the enumerable property names and
* symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {Function} keysFunc The function to get the keys of `object`.
* @param {Function} symbolsFunc The function to get the symbols of `object`.
* @returns {Array} Returns the array of property names and symbols.
*/ function baseGetAllKeys(object, keysFunc, symbolsFunc) {
var result = keysFunc(object);
return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
}
/**
* The base implementation of `getTag`.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/ function baseGetTag(value) {
return objectToString.call(value);
}
/**
* The base implementation of `_.isNative` without bad shim checks.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a native function,
* else `false`.
*/ function baseIsNative(value) {
if (!isObject(value) || isMasked(value)) return false;
var pattern = isFunction(value) || isHostObject(value) ? reIsNative : reIsHostCtor;
return pattern.test(toSource(value));
}
/**
* The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/ function baseKeys(object) {
if (!isPrototype(object)) return nativeKeys(object);
var result = [];
for(var key in Object(object))if (hasOwnProperty.call(object, key) && key != 'constructor') result.push(key);
return result;
}
/**
* Creates a clone of `buffer`.
*
* @private
* @param {Buffer} buffer The buffer to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Buffer} Returns the cloned buffer.
*/ function cloneBuffer(buffer, isDeep) {
if (isDeep) return buffer.slice();
var result = new buffer.constructor(buffer.length);
buffer.copy(result);
return result;
}
/**
* Creates a clone of `arrayBuffer`.
*
* @private
* @param {ArrayBuffer} arrayBuffer The array buffer to clone.
* @returns {ArrayBuffer} Returns the cloned array buffer.
*/ function cloneArrayBuffer(arrayBuffer) {
var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
new Uint8Array(result).set(new Uint8Array(arrayBuffer));
return result;
}
/**
* Creates a clone of `dataView`.
*
* @private
* @param {Object} dataView The data view to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned data view.
*/ function cloneDataView(dataView, isDeep) {
var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
}
/**
* Creates a clone of `map`.
*
* @private
* @param {Object} map The map to clone.
* @param {Function} cloneFunc The function to clone values.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned map.
*/ function cloneMap(map, isDeep, cloneFunc) {
var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map);
return arrayReduce(array, addMapEntry, new map.constructor);
}
/**
* Creates a clone of `regexp`.
*
* @private
* @param {Object} regexp The regexp to clone.
* @returns {Object} Returns the cloned regexp.
*/ function cloneRegExp(regexp) {
var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
result.lastIndex = regexp.lastIndex;
return result;
}
/**
* Creates a clone of `set`.
*
* @private
* @param {Object} set The set to clone.
* @param {Function} cloneFunc The function to clone values.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned set.
*/ function cloneSet(set, isDeep, cloneFunc) {
var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set);
return arrayReduce(array, addSetEntry, new set.constructor);
}
/**
* Creates a clone of the `symbol` object.
*
* @private
* @param {Object} symbol The symbol object to clone.
* @returns {Object} Returns the cloned symbol object.
*/ function cloneSymbol(symbol) {
return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
}
/**
* Creates a clone of `typedArray`.
*
* @private
* @param {Object} typedArray The typed array to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned typed array.
*/ function cloneTypedArray(typedArray, isDeep) {
var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
}
/**
* Copies the values of `source` to `array`.
*
* @private
* @param {Array} source The array to copy values from.
* @param {Array} [array=[]] The array to copy values to.
* @returns {Array} Returns `array`.
*/ function copyArray(source, array) {
var index = -1, length = source.length;
array || (array = Array(length));
while(++index < length)array[index] = source[index];
return array;
}
/**
* Copies properties of `source` to `object`.
*
* @private
* @param {Object} source The object to copy properties from.
* @param {Array} props The property identifiers to copy.
* @param {Object} [object={}] The object to copy properties to.
* @param {Function} [customizer] The function to customize copied values.
* @returns {Object} Returns `object`.
*/ function copyObject(source, props, object, customizer) {
object || (object = {});
var index = -1, length = props.length;
while(++index < length){
var key = props[index];
var newValue = customizer ? customizer(object[key], source[key], key, object, source) : undefined;
assignValue(object, key, newValue === undefined ? source[key] : newValue);
}
return object;
}
/**
* Copies own symbol properties of `source` to `object`.
*
* @private
* @param {Object} source The object to copy symbols from.
* @param {Object} [object={}] The object to copy symbols to.
* @returns {Object} Returns `object`.
*/ function copySymbols(source, object) {
return copyObject(source, getSymbols(source), object);
}
/**
* Creates an array of own enumerable property names and symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names and symbols.
*/ function getAllKeys(object) {
return baseGetAllKeys(object, keys, getSymbols);
}
/**
* Gets the data for `map`.
*
* @private
* @param {Object} map The map to query.
* @param {string} key The reference key.
* @returns {*} Returns the map data.
*/ function getMapData(map, key) {
var data = map.__data__;
return isKeyable(key) ? data[typeof key == 'string' ? 'string' : 'hash'] : data.map;
}
/**
* Gets the native function at `key` of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {string} key The key of the method to get.
* @returns {*} Returns the function if it's native, else `undefined`.
*/ function getNative(object, key) {
var value = getValue(object, key);
return baseIsNative(value) ? value : undefined;
}
/**
* Creates an array of the own enumerable symbol properties of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of symbols.
*/ var getSymbols = nativeGetSymbols ? overArg(nativeGetSymbols, Object) : stubArray;
/**
* Gets the `toStringTag` of `value`.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/ var getTag = baseGetTag;
// Fallback for data views, maps, sets, and weak maps in IE 11,
// for data views in Edge < 14, and promises in Node.js.
if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map && getTag(new Map) != mapTag || Promise && getTag(Promise.resolve()) != promiseTag || Set && getTag(new Set) != setTag || WeakMap && getTag(new WeakMap) != weakMapTag) getTag = function(value) {
var result = objectToString.call(value), Ctor = result == objectTag ? value.constructor : undefined, ctorString = Ctor ? toSource(Ctor) : undefined;
if (ctorString) switch(ctorString){
case dataViewCtorString:
return dataViewTag;
case mapCtorString:
return mapTag;
case promiseCtorString:
return promiseTag;
case setCtorString:
return setTag;
case weakMapCtorString:
return weakMapTag;
}
return result;
};
/**
* Initializes an array clone.
*
* @private
* @param {Array} array The array to clone.
* @returns {Array} Returns the initialized clone.
*/ function initCloneArray(array) {
var length = array.length, result = array.constructor(length);
// Add properties assigned by `RegExp#exec`.
if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
result.index = array.index;
result.input = array.input;
}
return result;
}
/**
* Initializes an object clone.
*
* @private
* @param {Object} object The object to clone.
* @returns {Object} Returns the initialized clone.
*/ function initCloneObject(object) {
return typeof object.constructor == 'function' && !isPrototype(object) ? baseCreate(getPrototype(object)) : {};
}
/**
* Initializes an object clone based on its `toStringTag`.
*
* **Note:** This function only supports cloning values with tags of
* `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
*
* @private
* @param {Object} object The object to clone.
* @param {string} tag The `toStringTag` of the object to clone.
* @param {Function} cloneFunc The function to clone values.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the initialized clone.
*/ function initCloneByTag(object, tag, cloneFunc, isDeep) {
var Ctor = object.constructor;
switch(tag){
case arrayBufferTag:
return cloneArrayBuffer(object);
case boolTag:
case dateTag:
return new Ctor(+object);
case dataViewTag:
return cloneDataView(object, isDeep);
case float32Tag:
case float64Tag:
case int8Tag:
case int16Tag:
case int32Tag:
case uint8Tag:
case uint8ClampedTag:
case uint16Tag:
case uint32Tag:
return cloneTypedArray(object, isDeep);
case mapTag:
return cloneMap(object, isDeep, cloneFunc);
case numberTag:
case stringTag:
return new Ctor(object);
case regexpTag:
return cloneRegExp(object);
case setTag:
return cloneSet(object, isDeep, cloneFunc);
case symbolTag:
return cloneSymbol(object);
}
}
/**
* Checks if `value` is a valid array-like index.
*
* @private
* @param {*} value The value to check.
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
*/ function isIndex(value, length) {
length = length == null ? MAX_SAFE_INTEGER : length;
return !!length && (typeof value == 'number' || reIsUint.test(value)) && value > -1 && value % 1 == 0 && value < length;
}
/**
* Checks if `value` is suitable for use as unique object key.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is suitable, else `false`.
*/ function isKeyable(value) {
var type = typeof value;
return type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean' ? value !== '__proto__' : value === null;
}
/**
* Checks if `func` has its source masked.
*
* @private
* @param {Function} func The function to check.
* @returns {boolean} Returns `true` if `func` is masked, else `false`.
*/ function isMasked(func) {
return !!maskSrcKey && maskSrcKey in func;
}
/**
* Checks if `value` is likely a prototype object.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
*/ function isPrototype(value) {
var Ctor = value && value.constructor, proto = typeof Ctor == 'function' && Ctor.prototype || objectProto;
return value === proto;
}
/**
* Converts `func` to its source code.
*
* @private
* @param {Function} func The function to process.
* @returns {string} Returns the source code.
*/ function toSource(func) {
if (func != null) {
try {
return funcToString.call(func);
} catch (e) {}
try {
return func + '';
} catch (e) {}
}
return '';
}
/**
* This method is like `_.clone` except that it recursively clones `value`.
*
* @static
* @memberOf _
* @since 1.0.0
* @category Lang
* @param {*} value The value to recursively clone.
* @returns {*} Returns the deep cloned value.
* @see _.clone
* @example
*
* var objects = [{ 'a': 1 }, { 'b': 2 }];
*
* var deep = _.cloneDeep(objects);
* console.log(deep[0] === objects[0]);
* // => false
*/ function cloneDeep(value) {
return baseClone(value, true, true);
}
/**
* Performs a
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* comparison between two values to determine if they are equivalent.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* var object = { 'a': 1 };
* var other = { 'a': 1 };
*
* _.eq(object, object);
* // => true
*
* _.eq(object, other);
* // => false
*
* _.eq('a', 'a');
* // => true
*
* _.eq('a', Object('a'));
* // => false
*
* _.eq(NaN, NaN);
* // => true
*/ function eq(value, other) {
return value === other || value !== value && other !== other;
}
/**
* Checks if `value` is likely an `arguments` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
* else `false`.
* @example
*
* _.isArguments(function() { return arguments; }());
* // => true
*
* _.isArguments([1, 2, 3]);
* // => false
*/ function isArguments(value) {
// Safari 8.1 makes `arguments.callee` enumerable in strict mode.
return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
}
/**
* Checks if `value` is classified as an `Array` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array, else `false`.
* @example
*
* _.isArray([1, 2, 3]);
* // => true
*
* _.isArray(document.body.children);
* // => false
*
* _.isArray('abc');
* // => false
*
* _.isArray(_.noop);
* // => false
*/ var isArray = Array.isArray;
/**
* Checks if `value` is array-like. A value is considered array-like if it's
* not a function and has a `value.length` that's an integer greater than or
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
* @example
*
* _.isArrayLike([1, 2, 3]);
* // => true
*
* _.isArrayLike(document.body.children);
* // => true
*
* _.isArrayLike('abc');
* // => true
*
* _.isArrayLike(_.noop);
* // => false
*/ function isArrayLike(value) {
return value != null && isLength(value.length) && !isFunction(value);
}
/**
* This method is like `_.isArrayLike` except that it also checks if `value`
* is an object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array-like object,
* else `false`.
* @example
*
* _.isArrayLikeObject([1, 2, 3]);
* // => true
*
* _.isArrayLikeObject(document.body.children);
* // => true
*
* _.isArrayLikeObject('abc');
* // => false
*
* _.isArrayLikeObject(_.noop);
* // => false
*/ function isArrayLikeObject(value) {
return isObjectLike(value) && isArrayLike(value);
}
/**
* Checks if `value` is a buffer.
*
* @static
* @memberOf _
* @since 4.3.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
* @example
*
* _.isBuffer(new Buffer(2));
* // => true
*
* _.isBuffer(new Uint8Array(2));
* // => false
*/ var isBuffer = nativeIsBuffer || stubFalse;
/**
* Checks if `value` is classified as a `Function` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
* @example
*
* _.isFunction(_);
* // => true
*
* _.isFunction(/abc/);
* // => false
*/ function isFunction(value) {
// The use of `Object#toString` avoids issues with the `typeof` operator
// in Safari 8-9 which returns 'object' for typed array and other constructors.
var tag = isObject(value) ? objectToString.call(value) : '';
return tag == funcTag || tag == genTag;
}
/**
* Checks if `value` is a valid array-like length.
*
* **Note:** This method is loosely based on
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
* @example
*
* _.isLength(3);
* // => true
*
* _.isLength(Number.MIN_VALUE);
* // => false
*
* _.isLength(Infinity);
* // => false
*
* _.isLength('3');
* // => false
*/ function isLength(value) {
return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/ function isObject(value) {
var type = typeof value;
return !!value && (type == 'object' || type == 'function');
}
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/ function isObjectLike(value) {
return !!value && typeof value == 'object';
}
/**
* Creates an array of the own enumerable property names of `object`.
*
* **Note:** Non-object values are coerced to objects. See the
* [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
* for more details.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.keys(new Foo);
* // => ['a', 'b'] (iteration order is not guaranteed)
*
* _.keys('hi');
* // => ['0', '1']
*/ function keys(object) {
return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
}
/**
* This method returns a new empty array.
*
* @static
* @memberOf _
* @since 4.13.0
* @category Util
* @returns {Array} Returns the new empty array.
* @example
*
* var arrays = _.times(2, _.stubArray);
*
* console.log(arrays);
* // => [[], []]
*
* console.log(arrays[0] === arrays[1]);
* // => false
*/ function stubArray() {
return [];
}
/**
* This method returns `false`.
*
* @static
* @memberOf _
* @since 4.13.0
* @category Util
* @returns {boolean} Returns `false`.
* @example
*
* _.times(2, _.stubFalse);
* // => [false, false]
*/ function stubFalse() {
return false;
}
module.exports = cloneDeep;
},{}],"k2gwb":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "getCropComponents", ()=>getCropComponents);
parcelHelpers.export(exports, "getVideoScaledCropComponentsFromCropString", ()=>getVideoScaledCropComponentsFromCropString);
parcelHelpers.export(exports, "getVideoScaledCropComponents", ()=>getVideoScaledCropComponents);
parcelHelpers.export(exports, "rotateCropComponentsClockWise", ()=>rotateCropComponentsClockWise);
parcelHelpers.export(exports, "rotateCropComponentsCounterClockWise", ()=>rotateCropComponentsCounterClockWise);
parcelHelpers.export(exports, "getRotatedCropComponents", ()=>getRotatedCropComponents);
parcelHelpers.export(exports, "getRotatedCropString", ()=>getRotatedCropString);
parcelHelpers.export(exports, "getNumericCropString", ()=>getNumericCropString);
parcelHelpers.export(exports, "isStaticCrop", ()=>isStaticCrop);
parcelHelpers.export(exports, "cropStringsEqual", ()=>cropStringsEqual);
parcelHelpers.export(exports, "getCropMultiples", ()=>getCropMultiples);
parcelHelpers.export(exports, "multiplyCropString", ()=>multiplyCropString);
parcelHelpers.export(exports, "multiplyMarkerPairCrops", ()=>multiplyMarkerPairCrops);
parcelHelpers.export(exports, "setCropComponentForAllPoints", ()=>setCropComponentForAllPoints);
parcelHelpers.export(exports, "setAspectRatioForAllPoints", ()=>setAspectRatioForAllPoints);
parcelHelpers.export(exports, "getDefaultCropRes", ()=>getDefaultCropRes);
parcelHelpers.export(exports, "setCropInputValue", ()=>setCropInputValue);
parcelHelpers.export(exports, "setCropString", ()=>setCropString);
parcelHelpers.export(exports, "multiplyAllCrops", ()=>multiplyAllCrops);
parcelHelpers.export(exports, "getRelevantCropString", ()=>getRelevantCropString);
parcelHelpers.export(exports, "updateCropStringWithCrop", ()=>updateCropStringWithCrop);
parcelHelpers.export(exports, "lastRenderedCropString", ()=>lastRenderedCropString);
parcelHelpers.export(exports, "updateAllMarkerPairCrops", ()=>updateAllMarkerPairCrops);
parcelHelpers.export(exports, "updateCropString", ()=>updateCropString);
var _immer = require("immer");
var _crop = require("./crop/crop");
var _appState = require("./appState");
var _util = require("./util/util");
var _undoredo = require("./util/undoredo");
var _settingsEditor = require("./features/settings/settings-editor");
var _charts = require("./charts");
var _cropOverlay = require("./crop-overlay");
function getCropComponents(cropString) {
if (!cropString && (0, _appState.appState).isSettingsEditorOpen) {
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedMarkerPairIndex != null) cropString = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex].crop;
else cropString = (0, _appState.appState).settings.newMarkerCrop;
}
if (!cropString) {
console.error('No valid crop string to extract components from.');
cropString = '0:0:iw:ih';
}
const cropArray = cropString.split(':').map((cropStringComponent, i)=>{
let cropComponent;
if (cropStringComponent === 'iw') cropComponent = (0, _appState.appState).settings.cropResWidth;
else if (cropStringComponent === 'ih') cropComponent = (0, _appState.appState).settings.cropResHeight;
else if (i % 2 == 0) {
cropComponent = parseFloat(cropStringComponent);
cropComponent = Math.min(Math.round(cropComponent), (0, _appState.appState).settings.cropResWidth);
} else {
cropComponent = parseFloat(cropStringComponent);
cropComponent = Math.min(Math.round(cropComponent), (0, _appState.appState).settings.cropResHeight);
}
return cropComponent;
});
return cropArray;
}
function getVideoScaledCropComponentsFromCropString(cropString) {
const cropComponents = getCropComponents(cropString);
return getVideoScaledCropComponents(cropComponents);
}
function getVideoScaledCropComponents(cropComponents) {
const [x, y, w, h] = cropComponents;
const videoWidth = (0, _appState.appState).video.videoWidth;
const videoHeight = (0, _appState.appState).video.videoHeight;
return [
videoWidth * (x / (0, _appState.appState).settings.cropResWidth),
videoHeight * (y / (0, _appState.appState).settings.cropResHeight),
videoWidth * (w / (0, _appState.appState).settings.cropResWidth),
videoHeight * (h / (0, _appState.appState).settings.cropResHeight)
];
}
function rotateCropComponentsClockWise(cropComponents, maxHeight) {
maxHeight ??= (0, _appState.appState).settings.cropResHeight;
let [x, y, w, h] = cropComponents;
y = maxHeight - (y + h);
[x, y, w, h] = [
y,
x,
h,
w
];
return [
x,
y,
w,
h
];
}
function rotateCropComponentsCounterClockWise(cropComponents, maxWidth) {
maxWidth ??= (0, _appState.appState).settings.cropResWidth;
let [x, y, w, h] = cropComponents;
x = maxWidth - (x + w);
[x, y, w, h] = [
y,
x,
h,
w
];
return [
x,
y,
w,
h
];
}
function getRotatedCropComponents(cropComponents, maxWidth, maxHeight) {
let [x, y, w, h] = cropComponents;
if ((0, _appState.appState).rotation === 90) [x, y, w, h] = rotateCropComponentsClockWise([
x,
y,
w,
h
], maxWidth);
else if ((0, _appState.appState).rotation === -90) [x, y, w, h] = rotateCropComponentsCounterClockWise([
x,
y,
w,
h
], maxHeight);
return [
x,
y,
w,
h
];
}
function getRotatedCropString(cropString) {
let [x, y, w, h] = getCropComponents(cropString);
[x, y, w, h] = getRotatedCropComponents([
x,
y,
w,
h
]);
return (0, _util.getCropString)(x, y, w, h);
}
function getNumericCropString(cropString) {
const [x, y, w, h] = getCropComponents(cropString);
return (0, _util.getCropString)(x, y, w, h);
}
function isStaticCrop(cropMap) {
return cropMap.length === 2 && cropStringsEqual(cropMap[0].crop, cropMap[1].crop);
}
function cropStringsEqual(a, b) {
const [ax, ay, aw, ah] = getCropComponents(a);
const [bx, by, bw, bh] = getCropComponents(b);
return ax === bx && ay === by && aw === bw && ah === bh;
}
function getCropMultiples(oldCropRes, newCropRes) {
const [oldWidth, oldHeight] = oldCropRes.split('x').map((str)=>parseInt(str), 10);
const [newWidth, newHeight] = newCropRes.split('x').map((str)=>parseInt(str), 10);
const cropMultipleX = newWidth / oldWidth;
const cropMultipleY = newHeight / oldHeight;
return {
cropMultipleX,
cropMultipleY,
newWidth,
newHeight
};
}
function multiplyCropString(cropMultipleX, cropMultipleY, cropString) {
const [xs, ys, ws, hs] = cropString.split(':');
const mx = String(Math.round(parseFloat(xs) * cropMultipleX));
const my = String(Math.round(parseFloat(ys) * cropMultipleY));
const mw = ws !== 'iw' ? String(Math.round(parseFloat(ws) * cropMultipleX)) : ws;
const mh = hs !== 'ih' ? String(Math.round(parseFloat(hs) * cropMultipleY)) : hs;
return [
mx,
my,
mw,
mh
].join(':');
}
function multiplyMarkerPairCrops(markerPair, cropMultipleX, cropMultipleY) {
markerPair.cropRes = (0, _appState.appState).settings.cropRes;
const draft = (0, _immer.createDraft)((0, _undoredo.getMarkerPairHistory)(markerPair));
draft.cropMap.forEach((cropPoint, idx)=>{
const multipliedCropString = multiplyCropString(cropMultipleX, cropMultipleY, cropPoint.crop);
cropPoint.crop = multipliedCropString;
if (idx === 0) draft.crop = multipliedCropString;
});
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair, false);
}
function setCropComponentForAllPoints(newCrop, draftCropMap, initialCropMap) {
draftCropMap.forEach((cropPoint, i)=>{
if (i === (0, _appState.appState).currentCropPointIndex) return;
const initCrop = initialCropMap[i].crop;
const [ix, iy, iw, ih] = getCropComponents(initCrop ?? cropPoint.crop);
const nw = newCrop.w ?? iw;
const nh = newCrop.h ?? ih;
const nx = newCrop.x ?? (0, _util.clampNumber)(ix, 0, (0, _appState.appState).settings.cropResWidth - nw);
const ny = newCrop.y ?? (0, _util.clampNumber)(iy, 0, (0, _appState.appState).settings.cropResHeight - nh);
cropPoint.crop = `${nx}:${ny}:${nw}:${nh}`;
});
}
function setAspectRatioForAllPoints(aspectRatio, draftCropMap, initialCropMap, referencePointIndex = (0, _appState.appState).currentCropPointIndex) {
(0, _crop.Crop).shouldConstrainMinDimensions = false;
const cropResWidth = (0, _appState.appState).settings.cropResWidth;
const cropResHeight = (0, _appState.appState).settings.cropResHeight;
draftCropMap.forEach((cropPoint, i)=>{
if (i === referencePointIndex) return;
const initCrop = initialCropMap[i].crop;
const [ix, iy, iw, ih] = getCropComponents(initCrop ?? cropPoint.crop);
const crop = new (0, _crop.Crop)(0, 0, 0, 0, cropResWidth, cropResHeight);
crop.defaultAspectRatio = aspectRatio;
if (ih >= iw) crop.resizeSAspectRatioLocked(ih);
else crop.resizeEAspectRatioLocked(iw);
crop.panX(ix);
crop.panY(iy);
cropPoint.crop = crop.cropString;
});
(0, _crop.Crop).shouldConstrainMinDimensions = true;
}
function getDefaultCropRes() {
const cropResWidth = (0, _appState.appState).videoInfo.isVerticalVideo ? Math.round(1920 * (0, _appState.appState).videoInfo.aspectRatio) : 1920;
const cropResHeight = (0, _appState.appState).videoInfo.isVerticalVideo ? 1920 : Math.round(1920 / (0, _appState.appState).videoInfo.aspectRatio);
const cropRes = `${cropResWidth}x${cropResHeight}`;
return {
cropResWidth,
cropResHeight,
cropRes
};
}
function setCropInputValue(cropString) {
if (!(0, _settingsEditor.cropInput)) return;
const rotatedCropString = getRotatedCropString(cropString);
if (rotatedCropString !== cropString && (0, _settingsEditor.cropInputLabel)) (0, _settingsEditor.cropInputLabel).textContent = `Crop (Rotated: ${rotatedCropString})`;
(0, _settingsEditor.cropInput).value = cropString;
}
function setCropString(markerPair, newCrop, forceCropConstraints = false) {
const prevCrop = markerPair.cropMap[(0, _appState.appState).currentCropPointIndex].crop;
const { isDynamicCrop, enableZoomPan, initCropMap } = (0, _charts.getCropMapProperties)();
const shouldMaintainCropAspectRatio = enableZoomPan && isDynamicCrop;
const crop = (0, _cropOverlay.transformCropWithPushBack)(prevCrop, newCrop, shouldMaintainCropAspectRatio);
updateCropString(crop, true, forceCropConstraints, initCropMap ?? undefined);
}
function multiplyAllCrops(cropMultipleX, cropMultipleY) {
const cropString = (0, _appState.appState).settings.newMarkerCrop;
const multipliedCropString = multiplyCropString(cropMultipleX, cropMultipleY, cropString);
(0, _appState.appState).settings.newMarkerCrop = multipliedCropString;
setCropInputValue(multipliedCropString);
(0, _appState.appState).markerPairs.forEach((markerPair)=>{
multiplyMarkerPairCrops(markerPair, cropMultipleX, cropMultipleY);
});
}
function getRelevantCropString() {
if (!(0, _appState.appState).isSettingsEditorOpen) return (0, _appState.appState).settings.newMarkerCrop;
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen) return (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex].cropMap[(0, _appState.appState).currentCropPointIndex].crop;
else return (0, _appState.appState).settings.newMarkerCrop;
}
function updateCropStringWithCrop(crop, shouldRerenderCharts = false, forceCropConstraints = false, initCropMap) {
let newCropString;
if ((0, _appState.appState).rotation === 90) newCropString = crop.rotatedCropStringCounterClockWise;
else if ((0, _appState.appState).rotation === -90) newCropString = crop.rotatedCropStringClockWise;
else newCropString = crop.cropString;
updateCropString(newCropString, shouldRerenderCharts, forceCropConstraints, initCropMap);
}
let lastRenderedCropString = null;
function updateAllMarkerPairCrops(newCrop) {
(0, _appState.appState).markerPairs.forEach((markerPair)=>{
const draft = (0, _immer.createDraft)((0, _undoredo.getMarkerPairHistory)(markerPair));
const cropMap = draft.cropMap;
if (isStaticCrop(cropMap)) {
draft.crop = newCrop;
cropMap[0].crop = newCrop;
cropMap[1].crop = newCrop;
}
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
});
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const cropMap = markerPair.cropMap;
if (isStaticCrop(cropMap)) {
setCropInputValue(newCrop);
(0, _charts.renderSpeedAndCropUI)();
}
}
(0, _util.flashMessage)(`All static marker crops updated to ${newCrop}`, 'olive');
}
function updateCropString(cropString, shouldRerenderCharts = false, forceCropConstraints = false, initCropMap) {
if (!(0, _appState.appState).isSettingsEditorOpen) throw new Error('No editor was open when trying to update crop.');
let draft;
const [nx, ny, nw, nh] = getCropComponents(cropString);
cropString = (0, _util.getCropString)(nx, ny, nw, nh);
let wasDynamicCrop = false; // eslint-disable-line no-useless-assignment
let enableZoomPan = false; // eslint-disable-line no-useless-assignment
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
enableZoomPan = markerPair.enableZoomPan;
const initState = (0, _undoredo.getMarkerPairHistory)(markerPair);
draft = (0, _immer.createDraft)(initState);
if (initCropMap == null) throw new Error('No initial crop map given when modifying marker pair crop.');
const draftCropMap = draft.cropMap;
wasDynamicCrop = !isStaticCrop(initCropMap) || initCropMap.length === 2 && (0, _appState.appState).currentCropPointIndex === 1;
const draftCropPoint = draftCropMap[(0, _appState.appState).currentCropPointIndex];
const initCrop = initCropMap[(0, _appState.appState).currentCropPointIndex].crop;
if (initCrop == null) throw new Error('Init crop undefined.');
draftCropPoint.crop = cropString;
if (wasDynamicCrop) {
if (!enableZoomPan || forceCropConstraints) setCropComponentForAllPoints({
w: nw,
h: nh
}, draftCropMap, initCropMap);
else if (enableZoomPan || forceCropConstraints) {
const aspectRatio = nw / nh;
setAspectRatioForAllPoints(aspectRatio, draftCropMap, initCropMap);
}
}
const maxIndex = draftCropMap.length - 1;
const isSecondLastPoint = (0, _appState.appState).currentCropPointIndex === maxIndex - 1;
const isLastSectionStatic = cropStringsEqual(initCrop, initCropMap[maxIndex].crop);
if (isSecondLastPoint && isLastSectionStatic) draftCropMap[maxIndex].crop = cropString;
draft.crop = draftCropMap[0].crop;
} else (0, _appState.appState).settings.newMarkerCrop = cropString;
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair, shouldRerenderCharts);
}
if (cropString !== lastRenderedCropString || shouldRerenderCharts) {
lastRenderedCropString = cropString;
(0, _charts.renderSpeedAndCropUI)(shouldRerenderCharts);
}
}
},{"immer":"R6AMM","./crop/crop":"axPMI","./appState":"g0AlP","./util/util":"99arg","./util/undoredo":"7UuTl","./features/settings/settings-editor":"jDViX","./charts":"hBxwj","./crop-overlay":"6s727","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"axPMI":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "Crop", ()=>Crop);
parcelHelpers.export(exports, "getCropSize", ()=>getCropSize);
parcelHelpers.export(exports, "getMinMaxAvgCropPoint", ()=>getMinMaxAvgCropPoint);
parcelHelpers.export(exports, "isVariableSize", ()=>isVariableSize);
var _util = require("../util/util");
class Crop {
static{
this.minX = 0;
}
static{
this.minY = 0;
}
static{
this._minW = 20;
}
static{
this._minH = 20;
}
static{
this.shouldConstrainMinDimensions = true;
}
static get minW() {
return Crop.shouldConstrainMinDimensions ? Crop._minW : 0;
}
static get minH() {
return Crop.shouldConstrainMinDimensions ? Crop._minH : 0;
}
constructor(_x, _y, _w, _h, maxW, maxH// private _minW: number, // private _minH: number
){
this._x = _x;
this._y = _y;
this._w = _w;
this._h = _h;
this.maxW = maxW;
this.maxH = maxH;
this._defaultAspectRatio = 1;
this._x = Math.max(Crop.minX, _x);
this._y = Math.max(Crop.minY, _y);
// this._minW = Crop.minW;
// this._minW = Crop.minW;
this.maxW = Math.max(Crop.minW, maxW);
this.maxH = Math.max(Crop.minH, maxH);
this._w = (0, _util.clampNumber)(_w, Crop.minW, this.maxW);
this._h = (0, _util.clampNumber)(_h, Crop.minH, this.maxH);
}
static fromCropString(cropString, cropRes) {
const [x, y, w, h] = Crop.getCropComponents(cropString);
const [maxW, maxH] = Crop.getMaxDimensions(cropRes);
return new this(x, y, w, h, maxW, maxH);
}
get cropString() {
return this.cropComponents.join(':');
}
set cropString(cropString) {
[this._x, this._y, this._w, this._h] = Crop.getCropComponents(cropString);
}
get rotatedCropStringClockWise() {
let [x, y, w, h] = this.cropComponents;
// bottom edge
y = this.maxH - (y + h);
[x, y, w, h] = [
y,
x,
h,
w
];
return [
x,
y,
w,
h
].join(':');
}
get rotatedCropStringCounterClockWise() {
let [x, y, w, h] = this.cropComponents;
// right edge
x = this.maxW - (x + w);
[x, y, w, h] = [
y,
x,
h,
w
];
return [
x,
y,
w,
h
].join(':');
}
setCropStringSafe(cropString, shouldMaintainCropAspectRatio = false) {
const [nx, ny, nw, nh] = Crop.getCropComponents(cropString);
const isDrag = nw === this._w && nh === this._h;
const maxX = isDrag ? this.maxW - this._w : this.maxW - Crop.minW;
const maxY = isDrag ? this.maxH - this._h : this.maxH - Crop.minH;
const cx = (0, _util.clampNumber)(nx, Crop.minX, maxX);
const cy = (0, _util.clampNumber)(ny, Crop.minY, maxY);
const maxW = this.maxW - cx;
const maxH = this.maxH - cy;
let cw = isDrag ? this._w : (0, _util.clampNumber)(nw, Crop.minW, maxW);
let ch = isDrag ? this._h : (0, _util.clampNumber)(nh, Crop.minH, maxH);
if (shouldMaintainCropAspectRatio) {
const ar = this.aspectRatio;
const ph = Math.floor(cw / ar);
const pw = Math.floor(ch * ar);
const phWithinBounds = Crop.minH <= ph && ph <= this.maxH;
const pwWithinBounds = Crop.minW <= pw && pw <= this.maxW;
if (!phWithinBounds && !pwWithinBounds) throw new Error('Could not determine a valid aspect-ratio-constrained crop.');
if (phWithinBounds) ch = ph;
else cw = pw;
}
this.cropString = (0, _util.getCropString)(cx, cy, cw, ch);
}
static getCropComponents(cropString, cropRes) {
let maxW, maxH;
if (cropRes != null) [maxW, maxH] = Crop.getMaxDimensions(cropRes);
const cropArr = cropString.split(':').map((cropComponent)=>{
if (cropComponent === 'iw') return maxW;
if (cropComponent === 'ih') return maxH;
return parseInt(cropComponent, 10);
});
return cropArr;
}
static getMaxDimensions(cropRes) {
const maxDimensions = cropRes.split('x').map((dim)=>parseInt(dim, 10));
return maxDimensions;
}
static getMultipliedCropRes(cropRes, cropMultipleX, cropMultipleY) {
let [maxW, maxH] = Crop.getMaxDimensions(cropRes);
maxW = maxW * cropMultipleX;
maxH = maxH * cropMultipleY;
return `${maxW}x${maxH}`;
}
get cropComponents() {
return [
this._x,
this._y,
this._w,
this._h
];
}
get x() {
return this._x;
}
get y() {
return this._y;
}
get w() {
return this._w;
}
get h() {
return this._h;
}
get r() {
return this._x + this._w;
}
get b() {
return this._y + this._h;
}
panX(delta) {
delta = (0, _util.clampNumber)(delta, -this._x, this.maxW - this.r);
this._x += delta;
}
panY(delta) {
delta = (0, _util.clampNumber)(delta, -this._y, this.maxH - this.b);
this._y += delta;
}
set defaultAspectRatio(aspectRatio) {
this._defaultAspectRatio = aspectRatio;
}
get aspectRatio() {
return this._w == 0 || this._h == 0 ? this._defaultAspectRatio : this._w / this._h;
}
get minResizeS() {
return -(this.b - (this._y + Crop.minH));
}
get maxResizeS() {
return this.maxH - this.b;
}
get minResizeE() {
return -(this.r - (this._x + Crop.minW));
}
get maxResizeE() {
return this.maxW - this.r;
}
get minResizeN() {
return -(this.b - Crop.minH - this._y);
}
get maxResizeN() {
return this._y;
}
get minResizeW() {
return -(this.r - Crop.minW - this._x);
}
get maxResizeW() {
return this._x;
}
get cx() {
return this._x + this._w / 2;
}
get cy() {
return this._y + this._h / 2;
}
clampResizeN(delta) {
delta = (0, _util.clampNumber)(delta, this.minResizeN, this.maxResizeN);
return delta;
}
clampResizeE(delta) {
delta = (0, _util.clampNumber)(delta, this.minResizeE, this.maxResizeE);
return delta;
}
clampResizeS(delta) {
delta = (0, _util.clampNumber)(delta, this.minResizeS, this.maxResizeS);
return delta;
}
clampResizeW(delta) {
delta = (0, _util.clampNumber)(delta, this.minResizeW, this.maxResizeW);
return delta;
}
resizeN(delta, shouldClamp = true) {
if (shouldClamp) delta = this.clampResizeN(delta);
this._y -= delta;
this._h += delta;
return delta;
}
resizeW(delta, shouldClamp = true) {
if (shouldClamp) delta = (0, _util.clampNumber)(delta, this.minResizeW, this.maxResizeW);
this._x -= delta;
this._w += delta;
return delta;
}
resizeS(delta, shouldClamp = true) {
if (shouldClamp) delta = (0, _util.clampNumber)(delta, this.minResizeS, this.maxResizeS);
this._h += delta;
return delta;
}
resizeE(delta, shouldClamp = true) {
if (shouldClamp) delta = (0, _util.clampNumber)(delta, this.minResizeE, this.maxResizeE);
this._w += delta;
return delta;
}
resizeNE(deltaY, deltaX) {
this.resizeN(deltaY);
this.resizeE(deltaX);
}
resizeSE(deltaY, deltaX) {
this.resizeS(deltaY);
this.resizeE(deltaX);
}
resizeSW(deltaY, deltaX) {
this.resizeS(deltaY);
this.resizeW(deltaX);
}
resizeNW(deltaY, deltaX) {
this.resizeN(deltaY);
this.resizeW(deltaX);
}
resizeNS(delta) {
if (delta >= 0) {
delta = this.clampResizeN(delta);
delta = this.clampResizeS(delta);
} else delta = Math.max(delta, -(this.b - this.cy - Crop.minH / 2));
this.resizeN(delta, false);
this.resizeS(delta, false);
}
resizeEW(delta) {
if (delta >= 0) {
delta = this.clampResizeE(delta);
delta = this.clampResizeW(delta);
} else delta = Math.max(delta, -(this.r - this.cx - Crop.minW / 2));
this.resizeE(delta, false);
this.resizeW(delta, false);
}
resizeNESW(deltaY, deltaX) {
if (deltaY >= 0) {
deltaY = this.clampResizeN(deltaY);
deltaY = this.clampResizeS(deltaY);
} else deltaY = Math.max(deltaY, -(this.b - this.cy - Crop.minH / 2));
if (deltaX >= 0) {
deltaX = this.clampResizeE(deltaX);
deltaX = this.clampResizeW(deltaX);
} else deltaX = Math.max(deltaX, -(this.r - this.cx - Crop.minW / 2));
this.resizeN(deltaY, false);
this.resizeS(deltaY, false);
this.resizeE(deltaX, false);
this.resizeW(deltaX, false);
}
resizeNAspectRatioLocked(delta) {
const aspectRatio = this.aspectRatio;
delta = this.clampResizeN(delta);
delta *= aspectRatio;
delta = Math.round(delta);
delta = this.resizeE(delta);
delta /= aspectRatio;
delta = Math.round(delta);
this.resizeN(delta);
}
resizeEAspectRatioLocked(delta) {
const aspectRatio = this.aspectRatio;
delta = this.clampResizeE(Math.round(delta));
delta /= aspectRatio;
delta = Math.round(delta);
delta = this.resizeS(Math.round(delta));
delta *= aspectRatio;
delta = Math.round(delta);
this.resizeE(delta);
}
resizeSAspectRatioLocked(delta) {
const aspectRatio = this.aspectRatio;
delta = this.clampResizeS(delta);
delta *= aspectRatio;
delta = Math.round(delta);
delta = this.resizeE(delta);
delta /= aspectRatio;
delta = Math.round(delta);
this.resizeS(delta);
}
resizeWAspectRatioLocked(delta) {
const aspectRatio = this.aspectRatio;
delta = this.clampResizeW(delta);
delta /= aspectRatio;
delta = Math.round(delta);
delta = this.resizeS(delta);
delta *= aspectRatio;
delta = Math.round(delta);
this.resizeW(delta);
}
get aspectRatioPair() {
const a = this.aspectRatio / (this.aspectRatio + 1);
const b = 1 - a;
return [
a,
b
];
}
resizeSEAspectRatioLocked(deltaY, deltaX) {
const [a, b] = this.aspectRatioPair;
deltaX *= a;
deltaY *= b;
deltaY += deltaX / this.aspectRatio;
deltaY = this.clampResizeS(deltaY);
deltaX = deltaY * this.aspectRatio;
deltaX = Math.round(deltaX);
deltaX = this.clampResizeE(deltaX);
deltaY = deltaX / this.aspectRatio;
deltaY = Math.round(deltaY);
deltaY = this.clampResizeS(deltaY);
this.resizeS(deltaY, false);
this.resizeE(deltaX, false);
}
resizeSWAspectRatioLocked(deltaY, deltaX) {
const [a, b] = this.aspectRatioPair;
deltaX *= a;
deltaY *= b;
deltaY += deltaX / this.aspectRatio;
deltaY = this.clampResizeS(deltaY);
deltaX = deltaY * this.aspectRatio;
deltaX = Math.round(deltaX);
deltaX = this.clampResizeW(deltaX);
deltaY = deltaX / this.aspectRatio;
deltaY = Math.round(deltaY);
deltaY = this.clampResizeS(deltaY);
this.resizeS(deltaY, false);
this.resizeW(deltaX, false);
}
resizeNEAspectRatioLocked(deltaY, deltaX) {
const [a, b] = this.aspectRatioPair;
deltaX *= a;
deltaY *= b;
deltaY += deltaX / this.aspectRatio;
deltaY = this.clampResizeN(deltaY);
deltaX = deltaY * this.aspectRatio;
deltaX = Math.round(deltaX);
deltaX = this.clampResizeE(deltaX);
deltaY = deltaX / this.aspectRatio;
deltaY = Math.round(deltaY);
deltaY = this.clampResizeN(deltaY);
this.resizeN(deltaY, false);
this.resizeE(deltaX, false);
}
resizeNWAspectRatioLocked(deltaY, deltaX) {
const [a, b] = this.aspectRatioPair;
deltaX *= a;
deltaY *= b;
deltaY += deltaX / this.aspectRatio;
deltaY = this.clampResizeN(deltaY);
deltaX = deltaY * this.aspectRatio;
deltaX = Math.round(deltaX);
deltaX = this.clampResizeW(deltaX);
deltaY = deltaX / this.aspectRatio;
deltaY = Math.round(deltaY);
deltaY = this.clampResizeN(deltaY);
this.resizeN(deltaY, false);
this.resizeW(deltaX, false);
}
resizeNESWAspectRatioLocked(deltaY, deltaX) {
const [a, b] = this.aspectRatioPair;
const isExpand = Math.abs(deltaX) > Math.abs(deltaY) ? deltaX >= 0 : deltaY >= 0;
deltaX *= a;
deltaY *= b;
if (isExpand) {
deltaY += deltaX / this.aspectRatio;
deltaY = this.clampResizeN(deltaY);
deltaY = this.clampResizeS(deltaY);
deltaX = deltaY * this.aspectRatio;
deltaX = Math.round(deltaX);
deltaX = this.clampResizeE(deltaX);
deltaX = this.clampResizeW(deltaX);
deltaY = deltaX / this.aspectRatio;
deltaY = Math.round(deltaY);
deltaY = this.clampResizeN(deltaY);
deltaY = this.clampResizeS(deltaY);
} else {
deltaY += deltaX / this.aspectRatio;
deltaY = Math.max(deltaY, -(this.b - this.cy - Crop.minH / 2));
deltaX = deltaY * this.aspectRatio;
deltaX = Math.round(deltaX);
deltaX = Math.max(deltaX, -(this.r - this.cx - Crop.minW / 2));
deltaY = deltaX / this.aspectRatio;
deltaY = Math.round(deltaY);
deltaY = Math.max(deltaY, -(this.b - this.cy - Crop.minH / 2));
}
this.resizeN(deltaY, false);
this.resizeE(deltaX, false);
this.resizeS(deltaY, false);
this.resizeW(deltaX, false);
}
}
function getCropSize(crop, cropRes) {
const [, , w, h] = Crop.getCropComponents(crop, cropRes);
const size = w * h;
const aspectRatio = w / h;
return {
w,
h,
size,
aspectRatio
};
}
function getMinMaxAvgCropPoint(cropMap, cropRes) {
const { aspectRatio } = getCropSize(cropMap[0].crop, cropRes);
let [minSize, minSizeW, minSizeH] = [
Infinity,
Infinity,
Infinity
];
let [maxSize, maxSizeW, maxSizeH] = [
-Infinity,
-Infinity,
-Infinity
];
let avgSizeW = 0;
cropMap.forEach((cropPoint, i)=>{
const { w, h, size } = getCropSize(cropPoint.crop, cropRes);
if (size < minSize) [minSizeW, minSizeH, minSize] = [
w,
h,
size
];
if (size > maxSize) [maxSizeW, maxSizeH, maxSize] = [
w,
h,
size
];
avgSizeW += (w - avgSizeW) / (i + 1);
});
const avgSizeH = Math.floor(avgSizeW / aspectRatio);
avgSizeW = Math.floor(avgSizeW);
const avgSize = avgSizeW * avgSizeH;
return {
minSizeW,
minSizeH,
minSize,
maxSizeW,
maxSizeH,
maxSize,
avgSizeW,
avgSizeH,
avgSize
};
}
function isVariableSize(cropMap, cropRes) {
const { size } = getCropSize(cropMap[0].crop, cropRes);
const isVariableSize = cropMap.some((cropPoint)=>{
return size !== getCropSize(cropPoint.crop, cropRes).size;
});
return isVariableSize;
}
},{"../util/util":"99arg","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"7UuTl":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "pushState", ()=>pushState);
parcelHelpers.export(exports, "undo", ()=>undo);
parcelHelpers.export(exports, "redo", ()=>redo);
parcelHelpers.export(exports, "peekLastState", ()=>peekLastState);
parcelHelpers.export(exports, "getMarkerPairHistory", ()=>getMarkerPairHistory);
parcelHelpers.export(exports, "saveMarkerPairHistory", ()=>saveMarkerPairHistory);
var _immer = require("immer");
const historySize = 100;
function pushState(undoredo, state) {
undoredo.history.splice(undoredo.index + 1);
undoredo.history.push(state);
if (undoredo.history.length > historySize) undoredo.history.shift();
else undoredo.index++;
}
var RestoreDirection = /*#__PURE__*/ function(RestoreDirection) {
RestoreDirection[RestoreDirection["undo"] = 0] = "undo";
RestoreDirection[RestoreDirection["redo"] = 1] = "redo";
return RestoreDirection;
}(RestoreDirection || {});
function undo(undoredo, restore) {
if (undoredo.index <= 0) return null;
else {
undoredo.index--;
const state = undoredo.history[undoredo.index];
restore(state, 0);
return state;
}
}
function redo(undoredo, restore) {
if (undoredo.index >= undoredo.history.length - 1) return null;
else {
undoredo.index++;
const state = undoredo.history[undoredo.index];
restore(state, 1);
return state;
}
}
function peekLastState(undoredo) {
const state = undoredo.history[undoredo.index];
return state;
}
function getMarkerPairHistory(markerPair) {
const { start, end, speed, speedMap, crop, cropMap, enableZoomPan, cropRes } = markerPair;
const history = {
start,
end,
speed,
speedMap,
crop,
cropMap,
enableZoomPan,
cropRes
};
return history;
}
function saveMarkerPairHistory(draft, markerPair, storeHistory = true) {
const newState = (0, _immer.finishDraft)(draft);
Object.assign(markerPair, newState);
if (storeHistory) pushState(markerPair.undoredo, newState);
}
},{"immer":"R6AMM","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"jDViX":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "gateHotkeys", ()=>gateHotkeys);
parcelHelpers.export(exports, "createBindings", ()=>createBindings);
parcelHelpers.export(exports, "deleteSettingsEditor", ()=>deleteSettingsEditor);
parcelHelpers.export(exports, "isExtraSettingsEditorEnabled", ()=>isExtraSettingsEditorEnabled);
parcelHelpers.export(exports, "toggleMarkerPairOverridesEditor", ()=>toggleMarkerPairOverridesEditor);
parcelHelpers.export(exports, "cropInputLabel", ()=>cropInputLabel);
parcelHelpers.export(exports, "setCropInputLabel", ()=>setCropInputLabel);
parcelHelpers.export(exports, "cropInput", ()=>cropInput);
parcelHelpers.export(exports, "setCropInput", ()=>setCropInput);
parcelHelpers.export(exports, "enableZoomPanInput", ()=>enableZoomPanInput);
parcelHelpers.export(exports, "setEnableZoomPanInput", ()=>setEnableZoomPanInput);
parcelHelpers.export(exports, "cropAspectRatioSpan", ()=>cropAspectRatioSpan);
parcelHelpers.export(exports, "setCropAspectRatioSpan", ()=>setCropAspectRatioSpan);
parcelHelpers.export(exports, "highlightModifiedSettings", ()=>highlightModifiedSettings);
parcelHelpers.export(exports, "presetsMap", ()=>(0, _presets.presetsMap));
parcelHelpers.export(exports, "updateSettingsValue", ()=>updateSettingsValue);
parcelHelpers.export(exports, "addCropInputHotkeys", ()=>addCropInputHotkeys);
parcelHelpers.export(exports, "commandPaletteToggleButton", ()=>commandPaletteToggleButton);
parcelHelpers.export(exports, "injectToggleCommandPaletteButton", ()=>injectToggleCommandPaletteButton);
parcelHelpers.export(exports, "showCommandPaletteToggleButton", ()=>showCommandPaletteToggleButton);
parcelHelpers.export(exports, "hideCommandPaletteToggleButton", ()=>hideCommandPaletteToggleButton);
parcelHelpers.export(exports, "hintsBarToggleButton", ()=>hintsBarToggleButton);
parcelHelpers.export(exports, "injectToggleHintsBarButton", ()=>injectToggleHintsBarButton);
parcelHelpers.export(exports, "showHintsBarToggleButton", ()=>showHintsBarToggleButton);
parcelHelpers.export(exports, "hideHintsBarToggleButton", ()=>hideHintsBarToggleButton);
parcelHelpers.export(exports, "shortcutsTableContainer", ()=>shortcutsTableContainer);
parcelHelpers.export(exports, "toggleShortcutsTable", ()=>toggleShortcutsTable);
parcelHelpers.export(exports, "arrowKeyCropAdjustmentEnabled", ()=>arrowKeyCropAdjustmentEnabled);
parcelHelpers.export(exports, "toggleArrowKeyCropAdjustment", ()=>toggleArrowKeyCropAdjustment);
parcelHelpers.export(exports, "arrowKeyCropAdjustmentHandler", ()=>arrowKeyCropAdjustmentHandler);
parcelHelpers.export(exports, "renderCropForm", ()=>renderCropForm);
parcelHelpers.export(exports, "highlightSpeedAndCropInputs", ()=>highlightSpeedAndCropInputs);
var _immer = require("immer");
var _commandPalette = require("../../../command-palette");
var _appState = require("../../appState");
var _charts = require("../../charts");
var _cropOverlay = require("../../crop-overlay");
var _cropUtils = require("../../crop-utils");
var _crop = require("../../crop/crop");
var _cropPreview = require("../../crop/crop-preview");
var _platforms = require("../../platforms/platforms");
var _presets = require("./presets");
var _speed = require("../../speed");
var _tooltips = require("../../ui/tooltips");
var _undoredo = require("../../util/undoredo");
var _util = require("../../util/util");
var _litHtml = require("lit-html");
var _toggleButton = require("../../ui/shortcuts-table/toggle-button");
var _toggleButton1 = require("../../ui/hints-bar/toggle-button");
var _hintsBar = require("../hints-bar/hints-bar");
var _ytClipper = require("../../yt_clipper");
function gateHotkeys(el) {
if (!el) return;
el.addEventListener('focus', disableHotkeys, false);
el.addEventListener('blur', enableHotkeys, false);
}
function disableHotkeys() {
(0, _appState.appState).isHotkeysEnabled = false;
}
function enableHotkeys() {
(0, _appState.appState).isHotkeysEnabled = true;
}
function createBindings(target) {
const collected = [];
return {
bind (id, field, type, opts) {
const highlightable = opts?.highlightable ?? true;
if (highlightable) collected.push({
id,
field,
type
});
return {
id,
onChange: (e)=>{
updateSettingsValue(e, id, target, field, type, highlightable);
opts?.afterChange?.(e);
}
};
},
all: ()=>collected
};
}
function deleteSettingsEditor() {
const settingsEditorDiv = document.getElementById('settings-editor-div');
(0, _util.assertDefined)(settingsEditorDiv, 'Settings editor div not found');
(0, _util.deleteElement)(settingsEditorDiv);
(0, _appState.appState).isSettingsEditorOpen = false;
(0, _appState.appState).wasGlobalSettingsEditorOpen = false;
(0, _appState.appState).markerHotkeysEnabled = false;
(0, _cropOverlay.hideCropOverlay)();
(0, _cropPreview.triggerCropPreviewRedraw)();
}
let isExtraSettingsEditorEnabled = false;
function toggleMarkerPairOverridesEditor() {
if ((0, _appState.appState).isSettingsEditorOpen) {
const markerPairOverridesEditor = document.getElementById('marker-pair-overrides');
if (markerPairOverridesEditor) {
if (markerPairOverridesEditor.style.display === 'none') {
markerPairOverridesEditor.style.display = 'block';
isExtraSettingsEditorEnabled = true;
} else {
markerPairOverridesEditor.style.display = 'none';
isExtraSettingsEditorEnabled = false;
}
}
const globalEncodeSettingsEditor = document.getElementById('global-encode-settings');
if (globalEncodeSettingsEditor) {
if (globalEncodeSettingsEditor.style.display === 'none') {
globalEncodeSettingsEditor.style.display = 'block';
isExtraSettingsEditorEnabled = true;
} else if (globalEncodeSettingsEditor.style.display === 'block') {
globalEncodeSettingsEditor.style.display = 'none';
isExtraSettingsEditorEnabled = false;
}
}
}
}
let cropInputLabel;
function setCropInputLabel(el) {
cropInputLabel = el;
}
let cropInput;
function setCropInput(el) {
cropInput = el;
}
let enableZoomPanInput;
function setEnableZoomPanInput(el) {
enableZoomPanInput = el;
}
let cropAspectRatioSpan;
function setCropAspectRatioSpan(el) {
cropAspectRatioSpan = el;
}
function highlightModifiedSettings(inputs, target) {
if ((0, _appState.appState).isSettingsEditorOpen) {
const markerPairSettingsLabelHighlight = 'marker-pair-settings-editor-highlighted-label';
const globalSettingsLabelHighlight = 'global-settings-editor-highlighted-label';
const inheritedSettingsLabelHighlight = 'inherited-settings-highlighted-label';
let markerPair;
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedMarkerPairIndex != null) markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
inputs.forEach((input)=>{
const { id, field: targetProperty, type: valueType } = input;
const inputElem = document.getElementById(id);
if (!inputElem) return;
const storedTargetValue = target[targetProperty];
let label = inputElem.previousElementSibling;
if (id === 'rotate-90-clock' || id === 'rotate-90-counterclock') label = inputElem.parentElement?.getElementsByTagName('span')[0] ?? null;
if (id === 'minterp-fps-multiplier-input') label = inputElem.closest('.fps-mul-stepper')?.parentElement?.querySelector('span') ?? null;
if (storedTargetValue == null) inputElem.classList.add(inheritedSettingsLabelHighlight);
else inputElem.classList.remove(inheritedSettingsLabelHighlight);
let shouldRemoveHighlight = storedTargetValue == null || storedTargetValue === '' || valueType === 'bool' && storedTargetValue === false;
if (target === (0, _appState.appState).settings) shouldRemoveHighlight ||= id === 'title-suffix-input' && storedTargetValue == `[${(0, _appState.appState).settings.videoID}]` || id === 'speed-input' && storedTargetValue === 1 || id === 'crop-input' && (storedTargetValue === '0:0:iw:ih' || storedTargetValue === `0:0:${(0, _appState.appState).settings.cropResWidth}:${(0, _appState.appState).settings.cropResHeight}`) || id === 'rotate-0';
if (shouldRemoveHighlight) {
label?.classList.remove(globalSettingsLabelHighlight);
label?.classList.remove(markerPairSettingsLabelHighlight);
return;
}
if (target === (0, _appState.appState).settings) label?.classList.add(globalSettingsLabelHighlight);
else {
let settingsProperty = targetProperty;
if (targetProperty === 'speed') settingsProperty = 'newMarkerSpeed';
if (targetProperty === 'crop') settingsProperty = 'newMarkerCrop';
const globalValue = (0, _appState.appState).settings[settingsProperty];
let shouldApplyGlobalHighlight = storedTargetValue === globalValue;
if (targetProperty === 'crop') {
shouldApplyGlobalHighlight = (0, _cropUtils.cropStringsEqual)(storedTargetValue, globalValue);
shouldApplyGlobalHighlight = shouldApplyGlobalHighlight && (0, _cropUtils.isStaticCrop)(markerPair.cropMap);
}
if (shouldApplyGlobalHighlight) {
label?.classList.add(globalSettingsLabelHighlight);
label?.classList.remove(markerPairSettingsLabelHighlight);
} else {
label?.classList.add(markerPairSettingsLabelHighlight);
label?.classList.remove(globalSettingsLabelHighlight);
}
}
});
}
}
function updateSettingsValue(e, id, target, targetProperty, valueType, highlightable) {
const inputTarget = e.target;
if (inputTarget.reportValidity()) {
const prevValue = inputTarget.value;
let newValue = inputTarget.value;
if (newValue != null) {
if (targetProperty !== 'titleSuffix' && targetProperty !== 'markerPairMergeList' && newValue === '') {
delete target[targetProperty];
newValue = undefined;
} else if (valueType === 'number') newValue = parseFloat(newValue);
else if (valueType === 'bool') {
if (newValue === 'Enabled') newValue = true;
else if (newValue === 'Disabled') newValue = false;
} else if (valueType === 'ternary' || valueType === 'inheritableString') {
if (newValue === 'Default' || newValue === 'Inherit') {
delete target[targetProperty];
newValue = undefined;
} else if (newValue === 'Enabled') newValue = true;
else if (newValue === 'Disabled') newValue = false;
} else if (valueType === 'preset') {
if (newValue === 'Inherit') {
delete target[targetProperty];
newValue = undefined;
} else newValue = (0, _presets.presetsMap)[targetProperty][newValue];
}
}
if (![
'crop',
'enableZoomPan',
'cropRes'
].includes(targetProperty)) target[targetProperty] = newValue;
if (targetProperty === 'newMarkerCrop') {
const newCrop = (0, _cropOverlay.transformCropWithPushBack)(prevValue, newValue);
(0, _cropUtils.updateCropString)(newCrop, true);
}
if (targetProperty === 'cropRes') {
const { cropMultipleX, cropMultipleY, newWidth, newHeight } = (0, _cropUtils.getCropMultiples)((0, _appState.appState).settings.cropRes, newValue);
(0, _appState.appState).settings.cropRes = newValue;
(0, _appState.appState).settings.cropResWidth = newWidth;
(0, _appState.appState).settings.cropResHeight = newHeight;
(0, _crop.Crop)._minW = Math.round((0, _crop.Crop).minW * cropMultipleX);
(0, _crop.Crop)._minH = Math.round((0, _crop.Crop).minH * cropMultipleY);
(0, _cropUtils.multiplyAllCrops)(cropMultipleX, cropMultipleY);
}
if (targetProperty === 'crop') {
const markerPair = target;
(0, _cropUtils.setCropString)(markerPair, newValue);
}
if (targetProperty === 'speed') {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
(0, _speed.updateMarkerPairSpeed)(markerPair, newValue);
(0, _charts.renderSpeedAndCropUI)();
}
if (targetProperty === 'enableZoomPan') {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const cropMap = markerPair.cropMap;
const draft = (0, _immer.createDraft)((0, _undoredo.getMarkerPairHistory)(markerPair));
const cropString = cropMap[(0, _appState.appState).currentCropPointIndex].crop;
const enableZoomPan = newValue;
const cropRes = (0, _appState.appState).settings.cropRes;
if (!enableZoomPan && (0, _crop.isVariableSize)(cropMap, cropRes)) {
(0, _appState.appState).video.pause();
const { minSizeW, minSizeH, maxSizeW, maxSizeH, avgSizeW, avgSizeH } = (0, _crop.getMinMaxAvgCropPoint)(cropMap, cropRes);
const crop = (0, _crop.Crop).fromCropString(cropString, (0, _appState.appState).settings.cropRes);
const tooltip = (0, _tooltips.Tooltips).zoomPanToPanOnlyTooltip(minSizeW, minSizeH, maxSizeW, maxSizeH, avgSizeW, avgSizeH);
const desiredSize = prompt(tooltip, 's');
let w;
let h;
switch(desiredSize){
case 's':
[w, h] = [
minSizeW,
minSizeH
];
break;
case 'l':
[w, h] = [
maxSizeW,
maxSizeH
];
break;
case 'a':
[w, h] = [
avgSizeW,
avgSizeH
];
break;
case null:
(0, _util.flashMessage)('Zoompan not disabled (canceled).', 'olive');
e.target.value = 'Enabled';
return;
default:
(0, _util.flashMessage)("Zoompan not disabled. Please enter 's' for smallest, 'l' for largest, or 'a' for average.", 'red');
e.target.value = 'Enabled';
return;
}
draft.enableZoomPan = false;
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair, false);
crop.setCropStringSafe((0, _util.getCropString)(crop.x, crop.y, w, h));
(0, _cropUtils.setCropString)(markerPair, crop.cropString, true);
(0, _util.flashMessage)(`Zoompan disabled. All crop points set to size ${w}x${h}.`, 'green');
} else {
draft.enableZoomPan = enableZoomPan;
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
(0, _charts.renderSpeedAndCropUI)();
}
}
}
if (highlightable) highlightModifiedSettings([
{
id,
field: targetProperty,
type: valueType
}
], target);
}
function addCropInputHotkeys() {
cropInput.addEventListener('keydown', (ke)=>{
if (ke.code === 'Space' || !ke.ctrlKey && !ke.altKey && ke.code.startsWith('Key') && ke.code >= 'KeyB' && ke.code <= 'KeyZ' && !(ke.code === 'KeyI' || ke.code === 'KeyW' || ke.code === 'KeyH') || ke.code === 'KeyA' && (ke.ctrlKey || ke.altKey) // blur on KeyA with ctrl or alt modifiers
) {
(0, _util.blockEvent)(ke);
cropInput.blur();
(0, _util.flashMessage)('Auto blurred crop input focus', 'olive');
return;
}
if (ke.code === 'ArrowUp' || ke.code === 'ArrowDown' || ke.code === 'KeyA' && !ke.ctrlKey && !ke.altKey) {
(0, _util.blockEvent)(ke);
const cropString = cropInput.value;
const cropStringArray = cropString.split(':');
const initialCropArray = (0, _cropUtils.getCropComponents)(cropString);
const cropArray = [
...initialCropArray
];
const cropStringCursorPos = ke.target.selectionStart ?? 0;
let cropComponentCursorPos = cropStringCursorPos;
let cropTarget = 0;
while(cropComponentCursorPos - (cropStringArray[cropTarget].length + 1) >= 0){
cropComponentCursorPos -= cropStringArray[cropTarget].length + 1;
cropTarget++;
}
const isValidCropTarget = cropTarget >= 0 && cropTarget <= cropArray.length - 1 && typeof cropArray[cropTarget] === 'number';
if (!isValidCropTarget) return;
if (ke.code === 'KeyA' && !(0, _appState.appState).wasGlobalSettingsEditorOpen) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const initState = (0, _undoredo.getMarkerPairHistory)(markerPair);
const draft = (0, _immer.createDraft)(initState);
const draftCropMap = draft.cropMap;
const { enableZoomPan } = (0, _charts.getCropMapProperties)();
const [ix, iy, iw, ih] = initialCropArray;
if (cropTarget === 0 || cropTarget === 1 || enableZoomPan && (cropTarget === 2 || cropTarget === 3)) {
draftCropMap.forEach((cropPoint, idx)=>{
if (!ke.shiftKey && idx <= (0, _appState.appState).currentCropPointIndex || ke.shiftKey && idx >= (0, _appState.appState).currentCropPointIndex) return;
let [x, y, w, h] = (0, _cropUtils.getCropComponents)(cropPoint.crop);
if (cropTarget === 0) x = ix;
if (cropTarget === 1) y = iy;
if (cropTarget === 2 || cropTarget === 3) {
w = iw;
h = ih;
}
cropPoint.crop = [
x,
y,
w,
h
].join(':');
if (idx === 0) draft.crop = cropPoint.crop;
});
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
(0, _charts.renderSpeedAndCropUI)();
}
const targetPointsMsg = `${ke.shiftKey ? 'preceding' : 'following'} point ${(0, _appState.appState).currentCropPointIndex + 1}`;
if (cropTarget === 0) (0, _util.flashMessage)(`Updated X values of crop points ${targetPointsMsg} to ${ix}`, 'green');
if (cropTarget === 1) (0, _util.flashMessage)(`Updated Y values crop points ${targetPointsMsg} Y values to ${iy}`, 'green');
if (enableZoomPan && (cropTarget === 2 || cropTarget === 3)) (0, _util.flashMessage)(`Updated size of all crop points ${targetPointsMsg} to ${iw}x${ih}`, 'green');
if (!enableZoomPan && (cropTarget === 2 || cropTarget === 3)) (0, _util.flashMessage)(`All crop points have the same size in pan-only mode`, 'olive');
} else if (ke.code === 'ArrowUp' || ke.code === 'ArrowDown') {
let changeAmount = 0;
const [ix, iy, iw, ih] = (0, _cropUtils.getCropComponents)(cropInput.value);
if (!ke.altKey && !ke.shiftKey) changeAmount = 10;
else if (ke.altKey && !ke.shiftKey) changeAmount = 1;
else if (!ke.altKey && ke.shiftKey) changeAmount = 50;
else if (ke.altKey && ke.shiftKey) changeAmount = 100;
const { isDynamicCrop, enableZoomPan } = (0, _charts.getCropMapProperties)();
const shouldMaintainCropAspectRatio = enableZoomPan && isDynamicCrop;
const cropResWidth = (0, _appState.appState).settings.cropResWidth;
const cropResHeight = (0, _appState.appState).settings.cropResHeight;
const crop = new (0, _crop.Crop)(ix, iy, iw, ih, cropResWidth, cropResHeight);
// without modifiers move crop x/y offset
// with ctrl key modifier expand/shrink crop width/height
if (cropTarget === 0) ke.code === 'ArrowUp' ? crop.panX(changeAmount) : crop.panX(-changeAmount);
else if (cropTarget === 1) ke.code === 'ArrowUp' ? crop.panY(changeAmount) : crop.panY(-changeAmount);
else {
let cursor = 'e-resize';
if (cropTarget === 2) cursor = 'e-resize';
if (cropTarget === 3) cursor = 's-resize';
if (ke.code === 'ArrowDown') changeAmount = -changeAmount;
(0, _cropOverlay.resizeCrop)(crop, cursor, changeAmount, changeAmount, shouldMaintainCropAspectRatio);
}
const { initCropMap } = (0, _charts.getCropMapProperties)();
(0, _cropUtils.updateCropString)(crop.cropString, true, false, initCropMap ?? undefined);
const updatedCropString = cropInput.value;
let newCursorPos = cropStringCursorPos - cropComponentCursorPos;
if (cropTarget === 3 && cropStringArray[3] === 'ih') {
const cropStringLengthDelta = updatedCropString.length - cropString.length;
const cursorPosAdjustment = cropStringLengthDelta - cropComponentCursorPos;
newCursorPos += cursorPosAdjustment;
}
cropInput.selectionStart = newCursorPos;
cropInput.selectionEnd = newCursorPos;
}
}
});
}
let commandPaletteToggleButton;
function injectToggleCommandPaletteButton() {
const container = document.createElement('div');
(0, _litHtml.render)((0, _toggleButton.shortcutsTableToggleButtonTemplate), container);
commandPaletteToggleButton = container.firstElementChild;
commandPaletteToggleButton.classList.add('yt-clipper-palette-button');
commandPaletteToggleButton.title = 'Open yt_clipper Command Palette (Ctrl+Shift+P)';
commandPaletteToggleButton.onclick = ()=>(0, _ytClipper.commandPalette)?.toggle();
if ([
(0, _platforms.VideoPlatforms).weverse,
(0, _platforms.VideoPlatforms).naver_tv
].includes((0, _ytClipper.platform))) commandPaletteToggleButton.classList.add('pzp-button', 'pzp-subtitle-button', 'pzp-pc-subtitle-button', 'pzp-pc__subtitle-button');
if ([
(0, _platforms.VideoPlatforms).afreecatv
].includes((0, _ytClipper.platform))) commandPaletteToggleButton.classList.add('btn_statistics');
if ((0, _ytClipper.platform) === (0, _platforms.VideoPlatforms).yt_clipper) {
const shortcutsTableButtonParent = (0, _appState.appState).hooks.shortcutsTableButton.parentElement;
(0, _util.assertDefined)(shortcutsTableButtonParent, 'shortcutsTableButton has no parentElement');
shortcutsTableButtonParent.insertBefore(commandPaletteToggleButton, (0, _appState.appState).hooks.shortcutsTableButton);
} else (0, _appState.appState).hooks.shortcutsTableButton.insertAdjacentElement('afterbegin', commandPaletteToggleButton);
}
function showCommandPaletteToggleButton() {
if (commandPaletteToggleButton) commandPaletteToggleButton.style.display = 'inline-block';
}
function hideCommandPaletteToggleButton() {
if (commandPaletteToggleButton) commandPaletteToggleButton.style.display = 'none';
}
let hintsBarToggleButton;
function injectToggleHintsBarButton() {
const container = document.createElement('div');
(0, _litHtml.render)((0, _toggleButton1.hintsBarToggleButtonTemplate), container);
hintsBarToggleButton = container.firstElementChild;
hintsBarToggleButton.classList.add('yt-clipper-hints-bar-button');
hintsBarToggleButton.onclick = ()=>(0, _hintsBar.toggleHintsBar)();
if ([
(0, _platforms.VideoPlatforms).weverse,
(0, _platforms.VideoPlatforms).naver_tv
].includes((0, _ytClipper.platform))) hintsBarToggleButton.classList.add('pzp-button', 'pzp-subtitle-button', 'pzp-pc-subtitle-button', 'pzp-pc__subtitle-button');
if ([
(0, _platforms.VideoPlatforms).afreecatv
].includes((0, _ytClipper.platform))) hintsBarToggleButton.classList.add('btn_statistics');
// Position relative to the command-palette button so the visible order
// is [command palette] [hints bar] on every platform regardless of
// which container is the actual insertion target. `injectToggle-
// CommandPaletteButton` runs first in `yt_clipper.ts`, so this is
// always set by the time we get here.
(0, _util.assertDefined)(commandPaletteToggleButton, 'commandPaletteToggleButton must be injected before the hints-bar toggle');
commandPaletteToggleButton.insertAdjacentElement('afterend', hintsBarToggleButton);
}
function showHintsBarToggleButton() {
if (hintsBarToggleButton) hintsBarToggleButton.style.display = 'inline-block';
}
function hideHintsBarToggleButton() {
if (hintsBarToggleButton) hintsBarToggleButton.style.display = 'none';
}
let shortcutsTableContainer;
function toggleShortcutsTable() {
if (!shortcutsTableContainer) {
(0, _ytClipper.initShortcutSystem)();
(0, _util.assertDefined)((0, _ytClipper.shortcutRegistry), 'shortcutRegistry must be initialized before rendering shortcuts table');
(0, _util.injectCSS)((0, _ytClipper.shortcutsTableStyle), 'shortcutsTableStyle');
shortcutsTableContainer = document.createElement('div');
shortcutsTableContainer.setAttribute('id', 'shortcutsTableContainer');
(0, _litHtml.render)((0, _commandPalette.renderShortcutsTable)((0, _ytClipper.shortcutRegistry)), shortcutsTableContainer);
(0, _appState.appState).hooks.shortcutsTable.insertAdjacentElement('beforebegin', shortcutsTableContainer);
} else if (shortcutsTableContainer.style.display !== 'none') shortcutsTableContainer.style.display = 'none';
else shortcutsTableContainer.style.display = 'block';
}
let arrowKeyCropAdjustmentEnabled = false;
function toggleArrowKeyCropAdjustment() {
if (arrowKeyCropAdjustmentEnabled) {
document.removeEventListener('keydown', arrowKeyCropAdjustmentHandler, true);
(0, _util.flashMessage)('Disabled crop adjustment with arrow keys', 'red');
arrowKeyCropAdjustmentEnabled = false;
} else {
document.addEventListener('keydown', arrowKeyCropAdjustmentHandler, true);
(0, _util.flashMessage)('Enabled crop adjustment with arrow keys', 'green');
arrowKeyCropAdjustmentEnabled = true;
}
}
function arrowKeyCropAdjustmentHandler(ke) {
if ((0, _appState.appState).isSettingsEditorOpen) {
if (cropInput !== document.activeElement && [
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight'
].includes(ke.code)) {
(0, _util.blockEvent)(ke);
const [ix, iy, iw, ih] = (0, _cropUtils.getCropComponents)(cropInput.value);
let changeAmount = 0;
if (!ke.altKey && !ke.shiftKey) changeAmount = 10;
else if (ke.altKey && !ke.shiftKey) changeAmount = 1;
else if (!ke.altKey && ke.shiftKey) changeAmount = 50;
else if (ke.altKey && ke.shiftKey) changeAmount = 100;
const { isDynamicCrop, enableZoomPan, initCropMap } = (0, _charts.getCropMapProperties)();
const shouldMaintainCropAspectRatio = enableZoomPan && isDynamicCrop;
const cropResWidth = (0, _appState.appState).settings.cropResWidth;
const cropResHeight = (0, _appState.appState).settings.cropResHeight;
const crop = new (0, _crop.Crop)(ix, iy, iw, ih, cropResWidth, cropResHeight);
// without modifiers move crop x/y offset
// with ctrl key modifier expand/shrink crop width/height
if (!ke.ctrlKey) switch(ke.code){
case 'ArrowUp':
crop.panY(-changeAmount);
break;
case 'ArrowDown':
crop.panY(changeAmount);
break;
case 'ArrowLeft':
crop.panX(-changeAmount);
break;
case 'ArrowRight':
crop.panX(changeAmount);
break;
}
else {
let cursor = 'e-resize';
switch(ke.code){
case 'ArrowUp':
cursor = 's-resize';
changeAmount = -changeAmount;
break;
case 'ArrowDown':
cursor = 's-resize';
break;
case 'ArrowLeft':
cursor = 'e-resize';
changeAmount = -changeAmount;
break;
case 'ArrowRight':
cursor = 'e-resize';
break;
}
(0, _cropOverlay.resizeCrop)(crop, cursor, changeAmount, changeAmount, shouldMaintainCropAspectRatio);
}
(0, _cropUtils.updateCropString)(crop.cropString, true, false, initCropMap ?? undefined);
}
}
}
function renderCropForm(crop) {
const [, , w, h] = (0, _cropUtils.getCropComponents)(crop);
(0, _cropUtils.setCropInputValue)(crop);
const cropAspectRatio = (w / h).toFixed(13);
cropAspectRatioSpan && (cropAspectRatioSpan.textContent = cropAspectRatio);
}
function highlightSpeedAndCropInputs() {
if ((0, _appState.appState).wasGlobalSettingsEditorOpen) highlightModifiedSettings([
{
id: 'crop-input',
field: 'newMarkerCrop',
type: 'string'
},
{
id: 'speed-input',
field: 'newMarkerSpeed',
type: 'number'
}
], (0, _appState.appState).settings);
else {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
highlightModifiedSettings([
{
id: 'crop-input',
field: 'crop',
type: 'string'
},
{
id: 'speed-input',
field: 'speed',
type: 'number'
},
{
id: 'enable-zoom-pan-input',
field: 'enableZoomPan',
type: 'bool'
}
], markerPair);
}
}
},{"immer":"R6AMM","../../../command-palette":"5ubiV","../../appState":"g0AlP","../../charts":"hBxwj","../../crop-overlay":"6s727","../../crop-utils":"k2gwb","../../crop/crop":"axPMI","../../crop/crop-preview":"9T0zg","../../platforms/platforms":"1kR7r","./presets":"9eKT1","../../speed":"6CgFD","../../ui/tooltips":"a02E8","../../util/undoredo":"7UuTl","../../util/util":"99arg","lit-html":"9fQBw","../../ui/shortcuts-table/toggle-button":"aFJlC","../../ui/hints-bar/toggle-button":"72Gvt","../hints-bar/hints-bar":"4nJHa","../../yt_clipper":"6vE65","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"5ubiV":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "ShortcutRegistry", ()=>(0, _registry.ShortcutRegistry));
parcelHelpers.export(exports, "HotkeyEngine", ()=>(0, _hotkeyEngine.HotkeyEngine));
parcelHelpers.export(exports, "CommandPalette", ()=>(0, _paletteUi.CommandPalette));
parcelHelpers.export(exports, "renderShortcutsTable", ()=>(0, _staticTable.renderShortcutsTable));
parcelHelpers.export(exports, "renderDisplayKey", ()=>(0, _staticTable.renderDisplayKey));
var _registry = require("./registry");
var _hotkeyEngine = require("./hotkey-engine");
var _paletteUi = require("./palette-ui");
var _staticTable = require("./static-table");
},{"./registry":"cjpan","./hotkey-engine":"f9iRH","./palette-ui":"53jFZ","./static-table":"5ZN0Y","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"cjpan":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "ShortcutRegistry", ()=>ShortcutRegistry);
var _fuzzysort = require("fuzzysort");
var _fuzzysortDefault = parcelHelpers.interopDefault(_fuzzysort);
class ShortcutRegistry {
register(def) {
if (this.byId.has(def.id)) throw new Error(`ShortcutRegistry: duplicate id "${def.id}"`);
def._prepared = (0, _fuzzysortDefault.default).prepare(def.description);
this.byId.set(def.id, def);
this.shortcuts.push(def);
}
registerAll(defs) {
for (const def of defs)this.register(def);
}
unregister(id) {
const def = this.byId.get(id);
if (!def) return;
this.byId.delete(id);
const idx = this.shortcuts.indexOf(def);
if (idx >= 0) this.shortcuts.splice(idx, 1);
}
getAll() {
return this.shortcuts.slice();
}
getById(id) {
return this.byId.get(id);
}
getGrouped() {
const result = new Map();
for (const def of this.shortcuts){
let byCategory = result.get(def.section);
if (!byCategory) {
byCategory = new Map();
result.set(def.section, byCategory);
}
let bucket = byCategory.get(def.category);
if (!bucket) {
bucket = [];
byCategory.set(def.category, bucket);
}
bucket.push(def);
}
return result;
}
getByBinding(code, modifiers) {
const matches = [];
for (const def of this.shortcuts){
if (!def.binding) continue;
if (def.binding.code !== code) continue;
if (!modifiersMatch(def.binding.modifiers, modifiers)) continue;
matches.push(def);
}
return matches;
}
search(query) {
const trimmed = query.trim();
if (trimmed === '') return this.shortcuts.map((shortcut)=>({
shortcut,
score: 0,
highlightRanges: []
}));
const normalizedQuery = normalizeDisplayKey(trimmed);
const seen = new Set();
const matches = [];
if (normalizedQuery !== '') {
const exactKeyMatches = [];
const partialKeyMatches = [];
for (const shortcut of this.shortcuts){
if (!shortcut.displayKey) continue;
const parts = splitDisplayKeyAlternatives(shortcut.displayKey);
let kind = null;
for (const part of parts){
const normalizedPart = normalizeDisplayKey(part);
if (normalizedPart === '') continue;
if (normalizedPart === normalizedQuery) {
kind = 'exactKey';
break;
}
if (kind == null && normalizedPart.includes(normalizedQuery)) kind = 'partialKey';
}
if (kind === 'exactKey') {
exactKeyMatches.push({
shortcut,
score: 0,
highlightRanges: [],
matchKind: 'exactKey'
});
seen.add(shortcut.id);
} else if (kind === 'partialKey') {
partialKeyMatches.push({
shortcut,
score: 0,
highlightRanges: [],
matchKind: 'partialKey'
});
seen.add(shortcut.id);
}
}
matches.push(...exactKeyMatches, ...partialKeyMatches);
}
const fuzzy = (0, _fuzzysortDefault.default).go(trimmed, this.shortcuts, {
key: '_prepared',
limit: 100
});
for (const r of fuzzy){
if (seen.has(r.obj.id)) continue;
matches.push({
shortcut: r.obj,
score: r.score,
highlightRanges: r.indexes.slice(),
matchKind: 'fuzzy'
});
seen.add(r.obj.id);
}
return matches;
}
constructor(){
this.shortcuts = [];
this.byId = new Map();
}
}
function normalizeDisplayKey(s) {
return s.toLowerCase().replace(/[\s+]/g, '');
}
function splitDisplayKeyAlternatives(displayKey) {
return displayKey.split(/\s*\/\s*|\s+or\s+/i);
}
function modifiersMatch(binding, event) {
if (binding.ctrl !== undefined && binding.ctrl !== !!event.ctrl) return false;
if (binding.shift !== undefined && binding.shift !== !!event.shift) return false;
if (binding.alt !== undefined && binding.alt !== !!event.alt) return false;
return true;
}
},{"fuzzysort":"9GWhz","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"9GWhz":[function(require,module,exports,__globalThis) {
// https://github.com/farzher/fuzzysort v3.0.2
// UMD (Universal Module Definition) for fuzzysort
((root, UMD)=>{
if (typeof define === 'function' && define.amd) define([], UMD);
else if (module.exports) module.exports = UMD();
else root['fuzzysort'] = UMD();
})(this, (_)=>{
'use strict';
var single = (search, target)=>{
if (!search || !target) return NULL;
var preparedSearch = getPreparedSearch(search);
if (!isPrepared(target)) target = getPrepared(target);
var searchBitflags = preparedSearch.bitflags;
if ((searchBitflags & target._bitflags) !== searchBitflags) return NULL;
return algorithm(preparedSearch, target);
};
var go = (search, targets, options)=>{
if (!search) return options?.all ? all(targets, options) : noResults;
var preparedSearch = getPreparedSearch(search);
var searchBitflags = preparedSearch.bitflags;
var containsSpace = preparedSearch.containsSpace;
var threshold = denormalizeScore(options?.threshold || 0);
var limit = options?.limit || INFINITY;
var resultsLen = 0;
var limitedCount = 0;
var targetsLen = targets.length;
function push_result(result) {
if (resultsLen < limit) {
q.add(result);
++resultsLen;
} else {
++limitedCount;
if (result._score > q.peek()._score) q.replaceTop(result);
}
}
// This code is copy/pasted 3 times for performance reasons [options.key, options.keys, no keys]
// options.key
if (options?.key) {
var key = options.key;
for(var i = 0; i < targetsLen; ++i){
var obj = targets[i];
var target = getValue(obj, key);
if (!target) continue;
if (!isPrepared(target)) target = getPrepared(target);
if ((searchBitflags & target._bitflags) !== searchBitflags) continue;
var result = algorithm(preparedSearch, target);
if (result === NULL) continue;
if (result._score < threshold) continue;
result.obj = obj;
push_result(result);
}
// options.keys
} else if (options?.keys) {
var keys = options.keys;
var keysLen = keys.length;
outer: for(var i = 0; i < targetsLen; ++i){
var obj = targets[i];
var keysBitflags = 0;
for(var keyI = 0; keyI < keysLen; ++keyI){
var key = keys[keyI];
var target = getValue(obj, key);
if (!target) {
tmpTargets[keyI] = noTarget;
continue;
}
if (!isPrepared(target)) target = getPrepared(target);
tmpTargets[keyI] = target;
keysBitflags |= target._bitflags;
}
if ((searchBitflags & keysBitflags) !== searchBitflags) continue;
if (containsSpace) for(let i = 0; i < preparedSearch.spaceSearches.length; i++)keysSpacesBestScores[i] = NEGATIVE_INFINITY;
for(var keyI = 0; keyI < keysLen; ++keyI){
target = tmpTargets[keyI];
if (target === noTarget) {
tmpResults[keyI] = noTarget;
continue;
}
tmpResults[keyI] = algorithm(preparedSearch, target, /*allowSpaces=*/ false, /*allowPartialMatch=*/ containsSpace);
if (tmpResults[keyI] === NULL) {
tmpResults[keyI] = noTarget;
continue;
}
// todo: this seems weird and wrong. like what if our first match wasn't good. this should just replace it instead of averaging with it
// if our second match isn't good we ignore it instead of averaging with it
if (containsSpace) for(let i = 0; i < preparedSearch.spaceSearches.length; i++){
if (allowPartialMatchScores[i] > -1000) {
if (keysSpacesBestScores[i] > NEGATIVE_INFINITY) {
var tmp = (keysSpacesBestScores[i] + allowPartialMatchScores[i]) / 4 /*bonus score for having multiple matches*/ ;
if (tmp > keysSpacesBestScores[i]) keysSpacesBestScores[i] = tmp;
}
}
if (allowPartialMatchScores[i] > keysSpacesBestScores[i]) keysSpacesBestScores[i] = allowPartialMatchScores[i];
}
}
if (containsSpace) for(let i = 0; i < preparedSearch.spaceSearches.length; i++){
if (keysSpacesBestScores[i] === NEGATIVE_INFINITY) continue outer;
}
else {
var hasAtLeast1Match = false;
for(let i = 0; i < keysLen; i++)if (tmpResults[i]._score !== NEGATIVE_INFINITY) {
hasAtLeast1Match = true;
break;
}
if (!hasAtLeast1Match) continue;
}
var objResults = new KeysResult(keysLen);
for(let i = 0; i < keysLen; i++)objResults[i] = tmpResults[i];
if (containsSpace) {
var score = 0;
for(let i = 0; i < preparedSearch.spaceSearches.length; i++)score += keysSpacesBestScores[i];
} else {
// todo could rewrite this scoring to be more similar to when there's spaces
// if we match multiple keys give us bonus points
var score = NEGATIVE_INFINITY;
for(let i = 0; i < keysLen; i++){
var result = objResults[i];
if (result._score > -1000) {
if (score > NEGATIVE_INFINITY) {
var tmp = (score + result._score) / 4 /*bonus score for having multiple matches*/ ;
if (tmp > score) score = tmp;
}
}
if (result._score > score) score = result._score;
}
}
objResults.obj = obj;
objResults._score = score;
if (options?.scoreFn) {
score = options.scoreFn(objResults);
if (!score) continue;
score = denormalizeScore(score);
objResults._score = score;
}
if (score < threshold) continue;
push_result(objResults);
}
// no keys
} else for(var i = 0; i < targetsLen; ++i){
var target = targets[i];
if (!target) continue;
if (!isPrepared(target)) target = getPrepared(target);
if ((searchBitflags & target._bitflags) !== searchBitflags) continue;
var result = algorithm(preparedSearch, target);
if (result === NULL) continue;
if (result._score < threshold) continue;
push_result(result);
}
if (resultsLen === 0) return noResults;
var results = new Array(resultsLen);
for(var i = resultsLen - 1; i >= 0; --i)results[i] = q.poll();
results.total = resultsLen + limitedCount;
return results;
};
// this is written as 1 function instead of 2 for minification. perf seems fine ...
// except when minified. the perf is very slow
var highlight = (result, open = '<b>', close = '</b>')=>{
var callback = typeof open === 'function' ? open : undefined;
var target = result.target;
var targetLen = target.length;
var indexes = result.indexes;
var highlighted = '';
var matchI = 0;
var indexesI = 0;
var opened = false;
var parts = [];
for(var i = 0; i < targetLen; ++i){
var char = target[i];
if (indexes[indexesI] === i) {
++indexesI;
if (!opened) {
opened = true;
if (callback) {
parts.push(highlighted);
highlighted = '';
} else highlighted += open;
}
if (indexesI === indexes.length) {
if (callback) {
highlighted += char;
parts.push(callback(highlighted, matchI++));
highlighted = '';
parts.push(target.substr(i + 1));
} else highlighted += char + close + target.substr(i + 1);
break;
}
} else if (opened) {
opened = false;
if (callback) {
parts.push(callback(highlighted, matchI++));
highlighted = '';
} else highlighted += close;
}
highlighted += char;
}
return callback ? parts : highlighted;
};
var prepare = (target)=>{
if (typeof target === 'number') target = '' + target;
else if (typeof target !== 'string') target = '';
var info = prepareLowerInfo(target);
return new_result(target, {
_targetLower: info._lower,
_targetLowerCodes: info.lowerCodes,
_bitflags: info.bitflags
});
};
var cleanup = ()=>{
preparedCache.clear();
preparedSearchCache.clear();
};
// Below this point is only internal code
// Below this point is only internal code
// Below this point is only internal code
// Below this point is only internal code
class Result {
get ['indexes']() {
return this._indexes.slice(0, this._indexes.len).sort((a, b)=>a - b);
}
set ['indexes'](indexes) {
return this._indexes = indexes;
}
['highlight'](open, close) {
return highlight(this, open, close);
}
get ['score']() {
return normalizeScore(this._score);
}
set ['score'](score) {
this._score = denormalizeScore(score);
}
}
class KeysResult extends Array {
get ['score']() {
return normalizeScore(this._score);
}
set ['score'](score) {
this._score = denormalizeScore(score);
}
}
var new_result = (target, options)=>{
const result = new Result();
result['target'] = target;
result['obj'] = options.obj ?? NULL;
result._score = options._score ?? NEGATIVE_INFINITY;
result._indexes = options._indexes ?? [];
result._targetLower = options._targetLower ?? '';
result._targetLowerCodes = options._targetLowerCodes ?? NULL;
result._nextBeginningIndexes = options._nextBeginningIndexes ?? NULL;
result._bitflags = options._bitflags ?? 0;
return result;
};
var normalizeScore = (score)=>{
if (score === NEGATIVE_INFINITY) return 0;
if (score > 1) return score;
return Math.E ** (((-score + 1) ** .04307 - 1) * -2);
};
var denormalizeScore = (normalizedScore)=>{
if (normalizedScore === 0) return NEGATIVE_INFINITY;
if (normalizedScore > 1) return normalizedScore;
return 1 - Math.pow(Math.log(normalizedScore) / -2 + 1, 1 / 0.04307);
};
var prepareSearch = (search)=>{
if (typeof search === 'number') search = '' + search;
else if (typeof search !== 'string') search = '';
search = search.trim();
var info = prepareLowerInfo(search);
var spaceSearches = [];
if (info.containsSpace) {
var searches = search.split(/\s+/);
searches = [
...new Set(searches)
] // distinct
;
for(var i = 0; i < searches.length; i++){
if (searches[i] === '') continue;
var _info = prepareLowerInfo(searches[i]);
spaceSearches.push({
lowerCodes: _info.lowerCodes,
_lower: searches[i].toLowerCase(),
containsSpace: false
});
}
}
return {
lowerCodes: info.lowerCodes,
_lower: info._lower,
containsSpace: info.containsSpace,
bitflags: info.bitflags,
spaceSearches: spaceSearches
};
};
var getPrepared = (target)=>{
if (target.length > 999) return prepare(target) // don't cache huge targets
;
var targetPrepared = preparedCache.get(target);
if (targetPrepared !== undefined) return targetPrepared;
targetPrepared = prepare(target);
preparedCache.set(target, targetPrepared);
return targetPrepared;
};
var getPreparedSearch = (search)=>{
if (search.length > 999) return prepareSearch(search) // don't cache huge searches
;
var searchPrepared = preparedSearchCache.get(search);
if (searchPrepared !== undefined) return searchPrepared;
searchPrepared = prepareSearch(search);
preparedSearchCache.set(search, searchPrepared);
return searchPrepared;
};
var all = (targets, options)=>{
var results = [];
results.total = targets.length // this total can be wrong if some targets are skipped
;
var limit = options?.limit || INFINITY;
if (options?.key) for(var i = 0; i < targets.length; i++){
var obj = targets[i];
var target = getValue(obj, options.key);
if (target == NULL) continue;
if (!isPrepared(target)) target = getPrepared(target);
var result = new_result(target.target, {
_score: target._score,
obj: obj
});
results.push(result);
if (results.length >= limit) return results;
}
else if (options?.keys) for(var i = 0; i < targets.length; i++){
var obj = targets[i];
var objResults = new KeysResult(options.keys.length);
for(var keyI = options.keys.length - 1; keyI >= 0; --keyI){
var target = getValue(obj, options.keys[keyI]);
if (!target) {
objResults[keyI] = noTarget;
continue;
}
if (!isPrepared(target)) target = getPrepared(target);
target._score = NEGATIVE_INFINITY;
target._indexes.len = 0;
objResults[keyI] = target;
}
objResults.obj = obj;
objResults._score = NEGATIVE_INFINITY;
results.push(objResults);
if (results.length >= limit) return results;
}
else for(var i = 0; i < targets.length; i++){
var target = targets[i];
if (target == NULL) continue;
if (!isPrepared(target)) target = getPrepared(target);
target._score = NEGATIVE_INFINITY;
target._indexes.len = 0;
results.push(target);
if (results.length >= limit) return results;
}
return results;
};
var algorithm = (preparedSearch, prepared, allowSpaces = false, allowPartialMatch = false)=>{
if (allowSpaces === false && preparedSearch.containsSpace) return algorithmSpaces(preparedSearch, prepared, allowPartialMatch);
var searchLower = preparedSearch._lower;
var searchLowerCodes = preparedSearch.lowerCodes;
var searchLowerCode = searchLowerCodes[0];
var targetLowerCodes = prepared._targetLowerCodes;
var searchLen = searchLowerCodes.length;
var targetLen = targetLowerCodes.length;
var searchI = 0 // where we at
;
var targetI = 0 // where you at
;
var matchesSimpleLen = 0;
// very basic fuzzy match; to remove non-matching targets ASAP!
// walk through target. find sequential matches.
// if all chars aren't found then exit
for(;;){
var isMatch = searchLowerCode === targetLowerCodes[targetI];
if (isMatch) {
matchesSimple[matchesSimpleLen++] = targetI;
++searchI;
if (searchI === searchLen) break;
searchLowerCode = searchLowerCodes[searchI];
}
++targetI;
if (targetI >= targetLen) return NULL // Failed to find searchI
;
}
var searchI = 0;
var successStrict = false;
var matchesStrictLen = 0;
var nextBeginningIndexes = prepared._nextBeginningIndexes;
if (nextBeginningIndexes === NULL) nextBeginningIndexes = prepared._nextBeginningIndexes = prepareNextBeginningIndexes(prepared.target);
targetI = matchesSimple[0] === 0 ? 0 : nextBeginningIndexes[matchesSimple[0] - 1];
// Our target string successfully matched all characters in sequence!
// Let's try a more advanced and strict test to improve the score
// only count it as a match if it's consecutive or a beginning character!
var backtrackCount = 0;
if (targetI !== targetLen) for(;;)if (targetI >= targetLen) {
// We failed to find a good spot for this search char, go back to the previous search char and force it forward
if (searchI <= 0) break; // We failed to push chars forward for a better match
++backtrackCount;
if (backtrackCount > 200) break; // exponential backtracking is taking too long, just give up and return a bad match
--searchI;
var lastMatch = matchesStrict[--matchesStrictLen];
targetI = nextBeginningIndexes[lastMatch];
} else {
var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI];
if (isMatch) {
matchesStrict[matchesStrictLen++] = targetI;
++searchI;
if (searchI === searchLen) {
successStrict = true;
break;
}
++targetI;
} else targetI = nextBeginningIndexes[targetI];
}
// check if it's a substring match
var substringIndex = searchLen <= 1 ? -1 : prepared._targetLower.indexOf(searchLower, matchesSimple[0]) // perf: this is slow
;
var isSubstring = !!~substringIndex;
var isSubstringBeginning = !isSubstring ? false : substringIndex === 0 || prepared._nextBeginningIndexes[substringIndex - 1] === substringIndex;
// if it's a substring match but not at a beginning index, let's try to find a substring starting at a beginning index for a better score
if (isSubstring && !isSubstringBeginning) for(var i = 0; i < nextBeginningIndexes.length; i = nextBeginningIndexes[i]){
if (i <= substringIndex) continue;
for(var s = 0; s < searchLen; s++)if (searchLowerCodes[s] !== prepared._targetLowerCodes[i + s]) break;
if (s === searchLen) {
substringIndex = i;
isSubstringBeginning = true;
break;
}
}
// tally up the score & keep track of matches for highlighting later
// if it's a simple match, we'll switch to a substring match if a substring exists
// if it's a strict match, we'll switch to a substring match only if that's a better score
var calculateScore = (matches)=>{
var score = 0;
var extraMatchGroupCount = 0;
for(var i = 1; i < searchLen; ++i)if (matches[i] - matches[i - 1] !== 1) {
score -= matches[i];
++extraMatchGroupCount;
}
var unmatchedDistance = matches[searchLen - 1] - matches[0] - (searchLen - 1);
score -= (12 + unmatchedDistance) * extraMatchGroupCount // penality for more groups
;
if (matches[0] !== 0) score -= matches[0] * matches[0] * .2 // penality for not starting near the beginning
;
if (!successStrict) score *= 1000;
else {
// successStrict on a target with too many beginning indexes loses points for being a bad target
var uniqueBeginningIndexes = 1;
for(var i = nextBeginningIndexes[0]; i < targetLen; i = nextBeginningIndexes[i])++uniqueBeginningIndexes;
if (uniqueBeginningIndexes > 24) score *= (uniqueBeginningIndexes - 24) * 10 // quite arbitrary numbers here ...
;
}
score -= (targetLen - searchLen) / 2 // penality for longer targets
;
if (isSubstring) score /= 1 + searchLen * searchLen * 1 // bonus for being a full substring
;
if (isSubstringBeginning) score /= 1 + searchLen * searchLen * 1 // bonus for substring starting on a beginningIndex
;
score -= (targetLen - searchLen) / 2 // penality for longer targets
;
return score;
};
if (!successStrict) {
if (isSubstring) for(var i = 0; i < searchLen; ++i)matchesSimple[i] = substringIndex + i // at this point it's safe to overwrite matchehsSimple with substr matches
;
var matchesBest = matchesSimple;
var score = calculateScore(matchesBest);
} else if (isSubstringBeginning) {
for(var i = 0; i < searchLen; ++i)matchesSimple[i] = substringIndex + i // at this point it's safe to overwrite matchehsSimple with substr matches
;
var matchesBest = matchesSimple;
var score = calculateScore(matchesSimple);
} else {
var matchesBest = matchesStrict;
var score = calculateScore(matchesStrict);
}
prepared._score = score;
for(var i = 0; i < searchLen; ++i)prepared._indexes[i] = matchesBest[i];
prepared._indexes.len = searchLen;
const result = new Result();
result.target = prepared.target;
result._score = prepared._score;
result._indexes = prepared._indexes;
return result;
};
var algorithmSpaces = (preparedSearch, target, allowPartialMatch)=>{
var seen_indexes = new Set();
var score = 0;
var result = NULL;
var first_seen_index_last_search = 0;
var searches = preparedSearch.spaceSearches;
var searchesLen = searches.length;
var changeslen = 0;
// Return _nextBeginningIndexes back to its normal state
var resetNextBeginningIndexes = ()=>{
for(let i = changeslen - 1; i >= 0; i--)target._nextBeginningIndexes[nextBeginningIndexesChanges[i * 2 + 0]] = nextBeginningIndexesChanges[i * 2 + 1];
};
var hasAtLeast1Match = false;
for(var i = 0; i < searchesLen; ++i){
allowPartialMatchScores[i] = NEGATIVE_INFINITY;
var search = searches[i];
result = algorithm(search, target);
if (allowPartialMatch) {
if (result === NULL) continue;
hasAtLeast1Match = true;
} else if (result === NULL) {
resetNextBeginningIndexes();
return NULL;
}
// if not the last search, we need to mutate _nextBeginningIndexes for the next search
var isTheLastSearch = i === searchesLen - 1;
if (!isTheLastSearch) {
var indexes = result._indexes;
var indexesIsConsecutiveSubstring = true;
for(let i = 0; i < indexes.len - 1; i++)if (indexes[i + 1] - indexes[i] !== 1) {
indexesIsConsecutiveSubstring = false;
break;
}
if (indexesIsConsecutiveSubstring) {
var newBeginningIndex = indexes[indexes.len - 1] + 1;
var toReplace = target._nextBeginningIndexes[newBeginningIndex - 1];
for(let i = newBeginningIndex - 1; i >= 0; i--){
if (toReplace !== target._nextBeginningIndexes[i]) break;
target._nextBeginningIndexes[i] = newBeginningIndex;
nextBeginningIndexesChanges[changeslen * 2 + 0] = i;
nextBeginningIndexesChanges[changeslen * 2 + 1] = toReplace;
changeslen++;
}
}
}
score += result._score / searchesLen;
allowPartialMatchScores[i] = result._score / searchesLen;
// dock points based on order otherwise "c man" returns Manifest.cpp instead of CheatManager.h
if (result._indexes[0] < first_seen_index_last_search) score -= (first_seen_index_last_search - result._indexes[0]) * 2;
first_seen_index_last_search = result._indexes[0];
for(var j = 0; j < result._indexes.len; ++j)seen_indexes.add(result._indexes[j]);
}
if (allowPartialMatch && !hasAtLeast1Match) return NULL;
resetNextBeginningIndexes();
// allows a search with spaces that's an exact substring to score well
var allowSpacesResult = algorithm(preparedSearch, target, /*allowSpaces=*/ true);
if (allowSpacesResult !== NULL && allowSpacesResult._score > score) {
if (allowPartialMatch) for(var i = 0; i < searchesLen; ++i)allowPartialMatchScores[i] = allowSpacesResult._score / searchesLen;
return allowSpacesResult;
}
if (allowPartialMatch) result = target;
result._score = score;
var i = 0;
for (let index of seen_indexes)result._indexes[i++] = index;
result._indexes.len = i;
return result;
};
// we use this instead of just .normalize('NFD').replace(/[\u0300-\u036f]/g, '') because that screws with japanese characters
var remove_accents = (str)=>str.replace(/\p{Script=Latin}+/gu, (match)=>match.normalize('NFD')).replace(/[\u0300-\u036f]/g, '');
var prepareLowerInfo = (str)=>{
str = remove_accents(str);
var strLen = str.length;
var lower = str.toLowerCase();
var lowerCodes = [] // new Array(strLen) sparse array is too slow
;
var bitflags = 0;
var containsSpace = false // space isn't stored in bitflags because of how searching with a space works
;
for(var i = 0; i < strLen; ++i){
var lowerCode = lowerCodes[i] = lower.charCodeAt(i);
if (lowerCode === 32) {
containsSpace = true;
continue; // it's important that we don't set any bitflags for space
}
var bit = lowerCode >= 97 && lowerCode <= 122 ? lowerCode - 97 // alphabet
: lowerCode >= 48 && lowerCode <= 57 ? 26 // numbers
: lowerCode <= 127 ? 30 // other ascii
: 31 // other utf8
;
bitflags |= 1 << bit;
}
return {
lowerCodes: lowerCodes,
bitflags: bitflags,
containsSpace: containsSpace,
_lower: lower
};
};
var prepareBeginningIndexes = (target)=>{
var targetLen = target.length;
var beginningIndexes = [];
var beginningIndexesLen = 0;
var wasUpper = false;
var wasAlphanum = false;
for(var i = 0; i < targetLen; ++i){
var targetCode = target.charCodeAt(i);
var isUpper = targetCode >= 65 && targetCode <= 90;
var isAlphanum = isUpper || targetCode >= 97 && targetCode <= 122 || targetCode >= 48 && targetCode <= 57;
var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum;
wasUpper = isUpper;
wasAlphanum = isAlphanum;
if (isBeginning) beginningIndexes[beginningIndexesLen++] = i;
}
return beginningIndexes;
};
var prepareNextBeginningIndexes = (target)=>{
target = remove_accents(target);
var targetLen = target.length;
var beginningIndexes = prepareBeginningIndexes(target);
var nextBeginningIndexes = [] // new Array(targetLen) sparse array is too slow
;
var lastIsBeginning = beginningIndexes[0];
var lastIsBeginningI = 0;
for(var i = 0; i < targetLen; ++i)if (lastIsBeginning > i) nextBeginningIndexes[i] = lastIsBeginning;
else {
lastIsBeginning = beginningIndexes[++lastIsBeginningI];
nextBeginningIndexes[i] = lastIsBeginning === undefined ? targetLen : lastIsBeginning;
}
return nextBeginningIndexes;
};
var preparedCache = new Map();
var preparedSearchCache = new Map();
// the theory behind these being globals is to reduce garbage collection by not making new arrays
var matchesSimple = [];
var matchesStrict = [];
var nextBeginningIndexesChanges = [] // allows straw berry to match strawberry well, by modifying the end of a substring to be considered a beginning index for the rest of the search
;
var keysSpacesBestScores = [];
var allowPartialMatchScores = [];
var tmpTargets = [];
var tmpResults = [];
// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
// prop = 'key1.key2' 10ms
// prop = ['key1', 'key2'] 27ms
// prop = obj => obj.tags.join() ??ms
var getValue = (obj, prop)=>{
var tmp = obj[prop];
if (tmp !== undefined) return tmp;
if (typeof prop === 'function') return prop(obj) // this should run first. but that makes string props slower
;
var segs = prop;
if (!Array.isArray(prop)) segs = prop.split('.');
var len = segs.length;
var i = -1;
while(obj && ++i < len)obj = obj[segs[i]];
return obj;
};
var isPrepared = (x)=>{
return typeof x === 'object' && typeof x._bitflags === 'number';
};
var INFINITY = Infinity;
var NEGATIVE_INFINITY = -INFINITY;
var noResults = [];
noResults.total = 0;
var NULL = null;
var noTarget = prepare('');
// Hacked version of https://github.com/lemire/FastPriorityQueue.js
var fastpriorityqueue = (r)=>{
var e = [], o = 0, a = {}, v = (r)=>{
for(var a = 0, v = e[a], c = 1; c < o;){
var s = c + 1;
a = c, s < o && e[s]._score < e[c]._score && (a = s), e[a - 1 >> 1] = e[a], c = 1 + (a << 1);
}
for(var f = a - 1 >> 1; a > 0 && v._score < e[f]._score; f = (a = f) - 1 >> 1)e[a] = e[f];
e[a] = v;
};
return a.add = (r)=>{
var a = o;
e[o++] = r;
for(var v = a - 1 >> 1; a > 0 && r._score < e[v]._score; v = (a = v) - 1 >> 1)e[a] = e[v];
e[a] = r;
}, a.poll = (r)=>{
if (0 !== o) {
var a = e[0];
return e[0] = e[--o], v(), a;
}
}, a.peek = (r)=>{
if (0 !== o) return e[0];
}, a.replaceTop = (r)=>{
e[0] = r, v();
}, a;
};
var q = fastpriorityqueue() // reuse this
;
// fuzzysort is written this way for minification. all names are mangeled unless quoted
return {
'single': single,
'go': go,
'prepare': prepare,
'cleanup': cleanup
};
}) // UMD
;
},{}],"f9iRH":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "HotkeyEngine", ()=>HotkeyEngine);
class HotkeyEngine {
constructor(registry){
this.registry = registry;
this.enabled = true;
this.blocker = null;
this.target = null;
this.listener = (e)=>{
this.dispatch(e);
};
}
attach(target) {
if (this.target) this.detach();
this.target = target;
target.addEventListener('keydown', this.listener, true);
}
detach() {
if (!this.target) return;
this.target.removeEventListener('keydown', this.listener, true);
this.target = null;
}
setEnabled(enabled) {
this.enabled = enabled;
}
isEnabled() {
return this.enabled;
}
setBlocker(fn) {
this.blocker = fn;
}
dispatch(e) {
if (!this.enabled) return;
const modifiers = {
ctrl: e.ctrlKey,
shift: e.shiftKey,
alt: e.altKey
};
const matches = this.registry.getByBinding(e.code, modifiers);
for (const def of matches){
if (!def.handler) continue;
if (def.guard && !def.guard()) continue;
if (this.blocker) this.blocker(e);
def.handler(e);
}
}
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"53jFZ":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "CommandPalette", ()=>CommandPalette);
var _paletteCss = require("./palette.css");
const STYLE_ELEMENT_ID = 'cmdp-styles';
const DISPLAY_KEY_SEPARATOR_RE = /(\s*\+\s*|\s*\/\s*|\s*,\s*|\s+or\s+)/g;
const LAST_SEARCHES_STORAGE_KEY = 'cmdp-last-searches';
const DEFAULT_MAX_LAST_SEARCHES = 3;
const SORT_MODE_STORAGE_KEY = 'cmdp-sort-mode';
const RECENT_COMMANDS_STORAGE_KEY = 'cmdp-recent-commands';
const DEFAULT_MAX_RECENT_COMMANDS = 3;
const RECENT_SECTION_LABEL = 'Recently used';
const DISABLED_CATEGORIES_STORAGE_KEY = 'cmdp-disabled-categories';
const ESSENTIAL_ONLY_STORAGE_KEY = 'cmdp-essential-only';
const EXECUTABLE_ONLY_STORAGE_KEY = 'cmdp-executable-only';
const EXPANDED_SECTIONS_STORAGE_KEY = 'cmdp-expanded-sections';
const AUTO_PAUSE_STORAGE_KEY = 'cmdp-auto-pause';
const CATEGORY_SHORT_LABELS = {
'General Shortcuts': 'General',
'Marker Editing Shortcuts': 'Editing',
'Marker Timing Shortcuts': 'Timing',
'Marker Navigation Shortcuts': 'Navigation',
'Cropping Shortcuts': 'Cropping',
'Global Settings Editor Shortcuts': 'Settings',
'Playback Shortcuts': 'Playback',
'Preview Shortcuts': 'Preview',
'Saving and Loading Shortcuts': 'Save/Load',
'Frame Capturer Shortcuts': 'Frames',
'Miscellaneous Shortcuts': 'Misc',
'General Chart Shortcuts': 'Charts',
'Speed Chart Shortcuts': 'Speed Chart',
'Crop Chart Shortcuts': 'Crop Chart',
'ZoomPan Mode Crop Chart Shortcuts': 'ZoomPan'
};
class CommandPalette {
constructor(registry, options = {}){
this.registry = registry;
this.overlay = null;
this.searchInput = null;
this.resultsEl = null;
this.essentialFilterEl = null;
this.executableFilterEl = null;
this.disabledCategories = new Set();
this.essentialOnly = false;
this.executableOnly = false;
this.counterEl = null;
this.itemNumber = 0;
this.categoryPillCounts = new Map();
this.categoryFiltersEl = null;
this.allCategories = [];
this.updateTabCounts = null;
this.lastSearchesContainerEl = null;
this.lastSearches = [];
this.recentCommandIds = [];
this.preserveOrder = false;
this.autoPause = false;
this.onOpenReference = null;
this.onOpenCallback = null;
this.onCloseCallback = null;
this.selectCallback = null;
this.highlightIndex = 0;
this.visibleEntries = [];
this.executableIndexes = [];
this.currentQuery = '';
this.container = options.container ?? document.body;
this.zIndex = options.zIndex ?? 99999;
this.maxLastSearches = Math.max(0, options.maxLastSearches ?? DEFAULT_MAX_LAST_SEARCHES);
this.maxRecentCommands = Math.max(0, options.maxRecentCommands ?? DEFAULT_MAX_RECENT_COMMANDS);
this.onOpenReference = options.onOpenReference ?? null;
this.onOpenCallback = options.onOpen ?? null;
this.onCloseCallback = options.onClose ?? null;
this.keydownHandler = (e)=>{
this.handleKeydown(e);
};
this.lastSearches = loadLastSearches(this.maxLastSearches);
this.recentCommandIds = loadRecentCommands(this.maxRecentCommands);
this.preserveOrder = loadPreserveOrder();
this.autoPause = loadBooleanPref(AUTO_PAUSE_STORAGE_KEY);
this.disabledCategories = loadDisabledCategories();
this.essentialOnly = loadBooleanPref(ESSENTIAL_ONLY_STORAGE_KEY);
this.executableOnly = loadBooleanPref(EXECUTABLE_ONLY_STORAGE_KEY);
ensureStyles();
}
open() {
if (this.overlay) return;
if (this.autoPause) this.onOpenCallback?.();
this.build();
this.refreshResults('');
this.searchInput?.focus();
document.addEventListener('keydown', this.keydownHandler, true);
}
close() {
if (!this.overlay) return;
const query = this.searchInput?.value.trim() ?? '';
if (query) this.saveSearch(query);
if (this.autoPause) this.onCloseCallback?.();
document.removeEventListener('keydown', this.keydownHandler, true);
this.overlay.remove();
this.overlay = null;
this.searchInput = null;
this.resultsEl = null;
this.essentialFilterEl = null;
this.executableFilterEl = null;
this.counterEl = null;
this.lastSearchesContainerEl = null;
this.visibleEntries = [];
this.executableIndexes = [];
this.highlightIndex = 0;
}
toggle() {
if (this.overlay) this.close();
else this.open();
}
isOpen() {
return this.overlay !== null;
}
onSelect(callback) {
this.selectCallback = callback;
}
build() {
const overlay = document.createElement('div');
overlay.className = 'cmdp-overlay';
overlay.style.zIndex = String(this.zIndex);
overlay.addEventListener('mousedown', (e)=>{
if (e.target === overlay) this.close();
});
const palette = document.createElement('div');
palette.className = 'cmdp-palette';
const searchRow = document.createElement('div');
searchRow.className = 'cmdp-search-row';
const search = document.createElement('input');
search.className = 'cmdp-search';
search.type = 'text';
search.placeholder = 'Search shortcuts...';
search.autocomplete = 'off';
search.spellcheck = false;
const clearBtn = document.createElement('button');
clearBtn.type = 'button';
clearBtn.className = 'cmdp-search-clear';
clearBtn.textContent = '\u00D7';
clearBtn.title = 'Clear search';
clearBtn.style.display = 'none';
clearBtn.addEventListener('mousedown', (e)=>{
e.preventDefault();
search.value = '';
clearBtn.style.display = 'none';
search.focus();
this.refreshResults('');
});
search.addEventListener('input', ()=>{
clearBtn.style.display = search.value ? '' : 'none';
this.refreshResults(search.value);
});
const counter = document.createElement('span');
counter.className = 'cmdp-counter';
searchRow.appendChild(search);
searchRow.appendChild(clearBtn);
searchRow.appendChild(counter);
palette.appendChild(searchRow);
const lastSearchesRow = document.createElement('div');
lastSearchesRow.className = 'cmdp-last-searches-row';
const lastSearchesLabel = document.createElement('span');
lastSearchesLabel.className = 'cmdp-region-label cmdp-help-text';
lastSearchesLabel.textContent = 'Recent';
lastSearchesRow.appendChild(lastSearchesLabel);
const lastSearches = document.createElement('div');
lastSearches.className = 'cmdp-last-searches';
lastSearchesRow.appendChild(lastSearches);
palette.appendChild(lastSearchesRow);
const filters = document.createElement('div');
filters.className = 'cmdp-filters';
const filtersLabel = document.createElement('span');
filtersLabel.className = 'cmdp-region-label cmdp-help-text';
filtersLabel.textContent = 'Options';
filters.appendChild(filtersLabel);
const essentialLabel = document.createElement('label');
essentialLabel.className = 'cmdp-essential-filter';
const essentialCheckbox = document.createElement('input');
essentialCheckbox.type = 'checkbox';
essentialCheckbox.className = 'cmdp-essential-checkbox';
essentialCheckbox.checked = this.essentialOnly;
essentialCheckbox.addEventListener('change', ()=>{
this.essentialOnly = essentialCheckbox.checked;
saveBooleanPref(ESSENTIAL_ONLY_STORAGE_KEY, this.essentialOnly);
this.refreshResults(search.value);
});
const essentialText = document.createElement('span');
essentialText.className = 'cmdp-essential-filter-text';
essentialText.textContent = 'essential only';
essentialLabel.appendChild(essentialCheckbox);
essentialLabel.appendChild(essentialText);
filters.appendChild(essentialLabel);
const sortLabel = document.createElement('label');
sortLabel.className = 'cmdp-sort-filter';
sortLabel.title = 'Keep matches in their stable registration order. When off, results are ranked by match quality.';
const sortCheckbox = document.createElement('input');
sortCheckbox.type = 'checkbox';
sortCheckbox.className = 'cmdp-sort-checkbox';
sortCheckbox.checked = this.preserveOrder;
sortCheckbox.addEventListener('change', ()=>{
this.preserveOrder = sortCheckbox.checked;
savePreserveOrder(this.preserveOrder);
this.refreshResults(search.value);
});
const sortText = document.createElement('span');
sortText.className = 'cmdp-sort-filter-text';
sortText.textContent = 'preserve order';
sortLabel.appendChild(sortCheckbox);
sortLabel.appendChild(sortText);
filters.appendChild(sortLabel);
const executableLabel = document.createElement('label');
executableLabel.className = 'cmdp-executable-filter';
executableLabel.title = 'Show only shortcuts that can be executed from the palette.';
const executableCheckbox = document.createElement('input');
executableCheckbox.type = 'checkbox';
executableCheckbox.className = 'cmdp-executable-checkbox';
executableCheckbox.checked = this.executableOnly;
executableCheckbox.addEventListener('change', ()=>{
this.executableOnly = executableCheckbox.checked;
saveBooleanPref(EXECUTABLE_ONLY_STORAGE_KEY, this.executableOnly);
this.refreshResults(search.value);
});
const executableText = document.createElement('span');
executableText.className = 'cmdp-executable-filter-text';
executableText.textContent = 'executable only';
executableLabel.appendChild(executableCheckbox);
executableLabel.appendChild(executableText);
filters.appendChild(executableLabel);
const autoPauseLabel = document.createElement('label');
autoPauseLabel.className = 'cmdp-autopause-filter';
autoPauseLabel.title = 'Automatically pause the video when the palette opens and resume on close.';
const autoPauseCheckbox = document.createElement('input');
autoPauseCheckbox.type = 'checkbox';
autoPauseCheckbox.className = 'cmdp-autopause-checkbox';
autoPauseCheckbox.checked = this.autoPause;
autoPauseCheckbox.addEventListener('change', ()=>{
this.autoPause = autoPauseCheckbox.checked;
saveBooleanPref(AUTO_PAUSE_STORAGE_KEY, this.autoPause);
});
const autoPauseText = document.createElement('span');
autoPauseText.className = 'cmdp-autopause-filter-text';
autoPauseText.textContent = 'auto pause';
autoPauseLabel.appendChild(autoPauseCheckbox);
autoPauseLabel.appendChild(autoPauseText);
filters.appendChild(autoPauseLabel);
const optionsResetBtn = document.createElement('button');
optionsResetBtn.type = 'button';
optionsResetBtn.className = 'cmdp-reset-settings';
optionsResetBtn.textContent = 'reset';
optionsResetBtn.title = 'Reset options to defaults';
optionsResetBtn.addEventListener('click', ()=>{
this.essentialOnly = false;
this.executableOnly = false;
this.preserveOrder = false;
this.autoPause = false;
saveBooleanPref(ESSENTIAL_ONLY_STORAGE_KEY, false);
saveBooleanPref(EXECUTABLE_ONLY_STORAGE_KEY, false);
saveBooleanPref(AUTO_PAUSE_STORAGE_KEY, false);
savePreserveOrder(false);
essentialCheckbox.checked = false;
executableCheckbox.checked = false;
sortCheckbox.checked = false;
autoPauseCheckbox.checked = false;
this.refreshResults(search.value);
});
filters.appendChild(optionsResetBtn);
palette.appendChild(filters);
const categoryFilters = document.createElement('div');
categoryFilters.className = 'cmdp-category-filters';
const allCategories = [];
const seenCategories = new Set();
for (const def of this.registry.getAll())if (!seenCategories.has(def.category)) {
seenCategories.add(def.category);
allCategories.push(def.category);
}
const grouped = this.registry.getGrouped();
const sectionEntries = [
...grouped.entries()
];
// Tab bar
const tabBar = document.createElement('div');
tabBar.className = 'cmdp-tab-bar';
// Tab panel (shows active tab's category pills)
const tabPanel = document.createElement('div');
tabPanel.className = 'cmdp-tab-panel';
const tabs = [];
const panels = [];
let activeTabIdx = 0;
try {
const saved = localStorage.getItem(EXPANDED_SECTIONS_STORAGE_KEY);
if (saved) {
const idx = sectionEntries.findIndex(([name])=>name === saved);
if (idx >= 0) activeTabIdx = idx;
}
} catch {
// ignore
}
const updateTabCount = (tab, sectionCats)=>{
const enabled = sectionCats.filter((c)=>!this.disabledCategories.has(c)).length;
const badge = tab.querySelector('.cmdp-tab-count');
if (badge) badge.textContent = `${enabled}/${sectionCats.length}`;
};
sectionEntries.forEach(([sectionName, categories], idx)=>{
const sectionCats = [
...categories.keys()
];
// Tab button
const tab = document.createElement('div');
tab.className = 'cmdp-tab';
if (idx === activeTabIdx) tab.classList.add('cmdp-tab-active');
tab.dataset.section = sectionName;
const tabText = document.createElement('span');
tabText.textContent = sectionName;
tab.appendChild(tabText);
const tabCount = document.createElement('span');
tabCount.className = 'cmdp-tab-count';
const enabled = sectionCats.filter((c)=>!this.disabledCategories.has(c)).length;
tabCount.textContent = `${enabled}/${sectionCats.length}`;
tab.appendChild(tabCount);
tabs.push(tab);
tabBar.appendChild(tab);
// Panel content
const panel = document.createElement('div');
panel.className = 'cmdp-tab-content';
if (idx !== activeTabIdx) panel.style.display = 'none';
for (const cat of sectionCats){
const pill = document.createElement('div');
pill.className = 'cmdp-category-pill';
pill.dataset.category = cat;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'cmdp-category-checkbox';
checkbox.checked = !this.disabledCategories.has(cat);
const text = document.createElement('span');
text.textContent = CATEGORY_SHORT_LABELS[cat] ?? cat;
const jumpBtn = document.createElement('button');
jumpBtn.type = 'button';
jumpBtn.className = 'cmdp-category-jump';
jumpBtn.title = 'Jump to ' + (CATEGORY_SHORT_LABELS[cat] ?? cat);
const soloBtn = document.createElement('button');
soloBtn.type = 'button';
soloBtn.className = 'cmdp-category-solo';
soloBtn.title = "Solo \u2014 show only " + (CATEGORY_SHORT_LABELS[cat] ?? cat) + '. Click again to unsolo.';
pill.addEventListener('click', (e)=>{
if (jumpBtn.contains(e.target)) return;
if (soloBtn.contains(e.target)) return;
checkbox.checked = !checkbox.checked;
if (checkbox.checked) this.disabledCategories.delete(cat);
else this.disabledCategories.add(cat);
saveDisabledCategories(this.disabledCategories);
this.refreshResults(search.value);
this.syncCategoryCheckboxes(categoryFilters);
});
jumpBtn.addEventListener('click', (e)=>{
e.stopPropagation();
if (this.disabledCategories.has(cat)) {
this.disabledCategories.delete(cat);
checkbox.checked = true;
saveDisabledCategories(this.disabledCategories);
this.refreshResults(search.value);
}
setTimeout(()=>{
if (!this.resultsEl) return;
const anchor = this.resultsEl.querySelector(`.cmdp-category-anchor[data-category="${CSS.escape(cat)}"]`);
if (anchor) {
const sectionHeader = anchor.previousElementSibling?.classList.contains('cmdp-section') ? anchor.previousElementSibling : this.resultsEl.querySelector('.cmdp-section');
const stickyOffset = sectionHeader?.offsetHeight ?? 0;
this.resultsEl.scrollTop = anchor.offsetTop - stickyOffset;
}
const header = this.resultsEl.querySelector(`.cmdp-category[data-category="${CSS.escape(cat)}"]`);
if (header) {
header.classList.remove('cmdp-flash');
header.offsetWidth;
header.classList.add('cmdp-flash');
let sibling = header.nextElementSibling;
while(sibling && !sibling.classList.contains('cmdp-category') && !sibling.classList.contains('cmdp-category-anchor') && !sibling.classList.contains('cmdp-section')){
if (sibling.classList.contains('cmdp-item')) {
sibling.classList.remove('cmdp-flash');
sibling.offsetWidth;
sibling.classList.add('cmdp-flash');
}
sibling = sibling.nextElementSibling;
}
}
}, 0);
});
soloBtn.addEventListener('click', (e)=>{
e.stopPropagation();
this.toggleSolo(cat);
});
const countSpan = document.createElement('span');
countSpan.className = 'cmdp-pill-count';
this.categoryPillCounts.set(cat, countSpan);
pill.appendChild(jumpBtn);
pill.appendChild(checkbox);
pill.appendChild(text);
pill.appendChild(countSpan);
pill.appendChild(soloBtn);
panel.appendChild(pill);
}
panels.push(panel);
tabPanel.appendChild(panel);
// Tab click: switch active tab
tab.addEventListener('click', ()=>{
tabs.forEach((t)=>t.classList.remove('cmdp-tab-active'));
panels.forEach((p)=>p.style.display = 'none');
tab.classList.add('cmdp-tab-active');
panel.style.display = '';
try {
localStorage.setItem(EXPANDED_SECTIONS_STORAGE_KEY, sectionName);
} catch {
// ignore
}
});
});
const resetBtn = document.createElement('button');
resetBtn.type = 'button';
resetBtn.className = 'cmdp-reset-settings';
resetBtn.textContent = 'reset';
resetBtn.title = 'Reset category filters to defaults';
resetBtn.addEventListener('click', ()=>{
this.disabledCategories.clear();
saveDisabledCategories(this.disabledCategories);
this.syncCategoryCheckboxes(categoryFilters);
this.refreshResults(search.value);
});
tabBar.appendChild(resetBtn);
categoryFilters.appendChild(tabBar);
categoryFilters.appendChild(tabPanel);
this.categoryFiltersEl = categoryFilters;
this.allCategories = allCategories;
this.updateTabCounts = ()=>{
sectionEntries.forEach(([, categories], idx)=>{
updateTabCount(tabs[idx], [
...categories.keys()
]);
});
};
palette.appendChild(categoryFilters);
const results = document.createElement('div');
results.className = 'cmdp-results';
palette.appendChild(results);
const footer = document.createElement('div');
footer.className = 'cmdp-footer';
const hints = document.createElement('div');
hints.className = 'cmdp-footer-hints cmdp-help-text';
hints.appendChild(makeFooterHint('\u2191\u2193', 'navigate'));
hints.appendChild(makeFooterHint('\u21b5', 'execute'));
hints.appendChild(makeFooterHint('esc', 'close'));
footer.appendChild(hints);
const footerLinks = document.createElement('div');
footerLinks.className = 'cmdp-footer-links';
if (this.onOpenReference) {
const refLink = document.createElement('button');
refLink.type = 'button';
refLink.className = 'cmdp-footer-reference';
refLink.title = 'Open the full shortcuts reference table';
refLink.textContent = '? full reference';
refLink.addEventListener('mousedown', (e)=>{
e.preventDefault();
e.stopPropagation();
const cb = this.onOpenReference;
this.close();
cb?.();
});
footerLinks.appendChild(refLink);
}
const homeLink = document.createElement('a');
homeLink.className = 'cmdp-footer-reference';
homeLink.href = 'https://github.com/exwm/yt_clipper';
homeLink.target = '_blank';
homeLink.rel = 'noopener noreferrer';
homeLink.title = 'yt_clipper on GitHub';
homeLink.textContent = 'GitHub';
footerLinks.appendChild(homeLink);
footer.appendChild(footerLinks);
palette.appendChild(footer);
overlay.appendChild(palette);
this.container.appendChild(overlay);
this.overlay = overlay;
this.searchInput = search;
this.resultsEl = results;
this.essentialFilterEl = essentialCheckbox;
this.executableFilterEl = executableCheckbox;
this.counterEl = counter;
this.lastSearchesContainerEl = lastSearches;
this.renderLastSearches();
}
saveSearch(query) {
if (this.maxLastSearches === 0) return;
const existingIdx = this.lastSearches.indexOf(query);
if (existingIdx >= 0) this.lastSearches.splice(existingIdx, 1);
this.lastSearches.unshift(query);
if (this.lastSearches.length > this.maxLastSearches) this.lastSearches.length = this.maxLastSearches;
saveLastSearches(this.lastSearches);
}
renderLastSearches() {
const container = this.lastSearchesContainerEl;
if (!container) return;
while(container.firstChild)container.removeChild(container.firstChild);
const currentValue = this.searchInput?.value.trim() ?? '';
if (this.maxLastSearches === 0 || this.lastSearches.length === 0) return;
for (const query of this.lastSearches){
if (query === currentValue) continue;
const pill = document.createElement('button');
pill.type = 'button';
pill.className = 'cmdp-last-search-pill';
pill.title = 'Reuse search: ' + query;
pill.textContent = query;
pill.addEventListener('mousedown', (e)=>{
e.preventDefault();
e.stopPropagation();
if (!this.searchInput) return;
this.searchInput.value = query;
this.searchInput.focus();
this.refreshResults(query);
});
container.appendChild(pill);
}
}
refreshResults(query) {
if (!this.resultsEl) return;
const essentialOnly = !!this.essentialFilterEl?.checked;
const executableOnly = !!this.executableFilterEl?.checked;
const trimmed = query.trim();
this.currentQuery = trimmed;
let candidates;
if (trimmed === '') candidates = this.registry.getAll().map((shortcut)=>({
shortcut,
score: 0,
highlightRanges: []
}));
else candidates = this.registry.search(trimmed);
if (essentialOnly) candidates = candidates.filter((r)=>r.shortcut.essential);
if (executableOnly) candidates = candidates.filter((r)=>r.shortcut.executable && !!r.shortcut.handler);
this.visibleEntries = [];
this.executableIndexes = [];
if (trimmed === '') this.buildRecentEntries(essentialOnly);
if (!this.preserveOrder && trimmed !== '') this.buildRankedEntries(candidates);
else this.buildGroupedEntries(candidates);
this.highlightIndex = this.pickInitialHighlight();
this.paintResults();
this.renderLastSearches();
const realItemCount = candidates.filter((r)=>!this.disabledCategories.has(r.shortcut.category)).length;
this.updateCounter(realItemCount);
this.updateCategoryPillCounts(candidates);
}
buildRecentEntries(essentialOnly) {
if (this.maxRecentCommands <= 0 || this.recentCommandIds.length === 0) return;
const recent = [];
for (const id of this.recentCommandIds){
const def = this.registry.getById(id);
if (!def) continue;
if (essentialOnly && !def.essential) continue;
recent.push(def);
}
if (recent.length === 0) return;
this.visibleEntries.push({
kind: 'section',
label: RECENT_SECTION_LABEL
});
for (const def of recent)this.pushItem({
shortcut: def,
score: 0,
highlightRanges: []
});
}
buildRankedEntries(candidates) {
const sectionOrder = [];
const categoriesBySection = new Map();
const bucketByKey = new Map();
for (const candidate of candidates){
const def = candidate.shortcut;
const { section, category } = def;
const key = section + '\u0000' + category;
if (!categoriesBySection.has(section)) {
sectionOrder.push(section);
categoriesBySection.set(section, []);
}
let bucket = bucketByKey.get(key);
if (!bucket) {
const sectionCategories = categoriesBySection.get(section);
if (sectionCategories == null) throw new Error(`Missing section: ${section}`);
sectionCategories.push(category);
bucket = [];
bucketByKey.set(key, bucket);
}
bucket.push(candidate);
}
for (const section of sectionOrder){
this.visibleEntries.push({
kind: 'section',
label: section
});
const sectionCategories = categoriesBySection.get(section);
if (sectionCategories == null) throw new Error(`Missing section: ${section}`);
for (const category of sectionCategories){
const disabled = this.disabledCategories.has(category);
this.visibleEntries.push({
kind: 'category',
label: category,
disabled
});
if (!disabled) {
const bucket = bucketByKey.get(section + '\u0000' + category);
if (bucket == null) throw new Error(`Missing bucket: ${section}/${category}`);
for (const result of bucket)this.pushItem(result);
}
}
}
}
buildGroupedEntries(candidates) {
const byId = new Map();
for (const c of candidates)byId.set(c.shortcut.id, c);
const bySection = new Map();
for (const def of this.registry.getAll()){
const match = byId.get(def.id);
if (!match) continue;
let byCategory = bySection.get(def.section);
if (!byCategory) {
byCategory = new Map();
bySection.set(def.section, byCategory);
}
let bucket = byCategory.get(def.category);
if (!bucket) {
bucket = [];
byCategory.set(def.category, bucket);
}
bucket.push(match);
}
for (const [section, categories] of bySection){
this.visibleEntries.push({
kind: 'section',
label: section
});
for (const [category, items] of categories){
const disabled = this.disabledCategories.has(category);
this.visibleEntries.push({
kind: 'category',
label: category,
disabled
});
if (!disabled) for (const result of items)this.pushItem(result);
}
}
}
pickInitialHighlight() {
for (const idx of this.executableIndexes){
const entry = this.visibleEntries[idx];
if (entry.kind === 'item' && entry.matchKind === 'exactKey') return idx;
}
for (const idx of this.executableIndexes){
const entry = this.visibleEntries[idx];
if (entry.kind === 'item' && entry.matchKind === 'partialKey') return idx;
}
return this.executableIndexes[0] ?? -1;
}
pushItem(result) {
this.visibleEntries.push({
kind: 'item',
shortcut: result.shortcut,
highlightRanges: result.highlightRanges,
matchKind: result.matchKind
});
const idx = this.visibleEntries.length - 1;
if (result.shortcut.executable && result.shortcut.handler) this.executableIndexes.push(idx);
}
paintResults() {
if (!this.resultsEl) return;
this.itemNumber = 0;
while(this.resultsEl.firstChild)this.resultsEl.removeChild(this.resultsEl.firstChild);
if (this.visibleEntries.length === 0) {
const empty = document.createElement('div');
empty.className = 'cmdp-empty cmdp-help-text';
empty.textContent = 'No shortcuts match your search.';
this.resultsEl.appendChild(empty);
return;
}
// Pre-compute item counts per section and category for display
const sectionCounts = new Map();
const categoryCounts = new Map();
let lastSectionIdx = -1;
let lastCategoryIdx = -1;
for(let i = 0; i < this.visibleEntries.length; i++){
const e = this.visibleEntries[i];
if (e.kind === 'section') lastSectionIdx = i;
else if (e.kind === 'category') lastCategoryIdx = i;
else if (e.kind === 'item') {
if (lastSectionIdx >= 0) sectionCounts.set(lastSectionIdx, (sectionCounts.get(lastSectionIdx) ?? 0) + 1);
if (lastCategoryIdx >= 0) categoryCounts.set(lastCategoryIdx, (categoryCounts.get(lastCategoryIdx) ?? 0) + 1);
}
}
// Pre-compute total counts per section and category from full registry
const totalBySection = new Map();
const totalByCategory = new Map();
for (const def of this.registry.getAll()){
totalBySection.set(def.section, (totalBySection.get(def.section) ?? 0) + 1);
totalByCategory.set(def.category, (totalByCategory.get(def.category) ?? 0) + 1);
}
const resultsEl = this.resultsEl;
let sectionNumber = 0;
let categoryNumber = 0;
this.visibleEntries.forEach((entry, idx)=>{
if (entry.kind === 'section') {
categoryNumber = 0;
const el = document.createElement('div');
el.className = 'cmdp-section';
const isRealSection = totalBySection.has(entry.label);
if (isRealSection) {
sectionNumber++;
this.itemNumber = 0;
const filtered = sectionCounts.get(idx) ?? 0;
const total = totalBySection.get(entry.label) ?? 0;
const countStr = filtered === total ? String(total) : `${filtered}/${total}`;
el.textContent = `${toRoman(sectionNumber)}. ${entry.label}`;
const countEl = document.createElement('span');
countEl.className = 'cmdp-header-count';
countEl.textContent = countStr;
el.appendChild(countEl);
} else el.textContent = entry.label;
resultsEl.appendChild(el);
return;
}
if (entry.kind === 'category') {
categoryNumber++;
const anchor = document.createElement('div');
anchor.className = 'cmdp-category-anchor';
anchor.dataset.category = entry.label;
resultsEl.appendChild(anchor);
const el = document.createElement('div');
el.className = 'cmdp-category';
el.dataset.category = entry.label;
if (entry.disabled) el.classList.add('cmdp-category-disabled');
const filtered = categoryCounts.get(idx) ?? 0;
const total = totalByCategory.get(entry.label) ?? 0;
const countStr = filtered === total ? String(total) : `${filtered}/${total}`;
el.textContent = `${categoryNumber}. ${entry.label}`;
const countEl = document.createElement('span');
countEl.className = 'cmdp-header-count';
countEl.textContent = entry.disabled ? `hidden \u2022 ${total}` : countStr;
el.appendChild(countEl);
const catToggle = document.createElement('input');
catToggle.type = 'checkbox';
catToggle.className = 'cmdp-category-header-toggle';
catToggle.checked = !entry.disabled;
catToggle.title = 'Toggle category visibility';
catToggle.addEventListener('change', (e)=>{
e.stopPropagation();
if (catToggle.checked) this.disabledCategories.delete(entry.label);
else this.disabledCategories.add(entry.label);
saveDisabledCategories(this.disabledCategories);
if (this.categoryFiltersEl) this.syncCategoryCheckboxes(this.categoryFiltersEl);
this.refreshResults(this.searchInput?.value ?? '');
});
el.appendChild(catToggle);
const catSoloBtn = document.createElement('button');
catSoloBtn.type = 'button';
catSoloBtn.className = 'cmdp-category-solo cmdp-category-header-solo';
catSoloBtn.title = "Solo \u2014 show only " + (CATEGORY_SHORT_LABELS[entry.label] ?? entry.label) + '. Click again to unsolo.';
catSoloBtn.addEventListener('click', (e)=>{
e.stopPropagation();
this.toggleSolo(entry.label);
});
el.appendChild(catSoloBtn);
resultsEl.appendChild(el);
return;
}
const item = document.createElement('div');
item.className = 'cmdp-item';
if (idx === this.highlightIndex) item.classList.add('cmdp-highlighted');
const nonExecutable = !entry.shortcut.executable || !entry.shortcut.handler;
if (nonExecutable) item.classList.add('cmdp-non-executable');
if (entry.shortcut.essential) item.classList.add('cmdp-essential');
if (entry.matchKind === 'exactKey') item.classList.add('cmdp-key-match-exact');
else if (entry.matchKind === 'partialKey') item.classList.add('cmdp-key-match-partial');
this.itemNumber++;
const num = document.createElement('span');
num.className = 'cmdp-item-num';
num.textContent = String(this.itemNumber);
item.appendChild(num);
const desc = document.createElement('div');
desc.className = 'cmdp-item-desc';
appendHighlightedText(desc, entry.shortcut.description, entry.highlightRanges);
item.appendChild(desc);
const keys = document.createElement('div');
keys.className = 'cmdp-item-keys';
if (entry.shortcut.displayNote) {
const note = document.createElement('span');
note.className = 'cmdp-non-executable-badge';
note.textContent = entry.shortcut.displayNote;
keys.appendChild(note);
} else if (entry.shortcut.displayKey) {
const highlightQuery = entry.matchKind === 'exactKey' || entry.matchKind === 'partialKey' ? this.currentQuery : '';
appendDisplayKey(keys, entry.shortcut.displayKey, highlightQuery);
} else if (nonExecutable) {
const badge = document.createElement('span');
badge.className = 'cmdp-non-executable-badge';
badge.textContent = 'mouse';
keys.appendChild(badge);
}
item.appendChild(keys);
const runBtn = document.createElement('button');
runBtn.type = 'button';
runBtn.className = 'cmdp-item-run';
runBtn.textContent = '\u25B6';
if (nonExecutable) {
runBtn.disabled = true;
runBtn.title = 'Not executable from palette';
runBtn.setAttribute('aria-label', 'Not executable: ' + entry.shortcut.description);
} else {
runBtn.title = 'Run';
runBtn.setAttribute('aria-label', 'Run ' + entry.shortcut.description);
runBtn.addEventListener('mousedown', (e)=>{
e.preventDefault();
e.stopPropagation();
this.executeAt(idx);
});
}
item.appendChild(runBtn);
item.addEventListener('mouseenter', ()=>{
if (!nonExecutable && this.highlightIndex !== idx) {
this.highlightIndex = idx;
this.paintResults();
}
});
resultsEl.appendChild(item);
});
const highlighted = this.resultsEl.querySelector('.cmdp-highlighted');
if (highlighted && 'scrollIntoView' in highlighted) highlighted.scrollIntoView({
block: 'nearest'
});
}
handleKeydown(e) {
if (e.code === 'Escape') {
e.preventDefault();
e.stopImmediatePropagation();
this.close();
return;
}
if (e.code === 'ArrowDown') {
e.preventDefault();
e.stopImmediatePropagation();
this.moveHighlight(1);
return;
}
if (e.code === 'ArrowUp') {
e.preventDefault();
e.stopImmediatePropagation();
this.moveHighlight(-1);
return;
}
if (e.code === 'Enter') {
e.preventDefault();
e.stopImmediatePropagation();
this.executeAt(this.highlightIndex);
return;
}
e.stopImmediatePropagation();
}
moveHighlight(delta) {
if (this.executableIndexes.length === 0) return;
const currentPos = this.executableIndexes.indexOf(this.highlightIndex);
let next = currentPos + delta;
if (currentPos < 0) next = delta > 0 ? 0 : this.executableIndexes.length - 1;
if (next < 0) next = this.executableIndexes.length - 1;
if (next >= this.executableIndexes.length) next = 0;
this.highlightIndex = this.executableIndexes[next];
this.paintResults();
}
executeAt(idx) {
const entry = this.visibleEntries[idx];
if (entry?.kind !== 'item') return;
const def = entry.shortcut;
if (!def.executable || !def.handler) return;
this.recordExecution(def.id);
this.close();
if (this.selectCallback) this.selectCallback(def);
else {
const fake = new KeyboardEvent('keydown', {
code: def.binding?.code ?? ''
});
def.handler(fake);
}
}
toggleSolo(category) {
const isSoloed = !this.disabledCategories.has(category) && this.disabledCategories.size === this.allCategories.length - 1;
this.disabledCategories.clear();
if (!isSoloed) {
for (const other of this.allCategories)if (other !== category) this.disabledCategories.add(other);
}
saveDisabledCategories(this.disabledCategories);
if (this.categoryFiltersEl) this.syncCategoryCheckboxes(this.categoryFiltersEl);
this.refreshResults(this.searchInput?.value ?? '');
if (this.resultsEl) {
if (!isSoloed) {
const anchor = this.resultsEl.querySelector(`.cmdp-category-anchor[data-category="${CSS.escape(category)}"]`);
if (anchor) {
const sectionHeader = anchor.previousElementSibling?.classList.contains('cmdp-section') ? anchor.previousElementSibling : this.resultsEl.querySelector('.cmdp-section');
const stickyOffset = sectionHeader?.offsetHeight ?? 0;
this.resultsEl.scrollTop = anchor.offsetTop - stickyOffset;
}
} else this.resultsEl.scrollTop = 0;
}
if (!isSoloed && this.resultsEl) {
let pastFirstCategory = false;
let inEnabledCategory = false;
for (const el of this.resultsEl.children){
if (el.classList.contains('cmdp-category')) {
pastFirstCategory = true;
inEnabledCategory = !el.classList.contains('cmdp-category-disabled');
}
if (pastFirstCategory && inEnabledCategory && (el.classList.contains('cmdp-category') || el.classList.contains('cmdp-item'))) {
el.classList.remove('cmdp-flash');
el.offsetWidth;
el.classList.add('cmdp-flash');
}
}
}
}
syncCategoryCheckboxes(container) {
container.querySelectorAll('.cmdp-category-pill').forEach((pill)=>{
const cat = pill.dataset.category ?? '';
const cb = pill.querySelector('.cmdp-category-checkbox');
if (cb) cb.checked = !this.disabledCategories.has(cat);
});
if (this.updateTabCounts) this.updateTabCounts();
}
updateCounter(filtered) {
if (!this.counterEl) return;
const total = this.registry.getAll().length;
this.counterEl.textContent = filtered === total ? String(total) : `${filtered}/${total}`;
}
updateCategoryPillCounts(candidates) {
if (this.categoryPillCounts.size === 0) return;
const filteredCounts = new Map();
for (const c of candidates){
const cat = c.shortcut.category;
filteredCounts.set(cat, (filteredCounts.get(cat) ?? 0) + 1);
}
const totalCounts = new Map();
for (const def of this.registry.getAll())totalCounts.set(def.category, (totalCounts.get(def.category) ?? 0) + 1);
for (const [cat, el] of this.categoryPillCounts){
const filtered = filteredCounts.get(cat) ?? 0;
const total = totalCounts.get(cat) ?? 0;
el.textContent = filtered === total ? String(total) : `${filtered}/${total}`;
}
}
recordExecution(id) {
if (this.maxRecentCommands <= 0) return;
const existingIdx = this.recentCommandIds.indexOf(id);
if (existingIdx >= 0) this.recentCommandIds.splice(existingIdx, 1);
this.recentCommandIds.unshift(id);
if (this.recentCommandIds.length > this.maxRecentCommands) this.recentCommandIds.length = this.maxRecentCommands;
saveRecentCommands(this.recentCommandIds);
}
}
function toRoman(n) {
const numerals = [
[
10,
'X'
],
[
9,
'IX'
],
[
5,
'V'
],
[
4,
'IV'
],
[
1,
'I'
]
];
let result = '';
for (const [value, symbol] of numerals)while(n >= value){
result += symbol;
n -= value;
}
return result;
}
function ensureStyles() {
if (document.getElementById(STYLE_ELEMENT_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ELEMENT_ID;
style.textContent = (0, _paletteCss.paletteCss);
document.head.appendChild(style);
}
function makeFooterHint(key, label) {
const hint = document.createElement('span');
const k = document.createElement('kbd');
k.textContent = key;
hint.appendChild(k);
hint.appendChild(document.createTextNode(' ' + label));
return hint;
}
function appendHighlightedText(parent, text, indexes) {
if (!indexes || indexes.length === 0) {
parent.appendChild(document.createTextNode(text));
return;
}
const sorted = indexes.slice().sort((a, b)=>a - b);
let cursor = 0;
let i = 0;
let buffer = '';
const flush = ()=>{
if (buffer.length > 0) {
parent.appendChild(document.createTextNode(buffer));
buffer = '';
}
};
while(cursor < text.length)if (i < sorted.length && sorted[i] === cursor) {
flush();
const mark = document.createElement('mark');
while(i < sorted.length && sorted[i] === cursor){
mark.appendChild(document.createTextNode(text[cursor]));
cursor++;
i++;
}
parent.appendChild(mark);
} else {
buffer += text[cursor];
cursor++;
}
flush();
}
function appendDisplayKey(parent, displayKey, query = '') {
if (displayKey === '') return;
const parts = displayKey.split(DISPLAY_KEY_SEPARATOR_RE);
const matchedPositions = computeKeyMatchPositions(parts, query);
parts.forEach((part, idx)=>{
if (idx % 2 === 1) parent.appendChild(document.createTextNode(part));
else if (part !== '') {
const kbd = document.createElement('kbd');
const partMatches = matchedPositions.get(idx);
if (!partMatches || partMatches.size === 0) kbd.textContent = part;
else {
let buffer = '';
const flush = ()=>{
if (buffer.length > 0) {
kbd.appendChild(document.createTextNode(buffer));
buffer = '';
}
};
for(let i = 0; i < part.length; i++)if (partMatches.has(i)) {
flush();
const mark = document.createElement('mark');
mark.textContent = part[i];
kbd.appendChild(mark);
} else buffer += part[i];
flush();
}
parent.appendChild(kbd);
}
});
}
function computeKeyMatchPositions(parts, query) {
const result = new Map();
const normalizedQuery = query.toLowerCase().replace(/[\s+]/g, '');
if (normalizedQuery === '') return result;
let normalized = '';
const origins = [];
parts.forEach((part, partIdx)=>{
if (partIdx % 2 === 1) return;
for(let i = 0; i < part.length; i++){
const ch = part[i];
if (/[\s+]/.test(ch)) continue;
normalized += ch.toLowerCase();
origins.push({
partIdx,
charIdx: i
});
}
});
let from = 0;
while(from <= normalized.length - normalizedQuery.length){
const found = normalized.indexOf(normalizedQuery, from);
if (found < 0) break;
for(let k = 0; k < normalizedQuery.length; k++){
const o = origins[found + k];
let set = result.get(o.partIdx);
if (!set) {
set = new Set();
result.set(o.partIdx, set);
}
set.add(o.charIdx);
}
from = found + 1;
}
return result;
}
function loadLastSearches(max) {
if (max <= 0) return [];
try {
const raw = localStorage.getItem(LAST_SEARCHES_STORAGE_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return [];
const cleaned = [];
for (const v of parsed){
if (typeof v === 'string' && v !== '' && !cleaned.includes(v)) cleaned.push(v);
if (cleaned.length >= max) break;
}
return cleaned;
} catch {
return [];
}
}
function saveLastSearches(searches) {
try {
localStorage.setItem(LAST_SEARCHES_STORAGE_KEY, JSON.stringify(searches));
} catch {
// storage disabled or quota exceeded; silently skip
}
}
function loadPreserveOrder() {
try {
const raw = localStorage.getItem(SORT_MODE_STORAGE_KEY);
if (raw === 'preserve') return true;
if (raw === 'rank') return false;
} catch {
// storage disabled
}
return false;
}
function savePreserveOrder(preserve) {
try {
localStorage.setItem(SORT_MODE_STORAGE_KEY, preserve ? 'preserve' : 'rank');
} catch {
// storage disabled or quota exceeded; silently skip
}
}
function loadRecentCommands(max) {
if (max <= 0) return [];
try {
const raw = localStorage.getItem(RECENT_COMMANDS_STORAGE_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return [];
const cleaned = [];
for (const v of parsed){
if (typeof v === 'string' && v !== '' && !cleaned.includes(v)) cleaned.push(v);
if (cleaned.length >= max) break;
}
return cleaned;
} catch {
return [];
}
}
function saveRecentCommands(ids) {
try {
localStorage.setItem(RECENT_COMMANDS_STORAGE_KEY, JSON.stringify(ids));
} catch {
// storage disabled or quota exceeded; silently skip
}
}
function loadBooleanPref(key) {
try {
return localStorage.getItem(key) === 'true';
} catch {
return false;
}
}
function saveBooleanPref(key, value) {
try {
localStorage.setItem(key, value ? 'true' : 'false');
} catch {
// storage disabled or quota exceeded; silently skip
}
}
function loadDisabledCategories() {
try {
const raw = localStorage.getItem(DISABLED_CATEGORIES_STORAGE_KEY);
if (!raw) return new Set();
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return new Set();
return new Set(parsed.filter((v)=>typeof v === 'string' && v !== ''));
} catch {
return new Set();
}
}
function saveDisabledCategories(categories) {
try {
localStorage.setItem(DISABLED_CATEGORIES_STORAGE_KEY, JSON.stringify([
...categories
]));
} catch {
// storage disabled or quota exceeded; silently skip
}
}
},{"./palette.css":"5Mqjx","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"5Mqjx":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "paletteCss", ()=>paletteCss);
const paletteCss = `
.cmdp-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.55);
display: flex;
opacity: 90%;
align-items: flex-start;
justify-content: center;
padding-top: 12vh;
font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--cmdp-accent: var(--bright-red, rgb(237, 28, 63));
--cmdp-bg: #222;
--cmdp-bg-alt: #2c2c2c;
--cmdp-bg-highlight: #3a3a3a;
--cmdp-fg: #ddd;
--cmdp-fg-dim: #888;
--cmdp-border: #666;
}
.cmdp-palette {
width: min(871px, 95vw);
max-height: 72vh;
background: var(--cmdp-bg);
color: var(--cmdp-fg);
border: 2px solid var(--cmdp-accent);
border-radius: 8px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
display: flex;
flex-direction: column;
overflow: hidden;
}
.cmdp-search-row {
display: flex;
align-items: center;
padding: 10px 14px;
border-bottom: 1px solid var(--cmdp-border);
gap: 10px;
}
.cmdp-search {
flex: 1;
background: transparent;
border: none;
outline: none;
color: var(--cmdp-fg);
font-size: 16px;
font-weight: 500;
}
.cmdp-search::placeholder {
color: var(--cmdp-fg-dim);
}
.cmdp-search-clear {
flex-shrink: 0;
width: 22px;
height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid var(--cmdp-border);
border-radius: 50%;
color: var(--cmdp-fg-dim);
font-size: 14px;
line-height: 1;
cursor: pointer;
padding: 0;
transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.cmdp-search-clear:hover {
color: #fff;
border-color: var(--cmdp-accent);
background: rgba(237, 28, 63, 0.15);
}
.cmdp-help-text {
font-size: 11px;
color: rgba(255, 255, 255, 0.55);
}
.cmdp-filters {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
padding: 8px 14px;
border-bottom: 1px solid var(--cmdp-border);
background: rgba(0, 0, 0, 0.25);
font-size: 12px;
color: var(--cmdp-fg-dim);
}
.cmdp-essential-filter,
.cmdp-sort-filter,
.cmdp-executable-filter,
.cmdp-autopause-filter {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 12px 4px 10px;
border: 1px solid var(--cmdp-border);
border-radius: 999px;
background: var(--cmdp-bg-alt);
color: var(--cmdp-fg);
font-size: 13px;
font-weight: 600;
cursor: pointer;
user-select: none;
transition: background 0.12s, border-color 0.12s, color 0.12s, box-shadow 0.12s;
}
.cmdp-essential-filter::before,
.cmdp-sort-filter::before,
.cmdp-executable-filter::before,
.cmdp-autopause-filter::before {
color: var(--cmdp-fg-dim);
font-size: 15px;
line-height: 1;
transition: color 0.12s, text-shadow 0.12s;
}
.cmdp-essential-filter::before { content: '\u2605'; }
.cmdp-sort-filter::before { content: '\u21C5'; }
.cmdp-executable-filter::before { content: '\u25B6'; }
.cmdp-autopause-filter::before { content: '\u23F8'; }
.cmdp-essential-filter:hover,
.cmdp-sort-filter:hover,
.cmdp-executable-filter:hover,
.cmdp-autopause-filter:hover {
border-color: var(--cmdp-accent);
color: #fff;
}
.cmdp-essential-filter:hover::before,
.cmdp-sort-filter:hover::before,
.cmdp-executable-filter:hover::before,
.cmdp-autopause-filter:hover::before {
color: var(--cmdp-accent);
}
.cmdp-essential-filter:has(.cmdp-essential-checkbox:checked),
.cmdp-sort-filter:has(.cmdp-sort-checkbox:checked),
.cmdp-executable-filter:has(.cmdp-executable-checkbox:checked),
.cmdp-autopause-filter:has(.cmdp-autopause-checkbox:checked) {
background: linear-gradient(to right, rgba(237, 28, 63, 0.25), var(--cmdp-bg-alt) 85%);
border-color: var(--cmdp-accent);
color: #fff;
box-shadow: 0 0 0 1px rgba(237, 28, 63, 0.35);
}
.cmdp-essential-filter:has(.cmdp-essential-checkbox:checked)::before,
.cmdp-sort-filter:has(.cmdp-sort-checkbox:checked)::before,
.cmdp-executable-filter:has(.cmdp-executable-checkbox:checked)::before,
.cmdp-autopause-filter:has(.cmdp-autopause-checkbox:checked)::before {
color: var(--cmdp-accent);
text-shadow: 0 0 6px rgba(237, 28, 63, 0.6);
}
.cmdp-essential-checkbox,
.cmdp-sort-checkbox,
.cmdp-executable-checkbox,
.cmdp-autopause-checkbox {
appearance: none;
-webkit-appearance: none;
width: 12px;
height: 12px;
margin: 0;
padding: 0;
border: 1px solid var(--cmdp-border);
border-radius: 2px;
background: var(--cmdp-bg);
cursor: pointer;
position: relative;
flex-shrink: 0;
}
.cmdp-essential-checkbox:checked,
.cmdp-sort-checkbox:checked,
.cmdp-executable-checkbox:checked,
.cmdp-autopause-checkbox:checked {
background: var(--cmdp-accent);
border-color: var(--cmdp-accent);
}
.cmdp-essential-checkbox:checked::after,
.cmdp-sort-checkbox:checked::after,
.cmdp-executable-checkbox:checked::after,
.cmdp-autopause-checkbox:checked::after {
content: '\u2713';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 10px;
font-weight: 900;
line-height: 1;
}
.cmdp-last-searches-row {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 14px;
border-bottom: 1px solid var(--cmdp-border);
background: rgba(0, 0, 0, 0.25);
}
.cmdp-last-searches-row:not(:has(.cmdp-last-search-pill)) {
display: none;
}
.cmdp-last-searches {
display: flex;
flex-wrap: wrap;
gap: 4px;
align-items: center;
}
.cmdp-last-search-pill {
display: inline-flex;
align-items: center;
gap: 4px;
max-width: 220px;
padding: 2px 10px;
font-family: inherit;
font-size: 12px;
color: var(--cmdp-fg);
background: var(--cmdp-bg-alt);
border: 1px solid var(--cmdp-border);
border-radius: 999px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.cmdp-last-search-pill::before {
content: '\u21BA';
color: var(--cmdp-fg-dim);
font-size: 12px;
}
.cmdp-last-search-pill:hover {
background: var(--cmdp-bg-highlight);
border-color: var(--cmdp-accent);
color: #fff;
}
.cmdp-last-search-pill:hover::before {
color: var(--cmdp-accent);
}
.cmdp-results {
flex: 1;
overflow-y: auto;
padding: 4px 0;
position: relative;
}
.cmdp-category-anchor {
height: 0;
overflow: hidden;
}
.cmdp-section {
display: flex;
align-items: center;
font-size: 13px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.18em;
color: #fff;
margin: 12px 6px 6px;
padding: 6px 14px;
background: linear-gradient(135deg, var(--cmdp-accent), rgb(140, 10, 35));
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.08) inset;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
position: sticky;
top: 0;
z-index: 2;
}
.cmdp-category {
display: flex;
align-items: center;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.14em;
color: #ff8a9e;
margin: 0;
padding: 6px 14px 4px 20px;
background: linear-gradient(to right, rgba(255, 138, 158, 0.1), var(--cmdp-bg) 70%), var(--cmdp-bg);
border-bottom: 1px solid rgba(255, 138, 158, 0.15);
border-left: 3px solid rgba(255, 138, 158, 0.4);
position: sticky;
top: 33px;
z-index: 1;
}
.cmdp-category-header-toggle {
margin-left: 8px;
flex-shrink: 0;
width: 14px;
height: 14px;
cursor: pointer;
accent-color: var(--cmdp-accent);
}
.cmdp-category-header-solo {
margin-left: 4px;
flex-shrink: 0;
}
.cmdp-category.cmdp-category-disabled {
color: rgba(255, 138, 158, 0.35);
background: var(--cmdp-bg);
border-left-color: transparent;
border-bottom-color: rgba(255, 255, 255, 0.05);
}
.cmdp-category.cmdp-category-disabled .cmdp-header-count {
color: var(--cmdp-fg-dim);
opacity: 1;
}
.cmdp-item {
display: grid;
grid-template-columns: 24px 1fr auto 26px;
align-items: center;
margin-left: 16px;
padding: 8px 14px 8px 12px;
cursor: default;
gap: 12px;
transition: background 0.08s;
background: rgba(255, 255, 255, 0.02);
border-left: 3px solid rgba(255, 138, 158, 0.12);
}
.cmdp-item:hover {
background: rgba(255, 255, 255, 0.06);
}
.cmdp-item.cmdp-highlighted {
background: linear-gradient(to right, rgba(237, 28, 63, 0.3), var(--cmdp-bg-highlight) 80%);
box-shadow: inset 4px 0 0 var(--cmdp-accent), inset 0 0 0 1px rgba(237, 28, 63, 0.45);
color: #fff;
}
.cmdp-item.cmdp-highlighted .cmdp-item-desc {
color: #fff;
font-weight: 600;
}
.cmdp-item.cmdp-non-executable {
opacity: 0.7;
}
.cmdp-item.cmdp-non-executable:hover {
opacity: 0.9;
}
.cmdp-item.cmdp-essential {
background: linear-gradient(to right, rgba(237, 28, 63, 0.12), transparent 60%);
}
.cmdp-item.cmdp-key-match-exact .cmdp-item-keys kbd,
.cmdp-item.cmdp-key-match-partial .cmdp-item-keys kbd {
border-color: var(--cmdp-accent);
box-shadow: 0 0 0 1px rgba(237, 28, 63, 0.45);
}
.cmdp-item.cmdp-key-match-exact .cmdp-item-keys kbd {
background-color: #fff;
color: #111;
}
.cmdp-item.cmdp-essential.cmdp-highlighted {
background: linear-gradient(to right, rgba(237, 28, 63, 0.38), var(--cmdp-bg-highlight) 80%);
}
.cmdp-item.cmdp-essential .cmdp-item-desc {
color: #fff;
font-weight: 600;
}
.cmdp-item .cmdp-item-desc::before {
content: '\u2605';
margin-right: 6px;
font-size: 12px;
visibility: hidden;
}
.cmdp-item.cmdp-essential .cmdp-item-desc::before {
visibility: visible;
color: var(--cmdp-accent);
}
.cmdp-item.cmdp-essential kbd {
background-color: #fff;
color: #111;
}
.cmdp-item-desc {
font-size: 13px;
line-height: 1.35;
min-width: 0;
}
.cmdp-item-desc mark {
background: transparent;
color: var(--cmdp-accent);
font-weight: 700;
}
.cmdp-item-keys {
display: flex;
align-items: center;
gap: 2px;
flex-shrink: 0;
justify-self: end;
}
.cmdp-item-run {
flex-shrink: 0;
width: 26px;
height: 26px;
display: inline-flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid var(--cmdp-border);
border-radius: 50%;
color: var(--cmdp-fg-dim);
font-size: 11px;
cursor: pointer;
padding: 0;
padding-left: 2px;
transition: background 0.12s, color 0.12s, border-color 0.12s, transform 0.12s;
}
.cmdp-item-run:hover:not(:disabled),
.cmdp-item.cmdp-highlighted .cmdp-item-run:not(:disabled) {
background: var(--cmdp-accent);
border-color: var(--cmdp-accent);
color: #fff;
transform: scale(1.08);
}
.cmdp-item-run:disabled {
cursor: default;
opacity: 0.35;
background: transparent;
border-color: var(--cmdp-border);
color: var(--cmdp-fg-dim);
transform: none;
}
.cmdp-item-run:focus {
outline: none;
}
.cmdp-item kbd {
border: 1px solid #999;
border-radius: 2px;
font-weight: bold;
padding: 2px 4px;
margin: 1px;
background-color: #e0e0e0;
color: #333;
font-family: inherit;
font-size: 11px;
}
.cmdp-item kbd mark {
background: transparent;
color: var(--cmdp-accent);
}
.cmdp-empty {
padding: 20px 14px;
text-align: center;
}
.cmdp-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 6px 14px;
border-top: 1px solid var(--cmdp-border);
}
.cmdp-footer-hints {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.cmdp-footer kbd {
padding: 1px 3px;
font-size: 10px;
}
.cmdp-footer-links {
display: flex;
align-items: center;
gap: 6px;
}
.cmdp-footer-reference {
font-family: inherit;
font-size: 11px;
font-weight: 600;
color: var(--cmdp-fg-dim);
background: transparent;
border: 1px solid var(--cmdp-border);
border-radius: 999px;
padding: 2px 10px;
cursor: pointer;
text-decoration: none;
transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.cmdp-footer-reference:hover {
color: #fff;
border-color: var(--cmdp-accent);
background: rgba(237, 28, 63, 0.15);
}
.cmdp-non-executable-badge {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--cmdp-fg-dim);
border: 1px solid var(--cmdp-border);
border-radius: 2px;
padding: 1px 4px;
}
.cmdp-counter {
flex-shrink: 0;
font-size: 11px;
font-weight: 600;
color: var(--cmdp-fg-dim);
white-space: nowrap;
}
.cmdp-item-num {
font-size: 11px;
font-weight: 700;
color: var(--cmdp-accent);
text-align: right;
opacity: 0.8;
font-variant-numeric: tabular-nums;
}
.cmdp-category-filters {
border-bottom: 1px solid var(--cmdp-border);
background: rgba(0, 0, 0, 0.15);
}
.cmdp-tab-bar {
display: flex;
align-items: stretch;
gap: 0;
padding-right: 14px;
border-bottom: 1px solid var(--cmdp-border);
}
.cmdp-tab {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
font-size: 12px;
font-weight: 700;
color: var(--cmdp-fg-dim);
cursor: pointer;
user-select: none;
border-bottom: 2px solid transparent;
transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.cmdp-tab:hover {
color: var(--cmdp-fg);
background: rgba(255, 255, 255, 0.04);
}
.cmdp-tab.cmdp-tab-active {
color: #fff;
border-bottom-color: var(--cmdp-accent);
background: rgba(237, 28, 63, 0.08);
}
.cmdp-tab-count {
font-size: 9px;
font-weight: 700;
opacity: 0.6;
font-variant-numeric: tabular-nums;
}
.cmdp-tab.cmdp-tab-active .cmdp-tab-count {
opacity: 0.9;
color: var(--cmdp-accent);
}
.cmdp-tab-panel {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
padding: 6px 14px;
}
.cmdp-tab-content {
display: contents;
}
.cmdp-category-pill {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 4px 3px 4px;
border: 1px solid var(--cmdp-border);
border-radius: 999px;
background: var(--cmdp-bg-alt);
color: var(--cmdp-fg-dim);
font-size: 12px;
font-weight: 600;
cursor: pointer;
user-select: none;
transition: background 0.12s, border-color 0.12s, color 0.12s, box-shadow 0.12s;
}
.cmdp-category-pill:hover {
border-color: var(--cmdp-accent);
color: var(--cmdp-fg);
}
.cmdp-category-pill:has(.cmdp-category-checkbox:checked) {
background: linear-gradient(to right, rgba(237, 28, 63, 0.2), var(--cmdp-bg-alt) 85%);
border-color: var(--cmdp-accent);
color: #fff;
box-shadow: 0 0 0 1px rgba(237, 28, 63, 0.3);
}
.cmdp-category-checkbox {
display: none;
}
.cmdp-category-jump {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
margin: 0;
padding: 0;
font-size: 0;
font-family: inherit;
background: rgba(255, 255, 255, 0.06);
border: 1px solid var(--cmdp-border);
border-radius: 50%;
color: inherit;
cursor: pointer;
transition: background 0.12s, border-color 0.12s, transform 0.12s, box-shadow 0.12s;
line-height: 1;
flex-shrink: 0;
}
.cmdp-category-jump::after {
content: '';
display: block;
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 6px solid currentColor;
margin-top: 1px;
}
.cmdp-category-jump:hover {
background: var(--cmdp-accent);
border-color: var(--cmdp-accent);
color: #fff;
transform: scale(1.1);
box-shadow: 0 0 6px rgba(237, 28, 63, 0.5);
}
.cmdp-category-solo {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
margin: 0;
padding: 0;
font-family: inherit;
font-size: 8px;
font-weight: 900;
letter-spacing: -0.5px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid var(--cmdp-border);
border-radius: 50%;
color: var(--cmdp-fg-dim);
cursor: pointer;
transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.12s, box-shadow 0.12s;
line-height: 1;
}
.cmdp-category-solo::after {
content: 'S';
font-size: 9px;
font-weight: 800;
}
.cmdp-category-solo:hover {
background: #d4a017;
border-color: #d4a017;
color: #000;
transform: scale(1.1);
box-shadow: 0 0 6px rgba(212, 160, 23, 0.5);
}
.cmdp-header-count {
margin-left: auto;
font-size: 10px;
font-weight: 600;
opacity: 0.7;
letter-spacing: 0;
text-transform: none;
}
.cmdp-section .cmdp-header-count {
font-size: 11px;
}
.cmdp-pill-count {
font-size: 9px;
font-weight: 700;
opacity: 0.6;
min-width: 12px;
text-align: center;
}
.cmdp-region-label {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
width: 62px;
flex-shrink: 0;
}
@keyframes cmdp-flash {
0% { box-shadow: inset 0 0 0 200px rgba(212, 160, 23, 0.3); }
100% { box-shadow: inset 0 0 0 200px transparent; }
}
.cmdp-flash {
animation: cmdp-flash 0.6s ease-out;
}
.cmdp-reset-settings {
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: auto;
align-self: center;
padding: 2px 10px;
font-family: inherit;
font-size: 11px;
font-weight: 600;
color: var(--cmdp-fg);
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 999px;
cursor: pointer;
transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.cmdp-reset-settings:hover {
color: #fff;
border-color: var(--cmdp-accent);
background: rgba(237, 28, 63, 0.15);
}
`;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"5ZN0Y":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "renderShortcutsTable", ()=>renderShortcutsTable);
parcelHelpers.export(exports, "renderDisplayKey", ()=>renderDisplayKey);
var _litHtml = require("lit-html");
const SEPARATOR_RE = /(\s*\+\s*|\s*\/\s*|\s*,\s*|\s+or\s+)/g;
function renderShortcutsTable(registry) {
const sections = [];
for (const [section, categories] of registry.getGrouped()){
const tables = [];
for (const [category, shortcuts] of categories)tables.push(renderCategoryTable(category, shortcuts));
sections.push((0, _litHtml.html)`<h2>${section}</h2>
${tables}`);
}
return (0, _litHtml.html)`${sections}`;
}
function renderCategoryTable(category, shortcuts) {
return (0, _litHtml.html)`
<table>
<thead>
<tr>
<th colspan="2">${category}</th>
</tr>
<tr>
<th>Action</th>
<th>Shortcut</th>
</tr>
</thead>
<tbody>
${shortcuts.map(renderRow)}
</tbody>
</table>
`;
}
function renderRow(def) {
const shortcutCell = def.displayNote ? (0, _litHtml.html)`<pre>${def.displayNote}</pre>` : renderDisplayKey(def.displayKey);
return def.essential ? (0, _litHtml.html)`<tr class="essential-row">
<td>${def.description}</td>
<td>${shortcutCell}</td>
</tr>` : (0, _litHtml.html)`<tr>
<td>${def.description}</td>
<td>${shortcutCell}</td>
</tr>`;
}
function renderDisplayKey(displayKey) {
if (displayKey === '') return 0, _litHtml.nothing;
const parts = displayKey.split(SEPARATOR_RE);
const nodes = parts.map((part, idx)=>{
if (idx % 2 === 1) return part;
if (part === '') return 0, _litHtml.nothing;
return (0, _litHtml.html)`<kbd>${part}</kbd>`;
});
return (0, _litHtml.html)`${nodes}`;
}
},{"lit-html":"9fQBw","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"hBxwj":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "chartState", ()=>chartState);
parcelHelpers.export(exports, "selectCropPointWithMouseWheel", ()=>selectCropPointWithMouseWheel);
parcelHelpers.export(exports, "inheritCropPointCrop", ()=>inheritCropPointCrop);
parcelHelpers.export(exports, "getCropMapProperties", ()=>getCropMapProperties);
parcelHelpers.export(exports, "renderSpeedAndCropUI", ()=>renderSpeedAndCropUI);
parcelHelpers.export(exports, "initChartHooks", ()=>initChartHooks);
parcelHelpers.export(exports, "toggleChart", ()=>toggleChart);
parcelHelpers.export(exports, "getCropPreviewMouseTimeSetter", ()=>getCropPreviewMouseTimeSetter);
parcelHelpers.export(exports, "getMouseChartTimeAnnotationSetter", ()=>getMouseChartTimeAnnotationSetter);
parcelHelpers.export(exports, "toggleChartLoop", ()=>toggleChartLoop);
parcelHelpers.export(exports, "initializeChartData", ()=>initializeChartData);
parcelHelpers.export(exports, "loadChartData", ()=>loadChartData);
parcelHelpers.export(exports, "updateChartBounds", ()=>updateChartBounds);
parcelHelpers.export(exports, "updateChartTimeAnnotation", ()=>updateChartTimeAnnotation);
parcelHelpers.export(exports, "toggleCropChartLooping", ()=>toggleCropChartLooping);
parcelHelpers.export(exports, "triggerCropChartUpdates", ()=>triggerCropChartUpdates);
parcelHelpers.export(exports, "cropChartPreviewHandler", ()=>cropChartPreviewHandler);
parcelHelpers.export(exports, "setCurrentCropPointWithCurrentTime", ()=>setCurrentCropPointWithCurrentTime);
parcelHelpers.export(exports, "getDynamicCropComponents", ()=>getDynamicCropComponents);
parcelHelpers.export(exports, "getEasedCropComponents", ()=>getEasedCropComponents);
parcelHelpers.export(exports, "easeInInstant", ()=>easeInInstant);
parcelHelpers.export(exports, "updateDynamicCropOverlays", ()=>updateDynamicCropOverlays);
parcelHelpers.export(exports, "getInterpolatedCrop", ()=>getInterpolatedCrop);
parcelHelpers.export(exports, "cropChartSectionLoop", ()=>cropChartSectionLoop);
parcelHelpers.export(exports, "showChart", ()=>showChart);
parcelHelpers.export(exports, "hideChart", ()=>hideChart);
parcelHelpers.export(exports, "toggleCurrentChartVisibility", ()=>toggleCurrentChartVisibility);
var _immer = require("immer");
var _appState = require("./appState");
var _cropChartSpec = require("./ui/chart/cropchart/cropChartSpec");
var _util = require("./util/util");
var _litHtml = require("lit-html");
var _cropUtils = require("./crop-utils");
var _hoverRegion = require("./features/hints-bar/hover-region");
var _settingsEditor = require("./features/settings/settings-editor");
var _cropOverlay = require("./crop-overlay");
var _cropPreview = require("./crop/crop-preview");
var _markers = require("./markers");
var _chartPrimitives = require("./ui/chart/chartPrimitives");
var _chartutil = require("./ui/chart/chartutil");
var _speedChartSpec = require("./ui/chart/speedchart/speedChartSpec");
var _dragRecovery = require("./util/drag-recovery");
var _chartJs = require("chart.js");
var _scatterChartSpec = require("./ui/chart/scatterChartSpec");
var _d3Ease = require("d3-ease");
var _videoUtil = require("./util/videoUtil");
/** Wires up hover-region tracking on a chart canvas. The mousemove handler
* uses Chart.js's hit-test to flip between `{type}-chart` (cursor over the
* empty canvas) and `{type}-chart-point` (cursor over a data point) so the
* hints bar can surface point-manipulation chips only when relevant. */ function attachChartHoverDetector(canvas, chartInput) {
const base = chartInput.type === 'crop' ? 'crop-chart' : 'speed-chart';
const pointRegion = chartInput.type === 'crop' ? 'crop-chart-point' : 'speed-chart-point';
let inside = false;
let overPoint = false;
const sync = ()=>{
(0, _hoverRegion.setHoveredRegion)(inside ? overPoint ? pointRegion : base : null);
};
canvas.addEventListener('mouseenter', ()=>{
inside = true;
sync();
});
canvas.addEventListener('mouseleave', ()=>{
inside = false;
overPoint = false;
sync();
});
canvas.addEventListener('mousemove', (e)=>{
const chart = chartInput.chart;
if (!chart) return;
const elements = chart.getElementAtEvent(e);
const newOverPoint = elements.length > 0;
if (newOverPoint !== overPoint) {
overPoint = newOverPoint;
sync();
}
});
}
const chartState = {
speedChartInput: {
chart: null,
type: 'speed',
chartContainer: null,
chartContainerId: 'speedChartContainer',
chartContainerHook: null,
chartContainerHookPosition: 'afterend',
chartContainerStyle: 'width: 100%; height: calc(100% - 20px); position: relative; z-index: 55; opacity:0.8;',
chartCanvasTemplate: (0, _litHtml.html)`<canvas
id="speedChartCanvas"
width="1600px"
height="900px"
></canvas>`,
chartSpec: (0, _speedChartSpec.speedChartSpec),
chartCanvasId: 'speedChartCanvas',
minBound: 0,
maxBound: 0,
chartLoopKey: 'speedChartLoop',
dataMapKey: 'speedMap'
},
cropChartInput: {
chart: null,
type: 'crop',
chartContainer: null,
chartContainerId: 'cropChartContainer',
chartContainerHook: null,
chartContainerHookPosition: 'beforebegin',
chartContainerStyle: 'display:flex',
chartCanvasTemplate: (0, _litHtml.html)`<canvas id="cropChartCanvas" width="1600px" height="87px"></canvas>`,
chartCanvasId: 'cropChartCanvas',
chartSpec: (0, _cropChartSpec.getCropChartConfig)(false),
minBound: 0,
maxBound: 0,
chartLoopKey: 'cropChartLoop',
dataMapKey: 'cropMap'
},
currentChartInput: null,
prevChartTime: undefined,
shouldTriggerCropChartUpdates: false
};
function selectCropPointWithMouseWheel(e) {
if ((0, _appState.appState).isHotkeysEnabled && !e.ctrlKey && e.altKey && !e.shiftKey) (0, _util.blockEvent)(e);
else return;
const cropChart = chartState.cropChartInput.chart;
if (!cropChart) return;
const datasets = cropChart.data.datasets;
(0, _util.assertDefined)(datasets);
const cropChartData = datasets[0].data;
if (Math.abs(e.deltaY) > 0 && (0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedEndMarker && chartState.cropChartInput.chart) {
if (e.deltaY < 0) {
if ((0, _cropChartSpec.currentCropChartMode) === (0, _cropChartSpec.cropChartMode).Start) (0, _cropChartSpec.setCurrentCropPoint)(cropChart, (0, _appState.appState).currentCropPointIndex + 1, (0, _cropChartSpec.cropChartMode).End);
else (0, _cropChartSpec.setCurrentCropPoint)(cropChart, (0, _appState.appState).currentCropPointIndex, (0, _cropChartSpec.cropChartMode).Start);
} else if (e.deltaY > 0) {
if ((0, _cropChartSpec.currentCropChartMode) === (0, _cropChartSpec.cropChartMode).End) (0, _cropChartSpec.setCurrentCropPoint)(cropChart, (0, _appState.appState).currentCropPointIndex - 1, (0, _cropChartSpec.cropChartMode).Start);
else (0, _cropChartSpec.setCurrentCropPoint)(cropChart, (0, _appState.appState).currentCropPointIndex, (0, _cropChartSpec.cropChartMode).End);
}
}
if (!(0, _appState.appState).isCropChartLoopingOn) triggerCropChartUpdates();
(0, _util.assertDefined)(cropChartData);
const cropPoint = cropChartData[(0, _appState.appState).currentCropPointIndex];
(0, _cropUtils.setCropInputValue)(cropPoint.crop);
(0, _settingsEditor.highlightSpeedAndCropInputs)();
if ((0, _appState.appState).isCurrentChartVisible && chartState.currentChartInput?.type === 'crop') chartState.currentChartInput.chart?.update();
}
function inheritCropPointCrop(e) {
if ((0, _appState.appState).isHotkeysEnabled && e.ctrlKey && e.altKey && e.shiftKey && Math.abs(e.deltaY) > 0 && (0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedEndMarker && chartState.cropChartInput.chart) {
(0, _util.blockEvent)(e);
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const cropMap = markerPair.cropMap;
const cropPoint = cropMap[(0, _appState.appState).currentCropPointIndex];
const oldCrop = cropPoint.crop;
let newCrop = oldCrop;
if (e.deltaY < 0) {
const nextCropPoint = cropMap[Math.min((0, _appState.appState).currentCropPointIndex + 1, cropMap.length - 1)];
newCrop = nextCropPoint.crop;
} else if (e.deltaY > 0) {
const prevCropPoint = cropMap[Math.max((0, _appState.appState).currentCropPointIndex - 1, 0)];
newCrop = prevCropPoint.crop;
}
const draftCropMap = (0, _immer.createDraft)(cropMap);
const initCropMap = (0, _immer.finishDraft)(draftCropMap);
const shouldUpdateCropChart = oldCrop !== newCrop;
(0, _cropUtils.updateCropString)(newCrop, shouldUpdateCropChart, false, initCropMap);
}
}
function getCropMapProperties() {
let isDynamicCrop = false;
let enableZoomPan = false;
let initCropMap = null;
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const cropMap = markerPair.cropMap;
const draftCropMap = (0, _immer.createDraft)(cropMap);
initCropMap = (0, _immer.finishDraft)(draftCropMap);
isDynamicCrop = !(0, _cropUtils.isStaticCrop)(cropMap) || cropMap.length === 2 && (0, _appState.appState).currentCropPointIndex === 1;
enableZoomPan = markerPair.enableZoomPan;
}
return {
isDynamicCrop,
enableZoomPan,
initCropMap
};
}
function renderSpeedAndCropUI(rerenderCharts = true, updateCurrentCropPoint = false) {
if ((0, _appState.appState).isSettingsEditorOpen) {
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
(0, _chartutil.updateCharts)(markerPair, rerenderCharts);
// avoid updating current crop point unless crop map times have changed
if (updateCurrentCropPoint) setCurrentCropPointWithCurrentTime();
(0, _markers.renderMarkerPair)(markerPair, (0, _appState.appState).prevSelectedMarkerPairIndex);
(0, _util.assertDefined)((0, _appState.appState).speedInput);
(0, _appState.appState).speedInput.value = markerPair.speed.toString();
const cropMap = markerPair.cropMap;
const crop = cropMap[(0, _appState.appState).currentCropPointIndex].crop;
const isDynamicCrop = !(0, _cropUtils.isStaticCrop)(cropMap);
(0, _settingsEditor.renderCropForm)(crop);
if (!isDynamicCrop) (0, _cropOverlay.renderStaticCropOverlay)(crop);
else updateDynamicCropOverlays(cropMap, (0, _appState.appState).video.getCurrentTime(), isDynamicCrop);
const enableZoomPan = markerPair.enableZoomPan;
(0, _settingsEditor.enableZoomPanInput).value = enableZoomPan ? 'Enabled' : 'Disabled';
const formatter = enableZoomPan ? (0, _cropChartSpec.cropPointFormatter) : (0, _cropChartSpec.cropPointXYFormatter);
if (chartState.cropChartInput.chart) {
const plugins = chartState.cropChartInput.chart.options.plugins;
(0, _util.assertDefined)(plugins);
plugins.datalabels.formatter = formatter;
} else chartState.cropChartInput.chartSpec = (0, _cropChartSpec.getCropChartConfig)(enableZoomPan);
} else {
const crop = (0, _appState.appState).settings.newMarkerCrop;
(0, _settingsEditor.renderCropForm)(crop);
(0, _cropOverlay.renderStaticCropOverlay)(crop);
}
(0, _settingsEditor.highlightSpeedAndCropInputs)();
(0, _cropPreview.triggerCropPreviewRedraw)();
}
}
function initChartHooks() {
chartState.speedChartInput.chartContainerHook = (0, _appState.appState).hooks.speedChartContainer;
chartState.cropChartInput.chartContainerHook = (0, _appState.appState).hooks.cropChartContainer;
}
(0, _chartJs.Chart).helpers.merge((0, _chartJs.Chart).defaults.global, (0, _scatterChartSpec.scatterChartDefaults));
function toggleChart(chartInput) {
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedMarkerPairIndex != null) {
if (!chartInput.chart) {
if (chartState.currentChartInput && (0, _appState.appState).isCurrentChartVisible) hideChart();
chartState.currentChartInput = chartInput;
initializeChartData(chartInput.chartSpec, chartInput.dataMapKey);
chartInput.chartContainer = document.createElement('div');
chartInput.chartContainer.id = chartInput.chartContainerId;
chartInput.chartContainer.setAttribute('style', chartInput.chartContainerStyle);
(0, _litHtml.render)(chartInput.chartCanvasTemplate, chartInput.chartContainer);
(0, _util.assertDefined)(chartInput.chartContainerHook);
chartInput.chartContainerHook.insertAdjacentElement(chartInput.chartContainerHookPosition, chartInput.chartContainer);
chartInput.chart = new (0, _chartJs.Chart)(chartInput.chartCanvasId, chartInput.chartSpec);
chartInput.chart.renderSpeedAndCropUI = renderSpeedAndCropUI;
const chartCanvas = chartInput.chart.canvas;
(0, _util.assertDefined)(chartCanvas);
attachChartHoverDetector(chartCanvas, chartInput);
chartCanvas.removeEventListener('wheel', chartInput.chart.$zoom._wheelHandler);
const wheelHandler = chartInput.chart.$zoom._wheelHandler;
chartInput.chart.$zoom._wheelHandler = (e)=>{
if (e.ctrlKey && !e.altKey && !e.shiftKey) wheelHandler(e);
};
const ctx = chartInput.chart.ctx;
(0, _util.assertDefined)(ctx);
ctx.canvas.addEventListener('wheel', chartInput.chart.$zoom._wheelHandler);
ctx.canvas.addEventListener('contextmenu', (e)=>{
(0, _util.blockEvent)(e);
}, true);
ctx.canvas.addEventListener('pointerdown', getMouseChartTimeAnnotationSetter(chartInput), true);
(0, _appState.appState).isCurrentChartVisible = true;
(0, _appState.appState).isChartEnabled = true;
updateChartTimeAnnotation();
cropChartPreviewHandler();
// console.log(chartInput.chart);
} else {
(0, _util.assertDefined)(chartState.currentChartInput);
if (chartState.currentChartInput.type !== chartInput.type) {
hideChart();
chartState.currentChartInput = chartInput;
}
toggleCurrentChartVisibility();
(0, _appState.appState).isChartEnabled = (0, _appState.appState).isCurrentChartVisible;
}
} else (0, _util.flashMessage)('Please open a marker pair editor before toggling a chart input.', 'olive');
}
function getCropPreviewMouseTimeSetter(modalContainer) {
function getSeekTime(e) {
const rect = modalContainer.getBoundingClientRect();
const x = e.clientX - rect.left;
const scaledX = x / rect.width;
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const duration = markerPair.end - markerPair.start;
const seekTime = markerPair.start + scaledX * duration;
return seekTime;
}
return function seekHandler(e) {
if (e.buttons !== 2) return;
(0, _util.blockEvent)(e);
const pointerId = e.pointerId;
// shift+right-click context menu opens screenshot tool in firefox 67.0.2
function seekDragHandler(e) {
// Self-defending end check: pointermove always reports the
// currently-held buttons bitmask. If the right button is no longer
// held but we're still receiving pointermove, the browser dropped
// our `pointerup` — known to happen in Vivaldi (mouse gestures
// suppress the up event), Chrome with certain devtools docking
// configurations, and after a suppressed contextmenu. End the
// drag immediately so the cursor stops scrubbing the video.
if ((e.buttons & 2) === 0) {
cleanup();
return;
}
const seekTime = (0, _util.timeRounder)(getSeekTime(e));
if (Math.abs((0, _appState.appState).video.getCurrentTime() - seekTime) >= 0.01) (0, _util.seekToSafe)((0, _appState.appState).video, seekTime);
}
seekDragHandler(e);
let unregisterDragRecovery = ()=>{};
function cleanup() {
modalContainer.removeEventListener('pointermove', seekDragHandler);
modalContainer.removeEventListener('pointerup', seekDragEnd, {
capture: true
});
modalContainer.removeEventListener('pointercancel', seekDragCancel, {
capture: true
});
if (modalContainer.hasPointerCapture(pointerId)) modalContainer.releasePointerCapture(pointerId);
unregisterDragRecovery();
}
function seekDragEnd(e) {
(0, _util.blockEvent)(e);
cleanup();
}
function seekDragCancel() {
cleanup();
}
modalContainer.setPointerCapture(pointerId);
// Attach to the captured target rather than `document` so the
// spec-mandated `pointercancel` reaches us when the browser
// implicitly releases capture — right-click drags were particularly
// prone to stuck-seeking because some browsers swallow `pointerup`
// when the user releases over a focused devtools panel or after a
// suppressed contextmenu.
modalContainer.addEventListener('pointermove', seekDragHandler);
modalContainer.addEventListener('pointerup', seekDragEnd, {
once: true,
capture: true
});
modalContainer.addEventListener('pointercancel', seekDragCancel, {
once: true,
capture: true
});
document.addEventListener('contextmenu', (0, _util.blockEvent), {
once: true,
capture: true
});
unregisterDragRecovery = (0, _dragRecovery.registerActiveDragCleanup)(cleanup);
};
}
function getMouseChartTimeAnnotationSetter(chartInput) {
return function mouseChartTimeAnnotationSetter(e) {
if (e.buttons !== 2) return;
(0, _util.blockEvent)(e);
if (!chartInput.chart) return;
const chart = chartInput.chart;
const configOptions = chart.config.options;
(0, _util.assertDefined)(configOptions);
const chartOpts = configOptions;
const chartLoop = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex][chartInput.chartLoopKey];
(0, _util.assertDefined)(chart.ctx);
const chartCtx = chart.ctx;
const captureTarget = chartCtx.canvas;
const pointerId = e.pointerId;
// shift+right-click context menu opens screenshot tool in firefox 67.0.2
function chartTimeAnnotationDragHandler(e) {
// Self-defending end check — see the matching seekDragHandler
// above for the rationale. Vivaldi and some Chromium-based browser
// configurations occasionally drop `pointerup` for right-button
// releases, leaving the seek tracking the cursor indefinitely.
// Treat a pointermove without the right button held as the
// implicit drag end.
if ((e.buttons & 2) === 0) {
cleanup();
return;
}
const time = (0, _util.timeRounder)(chart.scales['x-axis-1'].getValueForPixel(e.offsetX));
chartOpts.annotation.annotations[0].value = time;
if (Math.abs((0, _appState.appState).video.getCurrentTime() - time) >= 0.01) (0, _util.seekToSafe)((0, _appState.appState).video, time);
if (!e.ctrlKey && !e.altKey && e.shiftKey) {
chartOpts.annotation.annotations[1].value = time;
chartLoop.start = time;
chart.update();
} else if (!e.ctrlKey && e.altKey && !e.shiftKey) {
chartOpts.annotation.annotations[2].value = time;
chartLoop.end = time;
chart.update();
}
}
chartTimeAnnotationDragHandler(e);
let unregisterDragRecovery = ()=>{};
function cleanup() {
captureTarget.removeEventListener('pointermove', chartTimeAnnotationDragHandler);
captureTarget.removeEventListener('pointerup', chartTimeAnnotationDragEnd, {
capture: true
});
captureTarget.removeEventListener('pointercancel', chartTimeAnnotationDragCancel, {
capture: true
});
if (captureTarget.hasPointerCapture(pointerId)) captureTarget.releasePointerCapture(pointerId);
unregisterDragRecovery();
}
function chartTimeAnnotationDragEnd(e) {
(0, _util.blockEvent)(e);
cleanup();
}
function chartTimeAnnotationDragCancel() {
cleanup();
}
captureTarget.setPointerCapture(pointerId);
// Attach to the captured canvas so `pointercancel` reaches us when
// the browser implicitly releases capture. Right-click drags were
// particularly prone to stuck-seeking because some browsers don't
// dispatch `pointerup` to `document` after a suppressed contextmenu
// or when the user releases over a focused devtools panel.
captureTarget.addEventListener('pointermove', chartTimeAnnotationDragHandler);
captureTarget.addEventListener('pointerup', chartTimeAnnotationDragEnd, {
once: true,
capture: true
});
captureTarget.addEventListener('pointercancel', chartTimeAnnotationDragCancel, {
once: true,
capture: true
});
document.addEventListener('contextmenu', (0, _util.blockEvent), {
once: true,
capture: true
});
unregisterDragRecovery = (0, _dragRecovery.registerActiveDragCleanup)(cleanup);
};
}
function toggleChartLoop() {
if (chartState.currentChartInput && (0, _appState.appState).isCurrentChartVisible && (0, _appState.appState).prevSelectedMarkerPairIndex != null) {
const chart = chartState.currentChartInput.chart;
if (!chart) return;
const chartConfigOptions = chart.config.options;
(0, _util.assertDefined)(chartConfigOptions);
const chartOpts = chartConfigOptions;
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const chartLoop = markerPair[chartState.currentChartInput.chartLoopKey];
if (chartLoop.enabled) {
chartLoop.enabled = false;
chartOpts.annotation.annotations[1].borderColor = 'rgba(0, 255, 0, 0.4)';
chartOpts.annotation.annotations[2].borderColor = 'rgba(255, 215, 0, 0.4)';
(0, _util.flashMessage)('Speed chart looping disabled', 'red');
} else {
chartLoop.enabled = true;
chartOpts.annotation.annotations[1].borderColor = 'rgba(0, 255, 0, 0.9)';
chartOpts.annotation.annotations[2].borderColor = 'rgba(255, 215, 0, 0.9)';
(0, _util.flashMessage)('Speed chart looping enabled', 'green');
}
chart.update();
}
}
function initializeChartData(chartConfig, dataMapKey) {
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedMarkerPairIndex != null) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const dataMap = markerPair[dataMapKey];
const chartData = chartConfig.data;
(0, _util.assertDefined)(chartData);
const datasets = chartData.datasets;
(0, _util.assertDefined)(datasets);
datasets[0].data = dataMap;
updateChartBounds(chartConfig, markerPair.start, markerPair.end);
}
}
function loadChartData(chartInput) {
if (chartInput?.chart) {
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedMarkerPairIndex != null) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const dataMapKey = chartInput.dataMapKey;
const dataMap = markerPair[dataMapKey];
const chart = chartInput.chart;
const chartDatasets = chart.data.datasets;
(0, _util.assertDefined)(chartDatasets);
chartDatasets[0].data = dataMap;
updateChartBounds(chart.config, markerPair.start, markerPair.end);
if ((0, _appState.appState).isCurrentChartVisible && chartState.currentChartInput === chartInput) chart.update();
}
}
}
function updateChartBounds(chartConfig, start, end) {
if (chartState.cropChartInput) {
chartState.cropChartInput.minBound = start;
chartState.cropChartInput.maxBound = end;
}
if (chartState.speedChartInput) {
chartState.speedChartInput.minBound = start;
chartState.speedChartInput.maxBound = end;
}
const opts = chartConfig.options;
(0, _util.assertDefined)(opts);
const scales = opts.scales;
(0, _util.assertDefined)(scales);
const xAxes = scales.xAxes;
(0, _util.assertDefined)(xAxes);
const ticks = xAxes[0].ticks;
(0, _util.assertDefined)(ticks);
ticks.min = start;
ticks.max = end;
const plugins = opts.plugins;
(0, _util.assertDefined)(plugins);
plugins.zoom.pan.rangeMin.x = start;
plugins.zoom.pan.rangeMax.x = end;
plugins.zoom.zoom.rangeMin.x = start;
plugins.zoom.zoom.rangeMax.x = end;
}
function updateChartTimeAnnotation() {
if ((0, _appState.appState).isCurrentChartVisible) {
if (chartState.prevChartTime !== (0, _appState.appState).video.getCurrentTime()) {
const time = (0, _appState.appState).video.getCurrentTime();
chartState.prevChartTime = time;
const currentInput = chartState.currentChartInput;
(0, _util.assertDefined)(currentInput);
const chart = currentInput.chart;
(0, _util.assertDefined)(chart);
const configOptions = chart.config.options;
(0, _util.assertDefined)(configOptions);
configOptions.annotation.annotations[0].value = (0, _util.clampNumber)(time, currentInput.minBound, currentInput.maxBound);
const timeAnnotation = Object.values(chart.annotation.elements)[0];
timeAnnotation.options.value = (0, _util.clampNumber)(time, currentInput.minBound, currentInput.maxBound);
timeAnnotation.configure();
chart.render();
}
}
requestAnimationFrame(updateChartTimeAnnotation);
}
function toggleCropChartLooping() {
if (!(0, _appState.appState).isCropChartLoopingOn) {
(0, _appState.appState).isCropChartLoopingOn = true;
(0, _util.flashMessage)('Dynamic crop looping enabled', 'green');
} else {
(0, _appState.appState).isCropChartLoopingOn = false;
(0, _util.flashMessage)('Dynamic crop looping disabled', 'red');
}
}
function triggerCropChartUpdates() {
chartState.shouldTriggerCropChartUpdates = true;
cropChartPreviewHandler(false);
(0, _cropPreview.triggerCropPreviewRedraw)();
}
function cropChartPreviewHandler(loop = true) {
const chart = chartState.cropChartInput.chart;
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen && chart) {
const datasets = chart.data.datasets;
(0, _util.assertDefined)(datasets);
const chartData = datasets[0].data;
const time = (0, _appState.appState).video.getCurrentTime();
const isDynamicCrop = !(0, _cropUtils.isStaticCrop)(chartData);
const isCropChartVisible = chartState.currentChartInput?.type == 'crop' && (0, _appState.appState).isCurrentChartVisible;
if (chartState.shouldTriggerCropChartUpdates || // assume auto time-based update not required for crop chart section if looping section
(0, _appState.appState).isCropChartLoopingOn && isCropChartVisible || chartState.cropChartInput.chart && ((0, _cropOverlay.isMouseManipulatingCrop) || (0, _cropOverlay.isDrawingCrop))) {
chartState.shouldTriggerCropChartUpdates = false;
cropChartSectionLoop();
} else if (isDynamicCrop) setCurrentCropPointWithCurrentTime();
if (isDynamicCrop || (0, _appState.appState).currentCropPointIndex > 0) (0, _settingsEditor.cropInputLabel).textContent = `Crop Point ${(0, _appState.appState).currentCropPointIndex + 1}`;
else (0, _settingsEditor.cropInputLabel).textContent = `Crop`;
updateDynamicCropOverlays(chartData, time, isDynamicCrop);
}
if (loop) (0, _appState.appState).video.requestVideoFrameCallback(()=>{
cropChartPreviewHandler(loop);
});
}
function setCurrentCropPointWithCurrentTime() {
const cropChart = chartState.cropChartInput.chart;
if (cropChart) {
const cropDatasets = cropChart.data.datasets;
(0, _util.assertDefined)(cropDatasets);
const chartData = cropDatasets[0].data;
const time = (0, _appState.appState).video.getCurrentTime();
const searchCropPoint = {
x: time,
y: 0,
crop: ''
};
const [istart, iend] = (0, _cropChartSpec.currentCropChartSection);
let [start, end] = (0, _util.bsearch)(chartData, searchCropPoint, (0, _chartPrimitives.sortX));
if ((0, _cropChartSpec.currentCropChartMode) === (0, _cropChartSpec.cropChartMode).Start) {
if (start === end && end === iend) start--;
(0, _cropChartSpec.setCurrentCropPoint)(cropChart, Math.min(start, chartData.length - 2));
} else if ((0, _cropChartSpec.currentCropChartMode) === (0, _cropChartSpec.cropChartMode).End) {
if (start === end && start === istart) end++;
(0, _cropChartSpec.setCurrentCropPoint)(cropChart, Math.max(end, 1));
}
}
}
function getDynamicCropComponents() {
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const cropMap = markerPair.cropMap;
const chartData = cropMap;
const isDynamicCrop = !(0, _cropUtils.isStaticCrop)(cropMap);
if (!isDynamicCrop) return null;
const sectStart = chartData[(0, _cropChartSpec.currentCropChartSection)[0]];
const sectEnd = chartData[(0, _cropChartSpec.currentCropChartSection)[1]];
return getEasedCropComponents(sectStart, sectEnd);
}
return null;
}
function getEasedCropComponents(sectStart, sectEnd) {
const [startX, startY, startW, startH] = (0, _cropUtils.getCropComponents)(sectStart.crop);
const [endX, endY, endW, endH] = (0, _cropUtils.getCropComponents)(sectEnd.crop);
const currentTime = (0, _appState.appState).video.getCurrentTime();
const clampedCurrentTime = (0, _util.clampNumber)(currentTime, sectStart.x, sectEnd.x);
const easingFunc = sectEnd.easeIn == 'instant' ? easeInInstant : (0, _d3Ease.easeSinInOut);
const startTime = sectStart.x;
const endTime = (0, _videoUtil.getFrameTimeBetweenLeftFrames)(sectEnd.x);
const [easedX, easedY, easedW, easedH] = [
[
startX,
endX
],
[
startY,
endY
],
[
startW,
endW
],
[
startH,
endH
]
].map((pair)=>(0, _util.getEasedValue)(easingFunc, pair[0], pair[1], startTime, endTime, clampedCurrentTime));
return [
easedX,
easedY,
easedW,
easedH
];
}
const easeInInstant = (timePercentage)=>{
return timePercentage >= 1 ? 1 : 0;
};
function updateDynamicCropOverlays(chartData, _currentTime, isDynamicCrop) {
if (isDynamicCrop || (0, _appState.appState).currentCropPointIndex > 0) {
(0, _cropOverlay.cropOverlayElements).cropChartSectionStart.style.display = 'block';
(0, _cropOverlay.cropOverlayElements).cropChartSectionEnd.style.display = 'block';
(0, _cropOverlay.cropOverlayElements).cropRectBorder.style.opacity = '0.6';
} else {
(0, _cropOverlay.cropOverlayElements).cropChartSectionStart.style.display = 'none';
(0, _cropOverlay.cropOverlayElements).cropChartSectionEnd.style.display = 'none';
(0, _cropOverlay.cropOverlayElements).cropRectBorder.style.opacity = '1';
return;
}
const sectStart = chartData[(0, _cropChartSpec.currentCropChartSection)[0]];
const sectEnd = chartData[(0, _cropChartSpec.currentCropChartSection)[1]];
[
(0, _cropOverlay.cropOverlayElements).cropChartSectionStartBorderGreen,
(0, _cropOverlay.cropOverlayElements).cropChartSectionStartBorderWhite
].map((cropRect)=>{
(0, _util.assertDefined)(cropRect);
(0, _cropOverlay.setCropOverlay)(cropRect, sectStart.crop);
});
[
(0, _cropOverlay.cropOverlayElements).cropChartSectionEndBorderYellow,
(0, _cropOverlay.cropOverlayElements).cropChartSectionEndBorderWhite
].map((cropRect)=>{
(0, _util.assertDefined)(cropRect);
(0, _cropOverlay.setCropOverlay)(cropRect, sectEnd.crop);
});
const currentCropPoint = chartData[(0, _appState.appState).currentCropPointIndex];
if ((0, _cropOverlay.cropCrossHairEnabled) && (0, _cropOverlay.cropOverlayElements).cropCrossHair) {
(0, _cropOverlay.cropOverlayElements).cropCrossHairs.map((cropCrossHair)=>{
(0, _cropOverlay.setCropCrossHair)(cropCrossHair, currentCropPoint.crop);
});
(0, _cropOverlay.cropOverlayElements).cropCrossHair.style.stroke = (0, _cropChartSpec.currentCropChartMode) === (0, _cropChartSpec.cropChartMode).Start ? 'lime' : 'yellow';
}
const sectionStartEl = (0, _cropOverlay.cropOverlayElements).cropChartSectionStart;
(0, _util.assertDefined)(sectionStartEl);
const sectionEndEl = (0, _cropOverlay.cropOverlayElements).cropChartSectionEnd;
(0, _util.assertDefined)(sectionEndEl);
if ((0, _cropChartSpec.currentCropChartMode) === (0, _cropChartSpec.cropChartMode).Start) {
sectionStartEl.setAttribute('opacity', '0.8');
sectionEndEl.setAttribute('opacity', '0.3');
} else if ((0, _cropChartSpec.currentCropChartMode) === (0, _cropChartSpec.cropChartMode).End) {
sectionStartEl.setAttribute('opacity', '0.3');
sectionEndEl.setAttribute('opacity', '0.8');
}
const [easedX, easedY, easedW, easedH] = getEasedCropComponents(sectStart, sectEnd);
[
(0, _cropOverlay.cropOverlayElements).cropRect,
(0, _cropOverlay.cropOverlayElements).cropRectBorderBlack,
(0, _cropOverlay.cropOverlayElements).cropRectBorderWhite
].map((cropRect)=>{
(0, _util.assertDefined)(cropRect);
(0, _cropOverlay.setCropOverlayDimensions)(cropRect, easedX, easedY, easedW, easedH);
});
}
function getInterpolatedCrop(sectStart, sectEnd, time) {
const [startX, startY, startW, startH] = (0, _cropUtils.getCropComponents)(sectStart.crop);
const [endX, endY, endW, endH] = (0, _cropUtils.getCropComponents)(sectEnd.crop);
const clampedTime = (0, _util.clampNumber)(time, sectStart.x, sectEnd.x);
const easingFunc = sectEnd.easeIn == 'instant' ? easeInInstant : (0, _d3Ease.easeSinInOut);
const startTime = sectStart.x;
const endTime = (0, _videoUtil.getFrameTimeBetweenLeftFrames)(sectEnd.x);
const [x, y, w, h] = [
[
startX,
endX
],
[
startY,
endY
],
[
startW,
endW
],
[
startH,
endH
]
].map(([startValue, endValue])=>{
const eased = (0, _util.getEasedValue)(easingFunc, startValue, endValue, startTime, endTime, clampedTime);
return eased;
});
// return [x, y, w, h];
return (0, _util.getCropString)(x, y, w, h);
}
function cropChartSectionLoop() {
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen) {
if ((0, _appState.appState).prevSelectedMarkerPairIndex != null) {
const chart = chartState.cropChartInput.chart;
if (chart == null) return;
const loopDatasets = chart.data.datasets;
(0, _util.assertDefined)(loopDatasets);
const chartData = loopDatasets[0].data;
(0, _util.assertDefined)(chartData);
const [start, end] = (0, _cropChartSpec.currentCropChartSection);
const sectStart = chartData[start].x;
const sectEnd = chartData[end].x;
const isTimeBetweenCropChartSection = sectStart <= (0, _appState.appState).video.getCurrentTime() && (0, _appState.appState).video.getCurrentTime() <= sectEnd;
if (!isTimeBetweenCropChartSection) (0, _util.seekToSafe)((0, _appState.appState).video, sectStart);
}
}
}
function showChart() {
if (chartState.currentChartInput?.chartContainer) {
if (0, _cropOverlay.isDrawingCrop) (0, _cropOverlay.finishDrawingCrop)(true);
chartState.currentChartInput.chartContainer.style.display = 'block';
(0, _appState.appState).isCurrentChartVisible = true;
const chartToUpdate = chartState.currentChartInput.chart;
(0, _util.assertDefined)(chartToUpdate);
chartToUpdate.update();
// force chart time annotation to update
chartState.prevChartTime = -1;
}
}
function hideChart() {
if (chartState.currentChartInput?.chartContainer) {
chartState.currentChartInput.chartContainer.style.display = 'none';
(0, _appState.appState).isCurrentChartVisible = false;
}
}
function toggleCurrentChartVisibility() {
if (!(0, _appState.appState).isCurrentChartVisible) showChart();
else hideChart();
}
},{"immer":"R6AMM","./appState":"g0AlP","./ui/chart/cropchart/cropChartSpec":"67uoo","./util/util":"99arg","lit-html":"9fQBw","./crop-utils":"k2gwb","./features/hints-bar/hover-region":"l2Fo9","./features/settings/settings-editor":"jDViX","./crop-overlay":"6s727","./crop/crop-preview":"9T0zg","./markers":"EQEoZ","./ui/chart/chartPrimitives":"hk4AN","./ui/chart/chartutil":"AvPxz","./ui/chart/speedchart/speedChartSpec":"8LMgO","./util/drag-recovery":"3BYSh","chart.js":"kS68a","./ui/chart/scatterChartSpec":"4kM90","d3-ease":[["easeSinInOut","jTm0T","sinInOut"]],"./util/videoUtil":"93pN0","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"67uoo":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "cropChartMode", ()=>cropChartMode);
parcelHelpers.export(exports, "currentCropChartMode", ()=>currentCropChartMode);
parcelHelpers.export(exports, "setCropChartMode", ()=>setCropChartMode);
parcelHelpers.export(exports, "setCurrentCropPoint", ()=>setCurrentCropPoint);
parcelHelpers.export(exports, "currentCropChartSection", ()=>currentCropChartSection);
parcelHelpers.export(exports, "setCurrentCropChartSection", ()=>setCurrentCropChartSection);
parcelHelpers.export(exports, "updateCurrentCropPoint", ()=>updateCurrentCropPoint);
parcelHelpers.export(exports, "cropPointFormatter", ()=>cropPointFormatter);
parcelHelpers.export(exports, "cropPointXYFormatter", ()=>cropPointXYFormatter);
parcelHelpers.export(exports, "getCropChartConfig", ()=>getCropChartConfig);
var _chartJs = require("chart.js");
var _chartJsDefault = parcelHelpers.interopDefault(_chartJs);
var _appState = require("../../../appState");
var _util = require("../../../util/util");
var _chartPrimitives = require("../chartPrimitives");
var _scatterChartSpec = require("../scatterChartSpec");
var _lodashIsequal = require("lodash.isequal");
var _lodashIsequalDefault = parcelHelpers.interopDefault(_lodashIsequal);
const inputId = 'crop-input';
var cropChartMode = /*#__PURE__*/ function(cropChartMode) {
cropChartMode[cropChartMode["Start"] = 0] = "Start";
cropChartMode[cropChartMode["End"] = 1] = "End";
return cropChartMode;
}({});
let currentCropChartMode = 0;
function setCropChartMode(mode) {
currentCropChartMode = mode;
}
function setCurrentCropPoint(cropChart, cropPointIndex, mode) {
const maxIndex = cropChart?.data.datasets?.[0].data ? cropChart.data.datasets[0].data.length - 1 : 1;
const newCropPointIndex = (0, _util.clampNumber)(cropPointIndex, 0, maxIndex);
const cropPointIndexChanged = (0, _appState.appState).currentCropPointIndex !== newCropPointIndex;
(0, _appState.appState).currentCropPointIndex = newCropPointIndex;
const oldCropChartSection = currentCropChartSection;
if ((0, _appState.appState).currentCropPointIndex <= 0) {
setCropChartMode(0);
setCurrentCropChartSection(cropChart, [
0,
1
]);
} else if ((0, _appState.appState).currentCropPointIndex >= maxIndex) {
setCropChartMode(1);
setCurrentCropChartSection(cropChart, [
maxIndex - 1,
maxIndex
]);
} else {
if (mode != null) currentCropChartMode = mode;
currentCropChartMode === 0 ? setCurrentCropChartSection(cropChart, [
(0, _appState.appState).currentCropPointIndex,
(0, _appState.appState).currentCropPointIndex + 1
]) : setCurrentCropChartSection(cropChart, [
(0, _appState.appState).currentCropPointIndex - 1,
(0, _appState.appState).currentCropPointIndex
]);
}
const cropChartSectionChanged = !(0, _lodashIsequalDefault.default)(currentCropChartSection, oldCropChartSection);
if ((cropPointIndexChanged || cropChartSectionChanged) && cropChart) cropChart.renderSpeedAndCropUI(true, false);
}
let currentCropChartSection = [
0,
1
];
function setCurrentCropChartSection(cropChart, [left, right]) {
const maxIndex = cropChart?.data.datasets?.[0].data ? cropChart.data.datasets[0].data.length - 1 : 1;
if (left <= 0) currentCropChartSection = [
0,
1
];
else if (left >= maxIndex) currentCropChartSection = [
maxIndex - 1,
maxIndex
];
else if (left === right) currentCropChartSection = [
left,
left + 1
];
else currentCropChartSection = [
left,
right
];
}
const updateCurrentCropPoint = function(cropChart, cropString) {
const cropChartDatasets = cropChart.data.datasets;
(0, _util.assertDefined)(cropChartDatasets, 'Expected crop chart datasets');
const cropChartData = cropChartDatasets[0].data;
(0, _util.assertDefined)(cropChartData, 'Expected crop chart data');
const cropPoint = cropChartData[(0, _appState.appState).currentCropPointIndex];
cropPoint.crop = cropString;
cropChart.update();
};
const cropPointFormatter = (point)=>{
return `T:${point.x.toFixed(2)}\nC:${point.crop}`;
};
const cropPointXYFormatter = (point, ctx)=>{
const [x, y, w, h] = point.crop.split(':');
const index = ctx.dataIndex;
const label = index === 0 ? `T:${point.x.toFixed(2)}\nC:${x}:${y}:${w}:${h}` : `T:${point.x.toFixed(2)}\nC:${x}:${y}`;
return label;
};
function getCropPointStyle(ctx) {
const index = ctx.dataIndex;
return index === (0, _appState.appState).currentCropPointIndex ? 'rectRounded' : 'circle';
}
function getCropPointColor(ctx) {
const index = ctx.dataIndex;
if (index === currentCropChartSection[0]) return 'green';
else if (index === currentCropChartSection[1]) return 'yellow';
else return 'red';
}
function getCropPointBackgroundOverlayColor(ctx) {
const cropPoint = ctx.dataset.data[ctx.dataIndex];
return cropPoint.easeIn === 'instant' ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0)';
}
function getCropPointBorderColor(ctx) {
const index = ctx.dataIndex;
return index === (0, _appState.appState).currentCropPointIndex ? 'black' : (0, _chartPrimitives.medgrey)(0.9);
}
function getCropPointBorderWidth(ctx) {
const index = ctx.dataIndex;
return index === (0, _appState.appState).currentCropPointIndex ? 2 : 1;
}
function getCropPointRadius(ctx) {
const index = ctx.dataIndex;
return index === (0, _appState.appState).currentCropPointIndex ? 6 : 4;
}
const cropChartConfig = {
data: {
datasets: [
{
label: 'Crop',
lineTension: 0,
data: [],
showLine: true,
pointBackgroundColor: getCropPointColor,
pointBorderColor: getCropPointBorderColor,
pointBorderWidth: getCropPointBorderWidth,
pointStyle: getCropPointStyle,
pointRadius: getCropPointRadius,
backgroundOverlayColor: getCropPointBackgroundOverlayColor,
backgroundOverlayMode: 'multiply',
pointHitRadius: 3
}
]
},
options: {
scales: {
yAxes: [
{
display: false
}
]
},
plugins: {
datalabels: {
formatter: cropPointFormatter,
font: {
size: 10,
weight: 'normal'
}
}
},
dragY: false,
dragX: true
}
};
function getCropChartConfig(isCropChartPanOnly) {
let cropChartConfigOverrides = {}; // eslint-disable-line no-useless-assignment
if (isCropChartPanOnly) cropChartConfigOverrides = {
options: {
plugins: {
datalabels: {
formatter: cropPointXYFormatter
}
}
}
};
else cropChartConfigOverrides = {
options: {
plugins: {
datalabels: {
formatter: cropPointFormatter
}
}
}
};
const cropChartConfigOverridden = (0, _chartJsDefault.default).helpers.merge(cropChartConfig, cropChartConfigOverrides);
return (0, _chartJsDefault.default).helpers.merge((0, _scatterChartSpec.scatterChartSpec)('crop', inputId), cropChartConfigOverridden);
}
},{"chart.js":"kS68a","../../../appState":"g0AlP","../../../util/util":"99arg","../chartPrimitives":"hk4AN","../scatterChartSpec":"4kM90","lodash.isequal":"ioJZr","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"hk4AN":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "sortX", ()=>sortX);
parcelHelpers.export(exports, "lightgrey", ()=>lightgrey);
parcelHelpers.export(exports, "medgrey", ()=>medgrey);
parcelHelpers.export(exports, "grey", ()=>grey);
parcelHelpers.export(exports, "cubicInOutTension", ()=>cubicInOutTension);
parcelHelpers.export(exports, "roundX", ()=>roundX);
parcelHelpers.export(exports, "roundY", ()=>roundY);
parcelHelpers.export(exports, "getInputUpdater", ()=>getInputUpdater);
const sortX = (a, b)=>{
if (a.x < b.x) return -1;
if (a.x > b.x) return 1;
return 0;
};
const lightgrey = (opacity)=>`rgba(120, 120, 120, ${opacity})`;
const medgrey = (opacity)=>`rgba(90, 90, 90, ${opacity})`;
const grey = (opacity)=>`rgba(50, 50, 50, ${opacity})`;
const cubicInOutTension = 0.6;
function getRounder(multiple, precision) {
return (value)=>{
const roundedValue = Math.round(value / multiple) * multiple;
return +roundedValue.toFixed(precision);
};
}
const roundX = getRounder(0.01, 2);
const roundY = getRounder(0.05, 2);
function getInputUpdater(inputId) {
return function(newValue) {
const input = document.getElementById(inputId);
if (input) {
if (newValue != null) input.value = newValue.toString();
} else console.log(`Input with Id ${inputId} not found.`);
};
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"4kM90":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "scatterChartDefaults", ()=>scatterChartDefaults);
parcelHelpers.export(exports, "getScatterPointColor", ()=>getScatterPointColor);
parcelHelpers.export(exports, "addSpeedPoint", ()=>addSpeedPoint);
parcelHelpers.export(exports, "addCropPoint", ()=>addCropPoint);
parcelHelpers.export(exports, "scatterChartSpec", ()=>scatterChartSpec);
var _immer = require("immer");
var _undoredo = require("../../util/undoredo");
var _util = require("../../util/util");
var _charts = require("../../charts");
var _appState = require("../../appState");
var _chartPrimitives = require("./chartPrimitives");
var _cropChartSpec = require("./cropchart/cropChartSpec");
var _cropOverlay = require("../../crop-overlay");
const scatterChartDefaults = {
defaultColor: 'rgba(255, 255, 255, 1)',
defaultFontSize: 16,
defaultFontStyle: 'bold',
defaultFontColor: 'rgba(120, 120, 120, 1)',
maintainAspectRatio: false,
hover: {
mode: 'nearest'
},
animation: {
duration: 0
}
};
function getScatterPointColor(context) {
const index = context.dataIndex;
const value = context.dataset.data[index];
return value.y <= 1 ? `rgba(255, ${100 * value.y}, 100, 0.9)` : `rgba(${130 - 90 * (value.y - 1)}, 100, 245, 0.9)`;
}
function getScatterChartBounds(chartInstance) {
const scatterChartBounds = {
XMinBound: chartInstance.options.scales.xAxes[0].ticks.min,
XMaxBound: chartInstance.options.scales.xAxes[0].ticks.max,
YMinBound: 0.05,
YMaxBound: 2
};
return scatterChartBounds;
}
function displayDataLabel(context) {
return context.active ? true : 'auto';
}
function alignDataLabel(context) {
const index = context.dataIndex;
// const value = context.dataset.data[index];
if (index === 0) return 'right';
else if (index === context.dataset.data.length - 1) return 'left';
else if (context.dataset.data[context.dataIndex].y > 1.85) return 'start';
else return 'end';
}
const addSpeedPoint = function(time, speed) {
// console.log(element, dataAtClick);
if (time && speed) {
const scatterChartBounds = getScatterChartBounds(this);
if (time <= scatterChartBounds.XMinBound || time >= scatterChartBounds.XMaxBound || speed < scatterChartBounds.YMinBound || speed > scatterChartBounds.YMaxBound) return;
time = (0, _chartPrimitives.roundX)(time);
speed = (0, _chartPrimitives.roundY)(speed);
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const initialState = (0, _undoredo.getMarkerPairHistory)(markerPair);
const draft = (0, _immer.createDraft)(initialState);
draft.speedMap.push({
x: time,
y: speed
});
draft.speedMap.sort((0, _chartPrimitives.sortX));
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
this.renderSpeedAndCropUI(true);
}
};
const addCropPoint = function(time) {
// console.log(element, dataAtClick);
if (time) {
const scatterChartBounds = getScatterChartBounds(this);
if (time <= scatterChartBounds.XMinBound || time >= scatterChartBounds.XMaxBound) return;
time = (0, _chartPrimitives.roundX)(time);
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const initialState = (0, _undoredo.getMarkerPairHistory)(markerPair);
const draft = (0, _immer.createDraft)(initialState);
// Rapid keyframe workflow: if the user hits `Alt + A` mid-drag OR
// mid-resize of a crop point, snapshot the current (live-moved)
// crop value, revert the manipulated point back to where it
// started, and assign the live value to the new keyframe. The
// pointermove closure keeps writing to `currentCropPointIndex`
// (which moves to the new point below via setCurrentCropPoint), so
// the user can keep sweeping the cursor and dropping keyframes
// without releasing the mouse — the previously-held point stays
// anchored at its original crop and the new point becomes the
// live-edit target. For resize the per-keyframe variation only
// shows up in zoompan mode; pan-only mode keeps W/H equal across
// all points by mode invariant, so the revert is harmless but
// doesn't add per-keyframe size differences.
const isLiveCropManipulation = (0, _cropOverlay.isMouseManipulatingCrop) && (0, _cropOverlay.cropManipulationKind) != null && (0, _cropOverlay.cropDragStartCropString) != null;
const draggedPointRef = isLiveCropManipulation ? draft.cropMap[(0, _appState.appState).currentCropPointIndex] : null;
const liveCropAtAddTime = draggedPointRef ? draggedPointRef.crop : null;
if (draggedPointRef && (0, _cropOverlay.cropDragStartCropString) != null) draggedPointRef.crop = (0, _cropOverlay.cropDragStartCropString);
draft.cropMap.push({
x: time,
y: 0,
crop: liveCropAtAddTime ?? '0:0:iw:ih'
});
draft.cropMap.sort((0, _chartPrimitives.sortX));
const cropPointIndex = draft.cropMap.map((cropPoint)=>cropPoint.x).indexOf(time);
// Skip prev-inheritance when the new point already carries the
// live-manipulation crop — that value IS what the user visually
// placed here, and inheriting from prev would discard it.
if (cropPointIndex > 0 && !isLiveCropManipulation) {
const prevCropPointIndex = cropPointIndex - 1;
draft.cropMap[cropPointIndex].crop = draft.cropMap[prevCropPointIndex].crop;
}
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
this.renderSpeedAndCropUI(true);
// Auto-select the just-added point in Start mode so the user can
// immediately operate on it (e.g. set its crop via the input, or
// wheel forward to step the section through). Called after the
// render so `setCurrentCropPoint`'s bounds-clamp reads the chart's
// new data length, not the pre-insert length. For an insertion at
// the very end the helper auto-falls back to End mode (Start mode
// would place the section out of range).
(0, _cropChartSpec.setCurrentCropPoint)(this, cropPointIndex, (0, _cropChartSpec.cropChartMode).Start);
if (isLiveCropManipulation) {
// Refresh the in-progress drag's `initCropMap` snapshot so the
// next pointermove sees a snapshot that includes the new point —
// without this, `updateCropString` looks up the new point's
// index in the pre-insert snapshot and throws silently inside
// requestAnimationFrame, freezing the drag from that frame on.
(0, _cropOverlay.refreshCropDragInitState)?.();
// Re-baseline the "drag-start crop" to the value the new
// keyframe was just dropped at. Without this, every subsequent
// Alt + A would revert the just-added keyframe back to the
// ORIGINAL p0 crop — collapsing all intermediate keyframes to
// one value and leaving only the very last point usable.
if (liveCropAtAddTime != null) (0, _cropOverlay.setCropDragStartCropString)(liveCropAtAddTime);
// The Alt held to fire `Alt + A` would otherwise engage the
// pan handler's Y-axis lock on the next frame; tell the drag to
// ignore Alt until the user releases it.
(0, _cropOverlay.suppressNextAltLock)();
}
}
};
function scatterChartSpec(chartType, inputId) {
const updateInput = (0, _chartPrimitives.getInputUpdater)(inputId);
const onDragStart = function(e, chartInstance, _element, value) {
// console.log(arguments);
if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
chartInstance.options.plugins.zoom.pan.enabled = false;
e.target.style.cursor = 'grabbing';
if (chartType === 'crop') (0, _util.seekToSafe)((0, _appState.appState).video, (0, _util.timeRounder)(value.x));
chartInstance.update();
}
};
const onDrag = function(e, chartInstance, _datasetIndex, _index, fromValue, toValue) {
// console.log(datasetIndex, index, fromValue, toValue);
if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
const shouldDrag = {
dragX: true,
dragY: true,
chartType
};
const scatterChartBounds = getScatterChartBounds(chartInstance);
if (fromValue.x <= scatterChartBounds.XMinBound || fromValue.x >= scatterChartBounds.XMaxBound || toValue.x <= scatterChartBounds.XMinBound || toValue.x >= scatterChartBounds.XMaxBound) shouldDrag.dragX = false;
if (chartType === 'crop' || toValue.y < scatterChartBounds.YMinBound || toValue.y > scatterChartBounds.YMaxBound) shouldDrag.dragY = false;
if (chartType === 'crop' && shouldDrag.dragX && fromValue.x != toValue.x) (0, _util.seekToSafe)((0, _appState.appState).video, (0, _util.timeRounder)(toValue.x));
return shouldDrag;
} else return {
dragX: false,
dragY: false,
chartType
};
};
const onDragEnd = function(e, chartInstance, _datasetIndex, index, value) {
// console.log(datasetIndex, index, value);
if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const draft = (0, _immer.createDraft)((0, _undoredo.getMarkerPairHistory)(markerPair));
const draftMap = chartType === 'crop' ? draft.cropMap : draft.speedMap;
const currentCropPointXPreSort = chartType === 'crop' ? draftMap[(0, _appState.appState).currentCropPointIndex].x : null;
draftMap.sort((0, _chartPrimitives.sortX));
if (index === 0 && chartType === 'speed') draft.speed = value.y;
if (chartType === 'crop') {
const newCurrentCropPointIndex = draftMap.map((cropPoint)=>cropPoint.x).indexOf(currentCropPointXPreSort ?? -1);
(0, _cropChartSpec.setCurrentCropPoint)(chartInstance, newCurrentCropPointIndex);
}
chartInstance.options.plugins.zoom.pan.enabled = true;
e.target.style.cursor = 'default';
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
chartInstance.renderSpeedAndCropUI(true);
}
};
const onClick = function(event, dataAtClick) {
event.stopImmediatePropagation();
// add chart points on shift+left-click
if (event.button === 0 && !event.ctrlKey && !event.altKey && event.shiftKey && dataAtClick.length === 0) {
const time = this.scales['x-axis-1'].getValueForPixel(event.offsetX);
if (chartType === 'speed') {
const speed = this.scales['y-axis-1'].getValueForPixel(event.offsetY);
addSpeedPoint.call(this, time, speed);
} else if (chartType === 'crop') addCropPoint.call(this, time);
}
// delete chart points on alt+shift+left-click
if (event.button === 0 && !event.ctrlKey && event.altKey && event.shiftKey && dataAtClick.length === 1) {
const datum = dataAtClick[0];
if (datum) {
const index = datum._index;
const scatterChartMinBound = this.options.scales.xAxes[0].ticks.min;
const scatterChartMaxBound = this.options.scales.xAxes[0].ticks.max;
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const initialState = (0, _undoredo.getMarkerPairHistory)(markerPair);
const draft = (0, _immer.createDraft)(initialState);
let dataRef;
if (chartType === 'crop') dataRef = draft.cropMap;
else dataRef = draft.speedMap;
if (dataRef[index].x !== scatterChartMinBound && dataRef[index].x !== scatterChartMaxBound) {
dataRef.splice(index, 1);
if (chartType === 'crop') {
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
this.data.datasets[0].data = markerPair.cropMap;
if ((0, _appState.appState).currentCropPointIndex >= index) (0, _cropChartSpec.setCurrentCropPoint)(this, (0, _appState.appState).currentCropPointIndex - 1);
updateInput(markerPair.cropMap[(0, _appState.appState).currentCropPointIndex].crop);
} else {
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
this.data.datasets[0].data = markerPair.speedMap;
updateInput();
}
this.renderSpeedAndCropUI(true);
}
}
}
// change crop point ease in function
if (event.button === 0 && event.ctrlKey && !event.altKey && event.shiftKey && dataAtClick.length === 1) {
if (chartType === 'crop') {
const datum = dataAtClick[0];
if (datum) {
const index = datum._index;
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const initialState = (0, _undoredo.getMarkerPairHistory)(markerPair);
const draft = (0, _immer.createDraft)(initialState);
if (draft.cropMap[index].easeIn == null) draft.cropMap[index].easeIn = 'instant';
else delete draft.cropMap[index].easeIn;
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
this.renderSpeedAndCropUI(true);
}
}
}
if (event.ctrlKey && !event.altKey && !event.shiftKey) this.resetZoom();
};
function onHover(e, chartElements) {
e.target.style.cursor = chartElements[0] ? 'grab' : 'default';
if (chartType === 'crop' && !e.shiftKey && chartElements.length === 1) {
let mode;
if (e.ctrlKey && !e.altKey) mode = (0, _cropChartSpec.cropChartMode).Start;
else if (!e.ctrlKey && e.altKey) mode = (0, _cropChartSpec.cropChartMode).End;
else return;
const datum = chartElements[0];
if (datum) {
const index = datum._index;
(0, _cropChartSpec.setCurrentCropPoint)(this, index, mode);
(0, _charts.triggerCropChartUpdates)();
}
}
}
return {
type: 'scatter',
options: {
elements: {
line: {
fill: true,
backgroundColor: 'rgba(160,0, 255, 0.05)',
borderColor: (0, _chartPrimitives.lightgrey)(0.8),
borderWidth: 2,
borderDash: [
5,
2
]
}
},
legend: {
display: false
},
layout: {
padding: {
left: 0,
right: 0,
top: 15,
bottom: 0
}
},
tooltips: {
enabled: false
},
scales: {
xAxes: [
{
scaleLabel: {
display: true,
labelString: 'Time (s)',
fontSize: 12,
padding: -4
},
position: 'bottom',
gridLines: {
color: (0, _chartPrimitives.medgrey)(0.6),
lineWidth: 1
},
ticks: {
min: 0,
max: 10,
maxTicksLimit: 100,
autoSkip: false,
maxRotation: 60,
minRotation: 0,
major: {},
minor: {}
}
}
]
},
plugins: {
datalabels: {
clip: false,
clamp: true,
font: {
size: 14,
weight: 'bold'
},
textStrokeWidth: 2,
textStrokeColor: (0, _chartPrimitives.grey)(0.9),
textAlign: 'center',
display: displayDataLabel,
align: alignDataLabel,
color: getScatterPointColor
},
zoom: {
pan: {
enabled: true,
mode: 'x',
rangeMin: {
x: 0,
y: 0
},
rangeMax: {
x: 10,
y: 2
}
},
zoom: {
enabled: true,
mode: 'x',
drag: false,
speed: 0.1,
rangeMin: {
x: 0,
y: 0
},
rangeMax: {
x: 10,
y: 2
}
}
}
},
annotation: {
drawTime: 'afterDraw',
annotations: [
{
label: 'time',
type: 'line',
mode: 'vertical',
scaleID: 'x-axis-1',
value: -1,
borderColor: 'rgba(255, 0, 0, 0.9)',
borderWidth: 1
},
{
label: 'start',
type: 'line',
display: true,
mode: 'vertical',
scaleID: 'x-axis-1',
value: -1,
borderColor: 'rgba(0, 255, 0, 0.9)',
borderWidth: 1
},
{
label: 'end',
type: 'line',
display: true,
mode: 'vertical',
scaleID: 'x-axis-1',
value: -1,
borderColor: 'rgba(255, 215, 0, 0.9)',
borderWidth: 1
}
]
},
onHover: onHover,
dragData: true,
dragY: true,
dragX: true,
dragDataRound: 0.5,
dragDataRoundMultipleX: 0.01,
dragDataRoundPrecisionX: 2,
dragDataRoundMultipleY: 0.05,
dragDataRoundPrecisionY: 2,
dragDataSort: false,
dragDataSortFunction: (0, _chartPrimitives.sortX),
onDragStart: onDragStart,
onDrag: onDrag,
onDragEnd: onDragEnd,
onClick: onClick
}
};
}
},{"immer":"R6AMM","../../util/undoredo":"7UuTl","../../util/util":"99arg","../../charts":"hBxwj","../../appState":"g0AlP","./chartPrimitives":"hk4AN","./cropchart/cropChartSpec":"67uoo","../../crop-overlay":"6s727","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"6s727":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "addCropHoverListener", ()=>addCropHoverListener);
parcelHelpers.export(exports, "removeCropHoverListener", ()=>removeCropHoverListener);
parcelHelpers.export(exports, "cropHoverHandler", ()=>cropHoverHandler);
parcelHelpers.export(exports, "processCropHover", ()=>processCropHover);
parcelHelpers.export(exports, "updateCropHoverCursor", ()=>updateCropHoverCursor);
parcelHelpers.export(exports, "cropHoverRafId", ()=>cropHoverRafId);
parcelHelpers.export(exports, "pendingCropHoverEvent", ()=>pendingCropHoverEvent);
/** Returns true when the given client coords fall inside the visible crop
* rectangle. Used by the hints bar to scope crop-manipulation hints to
* "cursor over the crop" rather than "cursor anywhere on the video".
*
* In dynamic-crop mode the user is manipulating ONE specific crop point
* at a time — the chart mode (Start/End) tells us which one. The green
* rect previews the section's start point; the yellow rect previews the
* end point. Whichever matches the current chart mode is what the user's
* edits actually affect, so that's the rect we hit-test against. The
* time-based interpolated `cropRectBorder` (dimmed in this mode) would
* give "cursor over the crop" hints for a region the user isn't editing,
* which is misleading. */ parcelHelpers.export(exports, "isMouseInsideCrop", ()=>isMouseInsideCrop);
parcelHelpers.export(exports, "cropOverlayElements", ()=>cropOverlayElements);
parcelHelpers.export(exports, "createCropOverlay", ()=>createCropOverlay);
parcelHelpers.export(exports, "rerenderCropRafId", ()=>rerenderCropRafId);
parcelHelpers.export(exports, "resizeCropOverlay", ()=>resizeCropOverlay);
parcelHelpers.export(exports, "forceRerenderCrop", ()=>forceRerenderCrop);
parcelHelpers.export(exports, "centerVideo", ()=>centerVideo);
parcelHelpers.export(exports, "setCropOverlay", ()=>setCropOverlay);
parcelHelpers.export(exports, "setCropOverlayDimensions", ()=>setCropOverlayDimensions);
parcelHelpers.export(exports, "setCropCrossHair", ()=>setCropCrossHair);
parcelHelpers.export(exports, "cropDims", ()=>cropDims);
parcelHelpers.export(exports, "cropDimIndex", ()=>cropDimIndex);
parcelHelpers.export(exports, "cycleCropDimOpacity", ()=>cycleCropDimOpacity);
parcelHelpers.export(exports, "showCropOverlay", ()=>showCropOverlay);
parcelHelpers.export(exports, "hideCropOverlay", ()=>hideCropOverlay);
parcelHelpers.export(exports, "deleteCropOverlay", ()=>deleteCropOverlay);
parcelHelpers.export(exports, "isMouseManipulatingCrop", ()=>isMouseManipulatingCrop);
parcelHelpers.export(exports, "cropManipulationKind", ()=>cropManipulationKind);
parcelHelpers.export(exports, "cropDragStartCropString", ()=>cropDragStartCropString);
parcelHelpers.export(exports, "setCropDragStartCropString", ()=>setCropDragStartCropString);
parcelHelpers.export(exports, "suppressNextAltLock", ()=>suppressNextAltLock);
parcelHelpers.export(exports, "refreshCropDragInitState", ()=>refreshCropDragInitState);
parcelHelpers.export(exports, "endCropMouseManipulation", ()=>endCropMouseManipulation);
parcelHelpers.export(exports, "ctrlOrCommand", ()=>ctrlOrCommand);
parcelHelpers.export(exports, "addCropMouseManipulationListener", ()=>addCropMouseManipulationListener);
// mutates crop
parcelHelpers.export(exports, "resizeCrop", ()=>resizeCrop);
parcelHelpers.export(exports, "getClickPosScaled", ()=>getClickPosScaled);
parcelHelpers.export(exports, "getMouseCropHoverRegion", ()=>getMouseCropHoverRegion);
parcelHelpers.export(exports, "isDrawingCrop", ()=>isDrawingCrop);
parcelHelpers.export(exports, "prevNewMarkerCrop", ()=>prevNewMarkerCrop);
parcelHelpers.export(exports, "initDrawCropMap", ()=>initDrawCropMap);
parcelHelpers.export(exports, "beginDrawHandler", ()=>beginDrawHandler);
parcelHelpers.export(exports, "drawCrop", ()=>drawCrop);
parcelHelpers.export(exports, "drawCropHandler", ()=>drawCropHandler);
parcelHelpers.export(exports, "shouldFinishDrawMaintainAspectRatio", ()=>shouldFinishDrawMaintainAspectRatio);
parcelHelpers.export(exports, "beginDraw", ()=>beginDraw);
parcelHelpers.export(exports, "endDraw", ()=>endDraw);
parcelHelpers.export(exports, "finishDrawingCrop", ()=>finishDrawingCrop);
parcelHelpers.export(exports, "transformCropWithPushBack", ()=>transformCropWithPushBack);
parcelHelpers.export(exports, "cropCrossHairEnabled", ()=>cropCrossHairEnabled);
parcelHelpers.export(exports, "toggleCropCrossHair", ()=>toggleCropCrossHair);
parcelHelpers.export(exports, "renderStaticCropOverlay", ()=>renderStaticCropOverlay);
var _appState = require("./appState");
var _cropUtils = require("./crop-utils");
var _cropPreview = require("./crop/crop-preview");
var _litHtml = require("lit-html");
var _util = require("./util/util");
var _charts = require("./charts");
var _videoUtil = require("./util/videoUtil");
var _immer = require("immer");
var _crop = require("./crop/crop");
var _undoredo = require("./util/undoredo");
var _cropChartSpec = require("./ui/chart/cropchart/cropChartSpec");
var _dragRecovery = require("./util/drag-recovery");
function addCropHoverListener(e) {
const isCropBlockingChartVisible = (0, _appState.appState).isCurrentChartVisible && (0, _charts.chartState).currentChartInput && (0, _charts.chartState).currentChartInput.type !== 'crop';
if ((e.key === 'Control' || e.key === 'Meta') && (0, _appState.appState).isHotkeysEnabled && !e.repeat && (0, _appState.appState).isCropOverlayVisible && !isDrawingCrop && !isCropBlockingChartVisible) document.addEventListener('pointermove', cropHoverHandler, true);
}
function removeCropHoverListener(e) {
if (e.key === 'Control' || e.key === 'Meta') {
document.removeEventListener('pointermove', cropHoverHandler, true);
if (cropHoverRafId) {
cancelAnimationFrame(cropHoverRafId);
cropHoverRafId = 0;
pendingCropHoverEvent = null;
}
(0, _videoUtil.showPlayerControls)();
(0, _appState.appState).hooks.cropMouseManipulation.style.removeProperty('cursor');
// Reset anchor to default (top-left) for the next crop modification session
(0, _cropPreview.resetCropPreviewAnchor)();
}
}
function cropHoverHandler(e) {
if ((0, _appState.appState).isSettingsEditorOpen && (0, _appState.appState).isCropOverlayVisible && !isDrawingCrop) {
pendingCropHoverEvent = e;
if (!cropHoverRafId) cropHoverRafId = requestAnimationFrame(processCropHover);
}
}
function processCropHover() {
cropHoverRafId = 0;
const e = pendingCropHoverEvent;
pendingCropHoverEvent = null;
if (e && (0, _appState.appState).isSettingsEditorOpen && (0, _appState.appState).isCropOverlayVisible && !isDrawingCrop) updateCropHoverCursor(e);
}
function updateCropHoverCursor(e) {
const cursor = getMouseCropHoverRegion(e);
if (cursor) {
(0, _videoUtil.hidePlayerControls)();
(0, _appState.appState).hooks.cropMouseManipulation.style.cursor = cursor;
} else {
(0, _videoUtil.showPlayerControls)();
(0, _appState.appState).hooks.cropMouseManipulation.style.removeProperty('cursor');
}
}
let cropHoverRafId = 0;
let pendingCropHoverEvent = null;
let cropDiv;
let cropSvg;
let cropDim;
function isMouseInsideCrop(clientX, clientY) {
const border = getActiveCropHitRect();
if (!border) return false;
const bbox = border.getBoundingClientRect();
if (bbox.width === 0 || bbox.height === 0) return false;
return clientX >= bbox.left && clientX <= bbox.right && clientY >= bbox.top && clientY <= bbox.bottom;
}
/** Picks which crop rectangle currently represents the "active" crop for
* hit-testing — the time-interpolated one for static crops, or the
* selected point's preview (green/yellow) when dynamic-crop overlays are
* visible. Falls back to the time-based border if the chart-section
* rects haven't been laid out (e.g. before first render). */ function getActiveCropHitRect() {
const start = cropOverlayElements.cropChartSectionStartBorderGreen;
const end = cropOverlayElements.cropChartSectionEndBorderYellow;
const sectionGroup = cropOverlayElements.cropChartSectionStart;
const isDynamicOverlayVisible = sectionGroup?.style.display === 'block';
if (isDynamicOverlayVisible) {
const selected = (0, _cropChartSpec.currentCropChartMode) === (0, _cropChartSpec.cropChartMode).Start ? start : end;
if (selected) return selected;
}
return cropOverlayElements.cropRectBorder;
}
const cropOverlayElements = {
cropRect: null,
cropRectBorder: null,
cropRectBorderBlack: null,
cropRectBorderWhite: null,
cropCrossHair: null,
cropCrossHairXBlack: null,
cropCrossHairXWhite: null,
cropCrossHairYBlack: null,
cropCrossHairYWhite: null,
cropCrossHairs: [],
cropChartSectionStart: null,
cropChartSectionStartBorderGreen: null,
cropChartSectionStartBorderWhite: null,
cropChartSectionEnd: null,
cropChartSectionEndBorderYellow: null,
cropChartSectionEndBorderWhite: null
};
function CropOverlayTemplate(fillOpacity, crossHairDisplay) {
return (0, _litHtml.html)`
<svg id="crop-svg">
<defs>
<mask id="cropMask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<rect id="cropRect" x="0" y="0" width="100%" height="100%" fill="black" />
</mask>
</defs>
<rect
id="cropDim"
mask="url(#cropMask)"
x="0"
y="0"
width="100%"
height="100%"
fill="black"
fill-opacity=${fillOpacity}
/>
<g id="cropChartSectionStart" opacity="0.7" shape-rendering="geometricPrecision">
<rect
id="cropChartSectionStartBorderGreen"
x="0"
y="0"
width="0%"
height="0%"
fill="none"
stroke="lime"
stroke-width="1px"
/>
<rect
id="cropChartSectionStartBorderWhite"
x="0"
y="0"
width="0%"
height="0%"
fill="none"
stroke="black"
stroke-width="1px"
stroke-dasharray="5 10"
/>
</g>
<g id="cropChartSectionEnd" opacity="0.7" shape-rendering="geometricPrecision">
<rect
id="cropChartSectionEndBorderYellow"
x="0"
y="0"
width="0%"
height="0%"
fill="none"
stroke="yellow"
stroke-width="1px"
/>
<rect
id="cropChartSectionEndBorderWhite"
x="0"
y="0"
width="0%"
height="0%"
fill="none"
stroke="black"
stroke-width="1px"
stroke-dasharray="5 10"
/>
</g>
<g id="cropRectBorder" opacity="1" shape-rendering="geometricPrecision">
<rect
id="cropRectBorderBlack"
x="0"
y="0"
width="100%"
height="100%"
fill="none"
stroke="black"
stroke-width="1px"
stroke-opacity="0.8"
/>
<rect
id="cropRectBorderWhite"
x="0"
y="0"
width="100%"
height="100%"
fill="none"
stroke="white"
stroke-width="1px"
stroke-dasharray="5 5"
stroke-opacity="0.8"
></rect>
<g id="cropCrossHair" opacity="0.9" stroke="white" display=${crossHairDisplay}>
<line
id="cropCrossHairXBlack"
x1="0"
y1="50%"
x2="100%"
y2="50%"
stroke="black"
stroke-width="1px"
type="x"
/>
<line
id="cropCrossHairXWhite"
x1="0"
y1="50%"
x2="100%"
y2="50%"
stroke-width="1px"
stroke-dasharray="5 5"
type="x"
/>
<line
id="cropCrossHairYBlack"
x1="50%"
y1="0"
x2="50%"
y2="100%"
stroke="black"
stroke-width="1px"
type="y"
/>
<line
id="cropCrossHairYWhite"
x1="50%"
y1="0"
x2="50%"
y2="100%"
stroke-width="1px"
stroke-dasharray="5 5"
type="y"
/>
</g>
</g>
</svg>
`;
}
function createCropOverlay(cropString) {
deleteCropOverlay();
cropDiv = document.createElement('div');
cropDiv.setAttribute('id', 'crop-div');
(0, _litHtml.render)(CropOverlayTemplate(cropDims[cropDimIndex], cropCrossHairEnabled ? 'block' : 'none'), cropDiv);
resizeCropOverlay();
(0, _appState.appState).hooks.cropOverlay.insertAdjacentElement('afterend', cropDiv);
cropSvg = cropDiv.firstElementChild;
cropDim = document.getElementById('cropDim');
cropOverlayElements.cropRect = document.getElementById('cropRect');
cropOverlayElements.cropRectBorder = document.getElementById('cropRectBorder');
cropOverlayElements.cropRectBorderBlack = document.getElementById('cropRectBorderBlack');
cropOverlayElements.cropRectBorderWhite = document.getElementById('cropRectBorderWhite');
cropOverlayElements.cropChartSectionStart = document.getElementById('cropChartSectionStart');
cropOverlayElements.cropChartSectionStartBorderGreen = document.getElementById('cropChartSectionStartBorderGreen');
cropOverlayElements.cropChartSectionStartBorderWhite = document.getElementById('cropChartSectionStartBorderWhite');
cropOverlayElements.cropChartSectionEnd = document.getElementById('cropChartSectionEnd');
cropOverlayElements.cropChartSectionEndBorderYellow = document.getElementById('cropChartSectionEndBorderYellow');
cropOverlayElements.cropChartSectionEndBorderWhite = document.getElementById('cropChartSectionEndBorderWhite');
cropOverlayElements.cropCrossHair = document.getElementById('cropCrossHair');
cropOverlayElements.cropCrossHairXBlack = document.getElementById('cropCrossHairXBlack');
cropOverlayElements.cropCrossHairXWhite = document.getElementById('cropCrossHairXWhite');
cropOverlayElements.cropCrossHairYBlack = document.getElementById('cropCrossHairYBlack');
cropOverlayElements.cropCrossHairYWhite = document.getElementById('cropCrossHairYWhite');
(0, _util.assertDefined)(cropOverlayElements.cropCrossHairXBlack);
(0, _util.assertDefined)(cropOverlayElements.cropCrossHairXWhite);
(0, _util.assertDefined)(cropOverlayElements.cropCrossHairYBlack);
(0, _util.assertDefined)(cropOverlayElements.cropCrossHairYWhite);
cropOverlayElements.cropCrossHairs = [
cropOverlayElements.cropCrossHairXBlack,
cropOverlayElements.cropCrossHairXWhite,
cropOverlayElements.cropCrossHairYBlack,
cropOverlayElements.cropCrossHairYWhite
];
(0, _util.assertDefined)(cropOverlayElements.cropRect);
(0, _util.assertDefined)(cropOverlayElements.cropRectBorderBlack);
(0, _util.assertDefined)(cropOverlayElements.cropRectBorderWhite);
[
cropOverlayElements.cropRect,
cropOverlayElements.cropRectBorderBlack,
cropOverlayElements.cropRectBorderWhite
].map((cropRect)=>{
setCropOverlay(cropRect, cropString);
});
cropOverlayElements.cropCrossHairs.map((cropCrossHair)=>{
setCropCrossHair(cropCrossHair, cropString);
});
(0, _appState.appState).isCropOverlayVisible = true;
}
let rerenderCropRafId = 0;
function resizeCropOverlay() {
if (!rerenderCropRafId) rerenderCropRafId = requestAnimationFrame(forceRerenderCrop);
}
function forceRerenderCrop() {
rerenderCropRafId = 0;
centerVideo();
if (cropDiv) {
const videoRect = (0, _appState.appState).video.getBoundingClientRect();
const videoContainerRect = (0, _appState.appState).hooks.videoContainer.getBoundingClientRect();
const { width, height } = videoRect;
const top = videoRect.top - videoContainerRect.top;
const left = videoRect.left - videoContainerRect.left;
const styles = [
width,
height,
top,
left
].map((e)=>`${Math.floor(e)}px`);
Object.assign(cropDiv.style, {
width: styles[0],
height: styles[1],
top: styles[2],
left: styles[3],
position: 'absolute'
});
if (cropSvg) cropSvg.setAttribute('width', '0');
const cropString = (0, _cropUtils.getRelevantCropString)();
const [cx, cy, cw, ch] = (0, _cropUtils.getCropComponents)(cropString);
(0, _util.assertDefined)(cropOverlayElements.cropRect);
(0, _util.assertDefined)(cropOverlayElements.cropRectBorder);
(0, _util.assertDefined)(cropOverlayElements.cropRectBorderBlack);
(0, _util.assertDefined)(cropOverlayElements.cropRectBorderWhite);
setCropOverlayDimensions(cropOverlayElements.cropRect, cx, cy, cw, ch);
setCropOverlayDimensions(cropOverlayElements.cropRectBorder, cx, cy, cw, ch);
setCropOverlayDimensions(cropOverlayElements.cropRectBorderBlack, cx, cy, cw, ch);
setCropOverlayDimensions(cropOverlayElements.cropRectBorderWhite, cx, cy, cw, ch);
if (cropCrossHairEnabled && cropOverlayElements.cropCrossHair) cropOverlayElements.cropCrossHairs.map((crossHair)=>{
setCropCrossHair(crossHair, cropString);
});
}
}
function centerVideo() {
const videoContainerRect = (0, _appState.appState).hooks.videoContainer.getBoundingClientRect();
let width, height;
if ((0, _appState.appState).rotation === 0) {
height = videoContainerRect.height;
width = height * (0, _appState.appState).videoInfo.aspectRatio;
width = Math.floor(Math.min(width, videoContainerRect.width));
height = Math.floor(width / (0, _appState.appState).videoInfo.aspectRatio);
} else {
width = videoContainerRect.height;
height = width / (0, _appState.appState).videoInfo.aspectRatio;
height = Math.floor(Math.min(height, videoContainerRect.width));
width = Math.floor(height * (0, _appState.appState).videoInfo.aspectRatio);
}
const left = videoContainerRect.width / 2 - width / 2;
const top = videoContainerRect.height / 2 - height / 2;
const videoStyles = [
width,
height,
top,
left
].map((e)=>`${Math.round(e)}px`);
Object.assign((0, _appState.appState).video.style, {
width: videoStyles[0],
height: videoStyles[1],
top: videoStyles[2],
left: videoStyles[3],
position: 'absolute'
});
}
function setCropOverlay(cropRect, cropString) {
const [x, y, w, h] = (0, _cropUtils.getCropComponents)(cropString);
setCropOverlayDimensions(cropRect, x, y, w, h);
}
function setCropOverlayDimensions(cropRect, inX, inY, inW, inH) {
if (cropRect) {
let x = inX / (0, _appState.appState).settings.cropResWidth * 100;
let y = inY / (0, _appState.appState).settings.cropResHeight * 100;
let w = inW / (0, _appState.appState).settings.cropResWidth * 100;
let h = inH / (0, _appState.appState).settings.cropResHeight * 100;
[x, y, w, h] = (0, _cropUtils.getRotatedCropComponents)([
x,
y,
w,
h
], 100, 100);
const cropRectAttrs = {
x: `${x}%`,
y: `${y}%`,
width: `${w}%`,
height: `${h}%`
};
(0, _util.setAttributes)(cropRect, cropRectAttrs);
}
}
function setCropCrossHair(cropCrossHair, cropString) {
const [x, y, w, h] = (0, _cropUtils.getRotatedCropComponents)((0, _cropUtils.getCropComponents)(cropString));
if (cropCrossHair) {
const [x1M, x2M, y1M, y2M] = cropCrossHair.getAttribute('type') === 'x' ? [
0,
1,
0.5,
0.5
] : [
0.5,
0.5,
0,
1
];
let cropCrossHairAttrs = {
x1: `${(x + x1M * w) / (0, _appState.appState).settings.cropResWidth * 100}%`,
x2: `${(x + x2M * w) / (0, _appState.appState).settings.cropResWidth * 100}%`,
y1: `${(y + y1M * h) / (0, _appState.appState).settings.cropResHeight * 100}%`,
y2: `${(y + y2M * h) / (0, _appState.appState).settings.cropResHeight * 100}%`
};
if ((0, _appState.appState).rotation === 90 || (0, _appState.appState).rotation === -90) cropCrossHairAttrs = {
x1: `${(x + x1M * w) / (0, _appState.appState).settings.cropResHeight * 100}%`,
x2: `${(x + x2M * w) / (0, _appState.appState).settings.cropResHeight * 100}%`,
y1: `${(y + y1M * h) / (0, _appState.appState).settings.cropResWidth * 100}%`,
y2: `${(y + y2M * h) / (0, _appState.appState).settings.cropResWidth * 100}%`
};
(0, _util.setAttributes)(cropCrossHair, cropCrossHairAttrs);
}
}
const cropDims = [
0,
0.25,
0.5,
0.75,
0.9,
1
];
let cropDimIndex = 2;
function cycleCropDimOpacity() {
cropDimIndex = (cropDimIndex + 1) % cropDims.length;
cropDim.setAttribute('fill-opacity', cropDims[cropDimIndex].toString());
}
function showCropOverlay() {
if (cropSvg) {
cropSvg.style.display = 'block';
(0, _appState.appState).isCropOverlayVisible = true;
}
}
function hideCropOverlay() {
if (isDrawingCrop) finishDrawingCrop(true);
if (isMouseManipulatingCrop) endCropMouseManipulation(null, true);
if (cropSvg) {
cropSvg.style.display = 'none';
(0, _appState.appState).isCropOverlayVisible = false;
}
}
function deleteCropOverlay() {
const cropDiv = document.getElementById('crop-div');
if (cropDiv) (0, _util.deleteElement)(cropDiv);
(0, _appState.appState).isCropOverlayVisible = false;
}
let isMouseManipulatingCrop = false;
let cropManipulationKind = null;
let cropDragStartCropString = null;
function setCropDragStartCropString(value) {
cropDragStartCropString = value;
}
/** True for the brief window between a mid-drag `Alt + A` keypress and
* the user actually releasing Alt. While set, `processDragCrop` ignores
* `e.altKey` for the Y-axis lock — without this, the Alt held to fire
* the Alt + A hotkey accidentally engages "horizontal-only panning"
* for a frame or two and the drag visibly stutters. Auto-clears on the
* first pointermove that arrives without Alt held, so an intentional
* Alt re-press after the hotkey window still engages the lock. */ let suppressAltLockUntilRelease = false;
function suppressNextAltLock() {
suppressAltLockUntilRelease = true;
}
let refreshCropDragInitState = null;
let endCropMouseManipulation;
function ctrlOrCommand(e) {
return e.ctrlKey || e.metaKey;
}
function addCropMouseManipulationListener() {
(0, _appState.appState).hooks.cropMouseManipulation.addEventListener('pointerdown', cropMouseManipulationHandler, {
capture: true
});
function cropMouseManipulationHandler(e) {
const isCropBlockingChartVisible = (0, _appState.appState).isCurrentChartVisible && (0, _charts.chartState).currentChartInput && (0, _charts.chartState).currentChartInput.type !== 'crop';
if (ctrlOrCommand(e) && (0, _appState.appState).isSettingsEditorOpen && (0, _appState.appState).isCropOverlayVisible && !isDrawingCrop && !isCropBlockingChartVisible) {
const cropString = (0, _cropUtils.getRelevantCropString)();
// Mutable so the wheel-zoom-during-pan handler can re-baseline
// them on each tick — the pan formula computes
// `crop = Crop(ix, iy, iw, ih) + cursor_delta_from_clickPos`, so
// after a zoom updates the crop's origin/size the pan must
// continue from the new state instead of jumping back.
let [ix, iy, iw, ih] = (0, _cropUtils.getCropComponents)(cropString);
const cropResWidth = (0, _appState.appState).settings.cropResWidth;
const cropResHeight = (0, _appState.appState).settings.cropResHeight;
const videoRect = (0, _appState.appState).video.getBoundingClientRect();
let clickPosX = e.clientX - videoRect.left;
let clickPosY = e.clientY - videoRect.top;
const cursor = getMouseCropHoverRegion(e, cropString);
const pointerId = e.pointerId;
const { isDynamicCrop, enableZoomPan } = (0, _charts.getCropMapProperties)();
// Mutable so a mid-drag `Alt + A` (rapid-keyframe workflow in
// `addCropPoint`) can refresh it to the post-insert snapshot via
// `refreshCropDragInitState` below — otherwise `updateCropString`
// looks up the new point's index in a stale snapshot and throws,
// which silently breaks the drag from that frame on.
let initCropMap = (0, _charts.getCropMapProperties)().initCropMap;
let pendingCropDragEvent = null;
let cropDragRafId = 0;
let pendingCropResizeEvent = null;
let cropResizeRafId = 0;
// The captured-target element receives every pointer event for this
// drag while capture is held — including the spec-mandated
// `pointercancel` if the browser implicitly releases capture
// (devtools, alt-tab, OS pointer reassignment, etc.). Stash it as a
// local const so `endCropMouseManipulation` removes listeners from the
// exact element they were attached to even if the appState hook were
// swapped out mid-drag (defensive — shouldn't happen, but cheap).
const captureTarget = (0, _appState.appState).hooks.cropMouseManipulation;
let unregisterDragRecovery = ()=>{};
endCropMouseManipulation = (e, forceEnd = false)=>{
if (forceEnd) {
captureTarget.removeEventListener('pointerup', endCropMouseManipulation, {
capture: true
});
captureTarget.removeEventListener('pointercancel', endCropMouseManipulation, {
capture: true
});
}
unregisterDragRecovery();
isMouseManipulatingCrop = false;
cropManipulationKind = null;
cropDragStartCropString = null;
refreshCropDragInitState = null;
if (cropDragRafId) {
cancelAnimationFrame(cropDragRafId);
cropDragRafId = 0;
processDragCrop();
}
if (cropResizeRafId) {
cancelAnimationFrame(cropResizeRafId);
cropResizeRafId = 0;
processResizeCrop();
}
if (captureTarget.hasPointerCapture(pointerId)) captureTarget.releasePointerCapture(pointerId);
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const draft = (0, _immer.createDraft)((0, _undoredo.getMarkerPairHistory)(markerPair));
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
}
(0, _charts.renderSpeedAndCropUI)();
captureTarget.removeEventListener('pointermove', dragCropHandler);
captureTarget.removeEventListener('pointermove', cropResizeHandler);
captureTarget.removeEventListener('wheel', cropPanZoomHandler);
(0, _videoUtil.showPlayerControls)();
if (!forceEnd && e && ctrlOrCommand(e)) {
if (cursor) captureTarget.style.cursor = cursor;
updateCropHoverCursor(e);
document.addEventListener('pointermove', cropHoverHandler, true);
} else captureTarget.style.removeProperty('cursor');
document.addEventListener('keyup', removeCropHoverListener, true);
document.addEventListener('keydown', addCropHoverListener, true);
};
if (!cursor) return;
let cropResizeHandler;
document.addEventListener('click', (0, _videoUtil.blockVideoPause), {
once: true,
capture: true
});
document.removeEventListener('pointermove', cropHoverHandler, true);
document.removeEventListener('keydown', addCropHoverListener, true);
document.removeEventListener('keyup', removeCropHoverListener, true);
e.preventDefault();
(0, _appState.appState).hooks.cropMouseManipulation.setPointerCapture(pointerId);
// Rapid-keyframe workflow setup, shared by both pan (drag) and
// resize. Snapshot the manipulated point's crop at the start so a
// mid-manipulation `Alt + A` can revert it while assigning the
// live-moved value to the new keyframe. Install the init-map
// refresh callback so the next pointermove after an Alt + A sees
// a snapshot that includes the new point. Both are cleared in
// endCropMouseManipulation. For resize the resulting per-keyframe
// variation only shows up in zoompan mode (pan-only mode keeps
// W/H equal across all points by mode invariant, so the revert is
// harmless but doesn't add per-keyframe variation).
cropDragStartCropString = cropString;
refreshCropDragInitState = ()=>{
initCropMap = (0, _charts.getCropMapProperties)().initCropMap;
};
if (cursor === 'grab') {
cropManipulationKind = 'drag';
captureTarget.style.cursor = 'grabbing';
captureTarget.addEventListener('pointermove', dragCropHandler);
// Wheel zoom during pan-drag: scale the crop around the cursor.
// `passive: false` lets us preventDefault to suppress page
// scroll while the user is mid-gesture. Rotation isn't handled
// (cursor coords would need rotation-mapping); the gesture is a
// no-op in that mode so it can't produce a wrong crop.
captureTarget.addEventListener('wheel', cropPanZoomHandler, {
passive: false
});
} else {
cropManipulationKind = 'resize';
cropResizeHandler = (e)=>{
pendingCropResizeEvent = e;
if (!cropResizeRafId) cropResizeRafId = requestAnimationFrame(processResizeCrop);
};
captureTarget.addEventListener('pointermove', cropResizeHandler);
}
captureTarget.addEventListener('pointerup', endCropMouseManipulation, {
once: true,
capture: true
});
// Mirror pointerup with pointercancel: when the browser implicitly
// releases pointer capture (devtools panel grabbing focus, OS
// reassigning pointer ownership), pointerup will not fire on the
// captured target but pointercancel will. Both must run the same
// teardown so the user never gets stuck in a manipulating state.
captureTarget.addEventListener('pointercancel', (ev)=>endCropMouseManipulation(ev, true), {
once: true,
capture: true
});
// Belt-and-suspenders: window.blur / tab-hidden recovery. Some
// sequences (rapid alt-tab on Windows, certain devtools docking
// transitions) drop both pointerup and pointercancel for the
// captured target — this is the last-resort cleanup so the state
// can never permanently stick.
unregisterDragRecovery = (0, _dragRecovery.registerActiveDragCleanup)(()=>{
endCropMouseManipulation(null, true);
});
(0, _videoUtil.hidePlayerControls)();
isMouseManipulatingCrop = true;
function dragCropHandler(e) {
pendingCropDragEvent = e;
if (!cropDragRafId) cropDragRafId = requestAnimationFrame(processDragCrop);
}
/** Cursor-anchored wheel-zoom during a pan-drag. Scales the crop
* so the pixel under the cursor keeps the same relative position
* within the box — mirroring how image viewers, Figma, and maps
* handle wheel-zoom, and preserving the "grip" the user
* established when they started the pan-drag. Each tick rewrites
* `ix/iy/iw/ih` + `clickPosX/Y` so subsequent pointermoves
* continue panning from the new state instead of jumping back to
* the original size. In pan-only mode the new W/H propagates to
* every other point via `setCropComponentForAllPoints`
* (mode invariant); in zoompan mode only the current point's
* dimensions change. */ function cropPanZoomHandler(e) {
if (e.deltaY === 0) return;
e.preventDefault();
// Rotation handling would require mapping cursor coords
// through the rotation matrix on every tick; skip to avoid
// delivering a subtly wrong crop in those modes.
if ((0, _appState.appState).rotation !== 0) return;
const ZOOM_STEP = 1.05;
const factor = e.deltaY < 0 ? 1 / ZOOM_STEP : ZOOM_STEP;
const cursorX = e.clientX - videoRect.left;
const cursorY = e.clientY - videoRect.top;
// Read the CURRENT crop (mid-pan) not the drag-start values.
// Each pan tick writes through `updateCropString` which mutates
// `markerPair.cropMap` in place, so `getRelevantCropString`
// returns the latest on-screen state. Using the closure
// `ix/iy/iw/ih` here would anchor the zoom to the crop's
// position at drag-start, which makes the zoom visibly snap
// back to the original position on the first wheel tick.
const [curX, curY, curW, curH] = (0, _cropUtils.getCropComponents)((0, _cropUtils.getRelevantCropString)());
const MIN_DIM = 16;
const aspectRatio = curW / curH;
let newW = Math.round(curW * factor);
newW = (0, _util.clampNumber)(newW, MIN_DIM, cropResWidth);
let newH = Math.round(newW / aspectRatio);
newH = (0, _util.clampNumber)(newH, MIN_DIM, cropResHeight);
// If H clamp forced an AR drift, pull W back so AR is preserved.
if (Math.abs(newW / newH - aspectRatio) > 1e-3) {
newW = Math.round(newH * aspectRatio);
newW = (0, _util.clampNumber)(newW, MIN_DIM, cropResWidth);
}
// Center-out: keep the crop's geometric center fixed; edges
// expand/contract uniformly. Predictable since the anchor
// doesn't depend on where the cursor is — the user always knows
// what the zoom will do.
const centerX = curX + curW / 2;
const centerY = curY + curH / 2;
let newX = Math.round(centerX - newW / 2);
let newY = Math.round(centerY - newH / 2);
newX = (0, _util.clampNumber)(newX, 0, Math.max(0, cropResWidth - newW));
newY = (0, _util.clampNumber)(newY, 0, Math.max(0, cropResHeight - newH));
const crop = new (0, _crop.Crop)(newX, newY, newW, newH, cropResWidth, cropResHeight);
(0, _cropUtils.updateCropStringWithCrop)(crop, false, false, initCropMap ?? undefined);
// Re-baseline the pan formula so subsequent pointermoves
// continue from the just-zoomed crop, and snap the click anchor
// to the current cursor so accumulated pan delta is zero at
// this instant. (`ix/iy/iw/ih` are the pan formula's origin —
// pan computes `crop = Crop(ix..ih) + (cursor - clickPos)`.)
ix = newX;
iy = newY;
iw = newW;
ih = newH;
clickPosX = cursorX;
clickPosY = cursorY;
}
function processDragCrop() {
cropDragRafId = 0;
const e = pendingCropDragEvent;
pendingCropDragEvent = null;
if (!e) return;
// The Alt key briefly stays held while the user finishes the
// Alt + A hotkey, so honour `suppressAltLockUntilRelease`: skip
// the Y-axis lock until the user releases Alt at least once,
// then resume normal modifier semantics for any later re-press.
if (!e.altKey) suppressAltLockUntilRelease = false;
const shouldMaintainCropX = e.shiftKey;
const shouldMaintainCropY = e.altKey && !suppressAltLockUntilRelease;
const dragPosX = e.clientX - videoRect.left;
const dragPosY = e.clientY - videoRect.top;
const changeX = dragPosX - clickPosX;
const changeY = dragPosY - clickPosY;
let changeXScaled = Math.round(changeX / videoRect.width * cropResWidth);
let changeYScaled = Math.round(changeY / videoRect.height * cropResHeight);
let crop = new (0, _crop.Crop)(ix, iy, iw, ih, cropResWidth, cropResHeight);
if ((0, _appState.appState).rotation === 90) {
changeXScaled = Math.round(changeX / videoRect.width * cropResHeight);
changeYScaled = Math.round(changeY / videoRect.height * cropResWidth);
crop = new (0, _crop.Crop)(cropResHeight - iy - ih, ix, ih, iw, cropResHeight, cropResWidth);
} else if ((0, _appState.appState).rotation === -90) {
changeXScaled = Math.round(changeX / videoRect.width * cropResHeight);
changeYScaled = Math.round(changeY / videoRect.height * cropResWidth);
crop = new (0, _crop.Crop)(iy, cropResWidth - ix - iw, ih, iw, cropResHeight, cropResWidth);
}
if (shouldMaintainCropX) changeXScaled = 0;
if (shouldMaintainCropY) changeYScaled = 0;
crop.panX(changeXScaled);
crop.panY(changeYScaled);
(0, _cropUtils.updateCropStringWithCrop)(crop, false, false, initCropMap ?? undefined);
}
function getCropResizeHandler(e, cursor) {
const shouldMaintainCropAspectRatio = (!enableZoomPan || !isDynamicCrop) && e.altKey || enableZoomPan && isDynamicCrop && !e.altKey;
const dragPosX = e.clientX - videoRect.left;
const changeX = dragPosX - clickPosX;
const dragPosY = e.clientY - videoRect.top;
const changeY = dragPosY - clickPosY;
let changeXScaled = changeX / videoRect.width * (0, _appState.appState).settings.cropResWidth;
let changeYScaled = changeY / videoRect.height * (0, _appState.appState).settings.cropResHeight;
const shouldResizeCenterOut = e.shiftKey;
let crop = new (0, _crop.Crop)(ix, iy, iw, ih, cropResWidth, cropResHeight);
if ((0, _appState.appState).rotation === 90) {
changeXScaled = changeX / videoRect.width * cropResHeight;
changeYScaled = changeY / videoRect.height * cropResWidth;
crop = new (0, _crop.Crop)(cropResHeight - iy - ih, ix, ih, iw, cropResHeight, cropResWidth);
} else if ((0, _appState.appState).rotation === -90) {
changeXScaled = Math.round(changeX / videoRect.width * cropResHeight);
changeYScaled = Math.round(changeY / videoRect.height * cropResWidth);
crop = new (0, _crop.Crop)(iy, cropResWidth - ix - iw, ih, iw, cropResHeight, cropResWidth);
}
resizeCrop(crop, cursor, changeXScaled, changeYScaled, shouldMaintainCropAspectRatio, shouldResizeCenterOut);
(0, _cropUtils.updateCropStringWithCrop)(crop, false, false, initCropMap ?? undefined);
}
function processResizeCrop() {
cropResizeRafId = 0;
const e = pendingCropResizeEvent;
pendingCropResizeEvent = null;
if (e) getCropResizeHandler(e, cursor);
}
}
}
}
function resizeCrop(crop, cursor, deltaX, deltaY, shouldMaintainCropAspectRatio = false, shouldResizeCenterOut = false) {
const isWResize = [
'w-resize',
'nw-resize',
'sw-resize'
].includes(cursor);
const isNResize = [
'n-resize',
'nw-resize',
'ne-resize'
].includes(cursor);
if (isWResize) deltaX = -deltaX;
if (isNResize) deltaY = -deltaY;
const isDiagonalResize = [
'ne-resize',
'se-resize',
'sw-resize',
'nw-resize'
].includes(cursor);
if (shouldMaintainCropAspectRatio && shouldResizeCenterOut) crop.resizeNESWAspectRatioLocked(deltaY, deltaX);
else if (shouldResizeCenterOut && isDiagonalResize) crop.resizeNESW(deltaY, deltaX);
else switch(cursor){
case 'n-resize':
shouldMaintainCropAspectRatio ? crop.resizeNAspectRatioLocked(deltaY) : shouldResizeCenterOut ? crop.resizeNS(deltaY) : crop.resizeN(deltaY);
break;
case 'ne-resize':
shouldMaintainCropAspectRatio ? crop.resizeNEAspectRatioLocked(deltaY, deltaX) : crop.resizeNE(deltaY, deltaX);
break;
case 'e-resize':
shouldMaintainCropAspectRatio ? crop.resizeEAspectRatioLocked(deltaX) : shouldResizeCenterOut ? crop.resizeEW(deltaX) : crop.resizeE(deltaX);
break;
case 'se-resize':
shouldMaintainCropAspectRatio ? crop.resizeSEAspectRatioLocked(deltaY, deltaX) : crop.resizeSE(deltaY, deltaX);
break;
case 's-resize':
shouldMaintainCropAspectRatio ? crop.resizeSAspectRatioLocked(deltaY) : shouldResizeCenterOut ? crop.resizeNS(deltaY) : crop.resizeS(deltaY);
break;
case 'sw-resize':
shouldMaintainCropAspectRatio ? crop.resizeSWAspectRatioLocked(deltaY, deltaX) : crop.resizeSW(deltaY, deltaX);
break;
case 'w-resize':
shouldMaintainCropAspectRatio ? crop.resizeWAspectRatioLocked(deltaX) : shouldResizeCenterOut ? crop.resizeEW(deltaX) : crop.resizeW(deltaX);
break;
case 'nw-resize':
shouldMaintainCropAspectRatio ? crop.resizeNWAspectRatioLocked(deltaY, deltaX) : crop.resizeNW(deltaY, deltaX);
break;
}
}
function getClickPosScaled(e) {
const videoRect = (0, _appState.appState).video.getBoundingClientRect();
const { width, height, top, left } = videoRect;
const clickPosX = e.clientX - left;
const clickPosY = e.clientY - top;
let clickPosXScaled = clickPosX / width * (0, _appState.appState).settings.cropResWidth;
let clickPosYScaled = clickPosY / height * (0, _appState.appState).settings.cropResHeight;
if ((0, _appState.appState).rotation === 90 || (0, _appState.appState).rotation === -90) {
clickPosXScaled = clickPosX / width * (0, _appState.appState).settings.cropResHeight;
clickPosYScaled = clickPosY / height * (0, _appState.appState).settings.cropResWidth;
}
return [
clickPosXScaled,
clickPosYScaled
];
}
function getMouseCropHoverRegion(e, cropString) {
cropString = cropString ?? (0, _cropUtils.getRelevantCropString)();
let [x, y, w, h] = (0, _cropUtils.getCropComponents)(cropString);
const [clickPosXScaled, clickPosYScaled] = getClickPosScaled(e);
if ((0, _appState.appState).rotation === 90) [x, y, w, h] = (0, _cropUtils.rotateCropComponentsClockWise)([
x,
y,
w,
h
]);
else if ((0, _appState.appState).rotation === -90) [x, y, w, h] = (0, _cropUtils.rotateCropComponentsCounterClockWise)([
x,
y,
w,
h
]);
const slMultiplier = Math.min((0, _appState.appState).settings.cropResWidth, (0, _appState.appState).settings.cropResHeight) / 1080;
const sl = Math.ceil(Math.min(w, h) * slMultiplier * 0.1);
const edgeOffset = 30 * slMultiplier;
let cursor = '';
let mouseCropColumn = 0;
if (x - edgeOffset < clickPosXScaled && clickPosXScaled < x + sl) mouseCropColumn = 1;
else if (x + sl < clickPosXScaled && clickPosXScaled < x + w - sl) mouseCropColumn = 2;
else if (x + w - sl < clickPosXScaled && clickPosXScaled < x + w + edgeOffset) mouseCropColumn = 3;
let mouseCropRow = 0;
if (y - edgeOffset < clickPosYScaled && clickPosYScaled < y + sl) mouseCropRow = 1;
else if (y + sl < clickPosYScaled && clickPosYScaled < y + h - sl) mouseCropRow = 2;
else if (y + h - sl < clickPosYScaled && clickPosYScaled < y + h + edgeOffset) mouseCropRow = 3;
const isMouseInCropCenter = mouseCropColumn === 2 && mouseCropRow === 2;
const isMouseInCropN = mouseCropColumn === 2 && mouseCropRow === 1;
const isMouseInCropNE = mouseCropColumn === 3 && mouseCropRow === 1;
const isMouseInCropE = mouseCropColumn === 3 && mouseCropRow === 2;
const isMouseInCropSE = mouseCropColumn === 3 && mouseCropRow === 3;
const isMouseInCropS = mouseCropColumn === 2 && mouseCropRow === 3;
const isMouseInCropSW = mouseCropColumn === 1 && mouseCropRow === 3;
const isMouseInCropW = mouseCropColumn === 1 && mouseCropRow === 2;
const isMouseInCropNW = mouseCropColumn === 1 && mouseCropRow === 1;
if (isMouseInCropCenter) cursor = 'grab';
if (isMouseInCropN) cursor = 'n-resize';
if (isMouseInCropNE) cursor = 'ne-resize';
if (isMouseInCropE) cursor = 'e-resize';
if (isMouseInCropSE) cursor = 'se-resize';
if (isMouseInCropS) cursor = 's-resize';
if (isMouseInCropSW) cursor = 'sw-resize';
if (isMouseInCropW) cursor = 'w-resize';
if (isMouseInCropNW) cursor = 'nw-resize';
return cursor;
}
let isDrawingCrop = false;
let prevNewMarkerCrop = '0:0:iw:ih';
let initDrawCropMap;
let beginDrawHandler;
/** Active drag-recovery unregister for the in-flight draw, if any. Reset
* to a noop after `finishDrawingCrop` runs so multiple finish-calls are
* idempotent. */ let unregisterDrawRecovery = ()=>{};
function drawCrop() {
if (isDrawingCrop) finishDrawingCrop(true);
else if ((0, _appState.appState).isCurrentChartVisible && (0, _charts.chartState).currentChartInput && (0, _charts.chartState).currentChartInput.type !== 'crop') (0, _util.flashMessage)('Please toggle off the speed chart before drawing crop', 'olive');
else if (isMouseManipulatingCrop) (0, _util.flashMessage)('Please finish dragging or resizing before drawing crop', 'olive');
else if ((0, _appState.appState).isSettingsEditorOpen && (0, _appState.appState).isCropOverlayVisible) {
isDrawingCrop = true;
({ initCropMap: initDrawCropMap } = (0, _charts.getCropMapProperties)());
prevNewMarkerCrop = (0, _appState.appState).settings.newMarkerCrop;
(0, _crop.Crop).shouldConstrainMinDimensions = false;
document.removeEventListener('keydown', addCropHoverListener, true);
document.removeEventListener('pointermove', cropHoverHandler, true);
(0, _videoUtil.hidePlayerControls)();
(0, _appState.appState).hooks.cropMouseManipulation.style.removeProperty('cursor');
(0, _appState.appState).hooks.cropMouseManipulation.style.cursor = 'crosshair';
beginDrawHandler = (e)=>{
beginDraw(e);
};
(0, _appState.appState).hooks.cropMouseManipulation.addEventListener('pointerdown', beginDrawHandler, {
once: true,
capture: true
});
// Cover both the "armed but pre-click" window and the active-drag
// window with the same recovery cleanup. If the user alt-tabs while
// the cursor is the crosshair but no draw has started, or alt-tabs
// mid-drag, `finishDrawingCrop(true)` reverts to the previous crop
// and detaches every listener this code path attached.
unregisterDrawRecovery = (0, _dragRecovery.registerActiveDragCleanup)(()=>{
finishDrawingCrop(true);
});
(0, _util.flashMessage)('Begin drawing crop', 'green');
} else (0, _util.flashMessage)('Please open the global settings or a marker pair editor before drawing crop', 'olive');
}
let drawCropHandler;
let shouldFinishDrawMaintainAspectRatio = false;
function beginDraw(e) {
if (e.button === 0 && !drawCropHandler) {
e.preventDefault();
(0, _appState.appState).hooks.cropMouseManipulation.setPointerCapture(e.pointerId);
const cropResWidth = (0, _appState.appState).settings.cropResWidth;
const cropResHeight = (0, _appState.appState).settings.cropResHeight;
const videoRect = (0, _appState.appState).video.getBoundingClientRect();
const clickPosX = e.clientX - videoRect.left;
const clickPosY = e.clientY - videoRect.top;
const [clickPosXScaled, clickPosYScaled] = getClickPosScaled(e);
const { isDynamicCrop, enableZoomPan } = (0, _charts.getCropMapProperties)();
let prevCrop;
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen) {
(0, _util.assertDefined)(initDrawCropMap);
prevCrop = initDrawCropMap[(0, _appState.appState).currentCropPointIndex].crop;
} else prevCrop = prevNewMarkerCrop;
const shouldMaintainCropAspectRatio = (!enableZoomPan || !isDynamicCrop) && e.altKey || enableZoomPan && isDynamicCrop && !e.altKey;
shouldFinishDrawMaintainAspectRatio = shouldMaintainCropAspectRatio;
// rotate aspect ratio in rotated mode?
let [, , prevCropW, prevCropH] = (0, _cropUtils.getCropComponents)(prevCrop);
if ((0, _appState.appState).rotation === 90 || (0, _appState.appState).rotation === -90) [prevCropW, prevCropH] = [
prevCropH,
prevCropW
];
const prevCropAspectRatio = prevCropW <= 0 || prevCropH <= 0 ? 1 : prevCropW / prevCropH;
let crop = new (0, _crop.Crop)(clickPosXScaled, clickPosYScaled, (0, _crop.Crop).minW, (0, _crop.Crop).minH, cropResWidth, cropResHeight);
if ((0, _appState.appState).rotation === 90 || (0, _appState.appState).rotation === -90) // We already rotated clickPosXScaled and clickPosYScaled, so we don't need to do it again here
crop = new (0, _crop.Crop)(clickPosXScaled, clickPosYScaled, (0, _crop.Crop).minH, (0, _crop.Crop).minW, cropResHeight, cropResWidth);
(0, _cropUtils.updateCropStringWithCrop)(crop, false, false, initDrawCropMap ?? undefined);
const { initCropMap: zeroCropMap } = (0, _charts.getCropMapProperties)();
drawCropHandler = (e)=>{
const shouldMaintainCropAspectRatio = (!enableZoomPan || !isDynamicCrop) && e.altKey || enableZoomPan && isDynamicCrop && !e.altKey;
shouldFinishDrawMaintainAspectRatio = shouldMaintainCropAspectRatio;
const shouldResizeCenterOut = e.shiftKey;
const dragPosX = e.clientX - videoRect.left;
const changeX = dragPosX - clickPosX;
const dragPosY = e.clientY - videoRect.top;
const changeY = dragPosY - clickPosY;
let changeXScaled = changeX / videoRect.width * cropResWidth;
let changeYScaled = changeY / videoRect.height * cropResHeight;
let crop = new (0, _crop.Crop)(clickPosXScaled, clickPosYScaled, (0, _crop.Crop).minW, (0, _crop.Crop).minH, cropResWidth, cropResHeight);
if ((0, _appState.appState).rotation === 90 || (0, _appState.appState).rotation === -90) {
changeXScaled = changeX / videoRect.width * cropResHeight;
changeYScaled = changeY / videoRect.height * cropResWidth;
crop = new (0, _crop.Crop)(clickPosXScaled, clickPosYScaled, (0, _crop.Crop).minH, (0, _crop.Crop).minW, cropResHeight, cropResWidth);
}
crop.defaultAspectRatio = prevCropAspectRatio;
let cursor = 'default';
if (changeXScaled >= 0 && changeYScaled < 0) cursor = 'ne-resize';
if (changeXScaled >= 0 && changeYScaled >= 0) cursor = 'se-resize';
if (changeXScaled < 0 && changeYScaled >= 0) cursor = 'sw-resize';
if (changeXScaled < 0 && changeYScaled < 0) cursor = 'nw-resize';
resizeCrop(crop, cursor, changeXScaled, changeYScaled, shouldMaintainCropAspectRatio, shouldResizeCenterOut);
(0, _cropUtils.updateCropStringWithCrop)(crop, false, false, zeroCropMap ?? undefined);
};
// Attach to the captured target so the browser-implicit
// `pointercancel` (devtools focus, alt-tab, OS pointer reassignment)
// is reliably delivered to the same element our listeners live on.
// The earlier `setPointerCapture` call routes every subsequent
// pointer event for this pointer here regardless of cursor position.
(0, _appState.appState).hooks.cropMouseManipulation.addEventListener('pointermove', drawCropHandler);
(0, _appState.appState).hooks.cropMouseManipulation.addEventListener('pointerup', endDraw, {
once: true,
capture: true
});
// Mirror pointerup with pointercancel — see the matching drag/resize
// handler for the rationale.
(0, _appState.appState).hooks.cropMouseManipulation.addEventListener('pointercancel', endDrawOnCancel, {
once: true,
capture: true
});
// exact event listener reference only added once so remove not required
document.addEventListener('click', (0, _videoUtil.blockVideoPause), {
once: true,
capture: true
});
} else finishDrawingCrop(true);
}
/** Pointercancel companion for `endDraw`. The browser fires this when it
* releases pointer capture without a normal pointerup — we revert the
* in-progress draw rather than committing it (the user didn't choose to
* finalize). */ function endDrawOnCancel(e) {
finishDrawingCrop(true, e.pointerId);
}
function endDraw(e) {
if (e.button === 0) finishDrawingCrop(false, e.pointerId);
else finishDrawingCrop(true, e.pointerId);
if (ctrlOrCommand(e)) document.addEventListener('pointermove', cropHoverHandler, true);
}
function finishDrawingCrop(shouldRevertCrop, pointerId) {
(0, _crop.Crop).shouldConstrainMinDimensions = true;
unregisterDrawRecovery();
unregisterDrawRecovery = ()=>{};
if (pointerId != null && (0, _appState.appState).hooks.cropMouseManipulation.hasPointerCapture?.(pointerId)) (0, _appState.appState).hooks.cropMouseManipulation.releasePointerCapture(pointerId);
(0, _appState.appState).hooks.cropMouseManipulation.style.cursor = 'auto';
(0, _appState.appState).hooks.cropMouseManipulation.removeEventListener('pointerdown', beginDrawHandler, true);
if (drawCropHandler) (0, _appState.appState).hooks.cropMouseManipulation.removeEventListener('pointermove', drawCropHandler);
(0, _appState.appState).hooks.cropMouseManipulation.removeEventListener('pointerup', endDraw, true);
(0, _appState.appState).hooks.cropMouseManipulation.removeEventListener('pointercancel', endDrawOnCancel, true);
drawCropHandler = null;
isDrawingCrop = false;
(0, _videoUtil.showPlayerControls)();
document.addEventListener('keydown', addCropHoverListener, true);
if ((0, _appState.appState).wasGlobalSettingsEditorOpen) {
if (shouldRevertCrop) (0, _appState.appState).settings.newMarkerCrop = prevNewMarkerCrop;
else {
const newCrop = transformCropWithPushBack(prevNewMarkerCrop, (0, _appState.appState).settings.newMarkerCrop, shouldFinishDrawMaintainAspectRatio);
(0, _appState.appState).settings.newMarkerCrop = newCrop;
}
(0, _cropUtils.updateCropString)((0, _appState.appState).settings.newMarkerCrop, true);
}
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen) {
(0, _util.assertDefined)(initDrawCropMap);
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const cropMap = markerPair.cropMap;
if (shouldRevertCrop) {
const draft = (0, _immer.createDraft)((0, _undoredo.getMarkerPairHistory)(markerPair));
draft.cropMap = initDrawCropMap;
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair, false);
(0, _charts.renderSpeedAndCropUI)();
} else {
const newCrop = transformCropWithPushBack(initDrawCropMap[(0, _appState.appState).currentCropPointIndex].crop, cropMap[(0, _appState.appState).currentCropPointIndex].crop, shouldFinishDrawMaintainAspectRatio);
(0, _cropUtils.updateCropString)(newCrop, true, false, initDrawCropMap);
}
}
shouldRevertCrop ? (0, _util.flashMessage)('Drawing crop canceled', 'red') : (0, _util.flashMessage)('Finished drawing crop', 'green');
}
function transformCropWithPushBack(oldCrop, newCrop, shouldMaintainCropAspectRatio = false) {
const [, , iw, ih] = (0, _cropUtils.getCropComponents)(oldCrop);
const [nx, ny, nw, nh] = (0, _cropUtils.getCropComponents)(newCrop);
const dw = nw - iw;
const dh = nh - ih;
const crop = (0, _crop.Crop).fromCropString((0, _util.getCropString)(0, 0, iw, ih), (0, _appState.appState).settings.cropRes);
shouldMaintainCropAspectRatio ? crop.resizeSEAspectRatioLocked(dh, dw) : crop.resizeSE(dh, dw);
crop.panX(nx);
crop.panY(ny);
return crop.cropString;
}
let cropCrossHairEnabled = false;
function toggleCropCrossHair() {
if (cropCrossHairEnabled) {
(0, _util.flashMessage)('Disabled crop crosshair', 'red');
cropCrossHairEnabled = false;
cropOverlayElements.cropCrossHair && (cropOverlayElements.cropCrossHair.style.display = 'none');
} else {
(0, _util.flashMessage)('Enabled crop crosshair', 'green');
cropCrossHairEnabled = true;
cropOverlayElements.cropCrossHair && (cropOverlayElements.cropCrossHair.style.display = 'block');
(0, _charts.renderSpeedAndCropUI)(false, false);
}
}
function renderStaticCropOverlay(crop) {
const [x, y, w, h] = (0, _cropUtils.getCropComponents)(crop);
[
cropOverlayElements.cropRect,
cropOverlayElements.cropRectBorderBlack,
cropOverlayElements.cropRectBorderWhite
].map((cropRect)=>{
if (cropRect) setCropOverlayDimensions(cropRect, x, y, w, h);
});
if (cropCrossHairEnabled && cropOverlayElements.cropCrossHair) {
cropOverlayElements.cropCrossHairs.map((cropCrossHair)=>{
setCropCrossHair(cropCrossHair, (0, _util.getCropString)(x, y, w, h));
});
cropOverlayElements.cropCrossHair.style.stroke = 'white';
}
}
},{"./appState":"g0AlP","./crop-utils":"k2gwb","./crop/crop-preview":"9T0zg","lit-html":"9fQBw","./util/util":"99arg","./charts":"hBxwj","./util/videoUtil":"93pN0","immer":"R6AMM","./crop/crop":"axPMI","./util/undoredo":"7UuTl","./ui/chart/cropchart/cropChartSpec":"67uoo","./util/drag-recovery":"3BYSh","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"9T0zg":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "startCropPreview", ()=>startCropPreview);
parcelHelpers.export(exports, "disableCropPreview", ()=>disableCropPreview);
parcelHelpers.export(exports, "startDrawZoomedRegion", ()=>startDrawZoomedRegion);
parcelHelpers.export(exports, "triggerCropPreviewRedraw", ()=>triggerCropPreviewRedraw);
parcelHelpers.export(exports, "resetCropPreviewAnchor", ()=>resetCropPreviewAnchor);
parcelHelpers.export(exports, "toggleCropPreviewGammaPreview", ()=>toggleCropPreviewGammaPreview);
parcelHelpers.export(exports, "cropPreviewEnabled", ()=>cropPreviewEnabled);
parcelHelpers.export(exports, "toggleCropPreview", ()=>toggleCropPreview);
parcelHelpers.export(exports, "enableCropPreview", ()=>enableCropPreview);
parcelHelpers.export(exports, "getZoomRegion", ()=>getZoomRegion);
var _litHtml = require("lit-html");
var _util = require("../util/util");
var _appState = require("../appState");
var _previewGamma = require("../util/previewGamma");
var _videoPreviewElement = require("./video-preview-element");
var _charts = require("../charts");
var _cropUtils = require("../crop-utils");
let cropPreviewCanvas = null;
let gammaRenderer = null;
let floatingPreviewHandle = null;
let lastMiniState = null;
let lastPopoutBounds = null;
const cropPreviewModalTemplate = (0, _litHtml.html)`
<div id="ytc-zoom-modal" class="ytc-modal">
<div id="ytc-modal-content" class="ytc-modal-content">
<div class="ytc-canvas-wrapper">
<canvas id="ytc-zoom-canvas"></canvas>
</div>
</div>
</div>
`;
function startCropPreview(video, toggleCallback, getCropPreviewMouseTimeSetter, getSourceRect, mode = 'modal') {
const mountModal = ()=>{
const container = document.createElement('div');
(0, _litHtml.render)(cropPreviewModalTemplate, container);
const modalElement = container.firstElementChild;
document.body.insertAdjacentElement('afterbegin', modalElement);
const modalContent = document.getElementById('ytc-modal-content');
cropPreviewCanvas = document.getElementById('ytc-zoom-canvas');
gammaRenderer = (0, _previewGamma.createWebGLGammaRenderer)(cropPreviewCanvas);
cropPreviewCanvas.insertAdjacentElement('afterend', gammaRenderer.outputCanvas);
setTimeout(()=>{
modalElement.addEventListener('click', (e)=>{
if (!modalContent?.contains(e.target)) {
(0, _util.deleteElement)(modalElement);
toggleCallback();
}
});
}, 0);
modalElement.addEventListener('click', (e)=>{
if (modalContent?.contains(e.target)) {
if (video.paused) video.play();
else video.pause();
}
});
const cropPreviewMouseTimeSetter = getCropPreviewMouseTimeSetter(modalContent);
modalElement.addEventListener('pointerdown', cropPreviewMouseTimeSetter, true);
toggleCropPreviewGammaPreview();
if (getSourceRect) startDrawZoomedRegion(getSourceRect);
};
if (mode == 'modal') {
mountModal();
return;
}
const [, , w, h] = getSourceRect?.() ?? [
0,
0,
16,
9
];
const isRotated = (0, _appState.appState).rotation === 90 || (0, _appState.appState).rotation === -90;
let wasPopoutInThisSession = false;
floatingPreviewHandle = (0, _videoPreviewElement.mountFloatingVideoPreview)(video, {
getSourceRect,
aspectRatio: isRotated ? h / w : w / h,
initialX: lastMiniState?.x,
initialY: lastMiniState?.y,
initialWidth: lastMiniState?.width,
initialHeight: lastMiniState?.height,
initialPopoutBounds: lastPopoutBounds ?? undefined,
onPopoutClose: (bounds)=>{
lastPopoutBounds = bounds;
wasPopoutInThisSession = true;
},
onSwitchToModal: ()=>{
lastMiniState = floatingPreviewHandle?.getState() ?? null;
lastPopoutBounds = null;
floatingPreviewHandle = null;
mountModal();
},
onDestroy: ()=>{
if (!wasPopoutInThisSession) {
lastMiniState = floatingPreviewHandle?.getState() ?? null;
lastPopoutBounds = null;
}
wasPopoutInThisSession = false;
floatingPreviewHandle = null;
toggleCallback();
}
});
if (lastPopoutBounds !== null || mode == 'pop-out') floatingPreviewHandle.popOut();
floatingPreviewHandle.redraw();
}
function disableCropPreview() {
const wasPopped = floatingPreviewHandle?.isPopped() ?? false;
floatingPreviewHandle?.closePopup(); // if wasPopped: fires beforeunload → onPopoutClose + onDestroy
if (!wasPopped && floatingPreviewHandle) {
// Was floating (not popped out) and destroy will be silent — capture appState now
lastMiniState = floatingPreviewHandle.getState();
lastPopoutBounds = null;
}
floatingPreviewHandle?.destroy(true);
floatingPreviewHandle = null;
const modal = document.getElementById('ytc-zoom-modal');
if (modal) (0, _util.deleteElement)(modal);
}
function startDrawZoomedRegion(getZoomRegion) {
if (!cropPreviewCanvas) return;
const ctx = cropPreviewCanvas.getContext('2d');
const modal = document.getElementById('ytc-zoom-modal');
const modalContent = document.getElementsByClassName('ytc-modal-content')[0];
const [, , w, h] = getZoomRegion();
cropPreviewCanvas.width = w;
cropPreviewCanvas.height = h;
(0, _util.assertDefined)(ctx, 'Could not get 2d context from crop preview canvas');
(0, _util.assertDefined)(modal, 'Could not find ytc-zoom-modal element');
drawZoomedRegion(getZoomRegion, cropPreviewCanvas, ctx, modal, modalContent);
}
function drawZoomedRegion(getZoomRegion, canvas, ctx, modal, modalContent) {
if (modal && modal.isConnected && !modal.classList.contains('hidden')) {
const [x, y, w, h] = getZoomRegion();
canvas.width = w;
canvas.height = h;
resizeModalToAspect(modalContent, w, h);
ctx.drawImage((0, _appState.appState).video, x, y, w, h, 0, 0, canvas.width, canvas.height);
if ((0, _appState.appState).isGammaPreviewOn && gammaRenderer) gammaRenderer.render(canvas, (0, _previewGamma.prevGammaVal));
(0, _appState.appState).video.requestVideoFrameCallback(()=>{
drawZoomedRegion(getZoomRegion, canvas, ctx, modal, modalContent);
});
}
}
function resizeModalToAspect(modalContent, width, height) {
const maxWidth = window.innerWidth * 0.95;
const maxHeight = window.innerHeight * 0.95;
const aspectRatio = width / height;
let modalWidth = maxWidth;
let modalHeight = modalWidth / aspectRatio;
if (modalHeight > maxHeight) {
modalHeight = maxHeight;
modalWidth = modalHeight * aspectRatio;
}
modalContent.style.width = `${modalWidth}px`;
modalContent.style.height = `${modalHeight}px`;
}
function triggerCropPreviewRedraw() {
floatingPreviewHandle?.redraw();
}
function resetCropPreviewAnchor() {
floatingPreviewHandle?.resetAnchor();
}
function toggleCropPreviewGammaPreview() {
floatingPreviewHandle?.redraw();
if (!cropPreviewCanvas || !gammaRenderer) return;
if ((0, _appState.appState).isGammaPreviewOn) {
cropPreviewCanvas.style.display = 'none';
gammaRenderer.outputCanvas.style.display = '';
} else {
cropPreviewCanvas.style.display = '';
gammaRenderer.outputCanvas.style.display = 'none';
}
}
let cropPreviewEnabled = false;
function toggleCropPreview(mode = 'modal') {
if (cropPreviewEnabled) {
(0, _util.flashMessage)('Disabled crop preview', 'red');
cropPreviewEnabled = false;
disableCropPreview();
} else {
(0, _util.flashMessage)('Enabled crop preview', 'green');
cropPreviewEnabled = true;
const onCropPreviewDisabled = ()=>{
if (!cropPreviewEnabled) return;
cropPreviewEnabled = false;
(0, _util.flashMessage)('Disabled crop preview', 'red');
};
startCropPreview((0, _appState.appState).video, onCropPreviewDisabled, (0, _charts.getCropPreviewMouseTimeSetter), getZoomRegion, mode);
enableCropPreview();
}
}
function enableCropPreview() {
startDrawZoomedRegion(getZoomRegion);
}
function getZoomRegion() {
const dynamicCropComponents = (0, _charts.getDynamicCropComponents)();
if (dynamicCropComponents == null) {
const cropString = (0, _cropUtils.getRelevantCropString)();
const scaledCropComponents = (0, _cropUtils.getVideoScaledCropComponentsFromCropString)(cropString);
return scaledCropComponents;
} else return (0, _cropUtils.getVideoScaledCropComponents)(dynamicCropComponents);
}
},{"lit-html":"9fQBw","../util/util":"99arg","../appState":"g0AlP","../util/previewGamma":"6oouq","./video-preview-element":"jbY8I","../charts":"hBxwj","../crop-utils":"k2gwb","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"6oouq":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "prevGammaVal", ()=>prevGammaVal);
parcelHelpers.export(exports, "setPrevGammaVal", ()=>setPrevGammaVal);
parcelHelpers.export(exports, "createWebGLGammaRenderer", ()=>createWebGLGammaRenderer);
let prevGammaVal = 1;
function setPrevGammaVal(gammaVal) {
prevGammaVal = gammaVal;
}
const VERTEX_SHADER_SOURCE = `#version 300 es
precision highp float;
in vec2 a_position;
out vec2 v_uv;
void main() {
// Map NDC [-1, 1] -> UV [0, 1] for texture sampling.
v_uv = 0.5 * (a_position + 1.0);
gl_Position = vec4(a_position, 0.0, 1.0);
}
`;
// Gamma convention: exponent applied directly (not 1/gamma), matching
// FFmpeg's lutyuv=y=gammaval(gamma) — gamma > 1 darkens, gamma < 1 brightens.
//
// Per-channel (not luma-only) to avoid saturation artifacts: canvas pixels
// from ctx.drawImage are sRGB-encoded, so Rec.709 linear-light luma
// coefficients would be inaccurate. Scaling all channels by the same factor
// preserves their ratios (no hue or saturation shift).
const FRAGMENT_SHADER_SOURCE = `#version 300 es
precision highp float;
in vec2 v_uv;
uniform sampler2D u_texture;
uniform float u_gamma;
out vec4 outColor;
void main() {
vec4 color = texture(u_texture, v_uv);
// Clamp base to 1e-6 to guard against pow(0, very_small_gamma) edge cases.
// Alpha is passed through unchanged.
outColor = vec4(pow(max(color.rgb, vec3(1e-6)), vec3(u_gamma)), color.a);
}
`;
// Fullscreen triangle: three vertices that extend past the clip boundary.
// The rasterizer clips to the viewport, so every on-screen fragment is covered
// by a single draw call. The interpolated UVs within NDC [-1,1]x[-1,1] fall
// correctly in [0,1]x[0,1]; CLAMP_TO_EDGE handles the out-of-range vertices
// at (3,-1) and (-1,3) but those positions are never actually sampled.
const FULLSCREEN_TRIANGLE = new Float32Array([
-1,
-1,
3,
-1,
-1,
3
]);
function createShader(gl, type, source) {
const shader = gl.createShader(type);
if (!shader) throw new Error('Failed to create shader');
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const info = gl.getShaderInfoLog(shader) ?? 'Unknown shader compile error';
gl.deleteShader(shader);
throw new Error(info);
}
return shader;
}
function createProgram(gl, vertexSource, fragmentSource) {
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
const program = gl.createProgram();
if (!program) {
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
throw new Error('Failed to create program');
}
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
// Shaders are no longer needed after linking.
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const info = gl.getProgramInfoLog(program) ?? 'Unknown program link error';
gl.deleteProgram(program);
throw new Error(info);
}
return program;
}
function createWebGLGammaRenderer(sourceCanvas) {
const outputCanvas = document.createElement('canvas');
outputCanvas.id = 'ytc-zoom-canvas-webgl';
outputCanvas.width = sourceCanvas.width;
outputCanvas.height = sourceCanvas.height;
const gl = outputCanvas.getContext('webgl2', {
// Required so the DOM compositor can read the finished frame;
// without this the buffer may be cleared after compositing.
preserveDrawingBuffer: true,
// Source pixels have straight (non-premultiplied) alpha. Disabling
// premultipliedAlpha prevents the driver from modifying RGB on upload.
premultipliedAlpha: false
});
if (!gl) throw new Error('WebGL 2 not supported');
const program = createProgram(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);
// The VAO records the vertex buffer binding and attribute layout set up
// below. Rebinding it in render() restores all of that state in one call.
const vao = gl.createVertexArray();
if (!vao) {
gl.deleteProgram(program);
throw new Error('Failed to create VAO');
}
const buffer = gl.createBuffer();
if (!buffer) {
gl.deleteVertexArray(vao);
gl.deleteProgram(program);
throw new Error('Failed to create buffer');
}
gl.bindVertexArray(vao);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, FULLSCREEN_TRIANGLE, gl.STATIC_DRAW);
const aPosition = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
// Unbind VAO before unbinding the buffer so the VAO doesn't capture a null
// ARRAY_BUFFER — the VAO stores the buffer reference, not the binding slot.
gl.bindVertexArray(null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
const texture = gl.createTexture();
if (!texture) {
gl.deleteBuffer(buffer);
gl.deleteVertexArray(vao);
gl.deleteProgram(program);
throw new Error('Failed to create texture');
}
gl.bindTexture(gl.TEXTURE_2D, texture);
// CLAMP_TO_EDGE prevents border-colour bleed at the triangle edges that
// extend past the viewport (vertices at (3,-1) and (-1,3)).
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.bindTexture(gl.TEXTURE_2D, null);
// getUniformLocation returns null if the name is absent or was optimized
// away by the driver. Both uniforms are actively used in the shader so
// they will be present after a successful link; this is a safety guard.
const uTexture = gl.getUniformLocation(program, 'u_texture');
const uGamma = gl.getUniformLocation(program, 'u_gamma');
if (!uTexture || !uGamma) {
gl.deleteTexture(texture);
gl.deleteBuffer(buffer);
gl.deleteVertexArray(vao);
gl.deleteProgram(program);
throw new Error('Missing required uniform(s)');
}
function render(sourceCanvas, gamma) {
// Assigning canvas dimensions resets the drawing buffer (clears it).
// The viewport call and full-screen draw immediately after cover this.
outputCanvas.style.width = sourceCanvas.style.width;
outputCanvas.style.height = sourceCanvas.style.height;
outputCanvas.width = sourceCanvas.width;
outputCanvas.height = sourceCanvas.height;
if (!gl) throw new Error('WebGL2RenderingContext is null');
gl.viewport(0, 0, outputCanvas.width, outputCanvas.height);
gl.useProgram(program);
gl.bindVertexArray(vao);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
// Canvas (0,0) is top-left; WebGL texture V=0 is bottom. Flipping on
// upload corrects the orientation so the image appears right-side up.
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sourceCanvas);
gl.uniform1i(uTexture, 0); // texture unit 0
// Floor gamma at 1e-6 so pow(x, gamma) never degenerates to 1 for all x.
gl.uniform1f(uGamma, Math.max(gamma, 1e-6));
// No gl.clear() needed — the fullscreen triangle writes every pixel.
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.bindVertexArray(null);
gl.bindTexture(gl.TEXTURE_2D, null);
}
function destroy() {
if (!gl) throw new Error('WebGL2RenderingContext is null');
gl.deleteTexture(texture);
gl.deleteBuffer(buffer);
gl.deleteVertexArray(vao);
gl.deleteProgram(program);
// Explicitly release GPU resources rather than waiting for GC.
const loseContext = gl.getExtension('WEBGL_lose_context');
if (loseContext) loseContext.loseContext();
if (outputCanvas.parentNode) outputCanvas.parentNode.removeChild(outputCanvas);
}
return {
outputCanvas,
render,
destroy
};
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"jbY8I":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "mountFloatingVideoPreview", ()=>mountFloatingVideoPreview);
var _litHtml = require("lit-html");
var _previewGamma = require("../util/previewGamma");
var _appState = require("../appState");
var _previewResizeMath = require("./preview-resize-math");
const STYLE_ID = 'floating-video-preview-lit-html-styles';
function ensureStyles() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
.floating-video-preview-host {
position: absolute;
inset: 0;
display: block;
pointer-events: none;
}
.floating-video-preview-shell {
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
overflow: hidden;
background: #000;
box-shadow: 0 8px 24px rgb(0 0 0 / 0.28);
touch-action: none;
user-select: none;
pointer-events: auto;
cursor: grab;
}
.floating-video-preview-shell[data-dragging="true"] {
cursor: grabbing;
}
.floating-video-preview-video {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
background: #000;
pointer-events: none;
transform-origin: center;
}
.floating-video-preview-canvas {
display: block;
width: 100%;
height: 100%;
background: #000;
pointer-events: none;
}
.floating-video-preview-topbar {
position: absolute;
top: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
row-gap: 2px;
padding: 3px 4px;
background: linear-gradient(to bottom, rgb(0 0 0 / 0.5) 0%, rgb(0 0 0 / 0) 100%);
color: #fff;
pointer-events: auto;
opacity: 0;
transform: translateY(-4px);
transition:
opacity 140ms ease,
transform 140ms ease;
}
.floating-video-preview-topbar-btns {
display: flex;
flex-wrap: wrap;
gap: 2px;
}
.floating-video-preview-shell[data-controls-visible="true"] .floating-video-preview-topbar {
opacity: 1;
transform: translateY(0);
}
.floating-video-preview-resize-icon {
width: 14px;
height: 14px;
flex: 0 0 auto;
pointer-events: none;
opacity: 0.85;
background:
linear-gradient(
135deg,
transparent 0 35%,
rgb(255 255 255 / 0.88) 35% 45%,
transparent 45% 55%,
rgb(255 255 255 / 0.88) 55% 65%,
transparent 65% 100%
);
}
.floating-video-preview-close-btn {
background: none;
border: none;
color: rgb(255 255 255 / 0.85);
font: 16px/1 system-ui, sans-serif;
padding: 1px 5px 2px;
cursor: pointer;
border-radius: 4px;
pointer-events: auto;
display: flex;
align-items: center;
}
.floating-video-preview-close-btn:hover {
background: rgb(255 255 255 / 0.18);
color: #fff;
}
`;
document.head.appendChild(style);
}
function mountFloatingVideoPreview(sourceVideo, options = {}) {
const getSourceRect = options.getSourceRect ?? null;
const onSwitchToModal = options.onSwitchToModal ?? null;
const onDestroy = options.onDestroy ?? null;
const onPopoutClose = options.onPopoutClose ?? null;
const initialPopoutBounds = options.initialPopoutBounds ?? null;
if (!sourceVideo.isConnected) throw new Error('sourceVideo must already be connected to the document');
ensureStyles();
const parent = options.parent ?? document.body;
if (parent !== document.body) {
const computed = window.getComputedStyle(parent);
if (computed.position === 'static') parent.style.position = 'relative';
}
const host = document.createElement('div');
host.className = 'floating-video-preview-host';
if (parent === document.body) {
host.style.position = 'fixed';
host.style.inset = '0';
} else {
host.style.position = 'absolute';
host.style.inset = '0';
}
host.style.zIndex = '2147483647';
const previewWidth = options.initialWidth ?? 240;
const previewHeight = options.initialHeight ?? (options.aspectRatio != null ? Math.round(previewWidth / options.aspectRatio) : 135);
let initialX = options.initialX;
let initialY = options.initialY;
if (initialX === undefined || initialY === undefined) {
const sourceRect = sourceVideo.getBoundingClientRect();
const parentRect = parent.getBoundingClientRect();
const margin = 8;
initialX = initialX ?? sourceRect.right - parentRect.left - previewWidth - margin;
initialY = initialY ?? sourceRect.top + margin;
}
const state = {
sourceVideo,
x: initialX,
y: initialY,
width: previewWidth,
height: previewHeight,
minWidth: options.minWidth ?? 50,
minHeight: options.minHeight ?? 50,
aspectRatio: options.aspectRatio ?? 16 / 9,
zIndex: options.zIndex ?? 1000,
mirror: options.mirror ?? false,
controlsVisible: false,
dragging: false
};
let previewVideoEl = null;
let cropCanvasEl = null;
let gammaRenderer = null;
let cropLoopId = null;
let cropLoopSource = null;
let shellEl = null;
let controlsHideTimer = null;
const sourceCleanup = [];
let destroyed = false;
let popupWindow = null;
// Track previous source dimensions to determine which changed
let lastSourceW = null;
let lastSourceH = null;
let lastIsRotated = null;
// Track anchor preference for consistent grow/shrink behavior
let anchorX = 'left';
let anchorY = 'top';
// Track starting position for overflow detection
// Anchor only switches back when user has "undone" the overflow
let startModX = null;
let startModY = null;
const clearHideTimer = ()=>{
if (controlsHideTimer !== null) {
window.clearTimeout(controlsHideTimer);
controlsHideTimer = null;
}
};
const showControls = ()=>{
state.controlsVisible = true;
update();
};
const scheduleHideControls = ()=>{
clearHideTimer();
controlsHideTimer = window.setTimeout(()=>{
state.controlsVisible = false;
update();
}, 400);
};
const unbindSourceEvents = ()=>{
for (const fn of sourceCleanup)fn();
sourceCleanup.length = 0;
};
const cleanupPreviewVideo = ()=>{
if (!previewVideoEl) return;
previewVideoEl.pause();
previewVideoEl.removeAttribute('src');
previewVideoEl.srcObject = null;
previewVideoEl.load();
};
const stopCropLoop = ()=>{
if (cropLoopId !== null) {
cropLoopSource?.cancelVideoFrameCallback(cropLoopId);
cropLoopId = null;
cropLoopSource = null;
}
};
const drawFrame = ()=>{
if (destroyed || !cropCanvasEl || !state.sourceVideo || !getSourceRect) return;
const source = state.sourceVideo;
const [sx, sy, sw, sh] = getSourceRect();
const isRotated = (0, _appState.appState).rotation === 90 || (0, _appState.appState).rotation === -90;
const canvasW = isRotated ? sh : sw;
const canvasH = isRotated ? sw : sh;
if (cropCanvasEl.width !== canvasW) cropCanvasEl.width = canvasW;
if (cropCanvasEl.height !== canvasH) cropCanvasEl.height = canvasH;
const newAR = canvasW / canvasH;
if (Math.abs(newAR - state.aspectRatio) > 0.001) {
const hostRect = host.getBoundingClientRect();
const rotationChanged = lastIsRotated !== null && isRotated !== lastIsRotated;
if (rotationChanged) {
// Swap width and height to reflect the landscape↔portrait flip
const swappedW = Math.max(state.minWidth, Math.min(state.height, hostRect.width - state.x));
const swappedH = Math.max(state.minHeight, Math.min(state.width, hostRect.height - state.y));
state.width = swappedW;
state.height = swappedH;
state.aspectRatio = newAR;
anchorX = 'left';
anchorY = 'top';
startModX = null;
startModY = null;
} else {
// Regular AR change (crop resize) — determine which source dimension changed more
let lockDimension = 'width';
if (lastSourceW !== null && lastSourceH !== null) {
const wChange = Math.abs(sw - lastSourceW);
const hChange = Math.abs(sh - lastSourceH);
lockDimension = hChange > wChange ? 'width' : 'height';
// When rotated 90°, canvas axes are swapped relative to source, so invert lock dimension
if (isRotated) lockDimension = lockDimension === 'width' ? 'height' : 'width';
}
// Track starting position when modification begins (anchor is still left/top)
// This is used to determine when the user has "undone" the overflow
if (anchorX === 'left' && startModX === null) startModX = state.x;
if (anchorY === 'top' && startModY === null) startModY = state.y;
const r = (0, _previewResizeMath.computeARChange)({
x: state.x,
y: state.y,
width: state.width,
height: state.height,
minWidth: state.minWidth,
minHeight: state.minHeight,
newAR,
viewportW: hostRect.width,
viewportH: hostRect.height,
lockDimension,
anchorX,
anchorY,
startModX: startModX ?? undefined,
startModY: startModY ?? undefined
});
state.x = r.x;
state.y = r.y;
state.width = r.width;
state.height = r.height;
state.aspectRatio = newAR;
// Update anchor tracking based on which anchors were actually used
anchorX = r.anchorX;
anchorY = r.anchorY;
}
update();
}
// Update tracked dimensions for next comparison
lastSourceW = sw;
lastSourceH = sh;
lastIsRotated = isRotated;
const ctx = cropCanvasEl.getContext('2d');
if (ctx) {
ctx.save();
if ((0, _appState.appState).rotation === 90) {
ctx.translate(canvasW, 0);
ctx.rotate(Math.PI / 2);
ctx.drawImage(source, sx, sy, sw, sh, 0, 0, sw, sh);
} else if ((0, _appState.appState).rotation === -90) {
ctx.translate(0, canvasH);
ctx.rotate(-Math.PI / 2);
ctx.drawImage(source, sx, sy, sw, sh, 0, 0, sw, sh);
} else ctx.drawImage(source, sx, sy, sw, sh, 0, 0, sw, sh);
ctx.restore();
if ((0, _appState.appState).isGammaPreviewOn) {
gammaRenderer ??= (0, _previewGamma.createWebGLGammaRenderer)(cropCanvasEl);
gammaRenderer.render(cropCanvasEl, (0, _previewGamma.prevGammaVal));
ctx.drawImage(gammaRenderer.outputCanvas, 0, 0);
}
}
};
const startCropLoop = ()=>{
if (!getSourceRect || !state.sourceVideo || destroyed) return;
stopCropLoop();
const source = state.sourceVideo;
const draw = ()=>{
if (destroyed) return;
drawFrame();
cropLoopId = source.requestVideoFrameCallback(draw);
};
cropLoopSource = source;
cropLoopId = source.requestVideoFrameCallback(draw);
};
let redraw = ()=>{
if (!getSourceRect || !state.sourceVideo || destroyed) return;
drawFrame();
};
const syncFromSource = ()=>{
if (!(state.sourceVideo instanceof HTMLVideoElement) || !previewVideoEl) return;
const source = state.sourceVideo;
const preview = previewVideoEl;
if (source.srcObject instanceof MediaStream) {
if (preview.srcObject !== source.srcObject) preview.srcObject = source.srcObject;
preview.play().catch(()=>{});
return;
}
const captureCandidate = source;
const captured = captureCandidate.captureStream?.() ?? captureCandidate.mozCaptureStream?.();
if (captured) {
if (preview.srcObject !== captured) preview.srcObject = captured;
preview.play().catch(()=>{});
return;
}
if (source.currentSrc && preview.src !== source.currentSrc) {
preview.srcObject = null;
// eslint-disable-next-line local/no-url-attribute-interpolation -- source is a YouTube HTMLVideoElement; currentSrc is browser-generated (typically blob: from MSE), not attacker-controllable.
preview.src = source.currentSrc;
preview.currentTime = source.currentTime || 0;
preview.play().catch(()=>{});
}
};
const bindSourceEvents = ()=>{
if (!(state.sourceVideo instanceof HTMLVideoElement)) return;
if (getSourceRect) {
state.sourceVideo.addEventListener('play', startCropLoop);
sourceCleanup.push(()=>state.sourceVideo?.removeEventListener('play', startCropLoop));
} else {
const pause = ()=>{
previewVideoEl?.pause();
};
state.sourceVideo.addEventListener('loadedmetadata', syncFromSource);
state.sourceVideo.addEventListener('play', syncFromSource);
state.sourceVideo.addEventListener('pause', pause);
state.sourceVideo.addEventListener('emptied', cleanupPreviewVideo);
sourceCleanup.push(()=>state.sourceVideo?.removeEventListener('loadedmetadata', syncFromSource));
sourceCleanup.push(()=>state.sourceVideo?.removeEventListener('play', syncFromSource));
sourceCleanup.push(()=>state.sourceVideo?.removeEventListener('pause', pause));
sourceCleanup.push(()=>state.sourceVideo?.removeEventListener('emptied', cleanupPreviewVideo));
}
};
const closePopup = ()=>{
if (popupWindow && !popupWindow.closed) popupWindow.close();
popupWindow = null;
};
const popOutWithSourceRect = (source)=>{
if (!getSourceRect) return;
const [, , sw, sh] = getSourceRect();
const pb = initialPopoutBounds;
// When rotated 90° or -90°, swap width and height for the popup window
const isRotated = (0, _appState.appState).rotation === 90 || (0, _appState.appState).rotation === -90;
const popupW = isRotated ? sh : sw;
const popupH = isRotated ? sw : sh;
const features = `popup=yes,width=${pb?.width ?? popupW},height=${pb?.height ?? popupH}${pb ? `,left=${pb.screenX},top=${pb.screenY}` : ''}`;
const popup = window.open('', '_blank', features);
if (!popup) return;
popupWindow = popup;
popup.document.title = 'Video Preview';
popup.document.body.style.cssText = 'margin:0;padding:0;background:#000;overflow:hidden;display:flex;align-items:center;justify-content:center;width:100vw;height:100vh;';
const canvas = popup.document.createElement('canvas');
// Canvas dimensions are swapped when rotated
const canvasW = isRotated ? sh : sw;
const canvasH = isRotated ? sw : sh;
canvas.style.cssText = 'display:block;';
canvas.width = canvasW;
canvas.height = canvasH;
popup.document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
let popupLoopId = null;
let popupGammaRenderer = null;
const drawOnce = ()=>{
if (popup.closed || !ctx) return;
if (!getSourceRect) return;
const [sx, sy, fsw, fsh] = getSourceRect();
// Canvas dimensions are swapped when rotated — check live since rotation can change after pop-out
const isNowRotated = (0, _appState.appState).rotation === 90 || (0, _appState.appState).rotation === -90;
const fcw = isNowRotated ? fsh : fsw;
const fch = isNowRotated ? fsw : fsh;
const vpW = popup.innerWidth || fcw;
const vpH = popup.innerHeight || fch;
const scale = Math.min(vpW / fcw, vpH / fch);
const displayW = Math.floor(fcw * scale);
const displayH = Math.floor(fch * scale);
if (canvas.width !== fcw || canvas.height !== fch || canvas.style.width !== displayW + 'px' || canvas.style.height !== displayH + 'px') {
canvas.width = fcw;
canvas.height = fch;
canvas.style.width = displayW + 'px';
canvas.style.height = displayH + 'px';
}
// Apply rotation transform if needed
ctx.save();
if ((0, _appState.appState).rotation === 90) {
ctx.translate(canvas.width, 0);
ctx.rotate(Math.PI / 2);
ctx.drawImage(source, sx, sy, fsw, fsh, 0, 0, fsw, fsh);
} else if ((0, _appState.appState).rotation === -90) {
ctx.translate(0, canvas.height);
ctx.rotate(-Math.PI / 2);
ctx.drawImage(source, sx, sy, fsw, fsh, 0, 0, fsw, fsh);
} else ctx.drawImage(source, sx, sy, fsw, fsh, 0, 0, fsw, fsh);
ctx.restore();
if ((0, _appState.appState).isGammaPreviewOn) {
popupGammaRenderer ??= (0, _previewGamma.createWebGLGammaRenderer)(canvas);
popupGammaRenderer.render(canvas, (0, _previewGamma.prevGammaVal));
ctx.drawImage(popupGammaRenderer.outputCanvas, 0, 0);
}
};
const drawFrame = ()=>{
if (popup.closed) return;
drawOnce();
popupLoopId = source.requestVideoFrameCallback(drawFrame);
};
popupLoopId = source.requestVideoFrameCallback(drawFrame);
popup.addEventListener('beforeunload', ()=>{
if (popupLoopId !== null) source.cancelVideoFrameCallback(popupLoopId);
popupGammaRenderer?.destroy();
popupGammaRenderer = null;
onPopoutClose?.({
screenX: popup.screenX,
screenY: popup.screenY,
width: popup.outerWidth,
height: popup.outerHeight
});
popupWindow = null;
redraw = ()=>{};
onDestroy?.();
});
redraw = drawOnce;
redraw();
destroy(true);
};
const popOutWithVideo = (source)=>{
const w = source.videoWidth || 640;
const h = source.videoHeight || 360;
const pb = initialPopoutBounds;
const features = `popup=yes,width=${pb?.width ?? w},height=${pb?.height ?? h}${pb ? `,left=${pb.screenX},top=${pb.screenY}` : ''}`;
const popup = window.open('', '_blank', features);
if (!popup) return;
popupWindow = popup;
popup.document.title = 'Video Preview';
popup.document.body.style.cssText = 'margin:0;padding:0;background:#000;overflow:hidden;';
const vid = popup.document.createElement('video');
vid.style.cssText = 'width:100%;height:100%;display:block;background:#000;';
vid.muted = true;
vid.autoplay = true;
vid.playsInline = true;
popup.document.body.appendChild(vid);
const captureCandidate = source;
const stream = captureCandidate.captureStream?.() ?? captureCandidate.mozCaptureStream?.();
if (stream) vid.srcObject = stream;
else if (source.currentSrc) {
// eslint-disable-next-line local/no-url-attribute-interpolation -- source is a YouTube HTMLVideoElement; currentSrc is browser-generated (typically blob: from MSE), not attacker-controllable.
vid.src = source.currentSrc;
vid.currentTime = source.currentTime;
}
vid.play().catch(()=>{});
popup.addEventListener('beforeunload', ()=>{
onPopoutClose?.({
screenX: popup.screenX,
screenY: popup.screenY,
width: popup.outerWidth,
height: popup.outerHeight
});
popupWindow = null;
onDestroy?.();
});
destroy(true);
};
const popOut = ()=>{
const source = state.sourceVideo;
if (!source) return;
if (getSourceRect) popOutWithSourceRect(source);
else popOutWithVideo(source);
};
const destroy = (silent = false)=>{
if (destroyed) return;
destroyed = true;
clearHideTimer();
teardownInteract?.();
teardownInteract = null;
unbindSourceEvents();
stopCropLoop();
gammaRenderer?.destroy();
gammaRenderer = null;
cleanupPreviewVideo();
(0, _litHtml.render)(null, host);
host.remove();
if (!silent) onDestroy?.();
};
const template = ()=>(0, _litHtml.html)`
<div
class="floating-video-preview-shell"
data-controls-visible=${String(state.controlsVisible)}
data-dragging=${String(state.dragging)}
style=${[
`transform: translate(${state.x}px, ${state.y}px)`,
`width: ${state.width}px`,
`height: ${state.height}px`,
`z-index: ${state.zIndex}`
].join('; ')}
tabindex="0"
aria-label="Floating video preview"
>
${getSourceRect ? (0, _litHtml.html)`<canvas class="floating-video-preview-canvas"></canvas>` : (0, _litHtml.html)`<video
class="floating-video-preview-video"
muted
playsinline
autoplay
style=${`transform: ${state.mirror ? 'scaleX(-1)' : 'none'}`}
></video>`}
<div class="floating-video-preview-topbar" role="toolbar" aria-label="Preview controls">
<div class="floating-video-preview-resize-icon" aria-hidden="true"></div>
<div class="floating-video-preview-topbar-btns">
<button
class="floating-video-preview-close-btn"
@click=${()=>{
popOut();
}}
aria-label="Pop out preview"
title="Pop out to window"
>
⧉
</button>
${onSwitchToModal ? (0, _litHtml.html)`<button
class="floating-video-preview-close-btn"
@click=${(e)=>{
e.stopPropagation();
destroy(true);
onSwitchToModal();
}}
aria-label="Switch to modal view"
title="Switch to modal view"
>
⊞
</button>` : ''}
<button
class="floating-video-preview-close-btn"
@click=${()=>{
destroy();
}}
aria-label="Close preview"
title="Close preview"
>
×
</button>
</div>
</div>
</div>
`;
let teardownInteract = null;
const setupInteract = ()=>{
teardownInteract?.();
teardownInteract = null;
if (!shellEl) return;
const shell = shellEl;
const getResizeMargin = (w, h)=>{
// Scale margin with preview size: 15% of smaller dimension, clamped to [8, 25]
const smaller = Math.min(w, h);
return Math.max(8, Math.min(25, Math.round(smaller * 0.15)));
};
const getEdges = (e)=>{
const r = shell.getBoundingClientRect();
const margin = getResizeMargin(r.width, r.height);
return {
left: e.clientX - r.left < margin,
right: r.right - e.clientX < margin,
top: e.clientY - r.top < margin,
bottom: r.bottom - e.clientY < margin
};
};
const edgeCursor = ({ left, right, top, bottom })=>{
if (top && left || bottom && right) return 'nwse-resize';
if (top && right || bottom && left) return 'nesw-resize';
if (left || right) return 'ew-resize';
if (top || bottom) return 'ns-resize';
return 'grab';
};
const applyDirect = ()=>{
shell.style.transform = `translate(${state.x}px, ${state.y}px)`;
shell.style.width = `${state.width}px`;
shell.style.height = `${state.height}px`;
};
let mode = 'none';
let capturedId = null;
let resizeEdges = {
left: false,
right: false,
top: false,
bottom: false
};
let startPx = 0, startPy = 0;
let startX = 0, startY = 0, startW = 0, startH = 0;
const onPointerDown = (e)=>{
if (e.target.closest('.floating-video-preview-close-btn')) return;
if (e.button !== 0) return;
const edges = getEdges(e);
const isEdge = edges.left || edges.right || edges.top || edges.bottom;
capturedId = e.pointerId;
startPx = e.clientX;
startPy = e.clientY;
startX = state.x;
startY = state.y;
startW = state.width;
startH = state.height;
shell.setPointerCapture(e.pointerId);
if (isEdge) {
mode = 'resize';
resizeEdges = edges;
shell.style.cursor = edgeCursor(edges);
} else {
mode = 'drag';
state.dragging = true;
shell.style.cursor = 'grabbing';
update();
}
showControls();
};
const onPointerMove = (e)=>{
if (capturedId === null || e.pointerId !== capturedId) {
if (mode === 'none') shell.style.cursor = edgeCursor(getEdges(e));
return;
}
const dx = e.clientX - startPx;
const dy = e.clientY - startPy;
if (mode === 'drag') {
const hostRect = host.getBoundingClientRect();
const intendedX = startX + dx;
const intendedY = startY + dy;
state.x = Math.min(Math.max(intendedX, 0), hostRect.width - state.width);
state.y = Math.min(Math.max(intendedY, 0), hostRect.height - state.height);
applyDirect();
return;
}
// resize — derive viewport max bounds from the fixed anchor so neither
// axis can grow beyond the viewport edge
const hostRect = host.getBoundingClientRect();
const anchorRight = resizeEdges.left || resizeEdges.top && !resizeEdges.right;
const anchorBottom = resizeEdges.top;
const maxW = anchorRight ? startX + startW : hostRect.width - startX;
const maxH = anchorBottom ? startY + startH : hostRect.height - startY;
const result = (0, _previewResizeMath.computeARLockedResize)({
startX,
startY,
startW,
startH,
minW: state.minWidth,
minH: state.minHeight,
maxW,
maxH,
ar: state.aspectRatio,
edges: resizeEdges,
dx,
dy
});
state.x = result.x;
state.y = result.y;
state.width = result.w;
state.height = result.h;
applyDirect();
};
const onPointerUp = (e)=>{
if (e.pointerId !== capturedId) return;
mode = 'none';
capturedId = null;
state.dragging = false;
shell.style.cursor = '';
update();
if (!shell.matches(':hover')) scheduleHideControls();
};
shell.addEventListener('pointerdown', onPointerDown);
shell.addEventListener('pointermove', onPointerMove);
shell.addEventListener('pointerup', onPointerUp);
shell.addEventListener('pointercancel', onPointerUp);
teardownInteract = ()=>{
shell.removeEventListener('pointerdown', onPointerDown);
shell.removeEventListener('pointermove', onPointerMove);
shell.removeEventListener('pointerup', onPointerUp);
shell.removeEventListener('pointercancel', onPointerUp);
};
};
const bindHoverBehavior = ()=>{
if (!shellEl) return;
const onEnter = ()=>{
clearHideTimer();
showControls();
};
const onLeave = ()=>{
scheduleHideControls();
};
shellEl.addEventListener('mouseenter', onEnter);
shellEl.addEventListener('mouseleave', onLeave);
shellEl.addEventListener('focusin', onEnter);
shellEl.addEventListener('focusout', onLeave);
sourceCleanup.push(()=>{
shellEl?.removeEventListener('mouseenter', onEnter);
shellEl?.removeEventListener('mouseleave', onLeave);
shellEl?.removeEventListener('focusin', onEnter);
shellEl?.removeEventListener('focusout', onLeave);
});
};
const wireDomRefs = ()=>{
shellEl = host.querySelector('.floating-video-preview-shell');
previewVideoEl = host.querySelector('.floating-video-preview-video');
cropCanvasEl = host.querySelector('.floating-video-preview-canvas');
};
const update = ()=>{
if (destroyed) return;
(0, _litHtml.render)(template(), host);
wireDomRefs();
};
parent.appendChild(host);
update();
if (getSourceRect) startCropLoop();
else syncFromSource();
bindSourceEvents();
bindHoverBehavior();
setupInteract();
scheduleHideControls();
const resetAnchor = ()=>{
anchorX = 'left';
anchorY = 'top';
// Reset starting position tracking for the next modification session
startModX = null;
startModY = null;
};
return {
element: host,
destroy,
redraw: ()=>{
redraw();
},
closePopup,
getState: ()=>({
x: state.x,
y: state.y,
width: state.width,
height: state.height
}),
popOut,
isPopped: ()=>popupWindow !== null && !popupWindow.closed,
resetAnchor
};
}
},{"lit-html":"9fQBw","../util/previewGamma":"6oouq","../appState":"g0AlP","./preview-resize-math":"cpUon","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"cpUon":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
/**
* Computes new position and size when the source crop aspect ratio changes.
*
* By default, keeps the current preview **width** fixed and derives the new
* height from it: `newH = width / newAR`. This mirrors the most natural user
* interaction when dragging the top or bottom edge of the source crop:
*
* Source bottom dragged down (AR narrows / becomes taller):
* → preview height grows (width unchanged)
* Source bottom dragged up (AR widens / becomes shorter):
* → preview height shrinks (width unchanged)
*
* When `lockDimension: 'height'` is passed, keeps height fixed and derives
* width: `newW = height * newAR`. This mirrors dragging the left or right
* edge of the source crop:
*
* Source right dragged right (AR widens):
* → preview width grows (height unchanged)
* Source right dragged left (AR narrows):
* → preview width shrinks (height unchanged)
*
* Viewport clamping and minimum-size enforcement are applied after the ideal
* dimensions are computed, maintaining AR throughout.
*
* Anchor preference: top-left corner stays fixed and the preview grows /
* shrinks toward bottom-right. Falls back to the bottom-right anchor per axis
* only if the new size would overflow the viewport on that axis.
*/ parcelHelpers.export(exports, "computeARChange", ()=>computeARChange);
/**
* Computes a new position and size for an AR-locked resize drag.
*
* Uses the delta-propagation approach (mirroring Crop.ts): apply the primary
* delta, derive the secondary from the aspect ratio, propagate any secondary
* clamping back to the primary so AR is always maintained.
*
* Anchor rules (matching the original preview behaviour):
* left handle → right edge fixed, left moves
* right handle → left edge fixed, right moves
* top handle → bottom+right edges fixed, top+left move
* bottom handle → top+left edges fixed, bottom+right move
* corners → use width-primary; top flag still anchors the bottom edge
*/ parcelHelpers.export(exports, "computeARLockedResize", ()=>computeARLockedResize);
function computeARChange(p) {
const { x, y, width, height, minWidth, minHeight, newAR, viewportW, viewportH, lockDimension = 'width' } = p;
// Use provided anchor preferences, defaulting to left/top
const preferredAnchorX = p.anchorX ?? 'left';
const preferredAnchorY = p.anchorY ?? 'top';
const oldRight = x + width;
const oldBottom = y + height;
// Determine which dimension to keep fixed based on lockDimension
let newW;
let newH;
if (lockDimension === 'height') {
// Keep height fixed; derive width from the new AR.
newH = height;
newW = height * newAR;
} else {
// Keep width fixed; derive height from the new AR.
newW = width;
newH = width / newAR;
}
// Scale down to fit viewport while maintaining AR
if (newW > viewportW) {
newW = viewportW;
newH = newW / newAR;
}
if (newH > viewportH) {
newH = viewportH;
newW = newH * newAR;
}
// Enforce minimums while maintaining AR
if (newW < minWidth) {
newW = minWidth;
newH = newW / newAR;
}
if (newH < minHeight) {
newH = minHeight;
newW = newH * newAR;
}
const finalW = Math.round(newW);
const finalH = Math.round(newH);
// Apply anchor preferences:
// - 'left' anchor: keep left edge fixed, grow/shrink from right
// - 'right' anchor: keep right edge fixed, grow/shrink from left
// - 'top' anchor: keep top edge fixed, grow/shrink from bottom
// - 'bottom' anchor: keep bottom edge fixed, grow/shrink from top
let newX;
let newY;
let usedAnchorX;
let usedAnchorY;
// First, try the default (left/top) anchor to see if it works without overflow.
if (preferredAnchorX === 'right') {
// User previously had right anchor (from overflow).
// Keep using right anchor for the rest of the modification session.
// The anchor will be reset to 'left' when the user releases Ctrl (via resetAnchor).
newX = oldRight - finalW;
usedAnchorX = 'right';
} else {
// Keep left edge fixed
newX = x;
// Check if we need to fall back to right anchor due to overflow
if (newX + finalW > viewportW) {
newX = oldRight - finalW;
usedAnchorX = 'right';
} else usedAnchorX = 'left';
}
if (preferredAnchorY === 'bottom') {
// User previously had bottom anchor (from overflow).
// Keep using bottom anchor for the rest of the modification session.
// The anchor will be reset to 'top' when the user releases Ctrl (via resetAnchor).
newY = oldBottom - finalH;
usedAnchorY = 'bottom';
} else {
// Keep top edge fixed
newY = y;
// Check if we need to fall back to bottom anchor due to overflow
if (newY + finalH > viewportH) {
newY = oldBottom - finalH;
usedAnchorY = 'bottom';
} else usedAnchorY = 'top';
}
return {
x: Math.max(0, Math.min(newX, viewportW - finalW)),
y: Math.max(0, Math.min(newY, viewportH - finalH)),
width: finalW,
height: finalH,
anchorX: usedAnchorX,
anchorY: usedAnchorY
};
}
function computeARLockedResize(p) {
const { startX, startY, startW, startH, minW, minH, ar, edges, dx, dy } = p;
const maxW = p.maxW ?? Infinity;
const maxH = p.maxH ?? Infinity;
const { left, right, top, bottom } = edges;
const fixedRight = startX + startW;
const fixedBottom = startY + startH;
let dw;
let dh;
if (left || right) {
// ── Width-primary ────────────────────────────────────────────────────────
const rawDw = right ? dx : -dx;
// Clamp width within [minW, maxW].
dw = Math.min(Math.max(rawDw, minW - startW), maxW - startW);
// Derive height from AR.
dh = dw / ar;
// If height would drop below minH, clamp and propagate back.
if (startH + dh < minH) {
dh = minH - startH;
dw = dh * ar;
dw = Math.min(Math.max(dw, minW - startW), maxW - startW);
dh = dw / ar;
}
// If height would exceed maxH, clamp and propagate back.
if (startH + dh > maxH) {
dh = maxH - startH;
dw = dh * ar;
dw = Math.min(Math.max(dw, minW - startW), maxW - startW);
dh = dw / ar;
}
} else {
// ── Height-primary (pure top or bottom) ──────────────────────────────────
const rawDh = bottom ? dy : -dy;
// Clamp height within [minH, maxH].
dh = Math.min(Math.max(rawDh, minH - startH), maxH - startH);
// Derive width from AR.
dw = dh * ar;
// If width would drop below minW, clamp and propagate back.
if (startW + dw < minW) {
dw = minW - startW;
dh = dw / ar;
dh = Math.min(Math.max(dh, minH - startH), maxH - startH);
dw = dh * ar;
}
// If width would exceed maxW, clamp and propagate back.
if (startW + dw > maxW) {
dw = maxW - startW;
dh = dw / ar;
dh = Math.min(Math.max(dh, minH - startH), maxH - startH);
dw = dh * ar;
}
}
const newW = startW + dw;
const newH = startH + dh;
// ── Anchor / position ──────────────────────────────────────────────────────
// Right edge is fixed for: left handle, or pure-top handle (top && !right).
const anchorRight = left || top && !right;
const newX = anchorRight ? fixedRight - newW : startX;
const newY = top ? fixedBottom - newH : startY;
return {
x: Math.round(newX),
y: Math.round(newY),
w: Math.round(newW),
h: Math.round(newH)
};
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"93pN0":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "prevVideoWidth", ()=>prevVideoWidth);
parcelHelpers.export(exports, "getFPS", ()=>getFPS);
parcelHelpers.export(exports, "getFrameTimeBetweenLeftFrames", ()=>getFrameTimeBetweenLeftFrames);
parcelHelpers.export(exports, "hidePlayerControls", ()=>hidePlayerControls);
parcelHelpers.export(exports, "showPlayerControls", ()=>showPlayerControls);
parcelHelpers.export(exports, "addScrubVideoHandler", ()=>addScrubVideoHandler);
parcelHelpers.export(exports, "scrubVideoHandler", ()=>scrubVideoHandler);
parcelHelpers.export(exports, "blockVideoPause", ()=>blockVideoPause);
var _appState = require("../appState");
var _platforms = require("../platforms/platforms");
var _charts = require("../charts");
var _cropOverlay = require("../crop-overlay");
var _util = require("./util");
var _dragRecovery = require("./drag-recovery");
let prevVideoWidth;
function getFPS(defaultFPS = 60) {
let fps;
try {
if ((0, _appState.appState).videoInfo.fps != null && (0, _appState.appState).video.videoWidth != null && prevVideoWidth === (0, _appState.appState).video.videoWidth) fps = (0, _appState.appState).videoInfo.fps;
else if ((0, _platforms.getPlatform)() === (0, _platforms.VideoPlatforms).youtube) {
(0, _appState.appState).videoInfo.fps = parseFloat(/@(\d+)/.exec((0, _appState.appState).player.getStatsForNerds().resolution)?.[1] ?? '60');
fps = (0, _appState.appState).videoInfo.fps;
} else fps = defaultFPS ?? 60;
} catch (e) {
console.log('Could not detect fps', e);
fps = defaultFPS ?? 60; // by default parameter value assume high fps to avoid skipping frames
}
prevVideoWidth = (0, _appState.appState).video.videoWidth;
return fps;
}
function getFrameTimeBetweenLeftFrames(currentTime) {
const fps = getFPS();
const leftFrameIndex = Math.floor(currentTime * fps);
const midpointTime = (leftFrameIndex - 0.5) / fps;
return midpointTime;
}
function hidePlayerControls() {
(0, _appState.appState).hooks.controls.originalDisplay = (0, _appState.appState).hooks.controls.originalDisplay ?? (0, _appState.appState).hooks.controls.style.display;
(0, _appState.appState).hooks.controlsGradient.originalDisplay = (0, _appState.appState).hooks.controlsGradient.originalDisplay ?? (0, _appState.appState).hooks.controlsGradient.style.display;
(0, _appState.appState).hooks.controls.style.display = 'none';
(0, _appState.appState).hooks.controlsGradient.style.display = 'none';
}
function showPlayerControls() {
(0, _appState.appState).hooks.controls.style.display = (0, _appState.appState).hooks.controls.originalDisplay ?? '';
(0, _appState.appState).hooks.controlsGradient.style.display = (0, _appState.appState).hooks.controlsGradient.originalDisplay ?? '';
}
function addScrubVideoHandler() {
(0, _appState.appState).hooks.cropMouseManipulation.addEventListener('pointerdown', scrubVideoHandler, {
capture: true
});
}
function scrubVideoHandler(e) {
const isCropBlockingChartVisible = (0, _appState.appState).isCurrentChartVisible && (0, _charts.chartState).currentChartInput && (0, _charts.chartState).currentChartInput.type !== 'crop';
if (!e.ctrlKey && e.altKey && !e.shiftKey && !(0, _cropOverlay.isMouseManipulatingCrop) && !(0, _cropOverlay.isDrawingCrop) && !isCropBlockingChartVisible) {
(0, _util.blockEvent)(e);
document.addEventListener('click', blockVideoPause, {
once: true,
capture: true
});
const videoRect = (0, _appState.appState).video.getBoundingClientRect();
let prevClickPosX = e.clientX - videoRect.left;
const pointerId = e.pointerId;
(0, _appState.appState).video.setPointerCapture(pointerId);
// The captured element receives every subsequent pointer event for
// this pointer — pointermove, pointerup, and pointercancel — even when
// the cursor leaves the video region. Attaching listeners here rather
// than to `document` means the spec-mandated `pointercancel` reaches
// us when the browser implicitly releases capture (devtools focus,
// alt-tab, OS pointer reassignment), so the scrub never sticks.
const captureTarget = (0, _appState.appState).video;
let unregisterDragRecovery = ()=>{};
const baseWidth = 1920;
function dragHandler(e) {
(0, _util.blockEvent)(e);
const pixelRatio = window.devicePixelRatio;
const widthMultiple = baseWidth / screen.width;
const dragPosX = e.clientX - videoRect.left;
const changeX = (dragPosX - prevClickPosX) * pixelRatio * widthMultiple;
const seekBy = changeX * (1 / (0, _appState.appState).videoInfo.fps);
(0, _util.seekBySafe)((0, _appState.appState).video, seekBy);
prevClickPosX = e.clientX - videoRect.left;
}
function cleanup() {
captureTarget.removeEventListener('pointermove', dragHandler);
captureTarget.removeEventListener('pointerup', endDragHandler, {
capture: true
});
captureTarget.removeEventListener('pointercancel', cancelDragHandler, {
capture: true
});
if (captureTarget.hasPointerCapture(pointerId)) captureTarget.releasePointerCapture(pointerId);
unregisterDragRecovery();
}
function endDragHandler(e) {
(0, _util.blockEvent)(e);
cleanup();
}
function cancelDragHandler() {
cleanup();
}
captureTarget.addEventListener('pointermove', dragHandler);
captureTarget.addEventListener('pointerup', endDragHandler, {
once: true,
capture: true
});
captureTarget.addEventListener('pointercancel', cancelDragHandler, {
once: true,
capture: true
});
unregisterDragRecovery = (0, _dragRecovery.registerActiveDragCleanup)(cleanup);
}
}
function blockVideoPause(e) {
e.stopImmediatePropagation();
}
},{"../appState":"g0AlP","../platforms/platforms":"1kR7r","../charts":"hBxwj","../crop-overlay":"6s727","./util":"99arg","./drag-recovery":"3BYSh","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"3BYSh":[function(require,module,exports,__globalThis) {
/**
* Safety net for mouse-based drag handlers that need a guaranteed cleanup
* path even when the browser silently drops their `pointerup`.
*
* Each drag handler in the markup userscript follows the same shape:
* 1. On `pointerdown`, capture the pointer and attach listeners that track
* `pointermove` / `pointerup` / `pointercancel`.
* 2. On end, release the capture and detach the listeners.
*
* Layers 1+2 (captured-target listeners + `pointercancel`) handle the vast
* majority of edge cases the Pointer Events spec describes — devtools
* stealing focus, touch interruption, etc. But there is a residual class of
* situations where neither `pointerup` nor `pointercancel` fires for our
* captured target: the user alt-tabs away while mid-drag, the OS reassigns
* pointer ownership, or the tab is hidden (`document.hidden`) before the
* pointer is released. This module is the belt-and-suspenders fallback that
* fires the registered cleanups on those window-level signals.
*
* Usage:
*
* const unregister = registerActiveDragCleanup(() => {
* // restore module state, remove leftover listeners, release capture
* });
* // ...later, on normal pointerup:
* unregister();
*
* The cleanup must be idempotent — it may be invoked by either the normal
* end path or the recovery fallback, but not both for the same drag (the
* unregister call removes it from the registry).
*/ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
/** Iterates a snapshot of registered cleanups and fires each. Snapshotting
* first lets cleanups remove themselves from the registry (via their
* `unregister` closure) without mutating the set we're iterating. */ parcelHelpers.export(exports, "releaseAllActiveDrags", ()=>releaseAllActiveDrags);
parcelHelpers.export(exports, "registerActiveDragCleanup", ()=>registerActiveDragCleanup);
/** Test-only: clears registry state so unit tests start clean. Don't call
* from production code paths — drag handlers should always own their own
* unregister closure. */ parcelHelpers.export(exports, "_resetDragRecoveryForTests", ()=>_resetDragRecoveryForTests);
const activeCleanups = new Set();
let listenersAttached = false;
function ensureGlobalListeners() {
if (listenersAttached) return;
listenersAttached = true;
window.addEventListener('blur', releaseAllActiveDrags);
document.addEventListener('visibilitychange', ()=>{
if (document.hidden) releaseAllActiveDrags();
});
}
function releaseAllActiveDrags() {
const snapshot = Array.from(activeCleanups);
activeCleanups.clear();
for (const cleanup of snapshot)try {
cleanup();
} catch (err) {
// A buggy cleanup must not block the others. Surface it on the
// console rather than letting it tear down the recovery loop.
console.warn('[drag-recovery] cleanup threw', err);
}
}
function registerActiveDragCleanup(cleanup) {
ensureGlobalListeners();
activeCleanups.add(cleanup);
return ()=>{
activeCleanups.delete(cleanup);
};
}
function _resetDragRecoveryForTests() {
activeCleanups.clear();
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"ioJZr":[function(require,module,exports,__globalThis) {
/**
* Lodash (Custom Build) <https://lodash.com/>
* Build: `lodash modularize exports="npm" -o ./`
* Copyright JS Foundation and other contributors <https://js.foundation/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/ /** Used as the size to enable large array optimizations. */ var global = arguments[3];
var LARGE_ARRAY_SIZE = 200;
/** Used to stand-in for `undefined` hash values. */ var HASH_UNDEFINED = '__lodash_hash_undefined__';
/** Used to compose bitmasks for value comparisons. */ var COMPARE_PARTIAL_FLAG = 1, COMPARE_UNORDERED_FLAG = 2;
/** Used as references for various `Number` constants. */ var MAX_SAFE_INTEGER = 9007199254740991;
/** `Object#toString` result references. */ var argsTag = '[object Arguments]', arrayTag = '[object Array]', asyncTag = '[object AsyncFunction]', boolTag = '[object Boolean]', dateTag = '[object Date]', errorTag = '[object Error]', funcTag = '[object Function]', genTag = '[object GeneratorFunction]', mapTag = '[object Map]', numberTag = '[object Number]', nullTag = '[object Null]', objectTag = '[object Object]', promiseTag = '[object Promise]', proxyTag = '[object Proxy]', regexpTag = '[object RegExp]', setTag = '[object Set]', stringTag = '[object String]', symbolTag = '[object Symbol]', undefinedTag = '[object Undefined]', weakMapTag = '[object WeakMap]';
var arrayBufferTag = '[object ArrayBuffer]', dataViewTag = '[object DataView]', float32Tag = '[object Float32Array]', float64Tag = '[object Float64Array]', int8Tag = '[object Int8Array]', int16Tag = '[object Int16Array]', int32Tag = '[object Int32Array]', uint8Tag = '[object Uint8Array]', uint8ClampedTag = '[object Uint8ClampedArray]', uint16Tag = '[object Uint16Array]', uint32Tag = '[object Uint32Array]';
/**
* Used to match `RegExp`
* [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
*/ var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
/** Used to detect host constructors (Safari). */ var reIsHostCtor = /^\[object .+?Constructor\]$/;
/** Used to detect unsigned integer values. */ var reIsUint = /^(?:0|[1-9]\d*)$/;
/** Used to identify `toStringTag` values of typed arrays. */ var typedArrayTags = {};
typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = typedArrayTags[uint32Tag] = true;
typedArrayTags[argsTag] = typedArrayTags[arrayTag] = typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = typedArrayTags[errorTag] = typedArrayTags[funcTag] = typedArrayTags[mapTag] = typedArrayTags[numberTag] = typedArrayTags[objectTag] = typedArrayTags[regexpTag] = typedArrayTags[setTag] = typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
/** Detect free variable `global` from Node.js. */ var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
/** Detect free variable `self`. */ var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
/** Used as a reference to the global object. */ var root = freeGlobal || freeSelf || Function('return this')();
/** Detect free variable `exports`. */ var freeExports = exports && !exports.nodeType && exports;
/** Detect free variable `module`. */ var freeModule = freeExports && true && module && !module.nodeType && module;
/** Detect the popular CommonJS extension `module.exports`. */ var moduleExports = freeModule && freeModule.exports === freeExports;
/** Detect free variable `process` from Node.js. */ var freeProcess = moduleExports && freeGlobal.process;
/** Used to access faster Node.js helpers. */ var nodeUtil = function() {
try {
return freeProcess && freeProcess.binding && freeProcess.binding('util');
} catch (e) {}
}();
/* Node.js helper references. */ var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
/**
* A specialized version of `_.filter` for arrays without support for
* iteratee shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} predicate The function invoked per iteration.
* @returns {Array} Returns the new filtered array.
*/ function arrayFilter(array, predicate) {
var index = -1, length = array == null ? 0 : array.length, resIndex = 0, result = [];
while(++index < length){
var value = array[index];
if (predicate(value, index, array)) result[resIndex++] = value;
}
return result;
}
/**
* Appends the elements of `values` to `array`.
*
* @private
* @param {Array} array The array to modify.
* @param {Array} values The values to append.
* @returns {Array} Returns `array`.
*/ function arrayPush(array, values) {
var index = -1, length = values.length, offset = array.length;
while(++index < length)array[offset + index] = values[index];
return array;
}
/**
* A specialized version of `_.some` for arrays without support for iteratee
* shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} predicate The function invoked per iteration.
* @returns {boolean} Returns `true` if any element passes the predicate check,
* else `false`.
*/ function arraySome(array, predicate) {
var index = -1, length = array == null ? 0 : array.length;
while(++index < length){
if (predicate(array[index], index, array)) return true;
}
return false;
}
/**
* The base implementation of `_.times` without support for iteratee shorthands
* or max array length checks.
*
* @private
* @param {number} n The number of times to invoke `iteratee`.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the array of results.
*/ function baseTimes(n, iteratee) {
var index = -1, result = Array(n);
while(++index < n)result[index] = iteratee(index);
return result;
}
/**
* The base implementation of `_.unary` without support for storing metadata.
*
* @private
* @param {Function} func The function to cap arguments for.
* @returns {Function} Returns the new capped function.
*/ function baseUnary(func) {
return function(value) {
return func(value);
};
}
/**
* Checks if a `cache` value for `key` exists.
*
* @private
* @param {Object} cache The cache to query.
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ function cacheHas(cache, key) {
return cache.has(key);
}
/**
* Gets the value at `key` of `object`.
*
* @private
* @param {Object} [object] The object to query.
* @param {string} key The key of the property to get.
* @returns {*} Returns the property value.
*/ function getValue(object, key) {
return object == null ? undefined : object[key];
}
/**
* Converts `map` to its key-value pairs.
*
* @private
* @param {Object} map The map to convert.
* @returns {Array} Returns the key-value pairs.
*/ function mapToArray(map) {
var index = -1, result = Array(map.size);
map.forEach(function(value, key) {
result[++index] = [
key,
value
];
});
return result;
}
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/ function overArg(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
/**
* Converts `set` to an array of its values.
*
* @private
* @param {Object} set The set to convert.
* @returns {Array} Returns the values.
*/ function setToArray(set) {
var index = -1, result = Array(set.size);
set.forEach(function(value) {
result[++index] = value;
});
return result;
}
/** Used for built-in method references. */ var arrayProto = Array.prototype, funcProto = Function.prototype, objectProto = Object.prototype;
/** Used to detect overreaching core-js shims. */ var coreJsData = root['__core-js_shared__'];
/** Used to resolve the decompiled source of functions. */ var funcToString = funcProto.toString;
/** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty;
/** Used to detect methods masquerading as native. */ var maskSrcKey = function() {
var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
return uid ? 'Symbol(src)_1.' + uid : '';
}();
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/ var nativeObjectToString = objectProto.toString;
/** Used to detect if a method is native. */ var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$');
/** Built-in value references. */ var Buffer = moduleExports ? root.Buffer : undefined, Symbol = root.Symbol, Uint8Array = root.Uint8Array, propertyIsEnumerable = objectProto.propertyIsEnumerable, splice = arrayProto.splice, symToStringTag = Symbol ? Symbol.toStringTag : undefined;
/* Built-in method references for those with the same name as other `lodash` methods. */ var nativeGetSymbols = Object.getOwnPropertySymbols, nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, nativeKeys = overArg(Object.keys, Object);
/* Built-in method references that are verified to be native. */ var DataView = getNative(root, 'DataView'), Map = getNative(root, 'Map'), Promise = getNative(root, 'Promise'), Set = getNative(root, 'Set'), WeakMap = getNative(root, 'WeakMap'), nativeCreate = getNative(Object, 'create');
/** Used to detect maps, sets, and weakmaps. */ var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map), promiseCtorString = toSource(Promise), setCtorString = toSource(Set), weakMapCtorString = toSource(WeakMap);
/** Used to convert symbols to primitives and strings. */ var symbolProto = Symbol ? Symbol.prototype : undefined, symbolValueOf = symbolProto ? symbolProto.valueOf : undefined;
/**
* Creates a hash object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/ function Hash(entries) {
var index = -1, length = entries == null ? 0 : entries.length;
this.clear();
while(++index < length){
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the hash.
*
* @private
* @name clear
* @memberOf Hash
*/ function hashClear() {
this.__data__ = nativeCreate ? nativeCreate(null) : {};
this.size = 0;
}
/**
* Removes `key` and its value from the hash.
*
* @private
* @name delete
* @memberOf Hash
* @param {Object} hash The hash to modify.
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ function hashDelete(key) {
var result = this.has(key) && delete this.__data__[key];
this.size -= result ? 1 : 0;
return result;
}
/**
* Gets the hash value for `key`.
*
* @private
* @name get
* @memberOf Hash
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/ function hashGet(key) {
var data = this.__data__;
if (nativeCreate) {
var result = data[key];
return result === HASH_UNDEFINED ? undefined : result;
}
return hasOwnProperty.call(data, key) ? data[key] : undefined;
}
/**
* Checks if a hash value for `key` exists.
*
* @private
* @name has
* @memberOf Hash
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ function hashHas(key) {
var data = this.__data__;
return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
}
/**
* Sets the hash `key` to `value`.
*
* @private
* @name set
* @memberOf Hash
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the hash instance.
*/ function hashSet(key, value) {
var data = this.__data__;
this.size += this.has(key) ? 0 : 1;
data[key] = nativeCreate && value === undefined ? HASH_UNDEFINED : value;
return this;
}
// Add methods to `Hash`.
Hash.prototype.clear = hashClear;
Hash.prototype['delete'] = hashDelete;
Hash.prototype.get = hashGet;
Hash.prototype.has = hashHas;
Hash.prototype.set = hashSet;
/**
* Creates an list cache object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/ function ListCache(entries) {
var index = -1, length = entries == null ? 0 : entries.length;
this.clear();
while(++index < length){
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the list cache.
*
* @private
* @name clear
* @memberOf ListCache
*/ function listCacheClear() {
this.__data__ = [];
this.size = 0;
}
/**
* Removes `key` and its value from the list cache.
*
* @private
* @name delete
* @memberOf ListCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ function listCacheDelete(key) {
var data = this.__data__, index = assocIndexOf(data, key);
if (index < 0) return false;
var lastIndex = data.length - 1;
if (index == lastIndex) data.pop();
else splice.call(data, index, 1);
--this.size;
return true;
}
/**
* Gets the list cache value for `key`.
*
* @private
* @name get
* @memberOf ListCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/ function listCacheGet(key) {
var data = this.__data__, index = assocIndexOf(data, key);
return index < 0 ? undefined : data[index][1];
}
/**
* Checks if a list cache value for `key` exists.
*
* @private
* @name has
* @memberOf ListCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ function listCacheHas(key) {
return assocIndexOf(this.__data__, key) > -1;
}
/**
* Sets the list cache `key` to `value`.
*
* @private
* @name set
* @memberOf ListCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the list cache instance.
*/ function listCacheSet(key, value) {
var data = this.__data__, index = assocIndexOf(data, key);
if (index < 0) {
++this.size;
data.push([
key,
value
]);
} else data[index][1] = value;
return this;
}
// Add methods to `ListCache`.
ListCache.prototype.clear = listCacheClear;
ListCache.prototype['delete'] = listCacheDelete;
ListCache.prototype.get = listCacheGet;
ListCache.prototype.has = listCacheHas;
ListCache.prototype.set = listCacheSet;
/**
* Creates a map cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/ function MapCache(entries) {
var index = -1, length = entries == null ? 0 : entries.length;
this.clear();
while(++index < length){
var entry = entries[index];
this.set(entry[0], entry[1]);
}
}
/**
* Removes all key-value entries from the map.
*
* @private
* @name clear
* @memberOf MapCache
*/ function mapCacheClear() {
this.size = 0;
this.__data__ = {
'hash': new Hash,
'map': new (Map || ListCache),
'string': new Hash
};
}
/**
* Removes `key` and its value from the map.
*
* @private
* @name delete
* @memberOf MapCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ function mapCacheDelete(key) {
var result = getMapData(this, key)['delete'](key);
this.size -= result ? 1 : 0;
return result;
}
/**
* Gets the map value for `key`.
*
* @private
* @name get
* @memberOf MapCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/ function mapCacheGet(key) {
return getMapData(this, key).get(key);
}
/**
* Checks if a map value for `key` exists.
*
* @private
* @name has
* @memberOf MapCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ function mapCacheHas(key) {
return getMapData(this, key).has(key);
}
/**
* Sets the map `key` to `value`.
*
* @private
* @name set
* @memberOf MapCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the map cache instance.
*/ function mapCacheSet(key, value) {
var data = getMapData(this, key), size = data.size;
data.set(key, value);
this.size += data.size == size ? 0 : 1;
return this;
}
// Add methods to `MapCache`.
MapCache.prototype.clear = mapCacheClear;
MapCache.prototype['delete'] = mapCacheDelete;
MapCache.prototype.get = mapCacheGet;
MapCache.prototype.has = mapCacheHas;
MapCache.prototype.set = mapCacheSet;
/**
*
* Creates an array cache object to store unique values.
*
* @private
* @constructor
* @param {Array} [values] The values to cache.
*/ function SetCache(values) {
var index = -1, length = values == null ? 0 : values.length;
this.__data__ = new MapCache;
while(++index < length)this.add(values[index]);
}
/**
* Adds `value` to the array cache.
*
* @private
* @name add
* @memberOf SetCache
* @alias push
* @param {*} value The value to cache.
* @returns {Object} Returns the cache instance.
*/ function setCacheAdd(value) {
this.__data__.set(value, HASH_UNDEFINED);
return this;
}
/**
* Checks if `value` is in the array cache.
*
* @private
* @name has
* @memberOf SetCache
* @param {*} value The value to search for.
* @returns {number} Returns `true` if `value` is found, else `false`.
*/ function setCacheHas(value) {
return this.__data__.has(value);
}
// Add methods to `SetCache`.
SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
SetCache.prototype.has = setCacheHas;
/**
* Creates a stack cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/ function Stack(entries) {
var data = this.__data__ = new ListCache(entries);
this.size = data.size;
}
/**
* Removes all key-value entries from the stack.
*
* @private
* @name clear
* @memberOf Stack
*/ function stackClear() {
this.__data__ = new ListCache;
this.size = 0;
}
/**
* Removes `key` and its value from the stack.
*
* @private
* @name delete
* @memberOf Stack
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/ function stackDelete(key) {
var data = this.__data__, result = data['delete'](key);
this.size = data.size;
return result;
}
/**
* Gets the stack value for `key`.
*
* @private
* @name get
* @memberOf Stack
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/ function stackGet(key) {
return this.__data__.get(key);
}
/**
* Checks if a stack value for `key` exists.
*
* @private
* @name has
* @memberOf Stack
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/ function stackHas(key) {
return this.__data__.has(key);
}
/**
* Sets the stack `key` to `value`.
*
* @private
* @name set
* @memberOf Stack
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the stack cache instance.
*/ function stackSet(key, value) {
var data = this.__data__;
if (data instanceof ListCache) {
var pairs = data.__data__;
if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) {
pairs.push([
key,
value
]);
this.size = ++data.size;
return this;
}
data = this.__data__ = new MapCache(pairs);
}
data.set(key, value);
this.size = data.size;
return this;
}
// Add methods to `Stack`.
Stack.prototype.clear = stackClear;
Stack.prototype['delete'] = stackDelete;
Stack.prototype.get = stackGet;
Stack.prototype.has = stackHas;
Stack.prototype.set = stackSet;
/**
* Creates an array of the enumerable property names of the array-like `value`.
*
* @private
* @param {*} value The value to query.
* @param {boolean} inherited Specify returning inherited property names.
* @returns {Array} Returns the array of property names.
*/ function arrayLikeKeys(value, inherited) {
var isArr = isArray(value), isArg = !isArr && isArguments(value), isBuff = !isArr && !isArg && isBuffer(value), isType = !isArr && !isArg && !isBuff && isTypedArray(value), skipIndexes = isArr || isArg || isBuff || isType, result = skipIndexes ? baseTimes(value.length, String) : [], length = result.length;
for(var key in value)if ((inherited || hasOwnProperty.call(value, key)) && !(skipIndexes && // Safari 9 has enumerable `arguments.length` in strict mode.
(key == 'length' || // Node.js 0.10 has enumerable non-index properties on buffers.
isBuff && (key == 'offset' || key == 'parent') || // PhantomJS 2 has enumerable non-index properties on typed arrays.
isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset') || // Skip index properties.
isIndex(key, length)))) result.push(key);
return result;
}
/**
* Gets the index at which the `key` is found in `array` of key-value pairs.
*
* @private
* @param {Array} array The array to inspect.
* @param {*} key The key to search for.
* @returns {number} Returns the index of the matched value, else `-1`.
*/ function assocIndexOf(array, key) {
var length = array.length;
while(length--){
if (eq(array[length][0], key)) return length;
}
return -1;
}
/**
* The base implementation of `getAllKeys` and `getAllKeysIn` which uses
* `keysFunc` and `symbolsFunc` to get the enumerable property names and
* symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {Function} keysFunc The function to get the keys of `object`.
* @param {Function} symbolsFunc The function to get the symbols of `object`.
* @returns {Array} Returns the array of property names and symbols.
*/ function baseGetAllKeys(object, keysFunc, symbolsFunc) {
var result = keysFunc(object);
return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
}
/**
* The base implementation of `getTag` without fallbacks for buggy environments.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/ function baseGetTag(value) {
if (value == null) return value === undefined ? undefinedTag : nullTag;
return symToStringTag && symToStringTag in Object(value) ? getRawTag(value) : objectToString(value);
}
/**
* The base implementation of `_.isArguments`.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
*/ function baseIsArguments(value) {
return isObjectLike(value) && baseGetTag(value) == argsTag;
}
/**
* The base implementation of `_.isEqual` which supports partial comparisons
* and tracks traversed objects.
*
* @private
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @param {boolean} bitmask The bitmask flags.
* 1 - Unordered comparison
* 2 - Partial comparison
* @param {Function} [customizer] The function to customize comparisons.
* @param {Object} [stack] Tracks traversed `value` and `other` objects.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
*/ function baseIsEqual(value, other, bitmask, customizer, stack) {
if (value === other) return true;
if (value == null || other == null || !isObjectLike(value) && !isObjectLike(other)) return value !== value && other !== other;
return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
}
/**
* A specialized version of `baseIsEqual` for arrays and objects which performs
* deep comparisons and tracks traversed objects enabling objects with circular
* references to be compared.
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} [stack] Tracks traversed `object` and `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/ function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
var objIsArr = isArray(object), othIsArr = isArray(other), objTag = objIsArr ? arrayTag : getTag(object), othTag = othIsArr ? arrayTag : getTag(other);
objTag = objTag == argsTag ? objectTag : objTag;
othTag = othTag == argsTag ? objectTag : othTag;
var objIsObj = objTag == objectTag, othIsObj = othTag == objectTag, isSameTag = objTag == othTag;
if (isSameTag && isBuffer(object)) {
if (!isBuffer(other)) return false;
objIsArr = true;
objIsObj = false;
}
if (isSameTag && !objIsObj) {
stack || (stack = new Stack);
return objIsArr || isTypedArray(object) ? equalArrays(object, other, bitmask, customizer, equalFunc, stack) : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
}
if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
if (objIsWrapped || othIsWrapped) {
var objUnwrapped = objIsWrapped ? object.value() : object, othUnwrapped = othIsWrapped ? other.value() : other;
stack || (stack = new Stack);
return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
}
}
if (!isSameTag) return false;
stack || (stack = new Stack);
return equalObjects(object, other, bitmask, customizer, equalFunc, stack);
}
/**
* The base implementation of `_.isNative` without bad shim checks.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a native function,
* else `false`.
*/ function baseIsNative(value) {
if (!isObject(value) || isMasked(value)) return false;
var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
return pattern.test(toSource(value));
}
/**
* The base implementation of `_.isTypedArray` without Node.js optimizations.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
*/ function baseIsTypedArray(value) {
return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
}
/**
* The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/ function baseKeys(object) {
if (!isPrototype(object)) return nativeKeys(object);
var result = [];
for(var key in Object(object))if (hasOwnProperty.call(object, key) && key != 'constructor') result.push(key);
return result;
}
/**
* A specialized version of `baseIsEqualDeep` for arrays with support for
* partial deep comparisons.
*
* @private
* @param {Array} array The array to compare.
* @param {Array} other The other array to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} stack Tracks traversed `array` and `other` objects.
* @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
*/ function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
var isPartial = bitmask & COMPARE_PARTIAL_FLAG, arrLength = array.length, othLength = other.length;
if (arrLength != othLength && !(isPartial && othLength > arrLength)) return false;
// Assume cyclic values are equal.
var stacked = stack.get(array);
if (stacked && stack.get(other)) return stacked == other;
var index = -1, result = true, seen = bitmask & COMPARE_UNORDERED_FLAG ? new SetCache : undefined;
stack.set(array, other);
stack.set(other, array);
// Ignore non-index properties.
while(++index < arrLength){
var arrValue = array[index], othValue = other[index];
if (customizer) var compared = isPartial ? customizer(othValue, arrValue, index, other, array, stack) : customizer(arrValue, othValue, index, array, other, stack);
if (compared !== undefined) {
if (compared) continue;
result = false;
break;
}
// Recursively compare arrays (susceptible to call stack limits).
if (seen) {
if (!arraySome(other, function(othValue, othIndex) {
if (!cacheHas(seen, othIndex) && (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) return seen.push(othIndex);
})) {
result = false;
break;
}
} else if (!(arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
result = false;
break;
}
}
stack['delete'](array);
stack['delete'](other);
return result;
}
/**
* A specialized version of `baseIsEqualDeep` for comparing objects of
* the same `toStringTag`.
*
* **Note:** This function only supports comparing values with tags of
* `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @param {string} tag The `toStringTag` of the objects to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} stack Tracks traversed `object` and `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/ function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
switch(tag){
case dataViewTag:
if (object.byteLength != other.byteLength || object.byteOffset != other.byteOffset) return false;
object = object.buffer;
other = other.buffer;
case arrayBufferTag:
if (object.byteLength != other.byteLength || !equalFunc(new Uint8Array(object), new Uint8Array(other))) return false;
return true;
case boolTag:
case dateTag:
case numberTag:
// Coerce booleans to `1` or `0` and dates to milliseconds.
// Invalid dates are coerced to `NaN`.
return eq(+object, +other);
case errorTag:
return object.name == other.name && object.message == other.message;
case regexpTag:
case stringTag:
// Coerce regexes to strings and treat strings, primitives and objects,
// as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
// for more details.
return object == other + '';
case mapTag:
var convert = mapToArray;
case setTag:
var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
convert || (convert = setToArray);
if (object.size != other.size && !isPartial) return false;
// Assume cyclic values are equal.
var stacked = stack.get(object);
if (stacked) return stacked == other;
bitmask |= COMPARE_UNORDERED_FLAG;
// Recursively compare objects (susceptible to call stack limits).
stack.set(object, other);
var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
stack['delete'](object);
return result;
case symbolTag:
if (symbolValueOf) return symbolValueOf.call(object) == symbolValueOf.call(other);
}
return false;
}
/**
* A specialized version of `baseIsEqualDeep` for objects with support for
* partial deep comparisons.
*
* @private
* @param {Object} object The object to compare.
* @param {Object} other The other object to compare.
* @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
* @param {Function} customizer The function to customize comparisons.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Object} stack Tracks traversed `object` and `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/ function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
var isPartial = bitmask & COMPARE_PARTIAL_FLAG, objProps = getAllKeys(object), objLength = objProps.length, othProps = getAllKeys(other), othLength = othProps.length;
if (objLength != othLength && !isPartial) return false;
var index = objLength;
while(index--){
var key = objProps[index];
if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) return false;
}
// Assume cyclic values are equal.
var stacked = stack.get(object);
if (stacked && stack.get(other)) return stacked == other;
var result = true;
stack.set(object, other);
stack.set(other, object);
var skipCtor = isPartial;
while(++index < objLength){
key = objProps[index];
var objValue = object[key], othValue = other[key];
if (customizer) var compared = isPartial ? customizer(othValue, objValue, key, other, object, stack) : customizer(objValue, othValue, key, object, other, stack);
// Recursively compare objects (susceptible to call stack limits).
if (!(compared === undefined ? objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack) : compared)) {
result = false;
break;
}
skipCtor || (skipCtor = key == 'constructor');
}
if (result && !skipCtor) {
var objCtor = object.constructor, othCtor = other.constructor;
// Non `Object` object instances with different constructors are not equal.
if (objCtor != othCtor && 'constructor' in object && 'constructor' in other && !(typeof objCtor == 'function' && objCtor instanceof objCtor && typeof othCtor == 'function' && othCtor instanceof othCtor)) result = false;
}
stack['delete'](object);
stack['delete'](other);
return result;
}
/**
* Creates an array of own enumerable property names and symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names and symbols.
*/ function getAllKeys(object) {
return baseGetAllKeys(object, keys, getSymbols);
}
/**
* Gets the data for `map`.
*
* @private
* @param {Object} map The map to query.
* @param {string} key The reference key.
* @returns {*} Returns the map data.
*/ function getMapData(map, key) {
var data = map.__data__;
return isKeyable(key) ? data[typeof key == 'string' ? 'string' : 'hash'] : data.map;
}
/**
* Gets the native function at `key` of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {string} key The key of the method to get.
* @returns {*} Returns the function if it's native, else `undefined`.
*/ function getNative(object, key) {
var value = getValue(object, key);
return baseIsNative(value) ? value : undefined;
}
/**
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the raw `toStringTag`.
*/ function getRawTag(value) {
var isOwn = hasOwnProperty.call(value, symToStringTag), tag = value[symToStringTag];
try {
value[symToStringTag] = undefined;
var unmasked = true;
} catch (e) {}
var result = nativeObjectToString.call(value);
if (unmasked) {
if (isOwn) value[symToStringTag] = tag;
else delete value[symToStringTag];
}
return result;
}
/**
* Creates an array of the own enumerable symbols of `object`.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of symbols.
*/ var getSymbols = !nativeGetSymbols ? stubArray : function(object) {
if (object == null) return [];
object = Object(object);
return arrayFilter(nativeGetSymbols(object), function(symbol) {
return propertyIsEnumerable.call(object, symbol);
});
};
/**
* Gets the `toStringTag` of `value`.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/ var getTag = baseGetTag;
// Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map && getTag(new Map) != mapTag || Promise && getTag(Promise.resolve()) != promiseTag || Set && getTag(new Set) != setTag || WeakMap && getTag(new WeakMap) != weakMapTag) getTag = function(value) {
var result = baseGetTag(value), Ctor = result == objectTag ? value.constructor : undefined, ctorString = Ctor ? toSource(Ctor) : '';
if (ctorString) switch(ctorString){
case dataViewCtorString:
return dataViewTag;
case mapCtorString:
return mapTag;
case promiseCtorString:
return promiseTag;
case setCtorString:
return setTag;
case weakMapCtorString:
return weakMapTag;
}
return result;
};
/**
* Checks if `value` is a valid array-like index.
*
* @private
* @param {*} value The value to check.
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
*/ function isIndex(value, length) {
length = length == null ? MAX_SAFE_INTEGER : length;
return !!length && (typeof value == 'number' || reIsUint.test(value)) && value > -1 && value % 1 == 0 && value < length;
}
/**
* Checks if `value` is suitable for use as unique object key.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is suitable, else `false`.
*/ function isKeyable(value) {
var type = typeof value;
return type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean' ? value !== '__proto__' : value === null;
}
/**
* Checks if `func` has its source masked.
*
* @private
* @param {Function} func The function to check.
* @returns {boolean} Returns `true` if `func` is masked, else `false`.
*/ function isMasked(func) {
return !!maskSrcKey && maskSrcKey in func;
}
/**
* Checks if `value` is likely a prototype object.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
*/ function isPrototype(value) {
var Ctor = value && value.constructor, proto = typeof Ctor == 'function' && Ctor.prototype || objectProto;
return value === proto;
}
/**
* Converts `value` to a string using `Object.prototype.toString`.
*
* @private
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
*/ function objectToString(value) {
return nativeObjectToString.call(value);
}
/**
* Converts `func` to its source code.
*
* @private
* @param {Function} func The function to convert.
* @returns {string} Returns the source code.
*/ function toSource(func) {
if (func != null) {
try {
return funcToString.call(func);
} catch (e) {}
try {
return func + '';
} catch (e) {}
}
return '';
}
/**
* Performs a
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* comparison between two values to determine if they are equivalent.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* var object = { 'a': 1 };
* var other = { 'a': 1 };
*
* _.eq(object, object);
* // => true
*
* _.eq(object, other);
* // => false
*
* _.eq('a', 'a');
* // => true
*
* _.eq('a', Object('a'));
* // => false
*
* _.eq(NaN, NaN);
* // => true
*/ function eq(value, other) {
return value === other || value !== value && other !== other;
}
/**
* Checks if `value` is likely an `arguments` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
* else `false`.
* @example
*
* _.isArguments(function() { return arguments; }());
* // => true
*
* _.isArguments([1, 2, 3]);
* // => false
*/ var isArguments = baseIsArguments(function() {
return arguments;
}()) ? baseIsArguments : function(value) {
return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
};
/**
* Checks if `value` is classified as an `Array` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array, else `false`.
* @example
*
* _.isArray([1, 2, 3]);
* // => true
*
* _.isArray(document.body.children);
* // => false
*
* _.isArray('abc');
* // => false
*
* _.isArray(_.noop);
* // => false
*/ var isArray = Array.isArray;
/**
* Checks if `value` is array-like. A value is considered array-like if it's
* not a function and has a `value.length` that's an integer greater than or
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
* @example
*
* _.isArrayLike([1, 2, 3]);
* // => true
*
* _.isArrayLike(document.body.children);
* // => true
*
* _.isArrayLike('abc');
* // => true
*
* _.isArrayLike(_.noop);
* // => false
*/ function isArrayLike(value) {
return value != null && isLength(value.length) && !isFunction(value);
}
/**
* Checks if `value` is a buffer.
*
* @static
* @memberOf _
* @since 4.3.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
* @example
*
* _.isBuffer(new Buffer(2));
* // => true
*
* _.isBuffer(new Uint8Array(2));
* // => false
*/ var isBuffer = nativeIsBuffer || stubFalse;
/**
* Performs a deep comparison between two values to determine if they are
* equivalent.
*
* **Note:** This method supports comparing arrays, array buffers, booleans,
* date objects, error objects, maps, numbers, `Object` objects, regexes,
* sets, strings, symbols, and typed arrays. `Object` objects are compared
* by their own, not inherited, enumerable properties. Functions and DOM
* nodes are compared by strict equality, i.e. `===`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* var object = { 'a': 1 };
* var other = { 'a': 1 };
*
* _.isEqual(object, other);
* // => true
*
* object === other;
* // => false
*/ function isEqual(value, other) {
return baseIsEqual(value, other);
}
/**
* Checks if `value` is classified as a `Function` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
* @example
*
* _.isFunction(_);
* // => true
*
* _.isFunction(/abc/);
* // => false
*/ function isFunction(value) {
if (!isObject(value)) return false;
// The use of `Object#toString` avoids issues with the `typeof` operator
// in Safari 9 which returns 'object' for typed arrays and other constructors.
var tag = baseGetTag(value);
return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
}
/**
* Checks if `value` is a valid array-like length.
*
* **Note:** This method is loosely based on
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
* @example
*
* _.isLength(3);
* // => true
*
* _.isLength(Number.MIN_VALUE);
* // => false
*
* _.isLength(Infinity);
* // => false
*
* _.isLength('3');
* // => false
*/ function isLength(value) {
return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/ function isObject(value) {
var type = typeof value;
return value != null && (type == 'object' || type == 'function');
}
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/ function isObjectLike(value) {
return value != null && typeof value == 'object';
}
/**
* Checks if `value` is classified as a typed array.
*
* @static
* @memberOf _
* @since 3.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
* @example
*
* _.isTypedArray(new Uint8Array);
* // => true
*
* _.isTypedArray([]);
* // => false
*/ var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;
/**
* Creates an array of the own enumerable property names of `object`.
*
* **Note:** Non-object values are coerced to objects. See the
* [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
* for more details.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.keys(new Foo);
* // => ['a', 'b'] (iteration order is not guaranteed)
*
* _.keys('hi');
* // => ['0', '1']
*/ function keys(object) {
return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
}
/**
* This method returns a new empty array.
*
* @static
* @memberOf _
* @since 4.13.0
* @category Util
* @returns {Array} Returns the new empty array.
* @example
*
* var arrays = _.times(2, _.stubArray);
*
* console.log(arrays);
* // => [[], []]
*
* console.log(arrays[0] === arrays[1]);
* // => false
*/ function stubArray() {
return [];
}
/**
* This method returns `false`.
*
* @static
* @memberOf _
* @since 4.13.0
* @category Util
* @returns {boolean} Returns `false`.
* @example
*
* _.times(2, _.stubFalse);
* // => [false, false]
*/ function stubFalse() {
return false;
}
module.exports = isEqual;
},{}],"l2Fo9":[function(require,module,exports,__globalThis) {
/**
* Tracks which logical "region" of the page the mouse is currently over.
* The hints bar reads this to add region-specific shortcuts to the chip set.
*
* Two simple mechanisms:
*
* 1. **Stability debounce** — when the raw region changes, the reported
* value lags by a small window. Two flavors:
* - `HOVER_STABILITY_MS` (short): region → region transitions. Short
* enough to feel responsive when the user deliberately switches
* between adjacent regions.
* - `HOVER_GRACE_MS` (longer): region → null transitions. Gives the
* user time to transit from a region toward the hints bar without
* losing the contextual chip set before the cursor arrives.
* Entering a region from null (no prior region) is always instant.
*
* 2. **Freeze** — `freezeHover()` captures the currently-reported value
* and locks it until `unfreezeHover()`. The hints bar calls these from
* its own `mouseenter`/`mouseleave` listeners so that as soon as the
* cursor is actually on the bar, the chip set is pinned for inspection.
*
* Each call to `registerHoverRegion(el, region)` returns a disposer; the
* caller is responsible for calling it when the element is unmounted.
*/ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
/** Direct setter for callers that need fine-grained control (e.g. the chart
* canvas mousemove hit-tester switches between `{type}-chart` and
* `{type}-chart-point` based on whether the cursor is over a data point). */ parcelHelpers.export(exports, "setHoveredRegion", ()=>setHoveredRegion);
/** Peek at the raw current region, bypassing both the stability window and
* the freeze. Used to detect "cursor is over a tracked region" decisions
* that should not be affected by what's currently REPORTED. */ parcelHelpers.export(exports, "peekRawHoveredRegion", ()=>peekRawHoveredRegion);
parcelHelpers.export(exports, "getHoveredRegion", ()=>getHoveredRegion);
/** Captures the currently-reported region and locks it until `unfreezeHover`.
* No-op if there's nothing to freeze. */ parcelHelpers.export(exports, "freezeHover", ()=>freezeHover);
parcelHelpers.export(exports, "unfreezeHover", ()=>unfreezeHover);
parcelHelpers.export(exports, "registerHoverRegion", ()=>registerHoverRegion);
/** Region → region transitions debounce by this much. Short enough to feel
* instant, long enough to absorb fast transits between adjacent regions. */ const HOVER_STABILITY_MS = 200;
/** Region → null transitions debounce by this much. Longer than the
* region→region window so that the contextual chip set persists while the
* user moves from the region toward the hints bar. */ const HOVER_GRACE_MS = 400;
/** Null → region transitions debounce by this much. Suppresses
* "transit-through" context flips when the user is moving from one part
* of the page through an adjacent hover region to reach the hints bar
* (e.g., markers mode → crossing crop chart → reaching the bar). Brief
* enough that deliberate hovers still register without feeling sluggish. */ const HOVER_ENTRY_MS = 180;
let hoveredRegion = null;
let rawRegionChangedAt = 0;
let lastReportedRegion = null;
let isFrozen = false;
let frozenRegion = null;
function updateRegion(region) {
// Always track the raw region (even while frozen) so that after unfreeze
// we report what the cursor is actually over now, not a stale value.
if (region === hoveredRegion) return;
hoveredRegion = region;
rawRegionChangedAt = performance.now();
}
function setHoveredRegion(region) {
updateRegion(region);
}
function peekRawHoveredRegion() {
return hoveredRegion;
}
function getHoveredRegion() {
if (isFrozen) return frozenRegion;
if (hoveredRegion === lastReportedRegion) return lastReportedRegion;
// Pick the debounce window based on the transition kind:
// null → region: brief entry debounce so the cursor merely transiting
// through a region (e.g. crossing the crop chart on the way to the
// hints bar) doesn't flip context before it settles.
// region → null: longer grace so the contextual chip set persists while
// the user moves from the region toward the hints bar.
// region → region: short stability window for deliberate switches
// between adjacent regions.
let debounce;
if (lastReportedRegion === null) debounce = HOVER_ENTRY_MS;
else if (hoveredRegion === null) debounce = HOVER_GRACE_MS;
else debounce = HOVER_STABILITY_MS;
if (performance.now() - rawRegionChangedAt < debounce) return lastReportedRegion;
lastReportedRegion = hoveredRegion;
return lastReportedRegion;
}
function freezeHover() {
const current = getHoveredRegion();
if (current === null) return;
frozenRegion = current;
isFrozen = true;
}
function unfreezeHover() {
isFrozen = false;
frozenRegion = null;
}
/** Active registrations, keyed by element so the same element can't double-register. */ const activeRegistrations = new WeakMap();
function registerHoverRegion(el, region) {
if (!el) return ()=>{};
const existing = activeRegistrations.get(el);
if (existing) {
el.removeEventListener('mouseenter', existing.onEnter);
el.removeEventListener('mouseleave', existing.onLeave);
activeRegistrations.delete(el);
}
const onEnter = ()=>{
updateRegion(region);
};
const onLeave = ()=>{
if (hoveredRegion === region) updateRegion(null);
};
el.addEventListener('mouseenter', onEnter);
el.addEventListener('mouseleave', onLeave);
activeRegistrations.set(el, {
region,
onEnter,
onLeave
});
return ()=>{
el.removeEventListener('mouseenter', onEnter);
el.removeEventListener('mouseleave', onLeave);
activeRegistrations.delete(el);
if (hoveredRegion === region) updateRegion(null);
};
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"EQEoZ":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "togglePrevSelectedMarkerPair", ()=>togglePrevSelectedMarkerPair);
parcelHelpers.export(exports, "moveMarkerByFrameHandler", ()=>moveMarkerByFrameHandler);
parcelHelpers.export(exports, "initMarkersContainer", ()=>initMarkersContainer);
parcelHelpers.export(exports, "loopMarkerPair", ()=>loopMarkerPair);
parcelHelpers.export(exports, "jumpToNearestMarkerOrPair", ()=>jumpToNearestMarkerOrPair);
parcelHelpers.export(exports, "jumpToNearestMarkerPair", ()=>jumpToNearestMarkerPair);
parcelHelpers.export(exports, "dblJump", ()=>dblJump);
parcelHelpers.export(exports, "prevJumpKeyCode", ()=>prevJumpKeyCode);
parcelHelpers.export(exports, "prevTime", ()=>prevTime);
parcelHelpers.export(exports, "jumpToNearestMarker", ()=>jumpToNearestMarker);
parcelHelpers.export(exports, "marker_attrs", ()=>marker_attrs);
parcelHelpers.export(exports, "addMarker", ()=>addMarker);
parcelHelpers.export(exports, "pushMarkerPairsArray", ()=>pushMarkerPairsArray);
parcelHelpers.export(exports, "updateMarkerPairEditor", ()=>updateMarkerPairEditor);
parcelHelpers.export(exports, "addMarkerPairNumberings", ()=>addMarkerPairNumberings);
parcelHelpers.export(exports, "undoMarker", ()=>undoMarker);
parcelHelpers.export(exports, "redoMarker", ()=>redoMarker);
parcelHelpers.export(exports, "duplicateSelectedMarkerPair", ()=>duplicateSelectedMarkerPair);
parcelHelpers.export(exports, "markerNumberingMouseOverHandler", ()=>markerNumberingMouseOverHandler);
parcelHelpers.export(exports, "markerNumberingMouseDownHandler", ()=>markerNumberingMouseDownHandler);
parcelHelpers.export(exports, "enableMarkerHotkeys", ()=>enableMarkerHotkeys);
parcelHelpers.export(exports, "getActiveStartMarker", ()=>getActiveStartMarker);
parcelHelpers.export(exports, "getActiveEndMarker", ()=>getActiveEndMarker);
parcelHelpers.export(exports, "moveMarker", ()=>moveMarker);
parcelHelpers.export(exports, "renderMarkerPair", ()=>renderMarkerPair);
parcelHelpers.export(exports, "undoRedoMarkerPairChange", ()=>undoRedoMarkerPairChange);
parcelHelpers.export(exports, "deleteMarkerPair", ()=>deleteMarkerPair);
parcelHelpers.export(exports, "clearPrevSelectedMarkerPairReferences", ()=>clearPrevSelectedMarkerPairReferences);
parcelHelpers.export(exports, "selectedStartMarkerOverlay", ()=>selectedStartMarkerOverlay);
parcelHelpers.export(exports, "selectedEndMarkerOverlay", ()=>selectedEndMarkerOverlay);
parcelHelpers.export(exports, "highlightSelectedMarkerPair", ()=>highlightSelectedMarkerPair);
parcelHelpers.export(exports, "updateMarkerPairDuration", ()=>updateMarkerPairDuration);
parcelHelpers.export(exports, "renumberMarkerPairs", ()=>renumberMarkerPairs);
parcelHelpers.export(exports, "hideSelectedMarkerPairOverlay", ()=>hideSelectedMarkerPairOverlay);
var _immer = require("immer");
var _lodashClonedeep = require("lodash.clonedeep");
var _lodashClonedeepDefault = parcelHelpers.interopDefault(_lodashClonedeep);
var _appState = require("./appState");
var _autoSave = require("./auto-save");
var _charts = require("./charts");
var _cropOverlay = require("./crop-overlay");
var _cropUtils = require("./crop-utils");
var _globalSettingsEditor = require("./features/settings/global-settings-editor");
var _markerSettingsEditor = require("./features/settings/marker-settings-editor");
var _platforms = require("./platforms/platforms");
var _saveLoad = require("./save-load");
var _settingsEditor = require("./features/settings/settings-editor");
var _speed = require("./speed");
var _chartutil = require("./ui/chart/chartutil");
var _undoredo = require("./util/undoredo");
var _util = require("./util/util");
var _litHtml = require("lit-html");
var _videoUtil = require("./util/videoUtil");
var _ytClipper = require("./yt_clipper");
var _dragRecovery = require("./util/drag-recovery");
function togglePrevSelectedMarkerPair() {
if (enableMarkerHotkeysData.endMarker) (0, _markerSettingsEditor.toggleMarkerPairEditor)(enableMarkerHotkeysData.endMarker);
else if ((0, _appState.appState).prevSelectedEndMarker) (0, _markerSettingsEditor.toggleMarkerPairEditor)((0, _appState.appState).prevSelectedEndMarker);
else {
const firstEndMarker = (0, _appState.appState).markersSvg.firstElementChild ? (0, _appState.appState).markersSvg.firstElementChild.nextElementSibling : null;
if (firstEndMarker) (0, _markerSettingsEditor.toggleMarkerPairEditor)(firstEndMarker);
}
}
function moveMarkerByFrameHandler(event) {
if ((0, _appState.appState).isHotkeysEnabled && !event.ctrlKey && event.altKey && event.shiftKey && Math.abs(event.deltaY) > 0 && (0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedEndMarker && !(0, _appState.appState).video.seeking) {
if (!(0, _appState.appState).video.paused) (0, _appState.appState).video.pause();
(0, _speed.setIsMarkerSeekPending)(true);
if ((0, _speed.markerSeekDebounceTimeout) !== null) clearTimeout((0, _speed.markerSeekDebounceTimeout));
(0, _speed.setMarkerSeekDebounceTimeout)(setTimeout(()=>{
(0, _speed.setMarkerSeekDebounceTimeout)(null);
(0, _speed.setIsMarkerSeekPending)(false);
}, 500));
const fps = (0, _videoUtil.getFPS)();
let targetMarker = (0, _appState.appState).prevSelectedEndMarker;
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
let targetMarkerTime = markerPair.end;
if (event.pageX < window.innerWidth / 2) {
targetMarker = (0, _appState.appState).prevSelectedEndMarker.previousElementSibling;
targetMarkerTime = markerPair.start;
}
let newMarkerTime;
if (event.deltaY > 0) {
newMarkerTime = targetMarkerTime - 1 / fps;
moveMarker(targetMarker, Math.max(0, newMarkerTime));
} else {
newMarkerTime = targetMarkerTime + 1 / fps;
moveMarker(targetMarker, Math.min((0, _util.getVideoDuration)((0, _ytClipper.platform), (0, _appState.appState).video), newMarkerTime));
}
(0, _util.seekToSafe)((0, _appState.appState).video, newMarkerTime);
}
}
const markersSvgTemplate = (0, _litHtml.html)`
<svg id="markers-svg"></svg>
<svg id="selected-marker-pair-overlay" style="display:none">
<rect
id="selected-start-marker-overlay"
class="selected-marker-overlay"
width="1px"
height="8px"
y="3.5px"
shape-rendering="crispEdges"
></rect>
<rect
id="selected-end-marker-overlay"
class="selected-marker-overlay"
width="1px"
height="8px"
y="3.5px"
shape-rendering="crispEdges"
></rect>
</svg>
`;
const markerNumberingsTemplate = (0, _litHtml.html)`
<svg id="start-marker-numberings"></svg>
<svg id="end-marker-numberings"></svg>
`;
function initMarkersContainer() {
(0, _appState.appState).settings = {
platform: (0, _ytClipper.platform),
videoID: (0, _appState.appState).videoInfo.id,
videoTitle: (0, _appState.appState).videoInfo.title,
videoUrl: (0, _appState.appState).videoInfo.videoUrl,
newMarkerSpeed: 1.0,
newMarkerCrop: '0:0:iw:ih',
videoTag: `[${0, _ytClipper.platform}@${(0, _appState.appState).videoInfo.id}]`,
titleSuffix: `[${0, _ytClipper.platform}@${(0, _appState.appState).videoInfo.id}]`,
isVerticalVideo: (0, _appState.appState).videoInfo.isVerticalVideo,
markerPairMergeList: '',
...(0, _cropUtils.getDefaultCropRes)()
};
(0, _appState.appState).markersDiv = document.createElement('div');
(0, _appState.appState).markersDiv.setAttribute('id', 'markers-div');
(0, _litHtml.render)(markersSvgTemplate, (0, _appState.appState).markersDiv);
(0, _appState.appState).markersSvg = (0, _appState.appState).markersDiv.children[0];
(0, _appState.appState).selectedMarkerPairOverlay = (0, _appState.appState).markersDiv.children[1];
(0, _appState.appState).markerNumberingsDiv = document.createElement('div');
(0, _appState.appState).markerNumberingsDiv.setAttribute('id', 'marker-numberings-div');
(0, _litHtml.render)(markerNumberingsTemplate, (0, _appState.appState).markerNumberingsDiv);
(0, _appState.appState).startMarkerNumberings = (0, _appState.appState).markerNumberingsDiv.children[0];
(0, _appState.appState).endMarkerNumberings = (0, _appState.appState).markerNumberingsDiv.children[1];
if ([
(0, _platforms.VideoPlatforms).weverse,
(0, _platforms.VideoPlatforms).naver_tv,
(0, _platforms.VideoPlatforms).yt_clipper
].includes((0, _ytClipper.platform))) {
(0, _appState.appState).hooks.markerNumberingsDiv.prepend((0, _appState.appState).markerNumberingsDiv);
(0, _appState.appState).hooks.markersDiv.prepend((0, _appState.appState).markersDiv);
} else {
(0, _appState.appState).hooks.markersDiv.appendChild((0, _appState.appState).markersDiv);
(0, _appState.appState).hooks.markerNumberingsDiv.appendChild((0, _appState.appState).markerNumberingsDiv);
}
(0, _appState.appState).videoInfo.fps = (0, _videoUtil.getFPS)();
}
function loopMarkerPair() {
requestAnimationFrame(loopMarkerPair);
if (!(0, _speed.getIsMarkerLoopPreviewOn)() && !(0, _appState.appState).isCropChartLoopingOn) return;
if (!(0, _appState.appState).isSettingsEditorOpen || (0, _appState.appState).wasGlobalSettingsEditorOpen) return;
if ((0, _appState.appState).prevSelectedMarkerPairIndex == null) return;
if ((0, _appState.appState).video.seeking) return;
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const chartLoop = (0, _charts.chartState).currentChartInput ? markerPair[(0, _charts.chartState).currentChartInput.chartLoopKey] : null;
if (chartLoop && chartLoop.enabled && chartLoop.start && chartLoop.end && chartLoop.start > markerPair.start && chartLoop.end < markerPair.end && chartLoop.start < chartLoop.end) {
const isTimeBetweenChartLoop = chartLoop.start <= (0, _appState.appState).video.getCurrentTime() && (0, _appState.appState).video.getCurrentTime() <= chartLoop.end;
if (!isTimeBetweenChartLoop) (0, _util.seekToSafe)((0, _appState.appState).video, chartLoop.start);
} else if ((0, _appState.appState).isCropChartLoopingOn && (0, _appState.appState).isCurrentChartVisible && (0, _charts.chartState).currentChartInput?.type === 'crop' || (0, _charts.chartState).cropChartInput.chart && ((0, _cropOverlay.isMouseManipulatingCrop) || (0, _cropOverlay.isDrawingCrop))) {
(0, _charts.chartState).shouldTriggerCropChartUpdates = false;
(0, _charts.cropChartSectionLoop)();
} else if ((0, _speed.getIsMarkerLoopPreviewOn)() && !(0, _speed.isMarkerSeekPending)) {
const isTimeBetweenMarkerPair = markerPair.start <= (0, _appState.appState).video.getCurrentTime() && (0, _appState.appState).video.getCurrentTime() <= markerPair.end;
if (!isTimeBetweenMarkerPair) (0, _util.seekToSafe)((0, _appState.appState).video, markerPair.start);
}
}
function jumpToNearestMarkerOrPair(e, keyCode) {
if (!(0, _settingsEditor.arrowKeyCropAdjustmentEnabled)) {
if (e.ctrlKey && !e.altKey && !e.shiftKey) jumpToNearestMarker(e, (0, _appState.appState).video.getCurrentTime(), keyCode);
else if (e.altKey && !e.shiftKey) {
if (!e.ctrlKey && !((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen)) {
(0, _util.blockEvent)(e);
togglePrevSelectedMarkerPair();
}
if (enableMarkerHotkeysData.endMarker) jumpToNearestMarkerPair(e, enableMarkerHotkeysData.endMarker, keyCode);
}
}
}
function jumpToNearestMarkerPair(e, targetEndMarker, keyCode) {
(0, _util.blockEvent)(e);
const idx = targetEndMarker.getAttribute('data-idx');
(0, _util.assertDefined)(idx, 'Expected data-idx attribute on end marker');
let index = parseInt(idx) - 1;
const currentEndMarker = enableMarkerHotkeysData.endMarker;
(0, _util.assertDefined)(currentEndMarker, 'Expected enableMarkerHotkeysData.endMarker to be defined');
if (keyCode === 'ArrowLeft' && index > 0) {
const prevSibling = currentEndMarker.previousElementSibling;
(0, _util.assertDefined)(prevSibling, 'Expected previous sibling of end marker');
targetEndMarker = prevSibling.previousElementSibling;
targetEndMarker && (0, _markerSettingsEditor.toggleMarkerPairEditor)(targetEndMarker);
if (e.ctrlKey) {
index--;
(0, _util.seekToSafe)((0, _appState.appState).video, (0, _appState.appState).markerPairs[index].start);
}
} else if (keyCode === 'ArrowRight' && index < (0, _appState.appState).markerPairs.length - 1) {
const nextSibling = currentEndMarker.nextElementSibling;
(0, _util.assertDefined)(nextSibling, 'Expected next sibling of end marker');
targetEndMarker = nextSibling.nextElementSibling;
targetEndMarker && (0, _markerSettingsEditor.toggleMarkerPairEditor)(targetEndMarker);
if (e.ctrlKey) {
index++;
(0, _util.seekToSafe)((0, _appState.appState).video, (0, _appState.appState).markerPairs[index].start);
}
}
}
let dblJump = 0;
let prevJumpKeyCode;
let prevTime;
function jumpToNearestMarker(e, currentTime, keyCode) {
(0, _util.blockEvent)(e);
let minTime = currentTime;
currentTime = prevTime ?? currentTime;
let markerTimes = [];
(0, _appState.appState).markerPairs.forEach((markerPair)=>{
markerTimes.push(markerPair.start);
markerTimes.push(markerPair.end);
});
if (!(0, _appState.appState).isNextMarkerStart) markerTimes.push((0, _appState.appState).startTime);
markerTimes = markerTimes.map((markerTime)=>parseFloat(markerTime.toFixed(6)));
if (keyCode === 'ArrowLeft') {
markerTimes = markerTimes.filter((markerTime)=>markerTime < currentTime);
minTime = Math.max(...markerTimes);
if (dblJump != 0 && markerTimes.length > 0 && prevJumpKeyCode === 'ArrowLeft') {
markerTimes = markerTimes.filter((markerTime)=>markerTime < minTime);
minTime = Math.max(...markerTimes);
}
prevJumpKeyCode = 'ArrowLeft';
} else if (keyCode === 'ArrowRight') {
markerTimes = markerTimes.filter((markerTime)=>markerTime > currentTime);
minTime = Math.min(...markerTimes);
if (dblJump != 0 && markerTimes.length > 0 && prevJumpKeyCode === 'ArrowRight') {
markerTimes = markerTimes.filter((markerTime)=>markerTime > minTime);
minTime = Math.min(...markerTimes);
}
prevJumpKeyCode = 'ArrowRight';
}
if (dblJump !== 0) {
clearTimeout(dblJump);
dblJump = 0;
prevTime = null;
if (minTime !== currentTime && minTime != Infinity && minTime != -Infinity) (0, _util.seekToSafe)((0, _appState.appState).video, minTime);
} else {
prevTime = currentTime;
if (minTime !== currentTime && minTime != Infinity && minTime != -Infinity) (0, _util.seekToSafe)((0, _appState.appState).video, minTime);
dblJump = setTimeout(()=>{
dblJump = 0;
prevTime = null;
}, 150);
}
}
const marker_attrs = {
class: 'marker',
width: '1px',
height: '14px',
'shape-rendering': 'crispEdges'
};
function addMarker(markerConfig = {}) {
const preciseCurrentTime = markerConfig.time ?? (0, _appState.appState).video.getCurrentTime();
// TODO: Calculate appState.video fps precisely so current frame time
// is accurately determined.
// const currentFrameTime = getCurrentFrameTime(roughCurrentTime);
const currentFrameTime = preciseCurrentTime;
const progressPos = currentFrameTime / (0, _util.getVideoDuration)((0, _ytClipper.platform), (0, _appState.appState).video) * 100;
if (!(0, _appState.appState).isNextMarkerStart && currentFrameTime <= (0, _appState.appState).startTime) {
(0, _util.flashMessage)('End marker must be after start marker.', 'red');
return;
}
const marker = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
(0, _appState.appState).markersSvg.appendChild(marker);
(0, _util.setAttributes)(marker, marker_attrs);
marker.setAttribute('x', `${progressPos}%`);
const rectIdx = (0, _appState.appState).markerPairs.length + 1;
marker.setAttribute('data-idx', rectIdx.toString());
if ((0, _appState.appState).isNextMarkerStart) {
marker.classList.add('start-marker');
marker.setAttribute('type', 'start');
marker.setAttribute('z-index', '1');
(0, _appState.appState).startTime = currentFrameTime;
} else {
marker.addEventListener('pointerover', (0, _markerSettingsEditor.toggleMarkerPairEditorHandler), false);
marker.classList.add('end-marker');
marker.setAttribute('type', 'end');
marker.setAttribute('z-index', '2');
const startProgressPos = (0, _appState.appState).startTime / (0, _util.getVideoDuration)((0, _ytClipper.platform), (0, _appState.appState).video) * 100;
const [startNumbering, endNumbering] = addMarkerPairNumberings(rectIdx, startProgressPos, progressPos, marker);
pushMarkerPairsArray(currentFrameTime, {
...markerConfig,
startNumbering,
endNumbering
});
updateMarkerPairEditor();
}
(0, _appState.appState).isNextMarkerStart = !(0, _appState.appState).isNextMarkerStart;
console.log((0, _appState.appState).markerPairs);
}
function pushMarkerPairsArray(currentTime, markerPairConfig) {
const speed = markerPairConfig.speed ?? (0, _appState.appState).settings.newMarkerSpeed;
const crop = markerPairConfig.crop ?? (0, _appState.appState).settings.newMarkerCrop;
(0, _util.assertDefined)(markerPairConfig.startNumbering, 'Expected startNumbering in markerPairConfig');
(0, _util.assertDefined)(markerPairConfig.endNumbering, 'Expected endNumbering in markerPairConfig');
const newMarkerPair = {
start: (0, _appState.appState).startTime,
end: currentTime,
speed,
speedMap: markerPairConfig.speedMap ?? [
{
x: (0, _appState.appState).startTime,
y: speed
},
{
x: currentTime,
y: speed
}
],
speedChartLoop: markerPairConfig.speedChartLoop ?? {
enabled: true
},
crop,
cropMap: markerPairConfig.cropMap ?? [
{
x: (0, _appState.appState).startTime,
y: 0,
crop: crop
},
{
x: currentTime,
y: 0,
crop: crop
}
],
cropChartLoop: markerPairConfig.cropChartLoop ?? {
enabled: true
},
enableZoomPan: markerPairConfig.enableZoomPan ?? false,
cropRes: (0, _appState.appState).settings.cropRes,
outputDuration: markerPairConfig.outputDuration ?? currentTime - (0, _appState.appState).startTime,
startNumbering: markerPairConfig.startNumbering,
endNumbering: markerPairConfig.endNumbering,
overrides: markerPairConfig.overrides ?? {},
undoredo: markerPairConfig.undoredo ?? {
history: [],
index: -1
}
};
if (newMarkerPair.undoredo.history.length === 0) {
const draft = (0, _immer.createDraft)((0, _undoredo.getMarkerPairHistory)(newMarkerPair));
(0, _undoredo.saveMarkerPairHistory)(draft, newMarkerPair);
}
(0, _appState.appState).markerPairs.push(newMarkerPair);
(0, _autoSave.initAutoSave)();
}
function updateMarkerPairEditor() {
if ((0, _appState.appState).isSettingsEditorOpen) {
const markerPairCountLabel = document.getElementById('marker-pair-count-label');
if (markerPairCountLabel) {
markerPairCountLabel.textContent = (0, _appState.appState).markerPairs.length.toString();
(0, _markerSettingsEditor.markerPairNumberInput).setAttribute('max', (0, _appState.appState).markerPairs.length.toString());
}
}
}
function renderNumberingText(parent, cls, idx, progressPos) {
const container = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
(0, _litHtml.render)((0, _litHtml.svg)`<text class="markerNumbering ${cls}" data-idx=${idx} x="${progressPos}%" y="11.5px" text-anchor="middle">${idx}</text>`, container);
const numbering = container.children[0];
parent.appendChild(numbering);
return numbering;
}
function addMarkerPairNumberings(idx, startProgressPos, endProgressPos, endMarker) {
const startNumberingText = renderNumberingText((0, _appState.appState).startMarkerNumberings, 'startMarkerNumbering', idx, startProgressPos);
const endNumberingText = renderNumberingText((0, _appState.appState).endMarkerNumberings, 'endMarkerNumbering', idx, endProgressPos);
endNumberingText.marker = endMarker;
startNumberingText.marker = endMarker;
endNumberingText.addEventListener('pointerover', markerNumberingMouseOverHandler, false);
startNumberingText.addEventListener('pointerdown', markerNumberingMouseDownHandler, true);
endNumberingText.addEventListener('pointerdown', markerNumberingMouseDownHandler, true);
return [
startNumberingText,
endNumberingText
];
}
function undoMarker() {
const targetMarker = (0, _appState.appState).markersSvg.lastElementChild;
if (!targetMarker) return;
const targetMarkerType = targetMarker.getAttribute('type');
// toggle off marker pair editor before undoing a selected marker pair
if (targetMarkerType === 'end' && (0, _appState.appState).prevSelectedMarkerPairIndex >= (0, _appState.appState).markerPairs.length - 1) {
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen) (0, _markerSettingsEditor.toggleOffMarkerPairEditor)(true);
else hideSelectedMarkerPairOverlay(true);
clearPrevSelectedMarkerPairReferences();
}
(0, _util.deleteElement)(targetMarker);
if (targetMarkerType === 'end') {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).markerPairs.length - 1];
(0, _util.deleteElement)(markerPair.startNumbering);
(0, _util.deleteElement)(markerPair.endNumbering);
(0, _appState.appState).startTime = markerPair.start;
const removedPair = (0, _appState.appState).markerPairs.pop();
(0, _util.assertDefined)(removedPair, 'Expected marker pair to exist when undoing');
(0, _appState.appState).markerPairsHistory.push(removedPair);
console.log((0, _appState.appState).markerPairs);
updateMarkerPairEditor();
}
(0, _appState.appState).isNextMarkerStart = !(0, _appState.appState).isNextMarkerStart;
}
function redoMarker() {
if ((0, _appState.appState).markerPairsHistory.length > 0) {
const markerPairToRestore = (0, _appState.appState).markerPairsHistory[(0, _appState.appState).markerPairsHistory.length - 1];
if ((0, _appState.appState).isNextMarkerStart) addMarker({
time: markerPairToRestore.start
});
else {
(0, _appState.appState).markerPairsHistory.pop();
addMarker({
...markerPairToRestore,
time: markerPairToRestore.end
});
}
}
}
function duplicateSelectedMarkerPair() {
const markerPairIndex = (0, _appState.appState).prevSelectedMarkerPairIndex;
if (markerPairIndex != null) {
const markerPair = (0, _lodashClonedeepDefault.default)((0, _appState.appState).markerPairs[markerPairIndex]);
(0, _saveLoad.addMarkerPairs)([
markerPair
]);
(0, _util.flashMessage)(`Duplicated marker pair ${markerPairIndex + 1}.`, 'green');
} else (0, _util.flashMessage)(`No selected or previously selected marker pair to duplicate.`, 'red');
}
function markerNumberingMouseOverHandler(e) {
const targetMarker = e.target.marker;
(0, _markerSettingsEditor.toggleMarkerPairEditorHandler)(e, targetMarker);
}
function markerNumberingMouseDownHandler(e) {
if (!(e.button === 0)) return;
(0, _util.blockEvent)(e);
const numbering = e.target;
const numberingType = numbering.classList.contains('startMarkerNumbering') ? 'start' : 'end';
const targetEndMarker = numbering.marker;
const targetStartMarker = targetEndMarker.previousSibling;
const targetMarker = numberingType === 'start' ? targetStartMarker : targetEndMarker;
const dataIdx = numbering.getAttribute('data-idx');
(0, _util.assertDefined)(dataIdx, 'Expected data-idx attribute on numbering');
const markerPairIndex = parseInt(dataIdx) - 1;
const markerPair = (0, _appState.appState).markerPairs[markerPairIndex];
const markerTime = numberingType === 'start' ? markerPair.start : markerPair.end;
// open editor of target marker corresponding to clicked numbering
if (!(0, _appState.appState).isSettingsEditorOpen) (0, _markerSettingsEditor.toggleOnMarkerPairEditor)(targetEndMarker);
else {
if ((0, _appState.appState).wasGlobalSettingsEditorOpen) {
(0, _globalSettingsEditor.toggleOffGlobalSettingsEditor)();
(0, _markerSettingsEditor.toggleOnMarkerPairEditor)(targetEndMarker);
} else if ((0, _appState.appState).prevSelectedEndMarker != targetEndMarker) {
(0, _markerSettingsEditor.toggleOffMarkerPairEditor)();
(0, _markerSettingsEditor.toggleOnMarkerPairEditor)(targetEndMarker);
}
}
(0, _util.seekToSafe)((0, _appState.appState).video, markerTime);
if (!e.altKey) return;
const pointerId = e.pointerId;
numbering.setPointerCapture(pointerId);
// The element that owns pointer capture is the same element that will
// receive the implicit `pointercancel` if the browser releases capture
// out from under us (devtools focus, alt-tab, OS pointer reassignment).
// Attaching all subsequent listeners here — rather than to `document` —
// is the spec-correct way to be sure the cleanup path runs.
const captureTarget = numbering;
const numberingRect = numbering.getBoundingClientRect();
const progressBarRect = (0, _appState.appState).hooks.progressBar.getBoundingClientRect();
// `offsetX` preserves the grip point: wherever on the numbering label
// the user clicked, that point stays under the cursor for the duration
// of the drag. Subtracting it from `e.pageX` recovers the time-equivalent
// of the numbering's centre.
const offsetX = e.pageX - numberingRect.left - numberingRect.width / 2;
/** Absolute positioning — the marker's time tracks the cursor's
* horizontal position on the progress bar 1:1, mirroring how
* `scrubVideoHandler` handles Alt+Drag on the video itself. The
* previous implementation modulated motion by vertical cursor
* position (precision-scaling) and short-circuited on any vertical
* movement, which together produced visible jitter and rubber-band
* during normal horizontal drags. */ function getDragTime(e) {
const duration = (0, _util.getVideoDuration)((0, _ytClipper.platform), (0, _appState.appState).video);
const x = e.pageX - offsetX - progressBarRect.left;
const time = duration * x / progressBarRect.width;
return numberingType === 'start' ? (0, _util.clampNumber)(time, 0, markerPair.end - 1e-3) : (0, _util.clampNumber)(time, markerPair.start + 1e-3, duration);
}
// Coalesce pointermove events with requestAnimationFrame so the marker
// visual updates at the screen refresh rate (~60 Hz) instead of the
// raw pointer event rate (often 100–250 Hz). Same pattern as the crop
// drag/hover handlers in crop-overlay.ts — without it `moveMarker`
// (which re-renders markers + crops) and `seekToSafe` get called 2-4×
// per frame and the marker visibly lags the cursor.
let dragRafId = 0;
let pendingDragEvent = null;
function processDragNumbering() {
dragRafId = 0;
const e = pendingDragEvent;
pendingDragEvent = null;
if (!e) return;
const time = getDragTime(e);
if (Math.abs(time - (0, _appState.appState).video.getCurrentTime()) < 0.01) return;
moveMarker(targetMarker, time, false, false);
(0, _util.seekToSafe)((0, _appState.appState).video, time);
}
function dragNumbering(e) {
pendingDragEvent = e;
if (!dragRafId) dragRafId = requestAnimationFrame(processDragNumbering);
}
captureTarget.addEventListener('pointermove', dragNumbering);
let unregisterDragRecovery = ()=>{};
/** Idempotent cleanup — detaches every listener this handler attached,
* releases the captured pointer, cancels any pending RAF. Safe to call
* multiple times because each step checks first. */ function cleanup() {
if (dragRafId) {
cancelAnimationFrame(dragRafId);
dragRafId = 0;
pendingDragEvent = null;
}
captureTarget.removeEventListener('pointermove', dragNumbering);
captureTarget.removeEventListener('pointerup', onPointerUp, {
capture: true
});
captureTarget.removeEventListener('pointercancel', onPointerCancel, {
capture: true
});
if (captureTarget.hasPointerCapture(pointerId)) captureTarget.releasePointerCapture(pointerId);
unregisterDragRecovery();
}
function onPointerUp(e) {
cleanup();
const time = getDragTime(e);
if (Math.abs(time - markerTime) < 0.001) return;
moveMarker(targetMarker, time, true, true);
}
function onPointerCancel() {
// Browser-initiated release (devtools, alt-tab, OS pointer
// reassignment). Revert to the marker's original time rather than
// committing the in-progress drag — the user didn't choose to land.
cleanup();
}
captureTarget.addEventListener('pointerup', onPointerUp, {
once: true,
capture: true
});
captureTarget.addEventListener('pointercancel', onPointerCancel, {
once: true,
capture: true
});
// Belt-and-suspenders: window.blur / tab-hidden recovery covers cases
// where neither pointerup nor pointercancel fires for the captured
// target (rapid alt-tab on Windows, certain devtools docking moves).
unregisterDragRecovery = (0, _dragRecovery.registerActiveDragCleanup)(cleanup);
}
const enableMarkerHotkeysData = {
startMarker: null,
endMarker: null
};
function enableMarkerHotkeys(endMarker) {
(0, _appState.appState).markerHotkeysEnabled = true;
enableMarkerHotkeysData.endMarker = endMarker;
enableMarkerHotkeysData.startMarker = endMarker.previousSibling;
}
function getActiveStartMarker() {
return enableMarkerHotkeysData.startMarker;
}
function getActiveEndMarker() {
return enableMarkerHotkeysData.endMarker;
}
function moveMarker(marker, newTime, storeHistory = true, adjustCharts = true) {
const type = marker.getAttribute('type');
const dataIdx = marker.getAttribute('data-idx');
(0, _util.assertDefined)(dataIdx, 'Expected data-idx attribute on marker');
const idx = parseInt(dataIdx) - 1;
const markerPair = (0, _appState.appState).markerPairs[idx];
const toTime = newTime ?? (0, _appState.appState).video.getCurrentTime();
if (type === 'start' && toTime >= markerPair.end) {
(0, _util.flashMessage)('Start marker cannot be placed after or at end marker', 'red');
return;
}
if (type === 'end' && toTime <= markerPair.start) {
(0, _util.flashMessage)('End marker cannot be placed before or at start marker', 'red');
return;
}
const initialState = (0, _undoredo.getMarkerPairHistory)(markerPair);
const draft = (0, _immer.createDraft)(initialState);
const lastState = (0, _undoredo.peekLastState)(markerPair.undoredo);
const isStretch = type === 'start' ? toTime <= lastState.start : toTime >= lastState.end;
draft[type] = toTime;
if (adjustCharts) {
if (isStretch) {
draft.speedMap = (0, _chartutil.stretchPointMap)(draft, draft.speedMap, 'speed', toTime, type);
draft.cropMap = (0, _chartutil.stretchPointMap)(draft, draft.cropMap, 'crop', toTime, type);
} else {
draft.speedMap = (0, _chartutil.shrinkPointMap)(draft, draft.speedMap, 'speed', toTime, type);
draft.cropMap = (0, _chartutil.shrinkPointMap)(draft, draft.cropMap, 'crop', toTime, type);
}
}
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair, storeHistory);
(0, _charts.renderSpeedAndCropUI)(adjustCharts, adjustCharts);
}
function renderMarkerPair(markerPair, markerPairIndex) {
const startMarker = (0, _appState.appState).markersSvg.querySelector(`.start-marker[data-idx="${markerPairIndex + 1}"]`);
const endMarker = (0, _appState.appState).markersSvg.querySelector(`.end-marker[data-idx="${markerPairIndex + 1}"]`);
const startMarkerNumbering = (0, _appState.appState).startMarkerNumberings.children[markerPairIndex];
const endMarkerNumbering = (0, _appState.appState).endMarkerNumberings.children[markerPairIndex];
const startProgressPos = markerPair.start / (0, _util.getVideoDuration)((0, _ytClipper.platform), (0, _appState.appState).video) * 100;
const endProgressPos = markerPair.end / (0, _util.getVideoDuration)((0, _ytClipper.platform), (0, _appState.appState).video) * 100;
(0, _util.assertDefined)(startMarker, 'Expected start marker element');
(0, _util.assertDefined)(endMarker, 'Expected end marker element');
startMarker.setAttribute('x', `${startProgressPos}%`);
startMarkerNumbering.setAttribute('x', `${startProgressPos}%`);
selectedStartMarkerOverlay.setAttribute('x', `${startProgressPos}%`);
endMarker.setAttribute('x', `${endProgressPos}%`);
endMarkerNumbering.setAttribute('x', `${endProgressPos}%`);
selectedEndMarkerOverlay.setAttribute('x', `${endProgressPos}%`);
const startMarkerTimeSpan = document.getElementById(`start-time`);
const endMarkerTimeSpan = document.getElementById(`end-time`);
(0, _util.assertDefined)(startMarkerTimeSpan, 'Expected start-time element');
(0, _util.assertDefined)(endMarkerTimeSpan, 'Expected end-time element');
startMarkerTimeSpan.textContent = (0, _util.toHHMMSSTrimmed)(markerPair.start);
endMarkerTimeSpan.textContent = (0, _util.toHHMMSSTrimmed)(markerPair.end);
updateMarkerPairDuration(markerPair);
}
function undoRedoMarkerPairChange(dir) {
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedMarkerPairIndex != null) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const newState = dir === 'undo' ? (0, _undoredo.undo)(markerPair.undoredo, ()=>null) : (0, _undoredo.redo)(markerPair.undoredo, ()=>null);
if (newState == null) (0, _util.flashMessage)(`Nothing left to ${dir}.`, 'red');
else {
Object.assign(markerPair, newState);
if (markerPair.cropRes !== (0, _appState.appState).settings.cropRes) {
const { cropMultipleX, cropMultipleY } = (0, _cropUtils.getCropMultiples)(markerPair.cropRes, (0, _appState.appState).settings.cropRes);
(0, _cropUtils.multiplyMarkerPairCrops)(markerPair, cropMultipleX, cropMultipleY);
}
(0, _charts.renderSpeedAndCropUI)(true, true);
(0, _util.flashMessage)(`Applied ${dir}.`, 'green');
}
} else (0, _util.flashMessage)('Please select a marker pair editor for undo/redo.', 'olive');
}
function deleteMarkerPair(idx) {
idx ??= (0, _appState.appState).prevSelectedMarkerPairIndex;
const markerPair = (0, _appState.appState).markerPairs[idx];
const me = new PointerEvent('pointerover', {
shiftKey: true
});
if (enableMarkerHotkeysData.endMarker) {
enableMarkerHotkeysData.endMarker.dispatchEvent(me);
(0, _util.deleteElement)(enableMarkerHotkeysData.endMarker);
}
if (enableMarkerHotkeysData.startMarker) (0, _util.deleteElement)(enableMarkerHotkeysData.startMarker);
(0, _util.deleteElement)(markerPair.startNumbering);
(0, _util.deleteElement)(markerPair.endNumbering);
hideSelectedMarkerPairOverlay(true);
renumberMarkerPairs();
(0, _appState.appState).markerPairs.splice(idx, 1);
clearPrevSelectedMarkerPairReferences();
}
function clearPrevSelectedMarkerPairReferences() {
(0, _appState.appState).prevSelectedMarkerPairIndex = null;
(0, _appState.appState).prevSelectedEndMarker = null;
enableMarkerHotkeysData.startMarker = null;
enableMarkerHotkeysData.endMarker = null;
(0, _appState.appState).markerHotkeysEnabled = false;
}
let selectedStartMarkerOverlay;
let selectedEndMarkerOverlay;
function highlightSelectedMarkerPair(currentMarker) {
if (!selectedStartMarkerOverlay) {
const el = document.getElementById('selected-start-marker-overlay');
(0, _util.assertDefined)(el, 'Expected selected-start-marker-overlay element');
selectedStartMarkerOverlay = el;
}
if (!selectedEndMarkerOverlay) {
const el = document.getElementById('selected-end-marker-overlay');
(0, _util.assertDefined)(el, 'Expected selected-end-marker-overlay element');
selectedEndMarkerOverlay = el;
}
const startMarker = currentMarker.previousSibling;
const startX = startMarker.getAttribute('x');
(0, _util.assertDefined)(startX, 'Expected x attribute on start marker');
const endX = currentMarker.getAttribute('x');
(0, _util.assertDefined)(endX, 'Expected x attribute on current marker');
selectedStartMarkerOverlay.setAttribute('x', startX);
selectedEndMarkerOverlay.setAttribute('x', endX);
selectedStartMarkerOverlay.classList.remove('selected-marker-overlay-hidden');
selectedEndMarkerOverlay.classList.remove('selected-marker-overlay-hidden');
(0, _appState.appState).selectedMarkerPairOverlay.style.display = 'block';
}
function updateMarkerPairDuration(markerPair) {
const speedAdjustedDurationSpan = document.getElementById('duration');
const duration = markerPair.end - markerPair.start;
const durationHHMMSS = (0, _util.toHHMMSSTrimmed)(duration);
const speed = markerPair.speed;
const speedMap = markerPair.speedMap;
if ((0, _saveLoad.isVariableSpeed)(speedMap)) {
const outputDuration = (0, _util.getOutputDuration)(markerPair.speedMap, (0, _videoUtil.getFPS)());
const outputDurationHHMMSS = (0, _util.toHHMMSSTrimmed)(outputDuration);
if (speedAdjustedDurationSpan) speedAdjustedDurationSpan.textContent = `${durationHHMMSS} (${outputDurationHHMMSS})`;
markerPair.outputDuration = outputDuration;
} else {
const outputDuration = duration / speed;
const outputDurationHHMMSS = (0, _util.toHHMMSSTrimmed)(outputDuration);
if (speedAdjustedDurationSpan) speedAdjustedDurationSpan.textContent = `${durationHHMMSS}/${speed} = ${outputDurationHHMMSS}`;
markerPair.outputDuration = outputDuration;
}
}
function renumberMarkerPairs() {
const markersSvg = document.getElementById('markers-svg');
(0, _util.assertDefined)(markersSvg, 'Expected markers-svg element');
Array.from(markersSvg.children).forEach((markerRect, idx)=>{
// renumber markers by pair starting with index 1
const newIdx = Math.floor((idx + 2) / 2);
markerRect.setAttribute('data-idx', newIdx.toString());
});
Array.from((0, _appState.appState).startMarkerNumberings.children).forEach((startNumbering, idx)=>{
const newIdx = idx + 1;
startNumbering.setAttribute('data-idx', newIdx.toString());
startNumbering.textContent = newIdx.toString();
});
Array.from((0, _appState.appState).endMarkerNumberings.children).forEach((endNumbering, idx)=>{
const newIdx = idx + 1;
endNumbering.setAttribute('data-idx', newIdx.toString());
endNumbering.textContent = newIdx.toString();
});
}
function hideSelectedMarkerPairOverlay(hardHide = false) {
if (hardHide) (0, _appState.appState).selectedMarkerPairOverlay.style.display = 'none';
else {
selectedStartMarkerOverlay.classList.add('selected-marker-overlay-hidden');
selectedEndMarkerOverlay.classList.add('selected-marker-overlay-hidden');
}
}
},{"immer":"R6AMM","lodash.clonedeep":"hwif0","./appState":"g0AlP","./auto-save":"dz8Rw","./charts":"hBxwj","./crop-overlay":"6s727","./crop-utils":"k2gwb","./features/settings/global-settings-editor":"koJFH","./features/settings/marker-settings-editor":"ZduPU","./platforms/platforms":"1kR7r","./save-load":"3FwNw","./features/settings/settings-editor":"jDViX","./speed":"6CgFD","./ui/chart/chartutil":"AvPxz","./util/undoredo":"7UuTl","./util/util":"99arg","lit-html":"9fQBw","./util/videoUtil":"93pN0","./yt_clipper":"6vE65","./util/drag-recovery":"3BYSh","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"dz8Rw":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "initAutoSave", ()=>initAutoSave);
parcelHelpers.export(exports, "saveClipperInputDataToLocalStorage", ()=>saveClipperInputDataToLocalStorage);
parcelHelpers.export(exports, "loadClipperInputDataFromLocalStorage", ()=>loadClipperInputDataFromLocalStorage);
parcelHelpers.export(exports, "getMarkersDataEntriesFromLocalStorage", ()=>getMarkersDataEntriesFromLocalStorage);
parcelHelpers.export(exports, "clearYTClipperLocalStorage", ()=>clearYTClipperLocalStorage);
parcelHelpers.export(exports, "downloadAutoSavedMarkersData", ()=>downloadAutoSavedMarkersData);
var _commonTags = require("common-tags");
var _fileSaver = require("file-saver");
var _jszip = require("jszip");
var _jszipDefault = parcelHelpers.interopDefault(_jszip);
var _litHtml = require("lit-html");
var _appState = require("./appState");
var _util = require("./util/util");
var _saveLoad = require("./save-load");
var _loadMarkersReview = require("./save-load/load-markers-review");
var _parseClipperInput = require("./save-load/parse-clipper-input");
let autoSaveIntervalId;
const localStorageKeyPrefix = 'yt_clipper';
function initAutoSave() {
if (autoSaveIntervalId == null) {
(0, _util.flashMessage)('Initializing auto saving of markers data to local storage...', 'olive');
autoSaveIntervalId = setInterval(()=>{
saveClipperInputDataToLocalStorage();
}, 5000);
}
}
function saveClipperInputDataToLocalStorage() {
const date = Date.now(); /* */
const key = `${localStorageKeyPrefix}_${(0, _appState.appState).settings.videoTag}`;
const data = (0, _saveLoad.getClipperInputData)(date);
try {
localStorage.setItem(key, JSON.stringify(data, null, 2));
} catch (e) {
if (e instanceof DOMException && e.name === 'QuotaExceededError') {
const markersDataFiles = getMarkersDataEntriesFromLocalStorage();
(0, _util.flashMessage)(`Failed to save markers data.
Browser local storage quota exceeded with ${markersDataFiles?.length} markers data files.
Try clearing auto-saved markers data after backing it up (see marker data commands menu (shortcut: G).`, 'red', 4500);
} else (0, _util.flashMessage)(`Failed to save markers data. Error: ${String(e)}`, 'red');
}
}
function loadClipperInputDataFromLocalStorage() {
if ((0, _appState.appState).markerPairs.length !== 0) {
(0, _util.flashMessage)('Please delete all marker pairs before restoring markers data.', 'red');
return;
}
const key = `${localStorageKeyPrefix}_${(0, _appState.appState).settings.videoTag}`;
const clipperInputJSON = localStorage.getItem(key);
if (clipperInputJSON == null) {
(0, _util.flashMessage)(`No markers data found in local storage for appState.video ${(0, _appState.appState).settings.videoTag}.`, 'red');
return;
}
let result;
try {
result = (0, _parseClipperInput.parseClipperInputJSON)(clipperInputJSON);
} catch (err) {
if (err instanceof (0, _parseClipperInput.ClipperInputValidationError)) {
console.error('Failed to parse auto-saved markers data', err);
(0, _util.flashMessage)(`Failed to parse auto-saved markers data: ${err.message}`, 'red');
return;
}
throw err;
}
const date = new Date(result.input.date ?? NaN);
const pairCount = result.input.markerPairs.length;
(0, _loadMarkersReview.showLoadMarkersReviewModal)({
modalTitle: 'Restore auto-saved markers?',
// `videoTag` is rendered inside `<code>` so its boundary is visually
// unambiguous. Even though `videoTag` is no longer accepted from
// loaded JSON (see KEY_POLICY in parse-clipper-input.ts), keeping
// the delimiter defends against any future path that lets a
// user-controlled string reach modal chrome.
warning: (0, _litHtml.html)`⚠ The last auto-saved markers data for video
<code>${(0, _appState.appState).settings.videoTag}</code> will be restored. Saved on ${String(date)}.
Contains ${pairCount} marker pair(s).`,
sourceLabel: 'auto-saved data',
payload: result.input,
issues: result.issues,
onLoad: ()=>{
(0, _saveLoad.applyClipperInput)(result.input);
(0, _saveLoad.deleteMarkersDataCommands)();
(0, _util.flashMessage)(`Restored ${pairCount} marker pair(s) from auto-save.`, 'green');
}
});
}
function getMarkersDataEntriesFromLocalStorage() {
const entries = Object.entries(localStorage).map((x)=>x[0]).filter((x)=>x.startsWith(localStorageKeyPrefix));
return entries;
}
function clearYTClipperLocalStorage() {
const entries = getMarkersDataEntriesFromLocalStorage();
const nEntries = entries.length;
const clearAll = confirm((0, _commonTags.stripIndent)`
The following markers data files will be cleared from local storage:
${entries.map((entry)=>entry.replace(localStorageKeyPrefix + '_', '')).join(', ')}\n
Proceed to clear all (${nEntries}) markers data files from local storage?
`);
if (clearAll) {
entries.map((x)=>{
localStorage.removeItem(x);
});
(0, _util.flashMessage)(`Cleared ${nEntries} markers data files.`, 'olive');
}
}
function downloadAutoSavedMarkersData() {
const entries = Object.entries(localStorage).map((x)=>x[0]).filter((x)=>x.startsWith(localStorageKeyPrefix));
const nEntries = entries.length;
if (nEntries === 0) {
(0, _util.flashMessage)('No markers data in local storage to zip.', 'olive');
return;
}
(0, _util.flashMessage)(`Zipping ${nEntries} markers data files.`, 'olive');
const now = new Date();
const zip = new (0, _jszipDefault.default)();
const markersZipFolderName = 'yt_clipper_markers_data_' + now.toISOString();
const markersZip = zip.folder(markersZipFolderName);
(0, _util.assertDefined)(markersZip, 'Failed to create zip folder');
entries.forEach((entry)=>{
const data = localStorage.getItem(entry);
(0, _util.assertDefined)(data, `Expected localStorage entry for ${entry}`);
markersZip.file(entry.replace(localStorageKeyPrefix, '') + '.json', data, {
binary: false
});
});
const progressDiv = (0, _util.injectProgressBar)('green', 'Markers Data');
const progressSpan = progressDiv.firstElementChild;
(0, _util.assertDefined)(progressSpan, 'Expected progress bar to have a child element');
zip.generateAsync({
type: 'blob'
}, (metadata)=>{
const percent = metadata.percent.toFixed(2) + '%';
progressSpan.textContent = `Markers Data Zipping Progress: ${percent}`;
}).then((blob)=>{
(0, _fileSaver.saveAs)(blob, markersZipFolderName + '.zip');
progressDiv.dispatchEvent(new Event('done'));
});
}
},{"common-tags":"4Q3cx","file-saver":"6BFkB","jszip":"hmOqA","lit-html":"9fQBw","./appState":"g0AlP","./util/util":"99arg","./save-load":"3FwNw","./save-load/load-markers-review":"1yueE","./save-load/parse-clipper-input":"hnA8h","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"4Q3cx":[function(require,module,exports,__globalThis) {
// core
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "TemplateTag", ()=>(0, _templateTagDefault.default));
parcelHelpers.export(exports, "trimResultTransformer", ()=>(0, _trimResultTransformerDefault.default));
parcelHelpers.export(exports, "stripIndentTransformer", ()=>(0, _stripIndentTransformerDefault.default));
parcelHelpers.export(exports, "replaceResultTransformer", ()=>(0, _replaceResultTransformerDefault.default));
parcelHelpers.export(exports, "replaceSubstitutionTransformer", ()=>(0, _replaceSubstitutionTransformerDefault.default));
parcelHelpers.export(exports, "replaceStringTransformer", ()=>(0, _replaceStringTransformerDefault.default));
parcelHelpers.export(exports, "inlineArrayTransformer", ()=>(0, _inlineArrayTransformerDefault.default));
parcelHelpers.export(exports, "splitStringTransformer", ()=>(0, _splitStringTransformerDefault.default));
parcelHelpers.export(exports, "removeNonPrintingValuesTransformer", ()=>(0, _removeNonPrintingValuesTransformerDefault.default));
parcelHelpers.export(exports, "commaLists", ()=>(0, _commaListsDefault.default));
parcelHelpers.export(exports, "commaListsAnd", ()=>(0, _commaListsAndDefault.default));
parcelHelpers.export(exports, "commaListsOr", ()=>(0, _commaListsOrDefault.default));
parcelHelpers.export(exports, "html", ()=>(0, _htmlDefault.default));
parcelHelpers.export(exports, "codeBlock", ()=>(0, _codeBlockDefault.default));
parcelHelpers.export(exports, "source", ()=>(0, _sourceDefault.default));
parcelHelpers.export(exports, "safeHtml", ()=>(0, _safeHtmlDefault.default));
parcelHelpers.export(exports, "oneLine", ()=>(0, _oneLineDefault.default));
parcelHelpers.export(exports, "oneLineTrim", ()=>(0, _oneLineTrimDefault.default));
parcelHelpers.export(exports, "oneLineCommaLists", ()=>(0, _oneLineCommaListsDefault.default));
parcelHelpers.export(exports, "oneLineCommaListsOr", ()=>(0, _oneLineCommaListsOrDefault.default));
parcelHelpers.export(exports, "oneLineCommaListsAnd", ()=>(0, _oneLineCommaListsAndDefault.default));
parcelHelpers.export(exports, "inlineLists", ()=>(0, _inlineListsDefault.default));
parcelHelpers.export(exports, "oneLineInlineLists", ()=>(0, _oneLineInlineListsDefault.default));
parcelHelpers.export(exports, "stripIndent", ()=>(0, _stripIndentDefault.default));
parcelHelpers.export(exports, "stripIndents", ()=>(0, _stripIndentsDefault.default));
var _templateTag = require("./TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
// transformers
var _trimResultTransformer = require("./trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var _stripIndentTransformer = require("./stripIndentTransformer");
var _stripIndentTransformerDefault = parcelHelpers.interopDefault(_stripIndentTransformer);
var _replaceResultTransformer = require("./replaceResultTransformer");
var _replaceResultTransformerDefault = parcelHelpers.interopDefault(_replaceResultTransformer);
var _replaceSubstitutionTransformer = require("./replaceSubstitutionTransformer");
var _replaceSubstitutionTransformerDefault = parcelHelpers.interopDefault(_replaceSubstitutionTransformer);
var _replaceStringTransformer = require("./replaceStringTransformer");
var _replaceStringTransformerDefault = parcelHelpers.interopDefault(_replaceStringTransformer);
var _inlineArrayTransformer = require("./inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _splitStringTransformer = require("./splitStringTransformer");
var _splitStringTransformerDefault = parcelHelpers.interopDefault(_splitStringTransformer);
var _removeNonPrintingValuesTransformer = require("./removeNonPrintingValuesTransformer");
var _removeNonPrintingValuesTransformerDefault = parcelHelpers.interopDefault(_removeNonPrintingValuesTransformer);
// tags
var _commaLists = require("./commaLists");
var _commaListsDefault = parcelHelpers.interopDefault(_commaLists);
var _commaListsAnd = require("./commaListsAnd");
var _commaListsAndDefault = parcelHelpers.interopDefault(_commaListsAnd);
var _commaListsOr = require("./commaListsOr");
var _commaListsOrDefault = parcelHelpers.interopDefault(_commaListsOr);
var _html = require("./html");
var _htmlDefault = parcelHelpers.interopDefault(_html);
var _codeBlock = require("./codeBlock");
var _codeBlockDefault = parcelHelpers.interopDefault(_codeBlock);
var _source = require("./source");
var _sourceDefault = parcelHelpers.interopDefault(_source);
var _safeHtml = require("./safeHtml");
var _safeHtmlDefault = parcelHelpers.interopDefault(_safeHtml);
var _oneLine = require("./oneLine");
var _oneLineDefault = parcelHelpers.interopDefault(_oneLine);
var _oneLineTrim = require("./oneLineTrim");
var _oneLineTrimDefault = parcelHelpers.interopDefault(_oneLineTrim);
var _oneLineCommaLists = require("./oneLineCommaLists");
var _oneLineCommaListsDefault = parcelHelpers.interopDefault(_oneLineCommaLists);
var _oneLineCommaListsOr = require("./oneLineCommaListsOr");
var _oneLineCommaListsOrDefault = parcelHelpers.interopDefault(_oneLineCommaListsOr);
var _oneLineCommaListsAnd = require("./oneLineCommaListsAnd");
var _oneLineCommaListsAndDefault = parcelHelpers.interopDefault(_oneLineCommaListsAnd);
var _inlineLists = require("./inlineLists");
var _inlineListsDefault = parcelHelpers.interopDefault(_inlineLists);
var _oneLineInlineLists = require("./oneLineInlineLists");
var _oneLineInlineListsDefault = parcelHelpers.interopDefault(_oneLineInlineLists);
var _stripIndent = require("./stripIndent");
var _stripIndentDefault = parcelHelpers.interopDefault(_stripIndent);
var _stripIndents = require("./stripIndents");
var _stripIndentsDefault = parcelHelpers.interopDefault(_stripIndents);
},{"./TemplateTag":"eMPKI","./trimResultTransformer":"fsxq4","./stripIndentTransformer":"3ixfG","./replaceResultTransformer":"4mqTG","./replaceSubstitutionTransformer":"a8PEj","./replaceStringTransformer":"3lj7E","./inlineArrayTransformer":"igFg8","./splitStringTransformer":"7P0fW","./removeNonPrintingValuesTransformer":"5Xc1J","./commaLists":"a08rc","./commaListsAnd":"7n80v","./commaListsOr":"clT4C","./html":"2nscR","./codeBlock":"cP0yl","./source":"frDU5","./safeHtml":"xXiw1","./oneLine":"BHRTO","./oneLineTrim":"bt39h","./oneLineCommaLists":"58P7b","./oneLineCommaListsOr":"77xi1","./oneLineCommaListsAnd":"161R3","./inlineLists":"8oRMO","./oneLineInlineLists":"aVbcC","./stripIndent":"jp1SD","./stripIndents":"3sPMj","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"eMPKI":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _templateTagDefault.default));
var _templateTag = require("./TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
},{"./TemplateTag":"imZQD","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"imZQD":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _createClass = function() {
function defineProperties(target, props) {
for(var i = 0; i < props.length; i++){
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function(Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var _templateObject = _taggedTemplateLiteral([
'',
''
], [
'',
''
]);
function _taggedTemplateLiteral(strings, raw) {
return Object.freeze(Object.defineProperties(strings, {
raw: {
value: Object.freeze(raw)
}
}));
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
}
/**
* @class TemplateTag
* @classdesc Consumes a pipeline of composable transformer plugins and produces a template tag.
*/ var TemplateTag = function() {
/**
* constructs a template tag
* @constructs TemplateTag
* @param {...Object} [...transformers] - an array or arguments list of transformers
* @return {Function} - a template tag
*/ function TemplateTag() {
var _this = this;
for(var _len = arguments.length, transformers = Array(_len), _key = 0; _key < _len; _key++)transformers[_key] = arguments[_key];
_classCallCheck(this, TemplateTag);
this.tag = function(strings) {
for(var _len2 = arguments.length, expressions = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++)expressions[_key2 - 1] = arguments[_key2];
if (typeof strings === 'function') // if the first argument passed is a function, assume it is a template tag and return
// an intermediary tag that processes the template using the aforementioned tag, passing the
// result to our tag
return _this.interimTag.bind(_this, strings);
if (typeof strings === 'string') // if the first argument passed is a string, just transform it
return _this.transformEndResult(strings);
// else, return a transformed end result of processing the template with our tag
strings = strings.map(_this.transformString.bind(_this));
return _this.transformEndResult(strings.reduce(_this.processSubstitutions.bind(_this, expressions)));
};
// if first argument is an array, extrude it as a list of transformers
if (transformers.length > 0 && Array.isArray(transformers[0])) transformers = transformers[0];
// if any transformers are functions, this means they are not initiated - automatically initiate them
this.transformers = transformers.map(function(transformer) {
return typeof transformer === 'function' ? transformer() : transformer;
});
// return an ES2015 template tag
return this.tag;
}
/**
* Applies all transformers to a template literal tagged with this method.
* If a function is passed as the first argument, assumes the function is a template tag
* and applies it to the template, returning a template tag.
* @param {(Function|String|Array<String>)} strings - Either a template tag or an array containing template strings separated by identifier
* @param {...*} ...expressions - Optional list of substitution values.
* @return {(String|Function)} - Either an intermediary tag function or the results of processing the template.
*/ _createClass(TemplateTag, [
{
key: 'interimTag',
/**
* An intermediary template tag that receives a template tag and passes the result of calling the template with the received
* template tag to our own template tag.
* @param {Function} nextTag - the received template tag
* @param {Array<String>} template - the template to process
* @param {...*} ...substitutions - `substitutions` is an array of all substitutions in the template
* @return {*} - the final processed value
*/ value: function interimTag(previousTag, template) {
for(var _len3 = arguments.length, substitutions = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++)substitutions[_key3 - 2] = arguments[_key3];
return this.tag(_templateObject, previousTag.apply(undefined, [
template
].concat(substitutions)));
}
},
{
key: 'processSubstitutions',
value: function processSubstitutions(substitutions, resultSoFar, remainingPart) {
var substitution = this.transformSubstitution(substitutions.shift(), resultSoFar);
return ''.concat(resultSoFar, substitution, remainingPart);
}
},
{
key: 'transformString',
value: function transformString(str) {
var cb = function cb(res, transform) {
return transform.onString ? transform.onString(res) : res;
};
return this.transformers.reduce(cb, str);
}
},
{
key: 'transformSubstitution',
value: function transformSubstitution(substitution, resultSoFar) {
var cb = function cb(res, transform) {
return transform.onSubstitution ? transform.onSubstitution(res, resultSoFar) : res;
};
return this.transformers.reduce(cb, substitution);
}
},
{
key: 'transformEndResult',
value: function transformEndResult(endResult) {
var cb = function cb(res, transform) {
return transform.onEndResult ? transform.onEndResult(res) : res;
};
return this.transformers.reduce(cb, endResult);
}
}
]);
return TemplateTag;
}();
exports.default = TemplateTag;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"fsxq4":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _trimResultTransformerDefault.default));
var _trimResultTransformer = require("./trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
},{"./trimResultTransformer":"fmBaP","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"fmBaP":[function(require,module,exports,__globalThis) {
/**
* TemplateTag transformer that trims whitespace on the end result of a tagged template
* @param {String} side = '' - The side of the string to trim. Can be 'start' or 'end' (alternatively 'left' or 'right')
* @return {Object} - a TemplateTag transformer
*/ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var trimResultTransformer = function trimResultTransformer() {
var side = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
return {
onEndResult: function onEndResult(endResult) {
if (side === '') return endResult.trim();
side = side.toLowerCase();
if (side === 'start' || side === 'left') return endResult.replace(/^\s*/, '');
if (side === 'end' || side === 'right') return endResult.replace(/\s*$/, '');
throw new Error('Side not supported: ' + side);
}
};
};
exports.default = trimResultTransformer;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"3ixfG":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _stripIndentTransformerDefault.default));
var _stripIndentTransformer = require("./stripIndentTransformer");
var _stripIndentTransformerDefault = parcelHelpers.interopDefault(_stripIndentTransformer);
},{"./stripIndentTransformer":"gNFXQ","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"gNFXQ":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
function _toConsumableArray(arr) {
if (Array.isArray(arr)) {
for(var i = 0, arr2 = Array(arr.length); i < arr.length; i++)arr2[i] = arr[i];
return arr2;
} else return Array.from(arr);
}
/**
* strips indentation from a template literal
* @param {String} type = 'initial' - whether to remove all indentation or just leading indentation. can be 'all' or 'initial'
* @return {Object} - a TemplateTag transformer
*/ var stripIndentTransformer = function stripIndentTransformer() {
var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'initial';
return {
onEndResult: function onEndResult(endResult) {
if (type === 'initial') {
// remove the shortest leading indentation from each line
var match = endResult.match(/^[^\S\n]*(?=\S)/gm);
var indent = match && Math.min.apply(Math, _toConsumableArray(match.map(function(el) {
return el.length;
})));
if (indent) {
var regexp = new RegExp('^.{' + indent + '}', 'gm');
return endResult.replace(regexp, '');
}
return endResult;
}
if (type === 'all') // remove all indentation from each line
return endResult.replace(/^[^\S\n]+/gm, '');
throw new Error('Unknown type: ' + type);
}
};
};
exports.default = stripIndentTransformer;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"4mqTG":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _replaceResultTransformerDefault.default));
var _replaceResultTransformer = require("./replaceResultTransformer");
var _replaceResultTransformerDefault = parcelHelpers.interopDefault(_replaceResultTransformer);
},{"./replaceResultTransformer":"2h4vu","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"2h4vu":[function(require,module,exports,__globalThis) {
/**
* Replaces tabs, newlines and spaces with the chosen value when they occur in sequences
* @param {(String|RegExp)} replaceWhat - the value or pattern that should be replaced
* @param {*} replaceWith - the replacement value
* @return {Object} - a TemplateTag transformer
*/ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var replaceResultTransformer = function replaceResultTransformer(replaceWhat, replaceWith) {
return {
onEndResult: function onEndResult(endResult) {
if (replaceWhat == null || replaceWith == null) throw new Error('replaceResultTransformer requires at least 2 arguments.');
return endResult.replace(replaceWhat, replaceWith);
}
};
};
exports.default = replaceResultTransformer;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"a8PEj":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _replaceSubstitutionTransformerDefault.default));
var _replaceSubstitutionTransformer = require("./replaceSubstitutionTransformer");
var _replaceSubstitutionTransformerDefault = parcelHelpers.interopDefault(_replaceSubstitutionTransformer);
},{"./replaceSubstitutionTransformer":"clfdP","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"clfdP":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var replaceSubstitutionTransformer = function replaceSubstitutionTransformer(replaceWhat, replaceWith) {
return {
onSubstitution: function onSubstitution(substitution, resultSoFar) {
if (replaceWhat == null || replaceWith == null) throw new Error('replaceSubstitutionTransformer requires at least 2 arguments.');
// Do not touch if null or undefined
if (substitution == null) return substitution;
else return substitution.toString().replace(replaceWhat, replaceWith);
}
};
};
exports.default = replaceSubstitutionTransformer;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"3lj7E":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _replaceStringTransformerDefault.default));
var _replaceStringTransformer = require("./replaceStringTransformer");
var _replaceStringTransformerDefault = parcelHelpers.interopDefault(_replaceStringTransformer);
},{"./replaceStringTransformer":"dRRVL","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"dRRVL":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var replaceStringTransformer = function replaceStringTransformer(replaceWhat, replaceWith) {
return {
onString: function onString(str) {
if (replaceWhat == null || replaceWith == null) throw new Error('replaceStringTransformer requires at least 2 arguments.');
return str.replace(replaceWhat, replaceWith);
}
};
};
exports.default = replaceStringTransformer;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"igFg8":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _inlineArrayTransformerDefault.default));
var _inlineArrayTransformer = require("./inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
},{"./inlineArrayTransformer":"bYcoM","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"bYcoM":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var defaults = {
separator: '',
conjunction: '',
serial: false
};
/**
* Converts an array substitution to a string containing a list
* @param {String} [opts.separator = ''] - the character that separates each item
* @param {String} [opts.conjunction = ''] - replace the last separator with this
* @param {Boolean} [opts.serial = false] - include the separator before the conjunction? (Oxford comma use-case)
*
* @return {Object} - a TemplateTag transformer
*/ var inlineArrayTransformer = function inlineArrayTransformer() {
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaults;
return {
onSubstitution: function onSubstitution(substitution, resultSoFar) {
// only operate on arrays
if (Array.isArray(substitution)) {
var arrayLength = substitution.length;
var separator = opts.separator;
var conjunction = opts.conjunction;
var serial = opts.serial;
// join each item in the array into a string where each item is separated by separator
// be sure to maintain indentation
var indent = resultSoFar.match(/(\n?[^\S\n]+)$/);
if (indent) substitution = substitution.join(separator + indent[1]);
else substitution = substitution.join(separator + ' ');
// if conjunction is set, replace the last separator with conjunction, but only if there is more than one substitution
if (conjunction && arrayLength > 1) {
var separatorIndex = substitution.lastIndexOf(separator);
substitution = substitution.slice(0, separatorIndex) + (serial ? separator : '') + ' ' + conjunction + substitution.slice(separatorIndex + 1);
}
}
return substitution;
}
};
};
exports.default = inlineArrayTransformer;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"7P0fW":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _splitStringTransformerDefault.default));
var _splitStringTransformer = require("./splitStringTransformer");
var _splitStringTransformerDefault = parcelHelpers.interopDefault(_splitStringTransformer);
},{"./splitStringTransformer":"9cFGT","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"9cFGT":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var splitStringTransformer = function splitStringTransformer(splitBy) {
return {
onSubstitution: function onSubstitution(substitution, resultSoFar) {
if (splitBy != null && typeof splitBy === 'string') {
if (typeof substitution === 'string' && substitution.includes(splitBy)) substitution = substitution.split(splitBy);
} else throw new Error('You need to specify a string character to split by.');
return substitution;
}
};
};
exports.default = splitStringTransformer;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"5Xc1J":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _removeNonPrintingValuesTransformerDefault.default));
var _removeNonPrintingValuesTransformer = require("./removeNonPrintingValuesTransformer");
var _removeNonPrintingValuesTransformerDefault = parcelHelpers.interopDefault(_removeNonPrintingValuesTransformer);
},{"./removeNonPrintingValuesTransformer":"bsSGP","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"bsSGP":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var isValidValue = function isValidValue(x) {
return x != null && !Number.isNaN(x) && typeof x !== 'boolean';
};
var removeNonPrintingValuesTransformer = function removeNonPrintingValuesTransformer() {
return {
onSubstitution: function onSubstitution(substitution) {
if (Array.isArray(substitution)) return substitution.filter(isValidValue);
if (isValidValue(substitution)) return substitution;
return '';
}
};
};
exports.default = removeNonPrintingValuesTransformer;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"a08rc":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _commaListsDefault.default));
var _commaLists = require("./commaLists");
var _commaListsDefault = parcelHelpers.interopDefault(_commaLists);
},{"./commaLists":"6pt22","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"6pt22":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _stripIndentTransformer = require("../stripIndentTransformer");
var _stripIndentTransformerDefault = parcelHelpers.interopDefault(_stripIndentTransformer);
var _inlineArrayTransformer = require("../inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var commaLists = new (0, _templateTagDefault.default)((0, _inlineArrayTransformerDefault.default)({
separator: ','
}), (0, _stripIndentTransformerDefault.default), (0, _trimResultTransformerDefault.default));
exports.default = commaLists;
},{"../TemplateTag":"eMPKI","../stripIndentTransformer":"3ixfG","../inlineArrayTransformer":"igFg8","../trimResultTransformer":"fsxq4","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"7n80v":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _commaListsAndDefault.default));
var _commaListsAnd = require("./commaListsAnd");
var _commaListsAndDefault = parcelHelpers.interopDefault(_commaListsAnd);
},{"./commaListsAnd":"3FPUa","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"3FPUa":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _stripIndentTransformer = require("../stripIndentTransformer");
var _stripIndentTransformerDefault = parcelHelpers.interopDefault(_stripIndentTransformer);
var _inlineArrayTransformer = require("../inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var commaListsAnd = new (0, _templateTagDefault.default)((0, _inlineArrayTransformerDefault.default)({
separator: ',',
conjunction: 'and'
}), (0, _stripIndentTransformerDefault.default), (0, _trimResultTransformerDefault.default));
exports.default = commaListsAnd;
},{"../TemplateTag":"eMPKI","../stripIndentTransformer":"3ixfG","../inlineArrayTransformer":"igFg8","../trimResultTransformer":"fsxq4","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"clT4C":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _commaListsOrDefault.default));
var _commaListsOr = require("./commaListsOr");
var _commaListsOrDefault = parcelHelpers.interopDefault(_commaListsOr);
},{"./commaListsOr":"7ApSA","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"7ApSA":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _stripIndentTransformer = require("../stripIndentTransformer");
var _stripIndentTransformerDefault = parcelHelpers.interopDefault(_stripIndentTransformer);
var _inlineArrayTransformer = require("../inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var commaListsOr = new (0, _templateTagDefault.default)((0, _inlineArrayTransformerDefault.default)({
separator: ',',
conjunction: 'or'
}), (0, _stripIndentTransformerDefault.default), (0, _trimResultTransformerDefault.default));
exports.default = commaListsOr;
},{"../TemplateTag":"eMPKI","../stripIndentTransformer":"3ixfG","../inlineArrayTransformer":"igFg8","../trimResultTransformer":"fsxq4","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"2nscR":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _htmlDefault.default));
var _html = require("./html");
var _htmlDefault = parcelHelpers.interopDefault(_html);
},{"./html":"5R7M9","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"5R7M9":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _stripIndentTransformer = require("../stripIndentTransformer");
var _stripIndentTransformerDefault = parcelHelpers.interopDefault(_stripIndentTransformer);
var _inlineArrayTransformer = require("../inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var _splitStringTransformer = require("../splitStringTransformer");
var _splitStringTransformerDefault = parcelHelpers.interopDefault(_splitStringTransformer);
var _removeNonPrintingValuesTransformer = require("../removeNonPrintingValuesTransformer");
var _removeNonPrintingValuesTransformerDefault = parcelHelpers.interopDefault(_removeNonPrintingValuesTransformer);
var html = new (0, _templateTagDefault.default)((0, _splitStringTransformerDefault.default)('\n'), (0, _removeNonPrintingValuesTransformerDefault.default), (0, _inlineArrayTransformerDefault.default), (0, _stripIndentTransformerDefault.default), (0, _trimResultTransformerDefault.default));
exports.default = html;
},{"../TemplateTag":"eMPKI","../stripIndentTransformer":"3ixfG","../inlineArrayTransformer":"igFg8","../trimResultTransformer":"fsxq4","../splitStringTransformer":"7P0fW","../removeNonPrintingValuesTransformer":"5Xc1J","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"cP0yl":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _htmlDefault.default));
var _html = require("../html");
var _htmlDefault = parcelHelpers.interopDefault(_html);
},{"../html":"2nscR","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"frDU5":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _htmlDefault.default));
var _html = require("../html");
var _htmlDefault = parcelHelpers.interopDefault(_html);
},{"../html":"2nscR","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"xXiw1":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _safeHtmlDefault.default));
var _safeHtml = require("./safeHtml");
var _safeHtmlDefault = parcelHelpers.interopDefault(_safeHtml);
},{"./safeHtml":"d7n5x","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"d7n5x":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _stripIndentTransformer = require("../stripIndentTransformer");
var _stripIndentTransformerDefault = parcelHelpers.interopDefault(_stripIndentTransformer);
var _inlineArrayTransformer = require("../inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var _splitStringTransformer = require("../splitStringTransformer");
var _splitStringTransformerDefault = parcelHelpers.interopDefault(_splitStringTransformer);
var _replaceSubstitutionTransformer = require("../replaceSubstitutionTransformer");
var _replaceSubstitutionTransformerDefault = parcelHelpers.interopDefault(_replaceSubstitutionTransformer);
var safeHtml = new (0, _templateTagDefault.default)((0, _splitStringTransformerDefault.default)('\n'), (0, _inlineArrayTransformerDefault.default), (0, _stripIndentTransformerDefault.default), (0, _trimResultTransformerDefault.default), (0, _replaceSubstitutionTransformerDefault.default)(/&/g, '&'), (0, _replaceSubstitutionTransformerDefault.default)(/</g, '<'), (0, _replaceSubstitutionTransformerDefault.default)(/>/g, '>'), (0, _replaceSubstitutionTransformerDefault.default)(/"/g, '"'), (0, _replaceSubstitutionTransformerDefault.default)(/'/g, '''), (0, _replaceSubstitutionTransformerDefault.default)(/`/g, '`'));
exports.default = safeHtml;
},{"../TemplateTag":"eMPKI","../stripIndentTransformer":"3ixfG","../inlineArrayTransformer":"igFg8","../trimResultTransformer":"fsxq4","../splitStringTransformer":"7P0fW","../replaceSubstitutionTransformer":"a8PEj","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"BHRTO":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _oneLineDefault.default));
var _oneLine = require("./oneLine");
var _oneLineDefault = parcelHelpers.interopDefault(_oneLine);
},{"./oneLine":"cY92v","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"cY92v":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var _replaceResultTransformer = require("../replaceResultTransformer");
var _replaceResultTransformerDefault = parcelHelpers.interopDefault(_replaceResultTransformer);
var oneLine = new (0, _templateTagDefault.default)((0, _replaceResultTransformerDefault.default)(/(?:\n(?:\s*))+/g, ' '), (0, _trimResultTransformerDefault.default));
exports.default = oneLine;
},{"../TemplateTag":"eMPKI","../trimResultTransformer":"fsxq4","../replaceResultTransformer":"4mqTG","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"bt39h":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _oneLineTrimDefault.default));
var _oneLineTrim = require("./oneLineTrim");
var _oneLineTrimDefault = parcelHelpers.interopDefault(_oneLineTrim);
},{"./oneLineTrim":"8B3yq","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"8B3yq":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var _replaceResultTransformer = require("../replaceResultTransformer");
var _replaceResultTransformerDefault = parcelHelpers.interopDefault(_replaceResultTransformer);
var oneLineTrim = new (0, _templateTagDefault.default)((0, _replaceResultTransformerDefault.default)(/(?:\n\s*)/g, ''), (0, _trimResultTransformerDefault.default));
exports.default = oneLineTrim;
},{"../TemplateTag":"eMPKI","../trimResultTransformer":"fsxq4","../replaceResultTransformer":"4mqTG","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"58P7b":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _oneLineCommaListsDefault.default));
var _oneLineCommaLists = require("./oneLineCommaLists");
var _oneLineCommaListsDefault = parcelHelpers.interopDefault(_oneLineCommaLists);
},{"./oneLineCommaLists":"epfVl","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"epfVl":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _inlineArrayTransformer = require("../inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var _replaceResultTransformer = require("../replaceResultTransformer");
var _replaceResultTransformerDefault = parcelHelpers.interopDefault(_replaceResultTransformer);
var oneLineCommaLists = new (0, _templateTagDefault.default)((0, _inlineArrayTransformerDefault.default)({
separator: ','
}), (0, _replaceResultTransformerDefault.default)(/(?:\s+)/g, ' '), (0, _trimResultTransformerDefault.default));
exports.default = oneLineCommaLists;
},{"../TemplateTag":"eMPKI","../inlineArrayTransformer":"igFg8","../trimResultTransformer":"fsxq4","../replaceResultTransformer":"4mqTG","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"77xi1":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _oneLineCommaListsOrDefault.default));
var _oneLineCommaListsOr = require("./oneLineCommaListsOr");
var _oneLineCommaListsOrDefault = parcelHelpers.interopDefault(_oneLineCommaListsOr);
},{"./oneLineCommaListsOr":"iX39G","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"iX39G":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _inlineArrayTransformer = require("../inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var _replaceResultTransformer = require("../replaceResultTransformer");
var _replaceResultTransformerDefault = parcelHelpers.interopDefault(_replaceResultTransformer);
var oneLineCommaListsOr = new (0, _templateTagDefault.default)((0, _inlineArrayTransformerDefault.default)({
separator: ',',
conjunction: 'or'
}), (0, _replaceResultTransformerDefault.default)(/(?:\s+)/g, ' '), (0, _trimResultTransformerDefault.default));
exports.default = oneLineCommaListsOr;
},{"../TemplateTag":"eMPKI","../inlineArrayTransformer":"igFg8","../trimResultTransformer":"fsxq4","../replaceResultTransformer":"4mqTG","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"161R3":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _oneLineCommaListsAndDefault.default));
var _oneLineCommaListsAnd = require("./oneLineCommaListsAnd");
var _oneLineCommaListsAndDefault = parcelHelpers.interopDefault(_oneLineCommaListsAnd);
},{"./oneLineCommaListsAnd":"hnE20","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"hnE20":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _inlineArrayTransformer = require("../inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var _replaceResultTransformer = require("../replaceResultTransformer");
var _replaceResultTransformerDefault = parcelHelpers.interopDefault(_replaceResultTransformer);
var oneLineCommaListsAnd = new (0, _templateTagDefault.default)((0, _inlineArrayTransformerDefault.default)({
separator: ',',
conjunction: 'and'
}), (0, _replaceResultTransformerDefault.default)(/(?:\s+)/g, ' '), (0, _trimResultTransformerDefault.default));
exports.default = oneLineCommaListsAnd;
},{"../TemplateTag":"eMPKI","../inlineArrayTransformer":"igFg8","../trimResultTransformer":"fsxq4","../replaceResultTransformer":"4mqTG","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"8oRMO":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _inlineListsDefault.default));
var _inlineLists = require("./inlineLists");
var _inlineListsDefault = parcelHelpers.interopDefault(_inlineLists);
},{"./inlineLists":"8M18q","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"8M18q":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _stripIndentTransformer = require("../stripIndentTransformer");
var _stripIndentTransformerDefault = parcelHelpers.interopDefault(_stripIndentTransformer);
var _inlineArrayTransformer = require("../inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var inlineLists = new (0, _templateTagDefault.default)((0, _inlineArrayTransformerDefault.default), (0, _stripIndentTransformerDefault.default), (0, _trimResultTransformerDefault.default));
exports.default = inlineLists;
},{"../TemplateTag":"eMPKI","../stripIndentTransformer":"3ixfG","../inlineArrayTransformer":"igFg8","../trimResultTransformer":"fsxq4","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"aVbcC":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _oneLineInlineListsDefault.default));
var _oneLineInlineLists = require("./oneLineInlineLists");
var _oneLineInlineListsDefault = parcelHelpers.interopDefault(_oneLineInlineLists);
},{"./oneLineInlineLists":"eQUGl","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"eQUGl":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _inlineArrayTransformer = require("../inlineArrayTransformer");
var _inlineArrayTransformerDefault = parcelHelpers.interopDefault(_inlineArrayTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var _replaceResultTransformer = require("../replaceResultTransformer");
var _replaceResultTransformerDefault = parcelHelpers.interopDefault(_replaceResultTransformer);
var oneLineInlineLists = new (0, _templateTagDefault.default)((0, _inlineArrayTransformerDefault.default), (0, _replaceResultTransformerDefault.default)(/(?:\s+)/g, ' '), (0, _trimResultTransformerDefault.default));
exports.default = oneLineInlineLists;
},{"../TemplateTag":"eMPKI","../inlineArrayTransformer":"igFg8","../trimResultTransformer":"fsxq4","../replaceResultTransformer":"4mqTG","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"jp1SD":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _stripIndentDefault.default));
var _stripIndent = require("./stripIndent");
var _stripIndentDefault = parcelHelpers.interopDefault(_stripIndent);
},{"./stripIndent":"1vkix","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"1vkix":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _stripIndentTransformer = require("../stripIndentTransformer");
var _stripIndentTransformerDefault = parcelHelpers.interopDefault(_stripIndentTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var stripIndent = new (0, _templateTagDefault.default)((0, _stripIndentTransformerDefault.default), (0, _trimResultTransformerDefault.default));
exports.default = stripIndent;
},{"../TemplateTag":"eMPKI","../stripIndentTransformer":"3ixfG","../trimResultTransformer":"fsxq4","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"3sPMj":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "default", ()=>(0, _stripIndentsDefault.default));
var _stripIndents = require("./stripIndents");
var _stripIndentsDefault = parcelHelpers.interopDefault(_stripIndents);
},{"./stripIndents":"4EZK8","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"4EZK8":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
var _templateTag = require("../TemplateTag");
var _templateTagDefault = parcelHelpers.interopDefault(_templateTag);
var _stripIndentTransformer = require("../stripIndentTransformer");
var _stripIndentTransformerDefault = parcelHelpers.interopDefault(_stripIndentTransformer);
var _trimResultTransformer = require("../trimResultTransformer");
var _trimResultTransformerDefault = parcelHelpers.interopDefault(_trimResultTransformer);
var stripIndents = new (0, _templateTagDefault.default)((0, _stripIndentTransformerDefault.default)('all'), (0, _trimResultTransformerDefault.default));
exports.default = stripIndents;
},{"../TemplateTag":"eMPKI","../stripIndentTransformer":"3ixfG","../trimResultTransformer":"fsxq4","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"6BFkB":[function(require,module,exports,__globalThis) {
var global = arguments[3];
(function(a, b) {
if ("function" == typeof define && define.amd) define([], b);
else b();
})(this, function() {
"use strict";
function b(a, b) {
return "undefined" == typeof b ? b = {
autoBom: !1
} : "object" != typeof b && (console.warn("Deprecated: Expected third argument to be a object"), b = {
autoBom: !b
}), b.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type) ? new Blob([
"\uFEFF",
a
], {
type: a.type
}) : a;
}
function c(a, b, c) {
var d = new XMLHttpRequest;
d.open("GET", a), d.responseType = "blob", d.onload = function() {
g(d.response, b, c);
}, d.onerror = function() {
console.error("could not download file");
}, d.send();
}
function d(a) {
var b = new XMLHttpRequest;
b.open("HEAD", a, !1);
try {
b.send();
} catch (a) {}
return 200 <= b.status && 299 >= b.status;
}
function e(a) {
try {
a.dispatchEvent(new MouseEvent("click"));
} catch (c) {
var b = document.createEvent("MouseEvents");
b.initMouseEvent("click", !0, !0, window, 0, 0, 0, 80, 20, !1, !1, !1, !1, 0, null), a.dispatchEvent(b);
}
}
var f = "object" == typeof window && window.window === window ? window : "object" == typeof self && self.self === self ? self : "object" == typeof global && global.global === global ? global : void 0, a = f.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent), g = f.saveAs || ("object" != typeof window || window !== f ? function() {} : "download" in HTMLAnchorElement.prototype && !a ? function(b, g, h) {
var i = f.URL || f.webkitURL, j = document.createElement("a");
g = g || b.name || "download", j.download = g, j.rel = "noopener", "string" == typeof b ? (j.href = b, j.origin === location.origin ? e(j) : d(j.href) ? c(b, g, h) : e(j, j.target = "_blank")) : (j.href = i.createObjectURL(b), setTimeout(function() {
i.revokeObjectURL(j.href);
}, 4E4), setTimeout(function() {
e(j);
}, 0));
} : "msSaveOrOpenBlob" in navigator ? function(f, g, h) {
if (g = g || f.name || "download", "string" != typeof f) navigator.msSaveOrOpenBlob(b(f, h), g);
else if (d(f)) c(f, g, h);
else {
var i = document.createElement("a");
i.href = f, i.target = "_blank", setTimeout(function() {
e(i);
});
}
} : function(b, d, e, g) {
if (g = g || open("", "_blank"), g && (g.document.title = g.document.body.innerText = "downloading..."), "string" == typeof b) return c(b, d, e);
var h = "application/octet-stream" === b.type, i = /constructor/i.test(f.HTMLElement) || f.safari, j = /CriOS\/[\d]+/.test(navigator.userAgent);
if ((j || h && i || a) && "undefined" != typeof FileReader) {
var k = new FileReader;
k.onloadend = function() {
var a = k.result;
a = j ? a : a.replace(/^data:[^;]*;/, "data:attachment/file;"), g ? g.location.href = a : location = a, g = null;
}, k.readAsDataURL(b);
} else {
var l = f.URL || f.webkitURL, m = l.createObjectURL(b);
g ? g.location = m : location.href = m, g = null, setTimeout(function() {
l.revokeObjectURL(m);
}, 4E4);
}
});
f.saveAs = g.saveAs = g, module.exports = g;
});
},{}],"hmOqA":[function(require,module,exports,__globalThis) {
module.exports = JSZip;
},{}],"3FwNw":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "saveMarkersAndSettings", ()=>(0, _saveLoad.saveMarkersAndSettings));
parcelHelpers.export(exports, "getClipperInputData", ()=>(0, _saveLoad.getClipperInputData));
parcelHelpers.export(exports, "getClipperInputJSON", ()=>(0, _saveLoad.getClipperInputJSON));
parcelHelpers.export(exports, "isVariableSpeed", ()=>(0, _saveLoad.isVariableSpeed));
parcelHelpers.export(exports, "deleteMarkersDataCommands", ()=>(0, _saveLoad.deleteMarkersDataCommands));
parcelHelpers.export(exports, "toggleMarkersDataCommands", ()=>(0, _saveLoad.toggleMarkersDataCommands));
parcelHelpers.export(exports, "injectYtcWidget", ()=>(0, _saveLoad.injectYtcWidget));
parcelHelpers.export(exports, "loadClipperInputJSON", ()=>(0, _saveLoad.loadClipperInputJSON));
parcelHelpers.export(exports, "applyClipperInput", ()=>(0, _saveLoad.applyClipperInput));
parcelHelpers.export(exports, "addMarkerPairs", ()=>(0, _saveLoad.addMarkerPairs));
parcelHelpers.export(exports, "copyShareableUrl", ()=>(0, _shareUrl.copyShareableUrl));
parcelHelpers.export(exports, "tryLoadSharedMarkers", ()=>(0, _shareUrl.tryLoadSharedMarkers));
var _saveLoad = require("./save-load");
var _shareUrl = require("./share-url");
},{"./save-load":"Sn5Vp","./share-url":"1L9cT","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"Sn5Vp":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "saveMarkersAndSettings", ()=>saveMarkersAndSettings);
parcelHelpers.export(exports, "getClipperInputData", ()=>getClipperInputData);
parcelHelpers.export(exports, "getClipperInputJSON", ()=>getClipperInputJSON);
parcelHelpers.export(exports, "isVariableSpeed", ()=>isVariableSpeed);
parcelHelpers.export(exports, "deleteMarkersDataCommands", ()=>deleteMarkersDataCommands);
parcelHelpers.export(exports, "toggleMarkersDataCommands", ()=>toggleMarkersDataCommands);
parcelHelpers.export(exports, "injectYtcWidget", ()=>injectYtcWidget);
/** Apply an already-parsed ClipperInput to appState. Safe because:
* - parser stripped __proto__/constructor/prototype keys at load boundary.
* - parser allowlisted to known Settings keys only.
* - toApplicableSettings drops source-environment keys (videoID/title/...).
* - Object spread creates own data properties, never triggers setters. */ parcelHelpers.export(exports, "applyClipperInput", ()=>applyClipperInput);
parcelHelpers.export(exports, "loadClipperInputJSON", ()=>loadClipperInputJSON);
parcelHelpers.export(exports, "addMarkerPairs", ()=>addMarkerPairs);
var _fileSaver = require("file-saver");
var _appState = require("../appState");
var _autoSave = require("../auto-save");
var _litHtml = require("lit-html");
var _cropUtils = require("../crop-utils");
var _util = require("../util/util");
var _ytClipper = require("../yt_clipper");
var _markers = require("../markers");
var _loadMarkersReview = require("./load-markers-review");
var _parseClipperInput = require("./parse-clipper-input");
function saveMarkersAndSettings() {
const settingsJSON = getClipperInputJSON();
const blob = new Blob([
settingsJSON
], {
type: 'application/json;charset=utf-8'
});
(0, _fileSaver.saveAs)(blob, `${(0, _appState.appState).settings.titleSuffix || `[${(0, _appState.appState).settings.videoID}]`}.json`);
}
function getClipperInputData(date) {
(0, _appState.appState).markerPairs.forEach((markerPair, index)=>{
const speed = markerPair.speed;
if (typeof speed === 'string') {
markerPair.speed = Number(speed);
console.log(`Converted marker pair ${index}'s speed from String to Number`);
}
});
const markerPairsNumbered = (0, _appState.appState).markerPairs.map((markerPair, idx)=>{
const markerPairNumbered = {
number: idx + 1,
...markerPair,
speedMapLoop: undefined,
speedMap: isVariableSpeed(markerPair.speedMap) ? markerPair.speedMap : undefined,
speedChartLoop: undefined,
cropMap: !(0, _cropUtils.isStaticCrop)(markerPair.cropMap) ? markerPair.cropMap : undefined,
cropChartLoop: undefined,
undoredo: undefined,
startNumbering: undefined,
endNumbering: undefined,
moveHistory: undefined,
outputDuration: undefined
};
return markerPairNumbered;
});
const clipperInputData = {
...(0, _appState.appState).settings,
version: (0, _appState.__version__),
markerPairs: markerPairsNumbered,
date: date ?? undefined
};
return clipperInputData;
}
function getClipperInputJSON() {
const settingsJSON = JSON.stringify(getClipperInputData(), undefined, 2);
return settingsJSON;
}
function isVariableSpeed(speedMap) {
if (speedMap.length < 2) return false;
const isVarSpeed = speedMap.some((speedPoint, i)=>{
if (i === speedMap.length - 1) return false;
return speedPoint.y !== speedMap[i + 1].y;
});
return isVarSpeed;
}
function deleteMarkersDataCommands() {
const markersDataCommandsDiv = document.getElementById('markers-data-commands-div');
if (markersDataCommandsDiv) {
(0, _util.deleteElement)(markersDataCommandsDiv);
return true;
}
return false;
}
const markersUploadTemplate = (0, _litHtml.html)`
<fieldset>
<legend>Load markers data from an uploaded markers .json file.</legend>
<input type="file" id="markers-json-input" />
<input type="button" id="upload-markers-json" value="Load" />
</fieldset>
`;
function RestoreMarkersTemplate(markersDataFilesCount) {
return (0, _litHtml.html)`
<fieldset>
<legend>Restore auto-saved markers data from browser local storage.</legend>
<input type="button" id="restore-markers-data" value="Restore" />
</fieldset>
<fieldset>
<legend>
Zip and download ${markersDataFilesCount} auto-saved markers data files from browser local
storage.
</legend>
<input type="button" id="download-markers-data" value="Download" />
</fieldset>
`;
}
const clearMarkersTemplate = (0, _litHtml.html)`
<fieldset>
<legend>Clear all markers data files from browser local storage.</legend>
<input type="button" id="clear-markers-data" value="Clear" style="color:red" />
</fieldset>
`;
function toggleMarkersDataCommands() {
if (!deleteMarkersDataCommands()) {
const markersDataCommandsDiv = document.createElement('div');
markersDataCommandsDiv.setAttribute('id', 'markers-data-commands-div');
const markersUploadDiv = document.createElement('div');
markersUploadDiv.setAttribute('class', 'long-msg-div');
(0, _litHtml.render)(markersUploadTemplate, markersUploadDiv);
const restoreMarkersDataDiv = document.createElement('div');
restoreMarkersDataDiv.setAttribute('class', 'long-msg-div');
const markersDataFiles = (0, _autoSave.getMarkersDataEntriesFromLocalStorage)();
(0, _litHtml.render)(RestoreMarkersTemplate(markersDataFiles?.length), restoreMarkersDataDiv);
const clearMarkersDataDiv = document.createElement('div');
clearMarkersDataDiv.setAttribute('class', 'long-msg-div');
(0, _litHtml.render)(clearMarkersTemplate, clearMarkersDataDiv);
markersDataCommandsDiv.appendChild(markersUploadDiv);
markersDataCommandsDiv.appendChild(restoreMarkersDataDiv);
markersDataCommandsDiv.appendChild(clearMarkersDataDiv);
injectYtcWidget(markersDataCommandsDiv);
const fileUploadButton = document.getElementById('upload-markers-json');
const restoreMarkersDataButton = document.getElementById('restore-markers-data');
const downloadMarkersDataButton = document.getElementById('download-markers-data');
const clearMarkersDataButton = document.getElementById('clear-markers-data');
(0, _util.assertDefined)(fileUploadButton, 'Expected upload-markers-json button');
(0, _util.assertDefined)(restoreMarkersDataButton, 'Expected restore-markers-data button');
(0, _util.assertDefined)(downloadMarkersDataButton, 'Expected download-markers-data button');
(0, _util.assertDefined)(clearMarkersDataButton, 'Expected clear-markers-data button');
fileUploadButton.onclick = loadMarkersJson;
restoreMarkersDataButton.onclick = (0, _autoSave.loadClipperInputDataFromLocalStorage);
downloadMarkersDataButton.onclick = (0, _autoSave.downloadAutoSavedMarkersData);
clearMarkersDataButton.onclick = (0, _autoSave.clearYTClipperLocalStorage);
}
}
function injectYtcWidget(widget) {
(0, _ytClipper.updateSettingsEditorHook)();
if ((0, _ytClipper.isTheatreMode)()) (0, _appState.appState).settingsEditorHook.insertAdjacentElement('afterend', widget);
else {
widget.style.position = 'relative';
(0, _appState.appState).settingsEditorHook.insertAdjacentElement('beforebegin', widget);
}
}
function loadMarkersJson() {
const input = document.getElementById('markers-json-input');
if (!input.files || input.files.length === 0) return;
const file = input.files[0];
const fr = new FileReader();
fr.onload = (e)=>{
(0, _util.assertDefined)(e.target, 'Expected FileReader event target');
const text = typeof e.target.result === 'string' ? e.target.result : '';
showMarkersJsonReviewModal(file.name, text);
};
fr.readAsText(file);
deleteMarkersDataCommands();
}
function showMarkersJsonReviewModal(fileName, text) {
let result;
try {
result = (0, _parseClipperInput.parseClipperInputJSON)(text);
} catch (err) {
if (err instanceof (0, _parseClipperInput.ClipperInputValidationError)) {
console.error('Failed to parse clipper input', err);
(0, _util.flashMessage)(`${fileName}: ${err.message}`, 'red');
return;
}
throw err;
}
const pairCount = result.input.markerPairs.length;
(0, _loadMarkersReview.showLoadMarkersReviewModal)({
modalTitle: 'Load markers from file?',
warning: `\u{26A0} Review before loading. Loading will overwrite current settings and add ${pairCount} marker pair(s).`,
sourceLabel: `file: ${fileName}`,
payload: result.input,
issues: result.issues,
onLoad: ()=>{
applyClipperInput(result.input);
(0, _util.flashMessage)(`Loaded ${pairCount} marker pair(s) from ${fileName}.`, 'green');
}
});
}
function applyClipperInput(input) {
(0, _appState.appState).settings = {
...(0, _appState.appState).settings,
...(0, _parseClipperInput.toApplicableSettings)(input)
};
addMarkerPairs(input.markerPairs);
}
function loadClipperInputJSON(json) {
let result;
try {
result = (0, _parseClipperInput.parseClipperInputJSON)(json);
} catch (err) {
if (err instanceof (0, _parseClipperInput.ClipperInputValidationError)) {
console.error('Failed to parse clipper input', err);
(0, _util.flashMessage)(err.message, 'red');
return;
}
throw err;
}
(0, _util.flashMessage)('Loading markers data...', 'green');
applyClipperInput(result.input);
}
function addMarkerPairs(markerPairs) {
markerPairs.forEach((markerPair)=>{
const startMarkerConfig = {
time: markerPair.start,
type: 'start'
};
const endMarkerConfig = {
time: markerPair.end,
type: 'end',
speed: markerPair.speed,
speedMap: markerPair.speedMap,
speedChartLoop: markerPair.speedChartLoop,
crop: markerPair.crop,
cropMap: markerPair.cropMap,
cropChartLoop: markerPair.cropChartLoop,
enableZoomPan: markerPair.enableZoomPan,
overrides: markerPair.overrides,
undoredo: markerPair.undoredo
};
(0, _markers.addMarker)(startMarkerConfig);
(0, _markers.addMarker)(endMarkerConfig);
});
}
},{"file-saver":"6BFkB","../appState":"g0AlP","../auto-save":"dz8Rw","lit-html":"9fQBw","../crop-utils":"k2gwb","../util/util":"99arg","../yt_clipper":"6vE65","../markers":"EQEoZ","./load-markers-review":"1yueE","./parse-clipper-input":"hnA8h","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"1yueE":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "showLoadMarkersReviewModal", ()=>showLoadMarkersReviewModal);
var _commonTags = require("common-tags");
var _litHtml = require("lit-html");
var _refJs = require("lit-html/directives/ref.js");
var _modal = require("../components/modal");
var _util = require("../util/util");
var _parseClipperInput = require("./parse-clipper-input");
var _scanSuspiciousContent = require("./scan-suspicious-content");
const MODAL_ID = 'shared-markers-modal';
function deleteReviewModal() {
const el = document.getElementById(MODAL_ID);
if (el) (0, _util.deleteElement)(el);
}
// Populate the `<pre>` JSON preview using a single native `textContent`
// write. No lit-html interpolation, no library touches the attacker-
// controlled JSON string — absolute-safe path even against hypothetical
// library zero-days at the review stage.
function populateJsonPreview(container, payload) {
container.textContent = JSON.stringify(payload, null, 2);
}
function joinCodeElements(keys) {
return (0, _litHtml.html)`${keys.map((k, i)=>(0, _litHtml.html)`${i > 0 ? ', ' : ''}<code>${k}</code>`)}`;
}
function describeFormatIssue(issue) {
switch(issue.kind){
case 'unexpectedFields':
return (0, _litHtml.html)`<code>${issue.path}</code>: removed unknown field(s)
${joinCodeElements(issue.keys)}`;
case 'dangerousKeys':
return (0, _litHtml.html)`<code>${issue.path}</code>: blocked reserved JavaScript key(s)
${joinCodeElements(issue.keys)} (could modify internal runtime behavior)`;
case 'invalidPoint':
return (0, _litHtml.html)`<code>${issue.path}</code>: dropped invalid point`;
}
}
function ReviewModal(p, bindPre) {
const dangerousIssues = p.issues.filter((0, _parseClipperInput.isDangerousIssue));
const infoIssues = p.issues.filter((i)=>!(0, _parseClipperInput.isDangerousIssue)(i));
// XSS-severity bucket: HTML content scan findings + dangerousKeys issues.
const showDanger = p.findings.length > 0 || dangerousIssues.length > 0;
const dangerSection = showDanger ? (0, _litHtml.html)`
<div class="ytc-share-modal-findings">
<div class="ytc-share-modal-findings-header">
⚠ This data contains content that could be trying to run code.
</div>
<div class="ytc-share-modal-findings-callout">
If you don't trust the source of this data, cancel rather than load.
</div>
${p.findings.length ? (0, _litHtml.html)`<div>
Text that looks like executable HTML or scripts (e.g.
<code><iframe></code>, <code>onerror=</code>, or
<code>javascript:</code> URLs).
<br />
This kind of content does not belong in normal markers data and its presence
suggests tampering. Although we do our best to treat this content as plain text and
never execute it,
<strong>treat the source as untrusted and prefer to cancel.</strong>
</div>` : (0, _litHtml.nothing)}
<ul>
${p.findings.map((f)=>(0, _litHtml.html)`<li><code>${f.path}</code>: ${joinCodeElements(f.items)}</li>`)}
${dangerousIssues.map((issue)=>(0, _litHtml.html)`<li>${describeFormatIssue(issue)}</li>`)}
</ul>
</div>
` : (0, _litHtml.nothing);
// Informational bucket: fields that don't match the expected format.
const infoSection = infoIssues.length ? (0, _litHtml.html)`
<div class="ytc-share-modal-parse-issues">
<div class="ytc-share-modal-parse-issues-header">
ℹ Some fields didn't match the markers format and were cleaned up. Usually harmless
(e.g. files from a newer version, or hand-edited JSON):
</div>
<ul>
${infoIssues.map((issue)=>(0, _litHtml.html)`<li>${describeFormatIssue(issue)}</li>`)}
</ul>
</div>
` : (0, _litHtml.nothing);
const body = (0, _litHtml.html)`
${dangerSection}${infoSection}
<pre class="ytc-share-modal-json" ${(0, _refJs.ref)(bindPre)}></pre>
`;
const actions = (0, _litHtml.html)`
<input type="button" class="ytc-share-modal-copy" value="Copy JSON" @click=${p.onCopy} />
<input type="button" class="ytc-share-modal-load" value="Load" @click=${p.onLoad} />
<input type="button" class="ytc-share-modal-cancel" value="Cancel" @click=${p.onDismiss} />
`;
return (0, _modal.ModalShell)({
id: MODAL_ID,
extraClass: 'ytc-share-modal',
title: p.title,
warning: p.warning,
children: body,
actions,
onBackdropClick: p.onBackdropClick
});
}
function showLoadMarkersReviewModal(opts) {
deleteReviewModal();
const host = document.createElement('div');
document.body.appendChild(host);
const findings = (0, _scanSuspiciousContent.scanSuspiciousContent)(opts.payload);
const cleanup = ()=>{
document.removeEventListener('keydown', onKeydown, true);
deleteReviewModal();
if (host.parentNode) host.parentNode.removeChild(host);
};
const dismiss = ()=>{
cleanup();
opts.onDismiss?.();
};
const onKeydown = (e)=>{
if (e.key === 'Escape') {
e.stopImmediatePropagation();
e.preventDefault();
dismiss();
}
};
document.addEventListener('keydown', onKeydown, true);
const onLoad = ()=>{
if (findings.length > 0) {
const confirmed = confirm((0, _commonTags.stripIndent)`
⚠ Data contains HTML-like markup in ${findings.length} field(s).
Our renderer treats these as text (no execution), but they may be unexpected.
Load anyway?
`);
if (!confirmed) return;
}
try {
opts.onLoad();
cleanup();
} catch (err) {
console.error(`Failed to apply ${opts.sourceLabel}`, err);
(0, _util.flashMessage)(`Failed to apply data from ${opts.sourceLabel}. See console.`, 'red');
}
};
const onCopy = ()=>{
(0, _util.copyToClipboard)(JSON.stringify(opts.payload, null, 2));
(0, _util.flashMessage)('Copied JSON to clipboard.', 'green');
};
const bindPre = (el)=>{
if (!el) return;
populateJsonPreview(el, opts.payload);
};
(0, _litHtml.render)(ReviewModal({
title: opts.modalTitle,
warning: opts.warning,
findings,
issues: opts.issues ?? [],
onLoad,
onDismiss: dismiss,
onCopy,
onBackdropClick: dismiss
}, bindPre), host);
}
},{"common-tags":"4Q3cx","lit-html":"9fQBw","lit-html/directives/ref.js":"9tuBT","../components/modal":"cWB8N","../util/util":"99arg","./parse-clipper-input":"hnA8h","./scan-suspicious-content":"iuaXf","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"9tuBT":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "createRef", ()=>e);
parcelHelpers.export(exports, "ref", ()=>n);
var _litHtmlJs = require("../lit-html.js");
var _asyncDirectiveJs = require("../async-directive.js");
var _directiveJs = require("../directive.js");
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/ const e = ()=>new h;
class h {
}
const o = new WeakMap, n = (0, _directiveJs.directive)(class extends (0, _asyncDirectiveJs.AsyncDirective) {
render(i) {
return 0, _litHtmlJs.nothing;
}
update(i, [s]) {
const e = s !== this.G;
return e && void 0 !== this.G && this.rt(void 0), (e || this.lt !== this.ct) && (this.G = s, this.ht = i.options?.host, this.rt(this.ct = i.element)), _litHtmlJs.nothing;
}
rt(t) {
if (this.isConnected || (t = void 0), "function" == typeof this.G) {
const i = this.ht ?? globalThis;
let s = o.get(i);
void 0 === s && (s = new WeakMap, o.set(i, s)), void 0 !== s.get(this.G) && this.G.call(this.ht, void 0), s.set(this.G, t), void 0 !== t && this.G.call(this.ht, t);
} else this.G.value = t;
}
get lt() {
return "function" == typeof this.G ? o.get(this.ht ?? globalThis)?.get(this.G) : this.G?.value;
}
disconnected() {
this.lt === this.ct && this.rt(void 0);
}
reconnected() {
this.rt(this.ct);
}
});
},{"../lit-html.js":"9fQBw","../async-directive.js":"but0t","../directive.js":"lb2wk","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"but0t":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "directive", ()=>(0, _directiveJs.directive));
parcelHelpers.export(exports, "AsyncDirective", ()=>f);
parcelHelpers.export(exports, "Directive", ()=>(0, _directiveJs.Directive));
parcelHelpers.export(exports, "PartType", ()=>(0, _directiveJs.PartType));
var _directiveHelpersJs = require("./directive-helpers.js");
var _directiveJs = require("./directive.js");
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/ const s = (i, t)=>{
const e = i._$AN;
if (void 0 === e) return !1;
for (const i of e)i._$AO?.(t, !1), s(i, t);
return !0;
}, o = (i)=>{
let t, e;
do {
if (void 0 === (t = i._$AM)) break;
e = t._$AN, e.delete(i), i = t;
}while (0 === e?.size);
}, r = (i)=>{
for(let t; t = i._$AM; i = t){
let e = t._$AN;
if (void 0 === e) t._$AN = e = new Set;
else if (e.has(i)) break;
e.add(i), c(t);
}
};
function h(i) {
void 0 !== this._$AN ? (o(this), this._$AM = i, r(this)) : this._$AM = i;
}
function n(i, t = !1, e = 0) {
const r = this._$AH, h = this._$AN;
if (void 0 !== h && 0 !== h.size) {
if (t) {
if (Array.isArray(r)) for(let i = e; i < r.length; i++)s(r[i], !1), o(r[i]);
else null != r && (s(r, !1), o(r));
} else s(this, i);
}
}
const c = (i)=>{
i.type == (0, _directiveJs.PartType).CHILD && (i._$AP ??= n, i._$AQ ??= h);
};
class f extends (0, _directiveJs.Directive) {
constructor(){
super(...arguments), this._$AN = void 0;
}
_$AT(i, t, e) {
super._$AT(i, t, e), r(this), this.isConnected = i._$AU;
}
_$AO(i, t = !0) {
i !== this.isConnected && (this.isConnected = i, i ? this.reconnected?.() : this.disconnected?.()), t && (s(this, i), o(this));
}
setValue(t) {
if ((0, _directiveHelpersJs.isSingleExpression)(this._$Ct)) this._$Ct._$AI(t, this);
else {
const i = [
...this._$Ct._$AH
];
i[this._$Ci] = t, this._$Ct._$AI(i, this, 0);
}
}
disconnected() {}
reconnected() {}
}
},{"./directive-helpers.js":"lixsO","./directive.js":"lb2wk","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"lixsO":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "TemplateResultType", ()=>e);
parcelHelpers.export(exports, "clearPart", ()=>j);
parcelHelpers.export(exports, "getCommittedValue", ()=>M);
parcelHelpers.export(exports, "getDirectiveClass", ()=>f);
parcelHelpers.export(exports, "insertPart", ()=>v);
parcelHelpers.export(exports, "isCompiledTemplateResult", ()=>d);
parcelHelpers.export(exports, "isDirectiveResult", ()=>c);
parcelHelpers.export(exports, "isPrimitive", ()=>n);
parcelHelpers.export(exports, "isSingleExpression", ()=>r);
parcelHelpers.export(exports, "isTemplateResult", ()=>l);
parcelHelpers.export(exports, "removePart", ()=>h);
parcelHelpers.export(exports, "setChildPartValue", ()=>u);
parcelHelpers.export(exports, "setCommittedValue", ()=>p);
var _litHtmlJs = require("./lit-html.js");
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/ const { I: t } = (0, _litHtmlJs._$LH), i = (o)=>o, n = (o)=>null === o || "object" != typeof o && "function" != typeof o, e = {
HTML: 1,
SVG: 2,
MATHML: 3
}, l = (o, t)=>void 0 === t ? void 0 !== o?._$litType$ : o?._$litType$ === t, d = (o)=>null != o?._$litType$?.h, c = (o)=>void 0 !== o?._$litDirective$, f = (o)=>o?._$litDirective$, r = (o)=>void 0 === o.strings, s = ()=>document.createComment(""), v = (o, n, e)=>{
const l = o._$AA.parentNode, d = void 0 === n ? o._$AB : n._$AA;
if (void 0 === e) {
const i = l.insertBefore(s(), d), n = l.insertBefore(s(), d);
e = new t(i, n, o, o.options);
} else {
const t = e._$AB.nextSibling, n = e._$AM, c = n !== o;
if (c) {
let t;
e._$AQ?.(o), e._$AM = o, void 0 !== e._$AP && (t = o._$AU) !== n._$AU && e._$AP(t);
}
if (t !== d || c) {
let o = e._$AA;
for(; o !== t;){
const t = i(o).nextSibling;
i(l).insertBefore(o, d), o = t;
}
}
}
return e;
}, u = (o, t, i = o)=>(o._$AI(t, i), o), m = {}, p = (o, t = m)=>o._$AH = t, M = (o)=>o._$AH, h = (o)=>{
o._$AR(), o._$AA.remove();
}, j = (o)=>{
o._$AR();
};
},{"./lit-html.js":"9fQBw","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"lb2wk":[function(require,module,exports,__globalThis) {
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "Directive", ()=>i);
parcelHelpers.export(exports, "PartType", ()=>t);
parcelHelpers.export(exports, "directive", ()=>e);
const t = {
ATTRIBUTE: 1,
CHILD: 2,
PROPERTY: 3,
BOOLEAN_ATTRIBUTE: 4,
EVENT: 5,
ELEMENT: 6
}, e = (t)=>(...e)=>({
_$litDirective$: t,
values: e
});
class i {
constructor(t){}
get _$AU() {
return this._$AM._$AU;
}
_$AT(t, e, i) {
this._$Ct = t, this._$AM = e, this._$Ci = i;
}
_$AS(t, e) {
return this.update(t, e);
}
update(t, e) {
return this.render(...e);
}
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"cWB8N":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "ModalShell", ()=>(0, _modalShell.ModalShell));
var _modalShell = require("./modal-shell");
},{"./modal-shell":"6DdvC","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"6DdvC":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "ModalShell", ()=>ModalShell);
var _litHtml = require("lit-html");
function ModalShell(p) {
const outerClass = p.extraClass ? `ytc-modal ${p.extraClass}` : 'ytc-modal';
return (0, _litHtml.html)`
<div
id=${p.id ?? (0, _litHtml.nothing)}
class=${outerClass}
@click=${(e)=>{
if (e.target === e.currentTarget && p.onBackdropClick) p.onBackdropClick(e);
}}
>
<div class="ytc-share-modal-box">
<div class="ytc-share-modal-title">${p.title}</div>
${p.warning ? (0, _litHtml.html)`<div class="ytc-share-modal-warning">${p.warning}</div>` : (0, _litHtml.nothing)}
${p.children}
<div class="ytc-share-modal-actions">${p.actions}</div>
</div>
</div>
`;
}
},{"lit-html":"9fQBw","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"hnA8h":[function(require,module,exports,__globalThis) {
// Loose-but-safe parser for clipper input data (share URL payloads, JSON file
// imports, localStorage auto-saved blobs). Addresses attack classes that our
// XSS stack does NOT cover at this boundary:
// - Prototype pollution via `__proto__` / `constructor` / `prototype` keys.
// - NaN / Infinity in numeric time fields (silent math errors downstream).
// - Unbounded `markerPairs` array (DoS — matches share-format's 10K cap).
// - Stack overflow via deeply nested objects (recursion depth cap).
// - Unknown attacker-supplied keys flowing into `appState.settings` merge
// (allowlist filter — only known schema keys pass through).
//
// Design constraints:
// - Loose: extra/unknown fields are SILENTLY STRIPPED (not rejected) so
// older versions can load newer-format files. Known fields that happen
// to be missing are tolerated (downstream uses defaults).
// - Reject odd: non-object top-level, missing/non-array markerPairs,
// non-finite or negative start/end, pair count over the cap, nesting
// past the depth cap.
//
// In addition to the parsed output, the parser returns a list of `issues`
// describing soft-handled events (unknown keys stripped, proto-polluting
// keys stripped, invalid speed/crop points dropped). Callers can surface
// these in a review UI so the user can see what the parser did.
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "ClipperInputValidationError", ()=>ClipperInputValidationError);
parcelHelpers.export(exports, "isDangerousIssue", ()=>isDangerousIssue);
/** Validate an already-parsed object as clipper input. Records soft-handled
* events in `result.issues`; throws on hard structural errors. */ parcelHelpers.export(exports, "parseClipperInput", ()=>parseClipperInput);
/** Parse a JSON string as clipper input. Strips proto-polluting keys via the
* `JSON.parse` reviver (single pass, faster than post-parse recursive strip),
* then runs `parseClipperInput` on the result. */ parcelHelpers.export(exports, "parseClipperInputJSON", ()=>parseClipperInputJSON);
parcelHelpers.export(exports, "toApplicableSettings", ()=>toApplicableSettings);
/** Formats an issue list as a short multi-line human-readable string for log
* output or flash messages. */ parcelHelpers.export(exports, "formatIssues", ()=>formatIssues);
class ClipperInputValidationError extends Error {
constructor(message){
super(`Clipper input: ${message}`);
this.name = 'ClipperInputValidationError';
}
}
function isDangerousIssue(issue) {
return issue.kind === 'dangerousKeys';
}
// --- Allowlists, compile-time-enforced against the corresponding interfaces.
const settingsAllow = {
platform: true,
videoID: true,
videoTag: true,
videoTitle: true,
videoUrl: true,
newMarkerSpeed: true,
newMarkerCrop: true,
titleSuffix: true,
isVerticalVideo: true,
cropRes: true,
cropResWidth: true,
cropResHeight: true,
markerPairMergeList: true,
encodeSpeed: true,
crf: true,
targetMaxBitrate: true,
rotate: true,
enableHDR: true,
gamma: true,
twoPass: true,
denoise: true,
audio: true,
videoStabilization: true,
videoStabilizationDynamicZoom: true,
minterpFpsMultiplier: true,
loop: true,
fadeDuration: true
};
const markerPairAllow = {
start: true,
end: true,
speed: true,
speedMap: true,
crop: true,
cropMap: true,
enableZoomPan: true,
cropRes: true,
overrides: true,
number: true
};
const overridesAllow = {
titlePrefix: true,
enableHDR: true,
gamma: true,
encodeSpeed: true,
crf: true,
targetMaxBitrate: true,
twoPass: true,
denoise: true,
audio: true,
videoStabilization: true,
videoStabilizationDynamicZoom: true,
minterpFpsMultiplier: true,
loop: true,
fadeDuration: true
};
const speedPointAllow = {
x: true,
y: true
};
const cropPointAllow = {
x: true,
y: true,
crop: true,
easeIn: true
};
const topLevelAllow = {
...settingsAllow,
version: true,
date: true,
markerPairs: true
};
const MAX_MARKER_PAIRS = 10000;
const MAX_STRIP_DEPTH = 64;
const PROTO_KEYS = [
'__proto__',
'constructor',
'prototype'
];
// --- Implementation
/** Picks keys from `source` that are in `allow`. Records any unknown keys as
* an `unknownKeys` issue on `issues` attributed to `path`. */ function pickKnown(source, allow, path, issues) {
const out = {};
const unknown = [];
for (const key of Object.keys(source))if (Object.prototype.hasOwnProperty.call(allow, key)) out[key] = source[key];
else unknown.push(key);
if (unknown.length > 0) issues.push({
kind: 'unexpectedFields',
path,
keys: unknown
});
return out;
}
/** Recursively drop prototype-polluting keys. Records any finds as a
* `prototypeKeys` issue — these are a security signal worth surfacing. */ function stripPrototypeKeys(value, depth, path, issues) {
if (depth > MAX_STRIP_DEPTH) throw new ClipperInputValidationError('nesting too deep');
if (value === null || typeof value !== 'object') return value;
if (Array.isArray(value)) return value.map((item, i)=>stripPrototypeKeys(item, depth + 1, `${path}[${i}]`, issues));
const out = {};
const protoHits = [];
for (const [k, v] of Object.entries(value)){
if (PROTO_KEYS.includes(k)) {
protoHits.push(k);
continue;
}
out[k] = stripPrototypeKeys(v, depth + 1, path === '' ? k : `${path}.${k}`, issues);
}
if (protoHits.length > 0) issues.push({
kind: 'dangerousKeys',
path: path || '(root)',
keys: protoHits
});
return out;
}
function isPlainObject(v) {
return v !== null && typeof v === 'object' && !Array.isArray(v);
}
function isFiniteNumber(v) {
return typeof v === 'number' && Number.isFinite(v);
}
function remapLegacyKeys(obj) {
if (!('markerPairs' in obj) && 'markers' in obj) {
const { markers, ...rest } = obj;
return {
...rest,
markerPairs: markers
};
}
return obj;
}
function coerceSpeed(raw) {
if (isFiniteNumber(raw)) return raw;
if (typeof raw === 'string') {
const n = Number(raw);
if (Number.isFinite(n)) return n;
}
return undefined;
}
function requireNonNegativeFinite(value, field, index) {
if (!isFiniteNumber(value) || value < 0) throw new ClipperInputValidationError(`markerPairs[${index}].${field} must be a finite >= 0 number`);
return value;
}
/** Per-field value validators for `MarkerPairOverrides`. Compile-time-
* exhaustive via the `satisfies` constraint: a new key on
* `MarkerPairOverrides` requires a validator entry here or the build
* fails. Each validator is a coarse type guard (string / boolean /
* finite number / enum) — fine-grained semantic constraints (e.g.
* "crf must be 0–63") happen further downstream in the encoding
* pipeline; here we only ensure the type matches what the codebase
* expects to consume. */ const OVERRIDES_VALIDATORS = {
titlePrefix: (v)=>typeof v === 'string',
enableHDR: (v)=>typeof v === 'boolean',
gamma: (v)=>isFiniteNumber(v),
encodeSpeed: (v)=>isFiniteNumber(v),
crf: (v)=>isFiniteNumber(v),
targetMaxBitrate: (v)=>isFiniteNumber(v),
twoPass: (v)=>typeof v === 'boolean',
denoise: (v)=>isPlainObject(v),
audio: (v)=>typeof v === 'boolean',
videoStabilization: (v)=>isPlainObject(v),
videoStabilizationDynamicZoom: (v)=>typeof v === 'boolean',
minterpFpsMultiplier: (v)=>isFiniteNumber(v),
loop: (v)=>v === 'none' || v === 'fwrev' || v === 'fade',
fadeDuration: (v)=>isFiniteNumber(v)
};
function parseOverrides(raw, path, issues) {
if (!isPlainObject(raw)) return {};
const picked = pickKnown(raw, overridesAllow, path, issues);
const out = {};
for (const [key, validate] of Object.entries(OVERRIDES_VALIDATORS)){
const value = picked[key];
if (value !== undefined && validate(value)) out[key] = value;
}
return out;
}
function parseSpeedPoint(raw, path, issues) {
if (!isPlainObject(raw)) {
issues.push({
kind: 'invalidPoint',
path
});
return null;
}
const picked = pickKnown(raw, speedPointAllow, path, issues);
if (!isFiniteNumber(picked.x) || !isFiniteNumber(picked.y)) {
issues.push({
kind: 'invalidPoint',
path
});
return null;
}
return {
x: picked.x,
y: picked.y
};
}
function parseCropPoint(raw, path, issues) {
if (!isPlainObject(raw)) {
issues.push({
kind: 'invalidPoint',
path
});
return null;
}
const picked = pickKnown(raw, cropPointAllow, path, issues);
if (!isFiniteNumber(picked.x) || picked.y !== 0 || typeof picked.crop !== 'string') {
issues.push({
kind: 'invalidPoint',
path
});
return null;
}
const out = {
x: picked.x,
y: 0,
crop: picked.crop
};
if (picked.easeIn === 'instant') out.easeIn = 'instant';
return out;
}
function parseMarkerPair(raw, index, issues) {
if (!isPlainObject(raw)) throw new ClipperInputValidationError(`markerPairs[${index}] is not an object`);
const path = `markerPairs[${index}]`;
const picked = pickKnown(raw, markerPairAllow, path, issues);
const out = {
start: requireNonNegativeFinite(picked.start, 'start', index),
end: requireNonNegativeFinite(picked.end, 'end', index),
overrides: parseOverrides(picked.overrides, `${path}.overrides`, issues)
};
const speed = coerceSpeed(picked.speed);
if (speed !== undefined) out.speed = speed;
if (typeof picked.crop === 'string') out.crop = picked.crop;
if (typeof picked.cropRes === 'string') out.cropRes = picked.cropRes;
if (typeof picked.enableZoomPan === 'boolean') out.enableZoomPan = picked.enableZoomPan;
if (typeof picked.number === 'number') out.number = picked.number;
if (Array.isArray(picked.speedMap)) out.speedMap = picked.speedMap.map((p, i)=>parseSpeedPoint(p, `${path}.speedMap[${i}]`, issues)).filter((p)=>p !== null);
if (Array.isArray(picked.cropMap)) out.cropMap = picked.cropMap.map((p, i)=>parseCropPoint(p, `${path}.cropMap[${i}]`, issues)).filter((p)=>p !== null);
return out;
}
function parseClipperInput(raw) {
if (!isPlainObject(raw)) throw new ClipperInputValidationError('expected a JSON object at the top level');
const issues = [];
const stripped = stripPrototypeKeys(raw, 0, '', issues);
const normalized = remapLegacyKeys(stripped);
const { markerPairs: markerPairsRaw, ...rest } = pickKnown(normalized, topLevelAllow, '(root)', issues);
if (!Array.isArray(markerPairsRaw)) throw new ClipperInputValidationError('markerPairs must be an array');
if (markerPairsRaw.length > MAX_MARKER_PAIRS) throw new ClipperInputValidationError(`markerPairs has ${markerPairsRaw.length} entries (max ${MAX_MARKER_PAIRS})`);
const markerPairs = markerPairsRaw.map((pair, i)=>parseMarkerPair(pair, i, issues));
const input = {
...rest,
markerPairs
};
return {
input,
issues
};
}
function parseClipperInputJSON(text) {
let raw;
try {
raw = JSON.parse(text, (key, value)=>PROTO_KEYS.includes(key) ? undefined : value);
} catch (err) {
throw new ClipperInputValidationError(`invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
}
return parseClipperInput(raw);
}
/** Application policy for every `ClipperInput` key. Compile-time-exhaustive
* via the `satisfies Record<keyof ClipperInput, ...>` constraint: if a new
* field is added to `Settings` (and thereby to `ClipperInput`), TypeScript
* rejects the build until the field is explicitly classified here.
*
* - `'apply'`: the field merges into `appState.settings` on load. Used for
* user-configurable encoding/preview/marker settings.
* - `'skip'`: the field describes the source environment (which video,
* which save-time version, etc.) or is handled separately (markerPairs
* has its own application path via `addMarkerPairs`). Skipped to keep
* user state independent of where the file came from. */ const KEY_POLICY = {
// === source-environment metadata: never merged from loaded JSON ===
// These fields describe the file's origin, not user-tunable settings.
// Letting a loaded file overwrite `platform` or `videoTag` would
// corrupt the auto-save slot (which keys on `videoTag`) or break
// platform-specific code paths. They also act as the obvious target
// for any future modal-chrome interpolation, so keeping attacker-
// controlled strings out of `appState.settings` here closes the
// social-engineering surface at the source.
platform: 'skip',
videoID: 'skip',
videoTag: 'skip',
videoTitle: 'skip',
videoUrl: 'skip',
isVerticalVideo: 'skip',
version: 'skip',
date: 'skip',
markerPairs: 'skip',
// === user-configurable settings: merged into appState.settings ===
newMarkerSpeed: 'apply',
newMarkerCrop: 'apply',
titleSuffix: 'apply',
cropRes: 'apply',
cropResWidth: 'apply',
cropResHeight: 'apply',
markerPairMergeList: 'apply',
encodeSpeed: 'apply',
crf: 'apply',
targetMaxBitrate: 'apply',
rotate: 'apply',
enableHDR: 'apply',
gamma: 'apply',
twoPass: 'apply',
denoise: 'apply',
audio: 'apply',
videoStabilization: 'apply',
videoStabilizationDynamicZoom: 'apply',
minterpFpsMultiplier: 'apply',
loop: 'apply',
fadeDuration: 'apply'
};
function toApplicableSettings(input) {
const out = {};
for (const [key, policy] of Object.entries(KEY_POLICY)){
if (policy !== 'apply') continue;
const value = input[key];
if (value === undefined) continue;
out[key] = value;
}
return out;
}
function formatIssues(issues) {
return issues.map((issue)=>{
switch(issue.kind){
case 'unexpectedFields':
return `stripped unknown keys at ${issue.path}: ${issue.keys.join(', ')}`;
case 'dangerousKeys':
return `stripped prototype-polluting keys at ${issue.path}: ${issue.keys.join(', ')}`;
case 'invalidPoint':
return `dropped invalid point at ${issue.path}`;
}
}).join('\n');
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"iuaXf":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
// Walks `payload` and uses DOMPurify to scan every string leaf for
// HTML/markup that the library would strip. Returns per-field findings.
// Returns [] when the payload has no suspicious content.
parcelHelpers.export(exports, "scanSuspiciousContent", ()=>scanSuspiciousContent);
// Formats findings as a short multi-line string for logs / flash messages.
// Example: `settings.titleSuffix: <script>, onclick= attribute`.
parcelHelpers.export(exports, "formatFindings", ()=>formatFindings);
var _dompurify = require("dompurify");
var _dompurifyDefault = parcelHelpers.interopDefault(_dompurify);
// Maximally strict DOMPurify config for SCAN-AND-REPORT use. yt_clipper's
// loadable fields (titleSuffix / titlePrefix / crop strings / merge list /
// etc.) have no legitimate HTML use — titleSuffix becomes a filename, the
// rest are syntactic strings (numeric, regex-matched). Any HTML-like tag
// or attribute in loaded data is worth flagging for user review, even if
// benign (e.g. `<b>` in a share URL is a surprise the user should see).
//
// `ALLOWED_TAGS: []` + `ALLOWED_ATTR: []` strips EVERY tag/attribute, so
// `.removed` catches anything that DOMPurify's parser recognizes as markup.
// Stray `<` / `>` in non-tag contexts (e.g. `<3 clips`, `x < y`) are NOT
// flagged because the HTML parser treats them as text, not tags.
const SCAN_CONFIG = {
ALLOWED_TAGS: [],
ALLOWED_ATTR: []
};
// Walk an arbitrary loaded payload, invoking `visit` for every string leaf
// along with its dotted path.
function walkStrings(value, path, visit) {
if (typeof value === 'string') {
visit(path, value);
return;
}
if (Array.isArray(value)) {
value.forEach((v, i)=>walkStrings(v, `${path}[${i}]`, visit));
return;
}
if (value != null && typeof value === 'object') for (const [k, v] of Object.entries(value))walkStrings(v, path === '' ? k : `${path}.${k}`, visit);
}
// Elements DOMPurify's parser synthesizes around fragments (jsdom behavior
// varies too). Under strict `ALLOWED_TAGS: []`, these auto-generated
// wrappers get stripped and reported — but they weren't in the user's data,
// so filter them out UNLESS they carry attributes (which might be exploit
// vectors like `<body onload=...>`).
const PARSER_WRAPPER_TAGS = new Set([
'html',
'head',
'body'
]);
function describeRemoved(entry) {
if ('element' in entry && entry.element) {
const name = entry.element.nodeName;
const tagName = typeof name === 'string' ? name.toLowerCase() : '?';
if (PARSER_WRAPPER_TAGS.has(tagName)) {
const el = entry.element;
if (el.attributes.length === 0) return null;
}
return `<${tagName}>`;
}
if ('attribute' in entry && entry.attribute) return `${entry.attribute.name}= attribute`;
return 'unknown node';
}
function scanSuspiciousContent(payload) {
const findings = [];
walkStrings(payload, '', (path, str)=>{
// Empty strings are common and never have markup — skip the sanitizer call.
if (str.length === 0) return;
(0, _dompurifyDefault.default).sanitize(str, SCAN_CONFIG);
if ((0, _dompurifyDefault.default).removed.length === 0) return;
// Collect descriptions, filter parser-wrapper noise, de-duplicate.
const items = Array.from(new Set((0, _dompurifyDefault.default).removed.map(describeRemoved).filter((s)=>s !== null)));
// DOMPurify.removed is module-level state — reset before the next scan
// so findings don't accumulate across fields.
(0, _dompurifyDefault.default).removed.length = 0;
if (items.length === 0) return;
findings.push({
path,
items
});
});
return findings;
}
function formatFindings(findings) {
return findings.map((f)=>`${f.path}: ${f.items.join(', ')}`).join('\n');
}
},{"dompurify":"jEHBp","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"jEHBp":[function(require,module,exports,__globalThis) {
/*! @license DOMPurify 3.4.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.0/LICENSE */ (function(global, factory) {
module.exports = factory();
})(this, function() {
'use strict';
const { entries, setPrototypeOf, isFrozen, getPrototypeOf, getOwnPropertyDescriptor } = Object;
let { freeze, seal, create } = Object; // eslint-disable-line import/no-mutable-exports
let { apply, construct } = typeof Reflect !== 'undefined' && Reflect;
if (!freeze) freeze = function freeze(x) {
return x;
};
if (!seal) seal = function seal(x) {
return x;
};
if (!apply) apply = function apply(func, thisArg) {
for(var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++)args[_key - 2] = arguments[_key];
return func.apply(thisArg, args);
};
if (!construct) construct = function construct(Func) {
for(var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++)args[_key2 - 1] = arguments[_key2];
return new Func(...args);
};
const arrayForEach = unapply(Array.prototype.forEach);
const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
const arrayPop = unapply(Array.prototype.pop);
const arrayPush = unapply(Array.prototype.push);
const arraySplice = unapply(Array.prototype.splice);
const stringToLowerCase = unapply(String.prototype.toLowerCase);
const stringToString = unapply(String.prototype.toString);
const stringMatch = unapply(String.prototype.match);
const stringReplace = unapply(String.prototype.replace);
const stringIndexOf = unapply(String.prototype.indexOf);
const stringTrim = unapply(String.prototype.trim);
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
const regExpTest = unapply(RegExp.prototype.test);
const typeErrorCreate = unconstruct(TypeError);
/**
* Creates a new function that calls the given function with a specified thisArg and arguments.
*
* @param func - The function to be wrapped and called.
* @returns A new function that calls the given function with a specified thisArg and arguments.
*/ function unapply(func) {
return function(thisArg) {
if (thisArg instanceof RegExp) thisArg.lastIndex = 0;
for(var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++)args[_key3 - 1] = arguments[_key3];
return apply(func, thisArg, args);
};
}
/**
* Creates a new function that constructs an instance of the given constructor function with the provided arguments.
*
* @param func - The constructor function to be wrapped and called.
* @returns A new function that constructs an instance of the given constructor function with the provided arguments.
*/ function unconstruct(Func) {
return function() {
for(var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++)args[_key4] = arguments[_key4];
return construct(Func, args);
};
}
/**
* Add properties to a lookup table
*
* @param set - The set to which elements will be added.
* @param array - The array containing elements to be added to the set.
* @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
* @returns The modified set with added elements.
*/ function addToSet(set, array) {
let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
if (setPrototypeOf) // Make 'in' and truthy checks like Boolean(set.constructor)
// independent of any properties defined on Object.prototype.
// Prevent prototype setters from intercepting set as a this value.
setPrototypeOf(set, null);
let l = array.length;
while(l--){
let element = array[l];
if (typeof element === 'string') {
const lcElement = transformCaseFunc(element);
if (lcElement !== element) {
// Config presets (e.g. tags.js, attrs.js) are immutable.
if (!isFrozen(array)) array[l] = lcElement;
element = lcElement;
}
}
set[element] = true;
}
return set;
}
/**
* Clean up an array to harden against CSPP
*
* @param array - The array to be cleaned.
* @returns The cleaned version of the array
*/ function cleanArray(array) {
for(let index = 0; index < array.length; index++){
const isPropertyExist = objectHasOwnProperty(array, index);
if (!isPropertyExist) array[index] = null;
}
return array;
}
/**
* Shallow clone an object
*
* @param object - The object to be cloned.
* @returns A new object that copies the original.
*/ function clone(object) {
const newObject = create(null);
for (const [property, value] of entries(object)){
const isPropertyExist = objectHasOwnProperty(object, property);
if (isPropertyExist) {
if (Array.isArray(value)) newObject[property] = cleanArray(value);
else if (value && typeof value === 'object' && value.constructor === Object) newObject[property] = clone(value);
else newObject[property] = value;
}
}
return newObject;
}
/**
* This method automatically checks if the prop is function or getter and behaves accordingly.
*
* @param object - The object to look up the getter function in its prototype chain.
* @param prop - The property name for which to find the getter function.
* @returns The getter function found in the prototype chain or a fallback function.
*/ function lookupGetter(object, prop) {
while(object !== null){
const desc = getOwnPropertyDescriptor(object, prop);
if (desc) {
if (desc.get) return unapply(desc.get);
if (typeof desc.value === 'function') return unapply(desc.value);
}
object = getPrototypeOf(object);
}
function fallbackValue() {
return null;
}
return fallbackValue;
}
const html$1 = freeze([
'a',
'abbr',
'acronym',
'address',
'area',
'article',
'aside',
'audio',
'b',
'bdi',
'bdo',
'big',
'blink',
'blockquote',
'body',
'br',
'button',
'canvas',
'caption',
'center',
'cite',
'code',
'col',
'colgroup',
'content',
'data',
'datalist',
'dd',
'decorator',
'del',
'details',
'dfn',
'dialog',
'dir',
'div',
'dl',
'dt',
'element',
'em',
'fieldset',
'figcaption',
'figure',
'font',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'header',
'hgroup',
'hr',
'html',
'i',
'img',
'input',
'ins',
'kbd',
'label',
'legend',
'li',
'main',
'map',
'mark',
'marquee',
'menu',
'menuitem',
'meter',
'nav',
'nobr',
'ol',
'optgroup',
'option',
'output',
'p',
'picture',
'pre',
'progress',
'q',
'rp',
'rt',
'ruby',
's',
'samp',
'search',
'section',
'select',
'shadow',
'slot',
'small',
'source',
'spacer',
'span',
'strike',
'strong',
'style',
'sub',
'summary',
'sup',
'table',
'tbody',
'td',
'template',
'textarea',
'tfoot',
'th',
'thead',
'time',
'tr',
'track',
'tt',
'u',
'ul',
'var',
'video',
'wbr'
]);
const svg$1 = freeze([
'svg',
'a',
'altglyph',
'altglyphdef',
'altglyphitem',
'animatecolor',
'animatemotion',
'animatetransform',
'circle',
'clippath',
'defs',
'desc',
'ellipse',
'enterkeyhint',
'exportparts',
'filter',
'font',
'g',
'glyph',
'glyphref',
'hkern',
'image',
'inputmode',
'line',
'lineargradient',
'marker',
'mask',
'metadata',
'mpath',
'part',
'path',
'pattern',
'polygon',
'polyline',
'radialgradient',
'rect',
'stop',
'style',
'switch',
'symbol',
'text',
'textpath',
'title',
'tref',
'tspan',
'view',
'vkern'
]);
const svgFilters = freeze([
'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'
]);
// List of SVG elements that are disallowed by default.
// We still need to know them so that we can do namespace
// checks properly in case one wants to add them to
// allow-list.
const svgDisallowed = freeze([
'animate',
'color-profile',
'cursor',
'discard',
'font-face',
'font-face-format',
'font-face-name',
'font-face-src',
'font-face-uri',
'foreignobject',
'hatch',
'hatchpath',
'mesh',
'meshgradient',
'meshpatch',
'meshrow',
'missing-glyph',
'script',
'set',
'solidcolor',
'unknown',
'use'
]);
const mathMl$1 = freeze([
'math',
'menclose',
'merror',
'mfenced',
'mfrac',
'mglyph',
'mi',
'mlabeledtr',
'mmultiscripts',
'mn',
'mo',
'mover',
'mpadded',
'mphantom',
'mroot',
'mrow',
'ms',
'mspace',
'msqrt',
'mstyle',
'msub',
'msup',
'msubsup',
'mtable',
'mtd',
'mtext',
'mtr',
'munder',
'munderover',
'mprescripts'
]);
// Similarly to SVG, we want to know all MathML elements,
// even those that we disallow by default.
const mathMlDisallowed = freeze([
'maction',
'maligngroup',
'malignmark',
'mlongdiv',
'mscarries',
'mscarry',
'msgroup',
'mstack',
'msline',
'msrow',
'semantics',
'annotation',
'annotation-xml',
'mprescripts',
'none'
]);
const text = freeze([
'#text'
]);
const html = freeze([
'accept',
'action',
'align',
'alt',
'autocapitalize',
'autocomplete',
'autopictureinpicture',
'autoplay',
'background',
'bgcolor',
'border',
'capture',
'cellpadding',
'cellspacing',
'checked',
'cite',
'class',
'clear',
'color',
'cols',
'colspan',
'controls',
'controlslist',
'coords',
'crossorigin',
'datetime',
'decoding',
'default',
'dir',
'disabled',
'disablepictureinpicture',
'disableremoteplayback',
'download',
'draggable',
'enctype',
'enterkeyhint',
'exportparts',
'face',
'for',
'headers',
'height',
'hidden',
'high',
'href',
'hreflang',
'id',
'inert',
'inputmode',
'integrity',
'ismap',
'kind',
'label',
'lang',
'list',
'loading',
'loop',
'low',
'max',
'maxlength',
'media',
'method',
'min',
'minlength',
'multiple',
'muted',
'name',
'nonce',
'noshade',
'novalidate',
'nowrap',
'open',
'optimum',
'part',
'pattern',
'placeholder',
'playsinline',
'popover',
'popovertarget',
'popovertargetaction',
'poster',
'preload',
'pubdate',
'radiogroup',
'readonly',
'rel',
'required',
'rev',
'reversed',
'role',
'rows',
'rowspan',
'spellcheck',
'scope',
'selected',
'shape',
'size',
'sizes',
'slot',
'span',
'srclang',
'start',
'src',
'srcset',
'step',
'style',
'summary',
'tabindex',
'title',
'translate',
'type',
'usemap',
'valign',
'value',
'width',
'wrap',
'xmlns',
'slot'
]);
const svg = freeze([
'accent-height',
'accumulate',
'additive',
'alignment-baseline',
'amplitude',
'ascent',
'attributename',
'attributetype',
'azimuth',
'basefrequency',
'baseline-shift',
'begin',
'bias',
'by',
'class',
'clip',
'clippathunits',
'clip-path',
'clip-rule',
'color',
'color-interpolation',
'color-interpolation-filters',
'color-profile',
'color-rendering',
'cx',
'cy',
'd',
'dx',
'dy',
'diffuseconstant',
'direction',
'display',
'divisor',
'dur',
'edgemode',
'elevation',
'end',
'exponent',
'fill',
'fill-opacity',
'fill-rule',
'filter',
'filterunits',
'flood-color',
'flood-opacity',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'fx',
'fy',
'g1',
'g2',
'glyph-name',
'glyphref',
'gradientunits',
'gradienttransform',
'height',
'href',
'id',
'image-rendering',
'in',
'in2',
'intercept',
'k',
'k1',
'k2',
'k3',
'k4',
'kerning',
'keypoints',
'keysplines',
'keytimes',
'lang',
'lengthadjust',
'letter-spacing',
'kernelmatrix',
'kernelunitlength',
'lighting-color',
'local',
'marker-end',
'marker-mid',
'marker-start',
'markerheight',
'markerunits',
'markerwidth',
'maskcontentunits',
'maskunits',
'max',
'mask',
'mask-type',
'media',
'method',
'mode',
'min',
'name',
'numoctaves',
'offset',
'operator',
'opacity',
'order',
'orient',
'orientation',
'origin',
'overflow',
'paint-order',
'path',
'pathlength',
'patterncontentunits',
'patterntransform',
'patternunits',
'points',
'preservealpha',
'preserveaspectratio',
'primitiveunits',
'r',
'rx',
'ry',
'radius',
'refx',
'refy',
'repeatcount',
'repeatdur',
'restart',
'result',
'rotate',
'scale',
'seed',
'shape-rendering',
'slope',
'specularconstant',
'specularexponent',
'spreadmethod',
'startoffset',
'stddeviation',
'stitchtiles',
'stop-color',
'stop-opacity',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke',
'stroke-width',
'style',
'surfacescale',
'systemlanguage',
'tabindex',
'tablevalues',
'targetx',
'targety',
'transform',
'transform-origin',
'text-anchor',
'text-decoration',
'text-rendering',
'textlength',
'type',
'u1',
'u2',
'unicode',
'values',
'viewbox',
'visibility',
'version',
'vert-adv-y',
'vert-origin-x',
'vert-origin-y',
'width',
'word-spacing',
'wrap',
'writing-mode',
'xchannelselector',
'ychannelselector',
'x',
'x1',
'x2',
'xmlns',
'y',
'y1',
'y2',
'z',
'zoomandpan'
]);
const mathMl = freeze([
'accent',
'accentunder',
'align',
'bevelled',
'close',
'columnalign',
'columnlines',
'columnspacing',
'columnspan',
'denomalign',
'depth',
'dir',
'display',
'displaystyle',
'encoding',
'fence',
'frame',
'height',
'href',
'id',
'largeop',
'length',
'linethickness',
'lquote',
'lspace',
'mathbackground',
'mathcolor',
'mathsize',
'mathvariant',
'maxsize',
'minsize',
'movablelimits',
'notation',
'numalign',
'open',
'rowalign',
'rowlines',
'rowspacing',
'rowspan',
'rspace',
'rquote',
'scriptlevel',
'scriptminsize',
'scriptsizemultiplier',
'selection',
'separator',
'separators',
'stretchy',
'subscriptshift',
'supscriptshift',
'symmetric',
'voffset',
'width',
'xmlns'
]);
const xml = freeze([
'xlink:href',
'xml:id',
'xlink:title',
'xml:space',
'xmlns:xlink'
]);
// eslint-disable-next-line unicorn/better-regex
const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
);
const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
);
const DOCTYPE_NAME = seal(/^html$/i);
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
var EXPRESSIONS = /*#__PURE__*/ Object.freeze({
__proto__: null,
ARIA_ATTR: ARIA_ATTR,
ATTR_WHITESPACE: ATTR_WHITESPACE,
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
DATA_ATTR: DATA_ATTR,
DOCTYPE_NAME: DOCTYPE_NAME,
ERB_EXPR: ERB_EXPR,
IS_ALLOWED_URI: IS_ALLOWED_URI,
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
MUSTACHE_EXPR: MUSTACHE_EXPR,
TMPLIT_EXPR: TMPLIT_EXPR
});
/* eslint-disable @typescript-eslint/indent */ // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
const NODE_TYPE = {
element: 1,
text: 3,
// Deprecated
progressingInstruction: 7,
comment: 8,
document: 9
};
const getGlobal = function getGlobal() {
return typeof window === 'undefined' ? null : window;
};
/**
* Creates a no-op policy for internal use only.
* Don't export this function outside this module!
* @param trustedTypes The policy factory.
* @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
* @return The policy created (or null, if Trusted Types
* are not supported or creating the policy failed).
*/ const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') return null;
// Allow the callers to control the unique policy name
// by adding a data-tt-policy-suffix to the script element with the DOMPurify.
// Policy creation with duplicate names throws in Trusted Types.
let suffix = null;
const ATTR_NAME = 'data-tt-policy-suffix';
if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) suffix = purifyHostElement.getAttribute(ATTR_NAME);
const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
try {
return trustedTypes.createPolicy(policyName, {
createHTML (html) {
return html;
},
createScriptURL (scriptUrl) {
return scriptUrl;
}
});
} catch (_) {
// Policy creation failed (most likely another DOMPurify script has
// already run). Skip creating the policy, as this will only cause errors
// if TT are enforced.
console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
return null;
}
};
const _createHooksMap = function _createHooksMap() {
return {
afterSanitizeAttributes: [],
afterSanitizeElements: [],
afterSanitizeShadowDOM: [],
beforeSanitizeAttributes: [],
beforeSanitizeElements: [],
beforeSanitizeShadowDOM: [],
uponSanitizeAttribute: [],
uponSanitizeElement: [],
uponSanitizeShadowNode: []
};
};
function createDOMPurify() {
let window1 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
const DOMPurify = (root)=>createDOMPurify(root);
DOMPurify.version = '3.4.0';
DOMPurify.removed = [];
if (!window1 || !window1.document || window1.document.nodeType !== NODE_TYPE.document || !window1.Element) {
// Not running in a browser, provide a factory function
// so that you can pass your own Window
DOMPurify.isSupported = false;
return DOMPurify;
}
let { document } = window1;
const originalDocument = document;
const currentScript = originalDocument.currentScript;
const { DocumentFragment, HTMLTemplateElement, Node, Element, NodeFilter, NamedNodeMap = window1.NamedNodeMap || window1.MozNamedAttrMap, HTMLFormElement, DOMParser, trustedTypes } = window1;
const ElementPrototype = Element.prototype;
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
const remove = lookupGetter(ElementPrototype, 'remove');
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
// As per issue #47, the web-components registry is inherited by a
// new document created via createHTMLDocument. As per the spec
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
// a new empty registry is used when creating a template contents owner
// document, so we use that as our parent document to ensure nothing
// is inherited.
if (typeof HTMLTemplateElement === 'function') {
const template = document.createElement('template');
if (template.content && template.content.ownerDocument) document = template.content.ownerDocument;
}
let trustedTypesPolicy;
let emptyHTML = '';
const { implementation, createNodeIterator, createDocumentFragment, getElementsByTagName } = document;
const { importNode } = originalDocument;
let hooks = _createHooksMap();
/**
* Expose whether this browser supports running the full DOMPurify.
*/ DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
const { MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR, DATA_ATTR, ARIA_ATTR, IS_SCRIPT_OR_DATA, ATTR_WHITESPACE, CUSTOM_ELEMENT } = EXPRESSIONS;
let { IS_ALLOWED_URI: IS_ALLOWED_URI$1 } = EXPRESSIONS;
/**
* We consider the elements and attributes below to be safe. Ideally
* don't add any new ones but feel free to remove unwanted ones.
*/ /* allowed element names */ let ALLOWED_TAGS = null;
const DEFAULT_ALLOWED_TAGS = addToSet({}, [
...html$1,
...svg$1,
...svgFilters,
...mathMl$1,
...text
]);
/* Allowed attribute names */ let ALLOWED_ATTR = null;
const DEFAULT_ALLOWED_ATTR = addToSet({}, [
...html,
...svg,
...mathMl,
...xml
]);
/*
* Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
* @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
* @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
* @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
*/ let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
tagNameCheck: {
writable: true,
configurable: false,
enumerable: true,
value: null
},
attributeNameCheck: {
writable: true,
configurable: false,
enumerable: true,
value: null
},
allowCustomizedBuiltInElements: {
writable: true,
configurable: false,
enumerable: true,
value: false
}
}));
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ let FORBID_TAGS = null;
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ let FORBID_ATTR = null;
/* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */ const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {
tagCheck: {
writable: true,
configurable: false,
enumerable: true,
value: null
},
attributeCheck: {
writable: true,
configurable: false,
enumerable: true,
value: null
}
}));
/* Decide if ARIA attributes are okay */ let ALLOW_ARIA_ATTR = true;
/* Decide if custom data attributes are okay */ let ALLOW_DATA_ATTR = true;
/* Decide if unknown protocols are okay */ let ALLOW_UNKNOWN_PROTOCOLS = false;
/* Decide if self-closing tags in attributes are allowed.
* Usually removed due to a mXSS issue in jQuery 3.0 */ let ALLOW_SELF_CLOSE_IN_ATTR = true;
/* Output should be safe for common template engines.
* This means, DOMPurify removes data attributes, mustaches and ERB
*/ let SAFE_FOR_TEMPLATES = false;
/* Output should be safe even for XML used within HTML and alike.
* This means, DOMPurify removes comments when containing risky content.
*/ let SAFE_FOR_XML = true;
/* Decide if document with <html>... should be returned */ let WHOLE_DOCUMENT = false;
/* Track whether config is already set on this instance of DOMPurify. */ let SET_CONFIG = false;
/* Decide if all elements (e.g. style, script) must be children of
* document.body. By default, browsers might move them to document.head */ let FORCE_BODY = false;
/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
* string (or a TrustedHTML object if Trusted Types are supported).
* If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
*/ let RETURN_DOM = false;
/* Decide if a DOM `DocumentFragment` should be returned, instead of a html
* string (or a TrustedHTML object if Trusted Types are supported) */ let RETURN_DOM_FRAGMENT = false;
/* Try to return a Trusted Type object instead of a string, return a string in
* case Trusted Types are not supported */ let RETURN_TRUSTED_TYPE = false;
/* Output should be free from DOM clobbering attacks?
* This sanitizes markups named with colliding, clobberable built-in DOM APIs.
*/ let SANITIZE_DOM = true;
/* Achieve full DOM Clobbering protection by isolating the namespace of named
* properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
*
* HTML/DOM spec rules that enable DOM Clobbering:
* - Named Access on Window (§7.3.3)
* - DOM Tree Accessors (§3.1.5)
* - Form Element Parent-Child Relations (§4.10.3)
* - Iframe srcdoc / Nested WindowProxies (§4.8.5)
* - HTMLCollection (§4.2.10.2)
*
* Namespace isolation is implemented by prefixing `id` and `name` attributes
* with a constant string, i.e., `user-content-`
*/ let SANITIZE_NAMED_PROPS = false;
const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
/* Keep element content when removing element? */ let KEEP_CONTENT = true;
/* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
* of importing it into a new Document and returning a sanitized copy */ let IN_PLACE = false;
/* Allow usage of profiles like html, svg and mathMl */ let USE_PROFILES = {};
/* Tags to ignore content of when KEEP_CONTENT is true */ let FORBID_CONTENTS = null;
const DEFAULT_FORBID_CONTENTS = addToSet({}, [
'annotation-xml',
'audio',
'colgroup',
'desc',
'foreignobject',
'head',
'iframe',
'math',
'mi',
'mn',
'mo',
'ms',
'mtext',
'noembed',
'noframes',
'noscript',
'plaintext',
'script',
'style',
'svg',
'template',
'thead',
'title',
'video',
'xmp'
]);
/* Tags that are safe for data: URIs */ let DATA_URI_TAGS = null;
const DEFAULT_DATA_URI_TAGS = addToSet({}, [
'audio',
'video',
'img',
'source',
'image',
'track'
]);
/* Attributes safe for values like "javascript:" */ let URI_SAFE_ATTRIBUTES = null;
const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [
'alt',
'class',
'for',
'id',
'label',
'name',
'pattern',
'placeholder',
'role',
'summary',
'title',
'value',
'style',
'xmlns'
]);
const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
/* Document namespace */ let NAMESPACE = HTML_NAMESPACE;
let IS_EMPTY_INPUT = false;
/* Allowed XHTML+XML namespaces */ let ALLOWED_NAMESPACES = null;
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [
MATHML_NAMESPACE,
SVG_NAMESPACE,
HTML_NAMESPACE
], stringToString);
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, [
'mi',
'mo',
'mn',
'ms',
'mtext'
]);
let HTML_INTEGRATION_POINTS = addToSet({}, [
'annotation-xml'
]);
// Certain elements are allowed in both SVG and HTML
// namespace. We need to specify them explicitly
// so that they don't get erroneously deleted from
// HTML namespace.
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [
'title',
'style',
'font',
'a',
'script'
]);
/* Parsing of strict XHTML documents */ let PARSER_MEDIA_TYPE = null;
const SUPPORTED_PARSER_MEDIA_TYPES = [
'application/xhtml+xml',
'text/html'
];
const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
let transformCaseFunc = null;
/* Keep a reference to config to pass to hooks */ let CONFIG = null;
/* Ideally, do not touch anything below this line */ /* ______________________________________________ */ const formElement = document.createElement('form');
const isRegexOrFunction = function isRegexOrFunction(testValue) {
return testValue instanceof RegExp || testValue instanceof Function;
};
/**
* _parseConfig
*
* @param cfg optional config literal
*/ // eslint-disable-next-line complexity
const _parseConfig = function _parseConfig() {
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (CONFIG && CONFIG === cfg) return;
/* Shield configuration object from tampering */ if (!cfg || typeof cfg !== 'object') cfg = {};
/* Shield configuration object from prototype pollution */ cfg = clone(cfg);
PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes
SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
/* Set configuration parameters */ ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
RETURN_DOM = cfg.RETURN_DOM || false; // Default false
RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
FORCE_BODY = cfg.FORCE_BODY || false; // Default false
SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
IN_PLACE = cfg.IN_PLACE || false; // Default false
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || create(null);
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
if (SAFE_FOR_TEMPLATES) ALLOW_DATA_ATTR = false;
if (RETURN_DOM_FRAGMENT) RETURN_DOM = true;
/* Parse profile info */ if (USE_PROFILES) {
ALLOWED_TAGS = addToSet({}, text);
ALLOWED_ATTR = create(null);
if (USE_PROFILES.html === true) {
addToSet(ALLOWED_TAGS, html$1);
addToSet(ALLOWED_ATTR, html);
}
if (USE_PROFILES.svg === true) {
addToSet(ALLOWED_TAGS, svg$1);
addToSet(ALLOWED_ATTR, svg);
addToSet(ALLOWED_ATTR, xml);
}
if (USE_PROFILES.svgFilters === true) {
addToSet(ALLOWED_TAGS, svgFilters);
addToSet(ALLOWED_ATTR, svg);
addToSet(ALLOWED_ATTR, xml);
}
if (USE_PROFILES.mathMl === true) {
addToSet(ALLOWED_TAGS, mathMl$1);
addToSet(ALLOWED_ATTR, mathMl);
addToSet(ALLOWED_ATTR, xml);
}
}
/* Always reset function-based ADD_TAGS / ADD_ATTR checks to prevent
* leaking across calls when switching from function to array config */ EXTRA_ELEMENT_HANDLING.tagCheck = null;
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
/* Merge configuration parameters */ if (cfg.ADD_TAGS) {
if (typeof cfg.ADD_TAGS === 'function') EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
else {
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) ALLOWED_TAGS = clone(ALLOWED_TAGS);
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
}
}
if (cfg.ADD_ATTR) {
if (typeof cfg.ADD_ATTR === 'function') EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
else {
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) ALLOWED_ATTR = clone(ALLOWED_ATTR);
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
}
}
if (cfg.ADD_URI_SAFE_ATTR) addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
if (cfg.FORBID_CONTENTS) {
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS);
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
}
if (cfg.ADD_FORBID_CONTENTS) {
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS);
addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
}
/* Add #text in case KEEP_CONTENT is set to true */ if (KEEP_CONTENT) ALLOWED_TAGS['#text'] = true;
/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ if (WHOLE_DOCUMENT) addToSet(ALLOWED_TAGS, [
'html',
'head',
'body'
]);
/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */ if (ALLOWED_TAGS.table) {
addToSet(ALLOWED_TAGS, [
'tbody'
]);
delete FORBID_TAGS.tbody;
}
if (cfg.TRUSTED_TYPES_POLICY) {
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
// Overwrite existing TrustedTypes policy.
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
// Sign local variables required by `sanitize`.
emptyHTML = trustedTypesPolicy.createHTML('');
} else {
// Uninitialized policy, attempt to initialize the internal dompurify policy.
if (trustedTypesPolicy === undefined) trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
// If creating the internal policy succeeded sign internal variables.
if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') emptyHTML = trustedTypesPolicy.createHTML('');
}
// Prevent further manipulation of configuration.
// Not available in IE8, Safari 5, etc.
if (freeze) freeze(cfg);
CONFIG = cfg;
};
/* Keep track of all possible SVG and MathML tags
* so that we can perform the namespace checks
* correctly. */ const ALL_SVG_TAGS = addToSet({}, [
...svg$1,
...svgFilters,
...svgDisallowed
]);
const ALL_MATHML_TAGS = addToSet({}, [
...mathMl$1,
...mathMlDisallowed
]);
/**
* @param element a DOM element whose namespace is being checked
* @returns Return false if the element has a
* namespace that a spec-compliant parser would never
* return. Return true otherwise.
*/ const _checkValidNamespace = function _checkValidNamespace(element) {
let parent = getParentNode(element);
// In JSDOM, if we're inside shadow DOM, then parentNode
// can be null. We just simulate parent in this case.
if (!parent || !parent.tagName) parent = {
namespaceURI: NAMESPACE,
tagName: 'template'
};
const tagName = stringToLowerCase(element.tagName);
const parentTagName = stringToLowerCase(parent.tagName);
if (!ALLOWED_NAMESPACES[element.namespaceURI]) return false;
if (element.namespaceURI === SVG_NAMESPACE) {
// The only way to switch from HTML namespace to SVG
// is via <svg>. If it happens via any other tag, then
// it should be killed.
if (parent.namespaceURI === HTML_NAMESPACE) return tagName === 'svg';
// The only way to switch from MathML to SVG is via`
// svg if parent is either <annotation-xml> or MathML
// text integration points.
if (parent.namespaceURI === MATHML_NAMESPACE) return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
// We only allow elements that are defined in SVG
// spec. All others are disallowed in SVG namespace.
return Boolean(ALL_SVG_TAGS[tagName]);
}
if (element.namespaceURI === MATHML_NAMESPACE) {
// The only way to switch from HTML namespace to MathML
// is via <math>. If it happens via any other tag, then
// it should be killed.
if (parent.namespaceURI === HTML_NAMESPACE) return tagName === 'math';
// The only way to switch from SVG to MathML is via
// <math> and HTML integration points
if (parent.namespaceURI === SVG_NAMESPACE) return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
// We only allow elements that are defined in MathML
// spec. All others are disallowed in MathML namespace.
return Boolean(ALL_MATHML_TAGS[tagName]);
}
if (element.namespaceURI === HTML_NAMESPACE) {
// The only way to switch from SVG to HTML is via
// HTML integration points, and from MathML to HTML
// is via MathML text integration points
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) return false;
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) return false;
// We disallow tags that are specific for MathML
// or SVG and should never appear in HTML namespace
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
}
// For XHTML and XML documents that support custom namespaces
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) return true;
// The code should never reach this place (this means
// that the element somehow got namespace that is not
// HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
// Return false just in case.
return false;
};
/**
* _forceRemove
*
* @param node a DOM node
*/ const _forceRemove = function _forceRemove(node) {
arrayPush(DOMPurify.removed, {
element: node
});
try {
// eslint-disable-next-line unicorn/prefer-dom-node-remove
getParentNode(node).removeChild(node);
} catch (_) {
remove(node);
}
};
/**
* _removeAttribute
*
* @param name an Attribute name
* @param element a DOM node
*/ const _removeAttribute = function _removeAttribute(name, element) {
try {
arrayPush(DOMPurify.removed, {
attribute: element.getAttributeNode(name),
from: element
});
} catch (_) {
arrayPush(DOMPurify.removed, {
attribute: null,
from: element
});
}
element.removeAttribute(name);
// We void attribute values for unremovable "is" attributes
if (name === 'is') {
if (RETURN_DOM || RETURN_DOM_FRAGMENT) try {
_forceRemove(element);
} catch (_) {}
else try {
element.setAttribute(name, '');
} catch (_) {}
}
};
/**
* _initDocument
*
* @param dirty - a string of dirty markup
* @return a DOM, filled with the dirty markup
*/ const _initDocument = function _initDocument(dirty) {
/* Create a HTML document */ let doc = null;
let leadingWhitespace = null;
if (FORCE_BODY) dirty = '<remove></remove>' + dirty;
else {
/* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */ const matches = stringMatch(dirty, /^[\r\n\t ]+/);
leadingWhitespace = matches && matches[0];
}
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
/*
* Use the DOMParser API by default, fallback later if needs be
* DOMParser not work for svg when has multiple root element.
*/ if (NAMESPACE === HTML_NAMESPACE) try {
doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
} catch (_) {}
/* Use createHTMLDocument in case DOMParser is not available */ if (!doc || !doc.documentElement) {
doc = implementation.createDocument(NAMESPACE, 'template', null);
try {
doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
} catch (_) {
// Syntax error if dirtyPayload is invalid xml
}
}
const body = doc.body || doc.documentElement;
if (dirty && leadingWhitespace) body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
/* Work on whole document or just its body */ if (NAMESPACE === HTML_NAMESPACE) return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
return WHOLE_DOCUMENT ? doc.documentElement : body;
};
/**
* Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
*
* @param root The root element or node to start traversing on.
* @return The created NodeIterator
*/ const _createNodeIterator = function _createNodeIterator(root) {
return createNodeIterator.call(root.ownerDocument || root, root, // eslint-disable-next-line no-bitwise
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
};
/**
* _isClobbered
*
* @param element element to check for clobbering attacks
* @return true if clobbered, false if safe
*/ const _isClobbered = function _isClobbered(element) {
return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
};
/**
* Checks whether the given object is a DOM node.
*
* @param value object to check whether it's a DOM node
* @return true is object is a DOM node
*/ const _isNode = function _isNode(value) {
return typeof Node === 'function' && value instanceof Node;
};
function _executeHooks(hooks, currentNode, data) {
arrayForEach(hooks, (hook)=>{
hook.call(DOMPurify, currentNode, data, CONFIG);
});
}
/**
* _sanitizeElements
*
* @protect nodeName
* @protect textContent
* @protect removeChild
* @param currentNode to check for permission to exist
* @return true if node was killed, false if left alive
*/ const _sanitizeElements = function _sanitizeElements(currentNode) {
let content = null;
/* Execute a hook if present */ _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
/* Check if element is clobbered or can clobber */ if (_isClobbered(currentNode)) {
_forceRemove(currentNode);
return true;
}
/* Now let's check the element's type and name */ const tagName = transformCaseFunc(currentNode.nodeName);
/* Execute a hook if present */ _executeHooks(hooks.uponSanitizeElement, currentNode, {
tagName,
allowedTags: ALLOWED_TAGS
});
/* Detect mXSS attempts abusing namespace confusion */ if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
_forceRemove(currentNode);
return true;
}
/* Remove risky CSS construction leading to mXSS */ if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
_forceRemove(currentNode);
return true;
}
/* Remove any occurrence of processing instructions */ if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
_forceRemove(currentNode);
return true;
}
/* Remove any kind of possibly harmful comments */ if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
_forceRemove(currentNode);
return true;
}
/* Remove element if anything forbids its presence */ if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
/* Check if we have a custom element to handle */ if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
}
/* Keep content except for bad-listed elements */ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
const parentNode = getParentNode(currentNode) || currentNode.parentNode;
const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
if (childNodes && parentNode) {
const childCount = childNodes.length;
for(let i = childCount - 1; i >= 0; --i){
const childClone = cloneNode(childNodes[i], true);
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
parentNode.insertBefore(childClone, getNextSibling(currentNode));
}
}
}
_forceRemove(currentNode);
return true;
}
/* Check whether element has a valid namespace */ if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
_forceRemove(currentNode);
return true;
}
/* Make sure that older browsers don't get fallback-tag mXSS */ if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
_forceRemove(currentNode);
return true;
}
/* Sanitize element content to be template-safe */ if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
/* Get the element's text content */ content = currentNode.textContent;
arrayForEach([
MUSTACHE_EXPR,
ERB_EXPR,
TMPLIT_EXPR
], (expr)=>{
content = stringReplace(content, expr, ' ');
});
if (currentNode.textContent !== content) {
arrayPush(DOMPurify.removed, {
element: currentNode.cloneNode()
});
currentNode.textContent = content;
}
}
/* Execute a hook if present */ _executeHooks(hooks.afterSanitizeElements, currentNode, null);
return false;
};
/**
* _isValidAttribute
*
* @param lcTag Lowercase tag name of containing element.
* @param lcName Lowercase attribute name.
* @param value Attribute value.
* @return Returns true if `value` is valid, otherwise false.
*/ // eslint-disable-next-line complexity
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
/* FORBID_ATTR must always win, even if ADD_ATTR predicate would allow it */ if (FORBID_ATTR[lcName]) return false;
/* Make sure attribute cannot clobber */ if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) return false;
/* Allow valid data-* attributes: At least one character after "-"
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
We don't need to check the value; it's always URI safe. */ if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ;
else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ;
else if (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ;
else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
if (// First condition does a very basic check if a) it's basically a valid custom element tagname AND
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
// and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
_isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName, lcTag)) || // Alternative, second condition checks if it's an `is`-attribute, AND
// the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ;
else return false;
/* Check value is safe. First, is attr inert? If so, is safe */ } else if (URI_SAFE_ATTRIBUTES[lcName]) ;
else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ;
else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ;
else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ;
else if (value) return false;
return true;
};
/**
* _isBasicCustomElement
* checks if at least one dash is included in tagName, and it's not the first char
* for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
*
* @param tagName name of the tag of the node to sanitize
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
*/ const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
};
/**
* _sanitizeAttributes
*
* @protect attributes
* @protect nodeName
* @protect removeAttribute
* @protect setAttribute
*
* @param currentNode to sanitize
*/ const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
/* Execute a hook if present */ _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
const { attributes } = currentNode;
/* Check if we have attributes; if not we might have a text node */ if (!attributes || _isClobbered(currentNode)) return;
const hookEvent = {
attrName: '',
attrValue: '',
keepAttr: true,
allowedAttributes: ALLOWED_ATTR,
forceKeepAttr: undefined
};
let l = attributes.length;
/* Go backwards over all attributes; safely remove bad ones */ while(l--){
const attr = attributes[l];
const { name, namespaceURI, value: attrValue } = attr;
const lcName = transformCaseFunc(name);
const initValue = attrValue;
let value = name === 'value' ? initValue : stringTrim(initValue);
/* Execute a hook if present */ hookEvent.attrName = lcName;
hookEvent.attrValue = value;
hookEvent.keepAttr = true;
hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
_executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
value = hookEvent.attrValue;
/* Full DOM Clobbering protection via namespace isolation,
* Prefix id and name attributes with `user-content-`
*/ if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
// Remove the attribute with this value
_removeAttribute(name, currentNode);
// Prefix the value and later re-create the attribute with the sanitized value
value = SANITIZE_NAMED_PROPS_PREFIX + value;
}
/* Work around a security issue with comments inside attributes */ if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
_removeAttribute(name, currentNode);
continue;
}
/* Make sure we cannot easily use animated hrefs, even if animations are allowed */ if (lcName === 'attributename' && stringMatch(value, 'href')) {
_removeAttribute(name, currentNode);
continue;
}
/* Did the hooks approve of the attribute? */ if (hookEvent.forceKeepAttr) continue;
/* Did the hooks approve of the attribute? */ if (!hookEvent.keepAttr) {
_removeAttribute(name, currentNode);
continue;
}
/* Work around a security issue in jQuery 3.0 */ if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
_removeAttribute(name, currentNode);
continue;
}
/* Sanitize attribute content to be template-safe */ if (SAFE_FOR_TEMPLATES) arrayForEach([
MUSTACHE_EXPR,
ERB_EXPR,
TMPLIT_EXPR
], (expr)=>{
value = stringReplace(value, expr, ' ');
});
/* Is `value` valid for this attribute? */ const lcTag = transformCaseFunc(currentNode.nodeName);
if (!_isValidAttribute(lcTag, lcName, value)) {
_removeAttribute(name, currentNode);
continue;
}
/* Handle attributes that require Trusted Types */ if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
if (namespaceURI) ;
else switch(trustedTypes.getAttributeType(lcTag, lcName)){
case 'TrustedHTML':
value = trustedTypesPolicy.createHTML(value);
break;
case 'TrustedScriptURL':
value = trustedTypesPolicy.createScriptURL(value);
break;
}
}
/* Handle invalid data-* attribute set by try-catching it */ if (value !== initValue) try {
if (namespaceURI) currentNode.setAttributeNS(namespaceURI, name, value);
else /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ currentNode.setAttribute(name, value);
if (_isClobbered(currentNode)) _forceRemove(currentNode);
else arrayPop(DOMPurify.removed);
} catch (_) {
_removeAttribute(name, currentNode);
}
}
/* Execute a hook if present */ _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
};
/**
* _sanitizeShadowDOM
*
* @param fragment to iterate over recursively
*/ const _sanitizeShadowDOM2 = function _sanitizeShadowDOM(fragment) {
let shadowNode = null;
const shadowIterator = _createNodeIterator(fragment);
/* Execute a hook if present */ _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
while(shadowNode = shadowIterator.nextNode()){
/* Execute a hook if present */ _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
/* Sanitize tags and elements */ _sanitizeElements(shadowNode);
/* Check attributes next */ _sanitizeAttributes(shadowNode);
/* Deep shadow DOM detected */ if (shadowNode.content instanceof DocumentFragment) _sanitizeShadowDOM2(shadowNode.content);
}
/* Execute a hook if present */ _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
};
// eslint-disable-next-line complexity
DOMPurify.sanitize = function(dirty) {
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
let body = null;
let importedNode = null;
let currentNode = null;
let returnNode = null;
/* Make sure we have a string to sanitize.
DO NOT return early, as this will return the wrong type if
the user has requested a DOM object rather than a string */ IS_EMPTY_INPUT = !dirty;
if (IS_EMPTY_INPUT) dirty = '<!-->';
/* Stringify, in case dirty is an object */ if (typeof dirty !== 'string' && !_isNode(dirty)) {
if (typeof dirty.toString === 'function') {
dirty = dirty.toString();
if (typeof dirty !== 'string') throw typeErrorCreate('dirty is not a string, aborting');
} else throw typeErrorCreate('toString is not a function');
}
/* Return dirty HTML if DOMPurify cannot run */ if (!DOMPurify.isSupported) return dirty;
/* Assign config vars */ if (!SET_CONFIG) _parseConfig(cfg);
/* Clean up removed elements */ DOMPurify.removed = [];
/* Check if dirty is correctly typed for IN_PLACE */ if (typeof dirty === 'string') IN_PLACE = false;
if (IN_PLACE) /* Do some early pre-sanitization to avoid unsafe root nodes */ {
if (dirty.nodeName) {
const tagName = transformCaseFunc(dirty.nodeName);
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
}
} else if (dirty instanceof Node) {
/* If dirty is a DOM element, append to an empty document to avoid
elements being stripped by the parser */ body = _initDocument('<!---->');
importedNode = body.ownerDocument.importNode(dirty, true);
if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') /* Node is already a body, use as is */ body = importedNode;
else if (importedNode.nodeName === 'HTML') body = importedNode;
else // eslint-disable-next-line unicorn/prefer-dom-node-append
body.appendChild(importedNode);
} else {
/* Exit directly if we have nothing to do */ if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && // eslint-disable-next-line unicorn/prefer-includes
dirty.indexOf('<') === -1) return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
/* Initialize the document to work on */ body = _initDocument(dirty);
/* Check we have a DOM node from the data */ if (!body) return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
}
/* Remove first element node (ours) if FORCE_BODY is set */ if (body && FORCE_BODY) _forceRemove(body.firstChild);
/* Get node iterator */ const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
/* Now start iterating over the created document */ while(currentNode = nodeIterator.nextNode()){
/* Sanitize tags and elements */ _sanitizeElements(currentNode);
/* Check attributes next */ _sanitizeAttributes(currentNode);
/* Shadow DOM detected, sanitize it */ if (currentNode.content instanceof DocumentFragment) _sanitizeShadowDOM2(currentNode.content);
}
/* If we sanitized `dirty` in-place, return it. */ if (IN_PLACE) return dirty;
/* Return sanitized string or DOM */ if (RETURN_DOM) {
if (SAFE_FOR_TEMPLATES) {
body.normalize();
let html = body.innerHTML;
arrayForEach([
MUSTACHE_EXPR,
ERB_EXPR,
TMPLIT_EXPR
], (expr)=>{
html = stringReplace(html, expr, ' ');
});
body.innerHTML = html;
}
if (RETURN_DOM_FRAGMENT) {
returnNode = createDocumentFragment.call(body.ownerDocument);
while(body.firstChild)// eslint-disable-next-line unicorn/prefer-dom-node-append
returnNode.appendChild(body.firstChild);
} else returnNode = body;
if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) /*
AdoptNode() is not used because internal state is not reset
(e.g. the past names map of a HTMLFormElement), this is safe
in theory but we would rather not risk another attack vector.
The state that is cloned by importNode() is explicitly defined
by the specs.
*/ returnNode = importNode.call(originalDocument, returnNode, true);
return returnNode;
}
let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
/* Serialize doctype if allowed */ if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
/* Sanitize final string template-safe */ if (SAFE_FOR_TEMPLATES) arrayForEach([
MUSTACHE_EXPR,
ERB_EXPR,
TMPLIT_EXPR
], (expr)=>{
serializedHTML = stringReplace(serializedHTML, expr, ' ');
});
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
};
DOMPurify.setConfig = function() {
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_parseConfig(cfg);
SET_CONFIG = true;
};
DOMPurify.clearConfig = function() {
CONFIG = null;
SET_CONFIG = false;
};
DOMPurify.isValidAttribute = function(tag, attr, value) {
/* Initialize shared config vars if necessary. */ if (!CONFIG) _parseConfig({});
const lcTag = transformCaseFunc(tag);
const lcName = transformCaseFunc(attr);
return _isValidAttribute(lcTag, lcName, value);
};
DOMPurify.addHook = function(entryPoint, hookFunction) {
if (typeof hookFunction !== 'function') return;
arrayPush(hooks[entryPoint], hookFunction);
};
DOMPurify.removeHook = function(entryPoint, hookFunction) {
if (hookFunction !== undefined) {
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
}
return arrayPop(hooks[entryPoint]);
};
DOMPurify.removeHooks = function(entryPoint) {
hooks[entryPoint] = [];
};
DOMPurify.removeAllHooks = function() {
hooks = _createHooksMap();
};
return DOMPurify;
}
var purify = createDOMPurify();
return purify;
});
},{}],"1L9cT":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "DecompressionTooLargeError", ()=>DecompressionTooLargeError);
parcelHelpers.export(exports, "copyShareableUrl", ()=>copyShareableUrl);
parcelHelpers.export(exports, "tryLoadSharedMarkers", ()=>tryLoadSharedMarkers);
parcelHelpers.export(exports, "__testing", ()=>__testing);
var _appState = require("../appState");
var _cropUtils = require("../crop-utils");
var _util = require("../util/util");
var _loadMarkersReview = require("./load-markers-review");
var _saveLoad = require("./save-load");
var _parseClipperInput = require("./parse-clipper-input");
var _shareFormat = require("./share-format");
const SHARE_FRAGMENT_RE = /#ytc\/markers\/([^/]+)\/([^/]+)\/([A-Za-z0-9_-]+)/;
function base64UrlEncode(bytes) {
let binary = '';
for(let i = 0; i < bytes.byteLength; i++)binary += String.fromCharCode(bytes[i]);
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function base64UrlDecode(str) {
const padded = str.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - str.length % 4) % 4);
const binary = atob(padded);
const bytes = new Uint8Array(binary.length);
for(let i = 0; i < binary.length; i++)bytes[i] = binary.charCodeAt(i);
return bytes;
}
async function compressBytes(bytes) {
const stream = new Blob([
bytes
]).stream().pipeThrough(new CompressionStream('deflate-raw'));
const buf = await new Response(stream).arrayBuffer();
return new Uint8Array(buf);
}
const MAX_DECOMPRESSED_BYTES = 262144;
/** Hard cap on the base64 fragment length before we even attempt to decode
* it. Chosen at ~4× the bound implied by `MAX_DECOMPRESSED_BYTES` so any
* legitimately-encoded payload that decompresses within the cap easily
* fits, while a maliciously-large fragment is refused before it can
* consume CPU/memory on base64 + decompression. */ const MAX_BASE64_FRAGMENT_LEN = 1048576;
class DecompressionTooLargeError extends Error {
constructor(limit){
super(`share-format: decompressed payload exceeded ${limit} bytes`), this.limit = limit;
this.name = 'DecompressionTooLargeError';
}
}
async function decompressBytes(bytes) {
const stream = new Blob([
bytes
]).stream().pipeThrough(new DecompressionStream('deflate-raw'));
const reader = stream.getReader();
const chunks = [];
let total = 0;
try {
while(true){
const { value, done } = await reader.read();
if (done) break;
if (!value) continue;
total += value.byteLength;
if (total > MAX_DECOMPRESSED_BYTES) {
try {
await reader.cancel();
} catch {
// best-effort cancel; ignore
}
throw new DecompressionTooLargeError(MAX_DECOMPRESSED_BYTES);
}
chunks.push(value);
}
} finally{
reader.releaseLock();
}
const out = new Uint8Array(total);
let offset = 0;
for (const chunk of chunks){
out.set(chunk, offset);
offset += chunk.byteLength;
}
return out;
}
function buildSharePayload() {
const s = (0, _appState.appState).settings;
const settings = {};
if (s.cropResWidth != null && s.cropResHeight != null) {
settings.cropResWidth = s.cropResWidth;
settings.cropResHeight = s.cropResHeight;
}
if (s.titleSuffix) settings.titleSuffix = s.titleSuffix;
if (s.newMarkerSpeed != null && s.newMarkerSpeed !== 1) settings.newMarkerSpeed = s.newMarkerSpeed;
if (s.newMarkerCrop) settings.newMarkerCrop = s.newMarkerCrop;
if (s.markerPairMergeList) settings.markerPairMergeList = s.markerPairMergeList;
const markerPairs = (0, _appState.appState).markerPairs.map((pair)=>{
const out = {
start: pair.start,
end: pair.end,
speed: typeof pair.speed === 'string' ? Number(pair.speed) : pair.speed,
crop: pair.crop
};
if (pair.enableZoomPan) out.enableZoomPan = true;
if ((0, _saveLoad.isVariableSpeed)(pair.speedMap)) out.speedMap = pair.speedMap;
if (!(0, _cropUtils.isStaticCrop)(pair.cropMap)) out.cropMap = pair.cropMap;
if (pair.overrides && Object.keys(pair.overrides).length > 0) out.overrides = pair.overrides;
return out;
});
return {
settings,
markerPairs
};
}
function pad2(n) {
return n < 10 ? `0${n}` : String(n);
}
// UTC so the share URL doesn't leak the author's local timezone offset.
function buildUtcDate() {
const d = new Date();
const date = `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}`;
const time = `${pad2(d.getUTCHours())}-${pad2(d.getUTCMinutes())}-${pad2(d.getUTCSeconds())}`;
return {
date,
time
};
}
async function copyShareableUrl() {
try {
const payload = buildSharePayload();
const bytes = (0, _shareFormat.serializeBinary)(payload);
const compressed = await compressBytes(bytes);
const encoded = base64UrlEncode(compressed);
const { date, time } = buildUtcDate();
const baseUrl = location.href.split('#')[0];
const shareUrl = `${baseUrl}#ytc/markers/${date}/${time}/${encoded}`;
if (shareUrl.length > 6000) (0, _util.flashMessage)(`Shareable URL is ${shareUrl.length} chars \u{2014} may not work in all platforms. Consider exporting JSON instead.`, 'olive');
(0, _util.copyToClipboard)(shareUrl);
(0, _util.flashMessage)(`Copied shareable URL (${shareUrl.length} chars) to clipboard.`, 'green');
} catch (err) {
console.error('Failed to build shareable URL', err);
(0, _util.flashMessage)('Failed to build shareable URL. See console.', 'red');
}
}
function readSharedMarkersFromUrl() {
const hash = location.hash;
if (!hash) return null;
const match = SHARE_FRAGMENT_RE.exec(hash);
return match ? match[3] : null;
}
function stripSharedMarkersFromUrl() {
const hash = location.hash;
if (!hash) return;
const cleaned = hash.replace(SHARE_FRAGMENT_RE, '');
const suffix = cleaned && cleaned !== '#' ? cleaned : '';
history.replaceState(null, '', location.pathname + location.search + suffix);
}
function payloadToClipperInputObject(p) {
const markerPairs = p.markerPairs.map((pair, idx)=>({
number: idx + 1,
start: pair.start,
end: pair.end,
speed: pair.speed,
crop: pair.crop,
enableZoomPan: pair.enableZoomPan ?? false,
speedMap: pair.speedMap ?? undefined,
cropMap: pair.cropMap ?? undefined,
overrides: pair.overrides ?? {}
}));
return {
...p.settings,
version: (0, _appState.__version__),
markerPairs
};
}
async function tryLoadSharedMarkers() {
const encoded = readSharedMarkersFromUrl();
if (!encoded) return;
if ((0, _appState.appState).markerPairs.length > 0) {
(0, _util.flashMessage)("Shared markers detected in URL but existing markers present \u2014 clear them first to load.", 'olive');
return;
}
// Refuse oversized fragments before paying the base64 + decompression
// cost. The downstream `MAX_DECOMPRESSED_BYTES` cap (256 KB) is enforced
// per chunk during inflate, but reaching it requires walking the entire
// base64 string + spinning up a DecompressionStream. This pre-check
// shortcuts that work on a maliciously-large URL fragment.
if (encoded.length > MAX_BASE64_FRAGMENT_LEN) {
console.error('Shared URL payload base64 length', encoded.length, 'exceeds limit', MAX_BASE64_FRAGMENT_LEN);
(0, _util.flashMessage)("Shared URL payload is too large \u2014 refusing to decode.", 'red');
stripSharedMarkersFromUrl();
return;
}
let compressed;
try {
compressed = base64UrlDecode(encoded);
} catch (err) {
console.error('Shared URL payload base64 decode failed', err);
(0, _util.flashMessage)('Shared URL payload is corrupt (base64 decode failed).', 'red');
stripSharedMarkersFromUrl();
return;
}
let bytes;
try {
bytes = await decompressBytes(compressed);
} catch (err) {
if (err instanceof DecompressionTooLargeError) {
console.error('Shared URL payload exceeds size limit', err);
(0, _util.flashMessage)("Shared URL payload is too large \u2014 refusing to decompress.", 'red');
stripSharedMarkersFromUrl();
return;
}
console.error('Shared URL payload inflate failed', err);
(0, _util.flashMessage)('Shared URL payload is corrupt (inflate failed).', 'red');
stripSharedMarkersFromUrl();
return;
}
let payload;
try {
payload = (0, _shareFormat.deserializeBinary)(bytes);
} catch (err) {
if (err instanceof (0, _shareFormat.UnsupportedShareVersionError)) {
console.error('Unsupported share format version', err);
(0, _util.flashMessage)(`Shared URL uses format v${err.version}. Upgrade the userscript to load it.`, 'red');
return;
}
if (err instanceof (0, _shareFormat.ShareFormatLimitError)) {
console.error('Shared URL payload exceeds structural limits', err);
(0, _util.flashMessage)("Shared URL payload claims too many items \u2014 refusing to load.", 'red');
stripSharedMarkersFromUrl();
return;
}
if (err instanceof RangeError) {
console.error('Shared URL binary decode overran buffer', err);
(0, _util.flashMessage)('Shared URL payload is corrupt (truncated).', 'red');
stripSharedMarkersFromUrl();
return;
}
console.error('Shared URL binary decode failed', err);
(0, _util.flashMessage)('Shared URL payload is corrupt.', 'red');
stripSharedMarkersFromUrl();
return;
}
let clipperInput;
try {
clipperInput = payloadToClipperInputObject(payload);
} catch (err) {
console.error('Failed to format shared markers JSON', err);
(0, _util.flashMessage)('Failed to format shared markers. See console.', 'red');
return;
}
showSharedMarkersModal(clipperInput);
}
function showSharedMarkersModal(clipperInput) {
let result;
try {
result = (0, _parseClipperInput.parseClipperInput)(clipperInput);
} catch (err) {
if (err instanceof (0, _parseClipperInput.ClipperInputValidationError)) {
console.error('Shared URL payload failed validation', err);
(0, _util.flashMessage)(`Shared URL payload rejected: ${err.message}`, 'red');
stripSharedMarkersFromUrl();
return;
}
throw err;
}
const pairCount = result.input.markerPairs.length;
(0, _loadMarkersReview.showLoadMarkersReviewModal)({
modalTitle: 'Load shared markers?',
warning: `\u{26A0} Review the JSON below before loading. Shared URLs come from untrusted sources.
Loading will overwrite your current settings and add ${pairCount} marker pair(s).`,
sourceLabel: 'shared URL',
payload: result.input,
issues: result.issues,
onLoad: ()=>{
(0, _saveLoad.applyClipperInput)(result.input);
(0, _util.flashMessage)(`Loaded ${pairCount} marker pair(s) from shared URL.`, 'green');
stripSharedMarkersFromUrl();
},
onDismiss: ()=>{
stripSharedMarkersFromUrl();
(0, _util.flashMessage)('Dismissed shared markers URL.', 'olive');
}
});
}
const __testing = {
SHARE_FORMAT_VERSION: (0, _shareFormat.SHARE_FORMAT_VERSION),
SHARE_FRAGMENT_RE,
MAX_BASE64_FRAGMENT_LEN,
MAX_DECOMPRESSED_BYTES
};
},{"../appState":"g0AlP","../crop-utils":"k2gwb","../util/util":"99arg","./load-markers-review":"1yueE","./save-load":"Sn5Vp","./parse-clipper-input":"hnA8h","./share-format":"k2CCX","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"k2CCX":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "SHARE_FORMAT_VERSION", ()=>SHARE_FORMAT_VERSION);
parcelHelpers.export(exports, "MAX_PAIR_COUNT", ()=>MAX_PAIR_COUNT);
parcelHelpers.export(exports, "MAX_MAP_POINT_COUNT", ()=>MAX_MAP_POINT_COUNT);
parcelHelpers.export(exports, "ShareFormatLimitError", ()=>ShareFormatLimitError);
parcelHelpers.export(exports, "DENOISE_PRESET_ORDER", ()=>DENOISE_PRESET_ORDER);
parcelHelpers.export(exports, "VSTAB_PRESET_ORDER", ()=>VSTAB_PRESET_ORDER);
parcelHelpers.export(exports, "LOOP_ORDER", ()=>LOOP_ORDER);
parcelHelpers.export(exports, "ByteWriter", ()=>ByteWriter);
parcelHelpers.export(exports, "ByteReader", ()=>ByteReader);
parcelHelpers.export(exports, "quantizeTime", ()=>quantizeTime);
parcelHelpers.export(exports, "dequantizeTime", ()=>dequantizeTime);
parcelHelpers.export(exports, "quantizeCentipoint", ()=>quantizeCentipoint);
parcelHelpers.export(exports, "dequantizeCentipoint", ()=>dequantizeCentipoint);
parcelHelpers.export(exports, "serializeBinary", ()=>serializeBinary);
parcelHelpers.export(exports, "deserializeBinary", ()=>deserializeBinary);
parcelHelpers.export(exports, "UnsupportedShareVersionError", ()=>UnsupportedShareVersionError);
var _presets = require("../features/settings/presets");
const SHARE_FORMAT_VERSION = 0x01;
const MAX_PAIR_COUNT = 10000;
const MAX_MAP_POINT_COUNT = 10000;
// Starting capacity for ByteWriter's backing buffer. Sized to fit a typical
// small share (≤ ~100 B) in one allocation; grows by doubling on overflow.
const INITIAL_BUFFER_SIZE = 256;
// Upper bound on bytes consumed by a single varuint — enough headroom for any
// value that real fields produce, and tight enough to terminate on corrupted
// input rather than reading forever.
const VARUINT_MAX_BYTES = 9;
// ffmpeg crop filter expects exactly "x:y:w:h" (4 colon-separated parts).
const CROP_PART_COUNT = 4;
class ShareFormatLimitError extends Error {
constructor(message){
super(message);
this.name = 'ShareFormatLimitError';
}
}
const DENOISE_PRESET_ORDER = [
'Disabled',
'Very Weak',
'Weak',
'Medium',
'Strong',
'Very Strong'
];
const VSTAB_PRESET_ORDER = [
'Disabled',
'Very Weak',
'Weak',
'Medium',
'Strong',
'Very Strong',
'Strongest'
];
const LOOP_ORDER = [
'none',
'fwrev',
'fade'
];
class ByteWriter {
ensure(n) {
if (this.length + n <= this.buf.byteLength) return;
let next = this.buf.byteLength * 2;
while(this.length + n > next)next *= 2;
const grown = new Uint8Array(next);
grown.set(this.buf, 0);
this.buf = grown;
}
writeByte(b) {
this.ensure(1);
this.buf[this.length++] = b & 0xff;
}
writeBytes(bytes) {
this.ensure(bytes.byteLength);
this.buf.set(bytes, this.length);
this.length += bytes.byteLength;
}
writeVaruint(n) {
if (!Number.isFinite(n) || n < 0) throw new Error(`share-format: invalid varuint ${n}`);
let v = Math.floor(n);
while(v >= 0x80){
this.writeByte(v & 0x7f | 0x80);
v = Math.floor(v / 128);
}
this.writeByte(v);
}
writeVarsint(n) {
if (!Number.isFinite(n)) throw new Error(`share-format: invalid varsint ${n}`);
const zig = n >= 0 ? n * 2 : -n * 2 - 1;
this.writeVaruint(zig);
}
writeStr(s) {
const bytes = new TextEncoder().encode(s);
this.writeVaruint(bytes.byteLength);
this.writeBytes(bytes);
}
toUint8Array() {
return this.buf.slice(0, this.length);
}
constructor(){
this.buf = new Uint8Array(INITIAL_BUFFER_SIZE);
this.length = 0;
}
}
class ByteReader {
constructor(buf){
this.buf = buf;
this.pos = 0;
}
check(n) {
if (this.pos + n > this.buf.byteLength) throw new RangeError(`share-format: read past end at pos=${this.pos} need=${n} size=${this.buf.byteLength}`);
}
readByte() {
this.check(1);
return this.buf[this.pos++];
}
readVaruint() {
let result = 0;
let mult = 1;
for(let i = 0; i < VARUINT_MAX_BYTES; i++){
const byte = this.readByte();
result += (byte & 0x7f) * mult;
if ((byte & 0x80) === 0) return result;
mult *= 128;
}
throw new Error('share-format: varuint too long');
}
readVarsint() {
const z = this.readVaruint();
return z % 2 === 0 ? z / 2 : -(z + 1) / 2;
}
readStr() {
const len = this.readVaruint();
this.check(len);
const bytes = this.buf.subarray(this.pos, this.pos + len);
this.pos += len;
return new TextDecoder().decode(bytes);
}
eof() {
return this.pos >= this.buf.byteLength;
}
}
// Parse a "x:y:w:h" crop string into its parts plus a bitmask indicating which
// slots hold the literal keywords `iw` / `ih`. Slot i uses `iw` when i is even,
// `ih` when odd. Shared by writeCrop and writeCropMap — both need the same
// validation and literal detection before emitting bytes.
function parseCrop(crop) {
const parts = crop.split(':');
if (parts.length !== CROP_PART_COUNT) throw new Error(`share-format: crop must have ${CROP_PART_COUNT} parts, got "${crop}"`);
let literalFlags = 0;
for(let i = 0; i < CROP_PART_COUNT; i++)if (parts[i] === (i % 2 === 0 ? 'iw' : 'ih')) literalFlags |= 1 << i;
return {
parts,
literalFlags
};
}
function parseCropNumeric(parts, i, crop) {
const n = parseInt(parts[i], 10);
if (!Number.isFinite(n) || n < 0) throw new Error(`share-format: non-numeric crop part "${parts[i]}" in "${crop}"`);
return n;
}
function writeCrop(w, crop) {
const { parts, literalFlags } = parseCrop(crop);
w.writeByte(literalFlags);
for(let i = 0; i < CROP_PART_COUNT; i++){
if (literalFlags & 1 << i) continue;
w.writeVaruint(parseCropNumeric(parts, i, crop));
}
}
function readCrop(r) {
const flags = r.readByte();
const parts = [];
for(let i = 0; i < CROP_PART_COUNT; i++)if (flags & 1 << i) parts.push(i % 2 === 0 ? 'iw' : 'ih');
else parts.push(String(r.readVaruint()));
return parts.join(':');
}
function quantizeTime(seconds) {
return Math.round(seconds * 1000);
}
function dequantizeTime(ms) {
return ms / 1000;
}
function quantizeCentipoint(v) {
return Math.round(v * 100);
}
function dequantizeCentipoint(n) {
return n / 100;
}
const SETTINGS_BIT_CROP_RES = 1;
const SETTINGS_BIT_TITLE_SUFFIX = 2;
const SETTINGS_BIT_NEW_MARKER_SPEED = 4;
const SETTINGS_BIT_NEW_MARKER_CROP = 8;
const SETTINGS_BIT_MERGE_LIST = 16;
const PAIR_BIT_SPEED_MAP = 1;
const PAIR_BIT_CROP_MAP = 2;
const PAIR_BIT_ZOOM_PAN = 4;
const PAIR_BIT_OVERRIDES = 8;
const PAIR_BIT_DEFAULT_SPEED = 16;
const SPEED_DEFAULT_CENTIPOINT = 100;
// cropMap point flag byte: bits 0-3 mark iw/ih literals per slot (x,y,w,h),
// bit 4 marks easeIn='instant'. Kept in one byte vs. flags + separate easeIn byte.
const CROP_MAP_FLAG_EASE_IN = 16;
const OV_BIT_TITLE_PREFIX = 1;
const OV_BIT_ENABLE_HDR = 2;
const OV_BIT_GAMMA = 4;
const OV_BIT_ENCODE_SPEED = 8;
const OV_BIT_CRF = 16;
const OV_BIT_TARGET_MAX_BITRATE = 32;
const OV_BIT_TWO_PASS = 64;
const OV_BIT_DENOISE = 128;
const OV_BIT_AUDIO = 256;
const OV_BIT_VSTAB = 512;
const OV_BIT_VSTAB_DZ = 1024;
const OV_BIT_MINTERP_FPS_MUL = 2048;
const OV_BIT_LOOP = 4096;
const OV_BIT_FADE_DURATION = 8192;
function findDenoiseId(d) {
const idx = DENOISE_PRESET_ORDER.indexOf(d.desc);
if (idx < 0) {
console.warn(`share-format: unknown denoise preset "${d.desc}", snapping to Disabled`);
return 0;
}
return idx;
}
function findVStabId(v) {
const idx = VSTAB_PRESET_ORDER.indexOf(v.desc);
if (idx < 0) {
console.warn(`share-format: unknown videoStabilization preset "${v.desc}", snapping to Disabled`);
return 0;
}
return idx;
}
function denoisePresetFromId(id) {
const key = DENOISE_PRESET_ORDER[id] ?? DENOISE_PRESET_ORDER[0];
return {
...(0, _presets.presetsMap).denoise[key]
};
}
function vstabPresetFromId(id) {
const key = VSTAB_PRESET_ORDER[id] ?? VSTAB_PRESET_ORDER[0];
return {
...(0, _presets.presetsMap).videoStabilization[key]
};
}
function writeSpeedMap(w, points) {
if (points.length > MAX_MAP_POINT_COUNT) throw new ShareFormatLimitError(`share-format: speedMap count ${points.length} exceeds limit ${MAX_MAP_POINT_COUNT}`);
w.writeVaruint(points.length);
let prevX = 0;
for (const p of points){
const x = quantizeTime(p.x);
w.writeVarsint(x - prevX);
prevX = x;
w.writeVaruint(quantizeCentipoint(p.y));
}
}
function readSpeedMap(r) {
const count = r.readVaruint();
if (count > MAX_MAP_POINT_COUNT) throw new ShareFormatLimitError(`share-format: speedMap count ${count} exceeds limit ${MAX_MAP_POINT_COUNT}`);
const points = [];
let prevX = 0;
for(let i = 0; i < count; i++){
prevX += r.readVarsint();
const y = dequantizeCentipoint(r.readVaruint());
points.push({
x: dequantizeTime(prevX),
y
});
}
return points;
}
// cropMap point encoding:
// varsint(Δx)
// flagsByte: bits 0-3 = is-literal (iw/ih) per slot {x,y,w,h}; bit 4 = easeIn='instant'
// for each numeric slot (not literal): varsint(value - lastNumeric[slot])
// Literal slots contribute nothing; lastNumeric tracks the last *numeric* value per
// slot across the whole cropMap (init 0), so a repeated crop compresses to 4 zero deltas.
function writeCropMap(w, points) {
if (points.length > MAX_MAP_POINT_COUNT) throw new ShareFormatLimitError(`share-format: cropMap count ${points.length} exceeds limit ${MAX_MAP_POINT_COUNT}`);
w.writeVaruint(points.length);
let prevX = 0;
const lastNumeric = [
0,
0,
0,
0
];
for (const p of points){
const x = quantizeTime(p.x);
w.writeVarsint(x - prevX);
prevX = x;
const { parts, literalFlags } = parseCrop(p.crop);
let flags = literalFlags;
if (p.easeIn === 'instant') flags |= CROP_MAP_FLAG_EASE_IN;
w.writeByte(flags);
for(let i = 0; i < CROP_PART_COUNT; i++){
if (literalFlags & 1 << i) continue;
const n = parseCropNumeric(parts, i, p.crop);
w.writeVarsint(n - lastNumeric[i]);
lastNumeric[i] = n;
}
}
}
function readCropMap(r) {
const count = r.readVaruint();
if (count > MAX_MAP_POINT_COUNT) throw new ShareFormatLimitError(`share-format: cropMap count ${count} exceeds limit ${MAX_MAP_POINT_COUNT}`);
const points = [];
let prevX = 0;
const lastNumeric = [
0,
0,
0,
0
];
for(let i = 0; i < count; i++){
prevX += r.readVarsint();
const flags = r.readByte();
const parts = [];
for(let j = 0; j < CROP_PART_COUNT; j++)if (flags & 1 << j) parts.push(j % 2 === 0 ? 'iw' : 'ih');
else {
lastNumeric[j] += r.readVarsint();
parts.push(String(lastNumeric[j]));
}
const point = {
x: dequantizeTime(prevX),
y: 0,
crop: parts.join(':')
};
if (flags & CROP_MAP_FLAG_EASE_IN) point.easeIn = 'instant';
points.push(point);
}
return points;
}
// Overrides: a varuint mask selects which fields are present, then non-enum fields
// are written in order, then (if any of DENOISE/VSTAB/LOOP is set) one packed
// enum byte carries all three small-domain enums: denoise (3 bits) | vstab (3 bits) | loop (2 bits).
const OV_ENUM_MASK = OV_BIT_DENOISE | OV_BIT_VSTAB | OV_BIT_LOOP;
function writeOverrides(w, o) {
let mask = 0;
if (o.titlePrefix != null) mask |= OV_BIT_TITLE_PREFIX;
if (o.enableHDR === true) mask |= OV_BIT_ENABLE_HDR;
if (o.gamma != null) mask |= OV_BIT_GAMMA;
if (o.encodeSpeed != null) mask |= OV_BIT_ENCODE_SPEED;
if (o.crf != null) mask |= OV_BIT_CRF;
if (o.targetMaxBitrate != null) mask |= OV_BIT_TARGET_MAX_BITRATE;
if (o.twoPass === true) mask |= OV_BIT_TWO_PASS;
if (o.denoise != null) mask |= OV_BIT_DENOISE;
if (o.audio === true) mask |= OV_BIT_AUDIO;
if (o.videoStabilization != null) mask |= OV_BIT_VSTAB;
if (o.videoStabilizationDynamicZoom === true) mask |= OV_BIT_VSTAB_DZ;
if (o.minterpFpsMultiplier != null) mask |= OV_BIT_MINTERP_FPS_MUL;
if (o.loop != null) mask |= OV_BIT_LOOP;
if (o.fadeDuration != null) mask |= OV_BIT_FADE_DURATION;
w.writeVaruint(mask);
if (o.titlePrefix != null) w.writeStr(o.titlePrefix);
if (o.gamma != null) w.writeVaruint(quantizeCentipoint(o.gamma));
if (o.encodeSpeed != null) w.writeVaruint(quantizeCentipoint(o.encodeSpeed));
if (o.crf != null) w.writeVaruint(o.crf);
if (o.targetMaxBitrate != null) w.writeVaruint(o.targetMaxBitrate);
if (o.minterpFpsMultiplier != null) w.writeVaruint(quantizeCentipoint(o.minterpFpsMultiplier));
if (o.fadeDuration != null) w.writeVaruint(quantizeCentipoint(o.fadeDuration));
if (mask & OV_ENUM_MASK) {
const d = o.denoise != null ? findDenoiseId(o.denoise) : 0;
const v = o.videoStabilization != null ? findVStabId(o.videoStabilization) : 0;
const idx = o.loop != null ? LOOP_ORDER.indexOf(o.loop) : 0;
const l = idx < 0 ? 0 : idx;
w.writeByte(d & 0x07 | (v & 0x07) << 3 | (l & 0x03) << 6);
}
}
function readOverrides(r) {
const mask = r.readVaruint();
const o = {};
if (mask & OV_BIT_TITLE_PREFIX) o.titlePrefix = r.readStr();
if (mask & OV_BIT_ENABLE_HDR) o.enableHDR = true;
if (mask & OV_BIT_GAMMA) o.gamma = dequantizeCentipoint(r.readVaruint());
if (mask & OV_BIT_ENCODE_SPEED) o.encodeSpeed = dequantizeCentipoint(r.readVaruint());
if (mask & OV_BIT_CRF) o.crf = r.readVaruint();
if (mask & OV_BIT_TARGET_MAX_BITRATE) o.targetMaxBitrate = r.readVaruint();
if (mask & OV_BIT_TWO_PASS) o.twoPass = true;
if (mask & OV_BIT_AUDIO) o.audio = true;
if (mask & OV_BIT_VSTAB_DZ) o.videoStabilizationDynamicZoom = true;
if (mask & OV_BIT_MINTERP_FPS_MUL) o.minterpFpsMultiplier = dequantizeCentipoint(r.readVaruint());
if (mask & OV_BIT_FADE_DURATION) o.fadeDuration = dequantizeCentipoint(r.readVaruint());
if (mask & OV_ENUM_MASK) {
const enumByte = r.readByte();
if (mask & OV_BIT_DENOISE) o.denoise = denoisePresetFromId(enumByte & 0x07);
if (mask & OV_BIT_VSTAB) o.videoStabilization = vstabPresetFromId(enumByte >> 3 & 0x07);
if (mask & OV_BIT_LOOP) o.loop = LOOP_ORDER[enumByte >> 6 & 0x03] ?? 'none';
}
return o;
}
function writeSettings(w, s) {
let mask = 0;
if (s.cropResWidth != null && s.cropResHeight != null) mask |= SETTINGS_BIT_CROP_RES;
if (s.titleSuffix) mask |= SETTINGS_BIT_TITLE_SUFFIX;
if (s.newMarkerSpeed != null && s.newMarkerSpeed !== 1) mask |= SETTINGS_BIT_NEW_MARKER_SPEED;
if (s.newMarkerCrop) mask |= SETTINGS_BIT_NEW_MARKER_CROP;
if (s.markerPairMergeList) mask |= SETTINGS_BIT_MERGE_LIST;
w.writeVaruint(mask);
if (s.cropResWidth != null && s.cropResHeight != null) {
w.writeVaruint(s.cropResWidth);
w.writeVaruint(s.cropResHeight);
}
if (s.titleSuffix) w.writeStr(s.titleSuffix);
if (s.newMarkerSpeed != null && s.newMarkerSpeed !== 1) w.writeVaruint(quantizeCentipoint(s.newMarkerSpeed));
if (s.newMarkerCrop) writeCrop(w, s.newMarkerCrop);
if (s.markerPairMergeList) w.writeStr(s.markerPairMergeList);
}
function readSettings(r) {
const mask = r.readVaruint();
const settings = {};
if (mask & SETTINGS_BIT_CROP_RES) {
settings.cropResWidth = r.readVaruint();
settings.cropResHeight = r.readVaruint();
}
if (mask & SETTINGS_BIT_TITLE_SUFFIX) settings.titleSuffix = r.readStr();
if (mask & SETTINGS_BIT_NEW_MARKER_SPEED) settings.newMarkerSpeed = dequantizeCentipoint(r.readVaruint());
if (mask & SETTINGS_BIT_NEW_MARKER_CROP) settings.newMarkerCrop = readCrop(r);
if (mask & SETTINGS_BIT_MERGE_LIST) settings.markerPairMergeList = r.readStr();
return settings;
}
// Pair layout: mask varuint, then start (abs varuint for index 0, else Δ varsint),
// duration varuint, optional speed varuint, crop, and optional sub-blocks in the
// order speedMap → cropMap → overrides. Returns the pair's quantized start in ms
// so the caller can thread the delta state forward.
function writePair(w, p, index, prevStart) {
const speedCenti = quantizeCentipoint(p.speed);
let mask = 0;
if (p.speedMap && p.speedMap.length > 0) mask |= PAIR_BIT_SPEED_MAP;
if (p.cropMap && p.cropMap.length > 0) mask |= PAIR_BIT_CROP_MAP;
if (p.enableZoomPan === true) mask |= PAIR_BIT_ZOOM_PAN;
if (p.overrides && Object.keys(p.overrides).length > 0) mask |= PAIR_BIT_OVERRIDES;
if (speedCenti === SPEED_DEFAULT_CENTIPOINT) mask |= PAIR_BIT_DEFAULT_SPEED;
w.writeVaruint(mask);
const startMs = quantizeTime(p.start);
if (index === 0) w.writeVaruint(startMs);
else w.writeVarsint(startMs - prevStart);
const durationMs = Math.max(0, quantizeTime(p.end) - startMs);
w.writeVaruint(durationMs);
if (!(mask & PAIR_BIT_DEFAULT_SPEED)) w.writeVaruint(speedCenti);
writeCrop(w, p.crop);
if (p.speedMap && p.speedMap.length > 0) writeSpeedMap(w, p.speedMap);
if (p.cropMap && p.cropMap.length > 0) writeCropMap(w, p.cropMap);
if (p.overrides && Object.keys(p.overrides).length > 0) writeOverrides(w, p.overrides);
return startMs;
}
function readPair(r, index, prevStart) {
const mask = r.readVaruint();
const startMs = index === 0 ? r.readVaruint() : prevStart + r.readVarsint();
const durationMs = r.readVaruint();
const speed = mask & PAIR_BIT_DEFAULT_SPEED ? dequantizeCentipoint(SPEED_DEFAULT_CENTIPOINT) : dequantizeCentipoint(r.readVaruint());
const crop = readCrop(r);
const pair = {
start: dequantizeTime(startMs),
end: dequantizeTime(startMs + durationMs),
speed,
crop
};
if (mask & PAIR_BIT_ZOOM_PAN) pair.enableZoomPan = true;
if (mask & PAIR_BIT_SPEED_MAP) pair.speedMap = readSpeedMap(r);
if (mask & PAIR_BIT_CROP_MAP) pair.cropMap = readCropMap(r);
if (mask & PAIR_BIT_OVERRIDES) pair.overrides = readOverrides(r);
return {
pair,
startMs
};
}
function serializeBinary(payload) {
if (payload.markerPairs.length > MAX_PAIR_COUNT) throw new ShareFormatLimitError(`share-format: pair count ${payload.markerPairs.length} exceeds limit ${MAX_PAIR_COUNT}`);
const w = new ByteWriter();
w.writeByte(SHARE_FORMAT_VERSION);
writeSettings(w, payload.settings);
w.writeVaruint(payload.markerPairs.length);
let prevStart = 0;
for(let i = 0; i < payload.markerPairs.length; i++)prevStart = writePair(w, payload.markerPairs[i], i, prevStart);
return w.toUint8Array();
}
function deserializeBinary(bytes) {
const r = new ByteReader(bytes);
const version = r.readByte();
if (version !== SHARE_FORMAT_VERSION) throw new UnsupportedShareVersionError(version);
const settings = readSettings(r);
const pairCount = r.readVaruint();
if (pairCount > MAX_PAIR_COUNT) throw new ShareFormatLimitError(`share-format: pair count ${pairCount} exceeds limit ${MAX_PAIR_COUNT}`);
const markerPairs = [];
let prevStart = 0;
for(let i = 0; i < pairCount; i++){
const { pair, startMs } = readPair(r, i, prevStart);
markerPairs.push(pair);
prevStart = startMs;
}
return {
settings,
markerPairs
};
}
class UnsupportedShareVersionError extends Error {
constructor(version){
super(`Unsupported share format version: ${version}`), this.version = version;
this.name = 'UnsupportedShareVersionError';
}
}
},{"../features/settings/presets":"9eKT1","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"9eKT1":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "presetsMap", ()=>presetsMap);
const presetsMap = {
videoStabilization: {
Disabled: {
desc: 'Disabled',
enabled: false
},
'Very Weak': {
desc: 'Very Weak',
enabled: true,
shakiness: 2,
smoothing: 2,
zoomspeed: 0.05
},
Weak: {
desc: 'Weak',
enabled: true,
shakiness: 4,
smoothing: 4,
zoomspeed: 0.1
},
Medium: {
desc: 'Medium',
enabled: true,
shakiness: 6,
smoothing: 6,
zoomspeed: 0.2
},
Strong: {
desc: 'Strong',
enabled: true,
shakiness: 8,
smoothing: 10,
zoomspeed: 0.3
},
'Very Strong': {
desc: 'Very Strong',
enabled: true,
shakiness: 10,
smoothing: 16,
zoomspeed: 0.4
},
Strongest: {
desc: 'Strongest',
enabled: true,
shakiness: 10,
smoothing: 22,
zoomspeed: 0.5
}
},
denoise: {
Disabled: {
enabled: false,
desc: 'Disabled'
},
'Very Weak': {
enabled: true,
lumaSpatial: 1,
desc: 'Very Weak'
},
Weak: {
enabled: true,
lumaSpatial: 2,
desc: 'Weak'
},
Medium: {
enabled: true,
lumaSpatial: 4,
desc: 'Medium'
},
Strong: {
enabled: true,
lumaSpatial: 6,
desc: 'Strong'
},
'Very Strong': {
enabled: true,
lumaSpatial: 8,
desc: 'Very Strong'
}
}
};
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"koJFH":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "toggleGlobalSettingsEditor", ()=>toggleGlobalSettingsEditor);
parcelHelpers.export(exports, "toggleOffGlobalSettingsEditor", ()=>toggleOffGlobalSettingsEditor);
parcelHelpers.export(exports, "createGlobalSettingsEditor", ()=>createGlobalSettingsEditor);
parcelHelpers.export(exports, "bindFpsMulStepBtns", ()=>bindFpsMulStepBtns);
parcelHelpers.export(exports, "getMarkerPairMergeListDurations", ()=>getMarkerPairMergeListDurations);
var _litHtml = require("lit-html");
var _refJs = require("lit-html/directives/ref.js");
var _appState = require("../../appState");
var _charts = require("../../charts");
var _settings = require("../../components/settings");
var _encodeSettingsFieldset = require("./encode-settings-fieldset");
var _cropOverlay = require("../../crop-overlay");
var _cropUtils = require("../../crop-utils");
var _cropPreview = require("../../crop/crop-preview");
var _markerSettingsEditor = require("./marker-settings-editor");
var _saveLoad = require("../../save-load");
var _settingsEditor = require("./settings-editor");
var _tooltips = require("../../ui/tooltips");
var _util = require("../../util/util");
function toggleGlobalSettingsEditor() {
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen) (0, _markerSettingsEditor.toggleOffMarkerPairEditor)();
if ((0, _appState.appState).wasGlobalSettingsEditorOpen) toggleOffGlobalSettingsEditor();
else createGlobalSettingsEditor();
}
function toggleOffGlobalSettingsEditor() {
(0, _settingsEditor.deleteSettingsEditor)();
(0, _cropOverlay.hideCropOverlay)();
(0, _charts.hideChart)();
}
function GlobalSettingsEditorTemplate(binder) {
const { bind } = binder;
const settings = (0, _appState.appState).settings;
const cropInputValidation = `\\d+:\\d+:(\\d+|iw):(\\d+|ih)`;
const [, , w, h] = (0, _cropUtils.getCropComponents)(settings.newMarkerCrop);
const cropAspectRatio = (w / h).toFixed(13);
const numOrRange = `(\\d{1,2})|(\\d{1,2}-\\d{1,2})`;
const csvRange = `(${numOrRange})*(,(${numOrRange}))*`;
const csvRangeReq = `(${numOrRange}){1}(,(${numOrRange}))*`;
const mergeListInputValidation = `^(${csvRange})(;${csvRangeReq})*$`;
const gte100 = `([1-9]\\d{3}|[1-9]\\d{2})`;
const cropResInputValidation = `${gte100}x${gte100}`;
const { cropRes, cropResWidth, cropResHeight } = (0, _cropUtils.getDefaultCropRes)();
const cropResX2 = `${cropResWidth * 2}x${cropResHeight * 2}`;
const markerPairMergelistDurations = getMarkerPairMergeListDurations();
const encodeDisplay = (0, _settingsEditor.isExtraSettingsEditorEnabled) ? 'block' : 'none';
const rotate = settings.rotate;
const rotate0 = bind('rotate-0', 'rotate', 'string');
const rotate90Clock = bind('rotate-90-clock', 'rotate', 'string');
const rotate90CounterClock = bind('rotate-90-counterclock', 'rotate', 'string');
const mergeList = bind('merge-list-input', 'markerPairMergeList', 'string', {
afterChange: ()=>{
const span = document.getElementById('merge-list-durations');
if (span) span.textContent = getMarkerPairMergeListDurations();
}
});
return (0, _litHtml.html)`
${(0, _settings.SettingsFieldset)({
id: 'new-marker-defaults-inputs',
variant: 'global',
legend: 'New Marker Settings',
children: (0, _litHtml.html)`
${(0, _settings.NumberInputRow)({
...bind('speed-input', 'newMarkerSpeed', 'number'),
label: 'Speed',
value: settings.newMarkerSpeed,
tooltip: (0, _tooltips.Tooltips).speedTooltip,
min: 0.05,
max: 2,
step: 0.05,
placeholder: 'speed',
styleInfo: {
width: '7ch'
}
})}
${(0, _settings.TextInputRow)({
...bind('crop-input', 'newMarkerCrop', 'string'),
label: 'Crop',
value: settings.newMarkerCrop,
tooltip: (0, _tooltips.Tooltips).cropTooltip,
pattern: cropInputValidation,
styleInfo: {
width: '21ch'
},
required: true
})}
${(0, _settings.InfoRow)({
label: 'Crop Aspect Ratio',
valueId: 'crop-aspect-ratio',
value: cropAspectRatio
})}
`
})}
${(0, _settings.SettingsFieldset)({
id: 'global-marker-settings',
variant: 'global',
legend: 'Global Settings',
children: (0, _litHtml.html)`
${(0, _settings.TextInputRow)({
...bind('title-suffix-input', 'titleSuffix', 'string'),
label: 'Title Suffix',
value: settings.titleSuffix,
tooltip: (0, _tooltips.Tooltips).titleSuffixTooltip,
required: true
})}
${(0, _settings.TextInputRow)({
...bind('crop-res-input', 'cropRes', 'string', {
highlightable: false
}),
label: 'Crop Resolution',
value: settings.cropRes,
tooltip: (0, _tooltips.Tooltips).cropResolutionTooltip,
pattern: cropResInputValidation,
styleInfo: {
width: '14ch'
},
required: true,
list: 'resolutions',
listChildren: (0, _litHtml.html)`
<datalist id="resolutions" autocomplete="off">
<option value=${cropRes}></option>
<option value=${cropResX2}></option>
</datalist>
`
})}
<div
id="global-settings-rotate"
class="settings-editor-input-div"
title=${(0, _tooltips.Tooltips).rotateTooltip}
>
<span style="display:inline">Rotate: </span>
<input
id=${rotate0.id}
type="radio"
name="rotate"
value="0"
?checked=${rotate == null || rotate === '0'}
@change=${rotate0.onChange}
${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}
/>
<label for="rotate-0">0° </label>
<input
id=${rotate90Clock.id}
type="radio"
value="clock"
name="rotate"
?checked=${rotate === 'clock'}
@change=${rotate90Clock.onChange}
${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}
/>
<label for="rotate-90-clock">90° ⟳</label>
<input
id=${rotate90CounterClock.id}
type="radio"
value="cclock"
name="rotate"
?checked=${rotate === 'cclock'}
@change=${rotate90CounterClock.onChange}
${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}
/>
<label for="rotate-90-counterclock">90° ⟲</label>
</div>
<div
id="merge-list-div"
class="settings-editor-input-div"
title=${(0, _tooltips.Tooltips).mergeListTooltip}
>
<span style="display:inline">Merge List: </span>
<input
id=${mergeList.id}
pattern=${mergeListInputValidation}
placeholder="None"
style="min-width:15em"
.value=${settings.markerPairMergeList ?? ''}
@change=${mergeList.onChange}
${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}
/>
</div>
<div class="settings-editor-input-div">
<span style="display:inline">Merge Durations: </span>
<span id="merge-list-durations" style="display:inline"
>${markerPairMergelistDurations}</span
>
</div>
`
})}
${(0, _encodeSettingsFieldset.EncodeSettingsFieldset)({
id: 'global-encode-settings',
variant: 'global',
display: encodeDisplay,
source: settings,
bind: bind
})}
`;
}
function createGlobalSettingsEditor() {
(0, _cropOverlay.createCropOverlay)((0, _appState.appState).settings.newMarkerCrop);
const globalSettingsEditorDiv = document.createElement('div');
globalSettingsEditorDiv.setAttribute('id', 'settings-editor-div');
const binder = (0, _settingsEditor.createBindings)((0, _appState.appState).settings);
(0, _litHtml.render)(GlobalSettingsEditorTemplate(binder), globalSettingsEditorDiv);
(0, _saveLoad.injectYtcWidget)(globalSettingsEditorDiv);
bindFpsMulStepBtns();
(0, _settingsEditor.setCropInput)(document.getElementById('crop-input'));
(0, _settingsEditor.setCropAspectRatioSpan)(document.getElementById('crop-aspect-ratio'));
(0, _appState.appState).wasGlobalSettingsEditorOpen = true;
(0, _appState.appState).isSettingsEditorOpen = true;
(0, _settingsEditor.addCropInputHotkeys)();
(0, _settingsEditor.highlightModifiedSettings)(binder.all(), (0, _appState.appState).settings);
(0, _cropOverlay.showCropOverlay)();
(0, _cropPreview.triggerCropPreviewRedraw)();
}
function bindFpsMulStepBtns() {
document.querySelectorAll('.fps-mul-step-btn').forEach((btn)=>{
btn.addEventListener('click', ()=>{
const stepper = btn.closest('.fps-mul-stepper');
(0, _util.assertDefined)(stepper);
const input = stepper.querySelector('input[type="number"]');
(0, _util.assertDefined)(input);
const cur = parseFloat(input.value) || 0;
const stepDir = parseInt(btn.dataset.step ?? '', 10);
const min = parseFloat(input.min) || 0;
const max = parseFloat(input.max) || Infinity;
const next = stepDir > 0 ? Math.min(max, Math.floor(cur) + stepDir) : Math.max(min, Math.ceil(cur) + stepDir);
input.value = String(next);
input.dispatchEvent(new Event('change', {
bubbles: true
}));
});
});
}
function getMarkerPairMergeListDurations(markerPairMergeList = (0, _appState.appState).settings.markerPairMergeList) {
const durations = [];
for (const merge of markerPairMergeList.split(';')){
let duration = 0;
for (const mergeRange of merge.split(','))if (mergeRange.includes('-')) {
let [mergeRangeStart, mergeRangeEnd] = mergeRange.split('-').map((str)=>parseInt(str, 10) - 1);
if (mergeRangeStart > mergeRangeEnd) [mergeRangeStart, mergeRangeEnd] = [
mergeRangeEnd,
mergeRangeStart
];
for(let idx = mergeRangeStart; idx <= mergeRangeEnd; idx++)if (!isNaN(idx) && idx >= 0 && idx < (0, _appState.appState).markerPairs.length) {
const marker = (0, _appState.appState).markerPairs[idx];
duration += (marker.end - marker.start) / marker.speed;
}
} else {
const idx = parseInt(mergeRange, 10) - 1;
if (!isNaN(idx) && idx >= 0 && idx < (0, _appState.appState).markerPairs.length) {
const marker = (0, _appState.appState).markerPairs[idx];
duration += (marker.end - marker.start) / marker.speed;
}
}
durations.push(duration);
}
const markerPairMergelistDurations = durations.map((0, _util.toHHMMSSTrimmed)).join(' ; ');
return markerPairMergelistDurations;
}
},{"lit-html":"9fQBw","lit-html/directives/ref.js":"9tuBT","../../appState":"g0AlP","../../charts":"hBxwj","../../components/settings":"jkedK","./encode-settings-fieldset":"44SfE","../../crop-overlay":"6s727","../../crop-utils":"k2gwb","../../crop/crop-preview":"9T0zg","./marker-settings-editor":"ZduPU","../../save-load":"3FwNw","./settings-editor":"jDViX","../../ui/tooltips":"a02E8","../../util/util":"99arg","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"jkedK":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "SettingsRow", ()=>(0, _settingsRow.SettingsRow));
parcelHelpers.export(exports, "SettingsFieldset", ()=>(0, _settingsFieldset.SettingsFieldset));
parcelHelpers.export(exports, "NumberInputRow", ()=>(0, _numberInputRow.NumberInputRow));
parcelHelpers.export(exports, "TextInputRow", ()=>(0, _textInputRow.TextInputRow));
parcelHelpers.export(exports, "TernarySelect", ()=>(0, _ternarySelect.TernarySelect));
parcelHelpers.export(exports, "PresetSelect", ()=>(0, _presetSelect.PresetSelect));
parcelHelpers.export(exports, "LoopSelect", ()=>(0, _loopSelect.LoopSelect));
parcelHelpers.export(exports, "FpsMulStepper", ()=>(0, _fpsMulStepper.FpsMulStepper));
parcelHelpers.export(exports, "InfoRow", ()=>(0, _infoRow.InfoRow));
var _settingsRow = require("./settings-row");
var _settingsFieldset = require("./settings-fieldset");
var _numberInputRow = require("./number-input-row");
var _textInputRow = require("./text-input-row");
var _ternarySelect = require("./ternary-select");
var _presetSelect = require("./preset-select");
var _loopSelect = require("./loop-select");
var _fpsMulStepper = require("./fps-mul-stepper");
var _infoRow = require("./info-row");
},{"./settings-row":"6X1vc","./settings-fieldset":"fuwLs","./number-input-row":"h3bVe","./text-input-row":"agYM3","./ternary-select":"gpJUx","./preset-select":"f225m","./loop-select":"2qQpN","./fps-mul-stepper":"eM6k9","./info-row":"crG6i","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"6X1vc":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "SettingsRow", ()=>SettingsRow);
var _litHtml = require("lit-html");
var _styleMapJs = require("lit-html/directives/style-map.js");
function SettingsRow(p) {
const cls = p.extraClass ? `settings-editor-input-div ${p.extraClass}` : 'settings-editor-input-div';
return (0, _litHtml.html)`
<div
class=${cls}
title=${p.tooltip ?? (0, _litHtml.nothing)}
style=${p.display ? (0, _styleMapJs.styleMap)({
display: p.display
}) : (0, _litHtml.nothing)}
>
${p.children}
</div>
`;
}
},{"lit-html":"9fQBw","lit-html/directives/style-map.js":"9Jpf5","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"9Jpf5":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "styleMap", ()=>o);
var _litHtmlJs = require("../lit-html.js");
var _directiveJs = require("../directive.js");
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/ const n = "important", i = " !" + n, o = (0, _directiveJs.directive)(class extends (0, _directiveJs.Directive) {
constructor(t){
if (super(t), t.type !== (0, _directiveJs.PartType).ATTRIBUTE || "style" !== t.name || t.strings?.length > 2) throw Error("The `styleMap` directive must be used in the `style` attribute and must be the only part in the attribute.");
}
render(t) {
return Object.keys(t).reduce((e, r)=>{
const s = t[r];
return null == s ? e : e + `${r = r.includes("-") ? r : r.replace(/(?:^(webkit|moz|ms|o)|)(?=[A-Z])/g, "-$&").toLowerCase()}:${s};`;
}, "");
}
update(e, [r]) {
const { style: s } = e.element;
if (void 0 === this.ft) return this.ft = new Set(Object.keys(r)), this.render(r);
for (const t of this.ft)null == r[t] && (this.ft.delete(t), t.includes("-") ? s.removeProperty(t) : s[t] = null);
for(const t in r){
const e = r[t];
if (null != e) {
this.ft.add(t);
const r = "string" == typeof e && e.endsWith(i);
t.includes("-") || r ? s.setProperty(t, r ? e.slice(0, -11) : e, r ? n : "") : s[t] = e;
}
}
return 0, _litHtmlJs.noChange;
}
});
},{"../lit-html.js":"9fQBw","../directive.js":"lb2wk","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"fuwLs":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "SettingsFieldset", ()=>SettingsFieldset);
var _litHtml = require("lit-html");
var _styleMapJs = require("lit-html/directives/style-map.js");
function SettingsFieldset(p) {
const prefix = p.variant === 'global' ? 'global-settings-editor' : 'marker-pair-settings-editor';
const panelClass = `settings-editor-panel ${prefix} ${prefix}-highlighted-div`;
const legendClass = `${prefix}-highlighted-label`;
return (0, _litHtml.html)`
<fieldset
id=${p.id ?? (0, _litHtml.nothing)}
class=${panelClass}
style=${p.display ? (0, _styleMapJs.styleMap)({
display: p.display
}) : (0, _litHtml.nothing)}
>
<legend class=${legendClass}>${p.legend}</legend>
${p.children}
</fieldset>
`;
}
},{"lit-html":"9fQBw","lit-html/directives/style-map.js":"9Jpf5","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"h3bVe":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "NumberInputRow", ()=>NumberInputRow);
var _litHtml = require("lit-html");
var _refJs = require("lit-html/directives/ref.js");
var _styleMapJs = require("lit-html/directives/style-map.js");
var _settingsEditor = require("../../features/settings/settings-editor");
function NumberInputRow(p) {
const wrapperClass = p.compact ? (0, _litHtml.nothing) : 'settings-editor-input-div';
return (0, _litHtml.html)`
<div class=${wrapperClass} title=${p.tooltip ?? (0, _litHtml.nothing)}>
<span id=${p.labelId ?? (0, _litHtml.nothing)}>${p.label}</span>
<input
id=${p.id}
type="number"
min=${p.min ?? (0, _litHtml.nothing)}
max=${p.max ?? (0, _litHtml.nothing)}
step=${p.step ?? (0, _litHtml.nothing)}
placeholder=${p.placeholder ?? (0, _litHtml.nothing)}
style=${p.styleInfo ? (0, _styleMapJs.styleMap)(p.styleInfo) : (0, _litHtml.nothing)}
?required=${p.required ?? false}
.value=${p.value ?? ''}
@change=${p.onChange ?? (0, _litHtml.nothing)}
${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}
/>
</div>
`;
}
},{"lit-html":"9fQBw","lit-html/directives/ref.js":"9tuBT","lit-html/directives/style-map.js":"9Jpf5","../../features/settings/settings-editor":"jDViX","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"agYM3":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "TextInputRow", ()=>TextInputRow);
var _litHtml = require("lit-html");
var _refJs = require("lit-html/directives/ref.js");
var _styleMapJs = require("lit-html/directives/style-map.js");
var _settingsEditor = require("../../features/settings/settings-editor");
function TextInputRow(p) {
return (0, _litHtml.html)`
<div class="settings-editor-input-div" title=${p.tooltip ?? (0, _litHtml.nothing)}>
<span id=${p.labelId ?? (0, _litHtml.nothing)}>${p.label}</span>
<input
id=${p.id}
pattern=${p.pattern ?? (0, _litHtml.nothing)}
placeholder=${p.placeholder ?? (0, _litHtml.nothing)}
style=${p.styleInfo ? (0, _styleMapJs.styleMap)(p.styleInfo) : (0, _litHtml.nothing)}
list=${p.list ?? (0, _litHtml.nothing)}
?required=${p.required ?? false}
.value=${p.value ?? ''}
@change=${p.onChange ?? (0, _litHtml.nothing)}
${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}
/>
${p.listChildren ?? (0, _litHtml.nothing)}
</div>
`;
}
},{"lit-html":"9fQBw","lit-html/directives/ref.js":"9tuBT","lit-html/directives/style-map.js":"9Jpf5","../../features/settings/settings-editor":"jDViX","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"gpJUx":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "TernarySelect", ()=>TernarySelect);
var _litHtml = require("lit-html");
var _refJs = require("lit-html/directives/ref.js");
var _settingsEditor = require("../../features/settings/settings-editor");
function TernarySelect(p) {
const v = p.value;
const wrapperClass = p.compact ? (0, _litHtml.nothing) : 'settings-editor-input-div';
return (0, _litHtml.html)`
<div class=${wrapperClass} title=${p.tooltip ?? (0, _litHtml.nothing)}>
<span>${p.label}</span>
<select id=${p.id} @change=${p.onChange ?? (0, _litHtml.nothing)} ${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}>
<option value="Default" ?selected=${v == null}>${p.defaultOptionLabel}</option>
<option ?selected=${v === false}>Disabled</option>
<option ?selected=${v === true}>Enabled</option>
</select>
</div>
`;
}
},{"lit-html":"9fQBw","lit-html/directives/ref.js":"9tuBT","../../features/settings/settings-editor":"jDViX","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"f225m":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "PresetSelect", ()=>PresetSelect);
var _litHtml = require("lit-html");
var _refJs = require("lit-html/directives/ref.js");
var _settingsEditor = require("../../features/settings/settings-editor");
function PresetSelect(p) {
const v = p.value;
const wrapperClass = p.compact ? (0, _litHtml.nothing) : 'settings-editor-input-div';
return (0, _litHtml.html)`
<div class=${wrapperClass} title=${p.tooltip ?? (0, _litHtml.nothing)}>
<span>${p.label}</span>
<select id=${p.id} @change=${p.onChange ?? (0, _litHtml.nothing)} ${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}>
<option value="Inherit" ?selected=${v == null}>${p.defaultOptionLabel}</option>
${p.includeDisabledOption ? (0, _litHtml.html)`<option value="Disabled" ?selected=${v === 'Disabled'}>Disabled</option>` : (0, _litHtml.nothing)}
${p.options.map((opt)=>(0, _litHtml.html)`<option ?selected=${v === opt}>${opt}</option>`)}
</select>
</div>
`;
}
},{"lit-html":"9fQBw","lit-html/directives/ref.js":"9tuBT","../../features/settings/settings-editor":"jDViX","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"2qQpN":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "LoopSelect", ()=>LoopSelect);
var _litHtml = require("lit-html");
var _refJs = require("lit-html/directives/ref.js");
var _settingsEditor = require("../../features/settings/settings-editor");
function LoopSelect(p) {
const v = p.value;
return (0, _litHtml.html)`
<div title=${p.tooltip ?? (0, _litHtml.nothing)}>
<span>${p.label}</span>
<select id=${p.id} @change=${p.onChange ?? (0, _litHtml.nothing)} ${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}>
<option value="Default" ?selected=${v == null}>${p.defaultOptionLabel}</option>
<option ?selected=${v === 'none'}>none</option>
<option ?selected=${v === 'fwrev'}>fwrev</option>
<option ?selected=${v === 'fade'}>fade</option>
</select>
</div>
`;
}
},{"lit-html":"9fQBw","lit-html/directives/ref.js":"9tuBT","../../features/settings/settings-editor":"jDViX","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"eM6k9":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "FpsMulStepper", ()=>FpsMulStepper);
var _litHtml = require("lit-html");
var _refJs = require("lit-html/directives/ref.js");
var _settingsEditor = require("../../features/settings/settings-editor");
function FpsMulStepper(p) {
return (0, _litHtml.html)`
<div class="settings-editor-input-div">
<div title=${p.tooltip ?? (0, _litHtml.nothing)}>
<span id=${p.labelId ?? (0, _litHtml.nothing)}>${p.label}</span>
<div class="fps-mul-stepper">
<button class="fps-mul-step-btn" data-step="-1">−1</button>
<input
id=${p.id}
class="fps-mul-input"
type="number"
min="0"
max="5"
step="0.05"
placeholder="0"
.value=${p.value ?? ''}
@change=${p.onChange ?? (0, _litHtml.nothing)}
${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}
/>
<button class="fps-mul-step-btn" data-step="+1">+1</button>
<span id=${p.suffixSpanId ?? (0, _litHtml.nothing)} class="fps-mul-suffix">
${p.suffixText ?? ''}
</span>
</div>
</div>
</div>
`;
}
},{"lit-html":"9fQBw","lit-html/directives/ref.js":"9tuBT","../../features/settings/settings-editor":"jDViX","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"crG6i":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "InfoRow", ()=>InfoRow);
var _litHtml = require("lit-html");
function InfoRow(p) {
return (0, _litHtml.html)`
<div class="settings-editor-input-div settings-info-display" title=${p.tooltip ?? (0, _litHtml.nothing)}>
<span>${p.label}</span>
${p.breakBeforeValue ? (0, _litHtml.html)`<br />` : (0, _litHtml.nothing)}
<span id=${p.valueId ?? (0, _litHtml.nothing)}>${p.value}</span>
</div>
`;
}
},{"lit-html":"9fQBw","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"44SfE":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "EncodeSettingsFieldset", ()=>EncodeSettingsFieldset);
var _litHtml = require("lit-html");
var _refJs = require("lit-html/directives/ref.js");
var _settings = require("../../components/settings");
var _tooltips = require("../../ui/tooltips");
var _util = require("../../util/util");
var _settingsEditor = require("./settings-editor");
function renderFadeDurationInput(bound, value, placeholder) {
return (0, _litHtml.html)`
<div title=${(0, _tooltips.Tooltips).fadeDurationTooltip}>
<span>Fade Duration</span>
<input
id=${bound.id}
type="number"
min="0.1"
step="0.1"
placeholder=${placeholder}
style="width:7em"
.value=${String(value ?? '')}
@change=${bound.onChange}
${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}
/>
</div>
`;
}
function renderZoomPanRow(zoomPan) {
const bound = zoomPan.bind('enable-zoom-pan-input', 'enableZoomPan', 'bool');
return (0, _litHtml.html)`
<div class="settings-editor-input-div" title=${(0, _tooltips.Tooltips).enableZoomPanTooltip}>
<span>ZoomPan</span>
<select id=${bound.id} @change=${bound.onChange} ${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}>
<option ?selected=${!zoomPan.enabled}>Disabled</option>
<option ?selected=${zoomPan.enabled}>Enabled</option>
</select>
</div>
`;
}
const denoiseOptions = [
'Very Weak',
'Weak',
'Medium',
'Strong',
'Very Strong'
];
const vidstabOptions = [
'Very Weak',
'Weak',
'Medium',
'Strong',
'Very Strong',
'Strongest'
];
function numericPlaceholder(inherited, fallback) {
return inherited != null ? String(inherited) : fallback;
}
function presetLabel(value, fallback) {
return value ? `(${value.desc})` : fallback;
}
function loopLabel(value, fallback) {
return value != null ? `(${value})` : fallback;
}
function EncodeSettingsFieldset(p) {
const { source, inheritFrom, bind } = p;
const isMarker = inheritFrom != null;
const audioDefault = inheritFrom ? (0, _util.ternaryToString)(inheritFrom.audio) ?? '(Disabled)' : '(Disabled)';
const twoPassDefault = inheritFrom ? (0, _util.ternaryToString)(inheritFrom.twoPass) ?? '(Disabled)' : '(Disabled)';
const enableHDRDefault = inheritFrom ? (0, _util.ternaryToString)(inheritFrom.enableHDR) ?? '(Disabled)' : '(Disabled)';
const dynamicZoomDefault = inheritFrom ? (0, _util.ternaryToString)(inheritFrom.videoStabilizationDynamicZoom) ?? '(Disabled)' : '(Disabled)';
const denoiseDefault = inheritFrom ? presetLabel(inheritFrom.denoise, '(Disabled)') : '(Disabled)';
const vidstabDefault = inheritFrom ? presetLabel(inheritFrom.videoStabilization, '(Disabled)') : '(Disabled)';
const loopDefault = inheritFrom ? loopLabel(inheritFrom.loop, '(none)') : '(none)';
const encodeSpeedPlaceholder = numericPlaceholder(inheritFrom?.encodeSpeed, 'Auto');
const crfPlaceholder = numericPlaceholder(inheritFrom?.crf, 'Auto');
const targetBitratePlaceholder = numericPlaceholder(inheritFrom?.targetMaxBitrate, 'Auto');
const gammaPlaceholder = numericPlaceholder(inheritFrom?.gamma, '1');
const fadeDurationPlaceholder = numericPlaceholder(inheritFrom?.fadeDuration, '0.7');
const denoiseDesc = source.denoise?.desc ?? null;
const vidstabDesc = source.videoStabilization?.desc ?? null;
return (0, _settings.SettingsFieldset)({
id: p.id,
variant: p.variant,
legend: 'Encode Settings',
display: p.display,
children: (0, _litHtml.html)`
${(0, _settings.TernarySelect)({
...bind('audio-input', 'audio', 'ternary'),
label: 'Audio',
tooltip: (0, _tooltips.Tooltips).audioTooltip,
value: source.audio,
defaultOptionLabel: audioDefault
})}
${(0, _settings.NumberInputRow)({
...bind('encode-speed-input', 'encodeSpeed', 'number'),
label: 'Encode Speed (0-5)',
value: source.encodeSpeed ?? '',
tooltip: (0, _tooltips.Tooltips).encodeSpeedTooltip,
min: 0,
max: 5,
step: 1,
placeholder: encodeSpeedPlaceholder,
styleInfo: {
minWidth: '4em'
}
})}
${(0, _settings.NumberInputRow)({
...bind('crf-input', 'crf', 'number'),
label: 'CRF (0-63)',
value: source.crf ?? '',
tooltip: (0, _tooltips.Tooltips).CRFTooltip,
min: 0,
max: 63,
step: 1,
placeholder: crfPlaceholder,
styleInfo: {
minWidth: '4em'
}
})}
${(0, _settings.NumberInputRow)({
...bind('target-max-bitrate-input', 'targetMaxBitrate', 'number'),
label: isMarker ? 'Bitrate (kb/s)' : 'Target Bitrate (kb/s)',
value: source.targetMaxBitrate ?? '',
tooltip: (0, _tooltips.Tooltips).targetBitrateTooltip,
min: 0,
max: isMarker ? '10e5' : '1e5',
step: 100,
placeholder: targetBitratePlaceholder,
styleInfo: {
minWidth: '4em'
}
})}
${(0, _settings.NumberInputRow)({
...bind('gamma-input', 'gamma', 'number'),
label: 'Gamma (0-4)',
value: source.gamma ?? '',
tooltip: (0, _tooltips.Tooltips).gammaTooltip,
min: 0.01,
max: 4.0,
step: 0.01,
placeholder: gammaPlaceholder,
styleInfo: {
minWidth: '4em'
}
})}
${(0, _settings.TernarySelect)({
...bind('two-pass-input', 'twoPass', 'ternary'),
label: 'Two-Pass',
tooltip: (0, _tooltips.Tooltips).twoPassTooltip,
value: source.twoPass,
defaultOptionLabel: twoPassDefault
})}
${(0, _settings.TernarySelect)({
...bind('enable-hdr-input', 'enableHDR', 'ternary'),
label: 'Enable HDR',
tooltip: (0, _tooltips.Tooltips).hdrTooltip,
value: source.enableHDR,
defaultOptionLabel: enableHDRDefault
})}
${(0, _settings.PresetSelect)({
...bind('denoise-input', 'denoise', 'preset'),
label: 'Denoise',
tooltip: (0, _tooltips.Tooltips).denoiseTooltip,
value: denoiseDesc,
defaultOptionLabel: denoiseDefault,
options: denoiseOptions,
includeDisabledOption: isMarker
})}
${(0, _settings.FpsMulStepper)({
...bind('minterp-fps-multiplier-input', 'minterpFpsMultiplier', 'number', {
afterChange: p.fpsMulSuffix?.onChange
}),
label: 'Src FPS Multiplier',
labelId: p.fpsMulSuffix?.labelId,
value: source.minterpFpsMultiplier,
tooltip: (0, _tooltips.Tooltips).minterpFpsMultiplierTooltip,
suffixSpanId: p.fpsMulSuffix?.spanId,
suffixText: p.fpsMulSuffix?.text
})}
<div class="settings-editor-input-div multi-input-div" title=${(0, _tooltips.Tooltips).vidstabTooltip}>
${(0, _settings.PresetSelect)({
...bind('video-stabilization-input', 'videoStabilization', 'preset'),
label: 'Stabilization',
value: vidstabDesc,
defaultOptionLabel: vidstabDefault,
options: vidstabOptions,
includeDisabledOption: isMarker,
compact: true
})}
${(0, _settings.TernarySelect)({
...bind('video-stabilization-dynamic-zoom-input', 'videoStabilizationDynamicZoom', 'ternary'),
label: 'Dynamic Zoom',
tooltip: (0, _tooltips.Tooltips).dynamicZoomTooltip,
value: source.videoStabilizationDynamicZoom,
defaultOptionLabel: dynamicZoomDefault,
compact: true
})}
</div>
<div class="settings-editor-input-div multi-input-div" title=${(0, _tooltips.Tooltips).loopTooltip}>
${(0, _settings.LoopSelect)({
...bind('loop-input', 'loop', 'inheritableString'),
label: 'Loop',
value: source.loop,
defaultOptionLabel: loopDefault
})}
${renderFadeDurationInput(bind('fade-duration-input', 'fadeDuration', 'number'), source.fadeDuration, fadeDurationPlaceholder)}
</div>
${p.zoomPan ? renderZoomPanRow(p.zoomPan) : (0, _litHtml.nothing)}
`
});
}
},{"lit-html":"9fQBw","lit-html/directives/ref.js":"9tuBT","../../components/settings":"jkedK","../../ui/tooltips":"a02E8","../../util/util":"99arg","./settings-editor":"jDViX","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"a02E8":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "Tooltips", ()=>Tooltips);
var _commonTags = require("common-tags");
(function(Tooltips) {
Tooltips.markerPairNumberTooltip = (0, _commonTags.stripIndent)`
Enter a new marker pair number here to reorder marker pairs.
Does not automatically update merge list.
`;
Tooltips.speedTooltip = (0, _commonTags.stripIndent)`
Toggle speed previewing with C.
When audio is enabled, speeds below 0.5 are not yet supported.
YouTube can only preview speeds that are multiples of 0.05 (e.g., 0.75 or 0.45).
YouTube does not support previewing speeds below 0.25.
`;
Tooltips.timeDurationTooltip = (0, _commonTags.stripIndent)`
When speed is constant (static) the duration is formatted as <in-duration>/<speed> = <out-duration>.
When speed is variable (dynamic) the duration is formatted as <in-duration> (<out-duration>).
Durations are formatted as HH:MM:SS.MS omitting insignificant 0s on the left.
`;
Tooltips.cropTooltip = (0, _commonTags.stripIndent)`
Crop values are given as x-offset:y-offset:width:height. Each value is a positive integer in pixels.
Width and height can also be 'iw' and 'ih' respectively for input width and input height.
Use Ctrl+Click+Drag on the crop preview to adjust the crop with the mouse instead.
Increment/decrement values in the crop input with the up/down keys by ±10.
The cursor position within the crop string determines the crop component to change.
Use modifier keys to alter the change amount: Alt: ±1, Shift: ±50, Alt+Shift: ±100.
`;
Tooltips.titlePrefixTooltip = (0, _commonTags.stripIndent)`
Specify a title prefix to be prepended to the tile suffix of the file name generated by this marker pair.
`;
Tooltips.titleSuffixTooltip = (0, _commonTags.stripIndent)`
Specify a title suffix to be appended to the title prefixes specified for each marker pair.
The title suffix is followed by the marker pair number in the final file name for each marker pair.
The title suffix is also the the file name stem of the markers data json file saved with S.
The markers data file name is used as the title suffix when running the clipper script.
Thus it can be renamed to change the title suffix without editing the json data.
`;
Tooltips.cropResolutionTooltip = (0, _commonTags.stripIndent)`
The crop resolution specifies the scaling of crop strings, which should match the input video's resolution.
However, lower crop resolutions can be easier to work with.
The clipper script will automatically scale the crop resolution if a mismatch is detected.
`;
Tooltips.rotateTooltip = (0, _commonTags.stripIndent)`
Correct video rotation by rotating the input video clockwise or counterclockwise by 90 degrees.
Note that the YouTube video rotate preview using the R/Alt+R shortcuts does NOT affect the output video.
`;
Tooltips.mergeListTooltip = (0, _commonTags.stripIndent)`
Specify which marker pairs if any you would like to merge/concatenate.
Each merge is a comma separated list of marker pair numbers or ranges (e.g., '1-3,5,9' = '1,2,3,5,9').
Multiple merges are separated with semicolons (e.g., '1-3,5,9;6-2,8' will create two merged webms).
Merge occurs successfully only after successful generation of each required generated webm.
Merge does not require reencoding and simply orders each webm into one container.
`;
Tooltips.audioTooltip = (0, _commonTags.stripIndent)`
Enable audio.
Not yet compatible with special loop behaviors or time-variable speed.
`;
Tooltips.encodeSpeedTooltip = (0, _commonTags.stripIndent)`
Higher values will speed up encoding at the cost of some quality.
Very high values will also reduce bitrate control effectiveness, which may increase file sizes.
`;
Tooltips.CRFTooltip = (0, _commonTags.stripIndent)`
Constant Rate Factor or CRF allows the video bitrate to vary while maintaining roughly constant quality.
Lower CRF values result in higher quality but larger file sizes.
A CRF around 25 (~30 for 4k) usually results in file size compression that does not visibly reduce quality.
When the target bitrate is set to 0 (unlimited), the bitrate is unconstrained and operates in constant quality mode .
When the target bitrate is set to auto or a positive value in kbps, the script operates in constrained quality mode.
Constrained quality mode keeps file sizes reasonable even when low CRF values are specified.
`;
Tooltips.targetBitrateTooltip = (0, _commonTags.stripIndent)`
Specify the target bitrate in kbps of the output video.
The bitrate determines how much data is used to encode each second of video and thus the final file size.
If the bitrate is too low then the compression of the video will visibly reduce quality.
When the target bitrate is set to 0 for unlimited, the script operates in constant quality mode.
When the target bitrate is set to auto or a positive value in kbps, the script operates in constrained quality mode.
Constrained quality mode keeps file sizes reasonable even when low CRF values are specified.
`;
Tooltips.twoPassTooltip = (0, _commonTags.stripIndent)`
Encode in two passes for improved bitrate control which can reduce filesizes.
Results in better quality, with diminishing returns for high bitrate video.
Significantly reduces encode speed.
`;
Tooltips.hdrTooltip = (0, _commonTags.stripIndent)`
Enabling HDR (high dynamic range) may improve the output video's image vibrancy and colors at the expense of file size and playback compatibility.
`;
Tooltips.gammaTooltip = (0, _commonTags.stripIndent)`
A gamma function is used to map input luminance values to output luminance values or vice versa.
Note that the gamma preview is not accurate. Use the offline previewer for more accurate gamma preview.
The gamma value is an exponent applied to the input luminance values.
A gamma value of 1 is neutral and does not modify the video.
A gamma value greater than 1 can be used to darken the video and enhance highlight detail.
A gamma value less than 1 can be used to lighten the video and enhance shadow detail.
Even small changes in gamma can have large effects (smallest possible change is 0.01).
Use the gamma preview toggle (Alt+C) to set the gamma to taste.
`;
Tooltips.denoiseTooltip = (0, _commonTags.stripIndent)`
Reduce noise, static, and blockiness at the cost of some encoding speed.
Improves compression efficiency and thus reduces file sizes.
Higher strength presets may result in oversmoothing of details.
`;
Tooltips.minterpFpsMultiplierTooltip = (0, _commonTags.stripIndent)`
Input a source fps multiplier (≥ 1) to enable motion interpolation via video2x RIFE.
A value of 0 (default) disables motion interpolation.
A value of 1 targets the original source video fps, compensating for slowdown.
Higher values (e.g. 2) target N x source fps for extra-smooth slow motion.
Use −1/+1 buttons for integer steps, or type/arrow for 0.05 precision.
Motion interpolation can introduce artifacting (visual glitches).
Artifacting increases with the speed and complexity of the video.
Setting a source fps multiplier that is an integer multiple of the static speed (e.g. 1.2 = 2 * 0.6)
will preserve original frames when doing motion interpolation.
This is indicated with the effective clip fps multiplier in parentheses after the input label.
`;
Tooltips.vidstabTooltip = (0, _commonTags.stripIndent)`
Video stabilization tries to smooth out the motion in the video and reduce shaking.
Usually requires cropping and zooming the video.
Higher strength presets result in more cropping and zooming.
Low contrast video or video with flashing lights may give poor results.
Video stabilization may cause static elements within the cropped region to shake.
`;
Tooltips.dynamicZoomTooltip = (0, _commonTags.stripIndent)`
Allow cropping and zooming of video to vary with the need for stabilization over time.
`;
Tooltips.speedMapTooltip = (0, _commonTags.stripIndent)`
Time-variable speed maps are enabled by default, but can be force enabled/disabled with this setting.
A speed map may be specified using the speed chart (toggled with D).
`;
Tooltips.enableZoomPanTooltip = (0, _commonTags.stripIndent)`
Enable or disable dynamic crop zoompan mode.
When disabled, dynamic crop is in pan-only mode.
In pan-only mode, crop points all have the same size.
In zoompan mode, crop points can have different sizes but have the same aspect ratio.
Switching from zoompan to pan-only mode requires removing zooms and a prompt may appear.
`;
function zoomPanToPanOnlyTooltip(sw, sh, lw, lh, aw, ah) {
return (0, _commonTags.stripIndent)`
Switching from zoompan to pan-only mode requires removing zooms.
That is, all crop points must be made to have the same size.
This can be the (s)mallest, (l)argest or (a)verage size in the crop map.
Enter 's' for ${sw}x${sh}, 'l' for ${lw}x${lh}, or 'a' for ${aw}x${ah}.
*Note that choosing (l)argest or (a)verage will shift crops as necessary.
`;
}
Tooltips.zoomPanToPanOnlyTooltip = zoomPanToPanOnlyTooltip;
Tooltips.loopTooltip = (0, _commonTags.stripIndent)`
Enable one of the special loop behaviors.
fwrev loops will play the video normally once, then immediately play it in reverse.
fade loops will crossfade the end of the video into the start of the video.
fade loops can make short clips easier on the eyes and reduce the perceived jerkiness when the video repeats.
`;
Tooltips.fadeDurationTooltip = (0, _commonTags.stripIndent)`
The duration to cut from the beginning and end of the output video to produce the crossfade for fade loops.
Will be clamped to a minimum of 0.1 seconds and a maximum of 40% of the output clip duration.
Only applicable when loop is set to fade.
`;
})(Tooltips || (Tooltips = {}));
var Tooltips;
},{"common-tags":"4Q3cx","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"ZduPU":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "toggleMarkerPairEditorHandler", ()=>toggleMarkerPairEditorHandler);
parcelHelpers.export(exports, "markerPairNumberInput", ()=>markerPairNumberInput);
parcelHelpers.export(exports, "createMarkerPairEditor", ()=>createMarkerPairEditor);
parcelHelpers.export(exports, "markerPairNumberInputHandler", ()=>markerPairNumberInputHandler);
parcelHelpers.export(exports, "toggleMarkerPairEditor", ()=>toggleMarkerPairEditor);
parcelHelpers.export(exports, "autoHideUnselectedMarkerPairsStyle", ()=>autoHideUnselectedMarkerPairsStyle);
parcelHelpers.export(exports, "toggleAutoHideUnselectedMarkerPairs", ()=>toggleAutoHideUnselectedMarkerPairs);
// Assumes targetMarker is an end marker
parcelHelpers.export(exports, "toggleOnMarkerPairEditor", ()=>toggleOnMarkerPairEditor);
parcelHelpers.export(exports, "toggleOffMarkerPairEditor", ()=>toggleOffMarkerPairEditor);
var _litHtml = require("lit-html");
var _refJs = require("lit-html/directives/ref.js");
var _appState = require("../../appState");
var _charts = require("../../charts");
var _settings = require("../../components/settings");
var _encodeSettingsFieldset = require("./encode-settings-fieldset");
var _cropOverlay = require("../../crop-overlay");
var _cropUtils = require("../../crop-utils");
var _cropPreview = require("../../crop/crop-preview");
var _globalSettingsEditor = require("./global-settings-editor");
var _markers = require("../../markers");
var _saveLoad = require("../../save-load");
var _settingsEditor = require("./settings-editor");
var _speed = require("../../speed");
var _cropChartSpec = require("../../ui/chart/cropchart/cropChartSpec");
var _css = require("../../ui/css/css");
var _tooltips = require("../../ui/tooltips");
var _util = require("../../util/util");
function toggleMarkerPairEditorHandler(e, targetMarker) {
targetMarker = targetMarker ?? e.target;
if (targetMarker && e.shiftKey) toggleMarkerPairEditor(targetMarker);
}
let markerPairNumberInput;
function MarkerPairEditorTemplate(markerPair, pairBinder, overrideBinder) {
const { bind: bindPair } = pairBinder;
const { bind: bindOverride } = overrideBinder;
const markerPairIndex = (0, _appState.appState).markerPairs.indexOf(markerPair);
const endTime = (0, _util.toHHMMSSTrimmed)(markerPair.end);
const speed = markerPair.speed;
const duration = (0, _util.toHHMMSSTrimmed)(markerPair.end - markerPair.start);
const speedAdjustedDuration = (0, _util.toHHMMSSTrimmed)((markerPair.end - markerPair.start) / speed);
const crop = markerPair.crop;
const cropInputValidation = `\\d+:\\d+:(\\d+|iw):(\\d+|ih)`;
const [, , w, h] = (0, _cropUtils.getCropComponents)(crop);
const cropAspectRatio = (w / h).toFixed(13);
const overrides = markerPair.overrides;
const effectiveMinterpMul = overrides.minterpFpsMultiplier ?? (0, _appState.appState).settings.minterpFpsMultiplier ?? 0;
const minterpFpsMulLabel = (0, _speed.getMinterpFpsMulSuffix)(effectiveMinterpMul, speed);
const overridesDisplay = (0, _settingsEditor.isExtraSettingsEditorEnabled) ? 'block' : 'none';
const legend = (0, _litHtml.html)`
Marker Pair
<input
id="marker-pair-number-input"
title=${(0, _tooltips.Tooltips).markerPairNumberTooltip}
type="number"
step="1"
min="1"
max=${String((0, _appState.appState).markerPairs.length)}
style="width:3em"
required
.value=${String(markerPairIndex + 1)}
@change=${markerPairNumberInputHandler}
${(0, _refJs.ref)((0, _settingsEditor.gateHotkeys))}
/>
/
<span id="marker-pair-count-label">${(0, _appState.appState).markerPairs.length}</span>
Settings
`;
return (0, _litHtml.html)`
${(0, _settings.SettingsFieldset)({
variant: 'marker',
legend,
children: (0, _litHtml.html)`
${(0, _settings.NumberInputRow)({
...bindPair('speed-input', 'speed', 'number', {
afterChange: ()=>(0, _speed.updateMinterpFpsMulLabel)(markerPair)
}),
labelId: 'speed-input-label',
label: 'Speed',
value: speed,
tooltip: (0, _tooltips.Tooltips).speedTooltip,
min: 0.05,
max: 2,
step: 0.05,
placeholder: 'speed',
styleInfo: {
width: '7ch'
},
required: true
})}
${(0, _settings.TextInputRow)({
...bindPair('crop-input', 'crop', 'string'),
labelId: 'crop-input-label',
label: 'Crop',
value: crop,
tooltip: (0, _tooltips.Tooltips).cropTooltip,
pattern: cropInputValidation,
styleInfo: {
width: '20ch'
},
required: true
})}
${(0, _settings.InfoRow)({
label: 'Crop Aspect Ratio',
valueId: 'crop-aspect-ratio',
value: cropAspectRatio,
breakBeforeValue: true
})}
${(0, _settings.TextInputRow)({
...bindOverride('title-prefix-input', 'titlePrefix', 'string'),
label: 'Title Prefix',
value: overrides.titlePrefix ?? '',
tooltip: (0, _tooltips.Tooltips).titlePrefixTooltip,
placeholder: 'None',
styleInfo: {
width: '20ch'
}
})}
<div
class="settings-editor-input-div settings-info-display"
title=${(0, _tooltips.Tooltips).timeDurationTooltip}
>
<span>Time:</span>
<span id="start-time">${(0, _appState.appState).startTime}</span>
<span> - </span>
<span id="end-time">${endTime}</span>
<br />
<span>Duration: </span>
<span id="duration">${duration}/${markerPair.speed} = ${speedAdjustedDuration}</span>
</div>
`
})}
${(0, _encodeSettingsFieldset.EncodeSettingsFieldset)({
id: 'marker-pair-overrides',
variant: 'marker',
display: overridesDisplay,
source: overrides,
inheritFrom: (0, _appState.appState).settings,
bind: bindOverride,
fpsMulSuffix: {
labelId: 'minterp-fps-mul-label',
spanId: 'minterp-fps-mul-suffix',
text: minterpFpsMulLabel,
onChange: ()=>(0, _speed.updateMinterpFpsMulLabel)(markerPair)
},
zoomPan: {
enabled: markerPair.enableZoomPan,
bind: bindPair
}
})}
`;
}
function createMarkerPairEditor(targetMarker) {
const idx = targetMarker.getAttribute('data-idx');
(0, _util.assertDefined)(idx, 'targetMarker missing data-idx attribute');
const markerPairIndex = parseInt(idx, 10) - 1;
const markerPair = (0, _appState.appState).markerPairs[markerPairIndex];
(0, _cropOverlay.createCropOverlay)(markerPair.crop);
const settingsEditorDiv = document.createElement('div');
settingsEditorDiv.setAttribute('id', 'settings-editor-div');
const pairBinder = (0, _settingsEditor.createBindings)(markerPair);
const overrideBinder = (0, _settingsEditor.createBindings)(markerPair.overrides);
(0, _litHtml.render)(MarkerPairEditorTemplate(markerPair, pairBinder, overrideBinder), settingsEditorDiv);
(0, _saveLoad.injectYtcWidget)(settingsEditorDiv);
(0, _globalSettingsEditor.bindFpsMulStepBtns)();
markerPairNumberInput = document.getElementById('marker-pair-number-input');
(0, _appState.appState).speedInputLabel = document.getElementById('speed-input-label');
(0, _appState.appState).speedInput = document.getElementById('speed-input');
(0, _appState.appState).minterpFpsMulSuffixSpan = document.getElementById('minterp-fps-mul-suffix');
(0, _appState.appState).cropInputLabel = document.getElementById('crop-input-label');
(0, _appState.appState).cropInput = document.getElementById('crop-input');
(0, _appState.appState).cropAspectRatioSpan = document.getElementById('crop-aspect-ratio');
(0, _appState.appState).enableZoomPanInput = document.getElementById('enable-zoom-pan-input');
(0, _settingsEditor.setCropInputLabel)((0, _appState.appState).cropInputLabel);
(0, _settingsEditor.setCropInput)((0, _appState.appState).cropInput);
(0, _settingsEditor.setCropAspectRatioSpan)((0, _appState.appState).cropAspectRatioSpan);
(0, _settingsEditor.setEnableZoomPanInput)((0, _appState.appState).enableZoomPanInput);
(0, _appState.appState).isSettingsEditorOpen = true;
(0, _appState.appState).wasGlobalSettingsEditorOpen = false;
if ((0, _appState.appState).isForceSetSpeedOn) (0, _speed.updateSpeedInputLabel)(`Speed (${(0, _appState.appState).forceSetSpeedValue.toFixed(2)})`);
(0, _settingsEditor.highlightModifiedSettings)(pairBinder.all(), markerPair);
(0, _settingsEditor.highlightModifiedSettings)(overrideBinder.all(), markerPair.overrides);
}
function markerPairNumberInputHandler(e) {
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
const startNumbering = markerPair.startNumbering;
const endNumbering = markerPair.endNumbering;
const value = e.target.value;
const newIdx = value - 1;
(0, _appState.appState).markerPairs.splice(newIdx, 0, ...(0, _appState.appState).markerPairs.splice((0, _appState.appState).prevSelectedMarkerPairIndex, 1));
let targetMarkerRect = (0, _appState.appState).markersSvg.children[newIdx * 2];
let targetStartNumbering = (0, _appState.appState).startMarkerNumberings.children[newIdx];
let targetEndNumbering = (0, _appState.appState).endMarkerNumberings.children[newIdx];
// if target succeedes current marker pair, move pair after target
if (newIdx > (0, _appState.appState).prevSelectedMarkerPairIndex) {
(0, _util.assertDefined)(targetMarkerRect.nextElementSibling, 'targetMarkerRect has no next sibling');
(0, _util.assertDefined)(targetMarkerRect.nextElementSibling.nextElementSibling, 'targetMarkerRect has no second next sibling');
targetMarkerRect = targetMarkerRect.nextElementSibling.nextElementSibling;
(0, _util.assertDefined)(targetStartNumbering.nextElementSibling, 'targetStartNumbering has no next sibling');
targetStartNumbering = targetStartNumbering.nextElementSibling;
(0, _util.assertDefined)(targetEndNumbering.nextElementSibling, 'targetEndNumbering has no next sibling');
targetEndNumbering = targetEndNumbering.nextElementSibling;
}
const prevSelectedStartMarker = (0, _appState.appState).prevSelectedEndMarker.previousElementSibling;
(0, _util.assertDefined)(prevSelectedStartMarker, 'prevSelectedEndMarker has no previous sibling');
// if target precedes current marker pair, move pair before target
(0, _appState.appState).markersSvg.insertBefore(prevSelectedStartMarker, targetMarkerRect);
(0, _appState.appState).markersSvg.insertBefore((0, _appState.appState).prevSelectedEndMarker, targetMarkerRect);
(0, _appState.appState).startMarkerNumberings.insertBefore(startNumbering, targetStartNumbering);
(0, _appState.appState).endMarkerNumberings.insertBefore(endNumbering, targetEndNumbering);
(0, _markers.renumberMarkerPairs)();
(0, _appState.appState).prevSelectedMarkerPairIndex = newIdx;
}
function toggleMarkerPairEditor(targetMarker) {
// if target marker is previously selected marker: toggle target on/off
if ((0, _appState.appState).prevSelectedEndMarker === targetMarker && !(0, _appState.appState).wasGlobalSettingsEditorOpen) (0, _appState.appState).isSettingsEditorOpen ? toggleOffMarkerPairEditor() : toggleOnMarkerPairEditor(targetMarker);
else {
// delete current appState.settings editor appropriately
if ((0, _appState.appState).isSettingsEditorOpen) (0, _appState.appState).wasGlobalSettingsEditorOpen ? (0, _globalSettingsEditor.toggleOffGlobalSettingsEditor)() : toggleOffMarkerPairEditor();
// create new marker pair appState.settings editor
toggleOnMarkerPairEditor(targetMarker);
}
}
let autoHideUnselectedMarkerPairsStyle;
function toggleAutoHideUnselectedMarkerPairs(e) {
if (e.ctrlKey && !(0, _settingsEditor.arrowKeyCropAdjustmentEnabled)) {
(0, _util.blockEvent)(e);
if (!(0, _appState.appState).isAutoHideUnselectedMarkerPairsOn) {
autoHideUnselectedMarkerPairsStyle = (0, _util.injectCSS)((0, _css.autoHideUnselectedMarkerPairsCSS), 'auto-hide-unselected-marker-pairs-css');
(0, _appState.appState).isAutoHideUnselectedMarkerPairsOn = true;
(0, _util.flashMessage)('Auto-hiding of unselected marker pairs enabled', 'green');
} else {
(0, _util.deleteElement)(autoHideUnselectedMarkerPairsStyle);
(0, _appState.appState).isAutoHideUnselectedMarkerPairsOn = false;
(0, _util.flashMessage)('Auto-hiding of unselected marker pairs disabled', 'red');
}
}
}
function toggleOnMarkerPairEditor(targetMarker) {
(0, _appState.appState).prevSelectedEndMarker = targetMarker;
const idx = (0, _appState.appState).prevSelectedEndMarker.getAttribute('data-idx');
(0, _util.assertDefined)(idx, 'prevSelectedEndMarker missing data-idx attribute');
const selectedMarkerPairIndex = parseInt(idx) - 1;
if (selectedMarkerPairIndex !== (0, _appState.appState).prevSelectedMarkerPairIndex) (0, _cropChartSpec.setCurrentCropPoint)(null, 0);
(0, _appState.appState).prevSelectedMarkerPairIndex = selectedMarkerPairIndex;
(0, _markers.highlightSelectedMarkerPair)(targetMarker);
(0, _markers.enableMarkerHotkeys)(targetMarker);
// creating editor sets appState.isSettingsEditorOpen to true
createMarkerPairEditor(targetMarker);
(0, _settingsEditor.addCropInputHotkeys)();
(0, _charts.loadChartData)((0, _charts.chartState).speedChartInput);
(0, _charts.loadChartData)((0, _charts.chartState).cropChartInput);
(0, _cropOverlay.showCropOverlay)();
(0, _cropPreview.triggerCropPreviewRedraw)();
if ((0, _appState.appState).isChartEnabled) (0, _charts.showChart)();
targetMarker.classList.add('selected-marker');
(0, _util.assertDefined)(targetMarker.previousElementSibling, 'targetMarker has no previous sibling');
targetMarker.previousElementSibling.classList.add('selected-marker');
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
markerPair.startNumbering.classList.add('selectedMarkerNumbering');
markerPair.endNumbering.classList.add('selectedMarkerNumbering');
if ((0, _appState.appState).isAutoHideUnselectedMarkerPairsOn) autoHideUnselectedMarkerPairsStyle = (0, _util.injectCSS)((0, _css.autoHideUnselectedMarkerPairsCSS), 'auto-hide-unselected-marker-pairs-css');
}
function toggleOffMarkerPairEditor(hardHide = false) {
(0, _settingsEditor.deleteSettingsEditor)();
(0, _markers.hideSelectedMarkerPairOverlay)(hardHide);
(0, _cropOverlay.hideCropOverlay)();
(0, _charts.hideChart)();
(0, _appState.appState).prevSelectedEndMarker.classList.remove('selected-marker');
(0, _util.assertDefined)((0, _appState.appState).prevSelectedEndMarker.previousElementSibling, 'prevSelectedEndMarker has no previous sibling');
(0, _appState.appState).prevSelectedEndMarker.previousElementSibling.classList.remove('selected-marker');
const markerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
markerPair.startNumbering.classList.remove('selectedMarkerNumbering');
markerPair.endNumbering.classList.remove('selectedMarkerNumbering');
if ((0, _appState.appState).isAutoHideUnselectedMarkerPairsOn) (0, _util.deleteElement)(autoHideUnselectedMarkerPairsStyle);
}
},{"lit-html":"9fQBw","lit-html/directives/ref.js":"9tuBT","../../appState":"g0AlP","../../charts":"hBxwj","../../components/settings":"jkedK","./encode-settings-fieldset":"44SfE","../../crop-overlay":"6s727","../../crop-utils":"k2gwb","../../crop/crop-preview":"9T0zg","./global-settings-editor":"koJFH","../../markers":"EQEoZ","../../save-load":"3FwNw","./settings-editor":"jDViX","../../speed":"6CgFD","../../ui/chart/cropchart/cropChartSpec":"67uoo","../../ui/css/css":"hOiqQ","../../ui/tooltips":"a02E8","../../util/util":"99arg","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"6CgFD":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "getShortestActiveMarkerPair", ()=>getShortestActiveMarkerPair);
parcelHelpers.export(exports, "toggleMarkerPairSpeedPreview", ()=>toggleMarkerPairSpeedPreview);
parcelHelpers.export(exports, "getIsSpeedPreviewOn", ()=>getIsSpeedPreviewOn);
parcelHelpers.export(exports, "updateSpeed", ()=>updateSpeed);
parcelHelpers.export(exports, "updateSpeedInputLabel", ()=>updateSpeedInputLabel);
parcelHelpers.export(exports, "getMinterpFpsMulSuffix", ()=>getMinterpFpsMulSuffix);
parcelHelpers.export(exports, "updateMinterpFpsMulLabel", ()=>updateMinterpFpsMulLabel);
parcelHelpers.export(exports, "getSpeedMapping", ()=>getSpeedMapping);
parcelHelpers.export(exports, "getInterpolatedSpeed", ()=>getInterpolatedSpeed);
parcelHelpers.export(exports, "isMarkerSeekPending", ()=>isMarkerSeekPending);
parcelHelpers.export(exports, "markerSeekDebounceTimeout", ()=>markerSeekDebounceTimeout);
parcelHelpers.export(exports, "setIsMarkerSeekPending", ()=>setIsMarkerSeekPending);
parcelHelpers.export(exports, "setMarkerSeekDebounceTimeout", ()=>setMarkerSeekDebounceTimeout);
parcelHelpers.export(exports, "toggleMarkerPairLoop", ()=>toggleMarkerPairLoop);
parcelHelpers.export(exports, "getIsMarkerLoopPreviewOn", ()=>getIsMarkerLoopPreviewOn);
parcelHelpers.export(exports, "cycleForceSetSpeedValueDown", ()=>cycleForceSetSpeedValueDown);
parcelHelpers.export(exports, "toggleForceSetSpeed", ()=>toggleForceSetSpeed);
parcelHelpers.export(exports, "updateAllMarkerPairSpeeds", ()=>updateAllMarkerPairSpeeds);
parcelHelpers.export(exports, "updateMarkerPairSpeed", ()=>updateMarkerPairSpeed);
var _d3Ease = require("d3-ease");
var _immer = require("immer");
var _appState = require("./appState");
var _saveLoad = require("./save-load");
var _util = require("./util/util");
var _undoredo = require("./util/undoredo");
let isSpeedPreviewOn = false;
let prevSpeed = 1;
const defaultRoundSpeedMapEasing = 0.05;
const defaultSpeedRoundPrecision = 2;
function getShortestActiveMarkerPair(currentTime) {
currentTime ??= (0, _appState.appState).video.getCurrentTime();
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedMarkerPairIndex != null) {
const selectedMarkerPair = (0, _appState.appState).markerPairs[(0, _appState.appState).prevSelectedMarkerPairIndex];
if (currentTime >= Math.floor(selectedMarkerPair.start * 1e6) / 1e6 && currentTime <= Math.ceil(selectedMarkerPair.end * 1e6) / 1e6) return selectedMarkerPair;
}
const activeMarkerPairs = (0, _appState.appState).markerPairs.filter((markerPair)=>{
if (currentTime >= Math.floor(markerPair.start * 1e6) / 1e6 && currentTime <= Math.ceil(markerPair.end * 1e6) / 1e6) return true;
return false;
});
if (activeMarkerPairs.length === 0) return null;
const shortestActiveMarkerPair = activeMarkerPairs.reduce((prev, cur)=>{
if (cur.end - cur.start < prev.end - prev.start) return cur;
return prev;
});
return shortestActiveMarkerPair;
}
const toggleMarkerPairSpeedPreview = ()=>{
if (isSpeedPreviewOn) {
isSpeedPreviewOn = false;
(0, _util.flashMessage)('Marker pair speed preview disabled', 'red');
} else {
isSpeedPreviewOn = true;
if (!(0, _appState.appState).isForceSetSpeedOn) requestAnimationFrame(updateSpeed);
(0, _util.flashMessage)('Marker pair speed preview enabled', 'green');
}
};
function getIsSpeedPreviewOn() {
return isSpeedPreviewOn;
}
function updateSpeed() {
if (!isSpeedPreviewOn && !(0, _appState.appState).isForceSetSpeedOn) {
(0, _appState.appState).video.playbackRate = 1;
prevSpeed = 1;
updateSpeedInputLabel('Speed');
return;
}
if ((0, _appState.appState).isForceSetSpeedOn) {
if (prevSpeed !== (0, _appState.appState).forceSetSpeedValue) {
(0, _appState.appState).video.playbackRate = (0, _appState.appState).forceSetSpeedValue;
prevSpeed = (0, _appState.appState).forceSetSpeedValue;
updateSpeedInputLabel(`Speed (${(0, _appState.appState).forceSetSpeedValue.toFixed(2)})`);
}
requestAnimationFrame(updateSpeed);
return;
}
const shortestActiveMarkerPair = getShortestActiveMarkerPair();
let newSpeed = prevSpeed;
if (shortestActiveMarkerPair) {
let markerPairSpeed;
if ((0, _saveLoad.isVariableSpeed)(shortestActiveMarkerPair.speedMap)) markerPairSpeed = getSpeedMapping(shortestActiveMarkerPair.speedMap, (0, _appState.appState).video.getCurrentTime());
else markerPairSpeed = shortestActiveMarkerPair.speed;
// console.log(markerPairSpeed);
if (prevSpeed !== markerPairSpeed) newSpeed = markerPairSpeed;
} else newSpeed = 1;
if (prevSpeed !== newSpeed) {
(0, _appState.appState).video.playbackRate = newSpeed;
prevSpeed = newSpeed;
updateSpeedInputLabel('Speed');
}
requestAnimationFrame(updateSpeed);
}
function updateSpeedInputLabel(text) {
if ((0, _appState.appState).isSettingsEditorOpen && (0, _appState.appState).speedInputLabel != null) (0, _appState.appState).speedInputLabel.textContent = text;
}
function getMinterpFpsMulSuffix(mul, speed) {
if (mul <= 0 || speed <= 0) return '';
const ratio = mul / speed;
return ` \u{2192} ${ratio.toFixed(2)}\xd7 clip`;
}
function updateMinterpFpsMulLabel(markerPair) {
if (!(0, _appState.appState).isSettingsEditorOpen || (0, _appState.appState).minterpFpsMulSuffixSpan == null) return;
const mul = markerPair.overrides.minterpFpsMultiplier ?? (0, _appState.appState).settings.minterpFpsMultiplier ?? 0;
(0, _appState.appState).minterpFpsMulSuffixSpan.textContent = getMinterpFpsMulSuffix(mul, markerPair.speed);
}
function getSpeedMapping(speedMap, time, roundMultiple = defaultRoundSpeedMapEasing, roundPrecision = defaultSpeedRoundPrecision) {
let len = speedMap.length;
if (len === 2 && speedMap[0].y === speedMap[1].y) return speedMap[0].y;
len--;
let left;
let right;
for(let i = 0; i < len; ++i)if (speedMap[i].x <= time && time <= speedMap[i + 1].x) {
left = speedMap[i];
right = speedMap[i + 1];
break;
}
if (left && right) {
if (left.y === right.y) return left.y;
const speed = getInterpolatedSpeed(left, right, (0, _appState.appState).video.getCurrentTime(), roundMultiple, roundPrecision);
return speed;
} else return 1;
}
function getInterpolatedSpeed(left, right, time, roundMultiple = defaultRoundSpeedMapEasing, roundPrecision = defaultSpeedRoundPrecision) {
const elapsed = time - left.x;
const duration = right.x - left.x;
let easedTimePercentage = 0;
if ((0, _appState.appState).easingMode === 'cubicInOut') easedTimePercentage = (0, _d3Ease.easeCubicInOut)(elapsed / duration);
else if ((0, _appState.appState).easingMode === 'linear') easedTimePercentage = elapsed / duration;
const change = right.y - left.y;
const rawSpeed = left.y + change * easedTimePercentage || right.y;
const roundedSpeed = roundMultiple > 0 ? (0, _util.roundValue)(rawSpeed, roundMultiple, roundPrecision) : rawSpeed;
return roundedSpeed;
}
let isMarkerLoopPreviewOn = false;
let isMarkerSeekPending = false;
let markerSeekDebounceTimeout = null;
function setIsMarkerSeekPending(val) {
isMarkerSeekPending = val;
}
function setMarkerSeekDebounceTimeout(val) {
markerSeekDebounceTimeout = val;
}
function toggleMarkerPairLoop() {
if (isMarkerLoopPreviewOn) {
isMarkerLoopPreviewOn = false;
(0, _util.flashMessage)('Auto marker looping disabled', 'red');
} else {
isMarkerLoopPreviewOn = true;
(0, _util.flashMessage)('Auto marker looping enabled', 'green');
}
}
function getIsMarkerLoopPreviewOn() {
return isMarkerLoopPreviewOn;
}
function cycleForceSetSpeedValueDown() {
(0, _appState.appState).forceSetSpeedValue = (0, _appState.appState).forceSetSpeedValue - 0.25;
if ((0, _appState.appState).forceSetSpeedValue <= 0) (0, _appState.appState).forceSetSpeedValue = 1;
(0, _util.flashMessage)(`Force set appState.video speed value set to ${(0, _appState.appState).forceSetSpeedValue}`, 'green');
}
function toggleForceSetSpeed() {
if ((0, _appState.appState).isForceSetSpeedOn) {
(0, _appState.appState).isForceSetSpeedOn = false;
updateSpeedInputLabel(`Speed`);
(0, _util.flashMessage)('Force set speed disabled', 'red');
} else {
(0, _appState.appState).isForceSetSpeedOn = true;
updateSpeedInputLabel(`Speed (${(0, _appState.appState).forceSetSpeedValue.toFixed(2)})`);
if (!isSpeedPreviewOn) requestAnimationFrame(updateSpeed);
(0, _util.flashMessage)('Force set speed enabled', 'green');
}
}
function updateAllMarkerPairSpeeds(newSpeed, renderSpeedAndCropUI) {
(0, _appState.appState).markerPairs.forEach((markerPair)=>{
updateMarkerPairSpeed(markerPair, newSpeed);
});
if ((0, _appState.appState).isSettingsEditorOpen) {
if ((0, _appState.appState).wasGlobalSettingsEditorOpen) {
const markerPairMergeListInput = document.getElementById('merge-list-input');
markerPairMergeListInput?.dispatchEvent(new Event('change'));
} else {
if ((0, _appState.appState).speedInput) (0, _appState.appState).speedInput.value = newSpeed.toString();
renderSpeedAndCropUI();
}
}
(0, _util.flashMessage)(`All marker speeds updated to ${newSpeed}`, 'olive');
}
function updateMarkerPairSpeed(markerPair, newSpeed) {
const draft = (0, _immer.createDraft)((0, _undoredo.getMarkerPairHistory)(markerPair));
draft.speed = newSpeed;
const speedMap = draft.speedMap;
if (speedMap.length === 2 && speedMap[0].y === speedMap[1].y) speedMap[1].y = newSpeed;
speedMap[0].y = newSpeed;
(0, _undoredo.saveMarkerPairHistory)(draft, markerPair);
}
},{"d3-ease":[["easeCubicInOut","b0VTA","cubicInOut"]],"immer":"R6AMM","./appState":"g0AlP","./save-load":"3FwNw","./util/util":"99arg","./util/undoredo":"7UuTl","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"b0VTA":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "cubicIn", ()=>cubicIn);
parcelHelpers.export(exports, "cubicOut", ()=>cubicOut);
parcelHelpers.export(exports, "cubicInOut", ()=>cubicInOut);
function cubicIn(t) {
return t * t * t;
}
function cubicOut(t) {
return --t * t * t + 1;
}
function cubicInOut(t) {
return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2;
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"hOiqQ":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "autoHideUnselectedMarkerPairsCSS", ()=>autoHideUnselectedMarkerPairsCSS);
parcelHelpers.export(exports, "adjustRotatedVideoPositionCSS", ()=>adjustRotatedVideoPositionCSS);
parcelHelpers.export(exports, "getRotatedVideoCSS", ()=>getRotatedVideoCSS);
const autoHideUnselectedMarkerPairsCSS = `
rect.marker {
opacity: 0.25;
}
text.markerNumbering {
opacity: 0.25;
pointer-events: none;
}
rect.selected-marker {
opacity: 1;
}
text.selectedMarkerNumbering {
opacity: 1;
pointer-events: visibleFill;
}
rect.marker.end-marker {
pointer-events: none;
}
rect.selected-marker.end-marker {
pointer-events: visibleFill;
}
`;
const adjustRotatedVideoPositionCSS = `\
`;
function getRotatedVideoCSS(rotation) {
return `
.yt-clipper-video {
transform: rotate(${rotation}deg) !important;
}
#full-bleed-container {
height: 85vh !important;
max-height: none !important;
}
#page-manager {
margin-top: 0px !important;
}
#masthead {
display: none !important;
}
`;
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"8LMgO":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "speedChartSpec", ()=>speedChartSpec);
var _chartJs = require("chart.js");
var _chartJsDefault = parcelHelpers.interopDefault(_chartJs);
var _chartPrimitives = require("../chartPrimitives");
var _scatterChartSpec = require("../scatterChartSpec");
const inputId = 'speed-input';
const speedPointFormatter = (point)=>{
return `T:${point.x.toFixed(2)}\nS:${+point.y.toFixed(2)}`;
};
const speedChartConfig = {
data: {
datasets: [
{
label: 'Speed',
lineTension: 0,
data: [],
showLine: true,
pointBackgroundColor: (0, _scatterChartSpec.getScatterPointColor),
pointBorderColor: (0, _chartPrimitives.medgrey)(0.9),
pointRadius: 5,
pointHoverRadius: 4,
pointHoverBorderWidth: 1.5,
pointHoverBorderColor: (0, _chartPrimitives.lightgrey)(0.8),
pointHitRadius: 4
}
]
},
options: {
scales: {
yAxes: [
{
scaleLabel: {
display: true,
labelString: 'Speed',
fontSize: 12,
padding: 0
},
gridLines: {
color: (0, _chartPrimitives.medgrey)(0.6),
lineWidth: 1
},
ticks: {
stepSize: 0.1,
min: 0,
max: 2
}
}
]
},
plugins: {
datalabels: {
formatter: speedPointFormatter,
font: {
size: 10,
weight: 'normal'
}
}
}
}
};
const speedChartSpec = (0, _chartJsDefault.default).helpers.merge((0, _scatterChartSpec.scatterChartSpec)('speed', inputId), speedChartConfig);
},{"chart.js":"kS68a","../chartPrimitives":"hk4AN","../scatterChartSpec":"4kM90","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"jTm0T":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "sinIn", ()=>sinIn);
parcelHelpers.export(exports, "sinOut", ()=>sinOut);
parcelHelpers.export(exports, "sinInOut", ()=>sinInOut);
var pi = Math.PI, halfPi = pi / 2;
function sinIn(t) {
return +t === 1 ? 1 : 1 - Math.cos(t * halfPi);
}
function sinOut(t) {
return Math.sin(t * halfPi);
}
function sinInOut(t) {
return (1 - Math.cos(pi * t)) / 2;
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"aFJlC":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "shortcutsTableToggleButtonTemplate", ()=>shortcutsTableToggleButtonTemplate);
var _litHtml = require("lit-html");
const shortcutsTableToggleButtonTemplate = (0, _litHtml.html)`
<button
id="shortcutsTableToggleButton"
class="ytp-button"
title="Toggle yt_clipper Shortcuts Table"
>
${(0, _litHtml.svg)`
<svg version="1.1" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<path
d="M19.66 21.528l5.425 3.972c0.447 0.335 1.26 0.599 1.818 0.599h3.149l-13.319-9.773c0.073-0.27 0.116-0.579 0.116-0.899 0-1.964-1.592-3.556-3.556-3.556s-3.556 1.592-3.556 3.556c0 1.964 1.592 3.556 3.556 3.556 0.759 0 1.462-0.237 2.039-0.643l-0.011 0.008 2.265 1.656-2.265 1.656c-0.568-0.403-1.276-0.644-2.040-0.644-1.964 0-3.556 1.592-3.556 3.556s1.592 3.556 3.556 3.556c1.964 0 3.556-1.592 3.556-3.556 0-0.316-0.041-0.623-0.119-0.915l0.005 0.025 2.946-2.154zM13.29 16.957c-0.841 0-1.524-0.682-1.524-1.524s0.682-1.524 1.524-1.524v0c0.841 0 1.524 0.682 1.524 1.524s-0.682 1.524-1.524 1.524v0zM13.29 26.1c-0.841 0-1.524-0.682-1.524-1.524s0.682-1.524 1.524-1.524v0c0.841 0 1.524 0.682 1.524 1.524s-0.682 1.524-1.524 1.524v0zM25.075 14.508c0.515-0.348 1.143-0.567 1.82-0.599l0.008-0h3.149l-7.619 5.587-2.083-1.524 4.724-3.464z"
fill="#fff"
></path>
</svg>
`}
</button>
`;
},{"lit-html":"9fQBw","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"72Gvt":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "hintsBarToggleButtonTemplate", ()=>hintsBarToggleButtonTemplate);
var _litHtml = require("lit-html");
var _glyphs = require("../../features/icons/glyphs");
const hintsBarToggleButtonTemplate = (0, _litHtml.html)`
<button
id="hintsBarToggleButton"
class="ytp-button"
title="Toggle yt_clipper Contextual Hints Bar (Alt+F)"
>
<span class="yt-clipper-hints-bar-icon-wrap">${(0, _glyphs.renderUIIcon)('hamburger', 26)}</span>
</button>
`;
},{"lit-html":"9fQBw","../../features/icons/glyphs":"273gO","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"273gO":[function(require,module,exports,__globalThis) {
/**
* Icon registry + render helpers for the injected UI (hints bar chord pills,
* toggle buttons, hover-bar controls).
*
* Almost all glyphs come from Blender's open-source icon set (via
* ui.blender.org). Blender authors these specifically for dense UI
* rendering at 16–18 px with strict pixel alignment — exactly the size
* range our chord pills render at. Notable Blender coverage that's rare
* elsewhere: explicit left / right / middle mouse button variants,
* scroll-wheel, drag-with-button, AND key-cap glyphs for Ctrl / Shift /
* Alt. Each entry carries a `// blender:<NAME>` comment for traceability.
* Licensed CC BY-SA 4.0.
*
* The only custom glyph is **Meta** (the ⌘ Command key) — Blender is
* platform-agnostic about Mac vs Windows and doesn't ship a Command-key
* glyph, so we draw four filled squares ourselves.
*
* Adding a new Blender icon: find it on ui.blender.org's icon browser,
* copy the SVG, paste it as a new template literal below with the
* `// blender:<NAME>` comment, replace `fill="#fff"` (or any inline
* fill style) with `fill="currentColor"` so CSS color inheritance works,
* and surface a renderer helper if it's not already covered.
*/ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "renderModifierGlyph", ()=>renderModifierGlyph);
parcelHelpers.export(exports, "renderArrowGlyph", ()=>renderArrowGlyph);
parcelHelpers.export(exports, "renderMouseGlyph", ()=>renderMouseGlyph);
/** Renders a non-chord UI icon (close button, flip chevron, toggle-button
* icons). Size is configurable since the toggle buttons render at ~24-40 px
* while the chord-bar controls render at 12-14 px. */ parcelHelpers.export(exports, "renderUIIcon", ()=>renderUIIcon);
var _litHtml = require("lit-html");
// =============================================================================
// Blender icons — viewBox varies (1800x1800 for mouse/menu, 1000x800 for
// horizontal triangles, 800x1000 for vertical triangles). The renderer
// places them in a fixed-size box with SVG's default
// preserveAspectRatio="xMidYMid meet" — triangles end up centered with
// implicit padding on the short axis, which reads correctly in chord pills.
// =============================================================================
// blender:MOUSE_LMB — mouse outline with the left button highlighted.
const BLENDER_MOUSE_LMB = {
source: 'blender:MOUSE_LMB',
viewBox: '0 0 1800 1800',
body: (0, _litHtml.svg)`<g fill="currentColor"><path d="m10.484375 450.98353c-.752 0-1.4538175.239-2.0234375.64453-.88567.63055-1.4609375 1.67061-1.4609375 2.83985v4.03209c.00003.27537.2226769.4989.4980469.5l4.0097651.008c.27613-.00003.49997-.22387.5-.5l-.007812-6.98931v-.002-.0352c-.001-.27524-.22466-.49793-.5-.49796zm2.515625 0v1h2.508368c1.38452 0 2.484375 1.09985 2.484375 2.48438v9.04771c0 1.38452-1.099855 2.48438-2.484375 2.48438h-5.023993c-1.38452 0-2.484375-1.09986-2.484375-2.48438v-3.51562h-1v3.51562c0 1.92123 1.563145 3.48438 3.484375 3.48438h5.023993c1.92123 0 3.484375-1.56314 3.484375-3.48438v-9.04771c0-1.92123-1.563145-3.48438-3.484375-3.48438z" transform="matrix(100 0 0 100 -599.6372 -44999.17700000001)"/></g>`
};
// blender:MOUSE_RMB — mouse outline with the right button highlighted.
const BLENDER_MOUSE_RMB = {
source: 'blender:MOUSE_RMB',
viewBox: '0 0 1800 1800',
body: (0, _litHtml.svg)`<g fill="currentColor"><path d="m57.512484 450.98438c.752 0 1.453818.239 2.023438.64453.88567.63055 1.460937 1.67061 1.460937 2.83985v4.03124c-.00003.27537-.222677.4989-.498047.5l-4.009765.008c-.27613-.00003-.49997-.22387-.5-.5l.0078-6.98846v-.002-.0352c.001-.27524.22466-.49793.5-.49796zm-2.515625 0v1h-2.512484c-1.38452 0-2.484375 1.09985-2.484375 2.48438v9.04686c0 1.38452 1.099855 2.48438 2.484375 2.48438h5.028109c1.38452 0 2.484375-1.09986 2.484375-2.48438v-3.51562h1v3.51562c0 1.92123-1.563145 3.48438-3.484375 3.48438h-5.028109c-1.92123 0-3.484375-1.56314-3.484375-3.48438v-9.04686c0-1.92123 1.563145-3.48438 3.484375-3.48438z" transform="matrix(100 0 0 100 -4799.843 -44999.219)"/></g>`
};
// blender:MOUSE_MMB — mouse outline with the wheel/middle button highlighted.
const BLENDER_MOUSE_MMB = {
source: 'blender:MOUSE_MMB',
viewBox: '0 0 1800 1800',
body: (0, _litHtml.svg)`<g fill="currentColor"><path d="m31.484375 450.99458c-1.92123 0-3.484375 1.56315-3.484375 3.48438v9.03666c0 1.92123 1.563145 3.48438 3.484375 3.48438h5.082348c1.92123 0 3.484375-1.56315 3.484375-3.48438v-9.03666c0-1.92123-1.563145-3.48438-3.484375-3.48438zm0 1h5.082348c1.38453 0 2.484375 1.09985 2.484375 2.48438v9.03666c0 1.38453-1.099845 2.48438-2.484375 2.48438h-5.082348c-1.38453 0-2.484375-1.09985-2.484375-2.48438v-9.03666c0-1.38453 1.099845-2.48438 2.484375-2.48438zm1.5625 1c-.57133 0-1.046875.47555-1.046875 1.04688v4.91166c0 .57133.475545 1.04688 1.046875 1.04688h1.973036c.57133 0 1.046875-.47555 1.046875-1.04688v-4.91166c0-.57133-.475545-1.04688-1.046875-1.04688z" transform="matrix(99.598235 0 0 100 -2688.8846 -44999.729)"/></g>`
};
// blender:MOUSE_MMB_SCROLL — mouse outline with the wheel highlighted and
// up/down chevrons indicating scroll direction.
const BLENDER_MOUSE_MMB_SCROLL = {
source: 'blender:MOUSE_MMB_SCROLL',
viewBox: '0 0 1800 1800',
body: (0, _litHtml.svg)`<g fill="currentColor"><path d="m31.484382 450.99458c-1.921226 0-3.484389 1.56316-3.484389 3.48439v9.03664c0 1.92123 1.563163 3.48439 3.484389 3.48439h1.988723c-.0023-.007-.986951-1-.986951-1h-1.001824c-1.384526 0-2.484376-1.09986-2.484376-2.48439v-9.03664c0-1.38453 1.09985-2.48439 2.484376-2.48439h1.034848s1.00328-.99586 1.00327-1.00003zm3.042928.00001 1.004566 1.00003h1.03478c1.384528 0 2.484376 1.09986 2.484376 2.48439v9.03664c0 1.38453-1.099848 2.48439-2.484376 2.48439h-1.011359l-1.002942 1.00004h2.014337c1.921226 0 3.484389-1.56316 3.484389-3.48439v-9.03664c0-1.92123-1.563163-3.4844-3.484389-3.4844zm-1.469324 5.01106c-.571325.003-1.046863.47556-1.046863 1.04689v3.89319c0 .57133.475533 1.04689 1.046863 1.04689h1.928585c.571328 0 1.046862-.47556 1.046862-1.04689v-3.89319c0-.57133-.473869-1.0621-1.045187-1.05863z" transform="matrix(99.598235 0 0 100 -2688.8846 -44999.729)"/><g fill-rule="evenodd" stroke-width="25"><path d="m700.0224 1600.3852-299.81007-300.3127 599.88877-.1344z"/><path d="m699.97151 200.01734 300.20149 299.92135-599.88879.13433z"/></g></g>`
};
// blender:MOUSE_LMB_DRAG — left-button mouse glyph with a vertical drag
// indicator next to the mouse body. One glyph that reads as the whole
// click-and-drag gesture (used for the `drag` chord token, distinct from
// the plain `click` token which uses MOUSE_LMB).
const BLENDER_MOUSE_LMB_DRAG = {
source: 'blender:MOUSE_LMB_DRAG',
viewBox: '0 0 1800 1800',
body: (0, _litHtml.svg)`<g fill="currentColor"><path d="m1347.9272 100.2289c-44.785 1.82-64.741 57.103-31.445 87.11 58.809 54.453 84.18 108.56 84.18 160.156l-.391 601.94086c-1 67.61594 100.6792 67.61594 99.7232 0l.4-601.95586c0-85.09-42.9022-165.971-115.9342-233.594-9.82-9.31-23.001-14.245998-36.524-13.672zm301.563 200.17924c-27.537.4-49.542 23.047-49.219 50.586v398.10992c-1 67.616 100.8176 67.616 99.8616 0v-398.10992c.3-28.15-22.4936-51.025-50.6426-50.586z" stroke-width="100"/><path d="m10.484375 450.98353c-.752 0-1.4538175.239-2.0234375.64453-.88567.63055-1.4609375 1.67061-1.4609375 2.83985v4.03209c.00003.27537.2226769.4989.4980469.5l4.0097651.008c.27613-.00003.49997-.22387.5-.5l-.007812-6.98931v-.002-.0352c-.001-.27524-.22466-.49793-.5-.49796zm2.515625 0v1h2.508368c1.38452 0 2.484375 1.09985 2.484375 2.48438v9.04771c0 1.38452-1.099855 2.48438-2.484375 2.48438h-5.023993c-1.38452 0-2.484375-1.09986-2.484375-2.48438v-3.51562h-1v3.51562c0 1.92123 1.563145 3.48438 3.484375 3.48438h5.023993c1.92123 0 3.484375-1.56314 3.484375-3.48438v-9.04771c0-1.92123-1.563145-3.48438-3.484375-3.48438z" transform="matrix(100 0 0 100 -599.6372 -44999.17700000001)"/></g>`
};
// blender:MOUSE_MOVE — plain mouse outline (no button highlighted) plus a
// move/trail indicator. We use it for the `mouseover` chord token.
const BLENDER_MOUSE_MOVE = {
source: 'blender:MOUSE_MOVE',
viewBox: '0 0 1800 1800',
body: (0, _litHtml.svg)`<g fill="currentColor"><path d="m1347.7138 101.47718c-44.785 1.82-64.741 57.103-31.445 87.11 58.809 54.453 84.18 108.56 84.18 160.156l-.391 600.571c-1 67.61622 100.956 67.61622 100 0l.4-600.586c0-85.09-43.179-165.971-116.211-233.594-9.82-9.31-23.001-14.246-36.524-13.672zm301.563 201.563c-27.537.4-49.542 23.047-49.219 50.586v405.859c-1 67.616 100.956 67.616 100 0v-405.859c.3-28.15-22.632-51.025-50.781-50.586z" stroke-width="100"/><path d="m31.484375 450.99458c-1.92123 0-3.484375 1.56315-3.484375 3.48438v9.03666c0 1.92123 1.563145 3.48438 3.484375 3.48438h5.082348c1.92123 0 3.484375-1.56315 3.484375-3.48438v-9.03666c0-1.92123-1.563145-3.48438-3.484375-3.48438zm0 1h5.082348c1.38453 0 2.484375 1.09985 2.484375 2.48438v9.03666c0 1.38453-1.099845 2.48438-2.484375 2.48438h-5.082348c-1.38453 0-2.484375-1.09985-2.484375-2.48438v-9.03666c0-1.38453 1.099845-2.48438 2.484375-2.48438z" transform="matrix(99.598235 0 0 100 -2688.8846 -44999.729)"/></g>`
};
// blender:TRIA_UP — small upward-pointing triangle. Doubles as both the
// `up` arrow-key glyph in chord pills AND the chevron-up control on the
// hints-bar header (Blender doesn't ship a separate chevron; the triangle
// is the same shape we want).
const BLENDER_TRIA_UP = {
source: 'blender:TRIA_UP',
viewBox: '0 0 1000 800',
body: (0, _litHtml.svg)`<path fill="currentColor" fill-rule="evenodd" d="m156 629.49414c0 .27842.2216.50584.5.50586h7c.4051.0006.6427-.45544.4102-.78711l-3.5-5c-.199-.28542-.6214-.28542-.8204 0l-3.5 5c-.058.0826-.089.1806-.09.28125z" transform="matrix(100 0 0 100 -15500 -62301.636)"/>`
};
// blender:TRIA_DOWN — small downward-pointing triangle.
const BLENDER_TRIA_DOWN = {
source: 'blender:TRIA_DOWN',
viewBox: '0 0 1000 800',
body: (0, _litHtml.svg)`<path fill="currentColor" fill-rule="evenodd" d="m113.9992 624.50586c0-.27842.2216-.50584.5-.50586h7c.4051-.0006.6427.45544.4102.78711l-3.5 5c-.199.28542-.6214.28542-.8204 0l-3.5-5c-.058-.0826-.089-.1806-.09-.28125z" transform="matrix(100 0 0 100 -11300 -62303.986)"/>`
};
// blender:TRIA_LEFT
const BLENDER_TRIA_LEFT = {
source: 'blender:TRIA_LEFT',
viewBox: '0 0 800 1000',
body: (0, _litHtml.svg)`<path fill="currentColor" fill-rule="evenodd" d="m141.49531 622.99926c.27843 0 .50584.2216.50587.5v7c.00059.4051-.45545.6427-.78712.4102l-5-3.5c-.28541-.199-.28541-.6214 0-.8204l5-3.5c.0826-.058.1806-.089.28125-.09z" transform="matrix(100 0 0 100 -13500 -62200)"/>`
};
// blender:TRIA_RIGHT
const BLENDER_TRIA_RIGHT = {
source: 'blender:TRIA_RIGHT',
viewBox: '0 0 800 1000',
body: (0, _litHtml.svg)`<path fill="currentColor" fill-rule="evenodd" d="m94.50586 623c-.27842 0-.50585.2216-.50586.5v7c-.00061.4051.45544.6427.78711.4102l5-3.5c.28542-.199.28542-.6214 0-.8204l-5-3.5c-.0826-.058-.1806-.089-.28125-.09z" transform="matrix(100 0 0 100 -9300 -62200)"/>`
};
// blender:STATUSBAR — depicts a window/panel with a status bar at the
// bottom. Used as the hints-bar visibility toggle in the YouTube player
// rail because it directly suggests "toggle the bar at the bottom" — a
// much closer semantic match than the generic hamburger/menu glyph.
const BLENDER_STATUSBAR = {
source: 'blender:STATUSBAR',
viewBox: '0 0 1600 1600',
body: (0, _litHtml.svg)`<g fill="currentColor"><g transform="matrix(-100 0 0 -100 56200.001 66499.99900000001)"><path opacity=".6" d="m7 654v8c0 .54532.45468 1 1 1h12c.54532 0 1-.45468 1-1v-8h-1v8h-12v-8z" transform="translate(540 1)"/><path d="m27 680v3c0 .54532.45468 1 1 1h12c.54532 0 1-.45468 1-1v-3h-1-12zm1 1h2v2h-2z" transform="matrix(1 0 0 -1 520 1334)"/></g></g>`
};
// blender:PANEL_CLOSE — the close X. A `+` shape rotated 45° via the
// path's transform matrix (Blender's clever way of drawing an X with
// pixel-aligned arms).
const BLENDER_PANEL_CLOSE = {
source: 'blender:PANEL_CLOSE',
viewBox: '0 0 1000 1000',
body: (0, _litHtml.svg)`<g fill="currentColor"><path d="m306.99023 241.72461a.66673335.66673335 0 0 0 -.65625.67578v5.93359h-5.93359a.66673335.66673335 0 1 0 0 1.33204h5.93359v5.93359a.66673335.66673335 0 1 0 1.33204 0v-5.93359h5.93359a.66673335.66673335 0 1 0 0-1.33204h-5.93359v-5.93359a.66673335.66673335 0 0 0 -.67579-.67578z" transform="matrix(-53.033 -53.033 -53.033 53.033 29986.348 3575.914)"/></g>`
};
// =============================================================================
// Blender modifier glyphs — Ctrl and Alt render as a hollow rounded
// key-cap outline with the modifier symbol inside (Blender's unfilled
// `KEY_*` variants, not the heavier `KEY_*_FILLED` ones). The outline
// reads as a frame around the symbol rather than competing with it at
// 12–16 px. Shift is just the ⇧ silhouette — Blender's KEY_SHIFT ships
// without a surrounding cap.
//
// Note: Blender doesn't ship a Cmd/Meta key glyph (the app is keyboard-
// agnostic to Mac vs Windows), so MOD_META below stays custom.
// =============================================================================
// blender:KEY_CONTROL — hollow rounded key-cap outline with an up-chevron
// (⌃) inside. Outline style (not the FILLED variant) so the key-cap reads
// as a frame around the symbol rather than competing with it.
const MOD_CTRL = {
source: 'blender:KEY_CONTROL',
viewBox: '0 0 1800 1800',
body: (0, _litHtml.svg)`<g fill="currentColor"><path d="m 31.484375,450.99731 c -1.92123,0 -3.483002,1.56042 -3.483002,3.48165 v 9.03666 c 0,1.92123 1.561772,3.48168 3.483002,3.48168 h 9.095609 c 1.92123,0 3.485856,-1.56045 3.485856,-3.48168 v -9.03666 c 0,-1.92123 -1.564626,-3.48165 -3.485856,-3.48165 z m 0,1.00001 h 9.095609 c 1.38453,0 2.481865,1.09711 2.481865,2.48164 v 9.03666 c 0,1.38453 -1.097335,2.48168 -2.481865,2.48168 h -9.095609 c -1.38453,0 -2.479002,-1.09715 -2.479002,-2.48168 v -9.03666 c 0,-1.38453 1.094472,-2.48164 2.479002,-2.48164 z" transform="matrix(99.598235,0,0,100,-2688.8846,-44999.729)"/><path d="m 879.41846,602.96175 a 78.941779,81.883008 0 0 0 -35.3845,21.1851 L 423.05843,1060.8127 a 78.933886,81.874821 0 0 0 3e-5,115.7883 78.933886,81.874821 0 0 0 111.62985,0 L 899.84886,797.83031 1265.017,1176.6033 a 78.933886,81.874821 0 0 0 111.6299,0 78.933886,81.874821 0 0 0 0,-115.7883 L 955.66366,624.14695 a 78.941779,81.883008 0 0 0 -76.2451,-21.18511 z"/></g>`
};
// blender:KEY_SHIFT — bare shift-arrow shape, no key-cap outline. Blender's
// unfilled Shift glyph ships without the surrounding cap (unlike KEY_CONTROL
// / KEY_OPTION) — the ⇧ silhouette is iconic enough on its own.
const MOD_SHIFT = {
source: 'blender:KEY_SHIFT',
viewBox: '0 0 1800 1800',
body: (0, _litHtml.svg)`<g fill="currentColor" transform="matrix(12.467986,0,0,12.464937,1320.2753,-785.84062)"><path d="m -25.683279,74.626271 c -4.763505,-4.754888 -11.429476,-4.750014 -16.058382,-0.142961 l -53.705667,53.45212 c -4.971712,4.94825 -1.523831,15.31529 4.084739,15.32468 l 13.56107,0.0227 c 2.645399,0.004 3.96329,1.14044 3.9697,3.97372 l 0.06278,27.75038 c 0.03316,14.65826 -0.07318,24.42133 16.000057,24.421 l 48.1253755,-0.001 c 16.0775186,-3.4e-4 16.089285,-10.02945 16.0621447,-24.15618 l -0.053859,-28.03397 c -0.00516,-2.68487 1.0092566,-4.00455 4.0075438,-3.99701 l 13.363106,0.0336 c 5.784713,0.0145 9.314969,-10.01974 4.072176,-15.25305 z M -1.5576726,175.45807 c -0.1016219,11.73412 0.040504,15.94652 -12.1147894,15.94652 h -40.25719 c -12.333562,0 -11.549723,-4.07533 -11.904873,-16.05426 l 0.03836,-32.16491 c 0.0052,-4.33348 -3.176402,-7.92303 -8.040291,-7.92427 l -11.802624,-0.003 c -4.129012,-0.001 -3.772884,-2.17478 -2.010777,-3.92528 l 48.895713,-48.573612 c 5.158669,-5.124687 5.482955,-4.617169 10.150274,0.05419 l 48.376803,48.418682 c 1.473459,1.47473 3.121303,4.0331 -1.671324,4.02984 l -11.7436716,-0.008 c -4.9812408,-0.003 -7.9748377,2.99718 -7.9820861,8.03481 z"/></g>`
};
// blender:KEY_OPTION — rounded key-cap with the Mac ⌥ Option glyph
// inside (two stepped horizontal lines, the canonical Alt/Option shape).
const MOD_ALT = {
source: 'blender:KEY_OPTION',
viewBox: '0 0 1800 1800',
body: (0, _litHtml.svg)`<g fill="currentColor"><path d="m 31.484375,450.99731 c -1.92123,0 -3.483002,1.56042 -3.483002,3.48165 v 9.03666 c 0,1.92123 1.561772,3.48168 3.483002,3.48168 h 9.095609 c 1.92123,0 3.485856,-1.56045 3.485856,-3.48168 v -9.03666 c 0,-1.92123 -1.564626,-3.48165 -3.485856,-3.48165 z m 0,1.00001 h 9.095609 c 1.38453,0 2.481865,1.09711 2.481865,2.48164 v 9.03666 c 0,1.38453 -1.097335,2.48168 -2.481865,2.48168 h -9.095609 c -1.38453,0 -2.479002,-1.09715 -2.479002,-2.48168 v -9.03666 c 0,-1.38453 1.094472,-2.48164 2.479002,-2.48164 z" transform="matrix(99.598235,0,0,100,-2688.8846,-44999.729)"/><path d="M 6.9468697 9.1231197 C 6.5326565 9.1231197 6.2287565 9.4320814 6.2287565 9.8462941 C 6.2287565 10.260507 6.5326565 10.558765 6.9468697 10.558765 L 10.066848 10.558765 L 11.808277 13.937178 C 11.924805 14.21611 12.166848 14.397848 12.469144 14.398131 L 17.010615 14.398131 C 17.424828 14.398131 17.74468 14.091888 17.74468 13.677675 C 17.74468 13.263462 17.419524 12.959824 17.00531 12.959824 L 12.944474 12.959824 L 11.188255 9.5867161 C 11.071727 9.307783 10.856733 9.1234018 10.554437 9.1231197 L 6.9468697 9.1231197 z M 13.66838 9.1255378 C 13.254166 9.1255378 12.946555 9.4346108 12.946555 9.8488246 C 12.946555 10.263038 13.254166 10.558765 13.66838 10.558765 L 17.007972 10.558765 C 17.422185 10.558765 17.742037 10.258237 17.742037 9.8440259 C 17.742037 9.4298123 17.422185 9.1255378 17.007972 9.1255378 L 13.66838 9.1255378 z" transform="matrix(104.19557,0,0,104.19557,-348.94242,-300.46171)"/></g>`
};
// Meta — Blender doesn't ship a Cmd/Meta glyph. Keep the custom ⌘ shape
// (four filled squares) so chords with Meta still render unambiguously.
const MOD_META = {
source: 'custom',
viewBox: '0 0 14 14',
body: (0, _litHtml.svg)`<path fill="currentColor" d="M2 2.4 L 6 2.4 L 6 6.4 L 2 6.4 Z M 8 2.4 L 12 2.4 L 12 6.4 L 8 6.4 Z M 2 7.8 L 6 7.8 L 6 11.8 L 2 11.8 Z M 8 7.8 L 12 7.8 L 12 11.8 L 8 11.8 Z"/>`
};
const MODIFIER_GLYPHS = {
ctrl: {
glyph: MOD_CTRL,
label: 'Control'
},
shift: {
glyph: MOD_SHIFT,
label: 'Shift'
},
alt: {
glyph: MOD_ALT,
label: 'Alt'
},
meta: {
glyph: MOD_META,
label: 'Meta'
}
};
const ARROW_GLYPHS = {
up: BLENDER_TRIA_UP,
down: BLENDER_TRIA_DOWN,
left: BLENDER_TRIA_LEFT,
right: BLENDER_TRIA_RIGHT
};
const MOUSE_GLYPHS = {
click: BLENDER_MOUSE_LMB,
'right-click': BLENDER_MOUSE_RMB,
'wheel-click': BLENDER_MOUSE_MMB,
mousewheel: BLENDER_MOUSE_MMB_SCROLL,
drag: BLENDER_MOUSE_LMB_DRAG,
mouseover: BLENDER_MOUSE_MOVE
};
const MOUSE_LABELS = {
click: 'Click',
'right-click': 'Right click',
mouseover: 'Mouse hover',
drag: 'Drag',
mousewheel: 'Mouse wheel scroll',
'wheel-click': 'Mouse wheel click'
};
const UI_GLYPHS = {
close: BLENDER_PANEL_CLOSE,
// chevron-up/down reuse the TRIA_UP/DOWN triangles — Blender's icon
// language treats the small triangle as both an arrow indicator AND a
// chevron-style direction cue, so one shape covers both uses.
chevronUp: BLENDER_TRIA_UP,
chevronDown: BLENDER_TRIA_DOWN,
hamburger: BLENDER_STATUSBAR
};
const UI_LABELS = {
close: 'Close',
chevronUp: 'Chevron up',
chevronDown: 'Chevron down',
hamburger: 'Hints bar'
};
/** Standard glyph render sizes inside the chord pill. Blender icons are
* authored at 18 px native; we render close to that so the path
* coordinates pixel-align cleanly and detail (button highlights, key-cap
* outlines, modifier symbols inside the cap) reads at chord-pill size.
* Non-square triangles preserve aspect via the SVG default
* `preserveAspectRatio` — they appear centered inside their outer box. */ const SIZE_MOD_PX = 16;
const SIZE_ARROW_PX = 14;
const SIZE_MOUSE_PX = 16;
function renderModifierGlyph(mod) {
const { glyph, label } = MODIFIER_GLYPHS[mod];
return (0, _litHtml.html)`<svg
class="hints-bar-mod-icon"
viewBox=${glyph.viewBox}
width=${SIZE_MOD_PX}
height=${SIZE_MOD_PX}
role="img"
aria-label=${label}
>
${glyph.body}
</svg>`;
}
function renderArrowGlyph(dir) {
const glyph = ARROW_GLYPHS[dir];
return (0, _litHtml.html)`<svg
class="hints-bar-key-icon hints-bar-key-icon--arrow"
viewBox=${glyph.viewBox}
width=${SIZE_ARROW_PX}
height=${SIZE_ARROW_PX}
role="img"
aria-label=${`Arrow ${dir}`}
>
${glyph.body}
</svg>`;
}
function renderMouseGlyph(token) {
const glyph = MOUSE_GLYPHS[token];
return (0, _litHtml.html)`<svg
class="hints-bar-key-icon hints-bar-key-icon--mouse"
viewBox=${glyph.viewBox}
width=${SIZE_MOUSE_PX}
height=${SIZE_MOUSE_PX}
role="img"
aria-label=${MOUSE_LABELS[token]}
>
${glyph.body}
</svg>`;
}
function renderUIIcon(name, sizePx) {
const glyph = UI_GLYPHS[name];
return (0, _litHtml.html)`<svg
viewBox=${glyph.viewBox}
width=${sizePx}
height=${sizePx}
role="img"
aria-label=${UI_LABELS[name]}
>
${glyph.body}
</svg>`;
}
},{"lit-html":"9fQBw","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"4nJHa":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "setHintsBarEnabled", ()=>setHintsBarEnabled);
parcelHelpers.export(exports, "mountHintsBar", ()=>mountHintsBar);
parcelHelpers.export(exports, "getHintsBarHandle", ()=>getHintsBarHandle);
parcelHelpers.export(exports, "toggleHintsBar", ()=>toggleHintsBar);
var _fs = require("fs");
var _litHtml = require("lit-html");
var _util = require("../../util/util");
var _glyphs = require("../icons/glyphs");
var _hintContext = require("./hint-context");
var _hoverRegion = require("./hover-region");
var _inputFocus = require("./input-focus");
var _modifierTracker = require("./modifier-tracker");
const hintsBarCSS = "/* ============================================================================\n yt_clipper contextual hints bar\n Aesthetic: precision instrument. Mono-styled key glyphs, hairline edges,\n single brand-red accent on the drag handle.\n ============================================================================ */\n\n.hints-bar-host {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 2147483647;\n}\n\n/* Pinned bar \u2014 spans full viewport width, anchored to top or bottom edge\n (controlled by `data-pin` attribute). Single-row layout; chips that don't\n fit are reachable via horizontal scroll (mouse wheel converted to\n `scrollLeft` in JS) and fade out at the right edge as the overflow\n affordance. */\n.hints-bar-shell {\n position: absolute;\n left: 0;\n /* `--scrollbar-offset` is set in JS by `updateViewportOffsets()` to the\n page's vertical scrollbar width (0 if no scrollbar). Without this\n inset the shell would extend under the scrollbar on Windows Chrome,\n clipping the right edge of the last chip and the right fade overlay. */\n right: var(--scrollbar-offset, 0px);\n box-sizing: border-box;\n\n display: inline-flex;\n align-items: center;\n flex-wrap: nowrap;\n /* `overflow-x: clip` + `overflow-y: visible` is the ONLY combo that keeps\n the Y axis truly unclipped (chord popovers and group cards need to\n extend above/below the shell). Setting `overflow-x: auto` would coerce\n overflow-y to `auto` per the CSS spec and clip popovers. So horizontal\n \"scrolling\" is implemented manually: chips live inside `.hints-bar-scroller`\n which is translateX'd by the wheel / drag handlers in JS. */\n overflow-x: clip;\n overflow-y: visible;\n gap: 10px;\n /* Small right padding so chips can extend close to the shell's edge.\n The 56px-wide `::after` fade overlay sits OVER the rightmost chips\n when there's more to scroll (toggled by `.can-scroll-right`); when\n scrolled to max, the fade hides and chips read fully. */\n padding: 4px 12px 4px 5px;\n\n border-radius: 8px;\n background: rgba(12, 14, 16, 0.82);\n backdrop-filter: blur(12px) saturate(140%);\n -webkit-backdrop-filter: blur(12px) saturate(140%);\n border: 1px solid rgba(255, 255, 255, 0.08);\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.55),\n 0 1px 0 rgba(255, 255, 255, 0.06) inset,\n 0 -1px 0 rgba(0, 0, 0, 0.4) inset;\n\n color: #e8e8e8;\n font: 11px/1.2 'IBM Plex Sans', 'S\xf6hne', system-ui, -apple-system,\n 'Segoe UI Variable Display', 'Segoe UI', sans-serif;\n letter-spacing: 0.01em;\n\n pointer-events: auto;\n cursor: default;\n user-select: none;\n\n /* Resting state is translucent so the bar is unobtrusive over video content.\n Hovering the bar brings it to full opacity for inspection.\n On hide, visibility waits for the opacity fade so the transition plays. */\n opacity: 0.9;\n visibility: visible;\n transition:\n opacity 200ms ease,\n border-color 150ms ease,\n box-shadow 150ms ease,\n visibility 0s linear 0s;\n}\n\n/* Scroll viewport \u2014 chip overflow is clipped horizontally here while\n popovers extending up/down stay visible (the parent shell's\n overflow-y: visible cascades through). The inner is `inline-flex`\n with all chip rows + persistent chips; its natural width can exceed\n the viewport. JS applies `transform: translateX()` to the inner to\n simulate horizontal scrolling. */\n.hints-bar-scroller {\n flex: 1 1 0;\n min-width: 0;\n overflow-x: clip;\n overflow-y: visible;\n position: relative;\n}\n\n.hints-bar-scroller-inner {\n display: inline-flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: 10px;\n /* Right padding ensures the last chip is never flush against the\n scroller's `overflow-x: clip` boundary at max scroll. Sized to\n extend the last chip past the right fade overlay (32px wide, see\n `.hints-bar-shell::after`) so even if the fade is partway through\n its opacity transition, the chip's label is past the gradient and\n reads cleanly. The padding is part of `offsetWidth`, so\n `getMaxScroll()` accounts for it automatically. */\n padding-right: 36px;\n will-change: transform;\n}\n\n.hints-bar-shell:hover {\n opacity: 1;\n border-color: rgba(255, 255, 255, 0.14);\n}\n\n.hints-bar-shell.is-hidden {\n opacity: 0;\n visibility: hidden;\n transition:\n opacity 200ms ease,\n visibility 0s linear 200ms;\n}\n\n/* Right-edge fade overlay \u2014 the visible affordance for \"there are more\n chips, scroll right to reach them\". Anchored to the shell's right edge.\n Only visible when there's actually content scrolled off-screen to the\n right (toggled via the `can-scroll-right` class set by the scroll\n listener). Narrow (32px) so it stays an edge accent rather than\n covering meaningful chip content \u2014 the scroller-inner's 36px right\n padding keeps the last chip past the fade gradient at max scroll. */\n.hints-bar-shell::after {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: 32px;\n background: linear-gradient(to right, transparent, rgba(12, 14, 16, 0.82) 90%);\n pointer-events: none;\n z-index: 1;\n opacity: 0;\n transition: opacity 150ms ease;\n}\n\n.hints-bar-shell.can-scroll-right::after {\n opacity: 1;\n}\n\n.hints-bar-shell[data-pin='bottom'] {\n top: auto;\n bottom: 0;\n border-radius: 8px 8px 0 0;\n border-bottom: none;\n box-shadow:\n 0 -8px 32px rgba(0, 0, 0, 0.55),\n 0 1px 0 rgba(255, 255, 255, 0.06) inset;\n}\n\n.hints-bar-shell[data-pin='top'] {\n top: 0;\n bottom: auto;\n border-radius: 0 0 8px 8px;\n border-top: none;\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.55),\n 0 -1px 0 rgba(255, 255, 255, 0.06) inset;\n}\n\n/* Sticky controls wrapper \u2014 chevron + close button \u2014 stays anchored at\n the left of the bar so they're always reachable. The mode badge sits\n immediately to their right (also outside the scroller, so it stays\n anchored too). The \"scrolled-from-left\" fade affordance lives on the\n scroller's left edge, NOT here \u2014 that way it indicates scrolled-off\n chips without bleeding over the mode badge. */\n.hints-bar-controls {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n z-index: 2;\n background: rgb(12, 14, 16);\n flex: 0 0 auto;\n}\n\n/* Left-edge fade overlay on the scroller \u2014 the visible affordance for\n \"there are more chips scrolled off to the left\". Anchored at the\n scroller's left edge (immediately after the mode badge) so the badge\n itself stays fully opaque and unfaded. Toggled via `is-scrolled-x` on\n the shell. */\n.hints-bar-scroller::before {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 32px;\n background: linear-gradient(to right, rgba(12, 14, 16, 1), transparent);\n pointer-events: none;\n z-index: 1;\n opacity: 0;\n transition: opacity 150ms ease;\n}\n\n.hints-bar-shell.is-scrolled-x .hints-bar-scroller::before {\n opacity: 1;\n}\n\n/* Flip button \u2014 chevron that moves the bar between pinned-top and\n pinned-bottom. Sized identically to the close button for symmetry. */\n.hints-bar-flip {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n padding: 0;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 4px;\n color: rgba(220, 220, 220, 0.55);\n cursor: pointer;\n flex: 0 0 auto;\n transition:\n color 120ms ease,\n background 120ms ease,\n border-color 120ms ease;\n}\n\n.hints-bar-flip:hover {\n background: rgba(255, 255, 255, 0.07);\n color: #f3f3f3;\n}\n\n.hints-bar-flip:focus-visible {\n outline: 2px solid #ff6b85;\n outline-offset: 1px;\n}\n\n/* Mode badge \u2014 always visible, leading-edge after the close button. Surfaces\n what context the bar is showing chips for. Uniform brand-red styling\n across all modes so the badge consistently stands out from the hotkey\n chips; the label tells the user which mode they're in. */\n/* Shared typography for the mode badge + per-group section labels: same\n font stack, weight, size, tracking, and uppercase treatment. Each\n consumer adds its own colour and chrome (border, background, etc.). */\n.hints-bar-badge-text {\n display: inline-flex;\n align-items: center;\n height: 18px;\n flex: 0 0 auto;\n font:\n 600 9px/1 'IBM Plex Sans', 'S\xf6hne', system-ui, -apple-system,\n 'Segoe UI Variable Display', 'Segoe UI', sans-serif;\n text-transform: uppercase;\n letter-spacing: 0.14em;\n white-space: nowrap;\n}\n\n.hints-bar-mode-badge {\n padding: 0 7px;\n border-radius: 4px;\n background: rgba(255, 107, 133, 0.18);\n border: 1px solid rgba(255, 107, 133, 0.55);\n color: #ff8b9f;\n}\n\n/* Chip row ------------------------------------------------------------------- */\n\n/* Wrapper groups are transparent in the layout: their children participate\n directly in the shell's flexbox so each chip wraps individually. */\n.hints-bar-chips {\n display: contents;\n}\n\n.hints-bar-divider {\n display: inline-block;\n width: 1px;\n height: 18px;\n background: rgba(255, 255, 255, 0.14);\n flex: 0 0 auto;\n}\n\n/* Grouped chip cluster \u2014 soft glassy \"card\" tint behind the chips that\n reads as cohesion without adding hard borders or competing with the\n chord-pill aesthetic. The wrapper stays layout-neutral (no padding) so\n grouped vs ungrouped chips share identical flex metrics; the tint is\n painted by an absolutely-positioned `::after` that extends slightly\n past the wrapper on all sides.\n\n `z-index: 0` on the wrapper creates a local stacking context so the\n `z-index: -1` background tint stays behind the chips inside the\n wrapper rather than slipping behind the whole shell. */\n.hints-bar-chip-group {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n flex: 0 0 auto;\n position: relative;\n z-index: 0;\n /* Adds horizontal breathing room between adjacent group cards so they\n don't collide given the card's -6px outset on each side. */\n margin: 0 4px;\n}\n\n.hints-bar-chip-group::after {\n content: '';\n position: absolute;\n inset: -3px -6px;\n /* Opaque dark-burgundy card \u2014 calibrated to look like brand-red tint\n applied at 100% over the bar's dark glass, but rendered as a solid\n fill so the group reads as a defined card rather than a haze. */\n background: #2d1c22;\n border: 1px solid rgba(255, 107, 133, 0.42);\n border-radius: 6px;\n /* Soft brand-red drop shadow below \u2014 lifts the card off the bar and\n gives the group a faint colored \"edge\" pointing downward.\n Shadow is rendered outside the element's mask, so it isn't faded. */\n box-shadow: 0 3px 8px -1px rgba(255, 107, 133, 0.32);\n /* Fade the card (background + border) to transparent at the top so\n the chips appear to emerge from the group rather than being boxed\n in. The mask is a vertical gradient \u2014 `transparent` at y=0 (top\n edge) to fully opaque by 50% down. Standard CSS feature, widely\n supported. */\n -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 55%);\n mask-image: linear-gradient(to bottom, transparent 0%, black 55%);\n z-index: -1;\n pointer-events: none;\n}\n\n.hints-bar-group-label {\n color: rgba(255, 139, 159, 0.95);\n}\n\n.hints-bar-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n cursor: help;\n position: relative;\n}\n\n.hints-bar-chip:focus-visible {\n outline: 2px solid #ff6b85;\n outline-offset: 2px;\n border-radius: 4px;\n}\n\n.hints-bar-close:focus-visible {\n outline: 2px solid #ff6b85;\n outline-offset: 1px;\n}\n\n/* Live indicator on hover chips: a tiny amber dot near the chip corner.\n Subtle pulse signals \"this just appeared because of where you're pointing\". */\n.hints-bar-chip--hover::before {\n content: '';\n position: absolute;\n top: -2px;\n left: -3px;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: #ffb74d;\n box-shadow: 0 0 6px rgba(255, 183, 77, 0.65);\n animation: hints-bar-live-pulse 2.4s ease-in-out infinite;\n}\n\n@keyframes hints-bar-live-pulse {\n 0%,\n 100% {\n opacity: 0.65;\n transform: scale(1);\n }\n 50% {\n opacity: 1;\n transform: scale(1.18);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .hints-bar-chip--hover::before {\n animation: none;\n opacity: 0.85;\n }\n}\n\n/* Expandable chip --------------------------------------------------------- */\n\n.hints-bar-chip--expandable {\n position: relative;\n cursor: help;\n outline: none;\n}\n\n.hints-bar-chip--expandable .hints-bar-chord {\n /* slightly tighter to make room for the \"+\" suffix */\n padding-right: 4px;\n}\n\n.hints-bar-chord-suffix {\n display: inline-block;\n margin-left: 2px;\n color: rgba(243, 243, 243, 0.42);\n font-weight: 500;\n font-size: 9px;\n font-family: ui-sans-serif, system-ui, sans-serif;\n letter-spacing: 0;\n line-height: 1;\n transform: translateY(-1px);\n}\n\n.hints-bar-chip--expandable:hover .hints-bar-chord-suffix,\n.hints-bar-chip--expandable:focus-visible .hints-bar-chord-suffix {\n color: rgba(243, 243, 243, 0.85);\n}\n\n/* Popover ----------------------------------------------------------------- */\n\n.hints-bar-popover {\n position: absolute;\n bottom: calc(100% + 10px);\n left: 50%;\n display: flex;\n flex-direction: column;\n min-width: 168px;\n padding: 9px 11px 10px;\n\n border-radius: 8px;\n background: rgba(8, 9, 10, 0.94);\n backdrop-filter: blur(16px) saturate(160%);\n -webkit-backdrop-filter: blur(16px) saturate(160%);\n border: 1px solid rgba(255, 255, 255, 0.10);\n box-shadow:\n 0 16px 36px rgba(0, 0, 0, 0.6),\n 0 1px 0 rgba(255, 255, 255, 0.06) inset;\n\n pointer-events: none;\n opacity: 0;\n /* `--popover-shift-x` is set by JS in `adjustPopoverPosition()` on chip\n hover/focus. Defaults to 0 so popovers stay centered when there's\n room; gets adjusted positive (shift right) when the chip is near the\n scroller's left edge, negative (shift left) when near the right edge.\n Keeps popovers inside the scroller's `overflow-x: clip` bounds. */\n --popover-shift-x: 0px;\n transform: translate(calc(-50% + var(--popover-shift-x)), 4px);\n transition:\n opacity 140ms ease,\n transform 140ms ease;\n transition-delay: 0ms;\n z-index: 2;\n}\n\n.hints-bar-chip--expandable:hover .hints-bar-popover,\n.hints-bar-chip--expandable:focus-visible .hints-bar-popover {\n opacity: 1;\n transform: translate(calc(-50% + var(--popover-shift-x)), 0);\n pointer-events: auto;\n transition-delay: 80ms;\n}\n\n/* Tip pointer \u2014 counter-shifts by `--popover-shift-x` so it stays aligned\n with the chip's centerline even when the popover itself is shifted to\n stay within the scroller's clip bounds. */\n.hints-bar-popover::after {\n content: '';\n position: absolute;\n top: 100%;\n left: calc(50% - var(--popover-shift-x));\n width: 0;\n height: 0;\n margin-left: -5px;\n border-left: 5px solid transparent;\n border-right: 5px solid transparent;\n border-top: 5px solid rgba(8, 9, 10, 0.94);\n filter: drop-shadow(0 1px 0 rgba(255, 255, 255, 0.04));\n}\n\n/* Popover direction is chosen by JS based on whether there's room above the\n bar (see `updatePopoverDirection`). Default is above; when the bar's\n center is in the top half of the viewport, JS sets `data-popover-dir='below'`\n and we flip the popover and its tip pointer to flow downward. */\n.hints-bar-shell[data-popover-dir='below'] .hints-bar-popover {\n bottom: auto;\n top: calc(100% + 10px);\n transform: translate(calc(-50% + var(--popover-shift-x)), -4px);\n}\n\n.hints-bar-shell[data-popover-dir='below']\n .hints-bar-chip--expandable:hover\n .hints-bar-popover,\n.hints-bar-shell[data-popover-dir='below']\n .hints-bar-chip--expandable:focus-visible\n .hints-bar-popover {\n transform: translate(calc(-50% + var(--popover-shift-x)), 0);\n}\n\n.hints-bar-shell[data-popover-dir='below'] .hints-bar-popover::after {\n top: auto;\n bottom: 100%;\n border-top: none;\n border-bottom: 5px solid rgba(8, 9, 10, 0.94);\n filter: drop-shadow(0 -1px 0 rgba(255, 255, 255, 0.04));\n}\n\n.hints-bar-popover-header {\n font-size: 9px;\n font-weight: 600;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: rgba(220, 220, 220, 0.55);\n padding-bottom: 6px;\n margin-bottom: 6px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n}\n\n.hints-bar-popover-row {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 2px 0;\n}\n\n.hints-bar-popover-row + .hints-bar-popover-row {\n margin-top: 1px;\n}\n\n.hints-bar-popover .hints-bar-chord {\n min-width: 36px;\n justify-content: center;\n}\n\n.hints-bar-popover-name {\n color: rgba(232, 232, 232, 0.95);\n font-weight: 500;\n font-size: 11px;\n letter-spacing: 0.01em;\n}\n\n/* Chord chip (single kbd per chord) ----------------------------------------- */\n\n.hints-bar-chord {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 2px 6px;\n min-height: 18px;\n box-sizing: border-box;\n\n border-radius: 4px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.10);\n box-shadow:\n 0 1px 0 rgba(255, 255, 255, 0.06) inset,\n 0 -1px 0 rgba(0, 0, 0, 0.25) inset;\n\n color: #f3f3f3;\n font:\n 600 11px/1 'JetBrains Mono', 'Cascadia Code', 'Cascadia Mono', 'Fira Code',\n 'SF Mono', ui-monospace, Menlo, Consolas, monospace;\n letter-spacing: 0.04em;\n}\n\n.hints-bar-key {\n display: inline-block;\n padding: 0 1px;\n}\n\n.hints-bar-key-icon {\n display: inline-block;\n flex: 0 0 auto;\n color: #f3f3f3;\n vertical-align: middle;\n}\n\n.hints-bar-key-sep {\n display: inline-block;\n padding: 0 1px;\n color: rgba(243, 243, 243, 0.40);\n font-weight: 400;\n font-size: 11px;\n line-height: 1;\n}\n\n.hints-bar-mod-icon {\n display: inline-block;\n flex: 0 0 auto;\n /* Modifier glyphs render at 78% white \u2014 secondary to the primary key\n (the letter/arrow/mouse glyph) but still legible. Bumped from 62%\n when glyphs were redesigned with bolder fills; the previous opacity\n made them visually disappear at 11\u201312 px. */\n color: rgba(243, 243, 243, 0.78);\n vertical-align: middle;\n}\n\n/* Optional modifier \u2014 rendered as a modifier glyph wrapped in literal\n parens to signal \"this chord works with or without this modifier\"\n (e.g., `Z` vs `Shift+Z` for paired undo/redo). Used inline within\n a normal chord pill so the chip stays a single unit. */\n.hints-bar-mod-optional {\n display: inline-flex;\n align-items: center;\n flex: 0 0 auto;\n gap: 1px;\n}\n\n.hints-bar-paren {\n color: rgba(243, 243, 243, 0.5);\n font-weight: 400;\n font-size: 11px;\n line-height: 1;\n}\n\n/* Label ---------------------------------------------------------------------- */\n\n.hints-bar-label {\n color: rgba(220, 220, 220, 0.88);\n font-weight: 500;\n font-size: 11px;\n letter-spacing: 0.02em;\n}\n\n/* Hover-driven chips: brighter, slightly more present so the user can tell\n they appeared in response to where the cursor is. */\n.hints-bar-chip--hover .hints-bar-chord {\n background: rgba(255, 255, 255, 0.12);\n border-color: rgba(255, 255, 255, 0.22);\n color: #ffffff;\n}\n\n.hints-bar-chip--hover .hints-bar-mod-icon,\n.hints-bar-chip--hover .hints-bar-key-icon {\n color: #ffffff;\n}\n\n.hints-bar-chip--hover .hints-bar-label {\n color: rgba(255, 255, 255, 0.96);\n font-weight: 600;\n}\n\n/* Close button --------------------------------------------------------------- */\n\n.hints-bar-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n padding: 0;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 4px;\n color: rgba(220, 220, 220, 0.45);\n cursor: pointer;\n transition: color 120ms ease, background 120ms ease, border-color 120ms ease;\n}\n\n.hints-bar-close:hover {\n color: #f3f3f3;\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(255, 255, 255, 0.12);\n}\n\n.hints-bar-close:active {\n background: rgba(255, 255, 255, 0.14);\n}\n\n.hints-bar-close:focus-visible {\n outline: none;\n border-color: rgba(255, 107, 133, 0.7);\n color: #f3f3f3;\n}\n";
const VALID_MODES = [
'pinned-bottom',
'pinned-top'
];
const STORAGE_KEY = 'yt_clipper:hintsBar';
let host = null;
let shell = null;
let state = null;
let activeRegistry = null;
let cssInjected = false;
function loadPersistedState() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
const parsed = JSON.parse(raw);
if (typeof parsed.visible === 'boolean') {
// Older persisted state may have carried a `floating` mode plus
// x/y coordinates; only `pinned-top` / `pinned-bottom` survive.
// Anything else falls back to pinned-bottom.
const mode = parsed.mode !== undefined && VALID_MODES.includes(parsed.mode) ? parsed.mode : 'pinned-bottom';
return {
visible: parsed.visible,
mode
};
}
}
} catch {
// ignore: fall through to default
}
return {
visible: false,
mode: 'pinned-bottom'
};
}
function persistState() {
if (!state) return;
try {
const payload = {
visible: state.visible,
mode: state.mode
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
} catch {
// ignore: best-effort persistence
}
}
/** Sets `data-pin` so the shell's CSS positions it at top or bottom of
* the viewport. The shell is always full-width-pinned now — no floating
* / drag / transform pipeline. */ function applyMode() {
if (!shell || !state) return;
shell.setAttribute('data-pin', state.mode === 'pinned-top' ? 'top' : 'bottom');
updatePopoverDirection();
}
/** Picks popover direction based on whether the bar is pinned to the top
* half of the viewport — when at the top, the chord popovers flow
* downward so they don't clip; otherwise upward. */ function updatePopoverDirection() {
if (!shell || !state) return;
shell.setAttribute('data-popover-dir', state.mode === 'pinned-top' ? 'below' : 'above');
}
/** Flip the bar between pinned-top and pinned-bottom. The chevron button
* in the bar's leading edge invokes this. */ function flipPin() {
if (!state || !shell) return;
state.mode = state.mode === 'pinned-top' ? 'pinned-bottom' : 'pinned-top';
applyMode();
persistState();
rerender();
}
/** Single resize handler — recomputes everything that depends on the
* viewport: scrollbar inset, current scroll offset, fade-overlay flags,
* and chord-popover flow direction. Wired up once in `mountHintsBar`. */ function onWindowResize() {
updateViewportOffsets();
applyScroll();
updateScrollIndicators();
updatePopoverDirection();
}
let lastRenderedContext = null;
let lastRenderedHoverKey = '';
function classifyChip(def, primary, hovers) {
const c = def.hintContexts;
if (!c || c.length === 0) return null;
// Modifier gate: if the chip declares a `whenModifiers` requirement and the
// currently-held modifiers don't satisfy it, suppress entirely.
if (def.whenModifiers && !modifiersSatisfy(def.whenModifiers)) return null;
// `always` wins because those chips should appear in every context.
if (c.includes('always')) return 'always';
if (hovers.some((h)=>c.includes(h))) return 'hover';
if (c.includes(primary)) return 'state';
return null;
}
function modifiersSatisfy(req) {
const held = (0, _modifierTracker.getModifierState)();
if (req.ctrl !== undefined && req.ctrl !== held.ctrl) return false;
if (req.shift !== undefined && req.shift !== held.shift) return false;
if (req.alt !== undefined && req.alt !== held.alt) return false;
return true;
}
/**
* Returns the chips visible right now, applying the focus rule:
* - `always` chips are always shown.
* - If any hover-matching chip exists, `state` chips are SUPPRESSED so the
* user sees a narrow, focused set tied to where the cursor is.
* - Otherwise the broader state chips show as usual.
*/ function getVisibleShortcuts(primary, hovers) {
const reg = activeRegistry;
if (!reg) return [];
const candidates = [];
for (const def of reg.getAll()){
if (!def.hintLabel) continue;
if (def.guard && !def.guard()) continue;
const kind = classifyChip(def, primary, hovers);
if (kind == null) continue;
candidates.push({
def,
kind
});
}
const hasHoverFocus = candidates.some((c)=>c.kind === 'hover');
const visible = candidates.filter((c)=>c.kind === 'always' || c.kind === 'hover' || !hasHoverFocus && c.kind === 'state').map((c)=>c.def);
return visible.sort((a, b)=>{
const ao = a.hintOrder ?? Number.POSITIVE_INFINITY;
const bo = b.hintOrder ?? Number.POSITIVE_INFINITY;
return ao - bo;
});
}
function resolveContexts() {
const primary = (0, _hintContext.getCurrentHintContext)();
const hovers = (0, _hintContext.shouldApplyHoverLayer)(primary) ? (0, _hintContext.getCurrentHoverContexts)() : [];
return {
primary,
hovers
};
}
/**
* Chord-string parser. Splits "Ctrl + Shift + Q" into modifiers and key
* tokens, then dispatches to render helpers in `../icons/glyphs`. The icon
* artwork lives there; this file handles the chord-grammar parsing
* (modifier aliases, optional-modifier parens, alternative-key `/` notation).
*/ const MODIFIER_LOOKUP = {
ctrl: 'ctrl',
control: 'ctrl',
shift: 'shift',
alt: 'alt',
option: 'alt',
meta: 'meta',
cmd: 'meta',
command: 'meta',
super: 'meta',
win: 'meta'
};
/** Canonical render order for chord modifiers — applied regardless of the
* source `displayKey`'s ordering so chip pills read consistently. Ctrl
* comes first as the most common modifier; Meta last since it's rare
* enough to feel "extra". */ const MODIFIER_RENDER_ORDER = {
ctrl: 0,
alt: 1,
shift: 2,
meta: 3
};
function resolveModifier(part) {
const key = part.trim().toLowerCase();
return Object.prototype.hasOwnProperty.call(MODIFIER_LOOKUP, key) ? MODIFIER_LOOKUP[key] : null;
}
const ARROW_DIR_LOOKUP = {
left: 'left',
arrowleft: 'left',
right: 'right',
arrowright: 'right',
up: 'up',
arrowup: 'up',
down: 'down',
arrowdown: 'down'
};
function resolveArrow(part) {
const key = part.trim().toLowerCase();
return Object.prototype.hasOwnProperty.call(ARROW_DIR_LOOKUP, key) ? ARROW_DIR_LOOKUP[key] : null;
}
const MOUSE_TOKEN_LOOKUP = {
click: 'click',
'left-click': 'click',
leftclick: 'click',
'right-click': 'right-click',
rightclick: 'right-click',
mouseover: 'mouseover',
hover: 'mouseover',
mousehover: 'mouseover',
drag: 'drag',
mousewheel: 'mousewheel',
wheel: 'mousewheel',
scroll: 'mousewheel',
// Wheel-click is a distinct gesture: clicking the wheel button (a.k.a.
// middle-click), as opposed to rolling the wheel for scroll input.
'middle-click': 'wheel-click',
middleclick: 'wheel-click',
'wheel-click': 'wheel-click',
wheelclick: 'wheel-click',
'mousewheel-click': 'wheel-click',
mousewheelclick: 'wheel-click'
};
function resolveMouseToken(part) {
const key = part.trim().toLowerCase();
return Object.prototype.hasOwnProperty.call(MOUSE_TOKEN_LOOKUP, key) ? MOUSE_TOKEN_LOOKUP[key] : null;
}
function renderKey(part) {
const arrow = resolveArrow(part);
if (arrow) return (0, _glyphs.renderArrowGlyph)(arrow);
const mouse = resolveMouseToken(part);
if (mouse) return (0, _glyphs.renderMouseGlyph)(mouse);
// `X/Y` notation inside a single chord part — "this chord works with
// either key X or Y" (e.g., `Shift + Q/A` for start/end). Split on `/`
// and render the alternatives separated by a slash glyph, just like the
// existing arrow-key separator.
if (part.includes('/') && part.length > 1) {
const subs = part.split('/').map((p)=>p.trim()).filter((p)=>p.length > 0);
if (subs.length > 1) return (0, _litHtml.html)`${subs.map((s, i)=>(0, _litHtml.html)`${i > 0 ? (0, _litHtml.html)`<span class="hints-bar-key-sep" aria-hidden="true">/</span>` : (0, _litHtml.nothing)}${renderKey(s)}`)}`;
}
return (0, _litHtml.html)`<span class="hints-bar-key">${part}</span>`;
}
function renderChordContents(displayKey) {
if (!displayKey) return 0, _litHtml.nothing;
const parts = displayKey.split(/\s*\+\s*/).map((p)=>p.trim()).filter((p)=>p.length > 0);
if (parts.length === 0) return 0, _litHtml.nothing;
// A part wrapped in parens — e.g. `(Shift)` — is treated as an OPTIONAL
// modifier. The chord renders it with literal parens around the glyph so
// the user reads "this key is the same chord with or without (X)".
// Useful for paired actions like undo / redo (Z / Shift+Z).
const modifiers = [];
const keys = [];
for (const part of parts){
const optional = part.startsWith('(') && part.endsWith(')');
const cleanPart = optional ? part.slice(1, -1).trim() : part;
const mod = resolveModifier(cleanPart);
if (mod) modifiers.push({
kind: mod,
optional
});
else keys.push(part);
}
// Normalize modifier order to `Ctrl + Alt + Shift + Meta` regardless of
// the source displayKey's order — chord pills should read consistently
// across the bar even if shortcut definitions write modifiers in
// different orders.
modifiers.sort((a, b)=>MODIFIER_RENDER_ORDER[a.kind] - MODIFIER_RENDER_ORDER[b.kind]);
const keyNodes = [];
keys.forEach((k, idx)=>{
if (idx > 0 && resolveArrow(k) && resolveArrow(keys[idx - 1])) keyNodes.push((0, _litHtml.html)`<span class="hints-bar-key-sep" aria-hidden="true">/</span>`);
keyNodes.push(renderKey(k));
});
return (0, _litHtml.html)`${modifiers.map((m)=>m.optional ? renderOptionalModifier(m.kind) : (0, _glyphs.renderModifierGlyph)(m.kind))}
${keyNodes}`;
}
function renderOptionalModifier(mod) {
return (0, _litHtml.html)`<span class="hints-bar-mod-optional">
<span class="hints-bar-paren" aria-hidden="true">(</span>
${(0, _glyphs.renderModifierGlyph)(mod)}
<span class="hints-bar-paren" aria-hidden="true">)</span>
</span>`;
}
function renderChord(displayKey) {
const contents = renderChordContents(displayKey);
if (contents === (0, _litHtml.nothing)) return 0, _litHtml.nothing;
return (0, _litHtml.html)`<kbd class="hints-bar-chord">${contents}</kbd>`;
}
function isHoverChip(def) {
return def.hintContexts?.some((c)=>c.startsWith('hover-')) ?? false;
}
function chipClasses(def, extra = '') {
const hoverClass = isHoverChip(def) ? ' hints-bar-chip--hover' : '';
return `hints-bar-chip${extra}${hoverClass}`;
}
function renderChip(def) {
const primaryKey = def.hintDisplayKey ?? def.displayKey;
if (def.hintExpandedHelp && def.hintExpandedHelp.length > 0) return (0, _litHtml.html)`
<span
class=${chipClasses(def, ' hints-bar-chip--expandable')}
title=${def.description}
tabindex="0"
>
<kbd class="hints-bar-chord">
${renderChordContents(primaryKey)}
<span class="hints-bar-chord-suffix" aria-hidden="true">+</span>
</kbd>
<span class="hints-bar-label">${def.hintLabel}</span>
<span class="hints-bar-popover" role="tooltip">
<span class="hints-bar-popover-header">${def.hintLabel}</span>
${def.hintExpandedHelp.map((item)=>(0, _litHtml.html)`
<span class="hints-bar-popover-row">
${renderChord(item.key)}
<span class="hints-bar-popover-name">${item.label}</span>
</span>
`)}
</span>
</span>
`;
return (0, _litHtml.html)`
<span class=${chipClasses(def)} title=${def.description}>
${renderChord(primaryKey)}
<span class="hints-bar-label">${def.hintLabel}</span>
</span>
`;
}
/** Walks the state-chip list and bundles each contiguous run of same-
* `hintGroup` chips into a visible wrapper carrying the group's label and a
* thin accent underline. The wrapper is `inline-flex` so the group floats
* as one atomic unit in the bar's flex layout — chips inside never wrap
* away from their label. Ungrouped chips render bare.
*
* When a group's name matches the active mode badge (case-insensitive),
* the label text is suppressed — the badge already conveys "MARKERS" /
* "CROP" / etc., so repeating it inside the lane wastes space. The
* wrapper + underline still render, preserving the visual grouping. */ function renderGroupedChips(chips, suppressedGroupLabel) {
const suppressed = suppressedGroupLabel?.toLowerCase();
const nodes = [];
let i = 0;
while(i < chips.length){
const chip = chips[i];
const group = chip.hintGroup;
if (!group) {
nodes.push(renderChip(chip));
i += 1;
continue;
}
const groupChips = [];
while(i < chips.length && chips[i].hintGroup === group){
groupChips.push(chips[i]);
i += 1;
}
const hideLabel = suppressed != null && group.toLowerCase() === suppressed;
nodes.push((0, _litHtml.html)`
<span class="hints-bar-chip-group" role="group" aria-label=${group}>
${hideLabel ? (0, _litHtml.nothing) : (0, _litHtml.html)`<span class="hints-bar-badge-text hints-bar-group-label" aria-hidden="true"
>${group}</span
>`}
${groupChips.map(renderChip)}
</span>
`);
}
return nodes;
}
/** Maps the current context to a short, all-caps badge label that surfaces
* "you're in this mode right now". Uniform styling across all modes so the
* badge consistently stands out from the hotkey chips — the label itself
* tells the user what state they're in.
*
* Hover contexts over charts take precedence over the resting primary
* (default / marker-selected) because hovering a chart is the user's
* active focus — same reason chart-specific hover chips replace the
* state chips. Mid-action primaries (drawing/dragging/resizing/editors)
* always win because the hover layer is suppressed during them anyway. */ function getModeBadgeLabel(primary, hovers) {
// Mid-action primaries override hover — they suppress the hover layer
// anyway (see HOVER_SUPPRESSING_PRIMARIES), so badge follows suit.
switch(primary){
case 'crop-drawing':
return 'Draw';
case 'crop-dragging':
return 'Drag';
case 'crop-resizing':
return 'Resize';
}
// Chart hovers — point variants take precedence over their canvas parent.
if (hovers.includes('hover-speed-chart-point')) return 'Speed Pt';
if (hovers.includes('hover-crop-chart-point')) return 'Crop Pt';
if (hovers.includes('hover-speed-chart')) return 'Speed';
if (hovers.includes('hover-crop-chart-zoompan')) return 'Zoom/Pan';
if (hovers.includes('hover-crop-chart')) return 'Crop Chart';
// Video region — crop is a SUB-region inside video, so when both fire
// (cursor over the crop overlay) prefer the more specific "Video: Crop"
// label so the user can tell which sub-context they're in.
if (hovers.includes('crop-input-focused')) return 'Crop Input';
if (hovers.includes('hover-crop')) return 'Crop';
if (hovers.includes('hover-video')) return 'Video';
// Timeline — the progress-bar hover surfaces marker operations on the
// time axis. Distinct from the resting `marker-selected` ("MARKERS")
// primary so the user isn't seeing the same badge for two genuinely
// different contexts.
if (hovers.includes('hover-progress-bar')) return 'Timeline';
// Editor primaries surface only when no hover overrides them — they
// don't suppress hover (the user can still interact with video / crop /
// charts), so the more-specific hover label wins when applicable.
if (primary === 'global-editor') return 'Global';
if (primary === 'marker-editor') return 'Pair Edit';
// Resting fallback.
if (primary === 'marker-selected') return 'Markers';
return 'Default';
}
function renderBar() {
const { primary, hovers } = resolveContexts();
lastRenderedContext = primary;
lastRenderedHoverKey = hovers.join(',');
const shortcuts = getVisibleShortcuts(primary, hovers);
const hoverChips = shortcuts.filter(isHoverChip);
const stateChips = shortcuts.filter((s)=>!isHoverChip(s) && !s.hintContexts?.includes('always'));
const persistent = shortcuts.filter((s)=>s.hintContexts?.includes('always'));
const atTop = state?.mode === 'pinned-top';
const modeBadgeLabel = getModeBadgeLabel(primary, hovers);
return (0, _litHtml.html)`
<span class="hints-bar-controls" aria-hidden="false">
<button
type="button"
class="hints-bar-flip"
title=${atTop ? 'Move hints bar to bottom' : 'Move hints bar to top'}
aria-label=${atTop ? 'Move hints bar to bottom' : 'Move hints bar to top'}
@click=${onFlipClick}
>
${(0, _glyphs.renderUIIcon)(atTop ? 'chevronDown' : 'chevronUp', 14)}
</button>
<button
type="button"
class="hints-bar-close"
title="Hide hints bar (Alt+F)"
aria-label="Hide hints bar"
@click=${onCloseClick}
>
${(0, _glyphs.renderUIIcon)('close', 14)}
</button>
</span>
<span
class="hints-bar-badge-text hints-bar-mode-badge"
role="status"
aria-label=${`Current mode: ${modeBadgeLabel}`}
>${modeBadgeLabel}</span
>
<div class="hints-bar-scroller">
<div class="hints-bar-scroller-inner">
${hoverChips.length > 0 ? (0, _litHtml.html)`<span
class="hints-bar-chips hints-bar-chips--hover"
role="toolbar"
aria-label="Hover shortcuts"
>
${renderGroupedChips(hoverChips, modeBadgeLabel)}
</span>` : (0, _litHtml.nothing)}
${hoverChips.length > 0 && stateChips.length > 0 ? (0, _litHtml.html)`<span class="hints-bar-divider" aria-hidden="true"></span>` : (0, _litHtml.nothing)}
<span
class="hints-bar-chips hints-bar-chips--contextual"
role="toolbar"
aria-label="Contextual shortcuts"
>
${renderGroupedChips(stateChips, modeBadgeLabel)}
</span>
${persistent.length > 0 ? (0, _litHtml.html)`<span class="hints-bar-divider" aria-hidden="true"></span>
<span
class="hints-bar-chips hints-bar-chips--persistent"
role="toolbar"
aria-label="Persistent shortcuts"
>
${persistent.map(renderChip)}
</span>` : (0, _litHtml.nothing)}
</div>
</div>
`;
}
function onCloseClick(e) {
e.stopPropagation();
if (handle) handle.setVisible(false);
}
function onFlipClick(e) {
e.stopPropagation();
flipPin();
}
function rerender() {
if (!shell) return;
(0, _litHtml.render)(renderBar(), shell);
// Chip set just changed — overflow state may have changed and the inner
// wrapper was just re-templated, so re-apply the current scroll offset
// (clamped to new bounds) and refresh fade indicators.
applyScroll();
updateScrollIndicators();
}
/** Manual horizontal "scrolling" implemented as `transform: translateX()`
* on the inner chip-wrapper. We can't use native `overflow-x: auto` on the
* shell because that would coerce `overflow-y` to `auto` per CSS spec and
* clip chord-popovers extending above/below the shell. */ let scrollX = 0;
function getScroller() {
if (!shell) return null;
const scroller = shell.querySelector('.hints-bar-scroller');
const inner = scroller?.querySelector('.hints-bar-scroller-inner');
if (!scroller || !inner) return null;
return {
scroller,
inner
};
}
function getMaxScroll() {
const els = getScroller();
if (!els) return 0;
return Math.max(0, els.inner.offsetWidth - els.scroller.clientWidth);
}
function applyScroll() {
const els = getScroller();
if (!els) return;
const max = getMaxScroll();
if (scrollX > max) scrollX = max;
if (scrollX < 0) scrollX = 0;
els.inner.style.transform = `translateX(${-scrollX}px)`;
}
function setScrollX(x) {
scrollX = x;
applyScroll();
updateScrollIndicators();
}
let rafId = null;
let lastRenderedModifierKey = '';
function tick() {
if (state?.visible && shell) {
const { primary, hovers } = resolveContexts();
const hoverKey = hovers.join(',');
const mods = (0, _modifierTracker.getModifierState)();
const modKey = `${mods.ctrl ? 'C' : ''}${mods.shift ? 'S' : ''}${mods.alt ? 'A' : ''}`;
if (primary !== lastRenderedContext || hoverKey !== lastRenderedHoverKey || modKey !== lastRenderedModifierKey) {
lastRenderedModifierKey = modKey;
rerender();
}
}
rafId = requestAnimationFrame(tick);
}
function startTicking() {
if (rafId !== null) return;
rafId = requestAnimationFrame(tick);
}
/** Wires the mouse wheel to horizontally scroll the bar via the
* transform-based scroller. Mouse wheels default to vertical, so
* `deltaY` is converted to horizontal scroll. */ function attachWheelScroll(target) {
target.addEventListener('wheel', (e)=>{
if (getMaxScroll() <= 0) return; // nothing to scroll
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
// Touchpad horizontal swipe — honor it directly.
e.preventDefault();
setScrollX(scrollX + e.deltaX);
return;
}
e.preventDefault();
setScrollX(scrollX + e.deltaY);
}, {
passive: false
});
}
/** Click-and-drag horizontal scrolling — same effect as wheel scrolling
* but pointer-driven. Skipped when the pointerdown landed on an
* interactive element (button, expandable chip) so chip clicks /
* popovers still work. */ function attachDragToScroll(target) {
const DRAG_THRESHOLD_PX = 4;
let pointerId = null;
let startClientX = 0;
let startScrollX = 0;
let crossedThreshold = false;
target.addEventListener('pointerdown', (e)=>{
if (e.button !== 0) return;
if (e.target.closest('button, a, input, [tabindex]')) return;
pointerId = e.pointerId;
startClientX = e.clientX;
startScrollX = scrollX;
crossedThreshold = false;
});
target.addEventListener('pointermove', (e)=>{
if (pointerId === null || e.pointerId !== pointerId) return;
const dx = e.clientX - startClientX;
if (!crossedThreshold && Math.abs(dx) < DRAG_THRESHOLD_PX) return;
if (!crossedThreshold) {
target.setPointerCapture(e.pointerId);
crossedThreshold = true;
}
setScrollX(startScrollX - dx);
});
const end = (e)=>{
if (e.pointerId !== pointerId) return;
if (crossedThreshold && target.hasPointerCapture(e.pointerId)) target.releasePointerCapture(e.pointerId);
pointerId = null;
crossedThreshold = false;
};
target.addEventListener('pointerup', end);
target.addEventListener('pointercancel', end);
}
/** Sets `is-scrolled-x` / `can-scroll-right` classes on the shell so the
* left/right fade overlays appear only when there's actually content
* scrolled off-screen on that side. */ function updateScrollIndicators() {
if (!shell) return;
const max = getMaxScroll();
shell.classList.toggle('is-scrolled-x', scrollX > 0);
shell.classList.toggle('can-scroll-right', scrollX < max - 1);
}
/** Push the shell's right edge inboard of the page's vertical scrollbar.
* The host is `position: fixed; inset: 0`, which in Chromium extends to
* the viewport edges *including* the scrollbar — so without this offset
* the rightmost slice of the bar (last chip, fade overlay) renders under
* the scrollbar and is visibly clipped. `window.innerWidth -
* documentElement.clientWidth` is the canonical scrollbar-width probe;
* it returns 0 when no scrollbar is shown (overlay scrollbars, full-screen,
* etc.) so the offset auto-disables on platforms that don't need it. */ function updateViewportOffsets() {
if (!host) return;
const scrollbarWidth = Math.max(0, window.innerWidth - document.documentElement.clientWidth);
host.style.setProperty('--scrollbar-offset', `${scrollbarWidth}px`);
}
function ensureMounted() {
if (host && shell) return;
if (!cssInjected) {
(0, _util.injectCSS)(hintsBarCSS, 'hints-bar-css');
cssInjected = true;
}
host = document.createElement('div');
host.className = 'hints-bar-host';
shell = document.createElement('div');
shell.className = 'hints-bar-shell';
host.appendChild(shell);
document.body.appendChild(host);
updateViewportOffsets();
attachWheelScroll(shell);
attachDragToScroll(shell);
attachInspectionFreeze(shell);
attachFocusGuard(shell);
attachPopoverPositioner(shell);
}
/** Prevent the bar from stealing focus on click. Without this, clicking any
* button or focusable chip inside the bar blurs whatever element (e.g.,
* the crop input) was previously focused — and `crop-input-focused`
* hover-layer context drops out, so the user-visible chip set flips
* context just because they clicked on the bar. `mousedown.preventDefault()`
* is the canonical way to suppress the focus shift while still letting the
* click event fire on `mouseup`, so button onclick handlers continue to
* work normally. */ function attachFocusGuard(target) {
target.addEventListener('mousedown', (e)=>{
e.preventDefault();
});
}
/** Adjusts the `--popover-shift-x` CSS variable on an expandable chip's
* popover so it stays inside the scroller's horizontal bounds. The
* scroller has `overflow-x: clip`, so a centered popover near the left
* or right edge of the visible scrollport would be cut off — shifting
* it horizontally keeps the whole popover inside. The chip's tip pointer
* counter-shifts via the same variable (see hints-bar.css) so it still
* points at the chip. */ function adjustPopoverPosition(chip) {
const popover = chip.querySelector('.hints-bar-popover');
if (!popover) return;
const scrollerEl = chip.closest('.hints-bar-scroller');
if (!scrollerEl) return;
const popoverRect = popover.getBoundingClientRect();
if (popoverRect.width === 0) return; // not yet laid out
const chipRect = chip.getBoundingClientRect();
const scrollerRect = scrollerEl.getBoundingClientRect();
const PADDING = 6;
const chipCenter = chipRect.left + chipRect.width / 2;
const desiredLeft = chipCenter - popoverRect.width / 2;
const minLeft = scrollerRect.left + PADDING;
const maxLeft = scrollerRect.right - popoverRect.width - PADDING;
const clampedLeft = Math.max(minLeft, Math.min(desiredLeft, maxLeft));
const shiftX = clampedLeft - desiredLeft;
popover.style.setProperty('--popover-shift-x', `${shiftX}px`);
}
function attachPopoverPositioner(target) {
const handler = (e)=>{
const chip = e.target.closest('.hints-bar-chip--expandable');
if (chip) adjustPopoverPosition(chip);
};
target.addEventListener('mouseover', handler);
target.addEventListener('focusin', handler);
}
/** Wired in `ensureMounted()`. When the cursor actually enters the bar we
* freeze the hover region so the chip set locks while the user inspects
* it; leaving the bar unfreezes. The longer `HOVER_GRACE_MS` in
* `hover-region.ts` gives the user time to transit from a region toward
* the bar without losing the contextual chips on the way. */ function attachInspectionFreeze(target) {
target.addEventListener('mouseenter', (0, _hoverRegion.freezeHover));
target.addEventListener('mouseleave', (0, _hoverRegion.unfreezeHover));
}
let barEnabled = true;
function applyVisibility() {
if (!shell || !state) return;
const shouldShow = state.visible && barEnabled;
if (shouldShow) {
shell.classList.remove('is-hidden');
applyMode();
rerender();
} else {
shell.classList.add('is-hidden');
// Bar's about to be invisible — if the cursor happens to be on it,
// its `mouseleave` won't necessarily fire before the next region
// change, so release any active freeze proactively.
(0, _hoverRegion.unfreezeHover)();
}
}
function setVisible(visible) {
if (!shell || !state) return;
state.visible = visible;
persistState();
applyVisibility();
}
function setHintsBarEnabled(enabled) {
if (barEnabled === enabled) return;
barEnabled = enabled;
applyVisibility();
}
let handle = null;
function mountHintsBar(registry) {
activeRegistry = registry;
if (handle) return handle;
state = loadPersistedState();
ensureMounted();
applyMode();
rerender();
setVisible(state.visible);
(0, _modifierTracker.startModifierTracker)();
(0, _inputFocus.startInputFocusTracker)();
startTicking();
window.addEventListener('resize', onWindowResize, {
passive: true
});
handle = {
setVisible,
isVisible: ()=>state?.visible ?? false,
rerender,
getState: ()=>state
};
return handle;
}
function getHintsBarHandle() {
return handle;
}
function toggleHintsBar() {
if (!handle) return;
handle.setVisible(!handle.isVisible());
}
},{"fs":"1LEPD","lit-html":"9fQBw","../../util/util":"99arg","../icons/glyphs":"273gO","./hint-context":"cUdnW","./hover-region":"l2Fo9","./input-focus":"3PKwS","./modifier-tracker":"30Xzd","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"cUdnW":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
/**
* Read-only observers of app state. The hints bar combines two signals:
*
* 1. `getCurrentHintContext()` — the single "primary context" derived from
* a priority cascade over app state (mid-action > modal > selection >
* default). This represents the user's PRIMARY MODE.
* 2. `getCurrentHoverContext()` — which page region the mouse is over,
* translated to a hover-* context tag. ADDITIVE on top of the primary:
* hovered region adds region-specific chips to the visible set.
*
* When the primary context is mid-action (e.g. drawing a crop) or a modal
* editor is open, the hover layer is SUPPRESSED — the user's attention is
* focused and we don't want hover-driven swaps to add noise.
*/ parcelHelpers.export(exports, "getCurrentHintContext", ()=>getCurrentHintContext);
parcelHelpers.export(exports, "shouldApplyHoverLayer", ()=>shouldApplyHoverLayer);
/**
* Translates the raw hovered region into HintContext tags. Point-hover REPLACES
* (does not stack on top of) canvas-hover — when the cursor is over an existing
* chart point, only the `-point` context fires, so chips advertising
* canvas-level actions (add a new point, draw a new crop in zoompan mode)
* don't clutter the point-focused chip set. Canvas-level chips that DO remain
* useful while over a point — e.g. zoom / reset zoom — must tag the point
* context explicitly.
*
* The `crop` region still stacks `hover-video` underneath because the crop
* overlay is a child of the video region and the user's mental model is
* "I'm hovering both" — unlike a chart point, where "I'm interacting with this
* point" supersedes "I'm somewhere in the chart".
*
* Also folds in any active "focused input" context (additive layer driven
* by which form element has keyboard focus, not by cursor position).
*/ parcelHelpers.export(exports, "getCurrentHoverContexts", ()=>getCurrentHoverContexts);
var _appState = require("../../appState");
var _cropOverlay = require("../../crop-overlay");
var _hoverRegion = require("./hover-region");
var _inputFocus = require("./input-focus");
function getCurrentHintContext() {
// Mid-action modes take priority — they describe what the user is doing
// RIGHT NOW, and the chip set should change to "what comes next" in that
// workflow rather than the broader selection-level shortcuts.
if (0, _cropOverlay.isDrawingCrop) return 'crop-drawing';
if ((0, _cropOverlay.cropManipulationKind) === 'drag') return 'crop-dragging';
if ((0, _cropOverlay.cropManipulationKind) === 'resize') return 'crop-resizing';
// Editor panels — global editor edits the new-marker defaults; the pair
// editor edits the currently-selected pair. Both surface a distinct chip
// set but unlike the mid-action primaries they DON'T suppress the hover
// layer (the user can still hover the crop, charts, etc. and get the
// usual region-specific chips).
if ((0, _appState.appState).isSettingsEditorOpen && (0, _appState.appState).wasGlobalSettingsEditorOpen) return 'global-editor';
// 'marker-selected' fires only while the pair editor panel is actually
// open. `appState.prevSelectedMarkerPairIndex` is preserved across editor
// toggles (so re-toggling reopens the same pair) and would otherwise
// strand the bar in marker-selected after the user has deselected.
if ((0, _appState.appState).isSettingsEditorOpen && !(0, _appState.appState).wasGlobalSettingsEditorOpen && (0, _appState.appState).prevSelectedMarkerPairIndex != null) return 'marker-selected';
return 'default';
}
const HOVER_SUPPRESSING_PRIMARIES = new Set([
'crop-drawing',
'crop-dragging',
'crop-resizing'
]);
function shouldApplyHoverLayer(primary) {
return !HOVER_SUPPRESSING_PRIMARIES.has(primary);
}
function getCurrentHoverContexts() {
const contexts = regionHoverContexts();
const focused = (0, _inputFocus.getFocusedInputContext)();
if (focused) contexts.push(focused);
return contexts;
}
function regionHoverContexts() {
const region = (0, _hoverRegion.getHoveredRegion)();
if (region == null) return [];
switch(region){
case 'video':
return [
'hover-video'
];
case 'crop':
return [
'hover-video',
'hover-crop'
];
case 'progress-bar':
return [
'hover-progress-bar'
];
case 'speed-chart':
return [
'hover-speed-chart'
];
case 'speed-chart-point':
return [
'hover-speed-chart-point'
];
case 'crop-chart':
return [
isCropChartZoomPanActive() ? 'hover-crop-chart-zoompan' : 'hover-crop-chart'
];
case 'crop-chart-point':
return [
'hover-crop-chart-point'
];
}
}
function isCropChartZoomPanActive() {
const idx = (0, _appState.appState).prevSelectedMarkerPairIndex;
if (idx == null) return false;
const pair = (0, _appState.appState).markerPairs[idx];
if (!pair) return false;
return pair.enableZoomPan;
}
},{"../../appState":"g0AlP","../../crop-overlay":"6s727","./hover-region":"l2Fo9","./input-focus":"3PKwS","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"3PKwS":[function(require,module,exports,__globalThis) {
/**
* Tracks which form input in the settings editor currently has keyboard
* focus, so the hints bar can surface input-specific shortcuts as a
* hover-layer context.
*
* Listens once on `document` for focusin / focusout (events bubble) and
* compares the focused element against the singleton input references
* exported by `settings-editor`. Re-checks identity on every event, so
* an input element being replaced (editor reopened) doesn't strand us.
*/ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "startInputFocusTracker", ()=>startInputFocusTracker);
parcelHelpers.export(exports, "getFocusedInputContext", ()=>getFocusedInputContext);
var _settingsEditor = require("../settings/settings-editor");
let cropInputFocused = false;
/** True when the focus event targets the current `cropInput` element.
* `cropInput` is null before the settings editor is mounted and gets
* reassigned each time the editor reopens, so we identity-compare
* on every event rather than cache. */ function isCropInputEvent(e) {
return (0, _settingsEditor.cropInput) != null && e.target === (0, _settingsEditor.cropInput);
}
function onFocusIn(e) {
if (isCropInputEvent(e)) cropInputFocused = true;
}
function onFocusOut(e) {
if (isCropInputEvent(e)) cropInputFocused = false;
}
let started = false;
function startInputFocusTracker() {
if (started) return;
started = true;
document.addEventListener('focusin', onFocusIn, {
passive: true
});
document.addEventListener('focusout', onFocusOut, {
passive: true
});
}
function getFocusedInputContext() {
return cropInputFocused ? 'crop-input-focused' : null;
}
},{"../settings/settings-editor":"jDViX","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"30Xzd":[function(require,module,exports,__globalThis) {
/**
* Tracks which keyboard modifiers (Ctrl/Shift/Alt) are currently held.
*
* The hints bar consults `getModifierState()` per frame in its RAF loop and
* filters chips whose `whenModifiers` requirement no longer matches. This
* lets the bar reveal/hide modifier-conditioned hints in real time.
*
* - Listeners run in capture phase so we see the keys before other handlers.
* - `window.blur` and `visibilitychange` reset the state to handle Alt-Tab /
* browser-switch (where the keyup never fires).
*/ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "getModifierState", ()=>getModifierState);
parcelHelpers.export(exports, "startModifierTracker", ()=>startModifierTracker);
let state = {
ctrl: false,
shift: false,
alt: false
};
let started = false;
function getModifierState() {
return state;
}
function startModifierTracker() {
if (started) return;
started = true;
document.addEventListener('keydown', onKey, true);
document.addEventListener('keyup', onKey, true);
window.addEventListener('blur', reset);
document.addEventListener('visibilitychange', onVisibilityChange);
}
function onKey(e) {
state = {
ctrl: e.ctrlKey,
shift: e.shiftKey,
alt: e.altKey
};
}
function onVisibilityChange() {
if (document.hidden) reset();
}
function reset() {
state = {
ctrl: false,
shift: false,
alt: false
};
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"2Fvl0":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "createShortcutDefinitions", ()=>createShortcutDefinitions);
var _featureFlags = require("./feature-flags");
function createShortcutDefinitions(deps) {
const markerGuard = deps.isMarkerHotkeysEnabled;
const theatre = deps.isTheatreMode;
const cropAdjDisabled = deps.isArrowKeyCropAdjustmentDisabled;
const hasMarkerPairs = deps.hasMarkerPairs;
return [
// ===== Markup / General Shortcuts =====
{
id: 'toggleShortcuts',
description: 'Toggle shortcuts on/off',
displayKey: 'Alt + Shift + A',
section: 'Markup',
category: 'General Shortcuts',
essential: true,
binding: null,
handler: null,
executable: false
},
{
id: 'toggleCommandPalette',
description: 'Open command palette (search all shortcuts)',
displayKey: 'Shift + E',
section: 'Markup',
category: 'General Shortcuts',
essential: true,
binding: null,
handler: null,
executable: false,
hintLabel: 'Cmds',
hintContexts: [
'always'
],
hintOrder: 10
},
{
id: 'toggleHintsBar',
description: 'Toggle contextual shortcut hints bar',
displayKey: 'Alt + F',
section: 'Markup',
category: 'General Shortcuts',
essential: true,
binding: {
code: 'KeyF',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: ()=>{
deps.toggleHintsBar();
},
executable: true
},
{
id: 'showShortcutsReference',
description: 'Toggle full shortcuts reference table',
displayKey: '',
section: 'Markup',
category: 'General Shortcuts',
essential: false,
binding: null,
handler: ()=>{
deps.showShortcutsReference();
},
executable: true
},
// ===== Markup / Marker Editing Shortcuts =====
{
id: 'addMarker',
description: 'Add marker at current time. During an active crop manipulation (pan-drag or resize) with the crop chart visible, instead drops a crop keyframe at the current time so multiple keyframes can be placed in one continuous gesture.',
displayKey: 'A',
section: 'Markup',
category: 'Marker Editing Shortcuts',
essential: true,
binding: {
code: 'KeyA',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
deps.addMarker();
},
executable: true,
hintLabel: 'Add',
hintContexts: [
'default',
'marker-selected'
],
hintOrder: 100,
hintGroup: 'Markers'
},
{
id: 'addCropChartPointDuringDrag',
description: 'Drop a crop keyframe at the current time without releasing the crop manipulation (pan or resize). The currently-held point reverts to its last drop position; the new point becomes the live-edit target. For resize the per-keyframe variation is mostly meaningful in zoompan mode (pan-only mode keeps W/H equal across all points).',
displayKey: 'A',
section: 'Dynamic Effects',
category: 'Crop Chart Shortcuts',
essential: false,
// No binding — the routing happens in the `addMarker` dep, which
// detects the active crop manipulation (pan or resize) and
// dispatches to `addChartPoint` instead of `addMarker`. This
// entry only exists to surface the contextual chip in the hints
// bar during either manipulation kind.
binding: null,
handler: null,
executable: false,
hintLabel: 'Add Pt',
hintContexts: [
'crop-dragging',
'crop-resizing'
],
hintOrder: 5,
guard: deps.isInCropManipulationWithCropChart
},
{
id: 'toggleEndMarkerEditor',
description: "Hold Shift while hovering a marker's number to open its pair editor (no seek)",
displayKey: 'Shift + Mouseover',
section: 'Markup',
category: 'Marker Editing Shortcuts',
essential: true,
binding: null,
handler: null,
executable: false,
hintLabel: 'Open Pair (No Seek)',
hintContexts: [
'hover-progress-bar'
],
hintOrder: 20
},
{
id: 'openMarkerPairEditor',
description: "Click a marker pair's number on the progress bar to seek to it and open the pair editor",
displayKey: 'Click',
section: 'Markup',
category: 'Marker Editing Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Seek + Open Pair',
hintContexts: [
'hover-progress-bar'
],
hintOrder: 10,
guard: hasMarkerPairs
},
{
id: 'toggleMarkerPairOverridesEditor',
description: 'Toggle marker pair overrides editor',
displayKey: 'Shift + W',
section: 'Markup',
category: 'Marker Editing Shortcuts',
essential: false,
binding: {
code: 'KeyW',
modifiers: {
ctrl: false,
shift: true,
alt: false
}
},
handler: ()=>{
deps.toggleMarkerPairOverridesEditor();
},
executable: true,
hintLabel: 'Edit',
hintContexts: [
'marker-selected'
],
hintOrder: 130,
hintGroup: 'Markers'
},
{
id: 'duplicateSelectedMarkerPair',
description: 'Duplicate selected or previously selected marker pair',
displayKey: 'Ctrl + Shift + A',
section: 'Markup',
category: 'Marker Editing Shortcuts',
essential: false,
binding: {
code: 'KeyA',
modifiers: {
ctrl: true,
shift: true,
alt: false
}
},
handler: ()=>{
deps.duplicateSelectedMarkerPair();
},
executable: true
},
{
id: 'undoMarker',
description: 'Undo / redo history: pair add/remove (Z, Shift+Z) and pair edits (Alt+Z, Alt+Shift+Z)',
displayKey: 'Z',
section: 'Markup',
category: 'Marker Editing Shortcuts',
essential: true,
binding: {
code: 'KeyZ',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
deps.undoMarker();
},
executable: true,
// Pair history undo/redo. Shift is the optional modifier — render
// it parenthesized so one chord communicates both Z (undo) and
// Shift+Z (redo) without doubling the chip width.
hintLabel: 'Pair',
hintDisplayKey: '(Shift) + Z',
hintContexts: [
'default',
'marker-selected',
'global-editor'
],
hintOrder: 172,
hintGroup: 'Undo/Redo'
},
{
id: 'redoMarker',
description: 'Redo last marker',
displayKey: 'Shift + Z',
section: 'Markup',
category: 'Marker Editing Shortcuts',
essential: true,
binding: {
code: 'KeyZ',
modifiers: {
ctrl: false,
shift: true,
alt: false
}
},
handler: ()=>{
deps.redoMarker();
},
executable: true
},
{
id: 'deleteSelectedMarkerPair',
description: 'Delete selected marker pair',
displayKey: 'Ctrl + Alt + Shift + Z',
section: 'Markup',
category: 'Marker Editing Shortcuts',
essential: true,
binding: {
code: 'KeyZ',
modifiers: {
ctrl: true,
shift: true,
alt: true
}
},
handler: ()=>{
deps.deleteMarkerPair();
},
guard: markerGuard,
executable: true
},
{
id: 'undoMarkerPairChange',
description: 'Undo time, speed, and crop changes of selected pair',
displayKey: 'Alt + Z',
section: 'Markup',
category: 'Marker Editing Shortcuts',
essential: true,
binding: {
code: 'KeyZ',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: ()=>{
deps.undoMarkerPairChange();
},
executable: true,
// Edit history undo/redo. Only meaningful while a pair is open —
// kept off the bar in default / global-editor. Same parenthesized
// Shift treatment as the Pair chip.
hintLabel: 'Edit',
hintDisplayKey: '(Shift) + Alt + Z',
hintContexts: [
'marker-selected'
],
hintOrder: 174,
hintGroup: 'Undo/Redo'
},
{
id: 'redoMarkerPairChange',
description: 'Redo time, speed, and crop changes of selected pair',
displayKey: 'Alt + Shift + Z',
section: 'Markup',
category: 'Marker Editing Shortcuts',
essential: true,
binding: {
code: 'KeyZ',
modifiers: {
ctrl: false,
shift: true,
alt: true
}
},
handler: ()=>{
deps.redoMarkerPairChange();
},
executable: true
},
// ===== Markup / Marker Timing Shortcuts =====
{
id: 'moveStartMarkerToCurrentTime',
description: "Snap the selected pair's start or end marker to the current playhead",
displayKey: 'Shift + Q',
section: 'Markup',
category: 'Marker Timing Shortcuts',
essential: true,
binding: {
code: 'KeyQ',
modifiers: {
ctrl: false,
shift: true,
alt: false
}
},
handler: ()=>{
deps.moveMarkerToCurrentTime('start');
},
guard: markerGuard,
executable: true,
// Single chip for both Shift+Q (snap start) and Shift+A (snap end).
// `Q/A` chord-key notation (rendered as `Q / A` with a slash glyph)
// communicates the two-key pair under one Shift modifier so the chip
// stays compact while indicating both bindings.
hintLabel: 'Set start/end',
hintDisplayKey: 'Shift + Q/A',
hintContexts: [
'marker-selected'
],
hintOrder: 110,
hintGroup: 'Markers'
},
{
id: 'moveEndMarkerToCurrentTime',
description: 'Move end marker to current time',
displayKey: 'Shift + A',
section: 'Markup',
category: 'Marker Timing Shortcuts',
essential: true,
binding: {
code: 'KeyA',
modifiers: {
ctrl: false,
shift: true,
alt: false
}
},
handler: ()=>{
deps.moveMarkerToCurrentTime('end');
},
guard: markerGuard,
executable: true
},
{
id: 'dragMarkerNumbering',
description: 'Drag start/end marker numbering to new time',
displayKey: 'Alt + Drag',
section: 'Markup',
category: 'Marker Timing Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Drag Marker to Time',
hintContexts: [
'hover-progress-bar'
],
hintOrder: 30
},
{
id: 'moveMarkerByFrame',
description: 'Move start/end marker a frame when on left/right half of window',
displayKey: 'Alt + Shift + Mousewheel',
section: 'Markup',
category: 'Marker Timing Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Nudge Marker by Frame',
hintContexts: [
'hover-progress-bar'
],
hintOrder: 40
},
// ===== Markup / Marker Navigation Shortcuts =====
{
id: 'togglePrevSelectedMarkerPair',
description: 'Toggle marker pair selection',
displayKey: 'Ctrl + Up',
section: 'Markup',
category: 'Marker Navigation Shortcuts',
essential: false,
binding: {
code: 'ArrowUp',
modifiers: {
ctrl: true,
shift: false,
alt: false
}
},
handler: ()=>{
deps.togglePrevSelectedMarkerPair();
},
guard: cropAdjDisabled,
executable: true
},
{
id: 'toggleAutoHideUnselectedMarkerPairs',
description: 'Toggle auto-hiding unselected marker pairs',
displayKey: 'Ctrl + Down',
section: 'Markup',
category: 'Marker Navigation Shortcuts',
essential: false,
binding: {
code: 'ArrowDown',
modifiers: {
ctrl: true,
shift: false,
alt: false
}
},
handler: (e)=>{
deps.toggleAutoHideUnselectedMarkerPairs(e);
},
guard: cropAdjDisabled,
executable: true
},
{
id: 'jumpToNearestPrevMarker',
description: 'Jump to nearest previous marker',
displayKey: 'Ctrl + Left',
section: 'Markup',
category: 'Marker Navigation Shortcuts',
essential: true,
binding: {
code: 'ArrowLeft',
modifiers: {
ctrl: true,
shift: false,
alt: false
}
},
handler: (e)=>{
deps.jumpToNearestMarkerOrPair(e);
},
guard: cropAdjDisabled,
executable: true,
// Anchor chip for the Nav expandable. Renders both arrows on the chord
// so users see they can navigate either direction; the popover details
// the three modes (jump-only / select-only / select+jump).
hintLabel: 'Nav',
hintDisplayKey: 'Ctrl + Left + Right',
// Only surfaced in `default` (no pair selected yet) — once a pair is
// open, navigation is less central than the pair-scoped chips, and
// the user already knows pairs exist.
hintContexts: [
'default'
],
hintOrder: 135,
hintGroup: 'Markers',
hintExpandedHelp: [
{
key: 'Ctrl + Left + Right',
label: 'Jump to nearest marker'
},
{
key: 'Alt + Left + Right',
label: 'Select prev / next pair'
},
{
key: 'Ctrl + Alt + Left + Right',
label: 'Select pair + jump'
}
]
},
{
id: 'jumpToNearestNextMarker',
description: 'Jump to nearest next marker',
displayKey: 'Ctrl + Right',
section: 'Markup',
category: 'Marker Navigation Shortcuts',
essential: true,
binding: {
code: 'ArrowRight',
modifiers: {
ctrl: true,
shift: false,
alt: false
}
},
handler: (e)=>{
deps.jumpToNearestMarkerOrPair(e);
},
guard: cropAdjDisabled,
executable: true
},
{
id: 'selectPrevMarkerPair',
description: 'Select previous marker pair',
displayKey: 'Alt + Left',
section: 'Markup',
category: 'Marker Navigation Shortcuts',
essential: false,
binding: {
code: 'ArrowLeft',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: (e)=>{
deps.jumpToNearestMarkerOrPair(e);
},
guard: cropAdjDisabled,
executable: true
},
{
id: 'selectNextMarkerPair',
description: 'Select next marker pair',
displayKey: 'Alt + Right',
section: 'Markup',
category: 'Marker Navigation Shortcuts',
essential: false,
binding: {
code: 'ArrowRight',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: (e)=>{
deps.jumpToNearestMarkerOrPair(e);
},
guard: cropAdjDisabled,
executable: true
},
{
id: 'selectPrevMarkerPairAndJump',
description: 'Select previous marker pair and jump to start marker',
displayKey: 'Ctrl + Alt + Left',
section: 'Markup',
category: 'Marker Navigation Shortcuts',
essential: false,
binding: {
code: 'ArrowLeft',
modifiers: {
ctrl: true,
shift: false,
alt: true
}
},
handler: (e)=>{
deps.jumpToNearestMarkerOrPair(e);
},
guard: cropAdjDisabled,
executable: true
},
{
id: 'selectNextMarkerPairAndJump',
description: 'Select next marker pair and jump to start marker',
displayKey: 'Ctrl + Alt + Right',
section: 'Markup',
category: 'Marker Navigation Shortcuts',
essential: false,
binding: {
code: 'ArrowRight',
modifiers: {
ctrl: true,
shift: false,
alt: true
}
},
handler: (e)=>{
deps.jumpToNearestMarkerOrPair(e);
},
guard: cropAdjDisabled,
executable: true
},
// ===== Markup / Global Settings Editor =====
{
id: 'toggleGlobalSettingsEditor',
description: 'Toggle global settings editor',
displayKey: 'W',
section: 'Markup',
category: 'Global Settings Editor Shortcuts',
essential: true,
binding: {
code: 'KeyW',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
deps.toggleGlobalSettingsEditor();
},
executable: true,
hintLabel: 'Settings',
hintContexts: [
'default'
],
hintOrder: 200
},
{
id: 'toggleEncodingSettingsEditor',
description: 'Toggle encoding settings editor',
displayKey: 'Shift + W',
section: 'Markup',
category: 'Global Settings Editor Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Encoding',
hintContexts: [
'global-editor'
],
// Ordered after Data group so it doesn't lead the global-editor lane;
// sits at the end as a related-but-distinct editor toggle.
hintOrder: 195
},
{
id: 'updateAllMarkersSpeed',
description: 'Update all markers to default new marker speed',
displayKey: 'Alt + Shift + Q',
section: 'Markup',
category: 'Global Settings Editor Shortcuts',
essential: false,
binding: {
code: 'KeyQ',
modifiers: {
ctrl: false,
shift: true,
alt: true
}
},
handler: ()=>{
deps.updateAllMarkerPairSpeedsToDefault();
},
executable: true,
hintLabel: 'Speed',
hintContexts: [
'global-editor'
],
hintOrder: 150,
hintGroup: 'Apply'
},
{
id: 'updateAllMarkersCrop',
description: 'Update all markers to default new marker crop',
displayKey: 'Alt + Shift + X',
section: 'Markup',
category: 'Global Settings Editor Shortcuts',
essential: false,
binding: {
code: 'KeyX',
modifiers: {
ctrl: false,
shift: true,
alt: true
}
},
handler: ()=>{
deps.updateAllMarkerPairCropsToDefault();
},
executable: true,
hintLabel: 'Crop',
hintContexts: [
'global-editor'
],
hintOrder: 160,
hintGroup: 'Apply'
},
// ===== Markup / Cropping Shortcuts =====
{
id: 'beginDrawingCrop',
description: 'Begin drawing crop',
displayKey: 'X',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: true,
binding: {
code: 'KeyX',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
deps.drawCrop();
},
executable: true,
hintLabel: 'Draw',
// Intentionally not in `hover-crop`: Draw is a "start a new crop"
// action, and surfacing it on crop-hover would split the CROP group
// across the hover and state lanes (Draw classifies as `hover`,
// Preview stays in state), creating two visible CROP groups when only
// one belongs. State-lane visibility via marker-selected/global-editor
// is enough — the user is one glance away from the chip regardless.
hintContexts: [
'marker-selected',
'global-editor'
],
hintOrder: 140,
hintGroup: 'Crop'
},
{
id: 'drawCropMouse',
description: 'Draw crop',
displayKey: 'Click + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: true,
binding: null,
handler: null,
executable: false,
hintLabel: 'Drag to draw',
hintDisplayKey: 'Drag',
hintContexts: [
'crop-drawing'
],
hintOrder: 10
},
{
id: 'drawCropLockAR',
description: 'Lock aspect ratio while drawing crop',
displayKey: 'Alt + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Lock AR',
hintContexts: [
'crop-drawing'
],
hintOrder: 20
},
{
id: 'drawCropFromCenter',
description: 'Resize from center while drawing crop',
displayKey: 'Shift + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'From Center',
hintContexts: [
'crop-drawing'
],
hintOrder: 30
},
{
id: 'cancelDrawingCrop',
description: 'Cancel drawing crop',
displayKey: 'X',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Cancel',
hintContexts: [
'crop-drawing'
],
hintOrder: 40
},
// ===== Mid-manipulation chips (crop-dragging / crop-resizing) =====
// These mirror the modifier behaviors evaluated inside the active
// pointermove handlers. They are pure hints — no binding — surfaced
// once Ctrl+click has already begun a manipulation.
{
id: 'dragCropMove',
description: 'Drag to move crop',
displayKey: 'Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Drag to move',
hintContexts: [
'crop-dragging'
],
hintOrder: 10
},
{
id: 'dragCropVerticalOnly',
description: 'Constrain drag to vertical axis (X locked)',
displayKey: 'Shift + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Vertical only',
hintContexts: [
'crop-dragging'
],
hintOrder: 20
},
{
id: 'dragCropHorizontalOnly',
description: 'Constrain drag to horizontal axis (Y locked)',
displayKey: 'Alt + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Horizontal only',
hintContexts: [
'crop-dragging'
],
hintOrder: 30
},
{
id: 'cropPanZoomWheel',
description: 'Scroll the mouse wheel during a crop pan-drag to scale the crop around its center. Edges expand or contract uniformly so the center stays fixed. In pan-only mode the new size propagates to every crop point (mode invariant); in zoompan mode only the dragged point changes size.',
displayKey: 'Wheel',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Zoom',
hintContexts: [
'crop-dragging'
],
hintOrder: 40
},
{
id: 'resizeCropDrag',
description: 'Drag to resize crop',
displayKey: 'Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Drag to resize',
hintContexts: [
'crop-resizing'
],
hintOrder: 10
},
{
id: 'resizeCropLockAR',
description: 'Lock aspect ratio while resizing crop',
displayKey: 'Alt + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Lock AR',
hintContexts: [
'crop-resizing'
],
hintOrder: 20
},
{
id: 'resizeCropFromCenter',
description: 'Resize crop from center',
displayKey: 'Shift + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'From Center',
hintContexts: [
'crop-resizing'
],
hintOrder: 30
},
{
id: 'moveOrResizeCrop',
description: 'Move or resize crop',
displayKey: 'Ctrl + Click + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: true,
binding: null,
handler: null,
executable: false,
hintLabel: 'Move/Resize',
hintContexts: [
'hover-crop'
],
hintOrder: 10,
hintGroup: 'Resize'
},
{
id: 'cycleCropDimOpacity',
description: 'Cycle crop dim opacity up by 0.25',
displayKey: 'Ctrl + X',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: true,
binding: {
code: 'KeyX',
modifiers: {
ctrl: true,
shift: false,
alt: false
}
},
handler: ()=>{
deps.cycleCropDimOpacity();
},
executable: true,
hintLabel: 'Dim',
hintContexts: [
'hover-crop'
],
hintOrder: 40,
hintGroup: 'Display'
},
{
id: 'toggleCropCrossHair',
description: 'Toggle crop crosshair',
displayKey: 'Ctrl + Shift + X',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: {
code: 'KeyX',
modifiers: {
ctrl: true,
shift: true,
alt: false
}
},
handler: ()=>{
deps.toggleCropCrossHair();
},
executable: true,
hintLabel: 'Crosshair',
hintContexts: [
'hover-crop'
],
hintOrder: 50,
hintGroup: 'Display'
},
{
id: 'cropArLockedResize',
description: 'Crop-aspect-ratio-locked resize/draw of crop',
displayKey: 'Ctrl + Alt + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Lock AR',
hintContexts: [
'hover-crop'
],
hintOrder: 20,
hintGroup: 'Resize'
},
{
id: 'cropCenterOutResize',
description: 'Center-out resize/draw of crop',
displayKey: 'Ctrl + Shift + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'From Center',
hintContexts: [
'hover-crop'
],
hintOrder: 30,
hintGroup: 'Resize'
},
{
id: 'cropYOnlyDrag',
description: 'Horizontally-fixed (Y-only) drag of crop',
displayKey: 'Ctrl + Shift + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false
},
{
id: 'cropXOnlyDrag',
description: 'Vertically-fixed (X-only) drag of crop',
displayKey: 'Ctrl + Alt + Drag',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false
},
{
id: 'toggleArrowKeyCropAdjustment',
description: 'Toggle crop adjustment with arrow keys',
displayKey: 'Alt + X',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: {
code: 'KeyX',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: ()=>{
deps.toggleArrowKeyCropAdjustment();
},
executable: true
},
{
id: 'cropInputArrowAdjust',
description: 'Adjust crop input value at the text cursor with Up/Down arrows',
displayKey: '',
displayNote: 'Place cursor on target value',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
// Up/Down adjust the crop component the text cursor is on
// (x | y | w | h separated by `:` in the crop string). Left/Right
// are NOT intercepted — they're just standard text-cursor movement
// used to position over the target component. The popover documents
// the step-size modifiers from `cropChangeAmount`.
hintLabel: 'Adjust at cursor',
hintDisplayKey: 'Up + Down',
hintContexts: [
'crop-input-focused'
],
hintOrder: 10,
hintGroup: 'Crop Input',
hintExpandedHelp: [
{
key: 'Up + Down',
label: "Adjust the component under the text cursor (\xb110)"
},
{
key: 'Alt + Arrow',
label: "Fine step (\xb11)"
},
{
key: 'Shift + Arrow',
label: "Coarse step (\xb150)"
},
{
key: 'Alt + Shift + Arrow',
label: "Coarsest step (\xb1100)"
}
]
},
{
id: 'cropChangeAmount',
description: 'Change crop change amount from 10 to 1/50/100',
displayKey: 'Alt / Shift / Alt + Shift',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false
},
{
id: 'cropWidthHeightModify',
description: 'Modify crop width/height instead of x/y offset',
displayKey: 'Ctrl + ArrowKey',
section: 'Markup',
category: 'Cropping Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false
},
// ===== Playback & Export / Playback Shortcuts =====
{
id: 'seekFrameByFrame',
description: 'Seek video frame by frame',
displayKey: '< / > or Shift + Mousewheel',
section: 'Playback & Export',
category: 'Playback Shortcuts',
essential: true,
binding: null,
handler: null,
executable: false,
hintLabel: 'Seek',
hintDisplayKey: 'Shift + Wheel',
hintContexts: [
'hover-video'
],
// hintOrder placed in the 210+ range so the VIDEO group renders AFTER
// the more specific CROP group when both fire (cursor over the crop
// overlay, which is a sub-region of the video). Same applies to all
// other Video group chips.
hintOrder: 210,
hintGroup: 'Video',
hintExpandedHelp: [
{
key: 'Shift + Wheel',
label: 'Seek one frame'
},
{
key: '< / >',
label: 'Seek one frame (keyboard)'
}
]
},
{
id: 'cycleSeekRate',
description: 'Cycle seek rate (1-3 frames)',
displayKey: 'Shift + Mousewheel-click',
section: 'Playback & Export',
category: 'Playback Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Seek Rate',
hintDisplayKey: 'Shift + Middle-Click',
hintContexts: [
'hover-video'
],
hintOrder: 220,
hintGroup: 'Video'
},
{
id: 'scrubVideo',
description: 'Scrub video time left or right',
displayKey: 'Alt + Click + Drag',
section: 'Playback & Export',
category: 'Playback Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Scrub',
hintDisplayKey: 'Alt + Drag',
hintContexts: [
'hover-video'
],
hintOrder: 230,
hintGroup: 'Video'
},
{
id: 'toggleMarkerPairSpeedPreview',
description: 'Toggle previewing speed',
displayKey: 'C',
section: 'Playback & Export',
category: 'Playback Shortcuts',
essential: true,
binding: {
code: 'KeyC',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
deps.toggleMarkerPairSpeedPreview();
},
executable: true
},
{
id: 'toggleMarkerPairLoop',
description: 'Toggle auto marker pair looping',
displayKey: 'Shift + C',
section: 'Playback & Export',
category: 'Playback Shortcuts',
essential: true,
binding: {
code: 'KeyC',
modifiers: {
ctrl: false,
shift: true,
alt: false
}
},
handler: ()=>{
deps.toggleMarkerPairLoop();
},
executable: true
},
{
id: 'toggleCropChartLooping',
description: 'Toggle auto crop chart section looping',
displayKey: 'Ctrl + Shift + C',
section: 'Playback & Export',
category: 'Playback Shortcuts',
essential: false,
binding: {
code: 'KeyC',
modifiers: {
ctrl: true,
shift: true,
alt: false
}
},
handler: ()=>{
deps.toggleCropChartLooping();
},
executable: true
},
{
id: 'toggleForceSetSpeed',
description: 'Toggle force setting video speed',
displayKey: 'Q',
section: 'Playback & Export',
category: 'Playback Shortcuts',
essential: false,
binding: {
code: 'KeyQ',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
deps.toggleForceSetSpeed();
},
executable: true,
// Anchor chip for the Q-key playback-speed-override family. The
// `cycleForceSetSpeedValueDown` entry below folds into this chip's
// popover. Hover-video context groups it with the other playback
// overrides (Seek / Seek Rate / Scrub).
hintLabel: 'Force Speed',
hintDisplayKey: 'Q',
hintContexts: [
'hover-video'
],
hintOrder: 225,
hintGroup: 'Video',
hintExpandedHelp: [
{
key: 'Q',
label: 'Toggle force-set video speed'
},
{
key: 'Alt + Q',
label: 'Cycle force-set value down 0.25'
}
]
},
{
id: 'cycleForceSetSpeedValueDown',
description: 'Cycle force set video speed value down by 0.25',
displayKey: 'Alt + Q',
section: 'Playback & Export',
category: 'Playback Shortcuts',
essential: false,
binding: {
code: 'KeyQ',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: ()=>{
deps.cycleForceSetSpeedValueDown();
},
executable: true
},
// ===== Playback & Export / Preview Shortcuts =====
{
id: 'toggleCropPreviewPopOut',
description: 'Toggle previewing crop in pop-out window',
displayKey: 'Shift + X',
section: 'Playback & Export',
category: 'Preview Shortcuts',
essential: true,
binding: {
code: 'KeyX',
modifiers: {
ctrl: false,
shift: true,
alt: false
}
},
handler: ()=>{
deps.toggleCropPreviewPopOut();
},
executable: true
},
{
id: 'toggleCropPreviewModal',
description: 'Crop preview: modal window (Ctrl+Alt+X) or pop-out (Shift+X)',
displayKey: 'Ctrl + Alt + X',
section: 'Playback & Export',
category: 'Preview Shortcuts',
essential: true,
binding: {
code: 'KeyX',
modifiers: {
ctrl: true,
shift: false,
alt: true
}
},
handler: ()=>{
deps.toggleCropPreviewModal();
},
executable: true,
hintLabel: 'Preview',
hintContexts: [
'marker-selected'
],
hintOrder: 145,
hintGroup: 'Crop',
hintExpandedHelp: [
{
key: 'Ctrl + Alt + X',
label: 'Modal window'
},
{
key: 'Shift + X',
label: 'Pop-out window'
}
]
},
{
id: 'toggleGammaPreview',
description: 'Toggle previewing gamma',
displayKey: 'Alt + C',
section: 'Playback & Export',
category: 'Preview Shortcuts',
essential: false,
binding: {
code: 'KeyC',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: ()=>{
deps.toggleGammaPreview();
},
executable: true
},
{
id: 'toggleFadeLoopPreview',
description: 'Toggle previewing fade loop',
displayKey: 'Alt + Shift + C',
section: 'Playback & Export',
category: 'Preview Shortcuts',
essential: false,
binding: {
code: 'KeyC',
modifiers: {
ctrl: false,
shift: true,
alt: true
}
},
handler: ()=>{
deps.toggleFadeLoopPreview();
},
executable: true
},
{
id: 'toggleAllPreviews',
description: 'Preview toggles: speed (C), loop (Shift+C), gamma (Alt+C), fade loop (Alt+Shift+C), all (Ctrl+Alt+Shift+C)',
displayKey: 'Ctrl + Alt + Shift + C',
section: 'Playback & Export',
category: 'Preview Shortcuts',
essential: true,
binding: {
code: 'KeyC',
modifiers: {
ctrl: true,
shift: true,
alt: true
}
},
handler: ()=>{
deps.toggleAllPreviews();
},
executable: true,
hintLabel: 'Previews',
hintContexts: [
'marker-selected'
],
// Joins the Markers group (whose label is suppressed in marker-
// selected mode because it matches the MARKERS mode badge, so the
// group reads as "unnamed" visually). Sits between Edit (130, the
// pair overrides editor) and Nav (135) so the per-pair preview
// toggles cluster with the other per-pair editing actions.
hintOrder: 132,
hintGroup: 'Markers',
hintExpandedHelp: [
{
key: 'Ctrl + Alt + Shift + C',
label: 'All Previews'
},
{
key: 'C',
label: 'Speed'
},
{
key: 'Shift + C',
label: 'Loop'
},
{
key: 'Ctrl + Shift + C',
label: 'Crop Chart Loop'
},
{
key: 'Alt + C',
label: 'Gamma'
},
{
key: 'Alt + Shift + C',
label: 'Fade Loop'
},
{
key: 'Shift + R',
label: 'Big video preview thumbnails'
}
]
},
{
id: 'rotateVideoClock',
description: 'Toggle previewing rotation 90 degrees clockwise',
displayKey: 'R',
section: 'Playback & Export',
category: 'Preview Shortcuts',
essential: false,
binding: {
code: 'KeyR',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
if (theatre()) deps.rotateVideoClock();
else deps.flashNotTheatreMode();
},
executable: true,
// Anchor chip for the R-key rotation family. The counter-clockwise
// variant folds into this chip's popover. Hover-video because
// rotation toggles apply to the player preview directly — no marker
// pair required.
hintLabel: 'Rotate',
hintDisplayKey: 'R',
hintContexts: [
'hover-video'
],
hintOrder: 245,
hintGroup: 'Video',
hintExpandedHelp: [
{
key: 'R',
label: "Rotate preview 90\xb0 clockwise"
},
{
key: 'Alt + R',
label: "Rotate preview 90\xb0 counter-clockwise"
}
]
},
{
id: 'rotateVideoCClock',
description: 'Toggle previewing rotation 90 degrees anti-clockwise',
displayKey: 'Alt + R',
section: 'Playback & Export',
category: 'Preview Shortcuts',
essential: false,
binding: {
code: 'KeyR',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: ()=>{
if (theatre()) deps.rotateVideoCClock();
},
executable: true
},
{
id: 'toggleBigVideoPreviews',
description: 'Toggle big video preview thumbnails',
displayKey: 'Shift + R',
section: 'Playback & Export',
category: 'Preview Shortcuts',
essential: false,
binding: {
code: 'KeyR',
modifiers: {
ctrl: false,
shift: true,
alt: false
}
},
handler: ()=>{
deps.toggleBigVideoPreviews();
},
executable: true
},
// ===== Playback & Export / Frame Capturer =====
{
id: 'captureFrame',
description: 'Capture frame',
displayKey: 'E',
section: 'Playback & Export',
category: 'Frame Capturer Shortcuts',
essential: false,
binding: {
code: 'KeyE',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
deps.captureFrame();
},
executable: true,
// Anchor chip for the E-key frame-capture family. The Alt+E zip+
// download variant (`saveCapturedFrames` below) folds into this
// chip's popover so both ends of the capture workflow are reachable
// from one hover.
hintLabel: 'Capture',
hintContexts: [
'hover-video'
],
hintOrder: 240,
hintGroup: 'Video',
hintExpandedHelp: [
{
key: 'E',
label: 'Capture current frame'
},
{
key: 'Alt + E',
label: 'Zip + download captured frames'
}
]
},
{
id: 'saveCapturedFrames',
description: 'Zip and download captured frames',
displayKey: 'Alt + E',
section: 'Playback & Export',
category: 'Frame Capturer Shortcuts',
essential: false,
binding: {
code: 'KeyE',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: ()=>{
deps.saveCapturedFrames();
},
executable: true
},
// ===== Playback & Export / Saving and Loading =====
// DATA group: Save, Load, Copy, Share — visible in resting primaries
// (default + marker-selected). Save loses its `always` persistence so
// it can group cohesively with its siblings; the bar's mid-action modes
// are short-lived enough that this is an acceptable tradeoff.
{
id: 'saveMarkersAndSettings',
description: 'Save markers data as json',
displayKey: 'S',
section: 'Playback & Export',
category: 'Saving and Loading Shortcuts',
essential: true,
binding: {
code: 'KeyS',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
deps.saveMarkersAndSettings();
},
executable: true,
hintLabel: 'Save',
hintContexts: [
'default',
'marker-selected',
'global-editor'
],
hintOrder: 178,
hintGroup: 'Data'
},
{
id: 'copyMarkersToClipboard',
description: 'Copy markers data to clipboard',
displayKey: 'Alt + S',
section: 'Playback & Export',
category: 'Saving and Loading Shortcuts',
essential: false,
binding: {
code: 'KeyS',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: ()=>{
deps.copyMarkersToClipboard();
},
executable: true,
hintLabel: 'Copy',
hintContexts: [
'default',
'marker-selected',
'global-editor'
],
hintOrder: 186,
hintGroup: 'Data'
},
{
id: 'copyShareableUrl',
description: 'Copy shareable URL with embedded markers to clipboard',
displayKey: 'Shift + S',
section: 'Playback & Export',
category: 'Saving and Loading Shortcuts',
essential: false,
binding: {
code: 'KeyS',
modifiers: {
ctrl: false,
shift: true,
alt: false
}
},
handler: ()=>{
deps.copyShareableUrl();
},
executable: true,
// Gated by the `shareLink` flag — when off, both the Share chip
// in the Data group AND the Shift+S key binding skip via the
// shared guard check in `hints-bar.ts` and `hotkey-engine.ts`.
// Flip in `feature-flags.ts` to re-enable.
guard: ()=>(0, _featureFlags.featureFlags).shareLink,
hintLabel: 'Share',
hintContexts: [
'default',
'marker-selected',
'global-editor'
],
hintOrder: 190,
hintGroup: 'Data'
},
// Order summary now: Previews(170) → Undo/Redo Pair(172) → Undo/Redo
// Edit(174) → Data Save(178) → Load(182) → Copy(186) → Share(190).
{
id: 'toggleMarkersDataCommands',
description: 'Toggle markers data commands (loading, restoring, and clearing)',
displayKey: 'G',
section: 'Playback & Export',
category: 'Saving and Loading Shortcuts',
essential: false,
binding: {
code: 'KeyG',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
deps.toggleMarkersDataCommands();
},
executable: true,
hintLabel: 'Load',
hintContexts: [
'default',
'marker-selected',
'global-editor'
],
hintOrder: 182,
hintGroup: 'Data'
},
// ===== Playback & Export / Miscellaneous =====
{
id: 'flattenVRVideo',
description: 'Flatten VR Video',
displayKey: 'Shift + F',
section: 'Playback & Export',
category: 'Miscellaneous Shortcuts',
essential: false,
binding: {
code: 'KeyF',
modifiers: {
ctrl: false,
shift: true,
alt: false
}
},
handler: ()=>{
deps.flattenVRVideo();
},
executable: true
},
// ===== Dynamic Effects / General Chart Shortcuts =====
{
id: 'chartAddPoint',
description: 'Add chart point',
displayKey: 'Shift + Click',
section: 'Dynamic Effects',
category: 'General Chart Shortcuts',
essential: true,
binding: null,
handler: null,
executable: false,
hintLabel: 'Add at Cursor',
hintContexts: [
'hover-speed-chart',
'hover-crop-chart',
'hover-crop-chart-zoompan'
],
hintOrder: 10,
hintGroup: 'Points'
},
{
id: 'chartAddPointAtCurrentTime',
description: "Add chart point at current time. When fired mid-drag or mid-resize of a crop point, the held point reverts to its previous drop position and the new keyframe captures the current visual crop \u2014 drop multiple keyframes in one continuous gesture for rapid dynamic-crop tracking (per-keyframe size variation only applies in zoompan mode for resize).",
displayKey: 'Alt + A',
section: 'Dynamic Effects',
category: 'General Chart Shortcuts',
essential: false,
binding: {
code: 'KeyA',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: ()=>{
deps.addChartPoint();
},
guard: markerGuard,
executable: true,
hintLabel: 'Add at Time',
hintContexts: [
'hover-speed-chart',
'hover-crop-chart',
'hover-crop-chart-zoompan'
],
hintOrder: 11,
hintGroup: 'Points'
},
{
id: 'chartDeletePoint',
description: 'Delete chart point',
displayKey: 'Alt + Shift + Click',
section: 'Dynamic Effects',
category: 'General Chart Shortcuts',
essential: true,
binding: null,
handler: null,
executable: false,
hintLabel: 'Delete',
hintContexts: [
'hover-speed-chart',
'hover-speed-chart-point',
'hover-crop-chart',
'hover-crop-chart-point',
'hover-crop-chart-zoompan'
],
hintOrder: 20,
hintGroup: 'Points'
},
{
id: 'chartMovePointOrPan',
description: 'Move chart point or pan chart',
displayKey: 'Click + Drag',
section: 'Dynamic Effects',
category: 'General Chart Shortcuts',
essential: true,
binding: null,
handler: null,
executable: false,
hintLabel: 'Move',
hintContexts: [
'hover-speed-chart-point',
'hover-crop-chart-point'
],
hintOrder: 5,
hintGroup: 'Points'
},
{
id: 'chartZoom',
description: 'Zoom in and out of chart',
displayKey: 'Ctrl + Mousewheel',
section: 'Dynamic Effects',
category: 'General Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Zoom',
hintContexts: [
'hover-speed-chart',
'hover-speed-chart-point',
'hover-crop-chart',
'hover-crop-chart-point',
'hover-crop-chart-zoompan'
],
hintOrder: 55,
hintGroup: 'View'
},
{
id: 'chartResetZoom',
description: 'Reset chart zoom',
displayKey: 'Ctrl + Click',
section: 'Dynamic Effects',
category: 'General Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Reset',
hintContexts: [
'hover-speed-chart',
'hover-speed-chart-point',
'hover-crop-chart',
'hover-crop-chart-point',
'hover-crop-chart-zoompan'
],
hintOrder: 60,
hintGroup: 'View'
},
{
id: 'chartSeekToTime',
description: 'Seek to time on chart time-axis',
displayKey: 'Right-Click',
section: 'Dynamic Effects',
category: 'General Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Seek',
hintContexts: [
'hover-speed-chart',
'hover-speed-chart-point',
'hover-crop-chart',
'hover-crop-chart-point',
'hover-crop-chart-zoompan'
],
hintOrder: 65,
hintGroup: 'Playback'
},
{
id: 'chartSetLoopMarker',
description: 'Set chart loop start/end marker',
displayKey: 'Shift/Alt + Right-click',
section: 'Dynamic Effects',
category: 'General Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false
},
{
id: 'toggleChartLoop',
description: 'Toggle chart marker looping',
displayKey: 'Shift + D',
section: 'Dynamic Effects',
category: 'General Chart Shortcuts',
essential: false,
binding: {
code: 'KeyD',
modifiers: {
ctrl: false,
shift: true,
alt: false
}
},
handler: ()=>{
deps.toggleChartLoop();
},
executable: true,
hintLabel: 'Loop',
hintContexts: [
'hover-speed-chart',
'hover-speed-chart-point',
'hover-crop-chart',
'hover-crop-chart-point',
'hover-crop-chart-zoompan'
],
hintOrder: 70,
hintGroup: 'Playback',
hintExpandedHelp: [
{
key: 'Shift + D',
label: 'Toggle loop playback'
},
{
key: 'Shift + Right-Click',
label: 'Set loop start'
},
{
key: 'Alt + Right-Click',
label: 'Set loop end'
}
]
},
// ===== Dynamic Effects / Speed Chart =====
{
id: 'toggleSpeedChart',
description: 'Toggle speed chart',
displayKey: 'D',
section: 'Dynamic Effects',
category: 'Speed Chart Shortcuts',
essential: true,
binding: {
code: 'KeyD',
modifiers: {
ctrl: false,
shift: false,
alt: false
}
},
handler: ()=>{
deps.toggleSpeedChart();
},
executable: true,
hintLabel: 'Speed',
hintContexts: [
'marker-selected'
],
hintOrder: 150,
hintGroup: 'Charts'
},
// ===== Dynamic Effects / Crop Chart =====
{
id: 'toggleCropChart',
description: 'Toggle crop chart',
displayKey: 'Alt + D',
section: 'Dynamic Effects',
category: 'Crop Chart Shortcuts',
essential: true,
binding: {
code: 'KeyD',
modifiers: {
ctrl: false,
shift: false,
alt: true
}
},
handler: ()=>{
deps.toggleCropChart();
},
executable: true,
hintLabel: 'Crop',
hintContexts: [
'marker-selected'
],
hintOrder: 160,
hintGroup: 'Charts'
},
{
id: 'cropChartSelectPoint',
description: 'Select point as start/end of crop section',
displayKey: 'Ctrl/Alt + Mouseover',
section: 'Dynamic Effects',
category: 'Crop Chart Shortcuts',
essential: true,
binding: null,
handler: null,
executable: false,
// Anchor chip for the section-selection action. The `Ctrl + Hover`
// primary chord shows the start variant; the popover documents the
// matching `Alt + Hover` "set as end" variant alongside.
hintLabel: 'Set Start',
hintDisplayKey: 'Ctrl + Mouseover',
hintContexts: [
'hover-crop-chart-point'
],
hintOrder: 40,
hintGroup: 'Section',
hintExpandedHelp: [
{
key: 'Ctrl + Mouseover',
label: 'Set Start'
},
{
key: 'Alt + Mouseover',
label: 'Set End'
}
]
},
{
id: 'cropChartToggleModeSelectPrev',
description: 'Toggle start/end mode. If in end mode also select prev point',
displayKey: 'Alt + Mousewheel Down',
section: 'Dynamic Effects',
category: 'Crop Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
// Anchor chip for the wheel-driven step action (both Up and Down).
// Each tick is a half-step — flipping start/end mode in place or
// advancing/retreating to the next/prev point — so two ticks add up
// to one full point of section movement.
hintLabel: 'Step Pt',
hintDisplayKey: 'Alt + Wheel',
hintContexts: [
'hover-crop-chart-point'
],
hintOrder: 45,
hintGroup: 'Section',
hintExpandedHelp: [
{
key: 'Alt + Mousewheel Up',
label: 'Step forward'
},
{
key: 'Alt + Mousewheel Down',
label: 'Step backward'
}
]
},
{
id: 'cropChartToggleModeSelectNext',
description: 'Toggle start/end mode. If in start mode also select next point',
displayKey: 'Alt + Mousewheel Up',
section: 'Dynamic Effects',
category: 'Crop Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false
},
{
id: 'cropChartInheritCrop',
description: "Set current point's crop to next/prev point's crop",
displayKey: 'Ctrl + Alt + Shift + Mousewheel Up/Down',
section: 'Dynamic Effects',
category: 'Crop Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Copy Crop',
hintDisplayKey: 'Ctrl + Alt + Shift + Wheel',
hintContexts: [
'hover-crop-chart-point'
],
hintOrder: 50,
hintGroup: 'Section',
hintExpandedHelp: [
{
key: 'Ctrl + Alt + Shift + Mousewheel Up',
label: 'Copy from next point'
},
{
key: 'Ctrl + Alt + Shift + Mousewheel Down',
label: 'Copy from previous point'
}
]
},
{
id: 'cropChartToggleEase',
description: 'Toggle crop point ease in between auto and instant',
displayKey: 'Ctrl + Shift + Click',
section: 'Dynamic Effects',
category: 'Crop Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Ease',
hintContexts: [
'hover-crop-chart-point'
],
hintOrder: 30,
hintGroup: 'Points'
},
{
id: 'cropChartSetTargetComponent',
description: 'Set target crop component of all points following/preceding selected point. Select crop component with cursor in crop input field',
displayKey: '',
displayNote: 'a / Shift + A',
section: 'Dynamic Effects',
category: 'Crop Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
// Surfaced in the crop-input-focused context — the action depends on
// the text cursor's position inside the crop input (which component
// is targeted is read from where the caret sits in `x:y:w:h`).
hintLabel: 'Propagate Component',
hintDisplayKey: '(Shift) + A',
hintContexts: [
'crop-input-focused'
],
hintOrder: 30,
hintGroup: 'Crop Input',
hintExpandedHelp: [
{
key: 'A',
label: 'Apply component under cursor to all FOLLOWING crop points'
},
{
key: 'Shift + A',
label: 'Apply component under cursor to all PRECEDING crop points'
}
]
},
// ===== Dynamic Effects / ZoomPan Mode Crop Chart =====
{
id: 'zoomPanArLockedResize',
description: 'Crop-aspect-ratio-locked resize of crop',
displayKey: 'Ctrl + Drag',
section: 'Dynamic Effects',
category: 'ZoomPan Mode Crop Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'AR Resize',
hintContexts: [
'hover-crop-chart-zoompan'
],
hintOrder: 10
},
{
id: 'zoomPanFreelyResize',
description: 'Freely resize crop',
displayKey: 'Ctrl + Alt + Drag',
section: 'Dynamic Effects',
category: 'ZoomPan Mode Crop Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Free Resize',
hintContexts: [
'hover-crop-chart-zoompan'
],
hintOrder: 20
},
{
id: 'zoomPanArLockedDraw',
description: 'Crop-aspect-ratio-locked draw crop',
displayKey: 'X, Click + Drag',
section: 'Dynamic Effects',
category: 'ZoomPan Mode Crop Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Draw AR',
hintContexts: [
'hover-crop-chart-zoompan'
],
hintOrder: 30
},
{
id: 'zoomPanFreelyDraw',
description: 'Freely draw crop',
displayKey: 'X, Alt + Click + Drag',
section: 'Dynamic Effects',
category: 'ZoomPan Mode Crop Chart Shortcuts',
essential: false,
binding: null,
handler: null,
executable: false,
hintLabel: 'Free Draw',
hintContexts: [
'hover-crop-chart-zoompan'
],
hintOrder: 40
}
];
}
},{"./feature-flags":"e23zj","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"5uOzY":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "captureFrame", ()=>captureFrame);
parcelHelpers.export(exports, "getFrameCount", ()=>getFrameCount);
parcelHelpers.export(exports, "saveCapturedFrames", ()=>saveCapturedFrames);
var _fileSaver = require("file-saver");
var _jszip = require("jszip");
var _jszipDefault = parcelHelpers.interopDefault(_jszip);
var _litHtml = require("lit-html");
var _crop = require("./crop/crop");
var _platforms = require("./platforms/platforms");
var _appState = require("./appState");
var _util = require("./util/util");
var _videoUtil = require("./util/videoUtil");
var _cropUtils = require("./crop-utils");
let frameCaptureViewerWindow;
let frameCaptureViewerDoc;
let isFrameCapturerZippingInProgress = false;
function FrameCaptureViewerHeadTemplate() {
const canvasSizeRule = (0, _appState.appState).videoInfo.aspectRatio > 1 ? 'width: 98%;' : 'height: 96vh;';
return (0, _litHtml.html)`
<title>yt_clipper Frame Capture Viewer</title>
<style>
body {
margin: 0px;
text-align: center;
}
#frames-div {
font-family: Helvetica;
background-color: rgb(160, 50, 20);
margin: 0 auto;
padding: 2px;
width: 99%;
text-align: center;
}
.frame-div {
margin: 2px;
padding: 2px;
border: 2px black solid;
font-weight: bold;
color: black;
text-align: center;
}
figcaption {
display: inline-block;
margin: 2px;
}
button {
display: inline-block;
font-weight: bold;
margin-bottom: 2px;
cursor: pointer;
border: 2px solid black;
border-radius: 4px;
}
button.download {
background-color: rgb(66, 134, 244);
}
button.delete {
background-color: red;
}
button:hover {
box-shadow: 2px 4px 4px 0 rgba(0, 0, 0, 0.2);
}
canvas {
display: block;
margin: 0 auto;
${canvasSizeRule}
}
@keyframes flash {
0% {
opacity: 1;
}
100% {
opacity: 0.5;
}
}
.flash-div {
animation-name: flash;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
</style>
`;
}
const frameCaptureViewerBodyTemplate = (0, _litHtml.html)`<div id="frames-div"><strong></strong></div>`;
function FrameCaptionTemplate(width, height, frameFileName) {
return (0, _litHtml.html)`
<figcaption>Resolution: ${width}x${height} Name: ${frameFileName}</figcaption>
<button class="download">Download Frame</button>
<button class="delete">Delete Frame</button>
`;
}
function captureFrame() {
const currentTime = (0, _appState.appState).video.getCurrentTime();
for(let i = 0; i < (0, _appState.appState).video.buffered.length; i++){
console.log((0, _appState.appState).video.buffered.start(i), (0, _appState.appState).video.buffered.end(i));
if ((0, _appState.appState).video.buffered.start(i) <= currentTime && currentTime <= (0, _appState.appState).video.buffered.end(i)) break;
if (i === (0, _appState.appState).video.buffered.length - 1) {
(0, _util.flashMessage)('Frame not captured. Video has not yet buffered the frame.', 'red');
return;
}
}
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
(0, _util.assertDefined)(context, 'Failed to get 2d canvas context');
let resString;
if ((0, _appState.appState).isSettingsEditorOpen) {
const cropMultipleX = (0, _appState.appState).video.videoWidth / (0, _appState.appState).settings.cropResWidth;
const cropMultipleY = (0, _appState.appState).video.videoHeight / (0, _appState.appState).settings.cropResHeight;
if (!(0, _appState.appState).wasGlobalSettingsEditorOpen) {
const dataIdx = (0, _appState.appState).prevSelectedEndMarker.getAttribute('data-idx');
(0, _util.assertDefined)(dataIdx, 'Expected data-idx attribute on end marker');
const idx = parseInt(dataIdx, 10) - 1;
const markerPair = (0, _appState.appState).markerPairs[idx];
resString = (0, _cropUtils.multiplyCropString)(cropMultipleX, cropMultipleY, markerPair.crop);
} else resString = (0, _cropUtils.multiplyCropString)(cropMultipleX, cropMultipleY, (0, _appState.appState).settings.newMarkerCrop);
const cropRes = (0, _crop.Crop).getMultipliedCropRes((0, _appState.appState).settings.cropRes, cropMultipleX, cropMultipleY);
const [x, y, w, h] = (0, _crop.Crop).getCropComponents(resString, cropRes);
canvas.width = w;
canvas.height = h;
if (h > w) {
canvas.style.height = '96vh';
canvas.style.width = 'auto';
}
context.drawImage((0, _appState.appState).video, x, y, w, h, 0, 0, w, h);
resString = `x${x}y${y}w${w}h${h}`;
} else {
resString = `x0y0w${(0, _appState.appState).video.videoWidth}h${(0, _appState.appState).video.videoHeight}`;
canvas.width = (0, _appState.appState).video.videoWidth;
canvas.height = (0, _appState.appState).video.videoHeight;
context.drawImage((0, _appState.appState).video, 0, 0, (0, _appState.appState).video.videoWidth, (0, _appState.appState).video.videoHeight);
}
if (!frameCaptureViewerWindow || !frameCaptureViewerDoc || frameCaptureViewerWindow.closed) {
const newWindow = window.open('', 'window', `height=${window.innerHeight}, width=${window.innerWidth}`);
(0, _util.assertDefined)(newWindow, 'Failed to open frame capture viewer window');
frameCaptureViewerWindow = newWindow;
frameCaptureViewerDoc = frameCaptureViewerWindow.document;
(0, _litHtml.render)(FrameCaptureViewerHeadTemplate(), frameCaptureViewerDoc.head);
(0, _litHtml.render)(frameCaptureViewerBodyTemplate, frameCaptureViewerDoc.body);
}
const frameDiv = document.createElement('div');
frameDiv.setAttribute('class', 'frame-div');
const frameCount = getFrameCount(currentTime);
const frameFileName = `${(0, _appState.appState).settings.titleSuffix}-${resString}-@${currentTime}s(${(0, _util.toHHMMSSTrimmed)(currentTime).replace(':', ';')})-f${frameCount.frameNumber}(${frameCount.totalFrames})`;
(0, _litHtml.render)(FrameCaptionTemplate(canvas.width, canvas.height, frameFileName), frameDiv);
canvas.fileName = `${frameFileName}.png`;
frameDiv.appendChild(canvas);
frameDiv.getElementsByClassName('download')[0].onclick = ()=>{
canvas.toBlob((blob)=>{
(0, _util.assertDefined)(blob, 'Failed to create blob from canvas');
(0, _fileSaver.saveAs)(blob, canvas.fileName);
});
};
frameDiv.getElementsByClassName('delete')[0].onclick = ()=>{
frameDiv.setAttribute('class', 'frame-div flash-div');
setTimeout(()=>{
(0, _util.deleteElement)(frameDiv);
}, 300);
};
const framesDiv = frameCaptureViewerDoc.getElementById('frames-div');
(0, _util.assertDefined)(framesDiv, 'Expected frames-div element in frame capture viewer');
framesDiv.appendChild(frameDiv);
(0, _util.flashMessage)(`Captured frame: ${frameFileName}`, 'green');
}
function getFrameCount(seconds) {
const fps = (0, _videoUtil.getFPS)(null);
let frameNumber;
let totalFrames;
if (fps) {
frameNumber = Math.floor(seconds * fps);
totalFrames = Math.floor((0, _util.getVideoDuration)((0, _platforms.getPlatform)(), (0, _appState.appState).video) * fps);
} else {
frameNumber = 'Unknown';
totalFrames = 'Unknown';
}
return {
frameNumber,
totalFrames
};
}
function canvasBlobToPromise(canvas) {
return new Promise((resolve)=>{
canvas.toBlob((blob)=>{
(0, _util.assertDefined)(blob, 'Failed to create blob from canvas');
resolve(blob);
});
});
}
function saveCapturedFrames() {
if (isFrameCapturerZippingInProgress) {
(0, _util.flashMessage)('Frame Capturer zipping already in progress. Please wait before trying to zip again.', 'red');
return;
}
if (!frameCaptureViewerWindow || frameCaptureViewerWindow.closed || !frameCaptureViewerDoc) {
(0, _util.flashMessage)('Frame capturer not open. Please capture a frame before zipping.', 'olive');
return;
}
const zip = new (0, _jszipDefault.default)();
const titleFolder = zip.folder((0, _appState.appState).settings.titleSuffix);
(0, _util.assertDefined)(titleFolder, 'Failed to create zip folder');
const framesZip = titleFolder.folder('frames');
const frames = frameCaptureViewerDoc.getElementsByTagName('canvas');
if (frames.length === 0) {
(0, _util.flashMessage)('No frames to zip.', 'olive');
return;
}
isFrameCapturerZippingInProgress = true;
(0, _util.assertDefined)(framesZip, 'Failed to create frames zip folder');
Array.from(frames).forEach((frame)=>{
framesZip.file(frame.fileName, canvasBlobToPromise(frame), {
binary: true
});
});
const progressDiv = (0, _util.injectProgressBar)('green', 'Frame Capturer');
const progressSpan = progressDiv.firstElementChild;
(0, _util.assertDefined)(progressSpan, 'Expected progress span in progress bar');
zip.generateAsync({
type: 'blob'
}, (metadata)=>{
const percent = metadata.percent.toFixed(2) + '%';
progressSpan.textContent = `Frame Capturer Zipping Progress: ${percent}`;
}).then((blob)=>{
(0, _fileSaver.saveAs)(blob, `${(0, _appState.appState).settings.titleSuffix}-frames.zip`);
progressDiv.dispatchEvent(new Event('done'));
isFrameCapturerZippingInProgress = false;
});
}
},{"file-saver":"6BFkB","jszip":"hmOqA","lit-html":"9fQBw","./crop/crop":"axPMI","./platforms/platforms":"1kR7r","./appState":"g0AlP","./util/util":"99arg","./util/videoUtil":"93pN0","./crop-utils":"k2gwb","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"iAvvX":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "rotateVideo", ()=>rotateVideo);
parcelHelpers.export(exports, "fullscreenRotateVideoHandler", ()=>fullscreenRotateVideoHandler);
parcelHelpers.export(exports, "toggleBigVideoPreviews", ()=>toggleBigVideoPreviews);
var _appState = require("./appState");
var _cropPreview = require("./crop/crop-preview");
var _css = require("./ui/css/css");
var _util = require("./util/util");
var _cropOverlay = require("./crop-overlay");
let rotatedVideoCSS;
let fullscreenRotatedVideoCSS;
let rotatedVideoPreviewsCSS;
let rotatedVideoStyle;
let adjustRotatedVideoPositionStyle;
let fullscreenRotatedVideoStyle;
let rotatedVideoPreviewsStyle;
let bigVideoPreviewsStyle;
function rotateVideo(direction) {
if (direction === 'clock') (0, _appState.appState).rotation = (0, _appState.appState).rotation === 0 ? 90 : 0;
else if (direction === 'cclock') (0, _appState.appState).rotation = (0, _appState.appState).rotation === 0 ? -90 : 0;
if ((0, _appState.appState).rotation === 90 || (0, _appState.appState).rotation === -90) {
const scale = 1 / (0, _appState.appState).videoInfo.aspectRatio;
rotatedVideoCSS = (0, _css.getRotatedVideoCSS)((0, _appState.appState).rotation);
const tooltipOffset = Math.round(((0, _appState.appState).videoInfo.aspectRatio - 1) / 2 * 100);
rotatedVideoPreviewsCSS = `\
.ytp-tooltip {
transform: translateY(-${tooltipOffset}%) rotate(${(0, _appState.appState).rotation}deg) !important;
}
.ytp-tooltip-text-wrapper {
transform: rotate(${-(0, _appState.appState).rotation}deg) !important;
opacity: 0.6;
}
`;
fullscreenRotatedVideoCSS = `
.yt-clipper-video {
transform: rotate(${(0, _appState.appState).rotation}deg) scale(${scale}) !important;
margin-left: auto;
}
`;
if (document.fullscreenElement == null) {
adjustRotatedVideoPositionStyle = (0, _util.injectCSS)((0, _css.adjustRotatedVideoPositionCSS), 'adjust-rotated-video-position-css');
rotatedVideoStyle = (0, _util.injectCSS)(rotatedVideoCSS, 'yt-clipper-rotate-video-css');
window.dispatchEvent(new Event('resize'));
} else fullscreenRotatedVideoStyle = (0, _util.injectCSS)(fullscreenRotatedVideoCSS, 'fullscreen-rotated-video-css');
rotatedVideoPreviewsStyle = (0, _util.injectCSS)(rotatedVideoPreviewsCSS, 'yt-clipper-rotated-video-previews-css');
if (bigVideoPreviewsStyle) (0, _util.deleteElement)(bigVideoPreviewsStyle);
bigVideoPreviewsStyle = null;
window.dispatchEvent(new Event('resize'));
document.addEventListener('fullscreenchange', fullscreenRotateVideoHandler);
} else {
(0, _util.deleteElement)(rotatedVideoStyle);
(0, _util.deleteElement)(adjustRotatedVideoPositionStyle);
(0, _util.deleteElement)(fullscreenRotatedVideoStyle);
(0, _util.deleteElement)(rotatedVideoPreviewsStyle);
if (bigVideoPreviewsStyle) (0, _util.deleteElement)(bigVideoPreviewsStyle);
bigVideoPreviewsStyle = null;
window.dispatchEvent(new Event('resize'));
document.removeEventListener('fullscreenchange', fullscreenRotateVideoHandler);
}
(0, _cropOverlay.resizeCropOverlay)();
(0, _cropPreview.triggerCropPreviewRedraw)();
}
function fullscreenRotateVideoHandler() {
if (document.fullscreenElement != null) {
(0, _util.deleteElement)(rotatedVideoStyle);
(0, _util.deleteElement)(adjustRotatedVideoPositionStyle);
fullscreenRotatedVideoStyle = (0, _util.injectCSS)(fullscreenRotatedVideoCSS, 'fullscreen-rotated-video-css');
} else {
(0, _util.deleteElement)(fullscreenRotatedVideoStyle);
adjustRotatedVideoPositionStyle = (0, _util.injectCSS)((0, _css.adjustRotatedVideoPositionCSS), 'adjust-rotated-video-position-css');
rotatedVideoStyle = (0, _util.injectCSS)(rotatedVideoCSS, 'yt-clipper-rotate-video-css');
document.removeEventListener('fullscreenchange', fullscreenRotateVideoHandler);
window.dispatchEvent(new Event('resize'));
}
}
function toggleBigVideoPreviews() {
const bigVideoPreviewsCSS = `\
.ytp-tooltip {
left: 45% !important;
transform: ${(0, _appState.appState).rotation ? `translateY(-285%) rotate(${(0, _appState.appState).rotation}deg)` : 'translateY(-160%) '} scale(4) !important;
padding: 1px !important;
border-radius: 1px !important;
}
.ytp-tooltip-text-wrapper {
transform: scale(0.5) ${(0, _appState.appState).rotation ? `rotate(${-(0, _appState.appState).rotation}deg)` : ''}!important;
opacity: 0.6;
}
`;
if (bigVideoPreviewsStyle) {
(0, _util.deleteElement)(bigVideoPreviewsStyle);
bigVideoPreviewsStyle = null;
} else bigVideoPreviewsStyle = (0, _util.injectCSS)(bigVideoPreviewsCSS, 'yt-clipper-big-video-previews-css');
}
},{"./appState":"g0AlP","./crop/crop-preview":"9T0zg","./ui/css/css":"hOiqQ","./util/util":"99arg","./crop-overlay":"6s727","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"9Q63h":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "toggleGammaPreview", ()=>toggleGammaPreview);
parcelHelpers.export(exports, "toggleFadeLoopPreview", ()=>toggleFadeLoopPreview);
parcelHelpers.export(exports, "getIsFadeLoopPreviewOn", ()=>getIsFadeLoopPreviewOn);
parcelHelpers.export(exports, "getFadeBounds", ()=>getFadeBounds);
parcelHelpers.export(exports, "toggleAllPreviews", ()=>toggleAllPreviews);
var _d3Ease = require("d3-ease");
var _appState = require("./appState");
var _cropPreview = require("./crop/crop-preview");
var _speed = require("./speed");
var _litHtml = require("lit-html");
var _util = require("./util/util");
var _previewGamma = require("./util/previewGamma");
let gammaFilterDiv;
let gammaR;
let gammaG;
let gammaB;
let gammaFilterSvg;
const gammaFilterSvgTemplate = (0, _litHtml.html)`
<svg id="gamma-filter-svg" xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<defs>
<filter id="gamma-filter">
<feComponentTransfer id="gamma-filter-comp-transfer">
<feFuncR id="gamma-r" type="gamma" offset="0" amplitude="1"></feFuncR>
<feFuncG id="gamma-g" type="gamma" offset="0" amplitude="1"></feFuncG>
<feFuncB id="gamma-b" type="gamma" offset="0" amplitude="1"></feFuncB>
</feComponentTransfer>
</filter>
</defs>
</svg>
`;
function toggleGammaPreview() {
if (!gammaFilterDiv) {
gammaFilterDiv = document.createElement('div');
gammaFilterDiv.setAttribute('id', 'gamma-filter-div');
(0, _litHtml.render)(gammaFilterSvgTemplate, gammaFilterDiv);
document.body.appendChild(gammaFilterDiv);
gammaFilterSvg = gammaFilterDiv.firstElementChild;
gammaR = document.getElementById('gamma-r');
gammaG = document.getElementById('gamma-g');
gammaB = document.getElementById('gamma-b');
}
if (!(0, _appState.appState).isGammaPreviewOn) {
(0, _appState.appState).video.style.filter = 'url(#gamma-filter)';
(0, _appState.appState).isGammaPreviewOn = true;
requestAnimationFrame(gammaPreviewHandler);
(0, _util.flashMessage)('Gamma preview enabled', 'green');
} else {
(0, _appState.appState).video.style.filter = null;
(0, _appState.appState).isGammaPreviewOn = false;
(0, _util.flashMessage)('Gamma preview disabled', 'red');
}
(0, _cropPreview.toggleCropPreviewGammaPreview)();
}
function gammaPreviewHandler() {
const shortestActiveMarkerPair = (0, _speed.getShortestActiveMarkerPair)();
const markerPairGamma = shortestActiveMarkerPair?.overrides.gamma ?? (0, _appState.appState).settings.gamma ?? 1;
if (markerPairGamma == 1) {
if ((0, _appState.appState).video.style.filter) (0, _appState.appState).video.style.filter = null;
(0, _previewGamma.setPrevGammaVal)(1);
} else if ((0, _previewGamma.prevGammaVal) !== markerPairGamma) {
// console.log(`Updating gamma from ${prevGammaVal} to ${markerPairGamma}`);
gammaR.exponent.baseVal = markerPairGamma;
gammaG.exponent.baseVal = markerPairGamma;
gammaB.exponent.baseVal = markerPairGamma;
// force re-render of filter (possible bug with chrome and other browsers?)
if (!(0, _appState.appState).video.style.filter) (0, _appState.appState).video.style.filter = 'url(#gamma-filter)';
gammaFilterSvg.setAttribute('width', '0');
(0, _previewGamma.setPrevGammaVal)(markerPairGamma);
}
if ((0, _appState.appState).isGammaPreviewOn) requestAnimationFrame(gammaPreviewHandler);
}
let isFadeLoopPreviewOn = false;
function toggleFadeLoopPreview() {
if (!isFadeLoopPreviewOn) {
isFadeLoopPreviewOn = true;
requestAnimationFrame(fadeLoopPreviewHandler);
(0, _util.flashMessage)('Fade loop preview enabled', 'green');
} else {
isFadeLoopPreviewOn = false;
(0, _appState.appState).video.style.opacity = '1';
(0, _util.flashMessage)('Fade loop preview disabled', 'red');
}
}
function getIsFadeLoopPreviewOn() {
return isFadeLoopPreviewOn;
}
function fadeLoopPreviewHandler() {
const currentTime = (0, _appState.appState).video.getCurrentTime();
const shortestActiveMarkerPair = (0, _speed.getShortestActiveMarkerPair)();
if (shortestActiveMarkerPair && (shortestActiveMarkerPair.overrides.loop === 'fade' || shortestActiveMarkerPair.overrides.loop == null && (0, _appState.appState).settings.loop === 'fade')) {
const currentTimeP = getFadeBounds(shortestActiveMarkerPair, currentTime);
if (currentTimeP == null) (0, _appState.appState).video.style.opacity = '1';
else {
const currentTimeEased = Math.max(0.1, (0, _d3Ease.easeCubicInOut)(currentTimeP));
(0, _appState.appState).video.style.opacity = currentTimeEased.toString();
}
} else (0, _appState.appState).video.style.opacity = '1';
isFadeLoopPreviewOn ? requestAnimationFrame(fadeLoopPreviewHandler) : (0, _appState.appState).video.style.opacity = '1';
}
function getFadeBounds(markerPair, currentTime) {
const start = Math.floor(markerPair.start * 1e6) / 1e6;
const end = Math.ceil(markerPair.end * 1e6) / 1e6;
const inputDuration = end - start;
const outputDuration = markerPair.outputDuration;
let fadeDuration = markerPair.overrides.fadeDuration ?? (0, _appState.appState).settings.fadeDuration ?? 0.5;
fadeDuration = Math.min(fadeDuration, 0.4 * outputDuration);
const fadeInStartP = 0;
const fadeInEndP = fadeDuration / outputDuration;
const fadeOutStartP = (outputDuration - fadeDuration) / outputDuration;
const fadeOutEndP = outputDuration / outputDuration;
let currentTimeP = (currentTime - start) / inputDuration;
if (currentTimeP >= fadeInStartP && currentTimeP <= fadeInEndP) {
currentTimeP = (currentTime - start) / fadeDuration;
return currentTimeP;
} else if (currentTimeP >= fadeOutStartP && currentTimeP <= fadeOutEndP) {
currentTimeP = 1 - (currentTime - start - (inputDuration - fadeDuration)) / fadeDuration;
return currentTimeP;
} else return null;
}
function toggleAllPreviews() {
(0, _appState.appState).isAllPreviewsOn = (0, _speed.getIsSpeedPreviewOn)() && (0, _speed.getIsMarkerLoopPreviewOn)() && (0, _appState.appState).isGammaPreviewOn && isFadeLoopPreviewOn && (0, _appState.appState).isCropChartLoopingOn;
if (!(0, _appState.appState).isAllPreviewsOn) {
!(0, _speed.getIsSpeedPreviewOn)() && (0, _speed.toggleMarkerPairSpeedPreview)();
!(0, _speed.getIsMarkerLoopPreviewOn)() && (0, _speed.toggleMarkerPairLoop)();
!(0, _appState.appState).isGammaPreviewOn && toggleGammaPreview();
!isFadeLoopPreviewOn && toggleFadeLoopPreview();
(0, _appState.appState).isAllPreviewsOn = true;
} else {
(0, _speed.getIsSpeedPreviewOn)() && (0, _speed.toggleMarkerPairSpeedPreview)();
(0, _speed.getIsMarkerLoopPreviewOn)() && (0, _speed.toggleMarkerPairLoop)();
(0, _appState.appState).isGammaPreviewOn && toggleGammaPreview();
isFadeLoopPreviewOn && toggleFadeLoopPreview();
(0, _appState.appState).isAllPreviewsOn = false;
}
}
},{"d3-ease":[["easeCubicInOut","b0VTA","cubicInOut"]],"./appState":"g0AlP","./crop/crop-preview":"9T0zg","./speed":"6CgFD","lit-html":"9fQBw","./util/util":"99arg","./util/previewGamma":"6oouq","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"bwm01":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "resolvePlayerAndVideo", ()=>resolvePlayerAndVideo);
var _appState = require("./appState");
var _platforms = require("./platforms/platforms");
var _util = require("./util/util");
var _ytClipper = require("./yt_clipper");
async function resolvePlayerAndVideo() {
(0, _appState.appState).player = await (0, _util.retryUntilTruthyResult)(()=>document.querySelector((0, _ytClipper.selectors).player));
if ((0, _ytClipper.platform) === (0, _platforms.VideoPlatforms).yt_clipper) {
(0, _appState.appState).video = await (0, _util.retryUntilTruthyResult)(()=>document.querySelector((0, _ytClipper.selectors).video));
(0, _appState.appState).player = await (0, _util.retryUntilTruthyResult)(()=>document.querySelector((0, _ytClipper.selectors).player));
} else (0, _appState.appState).video = await (0, _util.retryUntilTruthyResult)(()=>(0, _appState.appState).player.querySelector((0, _ytClipper.selectors).video));
await (0, _util.retryUntilTruthyResult)(()=>(0, _appState.appState).video.readyState != 0);
await (0, _util.retryUntilTruthyResult)(()=>(0, _appState.appState).video.videoWidth * (0, _appState.appState).video.videoHeight * (0, _util.getVideoDuration)((0, _ytClipper.platform), (0, _appState.appState).video));
if ((0, _ytClipper.platform) === 'vlive') {
await (0, _util.retryUntilTruthyResult)(()=>!(0, _appState.appState).video.src.startsWith('data:video'));
await (0, _util.retryUntilTruthyResult)(()=>(0, _appState.appState).video.videoWidth * (0, _appState.appState).video.videoHeight * (0, _util.getVideoDuration)((0, _ytClipper.platform), (0, _appState.appState).video));
}
(0, _appState.appState).video.classList.add('yt-clipper-video');
}
},{"./appState":"g0AlP","./platforms/platforms":"1kR7r","./util/util":"99arg","./yt_clipper":"6vE65","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"13t4D":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "navObserver", ()=>navObserver);
parcelHelpers.export(exports, "startNavigationWatcher", ()=>startNavigationWatcher);
parcelHelpers.export(exports, "handleNavigation", ()=>handleNavigation);
parcelHelpers.export(exports, "navResolveInFlight", ()=>navResolveInFlight);
parcelHelpers.export(exports, "clearStaleVideoState", ()=>clearStaleVideoState);
parcelHelpers.export(exports, "showStaleVideoBanner", ()=>showStaleVideoBanner);
parcelHelpers.export(exports, "hideStaleVideoBanner", ()=>hideStaleVideoBanner);
parcelHelpers.export(exports, "staleVideoBannerEl", ()=>staleVideoBannerEl);
var _appState = require("./appState");
var _bootstrap = require("./bootstrap");
var _common = require("./platforms/blockers/common");
var _youtube = require("./platforms/blockers/youtube");
var _navigation = require("./platforms/navigation");
var _platforms = require("./platforms/platforms");
var _ytClipper = require("./yt_clipper");
var _litHtml = require("lit-html");
let navObserver = null;
function startNavigationWatcher() {
if (navObserver) return;
navObserver = (0, _platforms.videoPlatformDataRecords)[0, _ytClipper.platform].createNavObserver();
navObserver.start(()=>{
handleNavigation();
});
}
async function handleNavigation() {
if (!(0, _ytClipper.initOnceCalled)) {
if (navResolveInFlight) return;
navResolveInFlight = true;
(0, _appState.appState).isReady = false;
try {
await (0, _bootstrap.resolvePlayerAndVideo)();
(0, _appState.appState).isReady = true;
} catch (e) {
console.error('yt_clipper: failed to re-resolve player/video after navigation', e);
} finally{
navResolveInFlight = false;
}
return;
}
const loadedVideoID = (0, _appState.appState).settings?.videoID ?? null;
const currentPageVideoID = (0, _navigation.getCurrentPageVideoID)();
if (0, _navigation.isStaleVideo) {
if (loadedVideoID != null && currentPageVideoID != null && currentPageVideoID === loadedVideoID) clearStaleVideoState();
return;
}
if (loadedVideoID != null && currentPageVideoID != null && currentPageVideoID === loadedVideoID) return;
(0, _navigation.setIsStaleVideo)(true);
(0, _common.disableCommonBlockers)();
if ((0, _ytClipper.platform) === (0, _platforms.VideoPlatforms).youtube) (0, _youtube.disableYTBlockers)();
showStaleVideoBanner();
}
let navResolveInFlight = false;
function clearStaleVideoState() {
(0, _navigation.setIsStaleVideo)(false);
hideStaleVideoBanner();
if ((0, _appState.appState).isHotkeysEnabled) {
(0, _common.enableCommonBlockers)();
if ((0, _ytClipper.platform) === (0, _platforms.VideoPlatforms).youtube) (0, _youtube.enableYTBlockers)();
}
}
function StaleVideoBannerTemplate(loadedVideoID) {
return (0, _litHtml.html)`
<div id="ytc-stale-video-banner">
<span class="ytc-stale-banner-icon">!</span>
<div class="ytc-stale-banner-text">
<strong>Video changed</strong>
<span>
yt_clipper was loaded from appState.video with id
<code class="ytc-stale-banner-videoid">${loadedVideoID}</code> and may behave unexpectedly
on other videos. Navigate back to resume, or refresh the page to reload yt_clipper.
</span>
</div>
</div>
`;
}
function showStaleVideoBanner() {
if (staleVideoBannerEl) return;
const loadedVideoID = (0, _appState.appState).settings?.videoID ?? 'unknown';
const container = document.createElement('div');
(0, _litHtml.render)(StaleVideoBannerTemplate(loadedVideoID), container);
staleVideoBannerEl = container.firstElementChild;
(0, _appState.appState).hooks.flashMessage.insertAdjacentElement('afterbegin', staleVideoBannerEl);
}
function hideStaleVideoBanner() {
if (staleVideoBannerEl) {
staleVideoBannerEl.remove();
staleVideoBannerEl = null;
}
}
let staleVideoBannerEl = null;
},{"./appState":"g0AlP","./bootstrap":"bwm01","./platforms/blockers/common":"kg6wl","./platforms/blockers/youtube":"lEer5","./platforms/navigation":"l9QMf","./platforms/platforms":"1kR7r","./yt_clipper":"6vE65","lit-html":"9fQBw","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}],"96bfD":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "blockDangerousUrl", ()=>blockDangerousUrl);
parcelHelpers.export(exports, "identitySanitizer", ()=>identitySanitizer);
parcelHelpers.export(exports, "urlSanitizerFactory", ()=>urlSanitizerFactory);
// Install the URL sanitizer as lit-html's sanitizer factory. Must be called
// before the first `render()` invocation — lit-html caches the factory at
// template instantiation time, so a late install has no effect on templates
// already rendered.
//
// IMPORTANT: lit-html's sanitizer hooks are compiled out of the production
// build via `ENABLE_EXTRA_SECURITY_HOOKS = false`. In production bundles,
// `render.setSanitizer` does not exist — we guard against that or init will
// throw a TypeError. In dev/test builds the hook is present and the
// sanitizer runs. Effective in production only if Parcel is configured to
// resolve `lit-html` → `lit-html/development/lit-html.js` (not the default).
// That's a deliberate tradeoff (bundle size + perf for defense-in-depth) and
// we don't take it — our primary defense is the `local/no-url-attribute-
// interpolation` ESLint rule plus lit-html's built-in structural safety,
// which together prevent dynamic URLs from reaching template bindings at
// dev time and prevent HTML injection at runtime regardless.
parcelHelpers.export(exports, "installLitUrlSanitizer", ()=>installLitUrlSanitizer);
var _litHtml = require("lit-html");
// This sanitizer is defense-in-depth against scheme-based XSS in URL
// attributes committed through lit-html templates. It is NOT a complete
// defense against URL-based attacks:
//
// - Only blocks dangerous SCHEMES (`javascript:` / `data:` / `vbscript:`).
// An attacker-controlled `https://attacker.com/...` URL passes through.
// - Auto-fetch contexts (`<img src>`, `<link href>`, `<iframe src>`,
// `<script src>`, `<video poster>`) still leak Referer/IP/UA/timing to
// the attacker origin and enable third-party CSRF + CSS-selector exfil.
// - Does not cover direct DOM writes (`el.href = x`, `el.setAttribute(...)`)
// or navigation sinks (`location.href = x`, `window.open(x)`) — those are
// caught by the `local/no-url-attribute-interpolation` ESLint rule at dev
// time.
//
// The primary defense is the lint rule, which forbids dynamic URL values
// from reaching URL-context bindings at all. This sanitizer is the runtime
// backstop when that rule is bypassed (e.g. via `eslint-disable`).
//
// If we ever NEED dynamic URLs, the next hardening step is an origin
// allowlist (validate that the URL's origin matches a known-trusted host)
// plus `rel="noreferrer noopener"` on user-clickable `<a>` tags. We have
// no dynamic URLs today, so that complexity is deferred.
// URL-context HTML attributes — when lit-html commits a value to one of these
// attributes, schemes like `javascript:` / `data:` / `vbscript:` navigate or
// execute. Keep in sync with eslint-rules/no-url-attribute-interpolation.ts.
const URL_ATTRS = new Set([
'href',
'src',
'action',
'formaction',
'poster',
'background',
'cite',
'data',
'ping',
'xlink:href',
'xlink:show',
'xlink:actuate'
]);
// URL-valued DOM properties. camelCase `formAction` vs `formaction` attribute.
const URL_PROPS = new Set([
'href',
'src',
'action',
'formAction',
'poster'
]);
// Leading whitespace / C0 controls can be used to bypass naive scheme checks,
// so strip them before testing. Matches DOMPurify's ALLOWED_URI_REGEXP style.
// eslint-disable-next-line no-control-regex
const DANGEROUS_SCHEME = /^[\s\u0000-\u001f]*(javascript|data|vbscript):/i;
function isUrlContext(name, type) {
if (type === 'attribute') return URL_ATTRS.has(name.toLowerCase());
return URL_PROPS.has(name);
}
const blockDangerousUrl = (value)=>{
if (typeof value !== 'string') return value;
if (DANGEROUS_SCHEME.test(value)) {
console.error('[yt_clipper] Blocked dangerous URL scheme in lit template:', value);
return '';
}
return value;
};
const identitySanitizer = (value)=>value;
const urlSanitizerFactory = (_node, name, type)=>isUrlContext(name, type) ? blockDangerousUrl : identitySanitizer;
function installLitUrlSanitizer() {
const setSanitizer = (0, _litHtml.render).setSanitizer;
if (typeof setSanitizer !== 'function') return;
setSanitizer(urlSanitizerFactory);
}
},{"lit-html":"9fQBw","@parcel/transformer-js/src/esmodule-helpers.js":"6ky3m"}]},["6vE65"], "6vE65", "parcelRequiree4af", {})
//# sourceMappingURL=yt_clipper.js.map