diff --git a/ui/tournamentSchedule/src/dragscroll.d.ts b/ui/tournamentSchedule/src/dragscroll.d.ts new file mode 100644 index 0000000000..589b1d5b3c --- /dev/null +++ b/ui/tournamentSchedule/src/dragscroll.d.ts @@ -0,0 +1 @@ +declare module 'dragscroll'; diff --git a/ui/tournamentSchedule/src/interfaces.ts b/ui/tournamentSchedule/src/interfaces.ts new file mode 100644 index 0000000000..e8b77cad58 --- /dev/null +++ b/ui/tournamentSchedule/src/interfaces.ts @@ -0,0 +1,49 @@ +import perfIcons from 'common/perfIcons'; + +export interface Tournament { + id: string; + fullName: string; + schedule: { + freq: string; + speed: string; + }; + perf: { + key: keyof typeof perfIcons; + position: number; + name: string; + }; + hasMaxRating: boolean; + variant: Variant; + startsAt: number; + finishesAt: number; + status: number; + position: number; + rated: boolean; + minutes: number; + createdBy: string; + clock: Clock; + nbPlayers: number; +} + +export interface Data { + created: Tournament[]; + started: Tournament[]; + finished: Tournament[]; +} + +export interface Opts { + data: Data; + i18n: I18nDict; +} + +export interface Clock { + limit: number; + increment: number; +} + +export interface Ctrl { + data(): Data; + trans: Trans; +} + +export type Lane = Tournament[]; diff --git a/ui/tournamentSchedule/src/main.ts b/ui/tournamentSchedule/src/main.ts index 9b9b34ed49..49ada33b63 100644 --- a/ui/tournamentSchedule/src/main.ts +++ b/ui/tournamentSchedule/src/main.ts @@ -2,19 +2,20 @@ import view from './view'; import { init, VNode, classModule, attributesModule } from 'snabbdom'; import dragscroll from 'dragscroll'; +import { Opts, Tournament } from './interfaces'; const patch = init([classModule, attributesModule]); dragscroll; // required to include the dependency :( :( :( -export default function (env: any) { +export default function (opts: Opts) { lichess.StrongSocket.defaultParams.flag = 'tournament'; const element = document.querySelector('.tour-chart') as HTMLElement; const ctrl = { - data: () => env.data, - trans: lichess.trans(env.i18n), + data: () => opts.data, + trans: lichess.trans(opts.i18n), }; let vnode: VNode; @@ -27,16 +28,16 @@ export default function (env: any) { setInterval(redraw, 3700); lichess.pubsub.on('socket.in.reload', d => { - env.data = { - created: update(env.data.created, d.created), - started: update(env.data.started, d.started), - finished: update(env.data.finished, d.finished), + opts.data = { + created: update(opts.data.created, d.created), + started: update(opts.data.started, d.started), + finished: update(opts.data.finished, d.finished), }; redraw(); }); } -function update(prevs, news) { +function update(prevs: Tournament[], news: Tournament[]) { // updates ignore team tournaments (same for all) // also lacks finished tournaments const now = new Date().getTime(); diff --git a/ui/tournamentSchedule/src/view.ts b/ui/tournamentSchedule/src/view.ts index 265e256c5c..1dad73ad78 100644 --- a/ui/tournamentSchedule/src/view.ts +++ b/ui/tournamentSchedule/src/view.ts @@ -1,18 +1,19 @@ -import { h, VNode } from 'snabbdom'; +import { Classes, h, VNode } from 'snabbdom'; import perfIcons from 'common/perfIcons'; +import { Clock, Ctrl, Lane, Tournament } from './interfaces'; const scale = 8; let now: number, startTime: number, stopTime: number; -const i18nNames = {}; +const i18nNames: Record = {}; -function i18nName(t) { +function i18nName(t: Tournament) { if (!i18nNames[t.id]) i18nNames[t.id] = t.fullName; return i18nNames[t.id]; } -function displayClockLimit(limit) { +function displayClockLimit(limit: number) { switch (limit) { case 15: return '¼'; @@ -27,16 +28,16 @@ function displayClockLimit(limit) { } } -function displayClock(clock) { +function displayClock(clock: Clock) { return displayClockLimit(clock.limit) + '+' + clock.increment; } -function leftPos(time) { +function leftPos(time: number) { const rounded = 1000 * 60 * Math.floor(time / 1000 / 60); return (scale * (rounded - startTime)) / 1000 / 60; } -function laneGrouper(t) { +function laneGrouper(t: Tournament): number { if (t.schedule && t.schedule.freq === 'unique') { return -1; } else if (t.variant.key !== 'standard') { @@ -54,34 +55,34 @@ function laneGrouper(t) { } } -function group(arr, grouper) { - const groups = {}; +function group(arr: Tournament[], grouper: (t: Tournament) => number): Lane[] { + const groups: Dictionary = {}; let g; arr.forEach(e => { g = grouper(e); - if (!groups[g]) groups[g] = []; - groups[g].push(e); + if (groups[g]) groups[g]?.push(e); + else groups[g] = [e]; }); return Object.keys(groups) .sort() .map(function (k) { - return groups[k]; + return groups[k]!; }); } -function fitLane(lane, tour2) { - return !lane.some(function (tour1) { +function fitLane(lane: Lane, tour2: Tournament) { + return !lane.some(function (tour1: Tournament) { return !(tour1.finishesAt <= tour2.startsAt || tour2.finishesAt <= tour1.startsAt); }); } // splits lanes that have collisions, but keeps // groups separate by not compacting existing lanes -function splitOverlaping(lanes) { - let ret: any[] = [], +function splitOverlaping(lanes: Lane[]): Lane[] { + let ret: Lane[] = [], i: number; lanes.forEach(lane => { - const newLanes: any[] = [[]]; + const newLanes: Lane[] = [[]]; lane.forEach(tour => { let collision = true; for (i = 0; i < newLanes.length; i++) { @@ -98,7 +99,7 @@ function splitOverlaping(lanes) { return ret; } -function tournamentClass(tour) { +function tournamentClass(tour: Tournament): Classes { const finished = tour.status === 30, userCreated = tour.createdBy !== 'lichess', classes = { @@ -110,16 +111,16 @@ function tournamentClass(tour) { 'tsht-thematic': !!tour.position, 'tsht-short': tour.minutes <= 30, 'tsht-max-rating': !userCreated && tour.hasMaxRating, - }; + } as Classes; if (tour.schedule) classes['tsht-' + tour.schedule.freq] = true; return classes; } -const iconOf = tour => (tour.schedule?.freq === 'shield' ? '' : perfIcons[tour.perf.key]); +const iconOf = (tour: Tournament) => (tour.schedule?.freq === 'shield' ? '' : perfIcons[tour.perf.key]); let mousedownAt: number[] | undefined; -function renderTournament(ctrl, tour) { +function renderTournament(ctrl: Ctrl, tour: Tournament) { let width = tour.minutes * scale; const left = leftPos(tour.startsAt); // moves content into viewport, for long tourneys and marathons @@ -212,23 +213,23 @@ function renderTimeline() { } // converts Date to "%H:%M" with leading zeros -function timeString(time) { +function timeString(time: Date) { return ('0' + time.getHours()).slice(-2) + ':' + ('0' + time.getMinutes()).slice(-2); } -function isSystemTournament(t) { +function isSystemTournament(t: Tournament) { return !!t.schedule; } -export default function (ctrl) { +export default function (ctrl: Ctrl) { now = Date.now(); startTime = now - 3 * 60 * 60 * 1000; stopTime = startTime + 10 * 60 * 60 * 1000; const data = ctrl.data(); - const systemTours: any[] = [], - userTours: any[] = []; + const systemTours: Tournament[] = [], + userTours: Tournament[] = []; data.finished .concat(data.started) diff --git a/ui/tournamentSchedule/tsconfig.json b/ui/tournamentSchedule/tsconfig.json index d58bc6e3ad..4eb37fee05 100644 --- a/ui/tournamentSchedule/tsconfig.json +++ b/ui/tournamentSchedule/tsconfig.json @@ -1,6 +1,3 @@ { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "noImplicitAny": false - } + "extends": "../tsconfig.base.json" }