mirror of
https://github.com/starshardstudio/peafowl.git
synced 2024-12-14 15:24:19 +00:00
Add sorting to the games list
This commit is contained in:
parent
efe89dda2b
commit
0cc044ddd7
10 changed files with 296 additions and 18 deletions
3
.idea/dictionaries/steffo.xml
Normal file
3
.idea/dictionaries/steffo.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="steffo" />
|
||||
</component>
|
|
@ -3,10 +3,18 @@
|
|||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/_static" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/games" type="java-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/_site" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TemplatesService">
|
||||
<option name="TEMPLATE_FOLDERS">
|
||||
<list>
|
||||
<option value="$MODULE_DIR$/_includes" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</module>
|
|
@ -1,6 +1,6 @@
|
|||
<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">
|
||||
<option name="applicationArguments" value="--watch" />
|
||||
<option name="applicationArguments" value="--watch --host=0.0.0.0" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
|
@ -4,10 +4,10 @@ import {Progress, progressToClassName, progressToIconDef, progressToTitle} from
|
|||
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 const gameRowColumnKindDefault: GameRowColumnKind[] = ["rating", "name", "hascontent", "date", "progress", "hoursplayed"]
|
||||
export const gameRowColumnKindDefault: GameRowColumnKind[] = ["rating", "name", "namesort", "hascontent", "date", "progress", "hoursplayed"]
|
||||
|
||||
|
||||
export type GameRowProps = {
|
||||
|
@ -62,6 +62,13 @@ export function GameRow({game, columns = gameRowColumnKindDefault, priority}: Ga
|
|||
</td>
|
||||
)
|
||||
}
|
||||
case "namesort": {
|
||||
return (
|
||||
<td key={index} className={`review-namesort`} hidden={true}>
|
||||
<data value={game.name ?? ""}/>
|
||||
</td>
|
||||
)
|
||||
}
|
||||
case "hascontent": {
|
||||
return (
|
||||
<td key={index} className={`review-hascontent`}>
|
||||
|
|
|
@ -3,13 +3,14 @@ import {GameRow, GameRowColumnKind, gameRowColumnKindDefault, GameRowColumnPrior
|
|||
|
||||
|
||||
export type GameTableProps = {
|
||||
id?: string,
|
||||
games: GameData[],
|
||||
columns?: GameRowColumnKind[]
|
||||
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) => {
|
||||
switch(column) {
|
||||
case "rating": return (
|
||||
|
@ -56,9 +57,17 @@ export function GameTable({games, columns = gameRowColumnKindDefault, priority}:
|
|||
</abbr>
|
||||
</th>
|
||||
)
|
||||
case "hascontent": return (
|
||||
<th key={index} scope={"col"} className={`review-hascontent`}>
|
||||
<abbr title={"Whether the review has textual content, or just metadata."}>
|
||||
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`}>
|
||||
<abbr title={"Whether the review has textual content, or just metadata."}>
|
||||
<i className={`fa-sharp fa-regular fa-bars-sort`}/>
|
||||
</abbr>
|
||||
</th>
|
||||
|
@ -86,7 +95,7 @@ export function GameTable({games, columns = gameRowColumnKindDefault, priority}:
|
|||
))
|
||||
|
||||
return (
|
||||
<table>
|
||||
<table id={id}>
|
||||
<colgroup>
|
||||
{colElements}
|
||||
</colgroup>
|
||||
|
|
|
@ -91,8 +91,10 @@ export default function(data: GlobalData, helpers: Lume.Helpers) {
|
|||
</section>
|
||||
)
|
||||
|
||||
const games_cols = (games.length > 0) ? (
|
||||
<section className={"flex flex-v"}>
|
||||
const games_cols = (
|
||||
games.length > 0
|
||||
) ? (
|
||||
<section className={"flex flex-v"}>
|
||||
<h2>
|
||||
Videogames
|
||||
<small>
|
||||
|
@ -100,11 +102,6 @@ export default function(data: GlobalData, helpers: Lume.Helpers) {
|
|||
<i className={"fa-sharp fa-solid fa-magnifying-glass"}/> View all
|
||||
</a>
|
||||
</small>
|
||||
<small>
|
||||
<a href={helpers.url("/games/feed.rss")}>
|
||||
<i className={"fa-sharp fa-solid fa-rss"}/> Feed
|
||||
</a>
|
||||
</small>
|
||||
</h2>
|
||||
{active_games.length > 0 && (
|
||||
<div className={"flex flex-1"}>
|
||||
|
@ -112,7 +109,7 @@ export default function(data: GlobalData, helpers: Lume.Helpers) {
|
|||
</div>
|
||||
)}
|
||||
<div className={"flex flex-2"}>
|
||||
{top_games_section}
|
||||
{top_games_section}
|
||||
{progress_games_section}
|
||||
</div>
|
||||
<div className={"flex flex-2"}>
|
||||
|
|
|
@ -13,15 +13,24 @@ export default function(data: Lume.Data, helpers: Lume.Helpers) {
|
|||
) : null
|
||||
|
||||
const games: GameData[] = data.search.pages("game")
|
||||
.sort((a, b) => (b.rating - a.rating)) as GameData[]
|
||||
|
||||
const games_section = (
|
||||
<section id={"list-games-section-games"}>
|
||||
<h2>
|
||||
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>
|
||||
<div>
|
||||
<GameTable games={games} priority={"mixed"}/>
|
||||
<GameTable id={"list-games-table"} games={games} priority={"mixed"}/>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
@ -30,6 +39,8 @@ export default function(data: Lume.Data, helpers: Lume.Helpers) {
|
|||
<main id={"list-games-main"}>
|
||||
{intro_section}
|
||||
{games_section}
|
||||
<script src={"/_static/scripting/sort.js"}/>
|
||||
<script src={"/_static/scripting/installSortOnLoad.js"}/>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
|
5
_static/scripting/installSortOnLoad.js
Normal file
5
_static/scripting/installSortOnLoad.js
Normal 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
237
_static/scripting/sort.js
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -74,6 +74,7 @@ export function progressToTitle(progress?: Progress): string {
|
|||
}
|
||||
}
|
||||
|
||||
// Duplicated in _static/scripting/sort.js
|
||||
export function progress_to_number(progress?: Progress): number {
|
||||
switch (progress) {
|
||||
case undefined:
|
||||
|
|
Loading…
Reference in a new issue