starshard/peafowl
Template
1
Fork 0
mirror of https://github.com/starshardstudio/peafowl.git synced 2024-11-24 22:14:19 +00:00

Add sorting to the games list

This commit is contained in:
Steffo 2024-07-07 04:45:11 +02:00
parent efe89dda2b
commit 0cc044ddd7
Signed by: steffo
GPG key ID: 5ADA3868646C3FC0
10 changed files with 296 additions and 18 deletions

View file

@ -0,0 +1,3 @@
<component name="ProjectDictionaryState">
<dictionary name="steffo" />
</component>

View file

@ -3,10 +3,18 @@
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/_static" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/games" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/games" type="java-resource" />
<excludeFolder url="file://$MODULE_DIR$/_site" /> <excludeFolder url="file://$MODULE_DIR$/_site" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="TemplatesService">
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/_includes" />
</list>
</option>
</component>
</module> </module>

View file

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run" type="DenoConfigurationType" inputPath="$PROJECT_DIR$/_run.ts" programParameters="run --allow-read --allow-write --allow-net --allow-env --allow-run"> <configuration default="false" name="Run" type="DenoConfigurationType" inputPath="$PROJECT_DIR$/_run.ts" programParameters="run --allow-read --allow-write --allow-net --allow-env --allow-run">
<option name="applicationArguments" value="--watch" /> <option name="applicationArguments" value="--watch --host=0.0.0.0" />
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>

View file

