@@ -0,0 +1,266 @@
1+import {
2+ Checkbox,
3+ Col,
4+ H3,
5+ Row,
6+ Button as IndigoButton,
7+ Text,
8+} from '@tlon/indigo-react';
9+import { Button } from 'indigo-react';
10+import Modal from './Modal';
11+import { useCallback, useEffect, useState } from 'react';
12+import Point from 'lib/types/Point';
13+import useRoller from 'lib/useRoller';
14+import BodyPane from './Window/BodyPane';
15+import {
16+ InviteGeneratingStatus,
17+ useInvites,
18+ useInviteStore,
19+} from 'views/Invite/useInvites';
20+import { GeneratingModal } from 'views/Invite/GeneratingModal';
21+import { usePointCache } from 'store/pointCache';
22+
23+interface StarMap {
24+ [key: string]: {
25+ point: Point;
26+ children: Point[];
27+ };
28+}
29+
30+interface InviteConverterProps {
31+ points: Point[];
32+}
33+
34+function selectText(selected: string[], children: Point[]) {
35+ if (
36+ allChildrenSelected(
37+ selected,
38+ children.map(p => p.patp)
39+ )
40+ ) {
41+ return 'Deselect All';
42+ }
43+
44+ return 'Select All';
45+}
46+
47+function allChildrenSelected(selected: string[], childrenPatps: string[]) {
48+ const filtered = childrenPatps.filter(p => selected.includes(p));
49+ return filtered.length === childrenPatps.length;
50+}
51+
52+export const InviteConverter = ({ points }: InviteConverterProps) => {
53+ const { syncControlledPoints }: any = usePointCache();
54+ const { api, getPendingTransactions, getAndUpdatePoint } = useRoller();
55+ const { inviteJobs } = useInviteStore();
56+ const { generateInviteCodes } = useInvites();
57+ const [show, setShow] = useState(false);
58+ const [selected, setSelected] = useState<string[]>([]);
59+ const [starMap, setStarMap] = useState<StarMap>({});
60+ const [generatingStatus, setGeneratingStatus] = useState<
61+ InviteGeneratingStatus
62+ >('initial');
63+ const [currentPoint, setCurrentPoint] = useState<Point>();
64+ const currentJob = inviteJobs[currentPoint?.value || 0];
65+
66+ useEffect(() => {
67+ if (!points) {
68+ return;
69+ }
70+
71+ async function collect() {
72+ const pointMap: StarMap = {};
73+ const stars = points.filter(point => point.isStar);
74+
75+ for (const star of stars) {
76+ const spawns = await api.getSpawned(star.value);
77+ const filteredPoints = points.filter(p => spawns.includes(p.value));
78+
79+ if (filteredPoints.length !== 0) {
80+ pointMap[star.patp] = {
81+ point: star,
82+ children: filteredPoints,
83+ };
84+ }
85+ }
86+
87+ setStarMap(pointMap);
88+ }
89+
90+ collect();
91+ }, [points]);
92+
93+ const selectChildren = useCallback(
94+ (children: Point[]) => {
95+ setSelected(state => {
96+ const childrenPatps = children.map(p => p.patp);
97+ const withoutChildren = state.filter(p => !childrenPatps.includes(p));
98+
99+ if (allChildrenSelected(state, childrenPatps)) {
100+ return withoutChildren;
101+ } else {
102+ return withoutChildren.concat(childrenPatps);
103+ }
104+ });
105+ },
106+ [setSelected]
107+ );
108+
109+ const togglePoint = useCallback(
110+ (point: Point) => {
111+ setSelected(state => {
112+ if (state.includes(point.patp)) {
113+ return state.filter(p => p !== point.patp);
114+ } else {
115+ return state.concat([point.patp]);
116+ }
117+ });
118+ },
119+ [setSelected]
120+ );
121+
122+ const convert = useCallback(async () => {
123+ if (generatingStatus !== 'initial') {
124+ return;
125+ }
126+
127+ setGeneratingStatus('generating');
128+
129+ for (const { point, children } of Object.values(starMap)) {
130+ debugger;
131+ const filtered = children
132+ .filter(p => selected.includes(p.patp))
133+ .map(p => p.value);
134+
135+ if (filtered.length <= 0) {
136+ continue;
137+ }
138+
139+ setCurrentPoint(point);
140+ await generateInviteCodes(point, filtered, false);
141+ await getAndUpdatePoint(point.value);
142+ }
143+
144+ getPendingTransactions();
145+ syncControlledPoints();
146+ setGeneratingStatus('finished');
147+ }, [
148+ generatingStatus,
149+ selected,
150+ starMap,
151+ generateInviteCodes,
152+ getAndUpdatePoint,
153+ getPendingTransactions,
154+ syncControlledPoints,
155+ ]);
156+
157+ if (generatingStatus !== 'initial') {
158+ return (
159+ <GeneratingModal
160+ status={generatingStatus}
161+ current={currentJob?.generatingNum}
162+ total={
163+ generatingStatus === 'finished'
164+ ? selected.length
165+ : selected.filter(p => {
166+ const children = starMap[currentPoint?.patp || '']?.children;
167+ return children.map(c => c.patp).includes(p);
168+ }).length
169+ }
170+ fromStar={currentPoint?.patp}
171+ spawn={false}
172+ hide={() => {
173+ setGeneratingStatus('initial');
174+ setSelected([]);
175+ setShow(false);
176+ if (generatingStatus !== 'errored') {
177+ //pop();
178+ }
179+ }}
180+ />
181+ );
182+ }
183+
184+ return (
185+ <>
186+ {/*
187+ @ts-ignore */}
188+ <Button solid center onClick={() => setShow(true)}>
189+ Convert to Invites
190+ </Button>
191+ <Modal show={show} hide={() => setShow(false)}>
192+ <BodyPane p={0}>
193+ <Col width="420px">
194+ <H3 mb={2}>Convert Planet Spawns to Invites</H3>
195+ <Text>
196+ This allows you to convert a spawned planets into invites. After
197+ this operation, invites will show up under the point they were
198+ spawned from.
199+ </Text>
200+ <Col
201+ bg="washedGray"
202+ height="300px"
203+ mt={3}
204+ p={2}
205+ borderRadius={2}
206+ overflowY="auto">
207+ {Object.entries(starMap).map(([patp, { children }]) => (
208+ <Col mb={4}>
209+ <Row>
210+ <Text mb={2} fontSize={2}>
211+ Spawned by{' '}
212+ <Text bold fontSize={2}>
213+ {patp}
214+ </Text>
215+ </Text>
216+ <IndigoButton
217+ height="18px"
218+ px={2}
219+ fontSize={0}
220+ ml="auto"
221+ onClick={() => selectChildren(children)}>
222+ {selectText(selected, children)}
223+ </IndigoButton>
224+ </Row>
225+ {children.map(point => (
226+ <Row
227+ alignItems="center"
228+ justifyContent="space-between"
229+ ml={3}
230+ mb={2}
231+ py={1}
232+ borderBottomWidth="1px"
233+ borderBottomStyle="solid"
234+ borderBottomColor="washedGray">
235+ <Text>{point.patp}</Text>
236+ <Checkbox
237+ selected={selected.includes(point.patp)}
238+ onClick={() => togglePoint(point)}
239+ />
240+ </Row>
241+ ))}
242+ </Col>
243+ ))}
244+ </Col>
245+ <Row mt={4} justifyContent="end">
246+ <IndigoButton
247+ className="secondary"
248+ onClick={() => setShow(false)}>
249+ Close
250+ </IndigoButton>
251+ {/*
252+ @ts-ignore */}
253+ <IndigoButton
254+ className={selected.length === 0 ? '' : 'primary'}
255+ ml={2}
256+ onClick={convert}
257+ disabled={selected.length === 0}>
258+ Convert
259+ </IndigoButton>
260+ </Row>
261+ </Col>
262+ </BodyPane>
263+ </Modal>
264+ </>
265+ );
266+};