Skip to content
Merged
12 changes: 12 additions & 0 deletions backend/endpoints/responses/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,15 +329,27 @@ def sort_comparator(self) -> str:


class SimpleRomSchema(RomSchema):
has_notes: bool = False

@classmethod
def from_orm_with_request(cls, db_rom: Rom, request: Request) -> SimpleRomSchema:
user_id = request.user.id
db_rom = cls.populate_properties(db_rom, request)

# Calculate has_notes from already-loaded notes relationship
# Check if there are any notes for this ROM (public or user's own)
notes = getattr(db_rom, "notes", [])
db_rom.has_notes = any( # type: ignore
note.is_public or note.user_id == user_id for note in notes
)

return cls.model_validate(db_rom)

@classmethod
def from_orm_with_factory(cls, db_rom: Rom) -> SimpleRomSchema:
db_rom.rom_user = rom_user_schema_factory() # type: ignore
db_rom.siblings = [] # type: ignore
db_rom.has_notes = False # type: ignore
return cls.model_validate(db_rom)


Expand Down
2 changes: 2 additions & 0 deletions backend/handler/database/roms_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ def wrapper(*args, **kwargs):
selectinload(Rom.sibling_roms).options(
noload(Rom.platform), noload(Rom.metadatum)
),
# Show notes indicator on cards
selectinload(Rom.notes),
)
return func(*args, **kwargs)

Expand Down
1 change: 1 addition & 0 deletions frontend/src/__generated__/models/SimpleRomSchema.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 1 addition & 7 deletions frontend/src/components/common/Game/Card/Base.vue
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,6 @@ const {
forceBoxart: props.forceBoxart,
});

const hasNotes = computed(() => {
// TODO: Add note count to SimpleRom or check all_user_notes
// For now, return false until we implement proper note counting
return false;
});

