Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/main/features/game/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {
cancelBatchStorageSizeCalculation,
deleteGameMemory,
deleteGameSave,
hideGameFromRecentGames,
isBatchStorageSizeCalculationRunning,
recalculateLastRunDate,
restoreGameSave,
searchGameSavePaths,
updateGameMemoryCover
Expand Down Expand Up @@ -121,4 +123,12 @@ export function setupGameIPC(): void {
ipcManager.handle('game:is-batch-storage-size-calculation-running', async () => {
return isBatchStorageSizeCalculationRunning()
})

ipcManager.handle('game:recalculate-last-run-date', async (_, gameId: string) => {
return await recalculateLastRunDate(gameId)
})

ipcManager.handle('game:hide-from-recent-games', async (_, gameId: string) => {
await hideGameFromRecentGames(gameId)
})
}
1 change: 1 addition & 0 deletions src/main/features/game/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './utils'
export * from './save'
export * from './active'
export * from './size'
export * from './record'
19 changes: 19 additions & 0 deletions src/main/features/game/services/record.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { GameDBManager } from '~/core/database'
import { updateRecentGamesInTray } from '~/features/system'
import { calculateLastRunDateFromTimers } from '@appUtils'

export async function recalculateLastRunDate(gameId: string): Promise<string> {
const timers = await GameDBManager.getGameValue(gameId, 'record.timers')
const lastRunDate = calculateLastRunDateFromTimers(timers)

await GameDBManager.setGameValue(gameId, 'record.lastRunDate', lastRunDate)
await GameDBManager.setGameValue(gameId, 'record.hideFromRecentGames', false)
await updateRecentGamesInTray()

return lastRunDate
}

export async function hideGameFromRecentGames(gameId: string): Promise<void> {
await GameDBManager.setGameValue(gameId, 'record.hideFromRecentGames', true)
await updateRecentGamesInTray()
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ async function convertGame(gameId: string, gamePath: string): Promise<void> {
score: record.score || -1,
playTime: record.playingTime || 0,
playStatus: record.playStatus || 'unplayed',
hideFromRecentGames: false,
timers: record.timer || [],
dailyPlayTimes: [],
storageSize: STORAGE_SIZE_NOT_CALCULATED
Expand Down
1 change: 1 addition & 0 deletions src/main/features/monitor/services/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ export class GameMonitor {
if (await GameDBManager.getGame(this.options.gameId)) {
await GameDBManager.setGameValue(this.options.gameId, 'record.timers', dbTimers)
await GameDBManager.setGameValue(this.options.gameId, 'record.lastRunDate', this.endTime)
await GameDBManager.setGameValue(this.options.gameId, 'record.hideFromRecentGames', false)
await GameDBManager.setGameValue(this.options.gameId, 'record.playTime', playTime)
}

Expand Down
7 changes: 6 additions & 1 deletion src/main/features/system/services/tray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,12 @@ export class TrayManager {
const gameDocs = await GameDBManager.getAllGames()
const recentGameIds = (
await GameDBManager.sortGames({ by: 'record.lastRunDate', order: 'desc' })
).slice(0, 5)
)
.filter((gameId) => {
const game = gameDocs[gameId]
return game?.record?.lastRunDate && game.record.hideFromRecentGames !== true
})
.slice(0, 5)

const recentGames = await Promise.all(
recentGameIds.map(async (gameId) => {
Expand Down
16 changes: 14 additions & 2 deletions src/renderer/locales/en/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
"recent": {
"title": "Recent Games",
"hide": "Hide",
"empty": "No recent games"
"remove": "Remove from Recent Games",
"empty": "No recent games",
"notifications": {
"removing": "Removing from recent games…",
"removed": "Removed from recent games",
"removeError": "Failed to remove from recent games"
}
},
"filter": {
"results": "Filter Results",
Expand Down Expand Up @@ -410,6 +416,7 @@
"rename": "Edit Basic Information",
"editLogo": "Edit Logo",
"editPlayTime": "Edit Play Time",
"recalculateLastRunDate": "Recalculate Last Played Date",
"markNSFW": "Mark as NSFW",
"unmarkNSFW": "Unmark NSFW",
"downloadMetadata": "Update Metadata",
Expand All @@ -425,7 +432,12 @@
"storageSizeCalculated": "Game size calculated: {{size}}",
"storageSizeError": "Failed to calculate game size",
"storageSizeConfirm": "Calculate game size? This may take a moment.",
"storageSizeRecalculateConfirm": "Current size: {{currentSize}}. Recalculate game size?"
"storageSizeRecalculateConfirm": "Current size: {{currentSize}}. Recalculate game size?",
"recalculatingLastRunDate": "Recalculating last played date…",
"lastRunDateRecalculated": "Last played date updated to {{date, niceDate}}",
"lastRunDateCleared": "No valid timers found. Last played date cleared",
"lastRunDateRecalculateError": "Failed to recalculate last played date",
"lastRunDateRecalculateConfirm": "The last played date will be recalculated from the current timer list. If no valid timers exist, the date will be cleared."
}
},
"timersEditor": {
Expand Down
16 changes: 14 additions & 2 deletions src/renderer/locales/ja/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
"recent": {
"title": "最近のゲーム",
"hide": "非表示",
"empty": "最近のゲームはありません"
"remove": "最近のゲームから削除",
"empty": "最近のゲームはありません",
"notifications": {
"removing": "最近のゲームから削除中…",
"removed": "最近のゲームから削除しました",
"removeError": "最近のゲームからの削除に失敗しました"
}
},
"filter": {
"results": "フィルター結果",
Expand Down Expand Up @@ -410,6 +416,7 @@
"rename": "基本情報を編集",
"editLogo": "ロゴを編集",
"editPlayTime": "プレイ時間を編集",
"recalculateLastRunDate": "最終プレイ日を再計算",
"markNSFW": "NSFW としてマークする",
"unmarkNSFW": "NSFW のマークを解除する",
"downloadMetadata": "メタデータを更新",
Expand All @@ -425,7 +432,12 @@
"storageSizeCalculated": "ゲームサイズを計算しました:{{size}}",
"storageSizeError": "ゲームサイズの計算に失敗しました",
"storageSizeConfirm": "ゲームサイズを計算しますか?少し時間がかかる場合があります。",
"storageSizeRecalculateConfirm": "現在のサイズ:{{currentSize}}。ゲームサイズを再計算しますか?"
"storageSizeRecalculateConfirm": "現在のサイズ:{{currentSize}}。ゲームサイズを再計算しますか?",
"recalculatingLastRunDate": "最終プレイ日を再計算中…",
"lastRunDateRecalculated": "最終プレイ日を {{date, niceDate}} に更新しました",
"lastRunDateCleared": "有効なタイマーが見つかりません。最終プレイ日をクリアしました",
"lastRunDateRecalculateError": "最終プレイ日の再計算に失敗しました",
"lastRunDateRecalculateConfirm": "現在のタイマーリストから最終プレイ日を再計算します。有効なタイマーがない場合、日付はクリアされます。"
}
},
"timersEditor": {
Expand Down
16 changes: 14 additions & 2 deletions src/renderer/locales/zh-CN/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
"recent": {
"title": "最近游戏",
"hide": "隐藏",
"empty": "暂无最近游戏"
"remove": "从最近游戏中移除",
"empty": "暂无最近游戏",
"notifications": {
"removing": "正在移除最近游戏…",
"removed": "已从最近游戏中移除",
"removeError": "移除最近游戏失败"
}
},
"filter": {
"results": "筛选结果",
Expand Down Expand Up @@ -410,6 +416,7 @@
"rename": "修改基本信息",
"editLogo": "编辑徽标",
"editPlayTime": "修改游玩时间",
"recalculateLastRunDate": "重新计算最后运行日期",
"markNSFW": "标记为NSFW",
"unmarkNSFW": "取消标记为 NSFW",
"downloadMetadata": "更新资料数据",
Expand All @@ -425,7 +432,12 @@
"storageSizeCalculated": "游戏大小已计算:{{size}}",
"storageSizeError": "计算游戏大小失败",
"storageSizeConfirm": "计算游戏大小?这可能需要一些时间。",
"storageSizeRecalculateConfirm": "当前大小:{{currentSize}}。重新计算游戏大小?"
"storageSizeRecalculateConfirm": "当前大小:{{currentSize}}。重新计算游戏大小?",
"recalculatingLastRunDate": "正在重新计算最后运行日期…",
"lastRunDateRecalculated": "最后运行日期已更新为 {{date, niceDate}}",
"lastRunDateCleared": "没有有效计时器,最后运行日期已清空",
"lastRunDateRecalculateError": "重新计算最后运行日期失败",
"lastRunDateRecalculateConfirm": "将根据当前计时器列表重新设置最后运行日期。没有有效计时器时会清空该日期。"
}
},
"timersEditor": {
Expand Down
16 changes: 14 additions & 2 deletions src/renderer/locales/zh-TW/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
"recent": {
"title": "最近遊戲",
"hide": "隱藏",
"empty": "暫無最近遊戲"
"remove": "從最近遊戲中移除",
"empty": "暫無最近遊戲",
"notifications": {
"removing": "正在從最近遊戲中移除…",
"removed": "已從最近遊戲中移除",
"removeError": "從最近遊戲中移除失敗"
}
},
"filter": {
"results": "篩選結果",
Expand Down Expand Up @@ -410,6 +416,7 @@
"rename": "修改基本資訊",
"editLogo": "編輯徽標",
"editPlayTime": "修改遊玩時間",
"recalculateLastRunDate": "重新計算最後執行日期",
"markNSFW": "標記為 NSFW",
"unmarkNSFW": "取消標記為 NSFW",
"downloadMetadata": "更新資料",
Expand All @@ -425,7 +432,12 @@
"storageSizeCalculated": "遊戲大小已計算:{{size}}",
"storageSizeError": "計算遊戲大小失敗",
"storageSizeConfirm": "計算遊戲大小?這可能需要一些時間。",
"storageSizeRecalculateConfirm": "當前大小:{{currentSize}}。重新計算遊戲大小?"
"storageSizeRecalculateConfirm": "當前大小:{{currentSize}}。重新計算遊戲大小?",
"recalculatingLastRunDate": "正在重新計算最後執行日期…",
"lastRunDateRecalculated": "最後執行日期已更新為 {{date, niceDate}}",
"lastRunDateCleared": "沒有有效的計時器,最後執行日期已清除",
"lastRunDateRecalculateError": "重新計算最後執行日期失敗",
"lastRunDateRecalculateConfirm": "將根據目前的計時器列表重新設定最後執行日期。若沒有有效的計時器,將清除該日期。"
}
},
"timersEditor": {
Expand Down
9 changes: 8 additions & 1 deletion src/renderer/src/components/Game/Config/ManageMenu/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import {
} from '~/components/ui/dropdown-menu'
import { useConfigState, useGameLocalState, useGameState } from '~/hooks'
import { useGameAdderStore } from '~/pages/GameAdder/store'
import { formatStorageSize } from '~/utils'
import { RecalculateLastRunDateAlertDialog } from '../../Overview/Record/RecalculateLastRunDateAlertDialog'
import { useGameDetailStore } from '../../store'
import { DeleteGameAlert } from './DeleteGameAlert'
import { formatStorageSize } from '~/utils'

export function ManageMenu({
gameId,
Expand Down Expand Up @@ -176,6 +177,12 @@ export function ManageMenu({
{t('detail.manage.calculateStorageSize')}
</DropdownMenuItem>
)}
{/* Recalculate Last Run Date */}
<RecalculateLastRunDateAlertDialog gameId={gameId}>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
{t('detail.manage.recalculateLastRunDate')}
</DropdownMenuItem>
</RecalculateLastRunDateAlertDialog>

<DropdownMenuSeparator />

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { ipcManager } from '~/app/ipc'
import { useLibrarybarStore } from '~/components/Librarybar/store'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger
} from '~/components/ui/alert-dialog'

export function RecalculateLastRunDateAlertDialog({
gameId,
children
}: {
gameId: string
children: React.ReactNode
}): React.JSX.Element {
const { t } = useTranslation('game')
const refreshGameList = useLibrarybarStore((state) => state.refreshGameList)

const handleRecalculate = (): void => {
toast.promise(
ipcManager.invoke('game:recalculate-last-run-date', gameId).then((lastRunDate) => {
refreshGameList()
return lastRunDate
}),
{
loading: t('detail.manage.notifications.recalculatingLastRunDate'),
success: (lastRunDate: string) =>
lastRunDate
? t('detail.manage.notifications.lastRunDateRecalculated', {
date: lastRunDate
})
: t('detail.manage.notifications.lastRunDateCleared'),
error: () => t('detail.manage.notifications.lastRunDateRecalculateError')
}
)
}

return (
<AlertDialog>
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('detail.manage.recalculateLastRunDate')}</AlertDialogTitle>
<AlertDialogDescription>
{t('detail.manage.notifications.lastRunDateRecalculateConfirm')}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t('utils:common.cancel')}</AlertDialogCancel>
<AlertDialogAction onClick={handleRecalculate}>
{t('utils:common.confirm')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
27 changes: 16 additions & 11 deletions src/renderer/src/components/Game/Overview/Record/RecordCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,29 @@ import { Button } from '~/components/ui/button'
import { PopoverTrigger } from '~/components/ui/popover'
import { cn } from '~/utils'

export function RecordCard({
title,
content,
icon,
onClick,
asPopoverTrigger,
className = ''
}: {
interface RecordCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onClick'> {
title: string
content: string
icon?: string
onClick?: () => void
asPopoverTrigger?: boolean
className?: string
Comment thread
fishyy119 marked this conversation as resolved.
}): React.JSX.Element {
}

export const RecordCard = React.forwardRef<HTMLDivElement, RecordCardProps>(function RecordCard(
{ title, content, icon, onClick, asPopoverTrigger, className = '', ...props },
ref
): React.JSX.Element {
const ButtonWrapper = ({ children }: { children: React.ReactNode }): React.JSX.Element =>
asPopoverTrigger ? <PopoverTrigger asChild>{children}</PopoverTrigger> : <>{children}</>
const isInteractive = onClick || asPopoverTrigger

return (
<div className={cn('flex flex-row justify-center items-center', className)}>
<div
ref={ref}
className={cn('flex flex-row justify-center items-center', className)}
{...props}
>
{isInteractive ? (
<ButtonWrapper>
<Button variant={'bare'} size={'icon'} className={cn('group mr-3')} onClick={onClick}>
Expand All @@ -49,4 +52,6 @@ export function RecordCard({
</div>
</div>
)
}
})

RecordCard.displayName = 'RecordCard'
Loading