From db1b01ac8c24092733fb5e6ba02eaaffe403473a Mon Sep 17 00:00:00 2001 From: Jonathan Gamble <101470903+schlawg@users.noreply.github.com> Date: Wed, 6 Sep 2023 09:39:57 -0500 Subject: [PATCH] refactor dialogs --- .gitignore | 9 +- app/templating/AssetHelper.scala | 5 +- app/views/site/help.scala | 2 +- pnpm-lock.yaml | 9 +- public/javascripts/study/tour-chapter.js | 17 +- ui/@build/src/esbuild.ts | 3 +- ui/@build/src/main.ts | 8 +- ui/@types/lichess/index.d.ts | 3 +- ui/analyse/css/_analyse.base.scss | 1 + ui/analyse/css/_keyboard.scss | 2 - ui/analyse/css/build/_analyse.abstract.scss | 1 - ui/analyse/css/study/_editor.scss | 5 + ui/analyse/css/study/_invite.scss | 7 +- ui/analyse/css/study/_modal.scss | 2 +- ui/analyse/src/explorer/explorerConfig.ts | 6 +- ui/analyse/src/keyboard.ts | 19 +- ui/analyse/src/serverSideUnderboard.ts | 25 +- ui/analyse/src/study/chapterEditForm.ts | 6 +- ui/analyse/src/study/chapterNewForm.ts | 20 +- ui/analyse/src/study/inviteForm.ts | 6 +- ui/analyse/src/study/studyForm.ts | 6 +- ui/analyse/src/study/studySearch.ts | 6 +- ui/analyse/src/study/topics.ts | 10 +- ui/analyse/src/view/actionMenu.ts | 8 +- ui/ceval/src/ctrl.ts | 8 +- ui/chart/src/common.ts | 2 +- ui/cli/css/build/_clinput.help.scss | 1 - ui/cli/src/main.ts | 32 +-- ui/common/css/_lichess.scss | 2 + ui/common/css/component/_button.scss | 2 +- ui/common/css/component/_continue-with.scss | 4 +- ui/common/css/component/_dialog.scss | 77 ++++++ ui/common/css/component/_help.scss | 86 ++----- ui/common/package.json | 7 + ui/common/src/dialog.ts | 223 ++++++++++++++++++ ui/common/src/linkPopup.ts | 45 ++-- ui/common/src/modal.ts | 121 ---------- ui/common/src/snabbdom.ts | 32 ++- ui/editor/css/build/_editor.scss | 1 - ui/editor/src/view.ts | 7 +- ui/insight/css/build/_insight.scss | 1 - ui/keyboardMove/css/_keyboardMove.help.scss | 13 - ui/keyboardMove/css/_keyboardMove.scss | 12 + .../css/build/_keyboardMove.help.scss | 4 - .../css/build/keyboardMove.help.ltr.dark.scss | 3 - .../build/keyboardMove.help.ltr.light.scss | 3 - .../build/keyboardMove.help.ltr.transp.scss | 3 - .../css/build/keyboardMove.help.rtl.dark.scss | 3 - .../build/keyboardMove.help.rtl.light.scss | 3 - .../build/keyboardMove.help.rtl.transp.scss | 3 - ui/keyboardMove/src/main.ts | 17 +- ui/lobby/css/_lobby.scss | 4 - ui/lobby/css/_lobby.setup.scss | 2 - ui/lobby/css/_setup.scss | 97 ++++---- ui/lobby/css/app/_app.scss | 6 +- .../{_hook-filter.scss => _hook-filters.scss} | 0 ui/lobby/css/build/_lobby.setup.scss | 3 +- ui/lobby/src/view/realTime/filter.ts | 1 - ui/lobby/src/view/setup/modal.ts | 8 +- ui/puzzle/css/_keyboard.scss | 3 - ui/puzzle/css/build/_puzzle.keyboard.scss | 3 - ui/puzzle/css/build/_puzzle.scss | 1 - .../css/build/puzzle.keyboard.ltr.dark.scss | 3 - .../css/build/puzzle.keyboard.ltr.light.scss | 3 - .../css/build/puzzle.keyboard.ltr.transp.scss | 3 - .../css/build/puzzle.keyboard.rtl.dark.scss | 3 - .../css/build/puzzle.keyboard.rtl.light.scss | 3 - .../css/build/puzzle.keyboard.rtl.transp.scss | 3 - ui/puzzle/src/keyboard.ts | 20 +- ui/round/css/_keyboard.scss | 3 - ui/round/css/build/_round.keyboard.scss | 3 - ui/round/css/build/_round.scss | 1 - .../css/build/round.keyboard.ltr.dark.scss | 3 - .../css/build/round.keyboard.ltr.light.scss | 3 - .../css/build/round.keyboard.ltr.transp.scss | 3 - .../css/build/round.keyboard.rtl.dark.scss | 3 - .../css/build/round.keyboard.rtl.light.scss | 3 - .../css/build/round.keyboard.rtl.transp.scss | 3 - ui/round/src/keyboard.ts | 19 +- ui/round/src/socket.ts | 11 +- ui/simul/css/build/_simul.show.scss | 1 - ui/simul/src/view/created.ts | 17 +- ui/site/css/build/_forum.scss | 1 - ui/site/css/build/_linkPopup.scss | 1 - ui/site/css/build/_mod.publicChats.scss | 1 - ui/site/css/build/_modal.scss | 2 - ui/site/css/build/modal.ltr.dark.scss | 3 - ui/site/css/build/modal.ltr.light.scss | 3 - ui/site/css/build/modal.ltr.transp.scss | 3 - ui/site/css/build/modal.rtl.dark.scss | 3 - ui/site/css/build/modal.rtl.light.scss | 3 - ui/site/css/build/modal.rtl.transp.scss | 3 - ui/site/package.json | 12 +- ui/site/src/component/assets.ts | 9 +- ui/site/src/forum.ts | 25 +- ui/site/src/gameSearch.ts | 2 - ui/site/src/publicChats.ts | 42 ++-- ui/site/src/site.lichess.globals.ts | 4 +- ui/site/src/site.ts | 14 +- .../build/_tournament.show.team-battle.scss | 1 - ui/tournament/src/view/battle.ts | 13 +- ui/voice/css/_voice.scss | 13 +- ui/voice/css/_voiceMove.help.scss | 27 +-- ui/voice/css/build/_voiceMove.help.scss | 1 - ui/voice/src/view.ts | 42 ++-- 105 files changed, 684 insertions(+), 684 deletions(-) create mode 100644 ui/common/css/component/_dialog.scss create mode 100644 ui/common/src/dialog.ts delete mode 100644 ui/common/src/modal.ts delete mode 100644 ui/keyboardMove/css/_keyboardMove.help.scss delete mode 100644 ui/keyboardMove/css/build/_keyboardMove.help.scss delete mode 100644 ui/keyboardMove/css/build/keyboardMove.help.ltr.dark.scss delete mode 100644 ui/keyboardMove/css/build/keyboardMove.help.ltr.light.scss delete mode 100644 ui/keyboardMove/css/build/keyboardMove.help.ltr.transp.scss delete mode 100644 ui/keyboardMove/css/build/keyboardMove.help.rtl.dark.scss delete mode 100644 ui/keyboardMove/css/build/keyboardMove.help.rtl.light.scss delete mode 100644 ui/keyboardMove/css/build/keyboardMove.help.rtl.transp.scss delete mode 100644 ui/lobby/css/_lobby.setup.scss rename ui/lobby/css/app/{_hook-filter.scss => _hook-filters.scss} (100%) delete mode 100644 ui/puzzle/css/_keyboard.scss delete mode 100644 ui/puzzle/css/build/_puzzle.keyboard.scss delete mode 100644 ui/puzzle/css/build/puzzle.keyboard.ltr.dark.scss delete mode 100644 ui/puzzle/css/build/puzzle.keyboard.ltr.light.scss delete mode 100644 ui/puzzle/css/build/puzzle.keyboard.ltr.transp.scss delete mode 100644 ui/puzzle/css/build/puzzle.keyboard.rtl.dark.scss delete mode 100644 ui/puzzle/css/build/puzzle.keyboard.rtl.light.scss delete mode 100644 ui/puzzle/css/build/puzzle.keyboard.rtl.transp.scss delete mode 100644 ui/round/css/_keyboard.scss delete mode 100644 ui/round/css/build/_round.keyboard.scss delete mode 100644 ui/round/css/build/round.keyboard.ltr.dark.scss delete mode 100644 ui/round/css/build/round.keyboard.ltr.light.scss delete mode 100644 ui/round/css/build/round.keyboard.ltr.transp.scss delete mode 100644 ui/round/css/build/round.keyboard.rtl.dark.scss delete mode 100644 ui/round/css/build/round.keyboard.rtl.light.scss delete mode 100644 ui/round/css/build/round.keyboard.rtl.transp.scss delete mode 100644 ui/site/css/build/_modal.scss delete mode 100644 ui/site/css/build/modal.ltr.dark.scss delete mode 100644 ui/site/css/build/modal.ltr.light.scss delete mode 100644 ui/site/css/build/modal.ltr.transp.scss delete mode 100644 ui/site/css/build/modal.rtl.dark.scss delete mode 100644 ui/site/css/build/modal.rtl.light.scss delete mode 100644 ui/site/css/build/modal.rtl.transp.scss diff --git a/.gitignore b/.gitignore index 6e914be2463..116271c887b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,13 +14,7 @@ project/metals.sbt project/project project/target public/compiled -public/vendor/highcharts-4.2.5 -public/vendor/hopscotch -public/vendor/tagify -public/vendor/stockfish.wasm -public/vendor/stockfish-nnue.wasm -public/vendor/stockfish-mv.wasm -public/vendor/stockfish.js +public/npm public/lifat public/css/ target @@ -45,6 +39,7 @@ hs_*.log dependency-graph.png RUNNING_PID +.DS_Store # Eclipse auto-generated files /modules/*/.classpath diff --git a/app/templating/AssetHelper.scala b/app/templating/AssetHelper.scala index fb48be407ff..12bc9092ba2 100644 --- a/app/templating/AssetHelper.scala +++ b/app/templating/AssetHelper.scala @@ -84,8 +84,9 @@ if (window.matchMedia('(prefers-color-scheme: dark)').media === 'not all') def captchaTag = jsModule("captcha") def cashTag = iifeModule("javascripts/vendor/cash.min.js") def fingerprintTag = iifeModule("javascripts/fipr.js") - def highchartsLatestTag = iifeModule("vendor/highcharts-4.2.5/highcharts.js") - def highchartsMoreTag = iifeModule("vendor/highcharts-4.2.5/highcharts-more.js") + def highchartsLatestTag = iifeModule("npm/highcharts-4.2.5/highcharts.js") + def highchartsMoreTag = iifeModule("npm/highcharts-4.2.5/highcharts-more.js") + def chessgroundTag = script(tpe := "module", src := assetUrl("npm/chessground.min.js")) def prismicJs(using PageContext): Frag = raw: diff --git a/app/views/site/help.scala b/app/views/site/help.scala index 8d481c7760c..e68a2dd08c4 100644 --- a/app/views/site/help.scala +++ b/app/views/site/help.scala @@ -237,7 +237,7 @@ object help: row(voice("help"), trans.showHelpDialog()), tr( td, - td(button(cls := "button", id := "all-phrases-button")("Show me everything")) + td(button(cls := "button", cls := "all-phrases-button")("Show me everything")) ) ) ) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d6821bd4ff..84c578011cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -214,6 +214,9 @@ importers: ui/common: dependencies: + dialog-polyfill: + specifier: 0.5.6 + version: 0.5.6 lichess-pgn-viewer: specifier: ^2.0.0 version: 2.0.0 @@ -2683,6 +2686,10 @@ packages: engines: {node: '>=8'} dev: false + /dialog-polyfill@0.5.6: + resolution: {integrity: sha512-ZbVDJI9uvxPAKze6z146rmfUZjBqNEwcnFTVamQzXH+svluiV7swmVIGr7miwADgfgt1G2JQIytypM9fbyhX4w==} + dev: false + /diff-sequences@29.3.1: resolution: {integrity: sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} diff --git a/public/javascripts/study/tour-chapter.js b/public/javascripts/study/tour-chapter.js index 2910840f2a2..d9414210a3f 100644 --- a/public/javascripts/study/tour-chapter.js +++ b/public/javascripts/study/tour-chapter.js @@ -18,6 +18,7 @@ function loadShepherd(f) { } lichess.studyTourChapter = function (study) { loadShepherd(function (theme) { + const viewSel = 'dialog div.dialog-content'; var onTab = function (tab) { return { 'before-show': function () { @@ -39,18 +40,18 @@ lichess.studyTourChapter = function (study) { 'A study can have several chapters.
' + 'Each chapter has a distinct move tree,
' + 'and can be created in various ways.', - attachTo: '#modal-wrap label[for=chapter-name] left', + attachTo: `${viewSel} label[for=chapter-name] left`, }, { title: 'From initial position', text: 'Just a board setup for a new game.
' + 'Suited to explore openings.', - attachTo: '#modal-wrap .tabs-horiz .init top', + attachTo: `${viewSel} .tabs-horiz .init top`, when: onTab('init'), }, { title: 'Custom position', text: 'Setup the board your way.
' + 'Suited to explore endgames.', - attachTo: '#modal-wrap .tabs-horiz .edit bottom', + attachTo: `${viewSel} .tabs-horiz .edit bottom`, when: onTab('edit'), }, { @@ -59,7 +60,7 @@ lichess.studyTourChapter = function (study) { 'Paste a lichess game URL
' + '(like lichess.org/7fHIU0XI)
' + 'to load the game moves in the chapter.', - attachTo: '#modal-wrap .tabs-horiz .game top', + attachTo: `${viewSel} .tabs-horiz .game top`, when: onTab('game'), }, { @@ -68,19 +69,19 @@ lichess.studyTourChapter = function (study) { 'Paste a position in FEN format
' + '4k3/4rb2/8/7p/8/5Q2/1PP5/1K6 w
' + 'to start the chapter from a position.', - attachTo: '#modal-wrap .tabs-horiz .fen top', + attachTo: `${viewSel} .tabs-horiz .fen top`, when: onTab('fen'), }, { title: 'From a PGN game', text: 'Paste a game in PGN format.
' + 'to load moves, comments and variations in the chapter.', - attachTo: '#modal-wrap .tabs-horiz .pgn top', + attachTo: `${viewSel} .tabs-horiz .pgn top`, when: onTab('pgn'), }, { title: 'Studies support variants', text: 'Yes, you can study crazyhouse,
' + 'and all lichess variants!', - attachTo: '#modal-wrap label[for=chapter-variant] left', + attachTo: `${viewSel} label[for=chapter-variant] left`, when: onTab('init'), }, { @@ -92,7 +93,7 @@ lichess.studyTourChapter = function (study) { action: tour.next, }, ], - attachTo: '#modal-wrap .help bottom', + attachTo: `${viewSel} .help bottom`, }, ].forEach(function (s) { tour.addStep(s.title, s); diff --git a/ui/@build/src/esbuild.ts b/ui/@build/src/esbuild.ts index a70edac9f93..f9a610bb56a 100644 --- a/ui/@build/src/esbuild.ts +++ b/ui/@build/src/esbuild.ts @@ -9,12 +9,13 @@ const typeBundles = new Map>(); export async function esbuild(): Promise { if (!env.esbuild) return; - const define = { + const define: { [_: string]: string } = { __info__: JSON.stringify({ date: new Date(new Date().toUTCString()).toISOString().split('.')[0] + '+00:00', commit: cps.execSync('git rev-parse -q HEAD', { encoding: 'utf-8' }).trim(), message: cps.execSync('git log -1 --pretty=%s', { encoding: 'utf-8' }).trim(), }), + __debug__: String(env.debug), }; for (const mod of buildModules) { diff --git a/ui/@build/src/main.ts b/ui/@build/src/main.ts index 5fadc00897a..5473c97ca9a 100644 --- a/ui/@build/src/main.ts +++ b/ui/@build/src/main.ts @@ -7,7 +7,7 @@ import { build, postBuild } from './build'; export function main() { const configPath = path.resolve(__dirname, '../build-config.json'); const config: BuildOpts = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : {}; - const oneDashArgs = ps.argv.filter(x => /^-([hpsw]+)$/.test(x))?.flatMap(x => x.slice(1).split('')); + const oneDashArgs = ps.argv.filter(x => /^-([hpdsw]+)$/.test(x))?.flatMap(x => x.slice(1).split('')); if (ps.argv.includes('--tsc') || ps.argv.includes('--sass') || ps.argv.includes('--esbuild')) { // cli args override json, including any of these flags sets those not present to false @@ -28,11 +28,8 @@ export function main() { env.watch = ps.argv.includes('--watch') || oneDashArgs.includes('w'); env.prod = ps.argv.includes('--prod') || oneDashArgs.includes('p'); env.split = ps.argv.includes('--split') || oneDashArgs.includes('s'); + env.debug = ps.argv.includes('--debug') || oneDashArgs.includes('d'); - if (env.prod && env.watch) { - env.error('You cannot watch prod builds! Think of the children'); - return; - } build(ps.argv.slice(2).filter(x => !x.startsWith('-'))); } @@ -115,6 +112,7 @@ class Env { watch = false; prod = false; split = false; + debug = false; exitCode = new Map<'sass' | 'tsc' | 'esbuild', number | false>(); startTime: number | undefined = Date.now(); diff --git a/ui/@types/lichess/index.d.ts b/ui/@types/lichess/index.d.ts index f473e55991e..f5b14cf3550 100644 --- a/ui/@types/lichess/index.d.ts +++ b/ui/@types/lichess/index.d.ts @@ -4,6 +4,7 @@ interface Lichess { load: Promise; // DOMContentLoaded promise info: any; + debug: boolean; requestIdleCallback(f: () => void, timeout?: number): void; sri: string; storage: LichessStorageHelper; @@ -580,4 +581,4 @@ interface Dictionary { type SocketHandlers = Dictionary<(d: any) => void>; declare const lichess: Lichess; -declare const $as: (cash: Cash) => T; +declare const $as: (cashOrHtml: Cash | string) => T; diff --git a/ui/analyse/css/_analyse.base.scss b/ui/analyse/css/_analyse.base.scss index 7bff7280b01..e6f67491f91 100644 --- a/ui/analyse/css/_analyse.base.scss +++ b/ui/analyse/css/_analyse.base.scss @@ -19,3 +19,4 @@ @import 'context-menu'; @import 'player-clock'; @import 'persistence'; +@import 'keyboard'; diff --git a/ui/analyse/css/_keyboard.scss b/ui/analyse/css/_keyboard.scss index e5cf3f04ac6..a9e56fe3eba 100644 --- a/ui/analyse/css/_keyboard.scss +++ b/ui/analyse/css/_keyboard.scss @@ -1,6 +1,4 @@ .keyboard-help { - @extend %help-modal; - td.mouse li { list-style: disc inside; margin-#{$start-direction}: 1em; diff --git a/ui/analyse/css/build/_analyse.abstract.scss b/ui/analyse/css/build/_analyse.abstract.scss index 4d176387c35..843bf49775d 100644 --- a/ui/analyse/css/build/_analyse.abstract.scss +++ b/ui/analyse/css/build/_analyse.abstract.scss @@ -3,7 +3,6 @@ @import '../../../common/css/layout/uniboard'; @import '../../../common/css/base/scrollbar'; @import '../../../common/css/component/board-resize'; -@import '../../../common/css/component/modal'; @import '../../../common/css/component/continue-with'; @import '../../../common/css/component/color-icon'; @import '../../../common/css/component/crosstable'; diff --git a/ui/analyse/css/study/_editor.scss b/ui/analyse/css/study/_editor.scss index c99702790b1..67072901bbb 100644 --- a/ui/analyse/css/study/_editor.scss +++ b/ui/analyse/css/study/_editor.scss @@ -15,6 +15,11 @@ grid-template-columns: 280px 2vmin 210px; grid-template-rows: min-content auto min-content; grid-template-areas: '. . e-tools' 'spare-top . e-tools' 'e-board . e-tools' 'spare-bottom . e-tools' '. . e-tools'; + @media (max-width: 576px) { + grid-template-columns: 100%; + grid-template-rows: auto min-content; + grid-template-areas: 'e-board' 'e-tools'; + } user-select: none; .main-board { diff --git a/ui/analyse/css/study/_invite.scss b/ui/analyse/css/study/_invite.scss index 092a0cafe09..fa2ed092db9 100644 --- a/ui/analyse/css/study/_invite.scss +++ b/ui/analyse/css/study/_invite.scss @@ -1,11 +1,6 @@ .study__invite { max-width: 50em; - - > div { - overflow: visible !important; - - // required by user autocomplete - } + overflow: visible !important; .info { @extend %box-radius, %flex-center-nowrap; diff --git a/ui/analyse/css/study/_modal.scss b/ui/analyse/css/study/_modal.scss index 0d9db6eea0e..ebd07b76d17 100644 --- a/ui/analyse/css/study/_modal.scss +++ b/ui/analyse/css/study/_modal.scss @@ -1,4 +1,4 @@ -#modal-wrap { +.dialog-content { min-width: 80vw; @include breakpoint($mq-x-small) { diff --git a/ui/analyse/src/explorer/explorerConfig.ts b/ui/analyse/src/explorer/explorerConfig.ts index 03f8a956a81..fb8ecbb8f57 100644 --- a/ui/analyse/src/explorer/explorerConfig.ts +++ b/ui/analyse/src/explorer/explorerConfig.ts @@ -4,7 +4,7 @@ import * as licon from 'common/licon'; import { bind, dataIcon, iconTag, onInsert } from 'common/snabbdom'; import { storedProp, storedJsonProp, StoredJsonProp, StoredProp, storedStringProp } from 'common/storage'; import { ExplorerDb, ExplorerSpeed, ExplorerMode } from './interfaces'; -import { snabModal } from 'common/modal'; +import { snabDialog } from 'common/dialog'; import AnalyseCtrl from '../ctrl'; import { perf } from 'game/perf'; import { ucfirst } from './explorerUtil'; @@ -327,13 +327,13 @@ const playerModal = (ctrl: ExplorerConfigCtrl) => { } return '.button-metal'; }; - return snabModal({ + return snabDialog({ class: 'explorer__config__player__choice', onClose() { ctrl.data.playerName.open(false); ctrl.root.redraw(); }, - content: [ + vnodes: [ h('h2', 'Personal opening explorer'), h('div.input-wrapper', [ h('input', { diff --git a/ui/analyse/src/keyboard.ts b/ui/analyse/src/keyboard.ts index 61eb1baf7b8..12c2acbad63 100644 --- a/ui/analyse/src/keyboard.ts +++ b/ui/analyse/src/keyboard.ts @@ -1,9 +1,7 @@ import * as control from './control'; -import * as xhr from 'common/xhr'; import AnalyseCtrl from './ctrl'; -import { h, VNode } from 'snabbdom'; -import { snabModal } from 'common/modal'; -import { spinnerVdom as spinner } from 'common/spinner'; +import { VNode } from 'snabbdom'; +import { snabDialog } from 'common/dialog'; export const bind = (ctrl: AnalyseCtrl) => { const kbd = window.lichess.mousetrap; @@ -116,19 +114,12 @@ export const bind = (ctrl: AnalyseCtrl) => { }; export function view(ctrl: AnalyseCtrl): VNode { - return snabModal({ - class: 'keyboard-help', - onInsert: async ($wrap: Cash) => { - const [, html] = await Promise.all([ - lichess.loadCssPath('analyse.keyboard'), - xhr.text(xhr.url('/analysis/help', { study: !!ctrl.study })), - ]); - $wrap.find('.scrollable').html(html); - }, + return snabDialog({ + class: 'help.keyboard-help', + htmlUrl: '/analysis/help', onClose() { ctrl.keyboardHelp = false; ctrl.redraw(); }, - content: [h('div.scrollable', spinner())], }); } diff --git a/ui/analyse/src/serverSideUnderboard.ts b/ui/analyse/src/serverSideUnderboard.ts index 12c1e3abe01..668cca72c02 100644 --- a/ui/analyse/src/serverSideUnderboard.ts +++ b/ui/analyse/src/serverSideUnderboard.ts @@ -1,7 +1,7 @@ import AnalyseCtrl from './ctrl'; import { baseUrl } from './view/util'; import * as licon from 'common/licon'; -import modal from 'common/modal'; +import { domDialog } from 'common/dialog'; import { url as xhrUrl, textRaw as xhrTextRaw } from 'common/xhr'; import { AnalyseData } from './interfaces'; import { ChartGame, AcplChart } from 'chart'; @@ -137,23 +137,24 @@ export default function (element: HTMLElement, ctrl: AnalyseCtrl) { selection!.removeAllRanges(); selection!.addRange(range); }); + $panels.on('click', '.embed-howto', function (this: HTMLElement) { // location.hash is percent encoded, so no need to escape and make &bg=... // uglier in the process. const url = `${baseUrl()}/embed/game/${data.game.id}?theme=auto&bg=auto${location.hash}`; const iframe = ``; - modal({ - content: $( + domDialog({ + show: 'modal', + htmlText: '
' + - $(this).html() + - '

