/* eslint-disable react-refresh/only-export-components */ import { getAlphabeticId } from '@hydrooj/utils/lib/common'; import * as yaml from 'js-yaml'; import React from 'react'; import { HexColorInput, HexColorPicker } from 'react-colorful'; import { createPortal } from 'react-dom'; import { ActionDialog } from 'vj/components/dialog'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; import { i18n, pjax, request, tpl, } from 'vj/utils'; function Balloon({ tdoc, val }) { const [color, setColor] = React.useState(''); const [now, setNow] = React.useState(null); const [pickerTarget, setPickerTarget] = React.useState(null); const [pickerStyle, setPickerStyle] = React.useState({}); const updatePickerStyle = React.useCallback((target: HTMLElement) => { const row = target.closest('tr'); if (!row) return; const rect = row.getBoundingClientRect(); setPickerStyle({ position: 'fixed', left: rect.right, top: rect.top + rect.height / 2, transform: 'translateY(-50%)', zIndex: 10000, }); }, []); const showPicker = (pid: number, c: string, target: HTMLElement) => { setNow(pid); setColor(c); setPickerTarget(target); updatePickerStyle(target); }; React.useEffect(() => { if (!pickerTarget) return undefined; const update = () => updatePickerStyle(pickerTarget); window.addEventListener('resize', update); window.addEventListener('scroll', update, true); return () => { window.removeEventListener('resize', update); window.removeEventListener('scroll', update, true); }; }, [pickerTarget, updatePickerStyle]); return (
{tdoc.pids.map((pid) => { const { color: c, name } = val[+pid]; return ( ); })}
{i18n('Problem')} {i18n('Color')} {i18n('Name')}
{now === +pid ? ({getAlphabeticId(tdoc.pids.indexOf(+pid))}) : ({getAlphabeticId(tdoc.pids.indexOf(+pid))})} showPicker(+pid, c, e.currentTarget)} onChange={(e) => { val[+pid].color = e; setColor(e); }} /> showPicker(+pid, c, e.currentTarget)} onChange={(e) => { val[+pid].name = e.target.value; }} />
{now !== null && createPortal(
{ val[now].color = e; setColor(e); }} style={{ padding: '1rem' }} />
, document.body, )}
); } async function handleSetColor(tdoc) { const val = tdoc.balloon || {}; for (const pid of tdoc.pids) val[+pid] ||= { color: '#ffffff', name: '' }; Notification.info(i18n('Loading...')); const action = await new ActionDialog({ $body: tpl(<>

{i18n('Set Color')}

, true), }).open(); if (action !== 'ok') return; Notification.info(i18n('Updating...')); try { await request.post('', { operation: 'set_color', color: yaml.dump(val) }); } catch (e) { Notification.error(`${e.message} ${e.params?.[0]}`); } Notification.info(i18n('Successfully updated.')); if ($('[data-fragment-id="constest_balloon-tbody"]').length) pjax.request({ url: '', push: false }); else window.location.reload(); } const page = new NamedPage('contest_balloon', () => { const { tdoc } = UiContext; const beginAt = new Date(tdoc.beginAt).getTime(); const endAt = new Date(tdoc.endAt).getTime(); function update() { const now = Date.now(); if (beginAt <= now && now <= endAt) pjax.request({ url: '', push: false }); } $('[name="set_color"]').on('click', () => handleSetColor(tdoc)); setInterval(update, 60000); $(document).on('click', '[value="done"]', async (ev) => { ev.preventDefault(); const balloon = $(ev.currentTarget).data('balloon'); try { await request.post('', { balloon, operation: 'done' }); } catch (e) { Notification.error(`${e.message} ${e.params?.[0]}`); } Notification.info(i18n('Successfully updated.')); pjax.request({ url: '', push: false }); }); }); export default page;