Reestruturação dos arquivos e pastas do código da extensão
This commit is contained in:
328
src/content/shell-view.js
Normal file
328
src/content/shell-view.js
Normal file
@@ -0,0 +1,328 @@
|
||||
(() => {
|
||||
function create({
|
||||
IDS,
|
||||
CLASSES,
|
||||
USER_BUTTON_LABEL,
|
||||
normalizeText,
|
||||
isVisible,
|
||||
}) {
|
||||
function applyDialogTheme(overlay, theme) {
|
||||
const dialog = overlay?.querySelector(`#${IDS.dialog}`);
|
||||
|
||||
if (!dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
dialog.setAttribute("data-theme", theme);
|
||||
}
|
||||
|
||||
function ensureStyles() {
|
||||
// Styles are loaded by manifest.json. This function remains as a stable callsite.
|
||||
}
|
||||
|
||||
function applyTheme(header, functionsContainer, anchor, mount, overlay) {
|
||||
const headerStyle = window.getComputedStyle(header);
|
||||
const functionsStyle = window.getComputedStyle(functionsContainer);
|
||||
const sourceBackground = resolveBackgroundColor(header, headerStyle);
|
||||
const sourceForeground = resolveForegroundColor(
|
||||
header,
|
||||
functionsContainer,
|
||||
anchor,
|
||||
headerStyle,
|
||||
functionsStyle
|
||||
);
|
||||
const workbenchHeader = mix(
|
||||
sourceBackground,
|
||||
{ r: 75, g: 51, b: 93, a: 1 },
|
||||
0.52
|
||||
);
|
||||
const workbenchAccent = mix(
|
||||
workbenchHeader,
|
||||
{ r: 108, g: 77, b: 128, a: 1 },
|
||||
0.38
|
||||
);
|
||||
const focusColor = mix(sourceForeground, workbenchHeader, 0.28);
|
||||
const themeTargets = [mount, overlay].filter(Boolean);
|
||||
|
||||
for (const target of themeTargets) {
|
||||
target.style.setProperty(
|
||||
"--arch-panel-bg",
|
||||
toColor(mix(sourceBackground, sourceForeground, 0.12))
|
||||
);
|
||||
target.style.setProperty(
|
||||
"--arch-panel-bg-hover",
|
||||
toColor(mix(sourceBackground, sourceForeground, 0.22))
|
||||
);
|
||||
target.style.setProperty(
|
||||
"--arch-panel-border",
|
||||
toColor(mix(sourceBackground, sourceForeground, 0.32))
|
||||
);
|
||||
target.style.setProperty("--arch-panel-fg", toColor(sourceForeground, 1));
|
||||
target.style.setProperty("--arch-panel-focus", toColor(focusColor, 0.94));
|
||||
target.style.setProperty(
|
||||
"--arch-panel-font",
|
||||
functionsStyle.fontFamily || headerStyle.fontFamily || "inherit"
|
||||
);
|
||||
target.style.setProperty(
|
||||
"--arch-workbench-header",
|
||||
toColor(workbenchHeader, 1)
|
||||
);
|
||||
target.style.setProperty(
|
||||
"--arch-workbench-accent",
|
||||
toColor(workbenchAccent, 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveForegroundColor(
|
||||
header,
|
||||
functionsContainer,
|
||||
anchor,
|
||||
headerStyle,
|
||||
functionsStyle
|
||||
) {
|
||||
const salesPlanningElement = findTextElement(header, "Sales Planning");
|
||||
const salesPlanningColor = salesPlanningElement
|
||||
? parseColor(window.getComputedStyle(salesPlanningElement).color)
|
||||
: null;
|
||||
const functionsItemColor = findVisibleChildColor(functionsContainer);
|
||||
const userColor = anchor
|
||||
? parseColor(window.getComputedStyle(anchor).color)
|
||||
: null;
|
||||
|
||||
return (
|
||||
userColor ||
|
||||
functionsItemColor ||
|
||||
parseColor(functionsStyle.color) ||
|
||||
salesPlanningColor ||
|
||||
parseColor(headerStyle.color) || {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
a: 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function resolveBackgroundColor(element, style) {
|
||||
const directBackground = parseColor(style.backgroundColor);
|
||||
|
||||
if (directBackground && directBackground.a > 0) {
|
||||
return directBackground;
|
||||
}
|
||||
|
||||
let current = element.parentElement;
|
||||
|
||||
while (current) {
|
||||
const currentBackground = parseColor(
|
||||
window.getComputedStyle(current).backgroundColor
|
||||
);
|
||||
|
||||
if (currentBackground && currentBackground.a > 0) {
|
||||
return currentBackground;
|
||||
}
|
||||
|
||||
current = current.parentElement;
|
||||
}
|
||||
|
||||
return { r: 75, g: 51, b: 93, a: 1 };
|
||||
}
|
||||
|
||||
function parseColor(value) {
|
||||
if (!value || value === "transparent") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rgbMatch = value.match(
|
||||
/rgba?\(\s*(\d{1,3})[\s,]+(\d{1,3})[\s,]+(\d{1,3})(?:[\s,/]+([.\d]+))?\s*\)/i
|
||||
);
|
||||
|
||||
if (rgbMatch) {
|
||||
return {
|
||||
r: Number.parseInt(rgbMatch[1], 10),
|
||||
g: Number.parseInt(rgbMatch[2], 10),
|
||||
b: Number.parseInt(rgbMatch[3], 10),
|
||||
a: rgbMatch[4] === undefined ? 1 : Number.parseFloat(rgbMatch[4]),
|
||||
};
|
||||
}
|
||||
|
||||
const hexMatch = value.match(/^#([\da-f]{3,8})$/i);
|
||||
|
||||
if (!hexMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hexValue = hexMatch[1];
|
||||
|
||||
if (hexValue.length === 3 || hexValue.length === 4) {
|
||||
const [r, g, b, a = "f"] = hexValue.split("");
|
||||
|
||||
return {
|
||||
r: Number.parseInt(r + r, 16),
|
||||
g: Number.parseInt(g + g, 16),
|
||||
b: Number.parseInt(b + b, 16),
|
||||
a: Number.parseInt(a + a, 16) / 255,
|
||||
};
|
||||
}
|
||||
|
||||
if (hexValue.length === 6 || hexValue.length === 8) {
|
||||
return {
|
||||
r: Number.parseInt(hexValue.slice(0, 2), 16),
|
||||
g: Number.parseInt(hexValue.slice(2, 4), 16),
|
||||
b: Number.parseInt(hexValue.slice(4, 6), 16),
|
||||
a:
|
||||
hexValue.length === 8
|
||||
? Number.parseInt(hexValue.slice(6, 8), 16) / 255
|
||||
: 1,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function mix(base, tint, amount) {
|
||||
const ratio = clamp(amount, 0, 1);
|
||||
|
||||
return {
|
||||
r: Math.round(base.r + (tint.r - base.r) * ratio),
|
||||
g: Math.round(base.g + (tint.g - base.g) * ratio),
|
||||
b: Math.round(base.b + (tint.b - base.b) * ratio),
|
||||
a: base.a + (tint.a - base.a) * ratio,
|
||||
};
|
||||
}
|
||||
|
||||
function toColor(color, alphaOverride) {
|
||||
const alpha = alphaOverride ?? color.a ?? 1;
|
||||
|
||||
return `rgba(${color.r}, ${color.g}, ${color.b}, ${clamp(alpha, 0, 1)})`;
|
||||
}
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
function findUserAnchor(container) {
|
||||
const selector = [
|
||||
`button[label="${USER_BUTTON_LABEL}"]`,
|
||||
`[role="button"][label="${USER_BUTTON_LABEL}"]`,
|
||||
`oj-button[label="${USER_BUTTON_LABEL}"]`,
|
||||
`button[aria-label="${USER_BUTTON_LABEL}"]`,
|
||||
`[role="button"][aria-label="${USER_BUTTON_LABEL}"]`,
|
||||
`[label="${USER_BUTTON_LABEL}"]`,
|
||||
`[aria-label="${USER_BUTTON_LABEL}"]`,
|
||||
].join(", ");
|
||||
|
||||
const directMatch = container.querySelector(selector);
|
||||
|
||||
if (directMatch && isVisible(directMatch)) {
|
||||
return directMatch;
|
||||
}
|
||||
|
||||
const fallbackMatch = Array.from(container.querySelectorAll("*")).find(
|
||||
(element) => {
|
||||
if (!isVisible(element) || element.closest(`#${IDS.mount}`)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
element.getAttribute("label") === USER_BUTTON_LABEL ||
|
||||
element.getAttribute("aria-label") === USER_BUTTON_LABEL
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return fallbackMatch || null;
|
||||
}
|
||||
|
||||
function findTextElement(container, expectedText) {
|
||||
const normalizedExpected = normalizeText(expectedText);
|
||||
const elements = Array.from(container.querySelectorAll("*"));
|
||||
|
||||
return (
|
||||
elements.find((element) => {
|
||||
const text = normalizeText(element.textContent || "");
|
||||
|
||||
return text && text.includes(normalizedExpected);
|
||||
}) || null
|
||||
);
|
||||
}
|
||||
|
||||
function resolveInsertionTarget(container, anchor, mount) {
|
||||
const directAnchor = anchor ? getDirectChild(anchor, container) : null;
|
||||
const fallbackTarget = findFirstInsertionTarget(container, mount);
|
||||
const candidate = directAnchor || fallbackTarget;
|
||||
|
||||
if (!candidate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (candidate === mount) {
|
||||
return findNextValidSibling(container, mount);
|
||||
}
|
||||
|
||||
return candidate.parentElement === container ? candidate : null;
|
||||
}
|
||||
|
||||
function getDirectChild(element, container) {
|
||||
let current = element;
|
||||
|
||||
while (current && current.parentElement !== container) {
|
||||
current = current.parentElement;
|
||||
}
|
||||
|
||||
return current && current.parentElement === container ? current : null;
|
||||
}
|
||||
|
||||
function findFirstInsertionTarget(container, mount) {
|
||||
return Array.from(container.children).find((child) => child !== mount) || null;
|
||||
}
|
||||
|
||||
function findNextValidSibling(container, mount) {
|
||||
let sibling = mount.nextElementSibling;
|
||||
|
||||
while (sibling) {
|
||||
if (sibling.parentElement === container) {
|
||||
return sibling;
|
||||
}
|
||||
|
||||
sibling = sibling.nextElementSibling;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function findVisibleChildColor(container) {
|
||||
const visibleChild = Array.from(container.querySelectorAll("*")).find(
|
||||
(element) => isVisible(element) && !element.closest(`#${IDS.mount}`)
|
||||
);
|
||||
|
||||
return visibleChild
|
||||
? parseColor(window.getComputedStyle(visibleChild).color)
|
||||
: null;
|
||||
}
|
||||
|
||||
function lockScroll() {
|
||||
document.documentElement.classList.add(CLASSES.modalOpen);
|
||||
document.body?.classList.add(CLASSES.modalOpen);
|
||||
}
|
||||
|
||||
function unlockScroll() {
|
||||
document.documentElement.classList.remove(CLASSES.modalOpen);
|
||||
document.body?.classList.remove(CLASSES.modalOpen);
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
applyDialogTheme,
|
||||
ensureStyles,
|
||||
applyTheme,
|
||||
findUserAnchor,
|
||||
resolveInsertionTarget,
|
||||
lockScroll,
|
||||
unlockScroll,
|
||||
});
|
||||
}
|
||||
|
||||
globalThis.ArchPanelShellView = Object.freeze({
|
||||
create,
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user