Skip to content

Commit e2dadc7

Browse files
committed
Allow filtering by folder
1 parent 769ac4f commit e2dadc7

13 files changed

+187
-34
lines changed

Diff for: scripts/validate.mjs

+18-13
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import songsSchema from "../songs.schema.json" assert { type: "json" };
1010
const schemaLocation = "src/models/SongData.ts";
1111

1212
function validateContents(dataFile) {
13-
const errors = [];
13+
const errors = new Set();
1414

1515
const allKeys = [
1616
...dataFile.meta.styles,
@@ -20,58 +20,63 @@ function validateContents(dataFile) {
2020
const styles = new Set(dataFile.meta.styles);
2121
const difficulties = new Set(dataFile.meta.difficulties.map((d) => d.key));
2222
const flags = new Set(dataFile.meta.flags);
23+
const folders = new Set(dataFile.meta.folders);
2324

2425
if (dataFile.defaults.style && !styles.has(dataFile.defaults.style)) {
25-
errors.push("default style is not listed in meta");
26+
errors.add("default style is not listed in meta");
2627
}
2728

2829
if (dataFile.defaults.difficulties.some((d) => !difficulties.has(d))) {
29-
errors.push("some default difficulties are missing from meta");
30+
errors.add("some default difficulties are missing from meta");
3031
}
3132

32-
// removed to allow for hidden flags like "plus" charts in SMX
3333
// if (dataFile.defaults.flags.some((d) => !flags.has(d))) {
34-
// errors.push("some default flags are missing from meta");
34+
// errors.add("some default flags are missing from meta");
3535
// }
3636

3737
if (dataFile.defaults.lowerLvlBound > dataFile.defaults.upperLvlBound) {
38-
errors.push("default level bounds are reversed");
38+
errors.add("default level bounds are reversed");
3939
}
4040

4141
if (dataFile.i18n.ja) {
4242
for (const key of allKeys) {
4343
if (!(dataFile.i18n.en[key] && dataFile.i18n.ja[key])) {
44-
errors.push("missing translation for " + key);
44+
errors.add("missing translation for " + key);
4545
}
4646
if (
4747
difficulties.has(key) &&
4848
!(dataFile.i18n.en["$abbr"][key] && dataFile.i18n.ja["$abbr"][key])
4949
) {
50-
errors.push("missing abbreviated translation for " + key);
50+
errors.add("missing abbreviated translation for " + key);
5151
}
5252
}
5353
}
5454

5555
for (const song of dataFile.songs) {
56+
if (dataFile.meta.folders && song.folder) {
57+
if (!folders.has(song.folder)) {
58+
errors.add(`top level folders list is missing ${song.folder}`);
59+
}
60+
}
5661
if (song.jacket) {
5762
const jacketPath = join(jacketsDir, song.jacket);
5863
if (!existsSync(jacketPath)) {
59-
errors.push(`missing jacket image ${song.jacket}`);
64+
errors.add(`missing jacket image ${song.jacket}`);
6065
}
6166
}
6267

6368
for (const chart of song.charts) {
6469
if (!styles.has(chart.style)) {
65-
errors.push(`unrecognized style "${chart.style}" used by ${song.name}`);
70+
errors.add(`unrecognized style "${chart.style}" used by ${song.name}`);
6671
}
6772
if (!difficulties.has(chart.diffClass)) {
68-
errors.push(
73+
errors.add(
6974
`unrecognized diffClass "${chart.diffClass}" used by ${song.name}`,
7075
);
7176
}
7277
if (dataFile.meta.usesDrawGroups) {
7378
if (!chart.drawGroup) {
74-
errors.push(`${song.name} is missing a draw group`);
79+
errors.add(`${song.name} is missing a draw group`);
7580
}
7681
}
7782
}
@@ -93,7 +98,7 @@ for (const dataFile of dataFileNames) {
9398

9499
if (result.valid) {
95100
const consistencyErrors = validateContents(songData);
96-
if (consistencyErrors.length) {
101+
if (consistencyErrors.size) {
97102
consistencyErrors.forEach((err) => console.error(" * " + err));
98103
console.log(`\n${dataFile} has inconsistent data!`);
99104
hasError = true;

Diff for: songs.schema.json

+5
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
"$ref": "#/definitions/uniqueStringArr",
5252
"description": "List of all special flags one might filter songs by"
5353
},
54+
"folders": {
55+
"$ref": "#/definitions/uniqueStringArr",
56+
"description": "List of all possible folders, in order"
57+
},
5458
"usesDrawGroups": {
5559
"type": "boolean"
5660
},
@@ -75,6 +79,7 @@
7579
"style": { "type": "string" },
7680
"difficulties": { "$ref": "#/definitions/uniqueStringArr" },
7781
"flags": { "$ref": "#/definitions/uniqueStringArr" },
82+
"folders": { "$ref": "#/definitions/uniqueStringArr" },
7883
"lowerLvlBound": { "type": "number" },
7984
"upperLvlBound": { "type": "number" }
8085
}

Diff for: src/apply-default-config.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,20 @@ export function ApplyDefaultConfig({ defaults, granularResolution }: Props) {
1414
}
1515

1616
useConfigState.setState(() => {
17-
const { lowerLvlBound, upperLvlBound, flags, difficulties, style } =
18-
defaults;
17+
const {
18+
lowerLvlBound,
19+
upperLvlBound,
20+
flags,
21+
difficulties,
22+
folders,
23+
style,
24+
} = defaults;
1925
const ret: Partial<ConfigState> = {
2026
lowerBound: lowerLvlBound,
2127
upperBound: upperLvlBound,
2228
flags: new Set(flags),
2329
difficulties: new Set(difficulties),
30+
folders: new Set(folders),
2431
style,
2532
};
2633
if (!granularResolution) {

Diff for: src/assets/i18n.json

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"orderByAction": "Re-order by pick/ban",
3737
"style": "Style",
3838
"difficulties": "Difficulties",
39+
"folders": "Folders",
3940
"include": "Include",
4041
"tabs": {
4142
"general": "General",

Diff for: src/card-draw.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ export function songIsValid(
6161
if (forPocketPick && !config.constrainPocketPicks) {
6262
return true;
6363
}
64-
return !song.flags || song.flags.every((f) => config.flags.has(f));
64+
return (
65+
(!song.folder || !config.folders.size || config.folders.has(song.folder)) &&
66+
(!song.flags || song.flags.every((f) => config.flags.has(f)))
67+
);
6568
}
6669

6770
/** returns true if chart matches configured difficulty/style/lvl/flags */

Diff for: src/config-persistence.ts

+2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ function buildPersistedConfig(): PersistedConfigV1 {
101101
...configState,
102102
difficulties: Array.from(configState.difficulties),
103103
flags: Array.from(configState.flags),
104+
folders: Array.from(configState.folders),
104105
};
105106
const ret: PersistedConfigV1 = {
106107
version: 1,
@@ -133,6 +134,7 @@ async function loadPersistedConfig(saved: PersistedConfigV1) {
133134
...migrateOldNames(saved.configState),
134135
difficulties: new Set(saved.configState.difficulties),
135136
flags: new Set(saved.configState.flags),
137+
folders: new Set(saved.configState.folders),
136138
});
137139
}
138140

Diff for: src/config-state.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface ConfigState {
1414
forceDistribution: boolean;
1515
constrainPocketPicks: boolean;
1616
style: string;
17+
folders: ReadonlySet<string>;
1718
difficulties: ReadonlySet<string>;
1819
flags: ReadonlySet<string>;
1920
showEligibleCharts: boolean;
@@ -40,6 +41,7 @@ export const useConfigState = createWithEqualityFn<ConfigState>(
4041
forceDistribution: true,
4142
constrainPocketPicks: true,
4243
style: "",
44+
folders: new Set(),
4345
difficulties: new Set(),
4446
flags: new Set(),
4547
showEligibleCharts: false,

Diff for: src/controls/controls-drawer.tsx

+68-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
Button,
3+
ButtonGroup,
34
Card,
45
Checkbox,
56
Classes,
@@ -20,6 +21,8 @@ import {
2021
CaretDown,
2122
CaretRight,
2223
Plus,
24+
SmallTick,
25+
SmallCross,
2326
} from "@blueprintjs/icons";
2427
import { useMemo, useState } from "react";
2528
import { shallow } from "zustand/shallow";
@@ -120,15 +123,19 @@ export default function ControlsDrawer() {
120123

121124
function FlagSettings() {
122125
const { t } = useIntl();
123-
const [dataSetName, gameData] = useDrawState(
124-
(s) => [s.dataSetName, s.gameData],
126+
const [dataSetName, gameData, hasFlags] = useDrawState(
127+
(s) => [s.dataSetName, s.gameData, !!s.gameData?.meta.flags.length],
125128
shallow,
126129
);
127130
const [updateState, selectedFlags] = useConfigState(
128131
(s) => [s.update, s.flags],
129132
shallow,
130133
);
131134

135+
if (!hasFlags) {
136+
return false;
137+
}
138+
132139
return (
133140
<FormGroup label={t("controls.include")}>
134141
{gameData?.meta.flags.map((key) => (
@@ -154,10 +161,66 @@ function FlagSettings() {
154161
);
155162
}
156163

164+
function FolderSettings() {
165+
const { t } = useIntl();
166+
const availableFolders = useDrawState((s) => s.gameData?.meta.folders);
167+
const dataSetName = useDrawState((s) => s.dataSetName);
168+
const [updateState, selectedFolders] = useConfigState(
169+
(s) => [s.update, s.folders],
170+
shallow,
171+
);
172+
173+
if (!availableFolders?.length) {
174+
return null;
175+
}
176+
177+
return (
178+
<FormGroup
179+
label={t("controls.folders")}
180+
style={{ opacity: selectedFolders.size ? undefined : 0.8 }}
181+
>
182+
<ButtonGroup className={styles.smallText}>
183+
<Button
184+
small
185+
icon={<SmallTick />}
186+
onClick={() => updateState({ folders: new Set(availableFolders) })}
187+
>
188+
All
189+
</Button>
190+
<Button
191+
small
192+
icon={<SmallCross />}
193+
onClick={() => updateState({ folders: new Set() })}
194+
>
195+
Ignore Folders
196+
</Button>
197+
</ButtonGroup>
198+
{availableFolders.map((folder, idx) => (
199+
<Checkbox
200+
key={`${dataSetName}:${idx}`}
201+
label={folder}
202+
value={folder}
203+
checked={selectedFolders.has(folder)}
204+
onChange={() =>
205+
updateState((s) => {
206+
const newFolders = new Set(s.folders);
207+
if (newFolders.has(folder)) {
208+
newFolders.delete(folder);
209+
} else {
210+
newFolders.add(folder);
211+
}
212+
return { folders: newFolders };
213+
})
214+
}
215+
/>
216+
))}
217+
</FormGroup>
218+
);
219+
}
220+
157221
function GeneralSettings() {
158222
const { t } = useIntl();
159223
const gameData = useDrawState((s) => s.gameData);
160-
const hasFlags = useDrawState((s) => !!s.gameData?.meta.flags.length);
161224
const configState = useConfigState();
162225
const {
163226
useWeights,
@@ -394,7 +457,8 @@ function GeneralSettings() {
394457
/>
395458
))}
396459
</FormGroup>
397-
{hasFlags && <FlagSettings />}
460+
<FlagSettings />
461+
<FolderSettings />
398462
</Card>
399463
</Collapse>
400464
<FormGroup>

Diff for: src/controls/controls.css

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
margin-right: 25px;
2222
}
2323

24+
.smallText button {
25+
font-size: 8pt !important;
26+
}
27+
2428
.plus {
2529
margin-top: 30px;
2630
margin-left: -23px;

Diff for: src/game-data-utils.tsx

+21-8
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,31 @@ export function getAvailableLevels(
3636
return [];
3737
}
3838

39-
let getLevelForChart = (chart: Chart) =>
40-
useGranular ? chart.sanbaiTier || chart.lvl : chart.lvl;
41-
if (gameData.meta.usesDrawGroups) {
42-
getLevelForChart = (chart) => chart.drawGroup || chart.lvl;
43-
}
4439
const levelSet = new Set<number>();
45-
gameData.songs.forEach((song) => {
46-
song.charts.forEach((chart) => levelSet.add(getLevelForChart(chart)));
47-
});
40+
for (const song of gameData.songs) {
41+
for (const chart of song.charts) {
42+
levelSet.add(
43+
chartLevelOrTier(chart, useGranular, gameData.meta.usesDrawGroups),
44+
);
45+
}
46+
}
4847
return [...levelSet].sort((a, b) => a - b);
4948
}
5049

50+
// export function getAvailableFolders(gameData: GameData | null): string[] {
51+
// if (gameData === null) {
52+
// return [];
53+
// }
54+
55+
// const folderSet = new Set<string>();
56+
// for (const song of gameData.songs) {
57+
// if (song.folder) {
58+
// folderSet.add(song.folder);
59+
// }
60+
// }
61+
// return [...folderSet].sort((a, b) => (a < b ? -1 : 1));
62+
// }
63+
5164
export function getDiffAbbr(gameData: GameData, diffClass: string) {
5265
return ((gameData.i18n.en as I18NDict)["$abbr"] as I18NDict)[
5366
diffClass

0 commit comments

Comments
 (0)