Add typing of codes used by components
Showing 12 of 30 files from the diff.
bemuse/src/online/index.ts
changed.
bemuse/src/app/query-flags.ts
changed.
bemuse/src/game/player.ts
changed.
Other files ignored by Codecov
bemuse/src/app/io/MusicSelectionIO.js
was deleted.
bemuse/package.json
has changed.
bemuse/src/app/interactors/getNonMissedDeltas.ts
has changed.
bemuse/src/game/loaders/game-loader.ts
has changed.
bemuse/src/music-collection/index.js
was deleted.
bemuse/src/app/redux/createReducer.js
was deleted.
bemuse/src/flux/index.js
was deleted.
bemuse/src/game/game.ts
has changed.
bemuse/src/app/game-launcher.tsx
has changed.
bemuse/src/app/interactors/findMatchingSong.js
was deleted.
bemuse/src/app/index.tsx
has changed.
bemuse/src/app/options.js
was deleted.
bemuse/src/app/io/MusicSearchIO.ts
has changed.
common/config/rush/pnpm-lock.yaml
has changed.
bemuse/src/game/index.tsx
has changed.
@@ -12,6 +12,7 @@
Loading
12 | 12 | completed, |
|
13 | 13 | operation川FromPromise, |
|
14 | 14 | } from './operations' |
|
15 | + | import Immutable, { Seq } from 'immutable' |
|
15 | 16 | import { |
|
16 | 17 | Observable, |
|
17 | 18 | ObservableInput, |
@@ -33,13 +34,12 @@
Loading
33 | 34 | } from 'rxjs' |
|
34 | 35 | import { RecordLevel, fromObject } from './level' |
|
35 | 36 | ||
36 | - | import Immutable from 'immutable' |
|
37 | + | import { BatchedFetcher } from './BatchedFetcher' |
|
37 | 38 | import { ScoreCount } from 'bemuse/rules/accuracy' |
|
38 | 39 | import _ from 'lodash' |
|
39 | 40 | import id from './id' |
|
40 | 41 | import { queryClient } from 'bemuse/react-query' |
|
41 | 42 | import { rootQueryKey } from './queryKeys' |
|
42 | - | import { BatchedFetcher } from './BatchedFetcher' |
|
43 | 43 | ||
44 | 44 | export interface SignUpInfo { |
|
45 | 45 | username: string |
@@ -221,8 +221,8 @@
Loading
221 | 221 | ) |
|
222 | 222 | ) |
|
223 | 223 | .pipe(map((map) => map.valueSeq())) |
|
224 | - | .pipe(distinctUntilChanged(Immutable.is)) |
|
225 | - | .pipe(map((seq) => seq.toArray())) |
|
224 | + | .pipe(distinctUntilChanged<Seq.Indexed<RecordLevel>>(Immutable.is)) |
|
225 | + | .pipe(map((seq: Seq.Indexed<RecordLevel>) => seq.toArray())) |
|
226 | 226 | } |
|
227 | 227 | ||
228 | 228 | private fetchRecords = async ( |
@@ -0,0 +1,35 @@
Loading
1 | + | import { MusicServerIndex, SongMetadataInCollection } from 'bemuse-types' |
|
2 | + | import produce, { Draft } from 'immer' |
|
3 | + | ||
4 | + | export interface Preprocessed extends MusicServerIndex { |
|
5 | + | songOfTheDayEnabled?: boolean |
|
6 | + | } |
|
7 | + | ||
8 | + | export const preprocessCollection = produce( |
|
9 | + | (draft: Draft<Preprocessed>, songs?: SongMetadataInCollection[]) => { |
|
10 | + | if (songs) { |
|
11 | + | draft.songs = songs.map((song) => preprocessSong(song)) |
|
12 | + | } |
|
13 | + | } |
|
14 | + | ) |
|
15 | + | ||
16 | + | function preprocessSong( |
|
17 | + | song: SongMetadataInCollection |
|
18 | + | ): SongMetadataInCollection { |
|
19 | + | if (!song.chart_names) { |
|
20 | + | return song |
|
21 | + | } |
|
22 | + | return produce(song, (draft) => { |
|
23 | + | if (draft.charts) { |
|
24 | + | draft.charts = draft.charts.map((chart) => { |
|
25 | + | const name = song.chart_names![chart.file] |
|
26 | + | if (!name) return chart |
|
27 | + | return produce(chart, (draft) => { |
|
28 | + | draft.info.subtitles = [...chart.info.subtitles, name] |
|
29 | + | }) |
|
30 | + | }) |
|
31 | + | } |
|
32 | + | }) |
|
33 | + | } |
|
34 | + | ||
35 | + | export default preprocessCollection |
|
0 | 36 | imilarity index 80% |
|
1 | 37 | ename from bemuse/src/music-collection/sortSongs.js |
|
2 | 38 | ename to bemuse/src/music-collection/sortSongs.ts |
@@ -5,7 +5,7 @@
Loading
5 | 5 | * |
|
6 | 6 | * @see https://bemuse.ninja/project/docs/music-server.html |
|
7 | 7 | */ |
|
8 | - | export function getMusicServer() { |
|
8 | + | export function getMusicServer(): string | undefined { |
|
9 | 9 | return query.BEMUSE_MUSIC_SERVER || query.server |
|
10 | 10 | } |
|
11 | 11 |
@@ -14,7 +14,7 @@
Loading
14 | 14 | * |
|
15 | 15 | * @see https://bemuse.ninja/project/docs/music-server.html |
|
16 | 16 | */ |
|
17 | - | export function getSoundVolume() { |
|
17 | + | export function getSoundVolume(): number { |
|
18 | 18 | return +query.volume || 1 |
|
19 | 19 | } |
|
20 | 20 |
@@ -24,24 +24,24 @@
Loading
24 | 24 | * @see https://github.com/bemusic/bemuse/pull/568 |
|
25 | 25 | * @see https://twitter.com/Nekokan_Server/status/1173186650865713153 |
|
26 | 26 | */ |
|
27 | - | export function getPreloadArchiveFlag() { |
|
27 | + | export function getPreloadArchiveFlag(): string | undefined { |
|
28 | 28 | return query.archive |
|
29 | 29 | } |
|
30 | 30 | ||
31 | 31 | /** |
|
32 | 32 | * The `?grep` flag specifies the initials search text to be pre-filled when the player enters the music selection screen. |
|
33 | 33 | */ |
|
34 | - | export function getInitialGrepString() { |
|
34 | + | export function getInitialGrepString(): string | undefined { |
|
35 | 35 | return query.grep |
|
36 | 36 | } |
|
37 | 37 | ||
38 | 38 | /** |
|
39 | 39 | * The `?song` flag specifies the title of the song to be pre-selected when the player enters the music selection screen. |
|
40 | 40 | */ |
|
41 | - | export function getInitiallySelectedSong() { |
|
41 | + | export function getInitiallySelectedSong(): string | undefined { |
|
42 | 42 | return query.song |
|
43 | 43 | } |
|
44 | 44 | ||
45 | - | export function getTimeSynchroServer() { |
|
45 | + | export function getTimeSynchroServer(): string | undefined { |
|
46 | 46 | return query.BEMUSE_TIMESYNCHRO_SERVER |
|
47 | 47 | } |
@@ -1,15 +1,23 @@
Loading
1 | - | import _ from 'lodash' |
|
1 | + | import { Song } from 'bemuse/collection-model/types' |
|
2 | 2 | import { SongOfTheDay } from './SongOfTheDay' |
|
3 | + | import _ from 'lodash' |
|
4 | + | ||
5 | + | interface Grouping { |
|
6 | + | title: string |
|
7 | + | criteria: (song: Song, context: { songOfTheDay: SongOfTheDay }) => boolean |
|
8 | + | sort?: (song: Song) => string |
|
9 | + | reverse?: boolean |
|
10 | + | } |
|
3 | 11 | ||
4 | - | const grouping = [ |
|
5 | - | { title: 'Custom Song', criteria: (song) => song.custom }, |
|
6 | - | { title: 'Tutorial', criteria: (song) => song.tutorial }, |
|
7 | - | { title: 'Unreleased', criteria: (song) => song.unreleased }, |
|
12 | + | const grouping: readonly Grouping[] = [ |
|
13 | + | { title: 'Custom Song', criteria: (song) => !!song.custom }, |
|
14 | + | { title: 'Tutorial', criteria: (song) => !!song.tutorial }, |
|
15 | + | { title: 'Unreleased', criteria: (song) => !!song.unreleased }, |
|
8 | 16 | { |
|
9 | 17 | title: 'Recently Added Songs', |
|
10 | 18 | criteria: (song) => |
|
11 | - | song.added && Date.now() - Date.parse(song.added) < 60 * 86400000, |
|
12 | - | sort: (song) => song.added, |
|
19 | + | !!song.added && Date.now() - Date.parse(song.added) < 60 * 86400000, |
|
20 | + | sort: (song) => song.added ?? '', |
|
13 | 21 | reverse: true, |
|
14 | 22 | }, |
|
15 | 23 | { |
@@ -20,7 +28,7 @@
Loading
20 | 28 | ] |
|
21 | 29 | ||
22 | 30 | export function groupSongsIntoCategories( |
|
23 | - | songs, |
|
31 | + | songs: readonly Song[], |
|
24 | 32 | { songOfTheDayEnabled = false } = {} |
|
25 | 33 | ) { |
|
26 | 34 | const context = { |
@@ -28,7 +36,7 @@
Loading
28 | 36 | } |
|
29 | 37 | const groups = grouping.map((group) => ({ |
|
30 | 38 | input: group, |
|
31 | - | output: { title: group.title, songs: [] }, |
|
39 | + | output: { title: group.title, songs: [] as Song[] }, |
|
32 | 40 | })) |
|
33 | 41 | for (const song of songs) { |
|
34 | 42 | for (const { input, output } of groups) { |
@@ -1,6 +1,6 @@
Loading
1 | + | import { Song } from 'bemuse/collection-model/types' |
|
1 | 2 | import _ from 'lodash' |
|
2 | 3 | import { createHash } from 'crypto' |
|
3 | - | import { Song } from 'bemuse/collection-model/types' |
|
4 | 4 | ||
5 | 5 | const getHashFunction = _.once(() => { |
|
6 | 6 | const today = new Date(Date.now() + 9 * 3600e3).toISOString().split('T')[0] |
@@ -14,7 +14,7 @@
Loading
14 | 14 | ||
15 | 15 | export class SongOfTheDay { |
|
16 | 16 | private ids: Set<string> |
|
17 | - | constructor(songs: Song[], { enabled = true } = {}) { |
|
17 | + | constructor(songs: readonly Song[], { enabled = true } = {}) { |
|
18 | 18 | if (!enabled) { |
|
19 | 19 | this.ids = new Set() |
|
20 | 20 | return |
|
21 | 21 | imilarity index 74% |
|
22 | 22 | ename from bemuse/src/music-collection/getPlayableCharts.js |
|
23 | 23 | ename to bemuse/src/music-collection/getPlayableCharts.ts |
@@ -1,8 +1,8 @@
Loading
1 | + | import { Chart } from 'bemuse-types' |
|
1 | 2 | import _ from 'lodash' |
|
2 | - | ||
3 | 3 | import isChartPlayable from './isChartPlayable' |
|
4 | 4 | ||
5 | - | export function getPlayableCharts(charts) { |
|
5 | + | export function getPlayableCharts(charts: readonly Chart[]): Chart[] { |
|
6 | 6 | return _(charts) |
|
7 | 7 | .filter(isChartPlayable) |
|
8 | 8 | .orderBy([ |
|
9 | 9 | imilarity index 64% |
|
10 | 10 | ename from bemuse/src/music-collection/groupSongsIntoCategories.js |
|
11 | 11 | ename to bemuse/src/music-collection/groupSongsIntoCategories.ts |
@@ -1,8 +1,9 @@
Loading
1 | - | import Progress from 'bemuse/progress' |
|
2 | 1 | import * as ProgressUtils from 'bemuse/progress/utils' |
|
3 | - | import readBlob from 'bemuse/utils/read-blob' |
|
4 | 2 | import _ from 'lodash' |
|
3 | + | import Progress from 'bemuse/progress' |
|
4 | + | import readBlob from 'bemuse/utils/read-blob' |
|
5 | 5 | import throat from 'throat' |
|
6 | + | ||
6 | 7 | import { IResource, IResources } from './types' |
|
7 | 8 | import { URLResources } from './url' |
|
8 | 9 |
@@ -29,7 +30,7 @@
Loading
29 | 30 | } |
|
30 | 31 | ||
31 | 32 | constructor( |
|
32 | - | base: string | IResources, |
|
33 | + | base: string | URL | IResources, |
|
33 | 34 | options: { |
|
34 | 35 | metadataFilename?: string |
|
35 | 36 | fallback?: string | IResources |
@@ -37,7 +38,10 @@
Loading
37 | 38 | } = {} |
|
38 | 39 | ) { |
|
39 | 40 | if (typeof base === 'string') { |
|
40 | - | base = new URLResources(new URL(base, location.href)) |
|
41 | + | base = new URL(base, location.href) |
|
42 | + | } |
|
43 | + | if (base instanceof URL) { |
|
44 | + | base = new URLResources(base) |
|
41 | 45 | } |
|
42 | 46 | ||
43 | 47 | const fallback = |
@@ -0,0 +1,21 @@
Loading
1 | + | // Finds a song matching the title |
|
2 | + | ||
3 | + | import type { SongMetadataInCollection } from 'bemuse-types' |
|
4 | + | ||
5 | + | function findMatchingSong({ |
|
6 | + | songs, |
|
7 | + | title, |
|
8 | + | getTitle, |
|
9 | + | }: { |
|
10 | + | songs: readonly SongMetadataInCollection[] |
|
11 | + | title: string |
|
12 | + | getTitle: (song: SongMetadataInCollection) => string |
|
13 | + | }) { |
|
14 | + | return songs.find((song) => titleFullyMatches(getTitle(song), title)) |
|
15 | + | } |
|
16 | + | ||
17 | + | function titleFullyMatches(haystack: string, needle: string) { |
|
18 | + | return haystack.toLowerCase().trim() === needle.toLowerCase().trim() |
|
19 | + | } |
|
20 | + | ||
21 | + | export default findMatchingSong |
|
0 | 22 | imilarity index 82% |
|
1 | 23 | ename from bemuse/src/app/interactors/getNonMissedDeltas.js |
|
2 | 24 | ename to bemuse/src/app/interactors/getNonMissedDeltas.ts |
@@ -1,7 +1,8 @@
Loading
1 | + | import { Song } from 'bemuse/collection-model/types' |
|
1 | 2 | import _ from 'lodash' |
|
2 | 3 | import isChartPlayable from './isChartPlayable' |
|
3 | 4 | ||
4 | - | export function sortSongs(songs) { |
|
5 | + | export function sortSongs(songs: readonly Song[]) { |
|
5 | 6 | return _.orderBy(songs, [ |
|
6 | 7 | (song) => { |
|
7 | 8 | return _(song.charts) |
@@ -19,9 +19,9 @@
Loading
19 | 19 | export type PlayerOptionsGauge = 'off' | 'hope' |
|
20 | 20 | ||
21 | 21 | type PlayerOptionsInputMapping = { |
|
22 | - | keyboard: { [control in PlayerControlKeys]: string } |
|
23 | - | continuous: boolean |
|
24 | - | sensitivity: number |
|
22 | + | keyboard: { [control in PlayerControlKeys]: number } |
|
23 | + | continuous?: boolean |
|
24 | + | sensitivity?: number |
|
25 | 25 | } |
|
26 | 26 | ||
27 | 27 | type PlayerOptionsInternal = { |
@@ -42,7 +42,7 @@
Loading
42 | 42 | speed: number |
|
43 | 43 | placement?: PlayerOptionsPlacement |
|
44 | 44 | laneCover?: number |
|
45 | - | gauge: PlayerOptionsGauge |
|
45 | + | gauge?: PlayerOptionsGauge |
|
46 | 46 | input: PlayerOptionsInputMapping |
|
47 | 47 | tutorial?: boolean |
|
48 | 48 | } |
@@ -63,7 +63,7 @@
Loading
63 | 63 | scratch: options.scratch || 'left', |
|
64 | 64 | input: options.input, |
|
65 | 65 | laneCover: +(options.laneCover || 0), |
|
66 | - | gauge: options.gauge, |
|
66 | + | gauge: options.gauge || 'off', |
|
67 | 67 | tutorial: !!options.tutorial, |
|
68 | 68 | } |
|
69 | 69 | } |
@@ -1,12 +1,16 @@
Loading
1 | 1 | import * as Judgments from '../judgments' |
|
2 | 2 | ||
3 | - | import _ from 'lodash' |
|
4 | 3 | import Notechart from 'bemuse-notechart' |
|
4 | + | import _ from 'lodash' |
|
5 | 5 | ||
6 | 6 | const getAccuracyScore = (accuracy: number) => Math.floor(accuracy * 500000) |
|
7 | 7 | const getComboScore = (sum: number, total: number) => |
|
8 | 8 | Math.floor((sum * 55555) / total) |
|
9 | 9 | ||
10 | + | export type Counts = { |
|
11 | + | [k in Judgments.JudgedJudgment]: number |
|
12 | + | } |
|
13 | + | ||
10 | 14 | export class PlayerStats { |
|
11 | 15 | public totalCombo: number |
|
12 | 16 | public totalNotes: number |
@@ -15,7 +19,7 @@
Loading
15 | 19 | public rawSumJudgmentWeight: number |
|
16 | 20 | public rawTotalComboScore: number |
|
17 | 21 | public rawSumComboScore: number |
|
18 | - | public counts: { [k in Judgments.JudgedJudgment]: number } |
|
22 | + | public counts: Counts |
|
19 | 23 | public numJudgments: number |
|
20 | 24 | public poor: boolean |
|
21 | 25 | public deltas: number[] |
@@ -0,0 +1,32 @@
Loading
1 | + | import type { MusicServerIndex } from 'bemuse-types' |
|
2 | + | ||
3 | + | export const OFFICIAL_SERVER_URL = 'https://music4.bemuse.ninja/server' |
|
4 | + | ||
5 | + | export async function load( |
|
6 | + | serverUrl: string, |
|
7 | + | { fetch = global.fetch } = {} |
|
8 | + | ): Promise<MusicServerIndex> { |
|
9 | + | const indexUrl = getServerIndexFileUrl(serverUrl) |
|
10 | + | const data = await fetch(indexUrl).then((response) => response.json()) |
|
11 | + | ||
12 | + | if (Array.isArray(data.songs)) { |
|
13 | + | return data |
|
14 | + | } |
|
15 | + | if (Array.isArray(data.charts)) { |
|
16 | + | // Single-song server |
|
17 | + | const lastSlash = indexUrl.lastIndexOf('/') |
|
18 | + | const dir = |
|
19 | + | lastSlash === -1 ? indexUrl : indexUrl.substring(0, lastSlash + 1) |
|
20 | + | return { songs: [{ ...data, id: 'song', path: dir }] } |
|
21 | + | } |
|
22 | + | throw new Error( |
|
23 | + | `Invalid server file at ${indexUrl}: Does not contain "songs" array.` |
|
24 | + | ) |
|
25 | + | } |
|
26 | + | ||
27 | + | export function getServerIndexFileUrl(serverUrl: string) { |
|
28 | + | if (serverUrl.endsWith('/bemuse-song.json')) { |
|
29 | + | return serverUrl |
|
30 | + | } |
|
31 | + | return serverUrl.replace(/\/(?:index\.json)?$/, '') + '/index.json' |
|
32 | + | } |
Files | Coverage |
---|---|
bemuse/src | 85.08% |
packages/bms | 95.54% |
Project Totals (178 files) | 86.40% |
3861099767
3861099767
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file.
The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files.
The size and color of each slice is representing the number of statements and the coverage, respectively.