' + - '
' +
-          lichess.escapeHtml(iframe) +
-          '

' + - iframe + - '

' + - `Read more about embedding games
`, - ), + $(this).html() + + '

' + + '
' +
+        lichess.escapeHtml(iframe) +
+        '

' + + iframe + + '

' + + `Read more about embedding games`, }); }); } diff --git a/ui/analyse/src/study/chapterEditForm.ts b/ui/analyse/src/study/chapterEditForm.ts index ba495ac18f2..4f9620e641c 100644 --- a/ui/analyse/src/study/chapterEditForm.ts +++ b/ui/analyse/src/study/chapterEditForm.ts @@ -12,7 +12,7 @@ import { import { defined, prop, Prop } from 'common'; import { h, VNode } from 'snabbdom'; import { Redraw } from '../interfaces'; -import { snabModal } from 'common/modal'; +import { snabDialog } from 'common/dialog'; import { StudySocketSend } from '../socket'; export interface StudyChapterEditFormCtrl { @@ -88,13 +88,13 @@ export function view(ctrl: StudyChapterEditFormCtrl): VNode | undefined { const data = ctrl.current(), noarg = ctrl.trans.noarg; return data - ? snabModal({ + ? snabDialog({ class: 'edit-' + data.id, // full redraw when changing chapter onClose() { ctrl.current(null); ctrl.redraw(); }, - content: [ + vnodes: [ h('h2', noarg('editChapter')), h( 'form.form3', diff --git a/ui/analyse/src/study/chapterNewForm.ts b/ui/analyse/src/study/chapterNewForm.ts index b9898b5dff6..473850587cd 100644 --- a/ui/analyse/src/study/chapterNewForm.ts +++ b/ui/analyse/src/study/chapterNewForm.ts @@ -1,7 +1,7 @@ import { parseFen } from 'chessops/fen'; import { defined, prop, Prop } from 'common'; import * as licon from 'common/licon'; -import { snabModal } from 'common/modal'; +import { snabDialog } from 'common/dialog'; import { bind, bindSubmit, onInsert } from 'common/snabbdom'; import { StoredProp, storedStringProp } from 'common/storage'; import * as xhr from 'common/xhr'; @@ -130,8 +130,18 @@ export function view(ctrl: StudyChapterNewFormCtrl): VNode { 'span.' + key, { class: { active: activeTab === key }, - attrs: { role: 'tab', title }, - hook: bind('click', () => ctrl.vm.tab(key), ctrl.root.redraw), + attrs: { role: 'tab', title, tabindex: '0' }, + hook: onInsert(el => { + const select = (e: Event) => { + ctrl.vm.tab(key); + ctrl.root.redraw(); + e.preventDefault(); + }; + el.addEventListener('click', select); + el.addEventListener('keydown', e => { + if (e.key === 'Enter' || e.key === ' ') select(e); + }); + }), }, name, ); @@ -147,14 +157,14 @@ export function view(ctrl: StudyChapterNewFormCtrl): VNode { : 'normal'; const noarg = trans.noarg; - return snabModal({ + return snabDialog({ class: 'chapter-new', onClose() { ctrl.close(); ctrl.redraw(); }, noClickAway: true, - content: [ + vnodes: [ activeTab === 'edit' ? null : h('h2', [ diff --git a/ui/analyse/src/study/inviteForm.ts b/ui/analyse/src/study/inviteForm.ts index 265a09749fb..50a8015a63b 100644 --- a/ui/analyse/src/study/inviteForm.ts +++ b/ui/analyse/src/study/inviteForm.ts @@ -2,7 +2,7 @@ import * as licon from 'common/licon'; import { bind, onInsert } from 'common/snabbdom'; import { titleNameToId } from '../view/util'; import { h, VNode } from 'snabbdom'; -import { snabModal } from 'common/modal'; +import { snabDialog } from 'common/dialog'; import { prop, Prop } from 'common'; import { StudyMemberMap } from './interfaces'; import { AnalyseSocketSend } from '../socket'; @@ -59,13 +59,13 @@ export function view(ctrl: ReturnType): VNode { const candidates = [...new Set([...ctrl.spectators(), ...ctrl.previouslyInvited()])] .filter(s => !ctrl.members()[titleNameToId(s)]) // remove existing members .sort(); - return snabModal({ + return snabDialog({ class: 'study__invite', onClose() { ctrl.open(false); ctrl.redraw(); }, - content: [ + vnodes: [ h('h2', ctrl.trans.noarg('inviteToTheStudy')), h( 'p.info', diff --git a/ui/analyse/src/study/studyForm.ts b/ui/analyse/src/study/studyForm.ts index cbef5335fe8..624c46c6fdc 100644 --- a/ui/analyse/src/study/studyForm.ts +++ b/ui/analyse/src/study/studyForm.ts @@ -1,6 +1,6 @@ import { h, VNode } from 'snabbdom'; import * as licon from 'common/licon'; -import { snabModal } from 'common/modal'; +import { snabDialog } from 'common/dialog'; import { prop, Prop } from 'common'; import { bindSubmit, bindNonPassive } from 'common/snabbdom'; import { emptyRedButton } from '../view/util'; @@ -116,13 +116,13 @@ export function view(ctrl: StudyFormCtrl): VNode { ['member', ctrl.trans.noarg('members')], ['everyone', ctrl.trans.noarg('everyone')], ]; - return snabModal({ + return snabDialog({ class: 'study-edit', onClose() { ctrl.open(false); ctrl.redraw(); }, - content: [ + vnodes: [ h('h2', ctrl.trans.noarg(ctrl.relay ? 'editRoundStudy' : isNew ? 'createStudy' : 'editStudy')), h( 'form.form3', diff --git a/ui/analyse/src/study/studySearch.ts b/ui/analyse/src/study/studySearch.ts index 54ecc6e81b0..95cfe032065 100644 --- a/ui/analyse/src/study/studySearch.ts +++ b/ui/analyse/src/study/studySearch.ts @@ -1,6 +1,6 @@ import { Prop, propWithEffect } from 'common'; import * as licon from 'common/licon'; -import { snabModal } from 'common/modal'; +import { snabDialog } from 'common/dialog'; import { bind, dataIcon, onInsert } from 'common/snabbdom'; import { h, VNode } from 'snabbdom'; import { Redraw } from '../interfaces'; @@ -49,12 +49,12 @@ const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // export function view(ctrl: SearchCtrl) { const cleanQuery = ctrl.cleanQuery(); const highlightRegex = cleanQuery && new RegExp(escapeRegExp(cleanQuery), 'gi'); - return snabModal({ + return snabDialog({ class: 'study-search', onClose() { ctrl.open(false); }, - content: [ + vnodes: [ h('input', { attrs: { autofocus: 1, placeholder: `Search in ${ctrl.studyName}`, value: ctrl.query() }, hook: onInsert((el: HTMLInputElement) => { diff --git a/ui/analyse/src/study/topics.ts b/ui/analyse/src/study/topics.ts index 2e16e313e67..fbf8cd93fe7 100644 --- a/ui/analyse/src/study/topics.ts +++ b/ui/analyse/src/study/topics.ts @@ -1,6 +1,6 @@ import type Tagify from '@yaireo/tagify'; import { prop } from 'common'; -import { snabModal } from 'common/modal'; +import { snabDialog } from 'common/dialog'; import { bind, bindSubmit, onInsert } from 'common/snabbdom'; import * as xhr from 'common/xhr'; import { h, VNode } from 'snabbdom'; @@ -43,13 +43,13 @@ export const view = (ctrl: StudyCtrl): VNode => let tagify: Tagify | undefined; export const formView = (ctrl: TopicsCtrl, userId?: string): VNode => - snabModal({ + snabDialog({ class: 'study-topics', onClose() { ctrl.open(false); ctrl.redraw(); }, - content: [ + vnodes: [ h('h2', ctrl.trans.noarg('topics')), h( 'form', @@ -84,14 +84,14 @@ export const formView = (ctrl: TopicsCtrl, userId?: string): VNode => function setupTagify(elm: HTMLInputElement | HTMLTextAreaElement, userId?: string) { lichess.loadCssPath('tagify'); - lichess.loadIife('vendor/tagify/tagify.min.js').then(() => { + lichess.loadIife('npm/tagify/tagify.min.js').then(() => { const tagi = (tagify = new (window.Tagify as typeof Tagify)(elm, { pattern: /.{2,}/, maxTags: 30, })); let abortCtrl: AbortController | undefined; // for aborting the call tagi.on('input', e => { - const term = e.detail.value.trim(); + const term = (e.detail as Tagify.TagData).value.trim(); if (term.length < 2) return; tagi.settings.whitelist!.length = 0; // reset the whitelist abortCtrl && abortCtrl.abort(); diff --git a/ui/analyse/src/view/actionMenu.ts b/ui/analyse/src/view/actionMenu.ts index 33378ed37e2..0d2a77a54c6 100644 --- a/ui/analyse/src/view/actionMenu.ts +++ b/ui/analyse/src/view/actionMenu.ts @@ -1,6 +1,6 @@ import { isEmpty } from 'common'; import * as licon from 'common/licon'; -import modal from 'common/modal'; +import { domDialog } from 'common/dialog'; import { bind, dataIcon, MaybeVNodes } from 'common/snabbdom'; import { h, VNode } from 'snabbdom'; import { AutoplayDelay } from '../autoplay'; @@ -161,11 +161,7 @@ export function view(ctrl: AnalyseCtrl): VNode { ? h( 'a.button.button-empty', { - hook: bind('click', _ => - modal({ - content: $('.continue-with.g_' + d.game.id), - }), - ), + hook: bind('click', () => domDialog({ cash: $('.continue-with.g_' + d.game.id) })), attrs: dataIcon(licon.Swords), }, noarg('continueFromHere'), diff --git a/ui/ceval/src/ctrl.ts b/ui/ceval/src/ctrl.ts index df214265040..06fbcaa2495 100644 --- a/ui/ceval/src/ctrl.ts +++ b/ui/ceval/src/ctrl.ts @@ -177,7 +177,7 @@ export default class CevalCtrl { else if (this.technology == 'nnue') this.worker = new ThreadedWasmWorker( { - baseUrl: 'vendor/stockfish-nnue.wasm/', + baseUrl: 'npm/stockfish-nnue.wasm/', module: 'Stockfish', downloadProgress: throttle(200, mb => { this.downloadProgress(mb); @@ -192,7 +192,7 @@ export default class CevalCtrl { else if (this.technology == 'hce') this.worker = new ThreadedWasmWorker( { - baseUrl: this.officialStockfish ? 'vendor/stockfish.wasm/' : 'vendor/stockfish-mv.wasm/', + baseUrl: this.officialStockfish ? 'npm/stockfish.wasm/' : 'npm/stockfish-mv.wasm/', module: this.officialStockfish ? 'Stockfish' : 'StockfishMv', version: 'a022fa', wasmMemory: sharedWasmMemory(1024, this.platform.maxWasmPages(1088)), @@ -204,8 +204,8 @@ export default class CevalCtrl { { url: this.technology == 'wasm' - ? 'vendor/stockfish.js/stockfish.wasm.js' - : 'vendor/stockfish.js/stockfish.js', + ? 'npm/stockfish.js/stockfish.wasm.js' + : 'npm/stockfish.js/stockfish.js', }, this.opts.redraw, ); diff --git a/ui/chart/src/common.ts b/ui/chart/src/common.ts index 80b25740d7b..2f1ee5e4c81 100644 --- a/ui/chart/src/common.ts +++ b/ui/chart/src/common.ts @@ -20,7 +20,7 @@ export function selectPly(this: PlyChart, ply: number, onMainline: boolean) { export async function loadHighcharts(tpe: string) { if (highchartsPromise) return highchartsPromise; const file = tpe === 'highstock' ? 'highstock.js' : 'highcharts.js'; - highchartsPromise = lichess.loadIife('vendor/highcharts-4.2.5/' + file, { + highchartsPromise = lichess.loadIife('npm/highcharts-4.2.5/' + file, { noVersion: true, }); await highchartsPromise; diff --git a/ui/cli/css/build/_clinput.help.scss b/ui/cli/css/build/_clinput.help.scss index 21f04eaca6d..59be69e6880 100644 --- a/ui/cli/css/build/_clinput.help.scss +++ b/ui/cli/css/build/_clinput.help.scss @@ -1,3 +1,2 @@ @import '../../../common/css/plugin'; -@import '../../../common/css/component/modal'; @import '../help'; diff --git a/ui/cli/src/main.ts b/ui/cli/src/main.ts index 65e55cacebc..94c41f3aa09 100644 --- a/ui/cli/src/main.ts +++ b/ui/cli/src/main.ts @@ -1,4 +1,4 @@ -import modal from 'common/modal'; +import { domDialog } from 'common/dialog'; import { load as loadDasher } from 'dasher'; export function initModule({ input }: { input: HTMLInputElement }) { @@ -54,21 +54,21 @@ function commandHelp(aliases: string, args: string, desc: string) { } function help() { - lichess.loadCssPath('clinput.help'); - modal({ - content: $( - '

Commands

' + - commandHelp('/tv /follow', ' ', 'Watch someone play') + - commandHelp('/play /challenge /match', ' ', 'Challenge someone to play') + - commandHelp('/light /dark /transp /system', '', 'Change the background theme') + - commandHelp('/stream', '', 'Watch someone stream') + - '

Global hotkeys

' + - commandHelp('s', '', 'Search for a user') + - commandHelp('/', '', 'Type a command') + - commandHelp('c', '', 'Focus the chat input') + - commandHelp('esc', '', 'Close modals like this one') + - '
', - ), + domDialog({ + cssPath: 'clinput.help', class: 'clinput-help', + show: 'modal', + htmlText: + '

Commands

' + + commandHelp('/tv /follow', ' ', 'Watch someone play') + + commandHelp('/play /challenge /match', ' ', 'Challenge someone to play') + + commandHelp('/light /dark /transp /system', '', 'Change the background theme') + + commandHelp('/stream', '', 'Watch someone stream') + + '

Global hotkeys

' + + commandHelp('s', '', 'Search for a user') + + commandHelp('/', '', 'Type a command') + + commandHelp('c', '', 'Focus the chat input') + + commandHelp('esc', '', 'Close modals like this one') + + '
', }); } diff --git a/ui/common/css/_lichess.scss b/ui/common/css/_lichess.scss index 51a0796685d..8ab796b1716 100644 --- a/ui/common/css/_lichess.scss +++ b/ui/common/css/_lichess.scss @@ -36,3 +36,5 @@ @import 'component/reconnecting'; @import 'component/announce'; @import 'component/agreement'; +@import 'component/dialog'; +@import 'component/help'; diff --git a/ui/common/css/component/_button.scss b/ui/common/css/component/_button.scss index 5d489886404..2dfde17bae2 100644 --- a/ui/common/css/component/_button.scss +++ b/ui/common/css/component/_button.scss @@ -26,7 +26,7 @@ background: lighten($c-primary, 7); } - &:not(.disabled):focus { + &:not(.disabled):focus-visible { @extend %focus-shadow; } diff --git a/ui/common/css/component/_continue-with.scss b/ui/common/css/component/_continue-with.scss index 35331ed25da..cbe682cacca 100644 --- a/ui/common/css/component/_continue-with.scss +++ b/ui/common/css/component/_continue-with.scss @@ -1,6 +1,8 @@ .continue-with { @extend %flex-column; - + @media (hover: none) { + padding: 2em; + } > *:not(:first-child) { margin-top: 1em; } diff --git a/ui/common/css/component/_dialog.scss b/ui/common/css/component/_dialog.scss new file mode 100644 index 00000000000..5f54adc38bb --- /dev/null +++ b/ui/common/css/component/_dialog.scss @@ -0,0 +1,77 @@ +dialog { + @extend %box-radius, %popup-shadow; + position: fixed; + max-height: 100%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: z('modal'); + padding: 0; + border: none; + background: $c-bg-high; + + &::backdrop { + background: $c-page-mask; + } + + > div.scrollable { + max-height: calc(100 * var(--vh) - 16px); + overflow-x: clip; + overflow-y: auto; + } + + div.close-button-anchor { + position: relative; + width: 100%; + height: 0; + } + + button.close-button { + @extend %flex-around; + + position: absolute; + top: 4px; + #{$end-direction}: 4px; + width: 40px; // bigger for phones + height: 40px; + z-index: z('modal') + 1; + background: $c-bg-high; + color: $c-font; + border-radius: 6px; + border: 1px solid $c-border; + font-size: 20px; + text-align: center; + + cursor: pointer; + + &:not(:focus) { + outline: none; + } + } + + &:not(.touch-scroll) { + margin-top: 16px; + overflow: visible; + + button.close-button { + transform: translate($transform-direction * 18px, -18px); + width: 32px; + height: 32px; + font-size: 16px; + border-radius: 50%; + border: none; + &:hover { + box-shadow: $box-shadow; + background: $c-bad; + color: #fff; + } + } + } +} + +// top level to reduce specificity, allow easy overrides +.dialog-content { + text-align: center; + padding: 2em; + color: $c-font; +} diff --git a/ui/common/css/component/_help.scss b/ui/common/css/component/_help.scss index 2b8e566abdb..7df39d7e7ab 100644 --- a/ui/common/css/component/_help.scss +++ b/ui/common/css/component/_help.scss @@ -1,5 +1,15 @@ -%help { - @extend %flex-column; +dialog .help { + align-items: center; + text-align: center; + padding: 0.8em 0; + + h2 { + margin: 0.5em 0 0 0; + } + + table { + width: 100%; + } th p { margin: 1.2em 0 0.6em 0; @@ -14,16 +24,6 @@ text-align: left; } - .keys { - padding-#{$end-direction}: 1em; - text-align: right; - white-space: nowrap; - } - - .desc:first-letter { - text-transform: uppercase; - } - or { margin-#{$start-direction}: 0.2em; opacity: 0.5; @@ -43,64 +43,14 @@ border-radius: 3px; box-shadow: inset 0 -1px 0 #bbb; } -} -%help-modal { - @extend %help; - - table { - width: 100%; + .keys { + padding-#{$end-direction}: 1em; + text-align: right; + white-space: nowrap; } - > div { - padding: 0 !important; - } - - h2 { - margin: 0.5em 0 0 0; - } - - .scrollable { - overflow-y: auto; - padding: 0.8em 0; - } -} - -.help-ephemeral { - @extend %help, %box-radius, %popup-shadow; - position: absolute; - z-index: 100; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - align-items: center; - background: $c-bg-popup; - padding: 1em 2em; - white-space: nowrap; - pointer-events: none; - - kbd { - font-family: roboto, sans-serif; - } - - table { - display: inline-block; - width: auto; - } - - ul { - margin-bottom: 1em; - display: inline-block; - width: auto; - font-size: larger; - li { - padding-left: 0.5em; - list-style: disc; - } - } - - &.fade-out { - transition: opacity 0.5s linear; - opacity: 0; + .desc:first-letter { + text-transform: uppercase; } } diff --git a/ui/common/package.json b/ui/common/package.json index 5ec0590bf0f..c9bcbc38467 100644 --- a/ui/common/package.json +++ b/ui/common/package.json @@ -24,6 +24,7 @@ "author": "Thibault Duplessis", "license": "AGPL-3.0-or-later", "dependencies": { + "dialog-polyfill": "0.5.6", "lichess-pgn-viewer": "^2.0.0", "snabbdom": "^3.5.1", "tablesort": "^5.3.0" @@ -32,5 +33,11 @@ "compile": "tsc", "dev": "tsc", "prod": "tsc" + }, + "lichess": { + "copy": { + "src": "node_modules/dialog-polyfill/dist/dialog-polyfill.esm.js", + "dest": "../../public/npm" + } } } diff --git a/ui/common/src/dialog.ts b/ui/common/src/dialog.ts new file mode 100644 index 00000000000..fb02d95dbd7 --- /dev/null +++ b/ui/common/src/dialog.ts @@ -0,0 +1,223 @@ +import { VNode, Attrs } from 'snabbdom'; +import { onInsert, lh as h, MaybeVNodes } from './snabbdom'; +import { isTouchDevice, isIOS } from './mobile'; +import * as xhr from './xhr'; +import * as licon from './licon'; + +let dialogPolyfill: { registerDialog: (dialog: HTMLDialogElement) => void }; + +lichess.load.then(async () => { + window.addEventListener('resize', onResize); + + if (isIOS({ below: 15.4 })) dialogPolyfill = await import(lichess.assetUrl('npm/dialog-polyfill.esm.js')); +}); + +export interface Dialog { + readonly open: boolean; // is this dialog visible? + readonly view: HTMLElement; // your content div + readonly returnValue?: 'ok' | 'cancel' | string; // how did it close? + + showModal(): void; + show(): void; + close(): void; +} + +interface DialogOpts { + class?: string; // zero or more classes (period separated) for your view div + cssPath?: string; // for themed css craplets + cash?: Cash; // content, will be cloned and any 'none' class removed + htmlUrl?: string; // content, url will be xhr'd + htmlText?: string; // content, text will be used as-is + attrs?: { dialog?: Attrs; view?: Attrs }; // optional attrs for dialog and view div + onClose?: (dialog: Dialog) => void; // called when dialog closes + noCloseButton?: boolean; // if true, no upper right corener close button + noClickAway?: boolean; // if true, no click-away-to-close +} + +export interface DomDialogOpts extends DialogOpts { + parent?: Element; // for centering and dom placement, otherwise fixed on document.body + show?: 'modal' | boolean; // if not falsy, auto-show, and if 'modal' remove from dom on close +} + +export interface SnabDialogOpts extends DialogOpts { + vnodes?: MaybeVNodes; // snabDialog auto-shows by default, but you must still call redraw + onInsert?: (dialog: Dialog) => void; // prevents showModal, caller must do so manually +} + +// if no 'show' in opts, you must call show or showModal on the resolved promise +export async function domDialog(o: DomDialogOpts): Promise { + const [html] = await assets(o); + + const dialog = document.createElement('dialog'); + for (const [k, v] of Object.entries(o.attrs?.dialog ?? {})) dialog.setAttribute(k, String(v)); + if (isTouchDevice()) dialog.classList.add('touch-scroll'); + if (o.parent) dialog.style.position = 'absolute'; + + if (!o.noCloseButton) { + const anchor = $as('
'); + anchor.innerHTML = ` - - ${trans('proceedToX', url.host)} - -
- `, - ), - onInsert($wrap) { - $wrap.find('.cancel').on('click', modal.close); - }, - }), - ); + + + `, + }).then(dlg => { + $('.cancel', dlg.view).on('click', dlg.close); + $('a', dlg.view).on('click', () => setTimeout(dlg.close, 1000)); + dlg.showModal(); + }); return false; }; diff --git a/ui/common/src/modal.ts b/ui/common/src/modal.ts deleted file mode 100644 index 053c7ba1bdc..00000000000 --- a/ui/common/src/modal.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { h, VNode } from 'snabbdom'; -import { bind, MaybeVNodes, onInsert } from './snabbdom'; -import * as licon from './licon'; - -interface BaseModal { - class?: string; - onInsert?: ($wrap: Cash) => void; - onClose?(): void; - noClickAway?: boolean; -} - -interface Modal extends BaseModal { - content: Cash; -} - -interface SnabModal extends BaseModal { - content: MaybeVNodes; - onClose(): void; -} - -const overlayId = 'modal-overlay'; - -export default function modal(opts: Modal) { - modal.close(); - const $wrap = $( - ``, - ); - const $overlay = $(`
`); - if (!opts.noClickAway) $overlay.on('click', modal.close); - $('').appendTo($overlay); // guard against focus escaping to window chrome - $wrap.appendTo($overlay); - $('').appendTo($overlay); // guard against focus escaping to window chrome - opts.content.clone().removeClass('none').appendTo($wrap); - opts.onInsert && opts.onInsert($wrap); - modal.onClose = opts.onClose; - $wrap.find('.close,.cancel').each(function (this: HTMLElement) { - bindClose(this, modal.close); - }); - $('body').addClass('overlayed').prepend($overlay); - bindWrap($wrap); - return $wrap; -} - -modal.close = () => { - $('body').removeClass('overlayed'); - $(`#${overlayId}`).each(function (this: HTMLElement) { - if (modal.onClose) modal.onClose(); - $(this).remove(); - }); - delete modal.onClose; -}; - -modal.onClose = undefined as (() => void) | undefined; - -export function snabModal(opts: SnabModal): VNode { - const close = opts.onClose!; - return h( - `div#${overlayId}`, - opts.noClickAway - ? {} - : { - hook: bind('mousedown', (event: MouseEvent) => { - if ((event.target as HTMLElement).id == overlayId) close(); - }), - }, - [ - h( - 'div#modal-wrap.' + opts.class, - { - hook: onInsert(el => { - bindWrap($(el)); - opts.onInsert && opts.onInsert($(el)); - }), - }, - [ - h('span.close', { - attrs: { - 'data-icon': licon.X, - role: 'button', - 'aria-label': 'Close', - tabindex: '0', - }, - hook: onInsert(el => bindClose(el, close)), - }), - h('div', opts.content), - ], - ), - ], - ); -} - -const bindClose = (el: HTMLElement, close: () => void) => { - el.addEventListener('click', close); - el.addEventListener('keydown', e => (e.code === 'Enter' || e.code === 'Space' ? close() : true)); -}; - -const bindWrap = ($wrap: Cash) => { - $wrap.on('click', (e: Event) => e.stopPropagation()); - focusFirstChild($wrap); -}; - -const focusableSelectors = - 'button:not(:disabled), [href], input:not(:disabled):not([type="hidden"]), select:not(:disabled), textarea:not(:disabled), [tabindex="0"]'; - -export function trapFocus(event: FocusEvent) { - const wrap: HTMLElement | undefined = $('#modal-wrap')[0]; - if (!wrap) return; - const position = wrap.compareDocumentPosition(event.target as HTMLElement); - if (position & Node.DOCUMENT_POSITION_CONTAINED_BY) return; - const focusableChildren = $(wrap).find(focusableSelectors); - const index = position & Node.DOCUMENT_POSITION_FOLLOWING ? 0 : focusableChildren.length - 1; - focusableChildren.get(index)?.focus(); - event.preventDefault(); -} - -export const focusFirstChild = (parent: Cash) => { - const children = parent.find(focusableSelectors); - // prefer child 1 over child 0 because child 0 should be a close button - // use setTimeout to avoid race conditions with snabbdom - setTimeout(() => (children[1] ?? children[0])?.focus()); -}; diff --git a/ui/common/src/snabbdom.ts b/ui/common/src/snabbdom.ts index f92ccfa441d..0b7db3d7c72 100644 --- a/ui/common/src/snabbdom.ts +++ b/ui/common/src/snabbdom.ts @@ -1,4 +1,4 @@ -import { h, VNode, Hooks, Attrs } from 'snabbdom'; +import { h as snabH, VNode, VNodeData, VNodeChildElement, Hooks, Attrs } from 'snabbdom'; export type Redraw = () => void; export type MaybeVNode = VNode | string | null | undefined; @@ -36,4 +36,32 @@ export const dataIcon = (icon: string): Attrs => ({ 'data-icon': icon, }); -export const iconTag = (icon: string) => h('i', { attrs: dataIcon(icon) }); +export const iconTag = (icon: string) => snabH('i', { attrs: dataIcon(icon) }); + +type LooseVNode = VNodeChildElement | boolean; +type VNodeKids = LooseVNode | LooseVNode[]; + +function filterKids(children: VNodeKids): VNodeChildElement[] { + return ( + typeof children === 'boolean' + ? [] + : Array.isArray(children) + ? children.filter(x => typeof x !== 'boolean') + : [children] + ) as VNodeChildElement[]; +} + +/* obviate need for some ternary expressions in renders. Allows + lh('div', [ kids && h('div', 'kid') ]) + lh('div', [ noKids || h('div', 'kid') ]) + instead of + h('div', [ isKid ? h('div', 'kid') : null ]) + 'true' values are filtered out of children array same as 'false' (for || case) +*/ +export function lh(sel: string, dataOrKids?: VNodeData | null | VNodeKids, kids?: VNodeKids): VNode { + if (kids) return snabH(sel, dataOrKids as VNodeData, filterKids(kids)); + if (!dataOrKids) return snabH(sel); + if (Array.isArray(dataOrKids) || (typeof dataOrKids === 'object' && 'sel' in dataOrKids)) + return snabH(sel, filterKids(dataOrKids as VNodeKids)); + else return snabH(sel, dataOrKids as VNodeData); +} diff --git a/ui/editor/css/build/_editor.scss b/ui/editor/css/build/_editor.scss index 25a0220d0fd..c2fa51c3572 100644 --- a/ui/editor/css/build/_editor.scss +++ b/ui/editor/css/build/_editor.scss @@ -2,6 +2,5 @@ @import '../../../common/css/vendor/chessground/coords'; @import '../../../common/css/layout/uniboard'; @import '../../../common/css/component/board-resize'; -@import '../../../common/css/component/modal'; @import '../../../common/css/component/continue-with'; @import '../editor'; diff --git a/ui/editor/src/view.ts b/ui/editor/src/view.ts index 730b478d225..16cb98a0e16 100644 --- a/ui/editor/src/view.ts +++ b/ui/editor/src/view.ts @@ -5,7 +5,7 @@ import { dragNewPiece } from 'chessground/drag'; import { eventPosition, opposite } from 'chessground/util'; import { Rules } from 'chessops/types'; import { parseFen } from 'chessops/fen'; -import modal from 'common/modal'; +import { domDialog } from 'common/dialog'; import EditorCtrl from './ctrl'; import chessground from './chessground'; import { Selected, CastlingToggle, EditorState } from './interfaces'; @@ -274,10 +274,7 @@ function controls(ctrl: EditorCtrl, state: EditorState): VNode { }, on: { click: () => { - if (state.playable) - modal({ - content: $('.continue-with'), - }); + if (state.playable) domDialog({ cash: $('.continue-with'), show: 'modal' }); }, }, }, diff --git a/ui/insight/css/build/_insight.scss b/ui/insight/css/build/_insight.scss index 932a76fb392..4c3adff41ca 100644 --- a/ui/insight/css/build/_insight.scss +++ b/ui/insight/css/build/_insight.scss @@ -1,6 +1,5 @@ @import '../../../common/css/plugin'; @import '../../../common/css/base/scrollbar'; @import '../../../common/css/component/slist'; -@import '../../../common/css/component/modal'; @import '../../../common/css/vendor/multiple-select'; @import '../insight'; diff --git a/ui/keyboardMove/css/_keyboardMove.help.scss b/ui/keyboardMove/css/_keyboardMove.help.scss deleted file mode 100644 index 0d373dac1bd..00000000000 --- a/ui/keyboardMove/css/_keyboardMove.help.scss +++ /dev/null @@ -1,13 +0,0 @@ -.keyboard-move-help { - @extend %help-modal; - - td.tips li { - list-style: disc; - margin-#{$start-direction}: 2em; - margin-#{$end-direction}: 1em; - } - - a { - margin-#{$start-direction}: 0.25em; - } -} diff --git a/ui/keyboardMove/css/_keyboardMove.scss b/ui/keyboardMove/css/_keyboardMove.scss index 9517c898550..988f43547c5 100644 --- a/ui/keyboardMove/css/_keyboardMove.scss +++ b/ui/keyboardMove/css/_keyboardMove.scss @@ -21,3 +21,15 @@ color: $c-font-dim; } } + +.keyboard-move-help { + td.tips li { + list-style: disc; + margin-#{$start-direction}: 2em; + margin-#{$end-direction}: 1em; + } + + a { + margin-#{$start-direction}: 0.25em; + } +} diff --git a/ui/keyboardMove/css/build/_keyboardMove.help.scss b/ui/keyboardMove/css/build/_keyboardMove.help.scss deleted file mode 100644 index abea3dff3ed..00000000000 --- a/ui/keyboardMove/css/build/_keyboardMove.help.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import '../../../common/css/plugin'; -@import '../../../common/css/component/modal'; -@import '../../../common/css/component/help'; -@import '../keyboardMove.help'; diff --git a/ui/keyboardMove/css/build/keyboardMove.help.ltr.dark.scss b/ui/keyboardMove/css/build/keyboardMove.help.ltr.dark.scss deleted file mode 100644 index 55de7d4ac6f..00000000000 --- a/ui/keyboardMove/css/build/keyboardMove.help.ltr.dark.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/dark'; -@import 'keyboardMove.help'; diff --git a/ui/keyboardMove/css/build/keyboardMove.help.ltr.light.scss b/ui/keyboardMove/css/build/keyboardMove.help.ltr.light.scss deleted file mode 100644 index 4be62017648..00000000000 --- a/ui/keyboardMove/css/build/keyboardMove.help.ltr.light.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/light'; -@import 'keyboardMove.help'; diff --git a/ui/keyboardMove/css/build/keyboardMove.help.ltr.transp.scss b/ui/keyboardMove/css/build/keyboardMove.help.ltr.transp.scss deleted file mode 100644 index 593ddc0f7f6..00000000000 --- a/ui/keyboardMove/css/build/keyboardMove.help.ltr.transp.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/transp'; -@import 'keyboardMove.help'; diff --git a/ui/keyboardMove/css/build/keyboardMove.help.rtl.dark.scss b/ui/keyboardMove/css/build/keyboardMove.help.rtl.dark.scss deleted file mode 100644 index 1f53d4ec126..00000000000 --- a/ui/keyboardMove/css/build/keyboardMove.help.rtl.dark.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/dark'; -@import 'keyboardMove.help'; diff --git a/ui/keyboardMove/css/build/keyboardMove.help.rtl.light.scss b/ui/keyboardMove/css/build/keyboardMove.help.rtl.light.scss deleted file mode 100644 index ffbbd78fcf6..00000000000 --- a/ui/keyboardMove/css/build/keyboardMove.help.rtl.light.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/light'; -@import 'keyboardMove.help'; diff --git a/ui/keyboardMove/css/build/keyboardMove.help.rtl.transp.scss b/ui/keyboardMove/css/build/keyboardMove.help.rtl.transp.scss deleted file mode 100644 index 4a558df57d3..00000000000 --- a/ui/keyboardMove/css/build/keyboardMove.help.rtl.transp.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/transp'; -@import 'keyboardMove.help'; diff --git a/ui/keyboardMove/src/main.ts b/ui/keyboardMove/src/main.ts index e67c91a2b30..90a17233e43 100644 --- a/ui/keyboardMove/src/main.ts +++ b/ui/keyboardMove/src/main.ts @@ -1,11 +1,9 @@ import * as cg from 'chessground/types'; -import * as xhr from 'common/xhr'; import { Api as CgApi } from 'chessground/api'; import { h } from 'snabbdom'; import { onInsert } from 'common/snabbdom'; import { promote } from 'chess/promotion'; -import { snabModal } from 'common/modal'; -import { spinnerVdom as spinner } from 'common/spinner'; +import { snabDialog } from 'common/dialog'; import { propWithEffect, Prop } from 'common'; import { load as loadKeyboardMove } from './plugins/keyboardMove'; import KeyboardChecker from './plugins/keyboardChecker'; @@ -164,17 +162,10 @@ export function render(ctrl: KeyboardMove) { ? h('em', 'Enter SAN (Nc3), ICCF (2133) or UCI (b1c3) moves, type ? to learn more') : h('strong', 'Press to focus'), ctrl.helpModalOpen() - ? snabModal({ - class: 'keyboard-move-help', - content: [h('div.scrollable', spinner())], + ? snabDialog({ + class: 'help.keyboard-move-help', + htmlUrl: '/help/keyboard-move', onClose: () => ctrl.helpModalOpen(false), - onInsert: async ($wrap: Cash) => { - const [, html] = await Promise.all([ - lichess.loadCssPath('keyboardMove.help'), - xhr.text(xhr.url('/help/keyboard-move', {})), - ]); - $wrap.find('.scrollable').html(html); - }, }) : null, ]); diff --git a/ui/lobby/css/_lobby.scss b/ui/lobby/css/_lobby.scss index f38f4e57a5a..6f6bc9c23b8 100644 --- a/ui/lobby/css/_lobby.scss +++ b/ui/lobby/css/_lobby.scss @@ -66,10 +66,6 @@ body { } } -.game-setup { - display: none; -} - .about-side { @extend %page-text-shadow !optional; diff --git a/ui/lobby/css/_lobby.setup.scss b/ui/lobby/css/_lobby.setup.scss deleted file mode 100644 index 357787e6605..00000000000 --- a/ui/lobby/css/_lobby.setup.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'setup'; -@import 'app/hook-filter'; diff --git a/ui/lobby/css/_setup.scss b/ui/lobby/css/_setup.scss index 04e8ca5355b..a2c793f7a0d 100644 --- a/ui/lobby/css/_setup.scss +++ b/ui/lobby/css/_setup.scss @@ -1,15 +1,11 @@ $c-setup: $c-secondary; $c-slider: $c-setup; -.game-setup#modal-wrap { - display: block; +.game-setup { width: 30em; - text-align: center; + padding: 0; - > div { - padding: 0; - max-height: 96vh; - } + text-align: center; h2 { margin: 1.5rem 0; @@ -55,11 +51,6 @@ $c-slider: $c-setup; .label-select { @extend %flex-center; - &.variant { - margin-bottom: 1em; - padding-bottom: 0; - } - label { flex: 0 0 33%; text-align: right; @@ -91,45 +82,9 @@ $c-slider: $c-setup; border-radius: 0.5em; } - .range { - padding-top: 1em; - - span { - font-weight: bold; - } - - input { - font-size: 1.5em; - margin-top: 0.5em; - padding: 0; - width: 90%; - } - } - - .rating-range { - @extend %flex-center-nowrap; - justify-content: center; - .rating-min, - .rating-max { - flex: 0 0 7ch; - } - input { - width: 30%; - padding: 0.6em 0; - } - } - - .ratings { - padding: 1em; - width: 100%; - text-align: center; - strong { - margin-#{$end-direction}: 0.25em; - } - } - .color-submits { display: flex; + flex-flow: row nowrap; align-items: flex-end; justify-content: center; margin: 1em auto; @@ -182,4 +137,48 @@ $c-slider: $c-setup; margin: 10px auto 20px auto; } } + + .label-select { + &.variant { + margin-bottom: 1em; + padding-bottom: 0; + } + } + + .range { + padding-top: 1em; + + span { + font-weight: bold; + } + + input { + font-size: 1.5em; + margin-top: 0.5em; + padding: 0; + width: 90%; + } + } + + .rating-range { + @extend %flex-center-nowrap; + justify-content: center; + .rating-min, + .rating-max { + flex: 0 0 7ch; + } + input { + width: 30%; + padding: 0.6em 0; + } + } + + .ratings { + padding: 1em; + width: 100%; + text-align: center; + strong { + margin-#{$end-direction}: 0.25em; + } + } } diff --git a/ui/lobby/css/app/_app.scss b/ui/lobby/css/app/_app.scss index db34b0a38eb..8bc5d3d450b 100644 --- a/ui/lobby/css/app/_app.scss +++ b/ui/lobby/css/app/_app.scss @@ -1,5 +1,6 @@ @import 'pool'; @import 'hook-chart'; +@import 'hook-filters'; @import 'hook-list'; .lobby__app { @@ -86,8 +87,3 @@ padding: 0.4em; } } - -.hook__filters { - // will be overridden by _hook-filter.scss once it's loaded - display: none; -} diff --git a/ui/lobby/css/app/_hook-filter.scss b/ui/lobby/css/app/_hook-filters.scss similarity index 100% rename from ui/lobby/css/app/_hook-filter.scss rename to ui/lobby/css/app/_hook-filters.scss diff --git a/ui/lobby/css/build/_lobby.setup.scss b/ui/lobby/css/build/_lobby.setup.scss index bda1499462d..32edeb35f2c 100644 --- a/ui/lobby/css/build/_lobby.setup.scss +++ b/ui/lobby/css/build/_lobby.setup.scss @@ -1,5 +1,4 @@ @import '../../../common/css/plugin'; @import '../../../common/css/form/range'; @import '../../../common/css/form/radio'; -@import '../../../common/css/component/modal'; -@import '../lobby.setup'; +@import '../setup'; diff --git a/ui/lobby/src/view/realTime/filter.ts b/ui/lobby/src/view/realTime/filter.ts index de4d3d71fcc..a81abdd2dad 100644 --- a/ui/lobby/src/view/realTime/filter.ts +++ b/ui/lobby/src/view/realTime/filter.ts @@ -87,7 +87,6 @@ export const render = (ctrl: LobbyController) => insert(vnode) { const el = vnode.elm as FilterNode; if (el.filterLoaded) return; - lichess.loadCssPath('lobby.setup'); xhr.text('/setup/filter').then(html => { el.innerHTML = html; el.filterLoaded = true; diff --git a/ui/lobby/src/view/setup/modal.ts b/ui/lobby/src/view/setup/modal.ts index 8dab0dd5c3c..0a68849df78 100644 --- a/ui/lobby/src/view/setup/modal.ts +++ b/ui/lobby/src/view/setup/modal.ts @@ -1,5 +1,5 @@ import { MaybeVNode } from 'common/snabbdom'; -import { snabModal } from 'common/modal'; +import { snabDialog } from 'common/dialog'; import LobbyController from '../../ctrl'; import hookContent from './hookContent'; import friendContent from './friendContent'; @@ -15,10 +15,10 @@ export default function setupModal(ctrl: LobbyController): MaybeVNode { const { setupCtrl } = ctrl; if (!setupCtrl.gameType) return null; const renderContent = gameTypeToRenderer[setupCtrl.gameType]; - return snabModal({ + return snabDialog({ class: 'game-setup', - onInsert: () => lichess.loadCssPath('lobby.setup'), + cssPath: 'lobby.setup', onClose: setupCtrl.closeModal, - content: renderContent(ctrl), + vnodes: renderContent(ctrl), }); } diff --git a/ui/puzzle/css/_keyboard.scss b/ui/puzzle/css/_keyboard.scss deleted file mode 100644 index 290175182ee..00000000000 --- a/ui/puzzle/css/_keyboard.scss +++ /dev/null @@ -1,3 +0,0 @@ -.keyboard-help { - @extend %help-modal; -} diff --git a/ui/puzzle/css/build/_puzzle.keyboard.scss b/ui/puzzle/css/build/_puzzle.keyboard.scss deleted file mode 100644 index 4939f1a00af..00000000000 --- a/ui/puzzle/css/build/_puzzle.keyboard.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/plugin'; -@import '../../../common/css/component/help'; -@import '../keyboard'; diff --git a/ui/puzzle/css/build/_puzzle.scss b/ui/puzzle/css/build/_puzzle.scss index 4ea12d1f94e..99d9bbbbace 100644 --- a/ui/puzzle/css/build/_puzzle.scss +++ b/ui/puzzle/css/build/_puzzle.scss @@ -6,7 +6,6 @@ @import '../../../common/css/component/color-icon'; @import '../../../common/css/component/fbt'; @import '../../../common/css/component/glowing'; -@import '../../../common/css/component/modal'; @import '../../../common/css/component/zen-toggle'; @import '../../../common/css/form/cmn-toggle'; @import '../../../common/css/base/scrollbar'; diff --git a/ui/puzzle/css/build/puzzle.keyboard.ltr.dark.scss b/ui/puzzle/css/build/puzzle.keyboard.ltr.dark.scss deleted file mode 100644 index bf35ebfcf76..00000000000 --- a/ui/puzzle/css/build/puzzle.keyboard.ltr.dark.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/dark'; -@import 'puzzle.keyboard'; diff --git a/ui/puzzle/css/build/puzzle.keyboard.ltr.light.scss b/ui/puzzle/css/build/puzzle.keyboard.ltr.light.scss deleted file mode 100644 index 7504ea63eaa..00000000000 --- a/ui/puzzle/css/build/puzzle.keyboard.ltr.light.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/light'; -@import 'puzzle.keyboard'; diff --git a/ui/puzzle/css/build/puzzle.keyboard.ltr.transp.scss b/ui/puzzle/css/build/puzzle.keyboard.ltr.transp.scss deleted file mode 100644 index ae702fdfbb0..00000000000 --- a/ui/puzzle/css/build/puzzle.keyboard.ltr.transp.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/transp'; -@import 'puzzle.keyboard'; diff --git a/ui/puzzle/css/build/puzzle.keyboard.rtl.dark.scss b/ui/puzzle/css/build/puzzle.keyboard.rtl.dark.scss deleted file mode 100644 index 7c3f195fb28..00000000000 --- a/ui/puzzle/css/build/puzzle.keyboard.rtl.dark.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/dark'; -@import 'puzzle.keyboard'; diff --git a/ui/puzzle/css/build/puzzle.keyboard.rtl.light.scss b/ui/puzzle/css/build/puzzle.keyboard.rtl.light.scss deleted file mode 100644 index 507fcba50ef..00000000000 --- a/ui/puzzle/css/build/puzzle.keyboard.rtl.light.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/light'; -@import 'puzzle.keyboard'; diff --git a/ui/puzzle/css/build/puzzle.keyboard.rtl.transp.scss b/ui/puzzle/css/build/puzzle.keyboard.rtl.transp.scss deleted file mode 100644 index 352c3051a4d..00000000000 --- a/ui/puzzle/css/build/puzzle.keyboard.rtl.transp.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/transp'; -@import 'puzzle.keyboard'; diff --git a/ui/puzzle/src/keyboard.ts b/ui/puzzle/src/keyboard.ts index 7f164012cef..59ec666fd01 100644 --- a/ui/puzzle/src/keyboard.ts +++ b/ui/puzzle/src/keyboard.ts @@ -1,9 +1,6 @@ import * as control from './control'; -import * as xhr from 'common/xhr'; import { Controller, KeyboardController } from './interfaces'; -import { h, VNode } from 'snabbdom'; -import { snabModal } from 'common/modal'; -import { spinnerVdom as spinner } from 'common/spinner'; +import { snabDialog } from 'common/dialog'; export default (ctrl: KeyboardController) => lichess.mousetrap @@ -36,16 +33,9 @@ export default (ctrl: KeyboardController) => .bind('f', ctrl.flip) .bind('n', ctrl.nextPuzzle); -export const view = (ctrl: Controller): VNode => - snabModal({ - class: 'keyboard-help', - onInsert: async ($wrap: Cash) => { - const [, html] = await Promise.all([ - lichess.loadCssPath('puzzle.keyboard'), - xhr.text(xhr.url('/training/help', {})), - ]); - $wrap.find('.scrollable').html(html); - }, +export const view = (ctrl: Controller) => + snabDialog({ + class: 'help', + htmlUrl: '/training/help', onClose: () => ctrl.keyboardHelp(false), - content: [h('div.scrollable', spinner())], }); diff --git a/ui/round/css/_keyboard.scss b/ui/round/css/_keyboard.scss deleted file mode 100644 index 290175182ee..00000000000 --- a/ui/round/css/_keyboard.scss +++ /dev/null @@ -1,3 +0,0 @@ -.keyboard-help { - @extend %help-modal; -} diff --git a/ui/round/css/build/_round.keyboard.scss b/ui/round/css/build/_round.keyboard.scss deleted file mode 100644 index 4939f1a00af..00000000000 --- a/ui/round/css/build/_round.keyboard.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/plugin'; -@import '../../../common/css/component/help'; -@import '../keyboard'; diff --git a/ui/round/css/build/_round.scss b/ui/round/css/build/_round.scss index 8a0d65f05e8..fc2a894a451 100644 --- a/ui/round/css/build/_round.scss +++ b/ui/round/css/build/_round.scss @@ -11,7 +11,6 @@ @import '../../../common/css/component/context-streamer'; @import '../../../common/css/component/zen-toggle'; @import '../../../common/css/form/cmn-toggle'; -@import '../../../common/css/component/modal'; @import '../../../chess/css/variant-style'; @import '../../../chess/css/promotion'; @import '../../../chess/css/blindfold'; diff --git a/ui/round/css/build/round.keyboard.ltr.dark.scss b/ui/round/css/build/round.keyboard.ltr.dark.scss deleted file mode 100644 index 75ce08cc2c1..00000000000 --- a/ui/round/css/build/round.keyboard.ltr.dark.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/dark'; -@import 'round.keyboard'; diff --git a/ui/round/css/build/round.keyboard.ltr.light.scss b/ui/round/css/build/round.keyboard.ltr.light.scss deleted file mode 100644 index 94fe74fceea..00000000000 --- a/ui/round/css/build/round.keyboard.ltr.light.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/light'; -@import 'round.keyboard'; diff --git a/ui/round/css/build/round.keyboard.ltr.transp.scss b/ui/round/css/build/round.keyboard.ltr.transp.scss deleted file mode 100644 index 847ab3f953b..00000000000 --- a/ui/round/css/build/round.keyboard.ltr.transp.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/transp'; -@import 'round.keyboard'; diff --git a/ui/round/css/build/round.keyboard.rtl.dark.scss b/ui/round/css/build/round.keyboard.rtl.dark.scss deleted file mode 100644 index 6fa5bc187bf..00000000000 --- a/ui/round/css/build/round.keyboard.rtl.dark.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/dark'; -@import 'round.keyboard'; diff --git a/ui/round/css/build/round.keyboard.rtl.light.scss b/ui/round/css/build/round.keyboard.rtl.light.scss deleted file mode 100644 index c787ad33e2b..00000000000 --- a/ui/round/css/build/round.keyboard.rtl.light.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/light'; -@import 'round.keyboard'; diff --git a/ui/round/css/build/round.keyboard.rtl.transp.scss b/ui/round/css/build/round.keyboard.rtl.transp.scss deleted file mode 100644 index 08210346354..00000000000 --- a/ui/round/css/build/round.keyboard.rtl.transp.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/transp'; -@import 'round.keyboard'; diff --git a/ui/round/src/keyboard.ts b/ui/round/src/keyboard.ts index 54b4e473450..9d0f3cda0ab 100644 --- a/ui/round/src/keyboard.ts +++ b/ui/round/src/keyboard.ts @@ -1,8 +1,6 @@ import RoundController from './ctrl'; -import { h, VNode } from 'snabbdom'; -import * as xhr from 'common/xhr'; -import { snabModal } from 'common/modal'; -import { spinnerVdom as spinner } from 'common/spinner'; +import { VNode } from 'snabbdom'; +import { snabDialog } from 'common/dialog'; export const prev = (ctrl: RoundController) => ctrl.userJump(ctrl.ply - 1); @@ -34,18 +32,11 @@ export const init = (ctrl: RoundController) => }); export const view = (ctrl: RoundController): VNode => - snabModal({ - class: 'keyboard-help', - onInsert: async ($wrap: Cash) => { - const [, html] = await Promise.all([ - lichess.loadCssPath('round.keyboard'), - xhr.text(xhr.url('/round/help', {})), - ]); - $wrap.find('.scrollable').html(html); - }, + snabDialog({ + class: 'help', + htmlUrl: '/round/help', onClose() { ctrl.keyboardHelp = false; ctrl.redraw(); }, - content: [h('div.scrollable', spinner())], }); diff --git a/ui/round/src/socket.ts b/ui/round/src/socket.ts index 19ffd3fe831..b290a8db6e0 100644 --- a/ui/round/src/socket.ts +++ b/ui/round/src/socket.ts @@ -1,6 +1,6 @@ import * as game from 'game'; import throttle from 'common/throttle'; -import modal from 'common/modal'; +import { domDialog } from 'common/dialog'; import * as xhr from './xhr'; import RoundController from './ctrl'; import { defined } from 'common'; @@ -145,11 +145,10 @@ export function make(send: SocketSend, ctrl: RoundController): RoundSocket { } }, simulEnd(simul: game.Simul) { - lichess.loadCssPath('modal'); - modal({ - content: $( - ``, - ), + domDialog({ + htmlText: + '

Simul complete!



' + + `Back to ${simul.name} simul
`, }); }, }; diff --git a/ui/simul/css/build/_simul.show.scss b/ui/simul/css/build/_simul.show.scss index c6f88d31289..c37440c4864 100644 --- a/ui/simul/css/build/_simul.show.scss +++ b/ui/simul/css/build/_simul.show.scss @@ -2,7 +2,6 @@ @import '../../../common/css/component/quote'; @import '../../../common/css/component/slist'; @import '../../../common/css/component/now-playing'; -@import '../../../common/css/component/modal'; @import '../../../common/css/component/continue-with'; @import '../../../common/css/component/context-streamer'; @import '../../../chat/css/chat'; diff --git a/ui/simul/src/view/created.ts b/ui/simul/src/view/created.ts index 9b46b79238f..bee5ece02bc 100644 --- a/ui/simul/src/view/created.ts +++ b/ui/simul/src/view/created.ts @@ -5,7 +5,7 @@ import SimulCtrl from '../ctrl'; import { Applicant } from '../interfaces'; import xhr from '../xhr'; import * as util from './util'; -import modal from 'common/modal'; +import { domDialog } from 'common/dialog'; export default function (showText: (ctrl: SimulCtrl) => MaybeVNode) { return (ctrl: SimulCtrl) => { @@ -51,17 +51,12 @@ export default function (showText: (ctrl: SimulCtrl) => MaybeVNode) { ? bind('click', () => { if (ctrl.data.variants.length === 1) xhr.join(ctrl.data.id, ctrl.data.variants[0].key); - else { - modal({ - content: $('.simul .continue-with'), - onInsert($wrap) { - $wrap.find('button').on('click', function (this: HTMLElement) { - modal.close(); - xhr.join(ctrl.data.id, $(this).data('variant')); - }); - }, + else + domDialog({ + show: 'modal', + cash: $('.simul .continue-with'), + onClose: dlg => xhr.join(ctrl.data.id, $(dlg.view).data('variant')), }); - } }) : {}, }, diff --git a/ui/site/css/build/_forum.scss b/ui/site/css/build/_forum.scss index ca69bffe9aa..f5b2d197816 100644 --- a/ui/site/css/build/_forum.scss +++ b/ui/site/css/build/_forum.scss @@ -1,7 +1,6 @@ @import '../../../common/css/plugin'; @import '../../../common/css/component/slist'; @import '../../../common/css/component/pagination'; -@import '../../../common/css/component/modal'; @import '../../../common/css/component/lichess-pgn-viewer'; @import '../../../common/css/form/form3'; @import '../../../common/css/form/cmn-toggle'; diff --git a/ui/site/css/build/_linkPopup.scss b/ui/site/css/build/_linkPopup.scss index 357415e1644..12da52209c6 100644 --- a/ui/site/css/build/_linkPopup.scss +++ b/ui/site/css/build/_linkPopup.scss @@ -1,3 +1,2 @@ @import '../../../common/css/plugin'; -@import '../../../common/css/component/modal'; @import '../linkPopup'; diff --git a/ui/site/css/build/_mod.publicChats.scss b/ui/site/css/build/_mod.publicChats.scss index f5618beb60e..4444a1d5c9a 100644 --- a/ui/site/css/build/_mod.publicChats.scss +++ b/ui/site/css/build/_mod.publicChats.scss @@ -1,6 +1,5 @@ @import '../../../common/css/plugin'; @import '../../../common/css/base/scrollbar'; @import '../../../common/css/component/slist'; -@import '../../../common/css/component/modal'; @import '../../../common/css/component/continue-with'; @import '../mod/publicChats'; diff --git a/ui/site/css/build/_modal.scss b/ui/site/css/build/_modal.scss deleted file mode 100644 index 35b889eb52b..00000000000 --- a/ui/site/css/build/_modal.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '../../../common/css/plugin'; -@import '../../../common/css/component/modal'; diff --git a/ui/site/css/build/modal.ltr.dark.scss b/ui/site/css/build/modal.ltr.dark.scss deleted file mode 100644 index bfb8990b76c..00000000000 --- a/ui/site/css/build/modal.ltr.dark.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/dark'; -@import 'modal'; diff --git a/ui/site/css/build/modal.ltr.light.scss b/ui/site/css/build/modal.ltr.light.scss deleted file mode 100644 index 79f1a214a06..00000000000 --- a/ui/site/css/build/modal.ltr.light.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/light'; -@import 'modal'; diff --git a/ui/site/css/build/modal.ltr.transp.scss b/ui/site/css/build/modal.ltr.transp.scss deleted file mode 100644 index d4f6cb7f1e1..00000000000 --- a/ui/site/css/build/modal.ltr.transp.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/ltr'; -@import '../../../common/css/theme/transp'; -@import 'modal'; diff --git a/ui/site/css/build/modal.rtl.dark.scss b/ui/site/css/build/modal.rtl.dark.scss deleted file mode 100644 index b80887026bc..00000000000 --- a/ui/site/css/build/modal.rtl.dark.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/dark'; -@import 'modal'; diff --git a/ui/site/css/build/modal.rtl.light.scss b/ui/site/css/build/modal.rtl.light.scss deleted file mode 100644 index b402d9819d2..00000000000 --- a/ui/site/css/build/modal.rtl.light.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/light'; -@import 'modal'; diff --git a/ui/site/css/build/modal.rtl.transp.scss b/ui/site/css/build/modal.rtl.transp.scss deleted file mode 100644 index 4dd8cc24339..00000000000 --- a/ui/site/css/build/modal.rtl.transp.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../common/css/dir/rtl'; -@import '../../../common/css/theme/transp'; -@import 'modal'; diff --git a/ui/site/package.json b/ui/site/package.json index c8f677524b1..2d35523ffb5 100644 --- a/ui/site/package.json +++ b/ui/site/package.json @@ -77,19 +77,23 @@ "copy": [ { "src": "node_modules/hopscotch/dist/**", - "dest": "../../public/vendor/hopscotch/dist" + "dest": "../../public/npm/hopscotch/dist" }, { "src": "node_modules/highcharts/*.js", - "dest": "../../public/vendor/highcharts-4.2.5" + "dest": "../../public/npm/highcharts-4.2.5" }, { "src": "node_modules/@yaireo/tagify/dist/tagify.min.js", - "dest": "../../public/vendor/tagify" + "dest": "../../public/npm/tagify" }, { "src": "node_modules/stockfish*/*.{js,wasm}", - "dest": "../../public/vendor" + "dest": "../../public/npm" + }, + { + "src": "../../node_modules/chessground/dist/chessground.min.js", + "dest": "../../public/npm" } ] } diff --git a/ui/site/src/component/assets.ts b/ui/site/src/component/assets.ts index fc804891b1b..5f122ae7124 100644 --- a/ui/site/src/component/assets.ts +++ b/ui/site/src/component/assets.ts @@ -13,7 +13,7 @@ export const loadCss = (url: string, media?: 'dark' | 'light'): Promise => if (!loadedCss.has(url)) { const el = document.createElement('link'); el.rel = 'stylesheet'; - el.href = assetUrl(url); + el.href = assetUrl(lichess.debug ? `${url}?_=${Date.now()}` : url); if (media) el.media = `(prefers-color-scheme: ${media})`; loadedCss.set( url, @@ -35,8 +35,7 @@ export const loadCssPath = async (key: string): Promise => { ); if (theme === 'system') { if (supportsSystemTheme()) { - await load('dark', 'dark'); - await load('light', 'light'); + await Promise.all([load('dark', 'dark'), load('light', 'light')]); } else { await load('dark'); } @@ -66,8 +65,8 @@ export const userComplete = async (opts: UserCompleteOpts): Promise { - loadCss('vendor/hopscotch/dist/css/hopscotch.min.css'); - return loadIife('vendor/hopscotch/dist/js/hopscotch.min.js', { + loadCss('npm/hopscotch/dist/css/hopscotch.min.css'); + return loadIife('npm/hopscotch/dist/js/hopscotch.min.js', { noVersion: true, }); }; diff --git a/ui/site/src/forum.ts b/ui/site/src/forum.ts index c48f4df7b66..3ef0b43b00e 100644 --- a/ui/site/src/forum.ts +++ b/ui/site/src/forum.ts @@ -1,23 +1,20 @@ import * as xhr from 'common/xhr'; -import modal from 'common/modal'; +import { domDialog } from 'common/dialog'; lichess.load.then(() => { $('.forum') .on('click', 'a.delete', function (this: HTMLAnchorElement) { const link = this; - modal({ - content: $('.forum-delete-modal'), - onInsert($wrap) { - $wrap - .find('form') - .attr('action', link.href) - .on('submit', function (this: HTMLFormElement, e: Event) { - e.preventDefault(); - xhr.formToXhr(this); - modal.close(); - $(link).closest('.forum-post').hide(); - }); - }, + domDialog({ + cash: $('.forum-delete-modal'), + attrs: { view: { action: link.href } }, + }).then(dlg => { + $('form', dlg.view).on('submit', () => { + //e.preventDefault(); + xhr.text(link.href, { method: 'post' }); + $(link).closest('.forum-post').hide(); + }); + dlg.showModal(); }); return false; }) diff --git a/ui/site/src/gameSearch.ts b/ui/site/src/gameSearch.ts index 968763dbc81..b3b6feafb07 100644 --- a/ui/site/src/gameSearch.ts +++ b/ui/site/src/gameSearch.ts @@ -1,5 +1,3 @@ -export {}; // for tsc isolatedModules - lichess.load.then(() => { const form = document.querySelector('.search__form') as HTMLFormElement, $form = $(form), diff --git a/ui/site/src/publicChats.ts b/ui/site/src/publicChats.ts index 3a50abe21a5..2d49cd5d117 100644 --- a/ui/site/src/publicChats.ts +++ b/ui/site/src/publicChats.ts @@ -1,5 +1,5 @@ import { text, form } from 'common/xhr'; -import modal from 'common/modal'; +import { domDialog } from 'common/dialog'; lichess.load.then(() => { let autoRefreshEnabled = true; @@ -40,27 +40,25 @@ lichess.load.then(() => { $('#communication').on('click', '.line:not(.lichess)', function (this: HTMLDivElement) { const $l = $(this); - modal({ - content: $('.timeout-modal'), - onInsert($wrap) { - $wrap.find('.username').text($l.find('.user-link').text()); - $wrap.find('.text').text($l.text().split(' ').slice(1).join(' ')); - $wrap.on('click', '.button', function (this: HTMLButtonElement) { - const roomId = $l.parents('.game').data('room'); - const chan = $l.parents('.game').data('chan'); - text('/mod/public-chat/timeout', { - method: 'post', - body: form({ - roomId, - chan, - userId: $wrap.find('.username').text().toLowerCase(), - reason: this.value, - text: $wrap.find('.text').text(), - }), - }).then(_ => setTimeout(reloadNow, 1000)); - modal.close(); - }); - }, + domDialog({ cash: $('.timeout-modal') }).then(dlg => { + $('.username', dlg.view).text($l.find('.user-link').text()); + $('.text', dlg.view).text($l.text().split(' ').slice(1).join(' ')); + $('.button', dlg.view).on('click', function (this: HTMLButtonElement) { + const roomId = $l.parents('.game').data('room'); + const chan = $l.parents('.game').data('chan'); + text('/mod/public-chat/timeout', { + method: 'post', + body: form({ + roomId, + chan, + userId: $('.username', dlg.view).text().toLowerCase(), + reason: this.value, + text: $('.text', dlg.view).text(), + }), + }).then(_ => setTimeout(reloadNow, 1000)); + dlg.close(); + }); + dlg.showModal(); }); }); }; diff --git a/ui/site/src/site.lichess.globals.ts b/ui/site/src/site.lichess.globals.ts index 3c9300e0a32..288804379f0 100644 --- a/ui/site/src/site.lichess.globals.ts +++ b/ui/site/src/site.lichess.globals.ts @@ -30,9 +30,11 @@ import { format as timeago, formatter as dateFormat } from './component/timeago' import watchers from './component/watchers'; import { Chessground } from 'chessground'; +declare const __debug__: boolean; + export default () => { - window.$as = (cash: Cash) => cash[0] as T; const l = window.lichess; + l.debug = __debug__; l.StrongSocket = StrongSocket; l.mousetrap = new Mousetrap(document); l.requestIdleCallback = requestIdleCallback; diff --git a/ui/site/src/site.ts b/ui/site/src/site.ts index 18d18a19c62..49f4b919c1d 100644 --- a/ui/site/src/site.ts +++ b/ui/site/src/site.ts @@ -18,9 +18,10 @@ import { reload } from './component/reload'; import { requestIdleCallback } from './component/functions'; import { userComplete } from './component/assets'; import { siteTrans } from './component/trans'; -import { trapFocus } from 'common/modal'; import { isIOS } from 'common/mobile'; +window.$as = (cashOrHtml: Cash | string) => + (typeof cashOrHtml === 'string' ? $(cashOrHtml) : cashOrHtml)[0] as T; exportLichessGlobals(); lichess.info = info; @@ -116,17 +117,6 @@ lichess.load.then(() => { return false; }); - $('body').on('focusin', trapFocus); - - lichess.mousetrap.bind('esc', () => { - const $oc = $('#modal-wrap .close'); - if ($oc.length) $oc.trigger('click'); - else { - const $input = $(':focus'); - if ($input.length) $input.trigger('blur'); - } - }); - /* Edge randomly fails to rasterize SVG on page load * A different SVG must be loaded so a new image can be rasterized */ if (navigator.userAgent.includes('Edge/')) diff --git a/ui/tournament/css/build/_tournament.show.team-battle.scss b/ui/tournament/css/build/_tournament.show.team-battle.scss index cb8cc61091a..93ed20ec9ba 100644 --- a/ui/tournament/css/build/_tournament.show.team-battle.scss +++ b/ui/tournament/css/build/_tournament.show.team-battle.scss @@ -1,3 +1,2 @@ @import 'tournament.show'; -@import '../../../common/css/component/modal'; @import '../team-battle'; diff --git a/ui/tournament/src/view/battle.ts b/ui/tournament/src/view/battle.ts index da6ff2e966b..9437149ed14 100644 --- a/ui/tournament/src/view/battle.ts +++ b/ui/tournament/src/view/battle.ts @@ -3,7 +3,7 @@ import { bind, MaybeVNode } from 'common/snabbdom'; import { playerName } from './util'; import { h, VNode } from 'snabbdom'; import { TeamBattle, RankedTeam } from '../interfaces'; -import { snabModal } from 'common/modal'; +import { snabDialog } from 'common/dialog'; export function joinWithTeamSelector(ctrl: TournamentController) { const tb = ctrl.data.teamBattle!; @@ -11,16 +11,17 @@ export function joinWithTeamSelector(ctrl: TournamentController) { ctrl.joinWithTeamSelector = false; ctrl.redraw(); }; - return snabModal({ + return snabDialog({ class: 'team-battle__choice', - onInsert($el) { - $el.on('click', '.team-picker__team', e => { + onInsert(dlg) { + $('.team-picker__team', dlg.view).on('click', e => { ctrl.join(e.target.dataset['id']); - onClose(); + dlg.close(); }); + dlg.showModal(); }, onClose, - content: [ + vnodes: [ h('div.team-picker', [ h('h2', 'Pick your team'), h('br'), diff --git a/ui/voice/css/_voice.scss b/ui/voice/css/_voice.scss index 4f5b8169fe8..1cb348acca9 100644 --- a/ui/voice/css/_voice.scss +++ b/ui/voice/css/_voice.scss @@ -154,12 +154,13 @@ button#microphone-button { .active { @extend %active-primary; } +} - &__device { - flex-flow: row nowrap; - select { - width: 0; - flex: 1; - } +.voice-choices__device { + flex-flow: row nowrap; + + select { + width: 0; + flex: 1; } } diff --git a/ui/voice/css/_voiceMove.help.scss b/ui/voice/css/_voiceMove.help.scss index d334c2962bf..424a82af00c 100644 --- a/ui/voice/css/_voiceMove.help.scss +++ b/ui/voice/css/_voiceMove.help.scss @@ -1,6 +1,4 @@ .voice-move-help { - @extend %help-modal; - td.tips li { list-style: disc; font-size: 1.1em; @@ -10,15 +8,17 @@ a { margin-#{$start-direction}: 0.25em; } - &#modal-wrap { - max-width: 1000px; - &.bigger { - width: 100%; - } - } + table { align-self: start; } + + voice { + display: block; + font-weight: bold; + color: $c-font; + } + .commands { td:first-child { padding-#{$start-direction}: 2em; @@ -29,11 +29,13 @@ flex-direction: column; } } - #all-phrases-button { + + .all-phrases-button { margin-top: 1em; padding: 0.6em 1.2em; } - #big-table { + + .big-table { td { padding: 0 1em; white-space: nowrap; @@ -49,8 +51,3 @@ } } } -voice { - display: block; - font-weight: bold; - color: $c-font; -} diff --git a/ui/voice/css/build/_voiceMove.help.scss b/ui/voice/css/build/_voiceMove.help.scss index 026eea20c88..fef50a3d017 100644 --- a/ui/voice/css/build/_voiceMove.help.scss +++ b/ui/voice/css/build/_voiceMove.help.scss @@ -1,4 +1,3 @@ @import '../../../common/css/plugin'; -@import '../../../common/css/component/modal'; @import '../../../common/css/component/help'; @import '../voiceMove.help'; diff --git a/ui/voice/src/view.ts b/ui/voice/src/view.ts index 45f47c47156..a1e0132b02c 100644 --- a/ui/voice/src/view.ts +++ b/ui/voice/src/view.ts @@ -1,8 +1,7 @@ import { h } from 'snabbdom'; import * as licon from 'common/licon'; import { onInsert, bind } from 'common/snabbdom'; -import { snabModal } from 'common/modal'; -import { spinnerVdom as spinner } from 'common/spinner'; +import { snabDialog, type Dialog } from 'common/dialog'; import * as xhr from 'common/xhr'; import { onClickAway } from 'common'; import { Entry, VoiceCtrl } from './interfaces'; @@ -148,8 +147,8 @@ function voiceDisable() { } function renderHelpModal(ctrl: VoiceCtrl) { - const showMoveList = (el: Cash) => { - let html = ''; + const showMoveList = (dlg: Dialog) => { + let html = '
'; const all = ctrl .module() @@ -165,32 +164,30 @@ function renderHelpModal(ctrl: VoiceCtrl) { html += ''; } html += '
'; - el.find('.scrollable').html(html); + dlg.view.innerHTML = html; + if (!dlg.open) dlg.showModal(); }; - return snabModal({ - class: `voice-move-help`, - content: [h('div.scrollable', spinner())], - onClose: () => ctrl.showHelp(false), - onInsert: async el => { - const [, grammar, html] = await Promise.all([ - lichess.loadCssPath('voiceMove.help'), - ctrl.moduleId !== 'coords' - ? xhr.jsonSimple(lichess.assetUrl(`compiled/grammar/${ctrl.moduleId}-${ctrl.lang()}.json`)) - : Promise.resolve({ entries: [] }), - xhr.text(xhr.url(`/help/voice/${ctrl.moduleId}`, {})), - ]); + return snabDialog({ + class: 'help.voice-move-help', + htmlUrl: `/help/voice/${ctrl.moduleId}`, + cssPath: 'voiceMove.help', + onClose: () => ctrl.showHelp(false), + onInsert: async dlg => { if (ctrl.showHelp() === 'list') { - showMoveList(el); + showMoveList(dlg); return; } - // using lexicon instead of crowdin translations for moves/commands - el.find('.scrollable').html(html); + const grammar = + ctrl.moduleId === 'coords' + ? [] + : await xhr.jsonSimple(lichess.assetUrl(`compiled/grammar/${ctrl.moduleId}-${ctrl.lang()}.json`)); + const valToWord = (val: string, phonetic: boolean) => grammar.entries.find( (e: Entry) => (e.val ?? e.tok) === val && (!phonetic || e.tags?.includes('phonetic')), )?.in; - $('.val-to-word', el).each(function (this: HTMLElement) { + $('.val-to-word', dlg.view).each(function (this: HTMLElement) { const tryPhonetic = (val: string) => (this.classList.contains('phonetic') && valToWord(val, true)) || valToWord(val, false); this.innerText = this.innerText @@ -198,7 +195,8 @@ function renderHelpModal(ctrl: VoiceCtrl) { .map(v => tryPhonetic(v)) .join(' '); }); - el.find('#all-phrases-button').on('click', () => showMoveList(el)); + $('.all-phrases-button', dlg.view).on('click', () => showMoveList(dlg)); + dlg.showModal(); }, }); }