mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
onsite-toolkit: add resolver (#706)
Co-authored-by: panda <panda_dtdyy@outlook.com>
This commit is contained in:
1 parent
d1e79f3a9c
commit
f888537757
7 files changed
+508
-4
No files matched your search
@@ -0,0 +1,280 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { animated, easings, useSprings } from '@react-spring/web';
|
||||||
|
import useKey from 'react-use/lib/useKey';
|
||||||
|
import {
|
||||||
|
addPage, NamedPage, React, ReactDOM,
|
||||||
|
} from '@hydrooj/ui-default';
|
||||||
|
import { ResolverInput } from '../interface';
|
||||||
|
|
||||||
|
async function scrollTo(offset) {
|
||||||
|
const fixedOffset = offset.toFixed();
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const onScroll = function () {
|
||||||
|
if (window.pageYOffset.toFixed() === fixedOffset) {
|
||||||
|
window.removeEventListener('scroll', onScroll);
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', onScroll);
|
||||||
|
onScroll();
|
||||||
|
window.scrollTo({
|
||||||
|
top: offset,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export interface DisplaySettings {
|
||||||
|
showAvatar: boolean;
|
||||||
|
showSchool: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props extends DisplaySettings {
|
||||||
|
data: ResolverInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
function status(problem) {
|
||||||
|
if (!problem) return 'untouched';
|
||||||
|
if (problem.pass) return 'ac';
|
||||||
|
if (!problem.old && !problem.frozen) return 'untouched';
|
||||||
|
if (problem.frozen) return 'frozen';
|
||||||
|
return 'failed';
|
||||||
|
}
|
||||||
|
|
||||||
|
function submissions(problem) {
|
||||||
|
const st = status(problem);
|
||||||
|
if (st === 'ac') { return `${problem.old}`; }
|
||||||
|
if (st === 'frozen') { return `${problem.old}+${problem.frozen}`; }
|
||||||
|
if (st === 'failed') { return problem.old; }
|
||||||
|
return String.fromCharCode('A'.charCodeAt(0) + problem.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function start(data: ResolverInput, options: DisplaySettings) {
|
||||||
|
$('title').text(`${data.name} - Resolver`);
|
||||||
|
$('.header .title').text(`${data.name}`);
|
||||||
|
const teams = data.teams.map((v) => ({
|
||||||
|
id: v.id,
|
||||||
|
rank: 0,
|
||||||
|
score: 0,
|
||||||
|
penalty: 0,
|
||||||
|
ranked: !v.exclude,
|
||||||
|
total: 0,
|
||||||
|
problems: data.problems.map((problem, idx) => ({
|
||||||
|
old: 0,
|
||||||
|
frozen: 0,
|
||||||
|
pass: false,
|
||||||
|
id: problem.id,
|
||||||
|
index: idx,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
const allSubmissions = data.submissions.sort((a, b) => a.time - b.time);
|
||||||
|
const allAc = allSubmissions.filter((i) => i.verdict === 'AC');
|
||||||
|
for (const submission of allSubmissions) {
|
||||||
|
const team = teams.find((v) => v.id === submission.team);
|
||||||
|
if (!team) continue;
|
||||||
|
const isFrozen = submission.time > data.frozen;
|
||||||
|
const problem = team.problems.find((i) => i.id === submission.problem);
|
||||||
|
if (!problem || problem.pass) continue;
|
||||||
|
team.total++;
|
||||||
|
if (isFrozen) problem.frozen += 1;
|
||||||
|
else {
|
||||||
|
if (submission.verdict === 'AC') {
|
||||||
|
problem.pass = true;
|
||||||
|
team.score += 1;
|
||||||
|
team.penalty += submission.time + problem.old * 20 * 60;
|
||||||
|
}
|
||||||
|
problem.old += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function MainList(props: Props) {
|
||||||
|
const [selectedTeam, setTeam] = React.useState('');
|
||||||
|
const [selectedProblem, setP] = React.useState<string | null>(null);
|
||||||
|
const [executeIdx, setExecuteIdx] = React.useState(0);
|
||||||
|
const [, setRenderC] = React.useState(0);
|
||||||
|
|
||||||
|
function processRank(source = teams) {
|
||||||
|
const clone = [...source];
|
||||||
|
clone.sort((a, b) => b.score - a.score || a.penalty - b.penalty || b.total - a.total);
|
||||||
|
let rank = 1;
|
||||||
|
for (const team of clone) {
|
||||||
|
if (team.ranked) {
|
||||||
|
team.rank = rank;
|
||||||
|
rank++;
|
||||||
|
} else {
|
||||||
|
team.rank = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clone.map((i) => source.indexOf(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = React.useRef(processRank());
|
||||||
|
|
||||||
|
const [springs, api] = useSprings(teams.length, (index) => ({
|
||||||
|
y: order.current.indexOf(index) * 80 - index * 80,
|
||||||
|
scale: 1,
|
||||||
|
zIndex: 0,
|
||||||
|
shadow: 1,
|
||||||
|
immediate: (key: string) => key === 'y' || key === 'zIndex',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const operations = {
|
||||||
|
async highlightTeam(teamId: string, scrollIdx: number) {
|
||||||
|
setP(null);
|
||||||
|
setTeam(teamId);
|
||||||
|
await scrollTo(scrollIdx * 80 - window.innerHeight + 241 + 40);
|
||||||
|
},
|
||||||
|
async highlightProblem(problemId: string) {
|
||||||
|
setP(problemId);
|
||||||
|
},
|
||||||
|
async revealProblem(teamId: string, problemId: string) {
|
||||||
|
const team = teams.find((i) => i.id === teamId);
|
||||||
|
const problem = team?.problems.find((i) => i.id === problemId);
|
||||||
|
if (!team || !problem) return;
|
||||||
|
if (allAc.find((s) => s.team === teamId && s.problem === problemId)) {
|
||||||
|
const sub = allSubmissions.filter((s) => s.team === teamId && s.problem === problemId);
|
||||||
|
let penalty = 0;
|
||||||
|
for (const s of sub) {
|
||||||
|
if (s.verdict !== 'AC') {
|
||||||
|
penalty += 20 * 60;
|
||||||
|
problem.old++;
|
||||||
|
} else {
|
||||||
|
penalty += s.time;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
team.penalty += penalty;
|
||||||
|
team.score += 1;
|
||||||
|
problem.pass = true;
|
||||||
|
problem.frozen = 0;
|
||||||
|
} else {
|
||||||
|
problem.old += problem.frozen;
|
||||||
|
problem.frozen = 0;
|
||||||
|
}
|
||||||
|
setP(null);
|
||||||
|
},
|
||||||
|
async updateRank() {
|
||||||
|
order.current = processRank();
|
||||||
|
api.start((index) => ({
|
||||||
|
y: order.current.indexOf(index) * 80 - index * 80,
|
||||||
|
scale: 1,
|
||||||
|
zIndex: 0,
|
||||||
|
shadow: 1,
|
||||||
|
config: {
|
||||||
|
easing: easings.steps(5),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculated = React.useMemo(() => {
|
||||||
|
window.scrollTo(0, document.body.scrollHeight);
|
||||||
|
const clone = JSON.parse(JSON.stringify(teams));
|
||||||
|
const ops: { name: string, args: any[] }[] = [];
|
||||||
|
function queueOperations(name: string, ...args: any[]) {
|
||||||
|
ops.push({ name, args });
|
||||||
|
}
|
||||||
|
let orders = processRank(clone);
|
||||||
|
for (let i = clone.length - 1; i > 0; i--) {
|
||||||
|
const team = clone[orders[i]];
|
||||||
|
queueOperations('highlightTeam', team.id, i);
|
||||||
|
for (const pinfo of data.problems) {
|
||||||
|
const problem = team.problems.find((idx) => idx.id === pinfo.id);
|
||||||
|
if (!problem || !problem.frozen || problem.pass) continue;
|
||||||
|
queueOperations('highlightProblem', pinfo.id);
|
||||||
|
queueOperations('revealProblem', team.id, pinfo.id);
|
||||||
|
// scroll to selected line
|
||||||
|
if (allAc.find((s) => s.team === team.id && s.problem === problem.id)) {
|
||||||
|
const sub = allSubmissions.filter((s) => s.team === team.id && s.problem === problem.id);
|
||||||
|
let penalty = 0;
|
||||||
|
for (const s of sub) {
|
||||||
|
if (s.verdict !== 'AC') {
|
||||||
|
penalty += 20 * 60;
|
||||||
|
problem.old++;
|
||||||
|
} else {
|
||||||
|
penalty += s.time;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
team.penalty += penalty;
|
||||||
|
team.score += 1;
|
||||||
|
problem.pass = true;
|
||||||
|
problem.frozen = 0;
|
||||||
|
queueOperations('updateRank');
|
||||||
|
const oldOrder = JSON.stringify(orders);
|
||||||
|
orders = processRank(clone);
|
||||||
|
if (oldOrder !== JSON.stringify(orders)) {
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
problem.old += problem.frozen;
|
||||||
|
problem.frozen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ops;
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
useKey('n', async () => {
|
||||||
|
const op = calculated[executeIdx];
|
||||||
|
if (!op) return;
|
||||||
|
setExecuteIdx(executeIdx + 1);
|
||||||
|
await operations[op.name](...op.args);
|
||||||
|
setRenderC((i) => i + 1);
|
||||||
|
}, {}, [executeIdx, calculated]);
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
{springs.map(({
|
||||||
|
zIndex, y,
|
||||||
|
}, i) => {
|
||||||
|
const team = teams[i];
|
||||||
|
const teamInfo = data.teams.find((idx) => idx.id === team.id);
|
||||||
|
if (!teams[i]) return <animated.div key={i}>Team {i} not found</animated.div>;
|
||||||
|
if (!teamInfo) return <animated.div key={i}>Team info for id {team.id} not found</animated.div>;
|
||||||
|
return <animated.div
|
||||||
|
key={i}
|
||||||
|
className="rank-list-item clearfix"
|
||||||
|
style={{
|
||||||
|
zIndex,
|
||||||
|
// boxShadow: shadow.to((s) => `rgba(0, 0, 0, 0.15) 0px ${s}px ${2 * s}px 0px`),
|
||||||
|
y,
|
||||||
|
...(selectedTeam === team.id ? {
|
||||||
|
backgroundColor: '#406b82',
|
||||||
|
} : {
|
||||||
|
background: 'transparent',
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
children={<>
|
||||||
|
<div className="rank">{team.rank === -1 ? '*' : team.rank}</div>
|
||||||
|
{props.showAvatar && <img className="avatar" src={`${teamInfo?.avatar}`} />}
|
||||||
|
<div className="content">
|
||||||
|
<div className="name">
|
||||||
|
{props.showSchool ? `${teamInfo.institution} - ` : ''}{teamInfo.name}
|
||||||
|
</div>
|
||||||
|
<div className="problems">
|
||||||
|
{data.problems.map((v) => {
|
||||||
|
const uncover = team.id === selectedTeam && selectedProblem === v.id;
|
||||||
|
const problemStatus = team.problems.find((idx) => idx.id === v.id);
|
||||||
|
return <span key={v.id} className={`${status(problemStatus)} ${uncover ? 'uncover' : ''} item`}>
|
||||||
|
{submissions(problemStatus)}
|
||||||
|
</span>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="solved">{team.score}</div>
|
||||||
|
<div className="penalty">{Math.floor(team.penalty / 60)}</div>
|
||||||
|
</>}
|
||||||
|
/>;
|
||||||
|
})}
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
ReactDOM.createRoot(document.getElementById('rank-list')!).render(<MainList {...options} data={data} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
addPage(new NamedPage(['resolver'], () => {
|
||||||
|
start(UiContext.payload, {
|
||||||
|
showAvatar: true,
|
||||||
|
showSchool: true,
|
||||||
|
});
|
||||||
|
}));
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
Context, db, ForbiddenError, UserModel,
|
avatar, ContestModel, Context, db, ForbiddenError,
|
||||||
|
ObjectId, PERM, STATUS, Time, UserModel,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
import { ResolverInput } from './interface';
|
||||||
|
|
||||||
interface IpLoginInfo {
|
interface IpLoginInfo {
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -33,4 +35,45 @@ export function apply(ctx: Context) {
|
|||||||
that.session.user = that.user;
|
that.session.user = that.user;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ctx.inject(['scoreboard'], ({ scoreboard }) => {
|
||||||
|
scoreboard.addView('resolver-tiny', 'Resolver(Tiny)', { tdoc: 'tdoc' }, {
|
||||||
|
async display({ tdoc }) {
|
||||||
|
if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||||
|
const teams = await ContestModel.getMultiStatus(tdoc.domainId, { docId: tdoc.docId }).toArray();
|
||||||
|
const udict = await UserModel.getList(tdoc.domainId, teams.map((i) => i.uid));
|
||||||
|
const teamIds: Record<number, number> = {};
|
||||||
|
for (let i = 1; i <= teams.length; i++) teamIds[teams[i - 1].uid] = i;
|
||||||
|
const time = (t: ObjectId) => Math.floor((t.getTimestamp().getTime() - tdoc.beginAt.getTime()) / Time.second);
|
||||||
|
const pid = (i: number) => String.fromCharCode(65 + i);
|
||||||
|
const unknownSchool = this.translate('Unknown School');
|
||||||
|
const submissions = teams.flatMap((i) => {
|
||||||
|
if (!i.journal) return [];
|
||||||
|
return i.journal.filter((s) => tdoc.pids.includes(s.pid)).map((s) => ({ ...s, uid: i.uid }));
|
||||||
|
});
|
||||||
|
this.response.body = {
|
||||||
|
payload: {
|
||||||
|
name: tdoc.title,
|
||||||
|
duration: Math.floor((new Date(tdoc.endAt).getTime() - new Date(tdoc.beginAt).getTime()) / 1000),
|
||||||
|
frozen: Math.floor((new Date(tdoc.endAt).getTime() - new Date(tdoc.lockAt).getTime()) / 1000),
|
||||||
|
problems: tdoc.pids.map((i, n) => ({ name: pid(n), id: i.toString() })),
|
||||||
|
teams: teams.map((t) => ({
|
||||||
|
id: t.uid.toString(),
|
||||||
|
name: udict[t.uid].uname,
|
||||||
|
avatar: avatar(udict[t.uid].avatar),
|
||||||
|
institution: udict[t.uid].school || unknownSchool,
|
||||||
|
})),
|
||||||
|
submissions: submissions.map((i) => ({
|
||||||
|
team: i.uid.toString(),
|
||||||
|
problem: i.pid.toString(),
|
||||||
|
verdict: i.status === STATUS.STATUS_ACCEPTED ? 'AC' : 'RJ',
|
||||||
|
time: time(i.rid),
|
||||||
|
})),
|
||||||
|
} as ResolverInput,
|
||||||
|
};
|
||||||
|
this.response.template = 'resolver.html';
|
||||||
|
},
|
||||||
|
supportedRules: ['acm'],
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
type Verdict = 'RJ' | 'AC' | 'NA';
|
||||||
|
export interface ProblemInfo {
|
||||||
|
color?: string; // hex color
|
||||||
|
id: string;
|
||||||
|
name: string; // A, B, C, ...
|
||||||
|
}
|
||||||
|
export interface TeamInfo {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
avatar?: string;
|
||||||
|
institution?: string;
|
||||||
|
exclude?: boolean; // false by default
|
||||||
|
}
|
||||||
|
export interface InstitiutionInfo {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
avatar?: string;
|
||||||
|
}
|
||||||
|
export interface SubmissionInfo {
|
||||||
|
team: string;
|
||||||
|
problem: string;
|
||||||
|
verdict: Verdict;
|
||||||
|
time: number; // in seconds
|
||||||
|
}
|
||||||
|
export interface ResolverInput {
|
||||||
|
name: string;
|
||||||
|
duration: number; // in seconds
|
||||||
|
frozen: number; // in seconds
|
||||||
|
problems: ProblemInfo[];
|
||||||
|
submissions: SubmissionInfo[];
|
||||||
|
teams: TeamInfo[];
|
||||||
|
institutions: Record<string, InstitiutionInfo>;
|
||||||
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/onsite-toolkit",
|
"name": "@hydrooj/onsite-toolkit",
|
||||||
"version": "0.0.1"
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-spring/web": "^9.7.3",
|
||||||
|
"react-use": "^17.4.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
body {
|
||||||
|
color: #eee;
|
||||||
|
background-color: #121212;
|
||||||
|
}
|
||||||
|
.rank-list {
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
180deg,
|
||||||
|
#3e3e3e 0px,
|
||||||
|
#3e3e3e 80px,
|
||||||
|
#121212 80px,
|
||||||
|
#121212 160px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.rank-list-item {
|
||||||
|
padding: 10px 0;
|
||||||
|
height: 80px;
|
||||||
|
position: relative;
|
||||||
|
background: transparent;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.header .rank, .header .solved, .header .penalty {
|
||||||
|
width: 90px;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.header .content {
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.header .content .title {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.header .content .copyright {
|
||||||
|
color: #8a919b;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.rank-list-item .rank, .rank-list-item .solved, .rank-list-item .penalty {
|
||||||
|
padding: 10px 0px;
|
||||||
|
width: 90px;
|
||||||
|
font-size: 40px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.rank-list-item .avatar {
|
||||||
|
height: 60px;
|
||||||
|
width: 60px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.rank-list-item .content {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 60px
|
||||||
|
}
|
||||||
|
.rank-list-item .content .name {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.rank-list-item .content .problems {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.rank-list-item .problems .item {
|
||||||
|
padding: 2px;
|
||||||
|
width: 95px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.ac {
|
||||||
|
background-color: #009c00;
|
||||||
|
}
|
||||||
|
.failed, .WA {
|
||||||
|
background-color: #dd514c;
|
||||||
|
}
|
||||||
|
.frozen {
|
||||||
|
background-color: #3c6ef6;
|
||||||
|
}
|
||||||
|
.untouched {
|
||||||
|
background-color: #282828;
|
||||||
|
color: #8a919b;
|
||||||
|
}
|
||||||
|
.uncover {
|
||||||
|
animation: flashing 1000ms infinite;
|
||||||
|
-webkit-animation: flashing 1000ms infinite; /*Safari and Chrome*/
|
||||||
|
}
|
||||||
|
@keyframes flashing {
|
||||||
|
from { background-color: #3c6ef6 }
|
||||||
|
50% { background-color: #7699ea }
|
||||||
|
to { background-color: #3c6ef6 }
|
||||||
|
}
|
||||||
|
@-webkit-keyframes flashing {/*Safari and Chrome*/
|
||||||
|
from { background-color: #3c6ef6 }
|
||||||
|
50% { background-color: #7699ea }
|
||||||
|
to { background-color: #3c6ef6 }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{% extends "layout/html5.html" %}
|
||||||
|
{% block body %}
|
||||||
|
{{ set(UiContext, 'payload', payload) }}
|
||||||
|
<header class="header">
|
||||||
|
<span class="rank">#Rank</span>
|
||||||
|
<span class="content">
|
||||||
|
<span class="title">Contest</span>
|
||||||
|
<span class="copyright">@Hydro/TinyResolver</span>
|
||||||
|
</span>
|
||||||
|
<span class="solved">Solved</span>
|
||||||
|
<span class="penalty">Penalty</span>
|
||||||
|
</header>
|
||||||
|
<div class="rank-list" id="rank-list"></div>
|
||||||
|
<link rel="stylesheet" href="/resolver.css?_={{ Date.now() }}" />
|
||||||
|
{% endblock %}
|
||||||
@@ -29,14 +29,33 @@ const tmp = tmpdir();
|
|||||||
const federationPlugin: esbuild.Plugin = {
|
const federationPlugin: esbuild.Plugin = {
|
||||||
name: 'federation',
|
name: 'federation',
|
||||||
setup(b) {
|
setup(b) {
|
||||||
|
const packages = {
|
||||||
|
react: 'React',
|
||||||
|
'react-dom': 'ReactDOM',
|
||||||
|
jquery: '$',
|
||||||
|
};
|
||||||
b.onResolve({ filter: /^@hydrooj\/ui-default/ }, () => ({
|
b.onResolve({ filter: /^@hydrooj\/ui-default/ }, () => ({
|
||||||
path: 'api',
|
path: 'api',
|
||||||
namespace: 'ui-default',
|
namespace: 'ui-default',
|
||||||
}));
|
}));
|
||||||
b.onLoad({ filter: /.*/, namespace: 'ui-default' }, () => ({
|
for (const key in packages) {
|
||||||
|
b.onResolve({ filter: new RegExp(`^${key}($|\\/)`) }, () => ({
|
||||||
|
path: packages[key],
|
||||||
|
namespace: 'ui-default',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
b.onLoad({ filter: /.*/, namespace: 'ui-default' }, (args) => {
|
||||||
|
if (args.path === 'api') {
|
||||||
|
return {
|
||||||
contents: 'module.exports = window.HydroExports;',
|
contents: 'module.exports = window.HydroExports;',
|
||||||
loader: 'tsx',
|
loader: 'tsx',
|
||||||
}));
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
contents: `module.exports = window.HydroExports['${args.path}'];`,
|
||||||
|
loader: 'tsx',
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user