@@ -50,14 +50,15 @@ function writePinnedSet(pinnedSet) {
5050 window . localStorage . setItem ( PINNED_KEY , JSON . stringify ( [ ...pinnedSet ] . sort ( ) ) ) ;
5151}
5252
53- function buildClassTokens ( engineClassesUsed ) {
54- const classEntries = asArray ( engineClassesUsed )
55- . map ( ( entry ) => normalize ( entry ) )
56- . filter ( Boolean ) ;
57- return classEntries . map ( ( entry ) => {
58- const name = entry . split ( "/" ) . at ( - 1 ) || entry ;
59- return { value : entry , label : name } ;
60- } ) ;
53+ function buildClassTokens ( classValues , engineClassesUsed ) {
54+ const classEntries = asArray ( classValues ) . length > 0 ? asArray ( classValues ) : asArray ( engineClassesUsed ) ;
55+ const deduped = [ ...new Set ( classEntries . map ( ( entry ) => normalize ( entry ) ) . filter ( Boolean ) ) ] ;
56+ return deduped
57+ . map ( ( entry ) => {
58+ const name = entry . split ( "/" ) . at ( - 1 ) || entry ;
59+ return { value : entry , label : name } ;
60+ } )
61+ . sort ( ( a , b ) => a . label . localeCompare ( b . label , undefined , { sensitivity : "base" } ) ) ;
6162}
6263
6364function buildSampleRows ( metadata , pinnedSet ) {
@@ -90,7 +91,7 @@ function buildSampleRows(metadata, pinnedSet) {
9091 const description = normalize ( sample ?. description ) || "No description available." ;
9192 const href = normalize ( sample ?. href ) || `./phase-${ phase } /${ id } /index.html` ;
9293 const tags = asArray ( sample ?. tags ) . map ( ( tag ) => normalizeTag ( tag ) ) . filter ( Boolean ) ;
93- const classTokens = buildClassTokens ( sample ?. engineClassesUsed ) ;
94+ const classTokens = buildClassTokens ( sample ?. classValues , sample ?. engineClassesUsed ) ;
9495 const previewSrc = normalize ( sample ?. thumbnail ) || normalize ( sample ?. preview ) || "" ;
9596 return {
9697 id,
@@ -110,7 +111,11 @@ function buildSampleRows(metadata, pinnedSet) {
110111 . sort ( ( a , b ) => a . id . localeCompare ( b . id ) ) ;
111112
112113 const phases = [ ...new Set ( sampleRows . map ( ( sample ) => sample . phase ) ) ] . sort ( sortPhase ) ;
113- const classes = [ ...new Set ( sampleRows . flatMap ( ( sample ) => sample . classTokens . map ( ( token ) => token . value ) ) ) ] . sort ( ) ;
114+ const classes = [ ...new Map (
115+ sampleRows . flatMap ( ( sample ) => sample . classTokens ) . map ( ( token ) => [ token . value , token . label ] )
116+ ) . entries ( ) ]
117+ . map ( ( [ value , label ] ) => ( { value, label } ) )
118+ . sort ( ( a , b ) => a . label . localeCompare ( b . label , undefined , { sensitivity : "base" } ) ) ;
114119 const tags = [ ...new Set ( sampleRows . flatMap ( ( sample ) => sample . tags ) ) ] . sort ( ) ;
115120
116121 return { sampleRows, phases, classes, tags, phaseInfoMap } ;
@@ -154,24 +159,14 @@ function groupByPhase(sampleRows, phaseInfoMap) {
154159 return [ ...grouped . values ( ) ] . sort ( ( a , b ) => sortPhase ( a . phase , b . phase ) ) ;
155160}
156161
157- function renderPinnedList ( container , rows ) {
158- container . innerHTML = "" ;
159- if ( rows . length === 0 ) {
160- const note = document . createElement ( "p" ) ;
161- note . textContent = "No pinned samples yet." ;
162- container . appendChild ( note ) ;
163- return ;
164- }
165- for ( const sample of rows ) {
166- container . appendChild ( buildSampleCard ( sample ) ) ;
167- }
168- }
169-
170162function buildSampleCard ( sample ) {
171163 const card = document . createElement ( "article" ) ;
172164 card . className = "card-link sample-card" ;
173165 card . dataset . sampleId = sample . id ;
174166
167+ const previewWrap = document . createElement ( "div" ) ;
168+ previewWrap . className = "sample-preview-wrap" ;
169+
175170 const launch = document . createElement ( "a" ) ;
176171 launch . className = "sample-preview-link" ;
177172 launch . href = sample . href ;
@@ -183,32 +178,56 @@ function buildSampleCard(sample) {
183178 launch . classList . add ( "sample-preview-missing" ) ;
184179 }
185180
181+ const pinInputId = `sample-pin-${ sample . id } ` ;
182+ const pinInput = document . createElement ( "input" ) ;
183+ pinInput . id = pinInputId ;
184+ pinInput . type = "checkbox" ;
185+ pinInput . className = "sample-pin-toggle" ;
186+ pinInput . dataset . samplePin = sample . id ;
187+ pinInput . checked = sample . pinned ;
188+
189+ const pinLabel = document . createElement ( "label" ) ;
190+ pinLabel . className = "sample-pin-label" ;
191+ pinLabel . setAttribute ( "for" , pinInputId ) ;
192+ pinLabel . setAttribute ( "title" , sample . pinned ? "Unpin" : "Pin" ) ;
193+ pinLabel . setAttribute ( "aria-label" , sample . pinned ? "Unpin sample" : "Pin sample" ) ;
194+ pinLabel . textContent = "??" ;
195+
196+ previewWrap . appendChild ( launch ) ;
197+ previewWrap . appendChild ( pinInput ) ;
198+ previewWrap . appendChild ( pinLabel ) ;
199+
186200 const title = document . createElement ( "h3" ) ;
187201 title . innerHTML = `<a class="sample-title-link" href="${ escapeHtml ( sample . href ) } ">${ escapeHtml ( sample . title ) } </a>` ;
188202
189203 const description = document . createElement ( "p" ) ;
190204 description . textContent = sample . description ;
191205
192206 const meta = document . createElement ( "p" ) ;
193- const classLabel = sample . classTokens . length > 0
194- ? sample . classTokens . map ( ( token ) => token . label ) . join ( ", " )
195- : "none" ;
207+ const classLabel = sample . classTokens . length > 0 ? sample . classTokens . map ( ( token ) => token . label ) . join ( ", " ) : "none" ;
196208 const tagLabel = sample . tags . length > 0 ? sample . tags . join ( ", " ) : "none" ;
197209 meta . textContent = `Phase ${ sample . phase } | Classes: ${ classLabel } | Tags: ${ tagLabel } ` ;
198210
199- const pinRow = document . createElement ( "div" ) ;
200- pinRow . className = "sample-pin-row" ;
201- const pinInputId = `sample-pin-${ sample . id } ` ;
202- pinRow . innerHTML = `<input id="${ pinInputId } " type="checkbox" class="sample-pin-toggle" data-sample-pin="${ sample . id } " ${ sample . pinned ? "checked" : "" } ><label for="${ pinInputId } " class="sample-pin-label" title="${ sample . pinned ? "Unpin" : "Pin" } ">📌</label>` ;
203-
204- card . appendChild ( launch ) ;
211+ card . appendChild ( previewWrap ) ;
205212 card . appendChild ( title ) ;
206213 card . appendChild ( description ) ;
207214 card . appendChild ( meta ) ;
208- card . appendChild ( pinRow ) ;
209215 return card ;
210216}
211217
218+ function renderPinnedList ( container , rows ) {
219+ container . innerHTML = "" ;
220+ if ( rows . length === 0 ) {
221+ const note = document . createElement ( "p" ) ;
222+ note . textContent = "No pinned samples yet." ;
223+ container . appendChild ( note ) ;
224+ return ;
225+ }
226+ for ( const sample of rows ) {
227+ container . appendChild ( buildSampleCard ( sample ) ) ;
228+ }
229+ }
230+
212231function renderPhaseSections ( container , phaseGroups ) {
213232 container . innerHTML = "" ;
214233 for ( const phaseGroup of phaseGroups ) {
@@ -267,23 +286,26 @@ export async function initSamplesIndex() {
267286
268287 const model = buildSampleRows ( metadata , pinnedSet ) ;
269288 setSelectOptions ( phaseSelect , model . phases , ( value ) => `Phase ${ value } ` ) ;
270- setSelectOptions ( classSelect , model . classes , ( value ) => value . split ( "/" ) . at ( - 1 ) || value ) ;
289+ setSelectOptions ( classSelect , model . classes . map ( ( entry ) => entry . value ) , ( value ) => {
290+ const found = model . classes . find ( ( entry ) => entry . value === value ) ;
291+ return found ?. label || value . split ( "/" ) . at ( - 1 ) || value ;
292+ } ) ;
271293 setSelectOptions ( tagSelect , model . tags , ( value ) => value ) ;
272294
273295 const render = ( ) => {
274- const model = buildSampleRows ( metadata , pinnedSet ) ;
296+ const nextModel = buildSampleRows ( metadata , pinnedSet ) ;
275297 const filterState = {
276298 phase : normalize ( phaseSelect . value ) ,
277299 className : normalize ( classSelect . value ) ,
278300 tag : normalize ( tagSelect . value ) ,
279301 query : normalize ( searchInput . value )
280302 } ;
281- const filteredRows = filterSampleRows ( model . sampleRows , filterState ) ;
303+ const filteredRows = filterSampleRows ( nextModel . sampleRows , filterState ) ;
282304 const pinnedRows = filteredRows . filter ( ( entry ) => entry . pinned ) ;
283- const phaseGroups = groupByPhase ( filteredRows , model . phaseInfoMap ) ;
305+ const phaseGroups = groupByPhase ( filteredRows , nextModel . phaseInfoMap ) ;
284306 renderPinnedList ( pinnedContainer , pinnedRows ) ;
285307 renderPhaseSections ( listContainer , phaseGroups ) ;
286- updateStatus ( statusNode , filteredRows , model . sampleRows , phaseGroups ) ;
308+ updateStatus ( statusNode , filteredRows , nextModel . sampleRows , phaseGroups ) ;
287309 } ;
288310
289311 const handlePinEvent = ( event ) => {
@@ -317,4 +339,4 @@ export async function initSamplesIndex() {
317339
318340if ( typeof window !== "undefined" && typeof document !== "undefined" ) {
319341 initSamplesIndex ( ) ;
320- }
342+ }
0 commit comments