const computedAspectRatio = computed(() => {
return galleryViewStore.getAspectRatio({
platformId: props.rom.platform_id,
Expand Down Expand Up @@ -389,7 +383,7 @@ onBeforeUnmount(() => {
<v-icon>mdi-star</v-icon>
</v-chip>
<v-chip
v-if="hasNotes && showChips"
v-if="rom.has_notes && showChips"
class="translucent text-white mr-1 mb-1 px-1"
density="compact"
title="View notes"
Expand Down
114 changes: 23 additions & 91 deletions frontend/src/components/common/Game/Dialog/NoteDialog.vue
Original file line number Diff line number Diff line change
@@ -1,55 +1,48 @@
<script setup lang="ts">
import { MdPreview } from "md-editor-v3";
import "md-editor-v3/lib/style.css";
import type { Emitter } from "mitt";
import { inject, ref, computed } from "vue";
import { inject, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useTheme } from "vuetify";
import MultiNoteManager from "@/components/Details/MultiNoteManager.vue";
import RDialog from "@/components/common/RDialog.vue";
import romApi from "@/services/api/rom";
import type { SimpleRom } from "@/stores/roms";
import type { DetailedRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { toBrowserLocale } from "@/utils";

const theme = useTheme();
const emitter = inject<Emitter<Events>>("emitter");
const { t, locale } = useI18n();
const { t } = useI18n();

const rom = ref<SimpleRom | null>(null);
const rom = ref<DetailedRom | null>(null);
const show = ref(false);
const notes = ref<any[]>([]);
const loading = ref(false);

// Computed to get current user notes
const currentUserNotes = computed(() => {
return notes.value
.filter((note) => note.user_id === rom.value?.rom_user?.user_id)
.sort((a, b) => a.title.localeCompare(b.title));
});

emitter?.on("showNoteDialog", async (romToShow) => {
rom.value = romToShow;
show.value = true;

// Fetch notes for this ROM
if (romToShow.id) {
loading.value = true;
try {
const response = await romApi.getRomNotes({ romId: romToShow.id });
notes.value = response.data;
const response = await romApi.getRom({ romId: romToShow.id });
rom.value = response.data;
} catch (error) {
console.error("Failed to fetch notes:", error);
notes.value = [];
} finally {
loading.value = false;
console.error("Failed to fetch ROM details:", error);
rom.value = null;
}
}
});

async function onNotesUpdated() {
if (rom.value?.id) {
try {
const updatedRom = await romApi.getRom({ romId: rom.value.id });
// Update the rom with the new data
Object.assign(rom.value, updatedRom.data);
} catch (error) {
console.error("Failed to refetch ROM data:", error);
}
}
}

function closeDialog() {
show.value = false;
rom.value = null;
notes.value = [];
}
</script>

Expand All @@ -68,69 +61,8 @@ function closeDialog() {
</v-toolbar-title>
</template>
<template #content>
<div class="pa-4">
<div v-if="loading" class="text-center py-8">
<v-progress-circular indeterminate color="primary" />
<p class="text-body-2 mt-2">{{ t("common.loading") }}...</p>
</div>
<div v-else-if="currentUserNotes.length > 0">
<v-expansion-panels multiple flat variant="accordion">
<v-expansion-panel
v-for="note in currentUserNotes"
:key="note.title"
:value="note.title"
rounded="0"
>
<v-expansion-panel-title class="bg-toplayer">
<div class="d-flex justify-space-between align-center w-100">
<span class="text-body-1">{{ note.title }}</span>
<div class="d-flex gap-2 align-center mr-4">
<v-chip
:color="note.is_public ? 'success' : 'warning'"
variant="text"
class="mr-2"
>
<v-icon>
{{
note.is_public ? "mdi-lock-open-variant" : "mdi-lock"
}}
</v-icon>
</v-chip>
</div>
</div>
</v-expansion-panel-title>
<v-expansion-panel-text class="bg-surface">
<MdPreview
no-highlight
no-katex
no-mermaid
:model-value="note.content"
:theme="theme.global.name.value === 'dark' ? 'dark' : 'light'"
language="en-US"
preview-theme="vuepress"
code-theme="github"
class="py-4 px-6"
/>
<v-card-subtitle
v-if="note.updated_at"
class="text-caption mt-2 mb-2"
>
{{ t("common.last-updated") }}:
{{
new Date(note.updated_at).toLocaleString(
toBrowserLocale(locale),
)
}}
</v-card-subtitle>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</div>
<div v-else class="text-center py-8">
<v-icon color="grey" size="64">mdi-note-text-outline</v-icon>
<p class="text-h6 text-grey mt-4 mb-2">{{ t("rom.no-notes") }}</p>
<p class="text-body-2 text-grey">{{ t("rom.no-notes-desc") }}</p>
</div>
<div class="pa-2">
<MultiNoteManager :rom="rom" @notes-updated="onNotesUpdated" />
Comment thread
zurdi15 marked this conversation as resolved.
</div>
</template>
</RDialog>
Expand Down
10 changes: 2 additions & 8 deletions frontend/src/components/common/Game/VirtualTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,6 @@ const HEADERS = [

const selectedRomIDs = computed(() => selectedRoms.value.map((rom) => rom.id));

function hasNotes(item: SimpleRom): boolean {
// TODO: Add note count to SimpleRom or check all_user_notes
// For now, return false until we implement proper note counting
return false;
}

function showNoteDialog(event: MouseEvent | KeyboardEvent, item: SimpleRom) {
event.preventDefault();
emitter?.emit("showNoteDialog", item);
Expand Down Expand Up @@ -312,7 +306,7 @@ function updateOptions({ sortBy }: { sortBy: SortBy }) {
<v-icon>mdi-card-multiple-outline</v-icon>
</v-chip>
<v-chip
v-if="hasNotes(item)"
v-if="item.has_notes"
class="translucent text-white mr-1 px-1"
chip
size="x-small"
Expand All @@ -326,7 +320,7 @@ function updateOptions({ sortBy }: { sortBy: SortBy }) {
:text="`Missing from filesystem: ${item.fs_path}/${item.fs_name}`"
class="mr-1 px-1 item-chip"
chip
chip-size="x-small"
chip-size="small"
Comment thread
zurdi15 marked this conversation as resolved.
/>
</template>
</v-list-item>
Expand Down
Loading