[frontend] add CustomCursor component and update plugin loading mechanism
This commit is contained in:
parent
0db7875bc1
commit
7239a9587f
3 changed files with 289 additions and 13 deletions
|
@ -5,7 +5,7 @@ export async function loadPlugins(): Promise<any[]> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const files = await glob('../shared/plugins/**/*.plugin.{ts,js}');
|
const files = await glob('../shared/plugins/**/*.plugin.{ts,js}');
|
||||||
|
|
||||||
console.log(files)
|
console.log(files);
|
||||||
|
|
||||||
const plugins = [];
|
const plugins = [];
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CustomCursor from '@/components/CustomCursor.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="sidebar layer"></div>
|
||||||
|
<div class="app-content layer">
|
||||||
<h1>Hello App!</h1>
|
<h1>Hello App!</h1>
|
||||||
<p>
|
<p>
|
||||||
<strong>Current route path:</strong> {{ $route.fullPath }}
|
<strong>Current route path:</strong> {{ $route.fullPath }}
|
||||||
|
@ -7,7 +13,64 @@
|
||||||
<RouterLink to="/">Go to Home</RouterLink>
|
<RouterLink to="/">Go to Home</RouterLink>
|
||||||
<RouterLink to="/about">Go to About</RouterLink>
|
<RouterLink to="/about">Go to About</RouterLink>
|
||||||
</nav>
|
</nav>
|
||||||
<main>
|
<main class="layer">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CustomCursor></CustomCursor>
|
||||||
</template>
|
</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>
|
213
apps/frontend/src/components/CustomCursor.vue
Normal file
213
apps/frontend/src/components/CustomCursor.vue
Normal 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>
|
Loading…
Reference in a new issue