Start working on client menu, sort out CSS injection
This commit is contained in:
parent
fcfc0361dc
commit
ef12dc8cc5
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -61,6 +61,7 @@
|
||||
"rescan",
|
||||
"resizer",
|
||||
"resizers",
|
||||
"SCPI",
|
||||
"scrollbar",
|
||||
"shitiness",
|
||||
"smallcaps",
|
||||
|
||||
22
src/common/interfaces/ClientUiMenu.interface.ts
Normal file
22
src/common/interfaces/ClientUiMenu.interface.ts
Normal file
@ -0,0 +1,22 @@
|
||||
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 {
|
||||
anchor: MenuAnchor;
|
||||
activationRadius: number;
|
||||
items: MenuItemConfig[];
|
||||
}
|
||||
196
src/ext/lib/uwui/ClientMenu.ts
Normal file
196
src/ext/lib/uwui/ClientMenu.ts
Normal file
@ -0,0 +1,196 @@
|
||||
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);
|
||||
}
|
||||
|
||||
private createHost() {
|
||||
this.host = document.createElement("div");
|
||||
Object.assign(this.host.style, {
|
||||
position: "fixed",
|
||||
inset: "0",
|
||||
zIndex: "2147483647",
|
||||
// pointerEvents: "none",
|
||||
backgroundColor: "#00000099",
|
||||
});
|
||||
this.host.textContent="SAMPLE TEXT SAMPLE TEXT SAMPLE TEXT"
|
||||
}
|
||||
|
||||
private createShadow() {
|
||||
this.shadow = this.host.attachShadow({ mode: "open" });
|
||||
this.injectStyles();
|
||||
}
|
||||
|
||||
private createMenu() {
|
||||
this.root = document.createElement("div");
|
||||
this.root.className = "menu-root font-mono";
|
||||
|
||||
const trigger = document.createElement("div");
|
||||
trigger.className = "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 = "submenu";
|
||||
|
||||
for (const item of items) {
|
||||
const el = document.createElement("div");
|
||||
el.className = "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 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');
|
||||
|
||||
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;
|
||||
}
|
||||
${cssArr[cssArr.length - 1].split('}', 2)[1]};
|
||||
`;
|
||||
|
||||
// function scopeCss(css: string) {
|
||||
// return css.replace(
|
||||
// /(^|})\s*([^{@}][^{]*){/g,
|
||||
// (_, sep, selector) => `${sep} :host ${selector} {`
|
||||
// );
|
||||
// }
|
||||
|
||||
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,', '')
|
||||
;
|
||||
|
||||
// console.warn('CSS TO APPEND', css);
|
||||
style.textContent = css;
|
||||
this.shadow.appendChild(style);
|
||||
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,9 @@
|
||||
@import '@ui/res/styles/fonts.css';
|
||||
@import '@ui/res/styles/buttons.css';
|
||||
|
||||
@import '@ui/res/styles/player-menu.css';
|
||||
|
||||
|
||||
|
||||
html, body {
|
||||
@apply bg-stone-950 text-stone-300;
|
||||
|
||||
@ -48,7 +48,8 @@
|
||||
"res/icons/*",
|
||||
"res/img/*",
|
||||
"csui/*",
|
||||
"ui/*"
|
||||
"ui/*",
|
||||
"main.css"
|
||||
],
|
||||
"matches": [
|
||||
"*://*/*"
|
||||
|
||||
@ -116,7 +116,7 @@ import ShortcutButton from './components/ShortcutButton.vue';
|
||||
import CommsMixin from '@ui/utils/mixins/CommsMixin.vue';
|
||||
import KeyboardShortcutParserMixin from '@ui/utils/mixins/KeyboardShortcutParserMixin.vue';
|
||||
|
||||
import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators.svg?raw';
|
||||
import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators.svg';
|
||||
import {setupVideoAlignmentIndicatorInteraction, setVideoAlignmentIndicatorState} from '@ui/utils/video-alignment-indicator-handling';
|
||||
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
|
||||
|
||||
|
||||
14
src/ui/res/styles/player-menu.css
Normal file
14
src/ui/res/styles/player-menu.css
Normal file
@ -0,0 +1,14 @@
|
||||
@import "tailwindcss";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
@layer base {
|
||||
.menu-root {
|
||||
@apply font-mono;
|
||||
font-size: 5rem;
|
||||
color: #fa6;
|
||||
|
||||
* {
|
||||
@apply font-mono;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,9 @@
|
||||
--breakpoint-popup: 640px;
|
||||
--breakpoint-window: 960px;
|
||||
|
||||
--font-sans: "Heebo", ui-sans-serif, system-ui;
|
||||
--font-mono: "Source Code Pro", ui-monospace, monospace;
|
||||
|
||||
--color-primary: #ff872c;
|
||||
--color-primary-50: #ffddbe;
|
||||
--color-primary-100: #ffcda0;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { CommandInterface } from '@src/common/interfaces/SettingsInterface';
|
||||
import Settings from '@src/ext/lib/settings/Settings';
|
||||
|
||||
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
|
||||
@ -80,9 +81,6 @@ class MenuItem {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function buildMenu(settings: Settings, executor: (command: string, args?: any) => void) {
|
||||
const menu = new MenuItem(
|
||||
{label: 'Ultrawidify'}
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
"content": [
|
||||
"./src/**/*.{vue,js,ts,jsx,tsx}"
|
||||
],
|
||||
"safelist": [
|
||||
{
|
||||
"pattern": "/^(bg|text|hover:bg|rounded|p|px|py)-/"
|
||||
}
|
||||
],
|
||||
"theme": {
|
||||
"extend": {
|
||||
"screens": {
|
||||
|
||||
@ -36,6 +36,20 @@ const config = {
|
||||
},
|
||||
module: {
|
||||
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$/,
|
||||
exclude: /node_modules/,
|
||||
@ -68,34 +82,45 @@ const config = {
|
||||
},
|
||||
{
|
||||
test: /\.(sc|c|postc)ss$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
oneOf: [
|
||||
// Content script exports
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
// Uncomment if you want CSS modules
|
||||
// modules: {
|
||||
// localIdentName: "[name]-[hash]"
|
||||
// }
|
||||
}
|
||||
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'),
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
'postcss-loader',
|
||||
// For proper pages
|
||||
{
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
// Uncomment if you want CSS modules
|
||||
// modules: {
|
||||
// localIdentName: "[name]-[hash]"
|
||||
// }
|
||||
}
|
||||
},
|
||||
'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: [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user