Skip to content

Commit

Permalink
Merge pull request #1419 from rommapp/romm-1220
Browse files Browse the repository at this point in the history
[ROMM-1220] Generate qr code to download 3DA .cia files
  • Loading branch information
gantoine authored Jan 5, 2025
2 parents b8db6a7 + c6c584b commit 92850f1
Show file tree
Hide file tree
Showing 10 changed files with 360 additions and 24 deletions.
229 changes: 222 additions & 7 deletions frontend/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"mitt": "^3.0.1",
"nanoid": "^5.0.7",
"pinia": "^2.1.7",
"qrcode": "^1.5.4",
"roboto-fontface": "^0.10.0",
"semver": "^7.6.2",
"socket.io-client": "^4.7.5",
Expand All @@ -55,6 +56,7 @@
"@types/js-cookie": "^3.0.6",
"@types/lodash": "^4.17.1",
"@types/node": "^20.12.12",
"@types/qrcode": "^1.5.5",
"@types/semver": "^7.5.8",
"@types/webfontloader": "^1.6.38",
"@vitejs/plugin-vue": "^3.2.0",
Expand Down
26 changes: 16 additions & 10 deletions frontend/src/components/Details/ActionBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { DetailedRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import {
getDownloadLink,
is3DSCIARom,
isEJSEmulationSupported,
isRuffleEmulationSupported,
} from "@/utils";
Expand All @@ -20,26 +21,24 @@ const downloadStore = storeDownload();
const heartbeatStore = storeHeartbeat();
const emitter = inject<Emitter<Events>>("emitter");
const playInfoIcon = ref("mdi-play");
const qrCodeIcon = ref("mdi-qrcode");
const ejsEmulationSupported = computed(() =>
isEJSEmulationSupported(props.rom.platform_slug, heartbeatStore.value),
);
const ruffleEmulationSupported = computed(() =>
isRuffleEmulationSupported(props.rom.platform_slug, heartbeatStore.value),
);
const isCIARom = computed(() => {
return is3DSCIARom(props.rom);
});
// Functions
async function copyDownloadLink(rom: DetailedRom) {
const downloadLink =
location.protocol +
"//" +
location.host +
encodeURI(
getDownloadLink({
rom,
files: downloadStore.filesToDownload,
}),
);
const downloadLink = getDownloadLink({
rom,
files: downloadStore.filesToDownload,
});
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(downloadLink);
emitter?.emit("snackbarShow", {
Expand Down Expand Up @@ -110,6 +109,13 @@ async function copyDownloadLink(rom: DetailedRom) {
>
<v-icon :icon="playInfoIcon" />
</v-btn>
<v-btn
v-if="isCIARom"
class="flex-grow-1"
@click="emitter?.emit('showQRCodeDialog', rom)"
>
<v-icon :icon="qrCodeIcon" />
</v-btn>
<v-menu location="bottom">
<template #activator="{ props: menuProps }">
<v-btn class="flex-grow-1" v-bind="menuProps">
Expand Down
26 changes: 24 additions & 2 deletions frontend/src/components/common/Game/Card/ActionBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ import romApi from "@/services/api/rom";
import storeDownload from "@/stores/download";
import storeHeartbeat from "@/stores/heartbeat";
import type { SimpleRom } from "@/stores/roms";
import { isEJSEmulationSupported, isRuffleEmulationSupported } from "@/utils";
import { computed } from "vue";
import type { Events } from "@/types/emitter";
import {
isEJSEmulationSupported,
isRuffleEmulationSupported,
is3DSCIARom,
} from "@/utils";
import type { Emitter } from "mitt";
import { computed, inject } from "vue";
// Props
const props = defineProps<{ rom: SimpleRom }>();
const downloadStore = storeDownload();
const heartbeatStore = storeHeartbeat();
const emitter = inject<Emitter<Events>>("emitter");
const ejsEmulationSupported = computed(() => {
return isEJSEmulationSupported(props.rom.platform_slug, heartbeatStore.value);
Expand All @@ -22,6 +29,10 @@ const ruffleEmulationSupported = computed(() => {
heartbeatStore.value,
);
});
const isCIARom = computed(() => {
return is3DSCIARom(props.rom);
});
</script>

<template>
Expand Down Expand Up @@ -72,6 +83,17 @@ const ruffleEmulationSupported = computed(() => {
variant="text"
/>
</v-col>
<v-col v-if="isCIARom" class="d-flex">
<v-btn
@click.prevent
class="action-bar-btn-small flex-grow-1"
size="x-small"
@click="emitter?.emit('showQRCodeDialog', rom)"
icon="mdi-qrcode"
rounded="0"
variant="text"
/>
</v-col>
<v-col class="d-flex">
<v-menu location="bottom">
<template #activator="{ props }">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/Game/Dialog/EditRom.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useI18n } from "vue-i18n";
// Props
const { t } = useI18n();
const theme = useTheme();
const { lgAndUp, mdAndUp, smAndDown } = useDisplay();
const { lgAndUp, smAndDown } = useDisplay();
const heartbeat = storeHeartbeat();
const route = useRoute();
const show = ref(false);
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/components/common/Game/Dialog/ShowQRCode.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script setup lang="ts">
import type { SimpleRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import RDialog from "@/components/common/RDialog.vue";
import { getDownloadLink } from "@/utils";
import type { Emitter } from "mitt";
import { inject, nextTick, ref } from "vue";
import { useDisplay } from "vuetify";
import qrcode from "qrcode";
const { lgAndUp } = useDisplay();
const show = ref(false);
const emitter = inject<Emitter<Events>>("emitter");
emitter?.on("showQRCodeDialog", async (romToView: SimpleRom) => {
show.value = true;
await nextTick();
const downloadLink =
romToView.file_extension.toLowerCase() === "cia"
? getDownloadLink({
rom: romToView,
files: [],
})
: getDownloadLink({
rom: romToView,
files: [
romToView.files.filter((f) => f["filename"].endsWith(".cia"))[0]
.filename,
],
});
const qrCode = document.getElementById("qr-code");
qrcode.toCanvas(qrCode, downloadLink, {
margin: 1,
width: lgAndUp ? 300 : 200,
});
});
function closeDialog() {
show.value = false;
}
</script>

<template>
<r-dialog
@close="closeDialog"
v-model="show"
icon="mdi-pencil-box"
scroll-content
:width="lgAndUp ? 400 : 300"
>
<template #content>
<v-row no-gutters>
<v-col cols="12" class="text-center">
<canvas id="qr-code"></canvas>
</v-col>
</v-row>
</template>
</r-dialog>
</template>

<style scoped>
canvas {
margin: 2rem;
}
</style>
3 changes: 2 additions & 1 deletion frontend/src/layouts/Main.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import NewVersionDialog from "@/components/common/NewVersionDialog.vue";
import Notification from "@/components/common/Notifications/Notification.vue";
import UploadProgress from "@/components/common/Notifications/UploadProgress.vue";
import SearchCoverDialog from "@/components/common/SearchCover.vue";
import ViewLoader from "@/components/common/ViewLoader.vue";
import ShowQRCodeDialog from "@/components/common/Game/Dialog/ShowQRCode.vue";
import collectionApi from "@/services/api/collection";
import platformApi from "@/services/api/platform";
import storeCollections from "@/stores/collections";
Expand Down Expand Up @@ -72,6 +72,7 @@ onBeforeMount(async () => {
<remove-roms-from-collection-dialog />
<delete-rom-dialog />
<edit-user-dialog />
<show-q-r-code-dialog />

<new-version-dialog />
<upload-progress />
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/services/api/rom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import api from "@/services/api/index";
import socket from "@/services/socket";
import storeUpload from "@/stores/upload";
import type { DetailedRom, SimpleRom } from "@/stores/roms";
import { getDownloadLink } from "@/utils";
import { getDownloadPath } from "@/utils";
import type { AxiosProgressEvent } from "axios";
import storeHeartbeat from "@/stores/heartbeat";

Expand Down Expand Up @@ -126,7 +126,7 @@ async function downloadRom({
files?: string[];
}) {
const a = document.createElement("a");
a.href = getDownloadLink({ rom, files });
a.href = getDownloadPath({ rom, files });

document.body.appendChild(a);
a.click();
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types/emitter.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,5 @@ export type Events = {
updateDataTablePages: null;
sortBarShow: null;
romUpdated: DetailedRom;
showQRCodeDialog: SimpleRom;
};
23 changes: 22 additions & 1 deletion frontend/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export function convertCronExperssion(expression: string) {
* @param files Optional array of file names to include in the download.
* @returns The download link.
*/
export function getDownloadLink({
export function getDownloadPath({
rom,
files = [],
}: {
Expand All @@ -102,6 +102,16 @@ export function getDownloadLink({
}?${queryParams.toString()}`;
}

export function getDownloadLink({
rom,
files = [],
}: {
rom: SimpleRom;
files?: string[];
}) {
return `${window.location.origin}${encodeURI(getDownloadPath({ rom, files }))}`;
}

/**
* Format bytes as human-readable text.
*
Expand Down Expand Up @@ -532,3 +542,14 @@ export function getTextForStatus(status: PlayingStatus) {
export function getStatusKeyForText(text: string) {
return inverseRomStatusMap[text];
}

/**
* Check if a ROM is a 3DS .CIA file
* @param rom The ROM object.
* @returns True if the ROM is a 3DS .CIA file, false otherwise.
*/
export function is3DSCIARom(rom: SimpleRom) {
if (rom.platform_slug !== "3ds") return false;
if (rom.file_extension.toLowerCase() === "cia") return true;
return rom.files.some((f) => f["filename"].toLowerCase().endsWith(".cia"));
}

0 comments on commit 92850f1

Please sign in to comment.