@ -4,10 +4,10 @@ import {Progress, progressToClassName, progressToIconDef, progressToTitle} from
import {ratingToClassName} from "../_utils/rating.ts" import {ratingToClassName} from "../_utils/rating.ts"
export type GameRowColumnKind = "rating" | "progress" | "name" | "hascontent" | "date" | "hoursplayed" export type GameRowColumnKind = "rating" | "progress" | "name" | "namesort" | "hascontent" | "date" | "hoursplayed"
export type GameRowColumnPriority = undefined | "rating" | "progress" | "mixed" export type GameRowColumnPriority = undefined | "rating" | "progress" | "mixed"
export const gameRowColumnKindDefault: GameRowColumnKind[] = ["rating", "name", "hascontent", "date", "progress", "hoursplayed"] export const gameRowColumnKindDefault: GameRowColumnKind[] = ["rating", "name", "namesort", "hascontent", "date", "progress", "hoursplayed"]
export type GameRowProps = { export type GameRowProps = {
@ -62,6 +62,13 @@ export function GameRow({game, columns = gameRowColumnKindDefault, priority}: Ga
</td> </td>
) )
} }
case "namesort": {
return (
<td key={index} className={`review-namesort`} hidden={true}>
<data value={game.name ?? ""}/>
</td>
)
}
case "hascontent": { case "hascontent": {
return ( return (
<td key={index} className={`review-hascontent`}> <td key={index} className={`review-hascontent`}>

View file

@ -3,13 +3,14 @@ import {GameRow, GameRowColumnKind, gameRowColumnKindDefault, GameRowColumnPrior
export type GameTableProps = { export type GameTableProps = {
id?: string,
games: GameData[], games: GameData[],
columns?: GameRowColumnKind[] columns?: GameRowColumnKind[]
priority?: GameRowColumnPriority priority?: GameRowColumnPriority
} }
export function GameTable({games, columns = gameRowColumnKindDefault, priority}: GameTableProps) { export function GameTable({id, games, columns = gameRowColumnKindDefault, priority}: GameTableProps) {
const colElements = columns.map((column, index) => { const colElements = columns.map((column, index) => {
switch(column) { switch(column) {
case "rating": return ( case "rating": return (
@ -56,7 +57,15 @@ export function GameTable({games, columns = gameRowColumnKindDefault, priority}:
</abbr> </abbr>
</th> </th>
) )
case "hascontent": return ( case "namesort": return (
<th key={index} scope={"col"} className={`review-namesort`} hidden={true}>
<abbr title={"The title to sort the game as."}>
Sort by
</abbr>
</th>
)
case "hascontent":
return (
<th key={index} scope={"col"} className={`review-hascontent`}> <th key={index} scope={"col"} className={`review-hascontent`}>
<abbr title={"Whether the review has textual content, or just metadata."}> <abbr title={"Whether the review has textual content, or just metadata."}>
<i className={`fa-sharp fa-regular fa-bars-sort`}/> <i className={`fa-sharp fa-regular fa-bars-sort`}/>
@ -86,7 +95,7 @@ export function GameTable({games, columns = gameRowColumnKindDefault, priority}:
)) ))
return ( return (
<table> <table id={id}>
<colgroup> <colgroup>
{colElements} {colElements}
</colgroup> </colgroup>

View file

@ -91,7 +91,9 @@ export default function(data: GlobalData, helpers: Lume.Helpers) {
</section> </section>
) )
const games_cols = (games.length > 0) ? ( const games_cols = (
games.length > 0
) ? (
<section className={"flex flex-v"}> <section className={"flex flex-v"}>
<h2> <h2>
Videogames Videogames
@ -100,11 +102,6 @@ export default function(data: GlobalData, helpers: Lume.Helpers) {
<i className={"fa-sharp fa-solid fa-magnifying-glass"}/> View all <i className={"fa-sharp fa-solid fa-magnifying-glass"}/> View all
</a> </a>
</small> </small>
<small>
<a href={helpers.url("/games/feed.rss")}>
<i className={"fa-sharp fa-solid fa-rss"}/> Feed
</a>
</small>
</h2> </h2>
{active_games.length > 0 && ( {active_games.length > 0 && (
<div className={"flex flex-1"}> <div className={"flex flex-1"}>

View file

@ -13,15 +13,24 @@ export default function(data: Lume.Data, helpers: Lume.Helpers) {
) : null ) : null
const games: GameData[] = data.search.pages("game") const games: GameData[] = data.search.pages("game")
.sort((a, b) => (b.rating - a.rating)) as GameData[]
const games_section = ( const games_section = (
<section id={"list-games-section-games"}> <section id={"list-games-section-games"}>
<h2> <h2>
Videogames list Videogames list
<small>
<a href={helpers.url("/games/feed.rss")}>
<i className={"fa-sharp fa-solid fa-rss"}/> Feed
</a>
</small>
<small>
<a href={helpers.url("/games/index.json")}>
<i className={"fa-sharp fa-solid fa-brackets-curly"}/> JSON
</a>
</small>
</h2> </h2>
<div> <div>
<GameTable games={games} priority={"mixed"}/> <GameTable id={"list-games-table"} games={games} priority={"mixed"}/>
</div> </div>
</section> </section>
) )
@ -30,6 +39,8 @@ export default function(data: Lume.Data, helpers: Lume.Helpers) {
<main id={"list-games-main"}> <main id={"list-games-main"}>
{intro_section} {intro_section}
{games_section} {games_section}
<script src={"/_static/scripting/sort.js"}/>
<script src={"/_static/scripting/installSortOnLoad.js"}/>
</main> </main>
) )
} }

View file

@ -0,0 +1,5 @@
window.onload = function() {
const tableId = document.querySelector("table").id
installSort(tableId)
sortTable(tableId, readRating, (a, b) => b - a)
}

237
_static/scripting/sort.js Normal file
View file

@ -0,0 +1,237 @@
/**
* @param tableId {string}
* @param readFn {(a: HTMLTableRowElement) => string}
* @param compareFn {(a: string, b: string) => number}
*/
function sortTable(tableId, readFn, compareFn) {
console.debug("Sorting table `", tableId , "` with ", readFn, " and ", compareFn)
/**
* @type {HTMLTableElement | undefined}
*/
const table = document.getElementById(tableId)
if(!table) {
console.error("Table `", tableId, "` not found")
return
}
/**
* @type {HTMLTableSectionElement | undefined}
*/
const tbody = table.tBodies[0]
if(!tbody) {
console.error("Table body of`", tableId, "` not found")
return
}
/**
* @type {HTMLTableRowElement[]}
*/
const allRows = [...tbody.rows]
const undefinedRows = []
for(const row of allRows) {
if(readFn(row) === undefined) {
undefinedRows.push(row)
}
}
const definedRows = allRows.filter(row => readFn(row) !== undefined)
const sortedRows = definedRows.toSorted((a, b) => compareFn(readFn(a), readFn(b)))
let reverse = true
for(let idx = 0; idx < sortedRows.length; idx++) {
if(definedRows[idx] !== sortedRows[idx]) {
reverse = false
break
}
}
if(reverse) {
sortedRows.reverse()
}
const resultRows = sortedRows.concat(undefinedRows)
while(tbody.rows.length > 0) {
tbody.deleteRow(0)
}
for(const row of resultRows) {
tbody.appendChild(row)
}
}
/**
* @param a {HTMLTableRowElement}
*/
function readNameSort(a) {
for(const cell of a.cells) {
if(cell.classList.contains("review-namesort")) {
const value = cell.firstElementChild.value
if(value === "") return undefined
return value
}
}
}
/**
* @param a {HTMLTableRowElement}
*/
function readRating(a) {
for(const cell of a.cells) {
if(cell.classList.contains("review-rating")) {
const value = cell.firstElementChild.value
if(value === "") return undefined
return Number.parseInt(value)
}
}
}
/**
* @param a {HTMLTableRowElement}
*/
function readHoursPlayed(a) {
for(const cell of a.cells) {
if(cell.classList.contains("game-hoursplayed")) {
const value = cell.firstElementChild.value
if(value === "0") return undefined
return Number.parseInt(value)
}
}
}
/**
* @param a {HTMLTableRowElement}
*/
function readDate(a) {
for(const cell of a.cells) {
if(cell.classList.contains("review-date")) {
const value = cell.firstElementChild.dateTime
if(value === "") return undefined
return new Date(value)
}
}
}
/**
* @param a {HTMLTableRowElement}
*/
function readHasContent(a) {
for(const cell of a.cells) {
if(cell.classList.contains("review-hascontent")) {
const value = cell.firstElementChild.value
return value === "true"
}
}
}
/**
* @param a {HTMLTableRowElement}
*/
function readProgress(a) {
for(const cell of a.cells) {
if(cell.classList.contains("game-progress")) {
/**
* @type {HTMLDataElement}
*/
const data = cell.firstElementChild
switch (data.value) {
case undefined:
return undefined;
case "unset":
return undefined;
case "notapplicable":
return 5;
case "new":
return 10;
case "started":
return 20;
case "beaten":
return 30;
case "completed":
return 40;
case "mastered":
return 50;
}
}
}
}
/**
* @param tableId {string}
*/
function installSort(tableId) {
console.debug("Installing sorting capabilities on `", tableId, "`...")
/**
* @type {HTMLTableElement | undefined}
*/
const table = document.getElementById(tableId)
if(!table) {
console.error("Table `", tableId, "` not found")
return
}
/**
* @type {HTMLTableSectionElement | undefined}
*/
const thead = table.tHead
if(!thead) {
console.error("Table header of `", tableId, "` not found")
return
}
/**
* @type {HTMLTableRowElement | undefined}
*/
const thRow = thead.rows[0]
if(!thRow) {
console.error("Table header of `", tableId, "` not found")
return
}
for(const cell of thRow.cells) {
if(cell.classList.contains("review-name")) {
cell.onclick = function() {
sortTable(tableId, readNameSort, (a, b) => a.localeCompare(b))
}
cell.classList.add("sortable")
}
else if(cell.classList.contains("review-rating")) {
cell.onclick = function() {
sortTable(tableId, readRating, (a, b) => b - a)
}
cell.classList.add("sortable")
}
else if(cell.classList.contains("review-date")) {
cell.onclick = function() {
sortTable(tableId, readDate, (a, b) => b - a)
}
cell.classList.add("sortable")
}
else if(cell.classList.contains("game-hoursplayed")) {
cell.onclick = function() {
sortTable(tableId, readHoursPlayed, (a, b) => b - a)
}
cell.classList.add("sortable")
}
else if(cell.classList.contains("review-hascontent")) {
cell.onclick = function() {
sortTable(tableId, readHasContent, (a, b) => b - a)
}
cell.classList.add("sortable")
}
else if(cell.classList.contains("game-progress")) {
cell.onclick = function() {
sortTable(tableId, readProgress, (a, b) => b - a)
}
cell.classList.add("sortable")
}
}
}

View file

@ -74,6 +74,7 @@ export function progressToTitle(progress?: Progress): string {
} }
} }
// Duplicated in _static/scripting/sort.js
export function progress_to_number(progress?: Progress): number { export function progress_to_number(progress?: Progress): number {
switch (progress) { switch (progress) {
case undefined: case undefined: