mirror of
https://github.com/rommapp/romm.git
synced 2026-04-23 06:54:40 +00:00
Add eslint a11y plugin
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import eslint from "@eslint/js";
|
||||
import vue from "eslint-plugin-vue";
|
||||
import vuea11y from "eslint-plugin-vuejs-accessibility";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
@@ -7,6 +8,7 @@ export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...vue.configs["flat/recommended"],
|
||||
...vuea11y.configs["flat/recommended"],
|
||||
{
|
||||
ignores: [
|
||||
"logs",
|
||||
|
||||
Generated
+33
@@ -43,6 +43,7 @@
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-vue": "^9.33.0",
|
||||
"eslint-plugin-vuejs-accessibility": "^2.4.1",
|
||||
"globals": "^16.0.0",
|
||||
"openapi-typescript-codegen": "^0.29.0",
|
||||
"typescript": "^5.7.3",
|
||||
@@ -4223,6 +4224,15 @@
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"node_modules/aria-query": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/array-buffer-byte-length": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
|
||||
@@ -5285,6 +5295,29 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vuejs-accessibility": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vuejs-accessibility/-/eslint-plugin-vuejs-accessibility-2.4.1.tgz",
|
||||
"integrity": "sha512-ZRZhPdslplZXSF71MtSG+zXYRAT5KiHR4JVuo/DERQf9noAkDvi5W418VOE1qllmJd7wTenndxi1q8XeDMxdHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"aria-query": "^5.3.0",
|
||||
"emoji-regex": "^10.0.0",
|
||||
"vue-eslint-parser": "^9.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vuejs-accessibility/node_modules/emoji-regex": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz",
|
||||
"integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-vue": "^9.33.0",
|
||||
"eslint-plugin-vuejs-accessibility": "^2.4.1",
|
||||
"globals": "^16.0.0",
|
||||
"openapi-typescript-codegen": "^0.29.0",
|
||||
"typescript": "^5.7.3",
|
||||
|
||||
@@ -19,6 +19,7 @@ const romInfo = ref([
|
||||
{ label: "SHA-1", value: props.rom.sha1_hash },
|
||||
{ label: "MD5", value: props.rom.md5_hash },
|
||||
{ label: "CRC", value: props.rom.crc_hash },
|
||||
{ label: "Revision", value: props.rom.revision },
|
||||
]);
|
||||
|
||||
async function toggleMainSibling() {
|
||||
|
||||
@@ -160,7 +160,8 @@ onMounted(() => {
|
||||
<a
|
||||
:href="`https://retroachievements.org/achievement/${achievement.ra_id}`"
|
||||
target="_blank"
|
||||
style="height: 100%; width: 100%"
|
||||
class="h-100 w-100"
|
||||
:aria-label="achievement.badge_id || 'Achievement badge'"
|
||||
>
|
||||
<v-img
|
||||
:src="
|
||||
@@ -168,6 +169,7 @@ onMounted(() => {
|
||||
? (achievement.badge_path ?? '')
|
||||
: (achievement.badge_path_lock ?? '')
|
||||
"
|
||||
:alt="achievement.badge_id || 'Achievement badge'"
|
||||
/>
|
||||
</a>
|
||||
</v-avatar>
|
||||
|
||||
@@ -106,18 +106,6 @@ const hashMatches = computed(() => {
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row
|
||||
v-if="smAndDown && rom.revision"
|
||||
class="text-white text-shadow mt-2 text-center"
|
||||
no-gutters
|
||||
>
|
||||
<v-col>
|
||||
<v-chip v-if="rom.revision" size="small" class="ml-1">
|
||||
Revision {{ rom.revision }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row
|
||||
v-if="rom.is_identified"
|
||||
class="text-white text-shadow mt-2"
|
||||
|
||||
@@ -53,7 +53,6 @@ watch(
|
||||
v-model="searchTerm"
|
||||
density="default"
|
||||
clearable
|
||||
autofocus
|
||||
hide-details
|
||||
rounded="0"
|
||||
:label="t('common.search')"
|
||||
|
||||
@@ -85,6 +85,7 @@ watch(documentY, () => {
|
||||
title-on-hover
|
||||
:enable3-d-tilt="enable3DEffect"
|
||||
@hover="onHover"
|
||||
@focus="onHover"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -81,6 +81,7 @@ function onClosedMenu() {
|
||||
show-chips
|
||||
:enable3-d-tilt="enable3DEffect"
|
||||
@hover="onHover"
|
||||
@focus="onHover"
|
||||
@openedmenu="onOpenedMenu"
|
||||
@closedmenu="onClosedMenu"
|
||||
/>
|
||||
|
||||
@@ -63,6 +63,7 @@ function onHover(emitData: { isHovering: boolean; id: number }) {
|
||||
:platform="platform"
|
||||
:enable3-d-tilt="enable3DEffect"
|
||||
@hover="onHover"
|
||||
@focus="onHover"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -85,6 +85,7 @@ function onClosedMenu() {
|
||||
show-action-bar
|
||||
:enable3-d-tilt="enable3DEffect"
|
||||
@hover="onHover"
|
||||
@focus="onHover"
|
||||
@openedmenu="onOpenedMenu"
|
||||
@closedmenu="onClosedMenu"
|
||||
/>
|
||||
|
||||
@@ -162,13 +162,13 @@ function closeDialog() {
|
||||
"
|
||||
>
|
||||
<v-fade-transition>
|
||||
<div
|
||||
<v-btn
|
||||
v-if="isHovering"
|
||||
class="d-flex translucent cursor-pointer h-100 align-center justify-center text-h4"
|
||||
class="d-flex translucent cursor-pointer h-100 w-100 align-center justify-center text-h4"
|
||||
@click="triggerFileInput"
|
||||
>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</div>
|
||||
</v-btn>
|
||||
</v-fade-transition>
|
||||
<v-file-input
|
||||
id="file-input"
|
||||
|
||||
@@ -199,6 +199,11 @@ onBeforeUnmount(() => {
|
||||
emit('hover', { isHovering: false, id: collection.id });
|
||||
}
|
||||
"
|
||||
@blur="
|
||||
() => {
|
||||
emit('hover', { isHovering: false, id: collection.id });
|
||||
}
|
||||
"
|
||||
>
|
||||
<v-row v-if="showTitle" class="pa-1 justify-center bg-surface">
|
||||
<div
|
||||
|
||||
@@ -237,6 +237,11 @@ onBeforeUnmount(() => {
|
||||
emit('hover', { isHovering: false, id: rom.id });
|
||||
}
|
||||
"
|
||||
@blur="
|
||||
() => {
|
||||
emit('hover', { isHovering: false, id: rom.id });
|
||||
}
|
||||
"
|
||||
>
|
||||
<v-card-text class="pa-0">
|
||||
<v-hover v-slot="{ isHovering, props: imgProps }" open-delay="800">
|
||||
|
||||
@@ -536,7 +536,6 @@ onBeforeUnmount(() => {
|
||||
<v-text-field
|
||||
id="search-text-field"
|
||||
v-model="searchText"
|
||||
autofocus
|
||||
class="bg-toplayer"
|
||||
:disabled="searching"
|
||||
:label="t('common.search')"
|
||||
|
||||
@@ -4,7 +4,6 @@ import { storeToRefs } from "pinia";
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import AdminMenu from "@/components/common/Game/AdminMenu.vue";
|
||||
import FavBtn from "@/components/common/Game/FavBtn.vue";
|
||||
import PlayBtn from "@/components/common/Game/PlayBtn.vue";
|
||||
import RAvatarRom from "@/components/common/Game/RAvatar.vue";
|
||||
import MissingFromFSIcon from "@/components/common/MissingFromFSIcon.vue";
|
||||
@@ -282,7 +281,7 @@ function updateOptions({ sortBy }: { sortBy: SortBy }) {
|
||||
:text="`Missing from filesystem: ${item.fs_path}/${item.fs_name}`"
|
||||
class="mr-1 px-1 item-chip"
|
||||
chip
|
||||
chipSize="x-small"
|
||||
chip-size="x-small"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
@@ -75,7 +75,7 @@ function collapse() {
|
||||
>
|
||||
<template #prepend>
|
||||
<v-row no-gutters class="my-2 justify-center">
|
||||
<HomeBtn aria-label="Home" tabindex="1" />
|
||||
<HomeBtn aria-label="Home" />
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
@@ -84,7 +84,6 @@ function collapse() {
|
||||
<v-btn
|
||||
id="collapseBtn"
|
||||
aria-label="Collapse main navbar"
|
||||
tabindex="2"
|
||||
size="small"
|
||||
density="comfortable"
|
||||
variant="flat"
|
||||
@@ -101,41 +100,11 @@ function collapse() {
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</v-row>
|
||||
<SearchBtn
|
||||
:with-tag="!mainBarCollapsed"
|
||||
rounded
|
||||
class="mt-4"
|
||||
block
|
||||
tabindex="3"
|
||||
/>
|
||||
<PlatformsBtn
|
||||
:with-tag="!mainBarCollapsed"
|
||||
rounded
|
||||
class="mt-2"
|
||||
block
|
||||
tabindex="4"
|
||||
/>
|
||||
<CollectionsBtn
|
||||
:with-tag="!mainBarCollapsed"
|
||||
rounded
|
||||
class="mt-2"
|
||||
block
|
||||
tabindex="5"
|
||||
/>
|
||||
<ScanBtn
|
||||
:with-tag="!mainBarCollapsed"
|
||||
rounded
|
||||
class="mt-2"
|
||||
block
|
||||
tabindex="7"
|
||||
/>
|
||||
<ConsoleModeBtn
|
||||
:with-tag="!mainBarCollapsed"
|
||||
rounded
|
||||
class="mt-2"
|
||||
block
|
||||
tabindex="8"
|
||||
/>
|
||||
<SearchBtn :with-tag="!mainBarCollapsed" rounded class="mt-4" block />
|
||||
<PlatformsBtn :with-tag="!mainBarCollapsed" rounded class="mt-2" block />
|
||||
<CollectionsBtn :with-tag="!mainBarCollapsed" rounded class="mt-2" block />
|
||||
<ScanBtn :with-tag="!mainBarCollapsed" rounded class="mt-2" block />
|
||||
<ConsoleModeBtn :with-tag="!mainBarCollapsed" rounded class="mt-2" block />
|
||||
|
||||
<template #append>
|
||||
<UploadBtn
|
||||
@@ -143,10 +112,9 @@ function collapse() {
|
||||
rounded
|
||||
class="mt-2 mb-6"
|
||||
block
|
||||
tabindex="9"
|
||||
/>
|
||||
<v-row no-gutters class="my-2 justify-center">
|
||||
<UserBtn tabindex="10" aria-label="Settings menu" />
|
||||
<UserBtn aria-label="Settings menu" />
|
||||
</v-row>
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
|
||||
@@ -38,6 +38,13 @@ async function fetchLatestVersion() {
|
||||
document.removeEventListener("network-quiesced", fetchLatestVersion);
|
||||
}
|
||||
|
||||
function openNewVersion() {
|
||||
window.open(
|
||||
`https://github.com/rommapp/romm/releases/tag/${GITHUB_VERSION.value}`,
|
||||
"_blank",
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
document.addEventListener("network-quiesced", fetchLatestVersion);
|
||||
});
|
||||
@@ -53,23 +60,33 @@ onMounted(async () => {
|
||||
!latestVersionDismissed
|
||||
"
|
||||
class="pa-1 border-selected mx-auto"
|
||||
max-width="250"
|
||||
max-width="fit-content"
|
||||
>
|
||||
<v-card-text class="text-center py-2 px-4">
|
||||
<span class="text-white text-shadow">New version available</span>
|
||||
<span class="text-primary ml-1">v{{ GITHUB_VERSION }}</span>
|
||||
<v-row class="mt-1" no-gutters>
|
||||
<v-col>
|
||||
<span class="pointer text-grey" @click="dismissVersionBanner"
|
||||
>Dismiss</span
|
||||
><span class="ml-4"
|
||||
><a
|
||||
target="_blank"
|
||||
:href="`https://github.com/rommapp/romm/releases/tag/${GITHUB_VERSION}`"
|
||||
>See what's new!</a
|
||||
></span
|
||||
<span class="text-white text-body-1">New version available:</span>
|
||||
<span class="text-primary ml-1 text-body-1">{{
|
||||
GITHUB_VERSION
|
||||
}}</span>
|
||||
<v-row class="mt-2 flex justify-center" no-gutters>
|
||||
<v-btn-group>
|
||||
<v-btn
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
class="pointer"
|
||||
size="small"
|
||||
@click="dismissVersionBanner"
|
||||
>
|
||||
</v-col>
|
||||
Dismiss
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
size="small"
|
||||
@click="openNewVersion"
|
||||
>
|
||||
See what's new!
|
||||
</v-btn>
|
||||
</v-btn-group>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
@@ -61,6 +61,11 @@ onBeforeUnmount(() => {
|
||||
emit('hover', { isHovering: false, id: platform.id });
|
||||
}
|
||||
"
|
||||
@blur="
|
||||
() => {
|
||||
emit('hover', { isHovering: false, id: platform.id });
|
||||
}
|
||||
"
|
||||
>
|
||||
<v-card-text>
|
||||
<v-row class="pa-1 justify-center align-center bg-background">
|
||||
|
||||
@@ -611,7 +611,7 @@ onUnmounted(() => {
|
||||
}"
|
||||
tabindex="0"
|
||||
@click="openDescription()"
|
||||
@keydown.enter.prevent="openDescription()"
|
||||
@keydown.enter="openDescription()"
|
||||
>
|
||||
{{ rom.summary }}
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import { storeToRefs } from "pinia";
|
||||
import {
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
ref,
|
||||
watch,
|
||||
nextTick,
|
||||
onUnmounted,
|
||||
} from "vue";
|
||||
import { onMounted, onBeforeUnmount, ref, watch, nextTick } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import type { DetailedRomSchema } from "@/__generated__/models/DetailedRomSchema";
|
||||
import NavigationText from "@/console/components/NavigationText.vue";
|
||||
@@ -159,7 +152,10 @@ async function saveAndExit() {
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadState(stateFile: Uint8Array, screenshotFile: Uint8Array) {
|
||||
async function uploadState(
|
||||
stateFile: ArrayBuffer,
|
||||
screenshotFile: ArrayBuffer,
|
||||
) {
|
||||
if (!romRef.value) return;
|
||||
const filename = `${romRef.value.fs_name_no_ext.trim()} [${new Date()
|
||||
.toISOString()
|
||||
@@ -435,8 +431,8 @@ async function boot() {
|
||||
state: stateFile,
|
||||
screenshot: screenshotFile,
|
||||
}: {
|
||||
state: Uint8Array;
|
||||
screenshot: Uint8Array;
|
||||
state: ArrayBuffer;
|
||||
screenshot: ArrayBuffer;
|
||||
}) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
@@ -463,15 +459,15 @@ async function boot() {
|
||||
save: saveFile,
|
||||
screenshot: screenshotFile,
|
||||
}: {
|
||||
save: Uint8Array;
|
||||
screenshot: Uint8Array;
|
||||
save: ArrayBuffer;
|
||||
screenshot: ArrayBuffer;
|
||||
}) {
|
||||
console.info(
|
||||
"EJS_onSaveSave callback triggered",
|
||||
"saveFile:",
|
||||
saveFile?.length,
|
||||
saveFile?.byteLength,
|
||||
"screenshotFile:",
|
||||
screenshotFile?.length,
|
||||
screenshotFile?.byteLength,
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -779,6 +775,7 @@ onBeforeUnmount(() => {
|
||||
role="button"
|
||||
:aria-selected="focusedExitIndex === i"
|
||||
@click="activateExitOption(opt.id)"
|
||||
@keydown.enter="activateExitOption(opt.id)"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-1">
|
||||
|
||||
@@ -268,6 +268,7 @@ onBeforeRouteUpdate(async (to, from) => {
|
||||
@touchstart="onGameTouchStart"
|
||||
@touchend="onGameTouchEnd"
|
||||
@hover="onHover"
|
||||
@focus="onHover"
|
||||
@openedmenu="onOpenedMenu"
|
||||
@closedmenu="onClosedMenu"
|
||||
/>
|
||||
|
||||
@@ -278,6 +278,7 @@ onBeforeRouteUpdate(async (to, from) => {
|
||||
@touchstart="onGameTouchStart"
|
||||
@touchend="onGameTouchEnd"
|
||||
@hover="onHover"
|
||||
@focus="onHover"
|
||||
@openedmenu="onOpenedMenu"
|
||||
@closedmenu="onClosedMenu"
|
||||
/>
|
||||
|
||||
@@ -182,6 +182,7 @@ onUnmounted(() => {
|
||||
@touchstart="onGameTouchStart"
|
||||
@touchend="onGameTouchEnd"
|
||||
@hover="onHover"
|
||||
@focus="onHover"
|
||||
@openedmenu="onOpenedMenu"
|
||||
@closedmenu="onClosedMenu"
|
||||
/>
|
||||
|
||||
@@ -85,13 +85,13 @@ declare global {
|
||||
EJS_VirtualGamepadSettings: Record<string, unknown>;
|
||||
EJS_onGameStart: () => void;
|
||||
EJS_onSaveState: (args: {
|
||||
screenshot: Uint8Array;
|
||||
state: Uint8Array;
|
||||
screenshot: ArrayBuffer;
|
||||
state: ArrayBuffer;
|
||||
}) => void;
|
||||
EJS_onLoadState: () => void;
|
||||
EJS_onSaveSave: (args: {
|
||||
screenshot: Uint8Array;
|
||||
save: Uint8Array;
|
||||
screenshot: ArrayBuffer;
|
||||
save: ArrayBuffer;
|
||||
}) => void;
|
||||
EJS_onLoadSave: () => void;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ export async function saveState({
|
||||
screenshotFile,
|
||||
}: {
|
||||
rom: DetailedRom;
|
||||
stateFile: Uint8Array;
|
||||
screenshotFile?: Uint8Array;
|
||||
stateFile: ArrayBuffer;
|
||||
screenshotFile?: ArrayBuffer;
|
||||
}): Promise<StateSchema | null> {
|
||||
const filename = buildStateName(rom);
|
||||
try {
|
||||
@@ -62,8 +62,8 @@ export async function saveSave({
|
||||
}: {
|
||||
rom: DetailedRom;
|
||||
save: SaveSchema | null;
|
||||
saveFile: Uint8Array;
|
||||
screenshotFile?: Uint8Array;
|
||||
saveFile: ArrayBuffer;
|
||||
screenshotFile?: ArrayBuffer;
|
||||
}): Promise<SaveSchema | null> {
|
||||
if (save) {
|
||||
try {
|
||||
|
||||
@@ -156,12 +156,15 @@ onMounted(async () => {
|
||||
<v-col>
|
||||
<v-card-title class="text-subtitle-1 pa-0 text-uppercase">
|
||||
<v-icon class="mr-2"> mdi-palette </v-icon>
|
||||
{{ t("play.background-color") }}
|
||||
<label for="background-color-input">{{
|
||||
t("play.select-background-color")
|
||||
}}</label>
|
||||
</v-card-title>
|
||||
</v-col>
|
||||
<v-col class="d-flex justify-end">
|
||||
<input
|
||||
v-model="backgroundColor"
|
||||
id="background-color-input"
|
||||
type="color"
|
||||
class="h-100 w-50 text-right"
|
||||
:title="t('play.select-background-color')"
|
||||
|
||||
@@ -97,13 +97,13 @@ onUnmounted(() => {
|
||||
"
|
||||
>
|
||||
<v-fade-transition>
|
||||
<div
|
||||
<v-btn
|
||||
v-if="isHovering"
|
||||
class="d-flex translucent cursor-pointer h-100 align-center justify-center text-h4"
|
||||
class="d-flex translucent cursor-pointer h-100 w-100 align-center justify-center text-h4"
|
||||
@click="triggerFileInput"
|
||||
>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</div>
|
||||
</v-btn>
|
||||
</v-fade-transition>
|
||||
<v-file-input
|
||||
id="file-input"
|
||||
|
||||
Reference in New Issue
Block a user