[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) => {
|
||||
const files = await glob('../shared/plugins/**/*.plugin.{ts,js}');
|
||||
|
||||
console.log(files)
|
||||
console.log(files);
|
||||
|
||||
const plugins = [];
|
||||
for (const file of files) {
|
||||
|
|
|
@ -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>
|
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