[frontend] add CustomCursor component and update plugin loading mechanism

This commit is contained in:
Jo 2024-07-04 19:33:25 +02:00
parent 0db7875bc1
commit 7239a9587f
3 changed files with 289 additions and 13 deletions

View file

@ -5,7 +5,7 @@ export async function loadPlugins(): Promise<any[]> {
return new Promise(async (resolve, reject) => {
const files = await glob('../shared/plugins/**/*.plugin.{ts,js}');
console.log(files)
console.log(files);
const plugins = [];
for (const file of files) {

View file

@ -1,13 +1,76 @@
<script setup lang="ts">
import CustomCursor from '@/components/CustomCursor.vue';
</script>
<template>
<h1>Hello App!</h1>
<p>
<strong>Current route path:</strong> {{ $route.fullPath }}
</p>
<nav>
<RouterLink to="/">Go to Home</RouterLink>
<RouterLink to="/about">Go to About</RouterLink>
</nav>
<main>
<RouterView />
</main>
</template>
<div class="sidebar layer"></div>
<div class="app-content layer">
<h1>Hello App!</h1>
<p>
<strong>Current route path:</strong> {{ $route.fullPath }}
</p>
<nav>
<RouterLink to="/">Go to Home</RouterLink>
<RouterLink to="/about">Go to About</RouterLink>
</nav>
<main class="layer">
<RouterView />
</main>
</div>
<CustomCursor></CustomCursor>
</template>
<style>
:root {
--color-background: #292c3c;
--color-text: #c6d0f5;
--color-accent: #8caaee;
}
* {
cursor: none;
}
body {
height: 100vh;
width: 100vw;
color: var(--color-text);
background: linear-gradient(320deg,#f27121,#e94057,#8a2387);
}
#app {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
display: flex;
flex-direction: row;
}
.layer {
background: rgba(5, 7, 32, 0.27);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
border: 1px solid rgba(5, 7, 32, 0.17);
}
.box {
background: rgba(5, 7, 32, 0.27);
border-radius: 16px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
border: 1px solid rgba(5, 7, 32, 0.17);
}
.sidebar {
width: 70px;
}
.app-content {
width: calc(100% - 70px);
}
</style>

View file

@ -0,0 +1,213 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
// Cursor element references
let cursor = ref<HTMLDivElement | null>(null);
let cursorBorder = ref<HTMLDivElement | null>(null);
// Save the position of the cursor
let cursorPosition = ref({ x: 0, y: 0 });
let borderPosition = ref({ x: 0, y: 0 });
// Border immutability state
let borderIsImmutable = ref(false);
let cursorIsImmutable = ref(false);
// Save the matrix transform for cursor and border separately
let cursorMatrix = ref(`matrix(1, 0, 0, 1, 0, 0)`);
let borderMatrix = ref(`matrix(1, 0, 0, 1, 0, 0)`);
// Save the size of cursor, border and dialog
let cursorSize = ref(6);
let borderSize = ref({ width: 40, height: 40 });
let dialogSize = ref ({ width: 200, height: 200 });
// Dialog options
let dialogIsActive = ref(false);
// Debug dialog options
let debugEntries = ref([{ fieldName: "debugMenuActive", type: "boolean", state: "true", color: "red" }]);
function updateCursor(event: MouseEvent) {
if (!cursorIsImmutable.value) cursorPosition.value = { x: event.clientX, y: event.clientY };
if (!borderIsImmutable.value) borderPosition.value = { x: event.clientX, y: event.clientY };
debugEntries.value.push({ fieldName: "newCursorPosition", type: "string", state: JSON.stringify(cursorPosition.value), color: "red" })
}
function renderCursorChanges() {
cursorMatrix.value = `matrix(1, 0, 0, 1, ${cursorPosition.value.x - (cursorSize.value / 2)}, ${cursorPosition.value.y - (cursorSize.value / 2)})`;
borderMatrix.value = `matrix(1, 0, 0, 1, ${borderPosition.value.x - (borderSize.value.width / 2)}, ${borderPosition.value.y - (borderSize.value.height / 2)})`;
requestAnimationFrame(renderCursorChanges);
}
function enlargeCursor() {
cursorSize.value = 16;
}
function shrinkCursor() {
cursorSize.value = 6;
}
function enlargeBorder() {
borderSize.value = { width: 60, height: 60 };
}
function shrinkBorder() {
borderSize.value = { width: 40, height: 40 };
}
function bindBorderToElement(event: any) {
const targetRects = event.target.getClientRects()[0];
borderPosition.value = { x: targetRects.x + (targetRects.width / 2), y: targetRects.y + (targetRects.height / 2) };
borderSize.value = { width: targetRects.width, height: targetRects.height };
if (cursorBorder.value) cursorBorder.value.style.borderRadius = "0";
borderIsImmutable.value = true;
}
function releaseBorder() {
shrinkBorder();
if (cursorBorder.value) cursorBorder.value.style.borderRadius = "50px";
borderIsImmutable.value = false;
}
function onMouseEnter(event: any) {
bindBorderToElement(event);
}
function onMouseLeave(event: any) {
releaseBorder();
}
function onContextMenu(event: any) {
event.preventDefault();
console.log("Clicked :3");
borderIsImmutable.value = !borderIsImmutable.value;
dialogIsActive.value = borderIsImmutable.value;
}
onMounted(() => {
renderCursorChanges();
// Custom hover effect
window.addEventListener('mousemove', updateCursor);
// Add hover event listeners for the elements you want
const hoverElements = document.querySelectorAll('.hover-enlarge, a');
hoverElements.forEach(element => {
element.addEventListener('mouseenter', onMouseEnter);
element.addEventListener('mouseleave', onMouseLeave);
});
// Custom context-menu effect
window.addEventListener("contextmenu", onContextMenu);
});
onUnmounted(() => {
window.removeEventListener('mousemove', updateCursor);
// Remove hover event listeners for the elements
const hoverElements = document.querySelectorAll('.hover-enlarge, a');
hoverElements.forEach(element => {
element.removeEventListener('mouseenter', enlargeBorder);
element.removeEventListener('mouseleave', shrinkBorder);
});
});
</script>
<template>
<!-- Cursor and border element -->
<div class="cursor" :style="{transform: cursorMatrix, width: cursorSize + 'px', height: cursorSize + 'px'}" ref="cursor"></div>
<div class="cursor-border" :style="{transform: borderMatrix, width: borderSize.width + 'px', height: borderSize.height + 'px'}" ref="cursorBorder"></div>
<!-- Modal/Dialog component for cursor right click -->
<div class="cursor-dialog-backdrop" v-if="dialogIsActive"></div>
<div class="cursor-dialog" :style="{transform: cursorMatrix, width: dialogSize.width + 'px', height: dialogSize.height + 'px'}" v-if="dialogIsActive"></div>
<!-- Debugging menu -->
<div class="debug-dialog">
<p v-for="item in debugEntries">{{ item.fieldName }} = <span :style="{color: item.color}">{{ item.state }}</span></p>
</div>
</template>
<style scoped>
.cursor {
position: fixed;
top: 0;
left: 0;
will-change: transform;
background-color: var(--color-accent);
border-radius: 50%;
pointer-events: none;
transform-origin: top left;
z-index: 1002;
}
.cursor-border {
position: fixed;
top: 0;
left: 0;
border-radius: 50px;
outline: 3px solid var(--color-accent);
pointer-events: none;
transition: transform 100ms ease, border-radius 200ms ease;
z-index: 1002;
}
.cursor-dialog-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100vh;
height: 100%;
display: block;
background: rgba(0, 0, 0, .6);
backdrop-filter: blur(18px);
z-index: 1000;
}
.cursor-dialog {
position: fixed;
z-index: 1001;
}
.debug-dialog {
position: fixed;
top: 8px;
left: 8px;
width: 250px;
height: auto;
max-height: 800px;
overflow: scroll;
background: rgba(0, 0, 0, .8);
border: 2px solid red;
border-radius: 8px;
z-index: 2000;
}
.debug-dialog p {
padding: 0 0 0 6px;
margin: 3px;
text-align: left;
}
</style>