asciibird/src/Dashboard.vue

672 lines
18 KiB
Vue
Raw Normal View History

<template>
<div id="app">
2021-11-27 00:25:56 +00:00
<div><vue-file-toolbar-menu :content="myMenu" /></div>
2021-11-20 01:10:15 +00:00
2021-04-24 00:42:33 +00:00
<NewAscii />
2021-10-16 05:16:21 +00:00
<Options v-if="asciibirdMeta.length" />
2021-08-17 00:24:41 +00:00
<EditAscii v-if="asciibirdMeta.length" />
<PasteAscii />
2021-10-16 05:16:21 +00:00
<ImageOverlay v-if="asciibirdMeta.length" />
2021-04-17 01:58:45 +00:00
<KeyboardShortcuts
:selected-blocks="selectedBlocks"
2021-08-17 00:24:41 +00:00
:selecting="selecting"
2021-09-04 01:13:28 +00:00
:is-showing-dialog="isShowingDialog"
@updatecanvas="updatecanvas"
2021-08-19 01:42:33 +00:00
:is-inputting-brush-size="this.isInputtingBrushSize"
2021-11-06 00:59:17 +00:00
:canvas-x="canvasX"
:canvas-y="canvasY"
@triggerbrush="triggerbrush"
/>
2021-09-04 01:13:28 +00:00
<t-dialog
name="dialog-posthttp"
title="Enter URL"
text="Enter URL for POST command"
icon="question"
2021-11-27 00:25:56 +00:00
:clickToClose="false"
:showCloseButton="true"
:disableBodyScroll="true"
2021-09-04 01:13:28 +00:00
@closed="postHttp()"
>
<t-input v-model="lastPostURL" />
</t-dialog>
<context-menu ref="menu" class="z-50">
2021-04-17 01:36:44 +00:00
<ul>
2021-04-24 00:20:11 +00:00
<li
2021-04-24 00:42:33 +00:00
@click="$store.commit('openModal', 'new-ascii')"
2021-04-24 00:20:11 +00:00
class="ml-1"
@contextmenu.prevent
>
New ASCII
</li>
<li
2021-08-17 00:24:41 +00:00
@click="$store.commit('openModal', 'edit-ascii')"
class="ml-1"
2021-08-17 00:24:41 +00:00
v-if="asciibirdMeta.length"
>
2021-08-17 00:24:41 +00:00
Edit Ascii
</li>
2021-08-17 00:24:41 +00:00
<li
@click="closeTab(currentTab)"
class="ml-1 border-b"
v-if="asciibirdMeta.length"
>
Close Ascii
</li>
<li
@click="$store.commit('openModal', 'options')"
class="ml-1 border-b"
2021-11-06 01:19:15 +00:00
v-if="asciibirdMeta.length"
>
Options
</li>
2021-08-19 07:17:46 +00:00
<li @click="startImport('mirc')" class="ml-1">Import mIRC from File</li>
2021-08-04 03:21:06 +00:00
<li
@click="startExport('file')"
2021-08-17 00:24:41 +00:00
class="ml-1 border-b"
v-if="asciibirdMeta.length"
2021-04-24 00:20:11 +00:00
>
Export mIRC to File
</li>
2021-08-19 07:17:46 +00:00
<li class="ml-1" @click="$store.commit('openModal', 'paste-ascii')">
Import mIRC from Clipboard
</li>
2021-04-24 00:20:11 +00:00
<li
2021-08-21 00:13:56 +00:00
class="ml-1"
@click="startExport('clipboard')"
v-if="asciibirdMeta.length"
2021-04-17 01:36:44 +00:00
>
2021-04-24 00:20:11 +00:00
Export mIRC to Clipboard
2021-04-17 01:36:44 +00:00
</li>
<li
class="ml-1 border-b"
@click="startExport('post')"
v-if="asciibirdMeta.length"
>
2021-08-21 00:13:56 +00:00
Export mIRC to HTTP POST
</li>
2021-04-17 01:36:44 +00:00
<li
@click="exportAsciibirdState()"
class="ml-1"
v-if="asciibirdMeta.length"
2021-04-17 01:36:44 +00:00
>
Save Asciibird State
</li>
2021-08-19 07:17:46 +00:00
<li @click="startImport('asb')" class="ml-1">Load Asciibird State</li>
2021-04-17 01:36:44 +00:00
</ul>
</context-menu>
<span
@mouseup.right="openContextMenu"
@contextmenu.prevent
style="width: 100%; height: 100%; position: absolute; z-index: -1"
2021-08-04 03:21:06 +00:00
/>
2021-04-17 01:36:44 +00:00
<input
type="file"
style="display: none"
ref="asciiInput"
@change="onImport()"
2021-08-19 07:17:46 +00:00
/>
2021-04-17 01:36:44 +00:00
<template v-if="asciibirdMeta.length">
2021-09-04 01:13:28 +00:00
<div
2021-11-20 01:10:15 +00:00
class="bg-gray-500 relative z-auto"
2021-09-04 01:13:28 +00:00
ref="tabbar"
:style="toolbarString"
>
2021-08-17 00:24:41 +00:00
<span
v-for="(value, key) in asciibirdMeta"
:key="key"
2021-09-04 05:16:27 +00:00
class="mr-2 z-40"
>
2021-08-12 02:22:29 +00:00
<t-button
2021-09-04 05:16:27 +00:00
class="p-1 z-40"
2021-08-17 00:24:41 +00:00
:class="buttonStyle(key)"
@click="changeTab(key, value)"
2021-08-12 02:22:29 +00:00
>
<span>
<span class="material-icons relative">insert_drive_file</span>
<span class="bottom-1 relative pl-1 pr-1">{{ value.title }}</span>
<t-button class="relative bottom-1 z-40 rounded-3xl h-5" @click="closeTab(key)"><span class="material-icons" style="font-size:16px">close</span></t-button>
</span>
</t-button>
2021-08-17 00:24:41 +00:00
</span>
2021-08-16 07:03:53 +00:00
</div>
2021-01-09 01:57:48 +00:00
<Editor
@coordsupdate="updateCoords"
@selectedblocks="selectedblocks"
@textediting="textediting"
:update-canvas="updateCanvas"
2021-08-17 00:24:41 +00:00
@selecting="updateSelecting"
:y-offset="scrollOffset"
2021-11-06 00:59:17 +00:00
:brush="drawBrush"
/>
2021-03-29 08:44:42 +00:00
2021-09-04 05:16:27 +00:00
<Toolbar :y-offset="scrollOffset" />
2021-09-04 05:16:27 +00:00
<DebugPanel
:canvas-x="canvasX"
:canvas-y="canvasY"
v-if="debugPanelState.visible"
:y-offset="scrollOffset"
2021-03-30 08:36:53 +00:00
/>
2021-08-06 04:00:21 +00:00
<BrushLibrary v-show="brushLibraryState.visible" :y-offset="scrollOffset" />
2021-09-04 05:16:27 +00:00
2021-11-27 00:25:56 +00:00
<BrushPreview @inputtingbrush="inputtingbrush" :y-offset="scrollOffset" />
2021-09-04 05:16:27 +00:00
<LayersLibrary :y-offset="scrollOffset" />
<CharPicker
v-if="toolbarState.isChoosingChar"
class="z-50"
:y-offset="scrollOffset"
/>
<ColourPicker
v-if="toolbarState.isChoosingFg || toolbarState.isChoosingBg"
class="z-50"
:y-offset="scrollOffset"
/>
2021-04-17 01:36:44 +00:00
</template>
<template v-else>
<div
class="
absolute
top-1/2
left-1/2
transform
-translate-x-1/2 -translate-y-1/2
text-center
"
@mouseup.right="openContextMenu"
@contextmenu.prevent
>
<BrushCanvas :blocks="splashAscii" />
2021-04-17 01:36:44 +00:00
</div>
</template>
</div>
</template>
<script>
import LZString from "lz-string";
import Toolbar from "./components/Toolbar.vue";
import DebugPanel from "./components/DebugPanel.vue";
2021-08-06 04:00:21 +00:00
import BrushLibrary from "./components/BrushLibrary.vue";
import LayersLibrary from "./components/LayersLibrary.vue";
import Editor from "./views/Editor.vue";
import CharPicker from "./components/parts/CharPicker.vue";
import ColourPicker from "./components/parts/ColourPicker.vue";
import ContextMenu from "./components/parts/ContextMenu.vue";
import NewAscii from "./components/modals/NewAscii.vue";
import Options from "./components/modals/Options.vue";
2021-10-16 05:16:21 +00:00
import ImageOverlay from "./components/modals/ImageOverlay.vue";
import EditAscii from "./components/modals/EditAscii.vue";
import PasteAscii from "./components/modals/PasteAscii.vue";
import BrushCanvas from "./components/parts/BrushCanvas.vue";
2021-08-14 06:41:42 +00:00
import BrushPreview from "./components/parts/BrushPreview.vue";
import KeyboardShortcuts from "./components/parts/KeyboardShortcuts.vue";
import {
parseMircAscii,
toolbarIcons,
exportMirc,
downloadFile,
checkForGetRequest,
splashAscii,
} from "./ascii";
2021-11-27 00:25:56 +00:00
import VueFileToolbarMenu from "vue-file-toolbar-menu";
2021-11-20 01:10:15 +00:00
export default {
async created() {
2021-03-31 23:29:55 +00:00
// Load from irc watch if present in the URL bar
checkForGetRequest();
var isThis = this;
window.addEventListener("scroll", function (event) {
isThis.scrollOffset = this.scrollY;
});
},
destroyed() {
window.removeEventListener("scroll", function (event) {
isThis.scrollOffset = this.scrollY;
});
},
2021-03-31 23:29:55 +00:00
components: {
2021-04-17 01:36:44 +00:00
Toolbar,
DebugPanel,
Editor,
CharPicker,
ColourPicker,
2021-04-24 00:20:11 +00:00
ContextMenu,
NewAscii,
EditAscii,
PasteAscii,
BrushLibrary,
BrushCanvas,
2021-08-14 06:41:42 +00:00
BrushPreview,
KeyboardShortcuts,
2021-08-19 07:17:46 +00:00
LayersLibrary,
2021-10-16 05:16:21 +00:00
Options,
2021-11-20 01:10:15 +00:00
ImageOverlay,
2021-11-27 00:25:56 +00:00
VueFileToolbarMenu,
2021-03-31 23:29:55 +00:00
},
name: "Dashboard",
data: () => ({
2021-03-29 05:10:31 +00:00
canvasX: null,
canvasY: null,
2021-04-02 02:07:49 +00:00
dashboardX: 0,
dashboardY: 0,
importType: null,
2021-04-17 01:36:44 +00:00
showContextMenu: false,
selectedBlocks: null,
textEditing: null,
updateCanvas: false,
2021-08-17 00:24:41 +00:00
selecting: null,
2021-08-19 01:42:33 +00:00
isInputtingBrushSize: false,
scrollOffset: 0,
2021-09-04 01:13:28 +00:00
toolbarString: "top: 0px;",
lastPostURL: "",
isShowingDialog: false,
2021-11-06 00:59:17 +00:00
drawBrush: false,
2021-11-27 00:25:56 +00:00
happy: false,
}),
computed: {
splashAscii() {
return splashAscii;
},
2021-04-03 03:13:03 +00:00
currentTool() {
return toolbarIcons[this.$store.getters.currentTool] ?? null;
2021-04-03 03:13:03 +00:00
},
icon() {
2021-04-17 01:36:44 +00:00
return [
this.currentTool.fa ?? "fas",
this.currentTool.icon ?? "mouse-pointer",
2021-04-17 01:36:44 +00:00
];
2021-04-03 03:13:03 +00:00
},
// options() {
// return this.$store.getters.options;
// },
canFg() {
return this.$store.getters.isTargettingFg;
},
canBg() {
return this.$store.getters.isTargettingBg;
},
canText() {
return this.$store.getters.isTargettingChar;
},
currentFg() {
return this.$store.getters.currentFg;
},
currentBg() {
return this.$store.getters.currentBg;
},
currentChar() {
2021-08-06 01:51:58 +00:00
return this.$store.getters.currentChar;
},
toolbarState() {
return this.$store.getters.toolbarState;
},
asciibirdMeta() {
return this.$store.getters.asciibirdMeta;
},
debugPanelState() {
return this.$store.getters.debugPanel;
},
2021-08-07 02:41:47 +00:00
brushLibraryState() {
return this.$store.getters.brushLibraryState;
},
currentAscii() {
return this.$store.getters.currentAscii;
},
2021-08-16 07:03:53 +00:00
currentTab() {
return this.$store.getters.currentTab;
2021-08-12 02:22:29 +00:00
},
2021-12-04 03:47:31 +00:00
currentAsciiLayers() {
return this.$store.getters.currentAsciiLayers;
},
selectedLayer() {
return this.$store.getters.selectedLayer;
},
canToggleLayer() {
return this.currentAsciiLayers.length !== 1;
// We want to avoid hiding all the layers, so if there's only one
// visible left, we have to disable the buttons
},
imageOverlay() {
return this.$store.getters.imageOverlay || false;
},
imageOverlayUrl() {
return this.imageOverlay.url
? this.imageOverlay.url.split("/").pop()
: "";
},
asciiLayersMenu() {
let menu = [];
2021-11-20 01:10:15 +00:00
2021-12-04 03:47:31 +00:00
for (let i in [ ... this.currentAsciiLayers]) {
menu.push({
text: this.currentAsciiLayers[i].label,
click: () => this.$store.commit("changeLayer", this.currentAsciiLayers.length - i)
});
}
return menu.reverse();
},
2021-11-27 00:25:56 +00:00
myMenu() {
let menu = [];
2021-11-20 01:10:15 +00:00
2021-11-27 00:25:56 +00:00
menu.push({
text: "File",
menu: [
{
text: "New ASCII",
click: () => this.$store.commit("openModal", "new-ascii"),
2021-12-11 01:28:44 +00:00
icon: "fiber_new"
2021-11-27 00:25:56 +00:00
},
2021-11-20 01:10:15 +00:00
],
2021-11-27 00:25:56 +00:00
});
if (this.asciibirdMeta.length) {
menu[0].menu.push(
{
text: "Edit ASCII",
click: () => this.$store.commit("openModal", "edit-ascii"),
2021-12-11 01:28:44 +00:00
icon: "edit"
2021-11-27 00:25:56 +00:00
},
{
text: "Close ASCII",
click: () => this.closeTab(this.currentTab),
2021-12-11 01:28:44 +00:00
icon: "close"
2021-11-27 00:25:56 +00:00
}
);
menu.push({
text: "Options",
menu: [
{
text: "Show Options",
click: () => this.$store.commit("openModal", "options"),
2021-12-11 01:28:44 +00:00
icon: "settings",
2021-11-27 00:25:56 +00:00
},
],
});
}
menu.push({
text: "Import",
menu: [
2021-12-11 01:28:44 +00:00
{ text: "mIRC File", click: () => this.startImport("mirc"), icon: 'upload_file' },
2021-11-27 00:25:56 +00:00
{
text: "mIRC Clipboard",
click: () => this.$store.commit("openModal", "paste-ascii"),
2021-12-11 01:28:44 +00:00
icon: 'copy_all',
2021-11-27 00:25:56 +00:00
},
2021-12-11 01:28:44 +00:00
{ text: "ASCIIBIRD State", click: () => this.startImport("asb"), icon: 'save_alt' },
2021-11-20 01:10:15 +00:00
],
2021-11-27 00:25:56 +00:00
});
2021-11-20 01:10:15 +00:00
2021-11-27 00:25:56 +00:00
if (this.asciibirdMeta.length) {
2021-12-04 03:47:31 +00:00
menu.push(
{
text: "Export",
menu: [
2021-12-11 01:28:44 +00:00
{ text: "mIRC File", click: () => this.startExport("file"), icon: 'download_file' },
2021-12-04 03:47:31 +00:00
{
text: "mIRC Clipboard",
click: () => this.startExport("clipboard"),
2021-12-11 01:28:44 +00:00
icon: 'copy_all',
2021-12-04 03:47:31 +00:00
},
2021-12-11 01:28:44 +00:00
{ text: "HTTP POST", click: () => this.startExport("post"), icon: 'post_add' },
2021-12-04 03:47:31 +00:00
{
text: "ASCIIBIRD State",
click: () => this.exportAsciibirdState(),
2021-12-11 01:28:44 +00:00
icon: 'save_alt'
2021-12-04 03:47:31 +00:00
},
],
},
{
text: "Layers",
menu: [
// {
// text: "Change Layers",
// menu: this.asciiLayersMenu,
// },
{
text: "Show/Hide Layer",
click: () => this.$store.commit("toggleLayer", this.selectedLayer),
2021-12-11 01:28:44 +00:00
icon: 'panorama_fish_eye',
2021-12-04 03:47:31 +00:00
},
2021-12-11 01:28:44 +00:00
{ text: "Rename Layer", click: () => this.showLayerRename(this.selectedLayer, this.currentAsciiLayers[this.selectedLayer].label), icon: 'text_rotation_none' },
{ text: "Add Layer", click: () => this.$store.commit("addLayer"), icon: 'playlist_add' },
{ text: "Delete Layer", click: () => this.$store.commit("removeLayer", this.selectedLayer), icon: 'delete_sweep' },
{ text: "Move Layer Down", click: () => this.$store.commit("upLayer", this.selectedLayer), icon: 'arrow_downward' },
{ text: "Move Layer Up", click: () => this.$store.commit("downLayer", this.selectedLayer), icon: 'arrow_upward' },
{ text: "Merge All Layers", click: () => this.$store.commit("mergeAllLayers"), icon: 'playlist_add_circle' },
2021-12-04 03:47:31 +00:00
],
}
);
2021-11-27 00:25:56 +00:00
}
return menu;
},
},
watch: {
2021-09-04 05:16:27 +00:00
// scrollOffset(val) {
// this.$refs.tabbar.style.top = val;
// this.toolbarString = `top: ${val}px`;
// },
},
methods: {
2021-12-04 03:47:31 +00:00
showLayerRename(key, label) {
this.$store.commit("toggleDisableKeyboard", true);
this.$dialog
.prompt({
title: "Rename Layer",
text: "Please input your new layer name",
icon: "question",
inputValue: label,
clickToClose: false,
})
.then((result) => {
if (!result.input.length) {
this.$toasted.show("You must enter a layer name!", {
type: "error",
});
this.$store.commit("toggleDisableKeyboard", false);
return;
}
if (result.isOk) {
this.updateLayerName(key, result.input);
}
this.$store.commit("toggleDisableKeyboard", false);
});
},
updateLayerName(key, label) {
this.$store.commit("updateLayerName", {
key: key,
label: label,
});
},
2021-11-06 00:59:17 +00:00
triggerbrush() {
2021-11-27 00:25:56 +00:00
this.drawBrush = !this.drawBrush;
2021-11-06 00:59:17 +00:00
},
2021-08-19 01:42:33 +00:00
inputtingbrush(val) {
2021-08-21 00:13:56 +00:00
this.isInputtingBrushSize = val;
2021-08-19 01:42:33 +00:00
},
2021-08-16 07:03:53 +00:00
buttonStyle(key) {
2021-08-19 07:17:46 +00:00
return this.currentTab === key
2021-08-16 07:03:53 +00:00
? `text-sm pl-1 p-1 h-10 text-white border border-transparent shadow-sm hover:bg-blue-500 bg-gray-900`
: `text-sm pl-1 p-1 h-10 text-white border border-transparent shadow-sm hover:bg-blue-500 bg-gray-400`;
2021-08-19 07:17:46 +00:00
},
2021-04-17 01:36:44 +00:00
openContextMenu(e) {
2021-06-19 01:34:31 +00:00
e.preventDefault();
2021-04-17 01:36:44 +00:00
this.$refs.menu.open(e);
},
2021-03-29 05:10:31 +00:00
updateCoords(value) {
2021-03-30 08:36:53 +00:00
this.canvasX = value.x;
this.canvasY = value.y;
2021-03-29 05:10:31 +00:00
},
selectedblocks(value) {
2021-08-19 07:17:46 +00:00
this.selectedBlocks = value;
},
2021-08-17 00:24:41 +00:00
updateSelecting(value) {
2021-08-19 07:17:46 +00:00
this.selecting = value;
2021-08-17 00:24:41 +00:00
},
textediting(value) {
2021-08-19 07:17:46 +00:00
this.textEditing = value;
},
updatecanvas() {
2021-08-19 07:17:46 +00:00
this.updateCanvas = !this.updateCanvas;
},
async onImport() {
2021-01-23 02:50:32 +00:00
const { files } = this.$refs.asciiInput;
const filename = files[0].name;
2021-01-16 03:03:14 +00:00
const fileReader = new FileReader();
2021-08-04 03:21:06 +00:00
const fileType = this.importType;
fileReader.addEventListener("load", () => {
2021-08-04 03:21:06 +00:00
switch (fileType) {
case "asb":
2021-04-03 03:13:03 +00:00
this.importAsciibirdState(fileReader.result, filename);
break;
2021-08-04 03:21:06 +00:00
default:
case "mirc":
parseMircAscii(fileReader.result, filename);
break;
}
});
// This will fire the file reader 'load' event
2021-08-04 03:21:06 +00:00
fileReader.readAsText(files[0]);
},
startImport(type) {
2021-03-31 23:29:55 +00:00
// For ANSI we'll need to add back in the
// type cariable here
2021-04-03 03:13:03 +00:00
this.importType = type;
// console.log(this.importType);
this.$refs.asciiInput.click();
},
importAsciibirdState(fileContents) {
2021-08-04 03:21:06 +00:00
const contents = JSON.parse(
LZString.decompressFromEncodedURIComponent(fileContents)
2021-06-19 00:26:23 +00:00
);
this.$store.commit("changeState", { ...contents });
},
exportAsciibirdState() {
let output;
try {
2021-06-19 00:26:23 +00:00
output = LZString.compressToEncodedURIComponent(
JSON.stringify(this.$store.getters.state)
2021-06-19 00:26:23 +00:00
);
// Default timestamp for filename
2021-04-03 03:13:03 +00:00
const today = new Date();
const y = today.getFullYear();
const m = today.getMonth() + 1; // JavaScript months are 0-based.
const d = today.getDate();
const h = today.getHours();
const mi = today.getMinutes();
const s = today.getSeconds();
downloadFile(
2021-04-17 01:36:44 +00:00
output,
`asciibird-${y}-${m}-${d}-${h}-${mi}-${s}.asb`,
"application/gzip"
2021-04-17 01:36:44 +00:00
);
} catch (err) {
2021-08-12 02:22:29 +00:00
this.$toasted.show(err, {
type: "error",
});
}
},
startExport(type) {
let ascii = exportMirc();
2021-04-24 00:20:11 +00:00
switch (type) {
case "clipboard":
this.$copyText(ascii.output.join("")).then(
2021-08-04 03:21:06 +00:00
(e) => {
2021-08-12 02:22:29 +00:00
this.$toasted.show("Copied mIRC to clipboard!", {
type: "success",
});
2021-06-19 00:26:23 +00:00
},
2021-08-04 03:21:06 +00:00
(e) => {
2021-08-12 02:22:29 +00:00
this.$toasted.show("Error when copying mIRC to clipboard!", {
type: "error",
});
}
2021-06-19 00:26:23 +00:00
);
2021-04-24 00:20:11 +00:00
break;
default:
case "file":
downloadFile(ascii.output.join(""), ascii.filename, "text/plain");
2021-04-24 00:20:11 +00:00
break;
case "post":
2021-09-04 01:13:28 +00:00
this.$store.commit("toggleDisableKeyboard", true);
2021-11-27 00:25:56 +00:00
this.$dialog.show("dialog-posthttp");
2021-09-04 01:13:28 +00:00
this.isShowingDialog = true;
break;
2021-04-24 00:20:11 +00:00
}
},
2021-09-04 01:13:28 +00:00
postHttp() {
let ascii = exportMirc();
// this.lastPostURL = result.input;
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/octet-stream" },
body: ascii.output.join(""),
};
fetch(this.lastPostURL, requestOptions)
.then((response) => {
this.$toasted.show("POSTed ascii!");
})
.catch((error) => {
this.$toasted.show("Error POSTing");
});
this.$store.commit("toggleDisableKeyboard", false);
this.isShowingDialog = false;
},
2021-08-04 03:21:06 +00:00
changeTab(key) {
// Update the tab index in vuex store
this.$store.commit("changeTab", key);
},
closeTab(key) {
this.$dialog
.confirm({
2021-08-12 02:22:29 +00:00
title: `Close ${this.asciibirdMeta[key].title}?`,
text: "This action cannot be undone and the ASCII will be gone.",
icon: "info",
})
.then((result) => {
if (result.isOk) {
this.$store.commit("closeTab", key);
}
});
},
2021-04-02 02:07:49 +00:00
captureMouse(event) {
2021-04-03 03:13:03 +00:00
this.dashboardX = event.pageX;
this.dashboardY = event.pageY;
},
},
};
</script>