Compare commits
No commits in common. "af08c5094d0438b8ff60e6a83e7249026dbf7b2c" and "fcfc0361dc58e4b7a55b9068f00c33cf6eb33f15" have entirely different histories.
af08c5094d
...
fcfc0361dc
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -61,7 +61,6 @@
|
|||||||
"rescan",
|
"rescan",
|
||||||
"resizer",
|
"resizer",
|
||||||
"resizers",
|
"resizers",
|
||||||
"SCPI",
|
|
||||||
"scrollbar",
|
"scrollbar",
|
||||||
"shitiness",
|
"shitiness",
|
||||||
"smallcaps",
|
"smallcaps",
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
export interface MenuItemConfig {
|
|
||||||
label: string;
|
|
||||||
subitems?: MenuItemConfig[];
|
|
||||||
action?: () => void;
|
|
||||||
customHTML?: HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MenuAnchor =
|
|
||||||
| "LeftCenter"
|
|
||||||
| "RightCenter"
|
|
||||||
| "TopCenter"
|
|
||||||
| "BottomCenter"
|
|
||||||
| "TopLeft"
|
|
||||||
| "TopRight"
|
|
||||||
| "BottomLeft"
|
|
||||||
| "BottomRight";
|
|
||||||
|
|
||||||
export interface MenuConfig {
|
|
||||||
isGlobal?: boolean;
|
|
||||||
anchor: MenuAnchor;
|
|
||||||
activationRadius: number;
|
|
||||||
items: MenuItemConfig[];
|
|
||||||
}
|
|
||||||
@ -1,201 +0,0 @@
|
|||||||
import { MenuConfig, MenuItemConfig } from '@src/common/interfaces/ClientUiMenu.interface';
|
|
||||||
import extensionCss from '@src/main.css?inline';
|
|
||||||
|
|
||||||
export class ClientMenu {
|
|
||||||
|
|
||||||
private host!: HTMLDivElement;
|
|
||||||
private shadow!: ShadowRoot;
|
|
||||||
private root!: HTMLDivElement;
|
|
||||||
private visible = false;
|
|
||||||
|
|
||||||
constructor(private config: MenuConfig) {}
|
|
||||||
|
|
||||||
mount(anchorElement: HTMLElement) {
|
|
||||||
this.createHost();
|
|
||||||
this.createShadow();
|
|
||||||
this.createMenu();
|
|
||||||
this.position(anchorElement);
|
|
||||||
this.bindGlobalMouse(anchorElement);
|
|
||||||
|
|
||||||
// document.documentElement.appendChild(this.host);
|
|
||||||
anchorElement.appendChild(this.host);
|
|
||||||
}
|
|
||||||
|
|
||||||
private injectStyles() {
|
|
||||||
const style = document.createElement("style");
|
|
||||||
console.warn('imported styles: imported CSS (raw):', typeof extensionCss, extensionCss);
|
|
||||||
let css = extensionCss
|
|
||||||
.trim()
|
|
||||||
.replace(/'html, body \{/g, ':host {')
|
|
||||||
.replace(/'body \{/g, ':host {')
|
|
||||||
;
|
|
||||||
|
|
||||||
// this is bad but I can't be bothered to do it the proper way
|
|
||||||
const cssArr: string[] = css.split('@font-face');
|
|
||||||
const cssRemainder = cssArr[cssArr.length - 1].split('}').slice(1).join('}');
|
|
||||||
|
|
||||||
css = `
|
|
||||||
${cssArr[0]}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Heebo";
|
|
||||||
src: url(__FONT_HEEBO__) format("truetype-variations");
|
|
||||||
font-weight: 100 900;
|
|
||||||
font-stretch: 75% 125%;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Source Code Pro";
|
|
||||||
src: url(__FONT_SCP__) format("truetype-variations");
|
|
||||||
font-weight: 200 900;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Source Code Pro";
|
|
||||||
src: url(__FONT_SCPI__) format("truetype-variations");
|
|
||||||
font-weight: 200 900;
|
|
||||||
font-style: italic;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
${cssRemainder};
|
|
||||||
`;
|
|
||||||
|
|
||||||
console.log('+————————————————————————————————————', cssRemainder)
|
|
||||||
|
|
||||||
css = css
|
|
||||||
.replace('__FONT_HEEBO__', chrome.runtime.getURL('/ui/res/fonts/Heebo.ttf'))
|
|
||||||
.replace('__FONT_SCP__', chrome.runtime.getURL('/ui/res/fonts/SourceCodePro.ttf'))
|
|
||||||
.replace('__FONT_SCPI__', chrome.runtime.getURL('/ui/res/fonts/SourceCodePro-Italic.ttf'))
|
|
||||||
.replaceAll('html,', '')
|
|
||||||
;
|
|
||||||
|
|
||||||
style.textContent = css;
|
|
||||||
this.shadow.appendChild(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private createHost() {
|
|
||||||
this.host = document.createElement("div");
|
|
||||||
this.host.classList.add('uw-ultrawidify-container-root');
|
|
||||||
|
|
||||||
Object.assign(this.host.style, {
|
|
||||||
position: this.config.isGlobal ? "fixed" : "absolute",
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
border: 0,
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
zIndex: this.config.isGlobal ? "2147483647" : "2147483640",
|
|
||||||
// pointerEvents: "none",
|
|
||||||
backgroundColor: "#00000099",
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('UI host created:', this.host);
|
|
||||||
}
|
|
||||||
|
|
||||||
private createShadow() {
|
|
||||||
this.shadow = this.host.attachShadow({ mode: "open" });
|
|
||||||
this.injectStyles();
|
|
||||||
}
|
|
||||||
|
|
||||||
private createMenu() {
|
|
||||||
this.root = document.createElement("div");
|
|
||||||
this.root.className = "uw-menu-root font-mono";
|
|
||||||
|
|
||||||
const trigger = document.createElement("div");
|
|
||||||
trigger.className = "uw-menu-trigger";
|
|
||||||
trigger.textContent = "☰";
|
|
||||||
|
|
||||||
const submenu = this.buildSubmenu(this.config.items);
|
|
||||||
|
|
||||||
trigger.addEventListener("mouseenter", () => this.show());
|
|
||||||
this.root.addEventListener("mouseleave", () => this.hide());
|
|
||||||
|
|
||||||
this.root.append(trigger, submenu);
|
|
||||||
this.shadow.appendChild(this.root);
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildSubmenu(items: MenuItemConfig[]): HTMLDivElement {
|
|
||||||
const menu = document.createElement("div");
|
|
||||||
menu.className = "uw-submenu";
|
|
||||||
|
|
||||||
for (const item of items) {
|
|
||||||
const el = document.createElement("div");
|
|
||||||
el.className = "uw-menu-item";
|
|
||||||
|
|
||||||
if (item.customHTML) {
|
|
||||||
el.appendChild(item.customHTML);
|
|
||||||
} else {
|
|
||||||
el.textContent = item.label;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.action) {
|
|
||||||
el.addEventListener("click", e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
item.action?.();
|
|
||||||
this.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.subitems) {
|
|
||||||
el.appendChild(this.buildSubmenu(item.subitems));
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.appendChild(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
private position(anchorEl: HTMLElement) {
|
|
||||||
const rect = anchorEl.getBoundingClientRect();
|
|
||||||
const r = this.root.style;
|
|
||||||
|
|
||||||
// switch (this.config.anchor) {
|
|
||||||
// case "LeftCenter":
|
|
||||||
// r.left = "0";
|
|
||||||
// r.top = "50%";
|
|
||||||
// r.transform = "translateY(-50%)";
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case "TopLeft":
|
|
||||||
// r.left = "0";
|
|
||||||
// r.top = "0";
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// // others are trivial extensions
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
private bindGlobalMouse(anchorEl: HTMLElement) {
|
|
||||||
const rect = anchorEl.getBoundingClientRect();
|
|
||||||
const cx = rect.left + rect.width / 2;
|
|
||||||
const cy = rect.top + rect.height / 2;
|
|
||||||
|
|
||||||
document.addEventListener("mousemove", e => {
|
|
||||||
const d = Math.hypot(e.clientX - cx, e.clientY - cy);
|
|
||||||
if (d < this.config.activationRadius) {
|
|
||||||
this.show();
|
|
||||||
} else if (!this.root.matches(":hover")) {
|
|
||||||
this.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private show() {
|
|
||||||
if (!this.visible) {
|
|
||||||
this.visible = true;
|
|
||||||
this.root.classList.add("visible");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private hide() {
|
|
||||||
if (this.visible) {
|
|
||||||
this.visible = false;
|
|
||||||
this.root.classList.remove("visible");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +1,4 @@
|
|||||||
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
|
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
|
||||||
import { ClientMenu } from './ClientMenu';
|
|
||||||
import EventBus from '../EventBus';
|
|
||||||
import PlayerData from '../video-data/PlayerData';
|
|
||||||
import { SiteSettings } from '../settings/SiteSettings';
|
|
||||||
|
|
||||||
if (process.env.CHANNEL !== 'stable'){
|
if (process.env.CHANNEL !== 'stable'){
|
||||||
console.info("Loading: UI");
|
console.info("Loading: UI");
|
||||||
@ -17,38 +13,21 @@ const csuiVersions = {
|
|||||||
const MAX_IFRAME_ERROR_COUNT = 5;
|
const MAX_IFRAME_ERROR_COUNT = 5;
|
||||||
|
|
||||||
class UI {
|
class UI {
|
||||||
|
|
||||||
|
|
||||||
isGlobal: boolean;
|
|
||||||
isIframe: boolean;
|
|
||||||
private eventBus: EventBus;
|
|
||||||
private playerData: PlayerData;
|
|
||||||
private uiSettings: any;
|
|
||||||
private siteSettings: SiteSettings;
|
|
||||||
private disablePointerEvents: boolean = false;
|
|
||||||
|
|
||||||
private iframeErrorCount: number = 0;
|
|
||||||
private iframeConfirmed: boolean = false;
|
|
||||||
private iframeRejected: boolean = false;
|
|
||||||
private delayedDestroyTimer: any = undefined;
|
|
||||||
|
|
||||||
private csuiScheme;
|
|
||||||
private extensionBase: string;
|
|
||||||
|
|
||||||
// These can prolly be removed:
|
|
||||||
lastProbeResponseTs: number
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public interfaceId,
|
interfaceId,
|
||||||
public uiConfig, // {parentElement?, eventBus?, isGlobal?, playerData}
|
uiConfig, // {parentElement?, eventBus?, isGlobal?, playerData}
|
||||||
) {
|
) {
|
||||||
|
this.interfaceId = interfaceId;
|
||||||
|
this.uiConfig = uiConfig;
|
||||||
this.lastProbeResponseTs = null;
|
this.lastProbeResponseTs = null;
|
||||||
|
|
||||||
this.isGlobal = uiConfig.isGlobal ?? false;
|
this.isGlobal = uiConfig.isGlobal ?? false;
|
||||||
this.isIframe = window.self !== window.top;
|
this.isIframe = window.self !== window.top;
|
||||||
|
|
||||||
this.eventBus = uiConfig.eventBus;
|
this.eventBus = uiConfig.eventBus;
|
||||||
|
this.disablePointerEvents = false;
|
||||||
|
|
||||||
|
this.saveState = undefined;
|
||||||
this.playerData = uiConfig.playerData;
|
this.playerData = uiConfig.playerData;
|
||||||
this.uiSettings = uiConfig.uiSettings;
|
this.uiSettings = uiConfig.uiSettings;
|
||||||
this.siteSettings = uiConfig.siteSettings;
|
this.siteSettings = uiConfig.siteSettings;
|
||||||
@ -66,10 +45,8 @@ class UI {
|
|||||||
this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, "");
|
this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, "");
|
||||||
|
|
||||||
// UI will be initialized when setUiVisibility is called
|
// UI will be initialized when setUiVisibility is called
|
||||||
if (!this.isGlobal) {
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
canRun() {
|
canRun() {
|
||||||
if (this.isGlobal) {
|
if (this.isGlobal) {
|
||||||
@ -117,12 +94,6 @@ class UI {
|
|||||||
const random = Math.round(Math.random() * 69420);
|
const random = Math.round(Math.random() * 69420);
|
||||||
const uwid = `uw-ultrawidify-${this.interfaceId}-root-${random}`
|
const uwid = `uw-ultrawidify-${this.interfaceId}-root-${random}`
|
||||||
|
|
||||||
if (this.uiConfig.parentElement) {
|
|
||||||
const uwMenu = new ClientMenu({isGlobal: this.isGlobal, anchor: "LeftCenter", items: [{label: 'test'}]});
|
|
||||||
uwMenu.mount(this.uiConfig.parentElement);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
|
|
||||||
const rootDiv = document.createElement('div');
|
const rootDiv = document.createElement('div');
|
||||||
|
|
||||||
if (this.uiConfig.additionalStyle) {
|
if (this.uiConfig.additionalStyle) {
|
||||||
10
src/main.css
10
src/main.css
@ -6,22 +6,12 @@
|
|||||||
@import '@ui/res/styles/fonts.css';
|
@import '@ui/res/styles/fonts.css';
|
||||||
@import '@ui/res/styles/buttons.css';
|
@import '@ui/res/styles/buttons.css';
|
||||||
|
|
||||||
@import '@ui/res/styles/player-menu.css';
|
|
||||||
|
|
||||||
|
|
||||||
:host {
|
|
||||||
@apply bg-stone-950 text-stone-300;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
html, body {
|
html, body {
|
||||||
@apply bg-stone-950 text-stone-300;
|
@apply bg-stone-950 text-stone-300;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 999px) {
|
@media (max-width: 999px) {
|
||||||
:host {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
html, body {
|
html, body {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,8 +48,7 @@
|
|||||||
"res/icons/*",
|
"res/icons/*",
|
||||||
"res/img/*",
|
"res/img/*",
|
||||||
"csui/*",
|
"csui/*",
|
||||||
"ui/*",
|
"ui/*"
|
||||||
"main.css"
|
|
||||||
],
|
],
|
||||||
"matches": [
|
"matches": [
|
||||||
"*://*/*"
|
"*://*/*"
|
||||||
|
|||||||
@ -116,7 +116,7 @@ import ShortcutButton from './components/ShortcutButton.vue';
|
|||||||
import CommsMixin from '@ui/utils/mixins/CommsMixin.vue';
|
import CommsMixin from '@ui/utils/mixins/CommsMixin.vue';
|
||||||
import KeyboardShortcutParserMixin from '@ui/utils/mixins/KeyboardShortcutParserMixin.vue';
|
import KeyboardShortcutParserMixin from '@ui/utils/mixins/KeyboardShortcutParserMixin.vue';
|
||||||
|
|
||||||
import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators.svg';
|
import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators.svg?raw';
|
||||||
import {setupVideoAlignmentIndicatorInteraction, setVideoAlignmentIndicatorState} from '@ui/utils/video-alignment-indicator-handling';
|
import {setupVideoAlignmentIndicatorInteraction, setVideoAlignmentIndicatorState} from '@ui/utils/video-alignment-indicator-handling';
|
||||||
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
|
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
@import "tailwindcss/utilities";
|
|
||||||
|
|
||||||
.uw-menu-root {
|
|
||||||
@apply h-full w-full font-mono text-stone-200 text-[16px];
|
|
||||||
|
|
||||||
* {
|
|
||||||
@apply font-mono text-stone-200 text-[1em];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,9 +5,6 @@
|
|||||||
--breakpoint-popup: 640px;
|
--breakpoint-popup: 640px;
|
||||||
--breakpoint-window: 960px;
|
--breakpoint-window: 960px;
|
||||||
|
|
||||||
--font-sans: "Heebo", ui-sans-serif, system-ui;
|
|
||||||
--font-mono: "Source Code Pro", ui-monospace, monospace;
|
|
||||||
|
|
||||||
--color-primary: #ff872c;
|
--color-primary: #ff872c;
|
||||||
--color-primary-50: #ffddbe;
|
--color-primary-50: #ffddbe;
|
||||||
--color-primary-100: #ffcda0;
|
--color-primary-100: #ffcda0;
|
||||||
|
|||||||
281
src/ui/utils/svg.menu.ts
Normal file
281
src/ui/utils/svg.menu.ts
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
import { CommandInterface } from '@src/common/interfaces/SettingsInterface';
|
||||||
|
import Settings from '@src/ext/lib/settings/Settings';
|
||||||
|
|
||||||
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
|
|
||||||
|
class MenuItemConfig {
|
||||||
|
textSize: number;
|
||||||
|
width: number
|
||||||
|
height: number;
|
||||||
|
|
||||||
|
xPad: number;
|
||||||
|
yPad: number;
|
||||||
|
|
||||||
|
constructor(options: {textSize: number, width: number, xPad: number, yPad: number}) {
|
||||||
|
this.textSize = options.textSize;
|
||||||
|
this.xPad = options.xPad;
|
||||||
|
this.yPad = options.yPad;
|
||||||
|
this.height = options.textSize + 2 * options.yPad;
|
||||||
|
this.width = options.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MenuItem {
|
||||||
|
label: string;
|
||||||
|
hasHover: boolean;
|
||||||
|
clickAction: () => void;
|
||||||
|
parent?: MenuItem;
|
||||||
|
children?: MenuItem[];
|
||||||
|
|
||||||
|
|
||||||
|
svg: any;
|
||||||
|
|
||||||
|
static config: MenuItemConfig;
|
||||||
|
|
||||||
|
static fromCommand(command: CommandInterface, executor: (command: string, args?: any) => void, parent?: MenuItem) {
|
||||||
|
return new MenuItem({
|
||||||
|
label: command.label,
|
||||||
|
clickAction: () => {executor(command.action, command.arguments)},
|
||||||
|
parent: parent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(data: any) {
|
||||||
|
this.label = data.label;
|
||||||
|
this.clickAction = data.clickAction;
|
||||||
|
this.hasHover = false;
|
||||||
|
this.children = [] as MenuItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
private createSvg() {
|
||||||
|
const group = document.createElementNS(SVG_NS, "g");
|
||||||
|
|
||||||
|
const textElem = document.createElementNS(SVG_NS, "text");
|
||||||
|
textElem.textContent = this.label;
|
||||||
|
textElem.setAttribute('x', `${MenuItem.config.xPad}`);
|
||||||
|
textElem.setAttribute('y', `${MenuItem.config.yPad}`);
|
||||||
|
group.appendChild(textElem);
|
||||||
|
|
||||||
|
// Measure text width safely
|
||||||
|
// const width = textElem.getComputedTextLength() + 2 * hPadding;
|
||||||
|
// const height = fontSize + 2 * vPadding;
|
||||||
|
|
||||||
|
// Background rectangle
|
||||||
|
const rect = document.createElementNS(SVG_NS, "rect");
|
||||||
|
rect.setAttribute('x', '0');
|
||||||
|
rect.setAttribute('y', `0`);
|
||||||
|
rect.setAttribute('width', `${MenuItem.config.width}`);
|
||||||
|
rect.setAttribute('height', `${MenuItem.config.height}`);
|
||||||
|
group.insertBefore(rect, textElem);
|
||||||
|
|
||||||
|
this.svg = group;
|
||||||
|
}
|
||||||
|
|
||||||
|
addSubitem(subitem: MenuItem) {
|
||||||
|
this.children.push(subitem);
|
||||||
|
if (!subitem.parent) {
|
||||||
|
subitem.parent = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function buildMenu(settings: Settings, executor: (command: string, args?: any) => void) {
|
||||||
|
const menu = new MenuItem(
|
||||||
|
{label: 'Ultrawidify'}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const topLevelItems: { label: string; submenu?: CommandInterface[]; isZoom?: boolean }[] = [
|
||||||
|
{ label: "Ultrawidify" },
|
||||||
|
...(settings.active.commands.crop ? [{ label: "Crop", submenu: settings.active.commands.crop }] : []),
|
||||||
|
...(settings.active.commands.stretch ? [{ label: "Stretch", submenu: settings.active.commands.stretch }] : []),
|
||||||
|
...(settings.active.commands.zoom ? [{ label: "Zoom", submenu: settings.active.commands.zoom, isZoom: true }] : []),
|
||||||
|
{ label: "Alignment", submenu: [] },
|
||||||
|
{ label: "Extension settings" },
|
||||||
|
{ label: "Incorrect cropping?" },
|
||||||
|
{ label: "Report problem" }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function createMenuItem(label) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function createSvgMenu(playerEl: HTMLElement, settings: Settings) {
|
||||||
|
if (!settings) {
|
||||||
|
console.warn('trying to create menu, but settings are not defined:', settings, 'player el:', playerEl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Heebo';
|
||||||
|
src: url(${chrome.runtime.getURL('/ui/res/fonts/Heebo.tff')}) format('truetype');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'SourceCodePro';
|
||||||
|
src: url(${chrome.runtime.getURL('/ui/res/fonts/SourceCodePro.tff')}) format('truetype');
|
||||||
|
}
|
||||||
|
#_uw_player_menu { width:100%; height:100%; position:absolute; top:0; left:0; pointer-events:none; }
|
||||||
|
#_uw_player_menu {
|
||||||
|
.menu { pointer-events:auto; }
|
||||||
|
.menu-item text { font-family: 'Heebo'; font-size:14px; fill:#fff; }
|
||||||
|
.submenu { display:none; }
|
||||||
|
.menu-item:hover > .submenu { display:block; }
|
||||||
|
.hover-zone { fill:transparent; pointer-events:auto; }
|
||||||
|
.menu-item rect { fill:#222; rx:4; }
|
||||||
|
.menu-item:hover > rect { fill:#444; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const svg = document.createElementNS(SVG_NS, "svg");
|
||||||
|
svg.setAttribute('id', '_uw_player_menu');
|
||||||
|
svg.append(style);
|
||||||
|
playerEl.appendChild(svg);
|
||||||
|
// shadow.appendChild(svg);
|
||||||
|
|
||||||
|
|
||||||
|
// menuGroup.setAttribute('class', 'menu');
|
||||||
|
svg.appendChild(menuGroup);
|
||||||
|
|
||||||
|
const fontSize = 14;
|
||||||
|
const vPadding = fontSize * 0.4;
|
||||||
|
const hPadding = fontSize * 0.6;
|
||||||
|
|
||||||
|
let currentY = 0;
|
||||||
|
|
||||||
|
// --- Top level items ---
|
||||||
|
const topLevelItems: { label: string; submenu?: CommandInterface[]; isZoom?: boolean }[] = [
|
||||||
|
{ label: "Ultrawidify" },
|
||||||
|
...(settings.active.commands.crop ? [{ label: "Crop", submenu: settings.active.commands.crop }] : []),
|
||||||
|
...(settings.active.commands.stretch ? [{ label: "Stretch", submenu: settings.active.commands.stretch }] : []),
|
||||||
|
...(settings.active.commands.zoom ? [{ label: "Zoom", submenu: settings.active.commands.zoom, isZoom: true }] : []),
|
||||||
|
{ label: "Alignment", submenu: [] },
|
||||||
|
{ label: "Extension settings" },
|
||||||
|
{ label: "Incorrect cropping?" },
|
||||||
|
{ label: "Report problem" }
|
||||||
|
];
|
||||||
|
|
||||||
|
topLevelItems.forEach(item => {
|
||||||
|
const itemGroup = document.createElementNS(SVG_NS, "g");
|
||||||
|
itemGroup.setAttribute('class','menu-item');
|
||||||
|
menuGroup.appendChild(itemGroup);
|
||||||
|
|
||||||
|
// Text element
|
||||||
|
const textElem = document.createElementNS(SVG_NS, "text");
|
||||||
|
textElem.textContent = item.label;
|
||||||
|
textElem.setAttribute('x', `${hPadding}`);
|
||||||
|
textElem.setAttribute('y', `${currentY + fontSize}`);
|
||||||
|
itemGroup.appendChild(textElem);
|
||||||
|
|
||||||
|
// Measure text width safely
|
||||||
|
const width = textElem.getComputedTextLength() + 2 * hPadding;
|
||||||
|
const height = fontSize + 2 * vPadding;
|
||||||
|
|
||||||
|
// Background rectangle
|
||||||
|
const rect = document.createElementNS(SVG_NS, "rect");
|
||||||
|
rect.setAttribute('x', '0');
|
||||||
|
rect.setAttribute('y', `${currentY}`);
|
||||||
|
rect.setAttribute('width', `${width}`);
|
||||||
|
rect.setAttribute('height', `${height}`);
|
||||||
|
itemGroup.insertBefore(rect, textElem);
|
||||||
|
|
||||||
|
// --- Submenu ---
|
||||||
|
if(item.submenu && item.submenu.length > 0) {
|
||||||
|
const submenuGroup = document.createElementNS(SVG_NS, "g");
|
||||||
|
submenuGroup.setAttribute('class','submenu');
|
||||||
|
itemGroup.appendChild(submenuGroup);
|
||||||
|
|
||||||
|
let subY = currentY;
|
||||||
|
item.submenu.forEach(cmd => {
|
||||||
|
const subGroup = document.createElementNS(SVG_NS, "g");
|
||||||
|
subGroup.setAttribute('class','menu-item');
|
||||||
|
submenuGroup.appendChild(subGroup);
|
||||||
|
|
||||||
|
// Submenu text
|
||||||
|
const subText = document.createElementNS(SVG_NS, "text");
|
||||||
|
subText.textContent = cmd.label + (cmd.shortcut ? ` (${cmd.shortcut.key})` : '');
|
||||||
|
subText.setAttribute('x', `${width + hPadding}`);
|
||||||
|
subText.setAttribute('y', `${subY + fontSize}`);
|
||||||
|
subGroup.appendChild(subText);
|
||||||
|
|
||||||
|
const subWidth = subText.getComputedTextLength() + 2*hPadding;
|
||||||
|
const subHeight = fontSize + 2*vPadding;
|
||||||
|
|
||||||
|
const subRect = document.createElementNS(SVG_NS, "rect");
|
||||||
|
subRect.setAttribute('x', `${width}`);
|
||||||
|
subRect.setAttribute('y', `${subY}`);
|
||||||
|
subRect.setAttribute('width', `${subWidth}`);
|
||||||
|
subRect.setAttribute('height', `${subHeight}`);
|
||||||
|
subGroup.insertBefore(subRect, subText);
|
||||||
|
|
||||||
|
subY += subHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zoom slider
|
||||||
|
if(item.isZoom) {
|
||||||
|
const sliderGroup = document.createElementNS(SVG_NS, "foreignObject");
|
||||||
|
sliderGroup.setAttribute('x', `${width}`);
|
||||||
|
sliderGroup.setAttribute('y', `${subY}`);
|
||||||
|
sliderGroup.setAttribute('width', '150');
|
||||||
|
sliderGroup.setAttribute('height', '40');
|
||||||
|
sliderGroup.innerHTML = `<div style="width:100%;height:100%;background:#333;color:#fff;font-family:Heebo;padding:5px;">Zoom slider placeholder</div>`;
|
||||||
|
submenuGroup.appendChild(sliderGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover retention zone
|
||||||
|
const hoverZone = document.createElementNS(SVG_NS, "rect");
|
||||||
|
hoverZone.setAttribute('class','hover-zone');
|
||||||
|
hoverZone.setAttribute('x', `${width}`);
|
||||||
|
hoverZone.setAttribute('y', `${currentY}`);
|
||||||
|
hoverZone.setAttribute('width', '150');
|
||||||
|
hoverZone.setAttribute('height', `${subY - currentY}`);
|
||||||
|
itemGroup.appendChild(hoverZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentY += height;
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Trigger logic ---
|
||||||
|
const activation = settings.active.ui.inPlayer.activation;
|
||||||
|
const triggerZone = settings.active.ui.inPlayer.triggerZoneDimensions;
|
||||||
|
const distanceOptions = settings.active.ui.inPlayer.distanceOptions;
|
||||||
|
|
||||||
|
function updateMenuVisibility(x: number, y: number) {
|
||||||
|
let show = false;
|
||||||
|
const rect = playerEl.getBoundingClientRect();
|
||||||
|
|
||||||
|
if(activation === 'player') {
|
||||||
|
if(x >= 0 && x <= rect.width && y >= 0 && y <= rect.height) show = true;
|
||||||
|
} else if(activation === 'trigger-zone' && triggerZone) {
|
||||||
|
const tx = rect.width * (triggerZone.offsetX / 100);
|
||||||
|
const ty = rect.height * (triggerZone.offsetY / 100);
|
||||||
|
if(x >= tx && x <= tx + triggerZone.width && y >= ty && y <= ty + triggerZone.height) show = true;
|
||||||
|
} else if(activation === 'distance' && distanceOptions) {
|
||||||
|
const dist = distanceOptions.distance / 100 * (distanceOptions.relativeTo === 'width' ? rect.width : rect.height);
|
||||||
|
const cx = rect.width/2;
|
||||||
|
const cy = rect.height/2;
|
||||||
|
if(Math.sqrt((x-cx)**2 + (y-cy)**2) <= dist) show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
menuGroup.style.display = show ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.addEventListener('mousemove', e => {
|
||||||
|
const rect = svg.getBoundingClientRect();
|
||||||
|
const x = e.clientX - rect.left;
|
||||||
|
const y = e.clientY - rect.top;
|
||||||
|
updateMenuVisibility(x,y);
|
||||||
|
});
|
||||||
|
|
||||||
|
menuGroup.style.display = 'none';
|
||||||
|
return { svg, menuGroup };
|
||||||
|
}
|
||||||
@ -1,39 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
"./src/**/*.{vue,js,ts,jsx,tsx}"
|
|
||||||
],
|
|
||||||
safelist: [
|
|
||||||
{
|
|
||||||
pattern: /^uw-/,
|
|
||||||
variants: ['hover', 'focus', 'active', 'disabled'],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
screens: {
|
|
||||||
"popup-sm": {"max": "639px"},
|
|
||||||
"popup-lg": {"min": "640px", "max": "899px"},
|
|
||||||
window: {"min": "900px"}
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
primary: {
|
|
||||||
50: "#ffddbe",
|
|
||||||
100: "#ffcda0",
|
|
||||||
200: "#ffbd83",
|
|
||||||
300: "#ffac66",
|
|
||||||
400: "#ff9a4a",
|
|
||||||
500: "#ff872c",
|
|
||||||
600: "#de7622",
|
|
||||||
700: "#be6518",
|
|
||||||
800: "#9f540e",
|
|
||||||
900: "#824406",
|
|
||||||
950: "#663400"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fontFamily: {
|
|
||||||
sans: ["Heebo", "ui-sans-serif", "system-ui"],
|
|
||||||
mono: ["Source Code Pro", "ui-monospace", "monospace"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
33
tailwind.config.json
Normal file
33
tailwind.config.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"content": [
|
||||||
|
"./src/**/*.{vue,js,ts,jsx,tsx}"
|
||||||
|
],
|
||||||
|
"theme": {
|
||||||
|
"extend": {
|
||||||
|
"screens": {
|
||||||
|
"popup-sm": {"max": "639px"},
|
||||||
|
"popup-lg": {"min": "640px", "max": "899px"},
|
||||||
|
"window": {"min": "900px"}
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"primary": {
|
||||||
|
"50": "#ffddbe",
|
||||||
|
"100": "#ffcda0",
|
||||||
|
"200": "#ffbd83",
|
||||||
|
"300": "#ffac66",
|
||||||
|
"400": "#ff9a4a",
|
||||||
|
"500": "#ff872c",
|
||||||
|
"600": "#de7622",
|
||||||
|
"700": "#be6518",
|
||||||
|
"800": "#9f540e",
|
||||||
|
"900": "#824406",
|
||||||
|
"950": "#663400"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fontFamily": {
|
||||||
|
"sans": ["Heebo", "ui-sans-serif", "system-ui"],
|
||||||
|
"mono": ["Source Code Pro", "ui-monospace", "monospace"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,20 +36,6 @@ const config = {
|
|||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
|
||||||
test: /\.(png|jpg|webp|gif|svg|ico)$/,
|
|
||||||
type: 'asset/resource',
|
|
||||||
generator: {
|
|
||||||
filename: '[path][name][ext]' // Webpack 5 uses generator.filename
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(woff(2)?)$/,
|
|
||||||
type: 'asset/resource',
|
|
||||||
generator: {
|
|
||||||
filename: '[path][name][ext]'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
@ -82,29 +68,6 @@ const config = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(sc|c|postc)ss$/,
|
test: /\.(sc|c|postc)ss$/,
|
||||||
oneOf: [
|
|
||||||
// Content script exports
|
|
||||||
{
|
|
||||||
resourceQuery: query => query && query.includes('inline'),
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: 'css-loader',
|
|
||||||
options: {
|
|
||||||
exportType: "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'postcss-loader',
|
|
||||||
options: {
|
|
||||||
postcssOptions: {
|
|
||||||
config: path.resolve(__dirname, 'postcss.config.js'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// For proper pages
|
|
||||||
{
|
|
||||||
use: [
|
use: [
|
||||||
'vue-style-loader',
|
'vue-style-loader',
|
||||||
{
|
{
|
||||||
@ -118,9 +81,21 @@ const config = {
|
|||||||
},
|
},
|
||||||
'postcss-loader',
|
'postcss-loader',
|
||||||
]
|
]
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpg|webp|gif|svg|ico)$/,
|
||||||
|
type: 'asset/resource',
|
||||||
|
generator: {
|
||||||
|
filename: '[path][name][ext]' // Webpack 5 uses generator.filename
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff(2)?)$/,
|
||||||
|
type: 'asset/resource',
|
||||||
|
generator: {
|
||||||
|
filename: '[path][name][ext]'
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user