mirror of
https://git.boykissers.com/pawkey/pawkey-sk.git
synced 2025-12-20 12:14:18 +00:00
feat: [frontend] improve watermark
This commit is contained in:
@@ -11,30 +11,17 @@ const generateRandomClass = () => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const gridClass = ref(generateRandomClass());
|
const containerClass = ref(generateRandomClass());
|
||||||
const itemClasses = ref<string[]>([]);
|
|
||||||
const recreationKey = ref(0);
|
const recreationKey = ref(0);
|
||||||
const gridItemCount = ref(600);
|
|
||||||
let regenerationInterval: ReturnType<typeof setInterval> | null = null;
|
let regenerationInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
|
let checkInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
let styleElement: HTMLStyleElement | null = null;
|
let styleElement: HTMLStyleElement | null = null;
|
||||||
|
let watermarkElement: HTMLDivElement | null = null;
|
||||||
|
let mutationObserver: MutationObserver | null = null;
|
||||||
|
let headObserver: MutationObserver | null = null;
|
||||||
|
let attributeObserver: MutationObserver | null = null;
|
||||||
let allCreatedClasses: string[] = [];
|
let allCreatedClasses: string[] = [];
|
||||||
|
|
||||||
// Calculate grid items based on screen resolution
|
|
||||||
const calculateGridItemCount = () => {
|
|
||||||
const width = window.innerWidth;
|
|
||||||
const height = window.innerHeight;
|
|
||||||
|
|
||||||
// Grid cell size: 80px width + 10px gap, 16px height + 10px gap (more concise)
|
|
||||||
const itemWidth = 80 + 10;
|
|
||||||
const itemHeight = 16 + 10;
|
|
||||||
|
|
||||||
const columns = Math.ceil(width / itemWidth);
|
|
||||||
const rows = Math.ceil(height / itemHeight);
|
|
||||||
|
|
||||||
// Add some buffer (20%) to ensure coverage during animations/scrolling
|
|
||||||
return Math.ceil(columns * rows * 1.2);
|
|
||||||
};
|
|
||||||
|
|
||||||
const encodeTextToPath = (text: string) => {
|
const encodeTextToPath = (text: string) => {
|
||||||
const charPaths: { [key: string]: string } = {
|
const charPaths: { [key: string]: string } = {
|
||||||
a: "M2,8 L4,2 L6,2 L8,8 M3,6 L7,6",
|
a: "M2,8 L4,2 L6,2 L8,8 M3,6 L7,6",
|
||||||
@@ -94,31 +81,17 @@ const encodeTextToPath = (text: string) => {
|
|||||||
);
|
);
|
||||||
path += translatedPath + " ";
|
path += translatedPath + " ";
|
||||||
}
|
}
|
||||||
xOffset += 7; // Reduced from 10 to 7 for tighter spacing
|
xOffset += 7;
|
||||||
});
|
});
|
||||||
|
|
||||||
return path.trim();
|
return path.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateItemClasses = (count: number) => {
|
|
||||||
const classes: string[] = [];
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
classes.push(generateRandomClass());
|
|
||||||
}
|
|
||||||
return classes;
|
|
||||||
};
|
|
||||||
|
|
||||||
const regenerateWatermark = () => {
|
const regenerateWatermark = () => {
|
||||||
cleanupOldElements();
|
cleanupOldElements();
|
||||||
|
containerClass.value = generateRandomClass();
|
||||||
gridClass.value = generateRandomClass();
|
|
||||||
const count = calculateGridItemCount();
|
|
||||||
gridItemCount.value = count;
|
|
||||||
itemClasses.value = generateItemClasses(count);
|
|
||||||
recreationKey.value++;
|
recreationKey.value++;
|
||||||
|
allCreatedClasses.push(containerClass.value);
|
||||||
allCreatedClasses.push(gridClass.value, ...itemClasses.value);
|
|
||||||
|
|
||||||
createStyleElement();
|
createStyleElement();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,16 +101,14 @@ const cleanupOldElements = () => {
|
|||||||
const content = style.textContent || "";
|
const content = style.textContent || "";
|
||||||
const hasOldClasses = allCreatedClasses.some(
|
const hasOldClasses = allCreatedClasses.some(
|
||||||
(className) =>
|
(className) =>
|
||||||
content.includes(className) &&
|
content.includes(className) && className !== containerClass.value,
|
||||||
className !== gridClass.value &&
|
|
||||||
!itemClasses.value.includes(className),
|
|
||||||
);
|
);
|
||||||
if (hasOldClasses) {
|
if (hasOldClasses) {
|
||||||
style.remove();
|
style.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
allCreatedClasses = [gridClass.value, ...itemClasses.value];
|
allCreatedClasses = [containerClass.value];
|
||||||
};
|
};
|
||||||
|
|
||||||
const createStyleElement = () => {
|
const createStyleElement = () => {
|
||||||
@@ -147,103 +118,200 @@ const createStyleElement = () => {
|
|||||||
|
|
||||||
styleElement = document.createElement("style");
|
styleElement = document.createElement("style");
|
||||||
|
|
||||||
const itemStyles = itemClasses.value
|
const fgColor = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg').trim() || '#000000';
|
||||||
.map(
|
|
||||||
(className) => `
|
|
||||||
.${className} {
|
|
||||||
display: flex !important;
|
|
||||||
align-items: center !important;
|
|
||||||
justify-content: center !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.${className} svg {
|
const svgContent = `<svg viewBox="0 0 ${textWidth.value} 10" xmlns="http://www.w3.org/2000/svg"><path d="${pathData.value}" stroke="${fgColor}" fill="none" stroke-width="0.8"/></svg>`;
|
||||||
opacity: 0.02 !important;
|
const encodedSvg = encodeURIComponent(svgContent);
|
||||||
width: auto !important;
|
|
||||||
height: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.${className} svg path {
|
|
||||||
stroke: var(--MI_THEME-fg) !important;
|
|
||||||
fill: none !important;
|
|
||||||
stroke-width: 0.8 !important;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
)
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
const styles = `
|
const styles = `
|
||||||
.${gridClass.value} {
|
.${containerClass.value} {
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
top: 0 !important;
|
top: 0 !important;
|
||||||
left: 0 !important;
|
left: 0 !important;
|
||||||
width: 100vw !important;
|
width: 100vw !important;
|
||||||
height: 100vh !important;
|
height: 100vh !important;
|
||||||
display: grid !important;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)) !important;
|
|
||||||
grid-template-rows: repeat(auto-fill, minmax(16px, 1fr)) !important;
|
|
||||||
gap: 10px !important;
|
|
||||||
padding: 10px !important;
|
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
z-index: 999999999 !important;
|
z-index: 999999999 !important;
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
|
opacity: 0.02 !important;
|
||||||
|
background-image: url("data:image/svg+xml,${encodedSvg}") !important;
|
||||||
|
background-repeat: repeat !important;
|
||||||
|
background-size: 80px 16px !important;
|
||||||
|
background-position: 10px 10px !important;
|
||||||
}
|
}
|
||||||
${itemStyles}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
styleElement.textContent = styles;
|
styleElement.textContent = styles;
|
||||||
|
|
||||||
|
Object.defineProperty(styleElement, 'remove', {
|
||||||
|
value: () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!styleElement?.parentNode) {
|
||||||
|
document.head.appendChild(styleElement!);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
writable: false,
|
||||||
|
configurable: false,
|
||||||
|
});
|
||||||
|
|
||||||
document.head.appendChild(styleElement);
|
document.head.appendChild(styleElement);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ensureWatermarkExists = () => {
|
||||||
|
if (!watermarkElement || !document.body.contains(watermarkElement)) {
|
||||||
|
const newElement = document.createElement('div');
|
||||||
|
newElement.className = containerClass.value;
|
||||||
|
newElement.setAttribute('data-watermark', 'true');
|
||||||
|
|
||||||
|
Object.defineProperty(newElement, 'remove', {
|
||||||
|
value: () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!document.body.contains(newElement)) {
|
||||||
|
document.body.appendChild(newElement);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
writable: false,
|
||||||
|
configurable: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const originalStyleSetter = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'style')?.set;
|
||||||
|
if (originalStyleSetter) {
|
||||||
|
Object.defineProperty(newElement, 'style', {
|
||||||
|
get: () => {
|
||||||
|
return newElement.getAttribute('style') || '';
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(newElement);
|
||||||
|
watermarkElement = newElement;
|
||||||
|
|
||||||
|
startAttributeObserver();
|
||||||
|
} else if (watermarkElement.className !== containerClass.value) {
|
||||||
|
watermarkElement.className = containerClass.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!styleElement || !document.head.contains(styleElement)) {
|
||||||
|
createStyleElement();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startAttributeObserver = () => {
|
||||||
|
if (attributeObserver) {
|
||||||
|
attributeObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!watermarkElement) return;
|
||||||
|
|
||||||
|
attributeObserver = new MutationObserver((mutations) => {
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||||||
|
if (watermarkElement && watermarkElement.className !== containerClass.value) {
|
||||||
|
watermarkElement.className = containerClass.value;
|
||||||
|
}
|
||||||
|
} else if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
|
||||||
|
if (watermarkElement) {
|
||||||
|
const style = watermarkElement.getAttribute('style');
|
||||||
|
if (style && (style.includes('display') || style.includes('visibility') || style.includes('opacity'))) {
|
||||||
|
watermarkElement.removeAttribute('style');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
attributeObserver.observe(watermarkElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeOldValue: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const startMutationObserver = () => {
|
||||||
|
mutationObserver = new MutationObserver((mutations) => {
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
if (mutation.type === 'childList') {
|
||||||
|
mutation.removedNodes.forEach((node) => {
|
||||||
|
if (node === watermarkElement || node === styleElement) {
|
||||||
|
ensureWatermarkExists();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mutationObserver.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
headObserver = new MutationObserver((mutations) => {
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
if (mutation.type === 'childList') {
|
||||||
|
mutation.removedNodes.forEach((node) => {
|
||||||
|
if (node === styleElement) {
|
||||||
|
ensureWatermarkExists();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
headObserver.observe(document.head, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const watermarkText = computed(() => $i?.id || "not signed in");
|
const watermarkText = computed(() => $i?.id || "not signed in");
|
||||||
const pathData = computed(() => encodeTextToPath(watermarkText.value));
|
const pathData = computed(() => encodeTextToPath(watermarkText.value));
|
||||||
const textWidth = computed(() => watermarkText.value.length * 7 + 10); // Adjusted for tighter spacing
|
const textWidth = computed(() => watermarkText.value.length * 7 + 10);
|
||||||
|
|
||||||
let resizeTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
|
|
||||||
const handleResize = () => {
|
|
||||||
if (resizeTimeout) clearTimeout(resizeTimeout);
|
|
||||||
|
|
||||||
// Debounce resize to avoid excessive recalculations
|
|
||||||
resizeTimeout = setTimeout(() => {
|
|
||||||
regenerateWatermark();
|
|
||||||
}, 250);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const count = calculateGridItemCount();
|
allCreatedClasses.push(containerClass.value);
|
||||||
gridItemCount.value = count;
|
|
||||||
itemClasses.value = generateItemClasses(count);
|
|
||||||
allCreatedClasses.push(gridClass.value, ...itemClasses.value);
|
|
||||||
createStyleElement();
|
createStyleElement();
|
||||||
|
ensureWatermarkExists();
|
||||||
|
startMutationObserver();
|
||||||
|
|
||||||
|
checkInterval = setInterval(() => {
|
||||||
|
ensureWatermarkExists();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
regenerationInterval = setInterval(() => {
|
regenerationInterval = setInterval(() => {
|
||||||
const count = calculateGridItemCount();
|
|
||||||
gridItemCount.value = count;
|
|
||||||
itemClasses.value = generateItemClasses(count);
|
|
||||||
allCreatedClasses.push(gridClass.value, ...itemClasses.value);
|
|
||||||
regenerateWatermark();
|
regenerateWatermark();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (regenerationInterval) clearInterval(regenerationInterval);
|
if (checkInterval) {
|
||||||
if (resizeTimeout) clearTimeout(resizeTimeout);
|
clearInterval(checkInterval);
|
||||||
window.removeEventListener("resize", handleResize);
|
}
|
||||||
|
if (regenerationInterval) {
|
||||||
|
clearInterval(regenerationInterval);
|
||||||
|
}
|
||||||
|
if (mutationObserver) {
|
||||||
|
mutationObserver.disconnect();
|
||||||
|
}
|
||||||
|
if (headObserver) {
|
||||||
|
headObserver.disconnect();
|
||||||
|
}
|
||||||
|
if (attributeObserver) {
|
||||||
|
attributeObserver.disconnect();
|
||||||
|
}
|
||||||
cleanupOldElements();
|
cleanupOldElements();
|
||||||
if (styleElement && styleElement.parentNode) {
|
if (styleElement && styleElement.parentNode) {
|
||||||
styleElement.parentNode.removeChild(styleElement);
|
styleElement.parentNode.removeChild(styleElement);
|
||||||
}
|
}
|
||||||
|
if (watermarkElement && watermarkElement.parentNode) {
|
||||||
|
watermarkElement.parentNode.removeChild(watermarkElement);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :key="`watermark-${recreationKey}`" :class="gridClass">
|
<div style="display: none;" :key="`watermark-${recreationKey}`"></div>
|
||||||
<div v-for="n in gridItemCount" :key="n" :class="itemClasses[n]">
|
|
||||||
<svg :viewBox="`0 0 ${textWidth} 10`" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path :d="pathData" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user