File size: 4,122 Bytes
abcf568
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import { useI18n } from '../../../i18n'

interface PlotStudioHistoryAsideProps<TWorkSummary extends {
  work: { id: string; title: string; status?: string }
  result?: {
    kind?: string
    attachments?: Array<{ path: string; mimeType?: string }>
  } | null
}> {
  works: TWorkSummary[]
  selectedWorkId: string | null
  historyCountLabel: string
  maxHistorySlots: number
  onSelectWork: (workId: string) => void
}

export function PlotStudioHistoryAside<TWorkSummary extends {
  work: { id: string; title: string; status?: string }
  result?: {
    kind?: string
    attachments?: Array<{ path: string; mimeType?: string }>
  } | null
}>({
  works,
  selectedWorkId,
  historyCountLabel,
  maxHistorySlots,
  onSelectWork,
}: PlotStudioHistoryAsideProps<TWorkSummary>) {
  const { t } = useI18n()

  return (
    <aside className="flex min-h-0 w-full shrink-0 flex-col md:w-[32rem] lg:w-[35rem] xl:w-[38rem]">
      <div className="mb-3 h-[1px] bg-accent opacity-[0.08] dark:opacity-[0.18]" />
      <div className="mb-4 flex items-center justify-between font-mono text-[10px] uppercase tracking-[0.4em] opacity-40 dark:opacity-55">
        <span>{t('studio.plot.history')}</span>
        <span>{historyCountLabel}-{maxHistorySlots}</span>
      </div>

      <div className="no-scrollbar min-h-0 overflow-y-auto">
        <div className="grid grid-cols-3 content-start gap-4 md:gap-5">
          {works.map((entry) => {
            const isSelected = entry.work.id === selectedWorkId
            const attachment = entry.result?.attachments?.find((item) => (
              item.mimeType?.startsWith('image/') || /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(item.path)
            ))
            const failed = entry.work.status === 'failed' || entry.result?.kind === 'failure-report'

            return (
              <button
                key={entry.work.id}
                type="button"
                onClick={() => onSelectWork(entry.work.id)}
                className={`group relative aspect-square overflow-hidden rounded-[1.6rem] border transition-all duration-500 ${
                  isSelected
                    ? 'border-black/10 bg-black/[0.08] dark:border-white/10 dark:bg-bg-secondary/72'
                    : 'border-transparent bg-black/[0.028] hover:bg-black/[0.05] dark:bg-bg-secondary/38 dark:hover:bg-bg-secondary/55'
                }`}
              >
                {attachment ? (
                  <img
                    src={attachment.path}
                    alt={entry.work.title}
                    className={`h-full w-full object-cover transition-all duration-700 ${
                      isSelected
                        ? 'scale-100 opacity-100'
                        : 'scale-[1.08] opacity-32 group-hover:scale-100 group-hover:opacity-72 dark:opacity-45 dark:group-hover:opacity-80'
                    }`}
                  />
                ) : failed ? (
                  <div className="flex h-full w-full items-center justify-center bg-rose-500/[0.06] text-rose-700/60 dark:bg-rose-400/[0.08] dark:text-rose-200/65">
                    <span className="font-mono text-[8px] uppercase tracking-[0.24em]">Fail</span>
                  </div>
                ) : (
                  <div className="flex h-full w-full items-center justify-center">
                    <span className="font-mono text-[8px] uppercase tracking-[0.22em] opacity-12 dark:opacity-25">IMG</span>
                  </div>
                )}
                <span className="pointer-events-none absolute left-2 top-2 font-mono text-[8px] uppercase tracking-[0.24em] text-white/72 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
                  {entry.work.title.slice(0, 8)}
                </span>
              </button>
            )
          })}
          {works.length === 0 && (
            <div className="col-span-3 flex aspect-[3/1] items-center justify-center">
              <span className="font-mono text-[9px] uppercase tracking-[0.42em] opacity-[0.18]">Null Stack</span>
            </div>
          )}
        </div>
      </div>
    </aside>
  )
}