add secret game run with hold q and type hesabix
This commit is contained in:
parent
4b0333797f
commit
1c277f87bf
BIN
webUI/src/assets/dino.png
Normal file
BIN
webUI/src/assets/dino.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
webUI/src/assets/hero.png
Normal file
BIN
webUI/src/assets/hero.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
BIN
webUI/src/assets/peach.png
Normal file
BIN
webUI/src/assets/peach.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
195
webUI/src/components/application/buttons/CalculatorButton.vue
Normal file
195
webUI/src/components/application/buttons/CalculatorButton.vue
Normal file
|
@ -0,0 +1,195 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- دکمه برای باز کردن ماشین حساب -->
|
||||
<v-btn class="d-none d-sm-flex" stacked @click="dialog = true" @keyup.enter="dialog = true" tabindex="0">
|
||||
<v-icon>mdi-calculator</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<!-- دیالوگ پاپآپ ماشین حساب -->
|
||||
<v-dialog v-model="dialog" max-width="300" @keydown="handleKeydown">
|
||||
<v-card>
|
||||
<!-- نوار ابزار در بالای ماشین حساب -->
|
||||
<v-toolbar color="primary" dark>
|
||||
<v-toolbar-title>ماشین حساب</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="dialog = false" @keyup.enter="dialog = false" tabindex="0">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
|
||||
<v-card-text>
|
||||
<!-- نمایشگر ماشین حساب -->
|
||||
<v-text-field
|
||||
:value="formattedDisplay"
|
||||
readonly
|
||||
class="display"
|
||||
variant="outlined"
|
||||
tabindex="-1"
|
||||
></v-text-field>
|
||||
|
||||
<!-- دکمههای ماشین حساب -->
|
||||
<v-row>
|
||||
<v-col cols="3" v-for="btn in buttons" :key="btn">
|
||||
<v-btn
|
||||
:class="{ 'active-btn': activeButton === btn }"
|
||||
:color="getButtonColor(btn)"
|
||||
flat
|
||||
block
|
||||
@click="handleButtonClick(btn)"
|
||||
@keyup.enter="handleButtonClick(btn)"
|
||||
tabindex="0"
|
||||
>
|
||||
{{ btn }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
display: "0",
|
||||
current: "",
|
||||
previous: "",
|
||||
operation: null,
|
||||
activeButton: null,
|
||||
numberButtons: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."], // دکمههای اعداد
|
||||
operatorButtons: ["+", "-", "*", "/", "%"], // دکمههای عملگر
|
||||
actionButtons: ["C", "="], // دکمههای عملیاتی
|
||||
buttons: ["7", "8", "9", "/", "4", "5", "6", "*", "1", "2", "3", "-", "0", ".", "%", "+", "C", "="],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
formattedDisplay() {
|
||||
if (this.display === "خطا") return this.display;
|
||||
const num = parseFloat(this.display);
|
||||
return isNaN(num) ? this.display : num.toLocaleString("fa-IR");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleButtonClick(btn) {
|
||||
this.handleInput(btn);
|
||||
this.activeButton = btn;
|
||||
setTimeout(() => {
|
||||
this.activeButton = null;
|
||||
}, 100);
|
||||
},
|
||||
handleInput(btn) {
|
||||
if (btn === "C") {
|
||||
this.clear();
|
||||
} else if (btn === "=") {
|
||||
this.calculate();
|
||||
} else if (this.isOperator(btn)) {
|
||||
this.setOperation(btn);
|
||||
} else {
|
||||
this.appendNumber(btn);
|
||||
}
|
||||
},
|
||||
isOperator(btn) {
|
||||
return this.operatorButtons.includes(btn);
|
||||
},
|
||||
appendNumber(btn) {
|
||||
if (this.current === "0" && btn !== ".") {
|
||||
this.current = btn;
|
||||
} else {
|
||||
this.current += btn;
|
||||
}
|
||||
this.display = this.current;
|
||||
},
|
||||
setOperation(op) {
|
||||
if (this.current === "") return;
|
||||
if (this.previous !== "") {
|
||||
this.calculate();
|
||||
}
|
||||
this.operation = op;
|
||||
this.previous = this.current;
|
||||
this.current = "";
|
||||
},
|
||||
calculate() {
|
||||
if (this.current === "" || this.previous === "") return;
|
||||
|
||||
const prev = parseFloat(this.previous);
|
||||
const curr = parseFloat(this.current);
|
||||
let result = 0;
|
||||
|
||||
switch (this.operation) {
|
||||
case "+":
|
||||
result = prev + curr;
|
||||
break;
|
||||
case "-":
|
||||
result = prev - curr;
|
||||
break;
|
||||
case "*":
|
||||
result = prev * curr;
|
||||
break;
|
||||
case "/":
|
||||
result = curr !== 0 ? prev / curr : "خطا";
|
||||
break;
|
||||
case "%":
|
||||
result = (prev * curr) / 100;
|
||||
break;
|
||||
}
|
||||
|
||||
this.display = result.toString();
|
||||
this.current = result.toString();
|
||||
this.previous = "";
|
||||
this.operation = null;
|
||||
},
|
||||
clear() {
|
||||
this.display = "0";
|
||||
this.current = "";
|
||||
this.previous = "";
|
||||
this.operation = null;
|
||||
},
|
||||
handleKeydown(event) {
|
||||
const key = event.key;
|
||||
if (this.buttons.includes(key)) {
|
||||
this.handleButtonClick(key);
|
||||
event.preventDefault();
|
||||
} else if (key === "Enter" || key === "=") {
|
||||
this.handleButtonClick("=");
|
||||
event.preventDefault();
|
||||
} else if (key === "Escape") {
|
||||
this.dialog = false;
|
||||
event.preventDefault();
|
||||
} else if (key === "Backspace") {
|
||||
this.current = this.current.slice(0, -1) || "0";
|
||||
this.display = this.current;
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
// تعیین رنگ دکمهها بر اساس نوع
|
||||
getButtonColor(btn) {
|
||||
if (this.numberButtons.includes(btn)) return "blue darken-5"; // رنگ اعداد
|
||||
if (this.operatorButtons.includes(btn)) return "gray darken-1"; // رنگ عملگرها
|
||||
if (this.actionButtons.includes(btn)) return "red lighten-1"; // رنگ دکمههای عملیاتی
|
||||
return "grey lighten-4"; // رنگ پیشفرض
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.display {
|
||||
margin-bottom: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
.v-btn {
|
||||
min-width: 0 !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* تغییر رنگ لحظهای دکمه */
|
||||
.active-btn {
|
||||
background-color: #0288d1 !important; /* آبی تیرهتر برای حالت فعال */
|
||||
color: white !important;
|
||||
transition: background-color 0.1s ease;
|
||||
}
|
||||
</style>
|
359
webUI/src/components/application/buttons/SecretDialog.vue
Normal file
359
webUI/src/components/application/buttons/SecretDialog.vue
Normal file
|
@ -0,0 +1,359 @@
|
|||
<!-- SecretDialog.vue -->
|
||||
<template>
|
||||
<div>
|
||||
<v-dialog v-model="showDialog" :max-width="dialogWidth" persistent>
|
||||
<v-card>
|
||||
<v-card-title>به جنگ هلوها برو ...</v-card-title>
|
||||
<v-card-text>
|
||||
<canvas ref="gameCanvas"></canvas>
|
||||
<div class="mt-2">
|
||||
<strong>کنترلها:</strong> H برای پرش (حداکثر ۳ بار) | D برای حالت قهرمان ({{ superModeCount }} باقیمانده)
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<strong>امتیاز:</strong> {{ Math.floor(score) }}
|
||||
</div>
|
||||
<div class="mt-2 text-caption">
|
||||
هلو در حسابداری نمادی از انحصارگری نرمافزارهای حسابداری است که باید شکستش داد!
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="primary" @click="resetGame">
|
||||
{{ gameRunning ? 'شروع مجدد' : 'شروع' }}
|
||||
</v-btn>
|
||||
<v-btn color="error" @click="closeDialog">بستن</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dinoImage from '@/assets/dino.png'
|
||||
import peachImage from '@/assets/peach.png'
|
||||
import heroImage from '@/assets/hero.png'
|
||||
|
||||
export default {
|
||||
name: 'SecretDialog',
|
||||
data() {
|
||||
return {
|
||||
showDialog: false,
|
||||
keySequence: [],
|
||||
timeoutId: null,
|
||||
canvas: null,
|
||||
ctx: null,
|
||||
dino: {
|
||||
x: 0.1, // نسبت به عرض
|
||||
y: 0.75, // نسبت به ارتفاع
|
||||
width: 0.08,
|
||||
height: 0.2,
|
||||
dy: 0,
|
||||
gravity: 0.0015, // تنظیم شده برای رسپانسیو
|
||||
jump: -0.06, // تنظیم شده برای رسپانسیو
|
||||
jumpCount: 0,
|
||||
maxJumps: 3
|
||||
},
|
||||
obstacles: [],
|
||||
score: 0,
|
||||
gameRunning: false,
|
||||
animationFrameId: null,
|
||||
dinoImg: null,
|
||||
peachImg: null,
|
||||
heroImg: null,
|
||||
isQPressed: false,
|
||||
superMode: false,
|
||||
superModeCount: 3,
|
||||
superModeTimeout: null,
|
||||
hero: { x: 0.5, y: 0.5, width: 0.1, height: 0.3, visible: false },
|
||||
lastObstacleTime: 0,
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogWidth() {
|
||||
return window.innerWidth > 600 ? '600px' : '90%'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('keydown', this.handleKeyDown)
|
||||
window.addEventListener('keyup', this.handleKeyUp)
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
this.loadImages()
|
||||
},
|
||||
watch: {
|
||||
showDialog(newVal) {
|
||||
if (newVal) {
|
||||
this.$nextTick(() => {
|
||||
this.updateCanvasSize()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('keydown', this.handleKeyDown)
|
||||
window.removeEventListener('keyup', this.handleKeyUp)
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
if (this.animationFrameId) cancelAnimationFrame(this.animationFrameId)
|
||||
if (this.superModeTimeout) clearTimeout(this.superModeTimeout)
|
||||
},
|
||||
methods: {
|
||||
loadImages() {
|
||||
this.dinoImg = new Image()
|
||||
this.dinoImg.src = dinoImage
|
||||
this.dinoImg.onerror = () => console.error('Failed to load dino image:', dinoImage)
|
||||
|
||||
this.peachImg = new Image()
|
||||
this.peachImg.src = peachImage
|
||||
this.peachImg.onerror = () => console.error('Failed to load peach image:', peachImage)
|
||||
|
||||
this.heroImg = new Image()
|
||||
this.heroImg.src = heroImage
|
||||
this.heroImg.onerror = () => console.error('Failed to load hero image:', heroImage)
|
||||
this.heroImg.onload = () => console.log('Hero image loaded successfully')
|
||||
},
|
||||
updateCanvasSize() {
|
||||
if (!this.$refs.gameCanvas) return
|
||||
|
||||
this.canvas = this.$refs.gameCanvas
|
||||
this.ctx = this.canvas.getContext('2d')
|
||||
this.canvasWidth = this.canvas.parentElement.clientWidth
|
||||
this.canvasHeight = this.canvasWidth * 0.333 // نسبت 3:1
|
||||
this.canvas.width = this.canvasWidth
|
||||
this.canvas.height = this.canvasHeight
|
||||
},
|
||||
handleResize() {
|
||||
if (this.showDialog) {
|
||||
this.updateCanvasSize()
|
||||
}
|
||||
},
|
||||
handleKeyDown(event) {
|
||||
const gameKeys = [72, 68, 81] // H, D, Q
|
||||
if (gameKeys.includes(event.keyCode)) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
if (event.keyCode === 81) {
|
||||
this.isQPressed = true
|
||||
}
|
||||
if (this.showDialog && event.keyCode === 72 && this.dino.jumpCount < this.dino.maxJumps) {
|
||||
this.dino.dy = this.dino.jump - (this.dino.jumpCount * 0.015) // پرشهای بعدی قویتر
|
||||
this.dino.jumpCount++
|
||||
}
|
||||
if (this.showDialog && event.keyCode === 68 && this.superModeCount > 0 && !this.superMode) {
|
||||
this.activateSuperMode()
|
||||
}
|
||||
if (this.isQPressed) {
|
||||
this.checkSequence(event)
|
||||
}
|
||||
},
|
||||
handleKeyUp(event) {
|
||||
if (event.keyCode === 81) {
|
||||
this.isQPressed = false
|
||||
this.keySequence = []
|
||||
if (this.timeoutId) clearTimeout(this.timeoutId)
|
||||
}
|
||||
},
|
||||
checkSequence(event) {
|
||||
const targetKeyCodes = [72, 69, 83, 65, 66, 73, 88] // H E S A B I X
|
||||
const currentKeyCode = event.keyCode
|
||||
|
||||
if (this.timeoutId) clearTimeout(this.timeoutId)
|
||||
this.timeoutId = setTimeout(() => {
|
||||
this.keySequence = []
|
||||
}, 2000)
|
||||
|
||||
if (targetKeyCodes.includes(currentKeyCode)) {
|
||||
this.keySequence.push(currentKeyCode)
|
||||
}
|
||||
|
||||
if (this.keySequence.length >= targetKeyCodes.length) {
|
||||
const sequenceMatch = targetKeyCodes.every(
|
||||
(code, index) => this.keySequence[index] === code
|
||||
)
|
||||
if (sequenceMatch) {
|
||||
this.showDialog = true
|
||||
this.keySequence = []
|
||||
} else {
|
||||
this.keySequence = []
|
||||
}
|
||||
}
|
||||
},
|
||||
startGame() {
|
||||
if (!this.gameRunning) {
|
||||
this.updateCanvasSize()
|
||||
this.gameRunning = true
|
||||
this.score = 0
|
||||
this.obstacles = []
|
||||
this.lastObstacleTime = Date.now()
|
||||
this.gameLoop()
|
||||
}
|
||||
},
|
||||
gameLoop() {
|
||||
try {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
|
||||
this.drawBackground()
|
||||
this.ctx.fillStyle = '#d3d3d3'
|
||||
this.ctx.fillRect(0, this.canvas.height * 0.95, this.canvas.width, this.canvas.height * 0.05)
|
||||
|
||||
// اعمال جاذبه و حرکت عمودی دایناسور
|
||||
this.dino.dy += this.dino.gravity
|
||||
this.dino.y += this.dino.dy
|
||||
if (this.dino.y >= 0.75) {
|
||||
this.dino.y = 0.75
|
||||
this.dino.dy = 0
|
||||
this.dino.jumpCount = 0
|
||||
}
|
||||
if (this.dino.y < 0) this.dino.y = 0 // جلوگیری از خروج از بالا
|
||||
|
||||
const dinoX = this.dino.x * this.canvasWidth
|
||||
const dinoY = this.dino.y * this.canvasHeight
|
||||
const dinoWidth = this.dino.width * this.canvasWidth
|
||||
const dinoHeight = this.dino.height * this.canvasHeight
|
||||
|
||||
if (this.dinoImg && this.dinoImg.complete) {
|
||||
this.ctx.drawImage(this.dinoImg, dinoX, dinoY, dinoWidth, dinoHeight)
|
||||
} else {
|
||||
this.ctx.fillStyle = 'green'
|
||||
this.ctx.fillRect(dinoX, dinoY, dinoWidth, dinoHeight)
|
||||
console.warn('Dino image not loaded, using fallback')
|
||||
}
|
||||
|
||||
const heroX = this.hero.x * this.canvasWidth
|
||||
const heroY = this.hero.y * this.canvasHeight
|
||||
const heroWidth = this.hero.width * this.canvasWidth
|
||||
const heroHeight = this.hero.height * this.canvasHeight
|
||||
|
||||
if (this.hero.visible) {
|
||||
if (this.heroImg && this.heroImg.complete) {
|
||||
this.ctx.drawImage(this.heroImg, heroX, heroY, heroWidth, heroHeight)
|
||||
} else {
|
||||
this.ctx.fillStyle = 'blue'
|
||||
this.ctx.fillRect(heroX, heroY, heroWidth, heroHeight)
|
||||
console.warn('Hero image not loaded, using fallback')
|
||||
}
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
if (!this.superMode && now - this.lastObstacleTime > 2000 && Math.random() < 0.02) {
|
||||
this.obstacles.push({
|
||||
x: 1,
|
||||
y: 0.75,
|
||||
width: 0.05,
|
||||
height: 0.1,
|
||||
speed: 0.005,
|
||||
dx: 0,
|
||||
dy: 0
|
||||
})
|
||||
this.lastObstacleTime = now
|
||||
}
|
||||
|
||||
this.obstacles = this.obstacles.filter(obs => obs.x > -obs.width && obs.x < 1 + obs.width && obs.y > -obs.height)
|
||||
this.obstacles.forEach(obs => {
|
||||
if (this.superMode) {
|
||||
obs.x += obs.dx
|
||||
obs.y += obs.dy
|
||||
obs.dy += 0.001
|
||||
if (obs.y < 0) obs.y = 0 // جلوگیری از خروج از بالا
|
||||
} else {
|
||||
obs.x -= obs.speed
|
||||
}
|
||||
|
||||
const obsX = obs.x * this.canvasWidth
|
||||
const obsY = obs.y * this.canvasHeight
|
||||
const obsWidth = obs.width * this.canvasWidth
|
||||
const obsHeight = obs.height * this.canvasHeight
|
||||
|
||||
if (this.peachImg && this.peachImg.complete) {
|
||||
this.ctx.drawImage(this.peachImg, obsX, obsY, obsWidth, obsHeight)
|
||||
} else {
|
||||
this.ctx.fillStyle = '#ff9999'
|
||||
this.ctx.beginPath()
|
||||
this.ctx.arc(obsX + obsWidth / 2, obsY + obsHeight / 2, obsWidth / 2, 0, Math.PI * 2)
|
||||
this.ctx.fill()
|
||||
console.warn('Peach image not loaded, using fallback')
|
||||
}
|
||||
|
||||
if (!this.superMode && this.checkCollision(obs)) {
|
||||
this.gameOver()
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
this.score += 0.05
|
||||
|
||||
if (this.gameRunning) {
|
||||
this.animationFrameId = requestAnimationFrame(this.gameLoop)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in game loop:', error)
|
||||
this.gameOver()
|
||||
}
|
||||
},
|
||||
checkCollision(obs) {
|
||||
return (
|
||||
this.dino.x < obs.x + obs.width &&
|
||||
this.dino.x + this.dino.width > obs.x &&
|
||||
this.dino.y < obs.y + obs.height &&
|
||||
this.dino.y + this.dino.height > obs.y
|
||||
)
|
||||
},
|
||||
gameOver() {
|
||||
this.gameRunning = false
|
||||
this.hero.visible = false
|
||||
cancelAnimationFrame(this.animationFrameId)
|
||||
},
|
||||
resetGame() {
|
||||
this.gameOver()
|
||||
this.score = 0
|
||||
this.obstacles = []
|
||||
this.dino.y = 0.75
|
||||
this.dino.dy = 0
|
||||
this.dino.jumpCount = 0
|
||||
this.superMode = false
|
||||
this.hero.visible = false
|
||||
this.startGame()
|
||||
},
|
||||
closeDialog() {
|
||||
this.showDialog = false
|
||||
this.gameOver()
|
||||
},
|
||||
activateSuperMode() {
|
||||
if (this.superModeCount <= 0) return
|
||||
|
||||
this.superMode = true
|
||||
this.superModeCount -= 1
|
||||
this.hero.visible = true
|
||||
|
||||
this.obstacles.forEach(obs => {
|
||||
const direction = obs.x < this.hero.x ? -1 : 1
|
||||
obs.dx = direction * (Math.random() * 0.01 + 0.005)
|
||||
obs.dy = -(Math.random() * 0.01 + 0.005)
|
||||
})
|
||||
|
||||
this.superModeTimeout = setTimeout(() => {
|
||||
this.superMode = false
|
||||
this.hero.visible = false
|
||||
this.obstacles = []
|
||||
}, 15000)
|
||||
},
|
||||
drawBackground() {
|
||||
this.ctx.fillStyle = '#e0e0e0'
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
|
||||
this.ctx.font = `${this.canvasWidth * 0.06}px Arial`
|
||||
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'
|
||||
this.ctx.textAlign = 'center'
|
||||
this.ctx.fillText('Hesabix', this.canvas.width / 2, this.canvas.height * 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
canvas {
|
||||
border: 1px solid #ccc;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -9,7 +9,8 @@ import Notifications_btn from '@/components/application/buttons/notifications_bt
|
|||
import Year_cob from '@/components/application/combobox/year_cob.vue';
|
||||
import Currency_cob from '@/components/application/combobox/currency_cob.vue';
|
||||
import clock from '@/components/application/clock.vue';
|
||||
|
||||
import CalculatorButton from '@/components/application/buttons/CalculatorButton.vue'
|
||||
import SecretDialog from '@/components/application/buttons/SecretDialog.vue';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
@ -280,7 +281,9 @@ export default {
|
|||
Notifications_btn,
|
||||
Year_cob,
|
||||
Currency_cob,
|
||||
clock
|
||||
clock,
|
||||
CalculatorButton,
|
||||
SecretDialog
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -860,6 +863,8 @@ export default {
|
|||
<v-btn class="d-none d-sm-flex" stacked @click="showShortcutsDialog = true; isEditingShortcuts = false">
|
||||
<v-icon>mdi-help-circle</v-icon>
|
||||
</v-btn>
|
||||
<CalculatorButton />
|
||||
<SecretDialog />
|
||||
<v-dialog v-model="showShortcutsDialog" max-width="800" scrollable>
|
||||
<v-card>
|
||||
<v-card-title class="text-h5 pa-4">
|
||||
|
|
Loading…
Reference in a new issue