chrono-phylo-tree 1.1.8 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,267 +0,0 @@
1
- import { sortArray } from "multiple-sorting-array";
2
- import { SpeciesJSON } from "../types";
3
-
4
- export class Species {
5
- name = "";
6
- apparition = 0;
7
- duration = 0;
8
- ancestor?: Species = undefined;
9
- descendants: Species[] = [];
10
- description?: string = undefined;
11
- display = true;
12
- image?: string = undefined;
13
-
14
- private onPosition(ret = true) {
15
- if(!ret) return ret;
16
- return this.firstAncestor().stepsUntil(this)! % 2 === 0;
17
- }
18
-
19
- constructor(
20
- name = '',
21
- apparition = 0,
22
- duration = 0,
23
- ancestor: Species | undefined = undefined,
24
- descendants: Species[] = [],
25
- description: string | undefined = undefined,
26
- image: string | undefined = undefined,
27
- ) {
28
- if(duration <= 0){
29
- throw new Error("The duration of the species must be greater than 0");
30
- }
31
- this.name = name;
32
- this.apparition = apparition;
33
- this.duration = duration;
34
- this.ancestor = ancestor;
35
- this.descendants = descendants;
36
- this.description = description === "" ? undefined : description;
37
- this.image = image === "" ? undefined : image;
38
- }
39
-
40
- copy() : Species {
41
- const fa = this.firstAncestor();
42
- const n = fa.allDescendants(false).indexOf(this)
43
- const sp = Species.fromJSON(fa.toJSON());
44
- return sp.allDescendants(false)[n];
45
- }
46
-
47
- unlinkAncestor(): [Species, Species] | undefined {
48
- if(!this.ancestor) {
49
- return;
50
- }
51
- this.ancestor.descendants = this.ancestor.descendants.filter((d) => d !== this);
52
- const exAncestor = this.ancestor;
53
- this.ancestor = undefined;
54
- return [exAncestor.firstAncestor(), this];
55
- }
56
-
57
- unlinkDescendant(descendant: Species): [Species, Species] | undefined {
58
- if(!this.descendants.includes(descendant)) {
59
- return;
60
- }
61
- this.descendants = this.descendants.filter((d) => d !== descendant);
62
- descendant.ancestor = undefined;
63
- return [this.firstAncestor(), descendant];
64
- }
65
-
66
- linkAncestor(ancestor: Species) {
67
- if(this.ancestor === ancestor && ancestor.descendants.includes(this)) {
68
- return;
69
- }
70
- if(ancestor.apparition > this.apparition) {
71
- throw new Error(`The ancestor's apparition (${ancestor.apparition}) must be before or equal the descendant's apparition (${this.apparition})`);
72
- }
73
- if(ancestor.extinction() < this.apparition) {
74
- throw new Error(`The ancestor's extinction (${ancestor.extinction()}) must be after or equal the descendant's apparition (${this.apparition})`);
75
- }
76
- if(this.ancestor !== ancestor) {
77
- this.unlinkAncestor();
78
- }
79
- this.ancestor = ancestor;
80
- ancestor.descendants.push(this);
81
- }
82
-
83
- linkDescendant(descendant: Species) {
84
- if(descendant.ancestor === this && this.descendants.includes(descendant)) {
85
- return;
86
- }
87
- if(descendant.apparition < this.apparition) {
88
- throw new Error(`The descendant's apparition (${descendant.apparition}) must be after or equal the ancestor's apparition (${this.apparition})`);
89
- }
90
- if(descendant.extinction() > this.extinction()) {
91
- throw new Error(`The descendant's extinction (${descendant.extinction()}) must be before or equal the ancestor's extinction (${this.extinction()})`);
92
- }
93
- if(this.descendants.includes(descendant)) {
94
- return;
95
- }
96
- if(descendant.ancestor) {
97
- descendant.unlinkAncestor();
98
- }
99
- this.descendants.push(descendant);
100
- descendant.ancestor = this;
101
- }
102
-
103
- linkDescendants(descendants: Species[]) {
104
- for(const desc of descendants) {
105
- try {
106
- this.linkDescendant(desc);
107
- } catch (error) {
108
- console.error(`Error linking descendant ${desc.name} to ancestor ${this.name}:`, error);
109
- }
110
- }
111
- }
112
-
113
- addDescendant(
114
- name = '',
115
- afterApparition = 0,
116
- duration = 0,
117
- description: string | undefined = undefined,
118
- image: string | undefined = undefined,
119
- copy = false
120
- ) {
121
- if(afterApparition < 0 || afterApparition > this.duration) {
122
- throw new Error(`The apparition of the descendant must be between the apparition (${this.apparition}) and the extinction (${this.extinction()}) of the ancestor`);
123
- }
124
- const sp = copy ? this.copy() : this;
125
- const desc = new Species(
126
- name,
127
- sp.apparition + Math.max(afterApparition, 0),
128
- Math.max(duration, 0),
129
- undefined,
130
- [],
131
- description,
132
- image
133
- );
134
- desc.linkAncestor(sp);
135
- return copy ? sp : desc;
136
- }
137
-
138
- removeDescendant(desc: Species) {
139
- this.descendants = this.descendants.filter((d) => d !== desc);
140
- }
141
-
142
- addAncestor(
143
- name = '',
144
- previousApparition = 0,
145
- duration = 0,
146
- description: string | undefined = undefined,
147
- image: string | undefined = undefined,
148
- display = true,
149
- copy = false
150
- ) {
151
- if(previousApparition < 0) {
152
- throw new Error(`The apparition of the ancestor must be before or equal the apparition (${this.apparition}) of the descendant`);
153
- }
154
- if(duration < previousApparition){
155
- throw new Error(`The extiction of the ancestor must be after or equal the apparition (${this.apparition}) of the descendant`);
156
- }
157
- const sp = copy ? this.copy() : this;
158
- const anc = new Species(
159
- name,
160
- sp.apparition - Math.max(previousApparition, 0),
161
- duration,
162
- undefined,
163
- [],
164
- description,
165
- image
166
- );
167
- anc.display = display;
168
- sp.linkAncestor(anc);
169
- return copy ? sp : anc;
170
- }
171
-
172
- extinction() {
173
- return this.apparition + this.duration;
174
- }
175
-
176
- absoluteExtinction(): number {
177
- return this.descendants.length > 0
178
- ? Math.max(...this.allDescendants(false).map((desc) => desc.extinction()))
179
- : this.extinction();
180
- }
181
-
182
- absoluteDuration() {
183
- return this.absoluteExtinction() - this.apparition;
184
- }
185
-
186
- firstAncestor(includeNotDisplay = false): Species {
187
- return this.ancestor ? ((this.ancestor.display || includeNotDisplay) ? this.ancestor.firstAncestor() : this) : this;
188
- }
189
-
190
- cousinsExtinction() {
191
- return this.firstAncestor().absoluteExtinction();
192
- }
193
-
194
- allDescendants(sort = true): Species[] {
195
- const desc = sort ? sortArray(this.descendants, s => -s.apparition, s => -s.absoluteExtinction()) : this.descendants;
196
- if (desc.length === 0) {
197
- return [this];
198
- }
199
- const limitDesc = desc.filter(desc => desc.apparition >= this.extinction());
200
- const prevDesc = desc.filter(desc => limitDesc.indexOf(desc) === -1);
201
- const halfFunc = this.onPosition(sort) ? Math.ceil : Math.floor;
202
- const half = halfFunc(limitDesc.length / 2);
203
- const lim0 = limitDesc.slice(0, half);
204
- const lim1 = limitDesc.slice(half);
205
- return lim0.flatMap((d) => d.allDescendants(sort)).concat([this]).concat(lim1.flatMap((d) => d.allDescendants(sort))).concat(prevDesc.flatMap((d) => d.allDescendants(sort)));
206
- }
207
-
208
- stepsChain(desc: Species, includeNotDisplay = false): Species[] {
209
- if(!this.allDescendants(false).includes(desc)) {
210
- return [];
211
- }
212
- return [this as Species].concat(this.descendants.find(d => d.allDescendants(false).includes(desc))?.stepsChain(desc) ?? []).filter(d => d.display || includeNotDisplay);
213
- }
214
-
215
- stepsUntil(desc: Species, includeNotDisplay = false): number | undefined {
216
- if(!this.allDescendants(false).includes(desc)) {
217
- return;
218
- }
219
- return this.stepsChain(desc, includeNotDisplay).length - 1;
220
- }
221
-
222
- stepsUntilLastDescendant(icludeNotDisplay = false): number{
223
- if(this.descendants.length === 0) {
224
- return 0;
225
- }
226
- return Math.max(...this.allDescendants(false).filter(d => d.descendants.length === 0).map(d => this.stepsUntil(d, icludeNotDisplay) ?? 0));
227
- }
228
-
229
- toJSON(): SpeciesJSON {
230
- return {
231
- name: this.name,
232
- apparition: !this.ancestor ? this.apparition : undefined,
233
- afterApparition: this.ancestor ? this.apparition - this.ancestor.apparition : undefined,
234
- description: this.description,
235
- duration: this.duration,
236
- descendants: this.descendants.length > 0 ? this.descendants.map((desc) => desc.toJSON()) : undefined,
237
- image: this.image,
238
- };
239
- }
240
-
241
- async saveJSON(filename: string | undefined = undefined) {
242
- try{
243
- const jsonString = JSON.stringify(this.toJSON(), null, 2);
244
- const blob = new Blob([jsonString], { type: 'application/json' });
245
- const url = URL.createObjectURL(blob);
246
- const a = document.createElement('a');
247
- a.href = url;
248
- a.download = filename ?? `${this.name}.json`;
249
- a.click();
250
- URL.revokeObjectURL(url);
251
- } catch(errror) {
252
- console.error("Error saving file:", errror);
253
- }
254
- }
255
-
256
- static fromJSON(json: SpeciesJSON, ancestor?: Species): Species {
257
- const afterApparition = json.afterApparition ?? 0;
258
- const apparition = ancestor ? ancestor.apparition : json.apparition ?? 0;
259
- const sp = new Species(json.name ?? "", apparition + afterApparition, json.duration ?? 0, ancestor, [], json.description, json.image);
260
- if(json.descendants) {
261
- for (const desc of json.descendants) {
262
- sp.descendants.push(Species.fromJSON(desc, sp));
263
- }
264
- }
265
- return sp
266
- }
267
- }
@@ -1,27 +0,0 @@
1
- import { Species } from "../classes/Species";
2
- import { scientificNotation } from "../utils/scientificNotation";
3
-
4
- export const HoverDescription = ({
5
- hoverPosition,
6
- hoverSpecies,
7
- offset = { x: 0, y: 50 }
8
- } : HoverDescriptionProps) => {
9
- return (
10
- <nav
11
- style={{
12
- left: hoverPosition.x + offset.x,
13
- top: hoverPosition.y - offset.y,
14
- }}
15
- className="absolute bg-gray-500 p-2.5"
16
- >
17
- <p>{hoverSpecies.name} ({scientificNotation(hoverSpecies.apparition)} — {scientificNotation(hoverSpecies.extinction())}):</p>
18
- <p>{hoverSpecies.description}</p>
19
- </nav>
20
- );
21
- };
22
-
23
- interface HoverDescriptionProps {
24
- hoverPosition: { x: number; y: number };
25
- hoverSpecies: Species;
26
- offset?: { x: number; y: number };
27
- }
@@ -1,29 +0,0 @@
1
- import { codeText } from "../utils/translate";
2
-
3
- export const LanguageSelector = ({className = "", languages, language, setLanguage}: LanguageSelectorProps) => {
4
- return (
5
- <tr className={"text-start " + className}>
6
- <td>{codeText("nvlbl05", language)}:</td>
7
- <td>
8
- <select
9
- value={language}
10
- onChange={(e) => setLanguage(e.target.value)}
11
- className="bg-white dark:bg-[#242424] rounded"
12
- >
13
- {Array.from(languages).map(([key, value], index) => (
14
- <option value={key} key={index}>
15
- {value}
16
- </option>
17
- ))}
18
- </select>
19
- </td>
20
- </tr>
21
- );
22
- };
23
-
24
- interface LanguageSelectorProps {
25
- className?: string;
26
- languages: Map<string, string>;
27
- language: string;
28
- setLanguage: (language: string) => void;
29
- }
@@ -1,348 +0,0 @@
1
- import { useState } from "react";
2
- import { Species } from "../classes/Species";
3
- import { between } from "../utils/between";
4
- import { codeText } from "../utils/translate";
5
-
6
- interface MenuProps {
7
- species: Species;
8
- language?: string;
9
- open?: boolean;
10
- onClose?: () => void;
11
- saveSpecies?: (
12
- s: Species,
13
- name: string,
14
- apparition: number,
15
- duration: number,
16
- description: string,
17
- image: string
18
- ) => Promise<void>;
19
- createDescendant?: (
20
- s: Species,
21
- name: string,
22
- afterApparition: number,
23
- duration: number,
24
- description: string,
25
- image: string
26
- ) => Promise<void>;
27
- createAncestor?: (
28
- s: Species,
29
- name: string,
30
- previousApparition: number,
31
- duration: number,
32
- description: string,
33
- image: string
34
- ) => Promise<void>;
35
- deleteAncestor?: () => Promise<void>;
36
- deleteSpecies?: () => Promise<void>;
37
- }
38
-
39
- export const Menu = ({species, language, open, onClose, saveSpecies, createDescendant, createAncestor, deleteAncestor, deleteSpecies}: MenuProps) => {
40
- const [name, setName] = useState(species.name);
41
- const [apparition, setApparition] = useState(species.apparition);
42
- const [duration, setDuration] = useState(species.duration);
43
- const [description, setDescription] = useState(species.description ?? "");
44
- const [addDescendant, setAddDescendant] = useState(false);
45
- const [addAncestor, setAddAncestor] = useState(false);
46
- const [image, setImage] = useState(species.image ?? "");
47
-
48
- const toggleAddDescendant = () => {
49
- setAddDescendant(!addDescendant);
50
- };
51
-
52
- const toggleAddAncestor = () => {
53
- setAddAncestor(!addAncestor);
54
- };
55
-
56
- const uniqueDescendant = (s: Species): boolean => {
57
- return s.ancestor ? s.ancestor.descendants.length === 1 && uniqueDescendant(s.ancestor) : true;
58
- }
59
-
60
- return(
61
- <Modal open={open} onClose={onClose}>
62
- <form style={{backgroundColor: "grey"}} className="flex flex-col text-start w-auto fixed p-2.5">
63
- <Data
64
- name={name}
65
- setName={setName}
66
- apparition={apparition}
67
- setApparition={(n) => {
68
- setApparition(species.ancestor ? between(n, species.ancestor.apparition, species.ancestor.extinction()) : n);
69
- setDuration(species.descendants.length > 0 ? Math.max(Math.max(...species.descendants.map(desc => desc.apparition)) - apparition, duration) : duration);
70
- }}
71
- minApparition={species.ancestor ? species.ancestor.apparition : undefined}
72
- maxApparition={species.ancestor ? species.ancestor.extinction() : undefined}
73
- duration={duration}
74
- setDuration={setDuration}
75
- minDuration={species.descendants.length > 0 ? Math.max(...species.descendants.map(desc => desc.apparition)) - apparition : undefined}
76
- maxDuration={species.descendants.length > 0 ? Math.max(...species.descendants.map(desc => desc.apparition)) - apparition : undefined}
77
- description={description}
78
- setDescription={setDescription}
79
- image={image}
80
- setImage={setImage}
81
- language={language}
82
- >
83
- <button onClick={async () => {
84
- try{
85
- await saveSpecies?.(species, name, apparition, duration, description, image);
86
- onClose?.();
87
- } catch(e) {
88
- console.error(e);
89
- }
90
- }}>
91
- {codeText("spbtn00", language ?? "")}
92
- </button>
93
- <button type="button" onClick={deleteSpecies}>
94
- {codeText("spbtn01", language ?? "")}
95
- </button>
96
- <button type="button" onClick={toggleAddDescendant}>
97
- {codeText("spbtn02", language ?? "")}
98
- </button>
99
- {addDescendant &&
100
- <AddDescendant
101
- species={species}
102
- language={language}
103
- onClose={onClose}
104
- createDescendant={createDescendant}
105
- />}
106
- {species.ancestor ?
107
- uniqueDescendant(species) &&
108
- <button type="button" onClick={async () => {
109
- try {
110
- await deleteAncestor?.();
111
- onClose?.();
112
- } catch(e){
113
- console.error(e);
114
- }
115
- }}>
116
- {codeText("spbtn04" + (species.ancestor.ancestor ? "_0" : ""), language ?? "")}
117
- </button> :
118
- <button type="button" onClick={toggleAddAncestor}>
119
- {codeText("spbtn03", language ?? "")}
120
- </button>}
121
- {addAncestor &&
122
- <AddAncestor
123
- species={species}
124
- language={language}
125
- onClose={onClose}
126
- createAncestor={createAncestor}
127
- />}
128
- <button type="button" onClick={onClose}>
129
- {codeText("spbtn05", language ?? "")}
130
- </button>
131
- </Data>
132
- </form>
133
- </Modal>
134
- );
135
- };
136
-
137
- interface DataProps {
138
- name: string;
139
- setName: (name: string) => void;
140
- apparition: number;
141
- setApparition: (apparition: number) => void;
142
- minApparition?: number;
143
- maxApparition?: number;
144
- duration: number;
145
- setDuration: (duration: number) => void;
146
- minDuration?: number;
147
- maxDuration?: number;
148
- description?: string;
149
- setDescription: (description: string) => void;
150
- image: string;
151
- setImage: (image: string) => void;
152
- language?: string;
153
- children?: any;
154
- }
155
-
156
- const Data = ({
157
- name,
158
- setName,
159
- apparition,
160
- setApparition,
161
- minApparition,
162
- maxApparition,
163
- duration,
164
- setDuration,
165
- minDuration,
166
- maxDuration,
167
- description,
168
- setDescription,
169
- language,
170
- image,
171
- setImage,
172
- children
173
- }: DataProps) => {
174
- return (
175
- <>
176
- <table>
177
- <tbody>
178
- <tr>
179
- <td>{codeText("splbl00", language ?? "")}:</td>
180
- <td>
181
- <input
182
- type="text"
183
- value={name}
184
- onChange={(e) => setName(e.target.value)}
185
- />
186
- </td>
187
- </tr>
188
- <tr>
189
- <td>{codeText("splbl01", language ?? "")}:</td>
190
- <td>
191
- <input
192
- type="number"
193
- min={minApparition}
194
- max={maxApparition}
195
- value={apparition}
196
- onChange={(e) => setApparition(Number(e.target.value))}
197
- />
198
- </td>
199
- </tr>
200
- <tr>
201
- <td>{codeText("splbl02", language ?? "")}:</td>
202
- <td>
203
- <input
204
- type="number"
205
- min={minDuration}
206
- max={maxDuration}
207
- value={duration}
208
- onChange={(e) => setDuration(Number(e.target.value))}
209
- />
210
- </td>
211
- </tr>
212
- <tr>
213
- <td>{codeText("splbl03", language ?? "")}:</td>
214
- <td>
215
- <textarea
216
- value={description}
217
- onChange={(e) => setDescription(e.target.value)}
218
- />
219
- </td>
220
- </tr>
221
- <tr>
222
- <td>{codeText("splbl04", language ?? "")}:</td>
223
- <td>
224
- <input
225
- type="text"
226
- value={image}
227
- onChange={(e) => setImage(e.target.value)}
228
- />
229
- </td>
230
- </tr>
231
- <tr>
232
- <td colSpan={2}>
233
- <img
234
- src={image}
235
- style={{height: "100px"}}
236
- />
237
- </td>
238
- </tr>
239
- </tbody>
240
- </table>
241
- {children}
242
- </>
243
- );
244
- };
245
-
246
- const AddDescendant = ({species, language, onClose, createDescendant}: MenuProps) => {
247
- const [name, setName] = useState('');
248
- const [afterApparition, setAfterApparition] = useState(species.duration);
249
- const [duration, setDuration] = useState(species.duration);
250
- const [description, setDescription] = useState('');
251
- const [image, setImage] = useState('');
252
-
253
- return(
254
- <>
255
- <Data
256
- name={name}
257
- setName={setName}
258
- apparition={species.apparition + afterApparition}
259
- setApparition={(n) => setAfterApparition(n - species.apparition)}
260
- minApparition={species.apparition}
261
- maxApparition={species.extinction()}
262
- duration={duration}
263
- setDuration={setDuration}
264
- description={description}
265
- setDescription={setDescription}
266
- image={image}
267
- setImage={setImage}
268
- language={language}
269
- >
270
- <button type="button" onClick={async () => {
271
- try{
272
- await createDescendant?.(species, name, afterApparition, duration, description, image);
273
- onClose?.();
274
- } catch(e) {
275
- console.error(e);
276
- }
277
- }}>
278
- {codeText("cdbtn00", language ?? "")}
279
- </button>
280
- </Data>
281
- </>
282
- );
283
- };
284
-
285
- const AddAncestor = ({species, language, onClose, createAncestor}: MenuProps) => {
286
- const [name, setName] = useState('');
287
- const [previousApparition, setPreviousApparition] = useState(species.duration);
288
- const [duration, setDuration] = useState(species.duration);
289
- const [description, setDescription] = useState('');
290
- const [image, setImage] = useState('');
291
-
292
- return(
293
- <>
294
- <Data
295
- name={name}
296
- setName={setName}
297
- apparition={species.apparition - previousApparition}
298
- setApparition={(n) => {
299
- setPreviousApparition(species.apparition - n);
300
- setDuration(Math.max(species.apparition - n, duration));
301
- }}
302
- maxApparition={species.apparition}
303
- duration={duration}
304
- setDuration={setDuration}
305
- minDuration={previousApparition}
306
- description={description}
307
- setDescription={setDescription}
308
- image={image}
309
- setImage={setImage}
310
- language={language}
311
- >
312
- <button type="button" onClick={async () => {
313
- try {
314
- await createAncestor?.(species, name, previousApparition, duration, description, image);
315
- onClose?.();
316
- } catch (e) {
317
- console.error(e);
318
- }
319
- }}>
320
- {codeText("cdbtn00", language ?? "")}
321
- </button>
322
- </Data>
323
- </>
324
- );
325
- };
326
-
327
- interface ModalProps {
328
- open?: boolean;
329
- onClose?: () => void;
330
- children: any;
331
- }
332
-
333
- const Modal = ({open, onClose, children}: ModalProps) => {
334
- return (
335
- <div
336
- style={{backgroundColor: open ? 'rgba(0, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0)',}}
337
- className={`flex fixed inset-0 justify-center items-center transition-colors duration-300 ease-in-out ${open ? "visible" : "hidden"}`}
338
- onClick={onClose}
339
- >
340
- <div
341
- onClick={(e) => e.stopPropagation()}
342
- className="flex justify-center items-center"
343
- >
344
- {children}
345
- </div>
346
- </div>
347
- );
348
- };