worldorbit 3.0.6 → 3.1.0

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.
Files changed (53) hide show
  1. package/README.md +2 -2
  2. package/dist/browser/core/dist/atlas-edit.js +5 -1
  3. package/dist/browser/core/dist/atlas-validate.js +1 -1
  4. package/dist/browser/core/dist/draft-parse.js +14 -9
  5. package/dist/browser/core/dist/draft.d.ts +1 -0
  6. package/dist/browser/core/dist/draft.js +4 -2
  7. package/dist/browser/core/dist/format.js +5 -3
  8. package/dist/browser/core/dist/load.js +3 -2
  9. package/dist/browser/core/dist/normalize.js +36 -0
  10. package/dist/browser/core/dist/parse.js +54 -0
  11. package/dist/browser/core/dist/scene.js +1 -0
  12. package/dist/browser/core/dist/types.d.ts +21 -1
  13. package/dist/browser/viewer/dist/runtime-3d.js +396 -117
  14. package/dist/browser/viewer/dist/theme.js +27 -0
  15. package/dist/browser/viewer/dist/types.d.ts +17 -0
  16. package/dist/browser/viewer/dist/viewer.js +51 -0
  17. package/dist/unpkg/core/dist/atlas-edit.js +5 -1
  18. package/dist/unpkg/core/dist/atlas-validate.js +1 -1
  19. package/dist/unpkg/core/dist/draft-parse.js +14 -9
  20. package/dist/unpkg/core/dist/draft.d.ts +1 -0
  21. package/dist/unpkg/core/dist/draft.js +4 -2
  22. package/dist/unpkg/core/dist/format.js +5 -3
  23. package/dist/unpkg/core/dist/load.js +3 -2
  24. package/dist/unpkg/core/dist/normalize.js +36 -0
  25. package/dist/unpkg/core/dist/parse.js +54 -0
  26. package/dist/unpkg/core/dist/scene.js +1 -0
  27. package/dist/unpkg/core/dist/types.d.ts +21 -1
  28. package/dist/unpkg/viewer/dist/runtime-3d.js +396 -117
  29. package/dist/unpkg/viewer/dist/theme.js +27 -0
  30. package/dist/unpkg/viewer/dist/types.d.ts +17 -0
  31. package/dist/unpkg/viewer/dist/viewer.js +51 -0
  32. package/dist/unpkg/worldorbit-core.min.js +9 -9
  33. package/dist/unpkg/worldorbit-editor.min.js +360 -356
  34. package/dist/unpkg/worldorbit-markdown.min.js +20 -20
  35. package/dist/unpkg/worldorbit-viewer.min.js +210 -206
  36. package/dist/unpkg/worldorbit.js +557 -120
  37. package/dist/unpkg/worldorbit.min.js +216 -212
  38. package/package.json +1 -1
  39. package/packages/core/dist/atlas-edit.js +5 -1
  40. package/packages/core/dist/atlas-validate.js +1 -1
  41. package/packages/core/dist/draft-parse.js +14 -9
  42. package/packages/core/dist/draft.d.ts +1 -0
  43. package/packages/core/dist/draft.js +4 -2
  44. package/packages/core/dist/format.js +5 -3
  45. package/packages/core/dist/load.js +3 -2
  46. package/packages/core/dist/normalize.js +36 -0
  47. package/packages/core/dist/parse.js +54 -0
  48. package/packages/core/dist/scene.js +1 -0
  49. package/packages/core/dist/types.d.ts +21 -1
  50. package/packages/viewer/dist/runtime-3d.js +396 -117
  51. package/packages/viewer/dist/theme.js +27 -0
  52. package/packages/viewer/dist/types.d.ts +17 -0
  53. package/packages/viewer/dist/viewer.js +51 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worldorbit",
3
- "version": "3.0.6",
3
+ "version": "3.1.0",
4
4
  "description": "A text-based DSL and parser pipeline for orbital worldbuilding",
5
5
  "type": "module",
6
6
  "main": "./dist/unpkg/worldorbit.esm.js",
@@ -1,10 +1,14 @@
1
1
  import { collectAtlasDiagnostics } from "./atlas-validate.js";
2
- export function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.5") {
2
+ export function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.6.1") {
3
3
  return {
4
4
  format: "worldorbit",
5
5
  version,
6
6
  schemaVersion: version,
7
7
  sourceVersion: "1.0",
8
+ theme: {
9
+ preset: "blueprint",
10
+ styles: {},
11
+ },
8
12
  system: {
9
13
  type: "system",
10
14
  id: systemId,
@@ -67,7 +67,7 @@ function validateRelation(relation, objectMap, diagnostics) {
67
67
  }
68
68
  function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
69
69
  const filter = viewpoint.filter;
70
- if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
70
+ if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5" || sourceSchemaVersion === "2.6.1") {
71
71
  if (filter) {
72
72
  for (const groupId of filter.groupIds) {
73
73
  if (!groupIds.has(groupId)) {
@@ -174,6 +174,7 @@ function parseAtlasSource(source, forcedOutputVersion) {
174
174
  const baseDocument = {
175
175
  format: "worldorbit",
176
176
  sourceVersion: "1.0",
177
+ theme: null,
177
178
  system,
178
179
  groups,
179
180
  relations,
@@ -209,17 +210,19 @@ function parseAtlasSource(source, forcedOutputVersion) {
209
210
  function assertDraftSchemaHeader(tokens, line) {
210
211
  if (tokens.length !== 2 ||
211
212
  tokens[0].value.toLowerCase() !== "schema" ||
212
- !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
213
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
213
+ !["2.0-draft", "2.0", "2.1", "2.5", "2.6.1"].includes(tokens[1].value.toLowerCase())) {
214
+ throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", "schema 2.6.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
214
215
  }
215
216
  const version = tokens[1].value.toLowerCase();
216
- return version === "2.5"
217
- ? "2.5"
218
- : version === "2.1"
219
- ? "2.1"
220
- : version === "2.0-draft"
221
- ? "2.0-draft"
222
- : "2.0";
217
+ return version === "2.6.1"
218
+ ? "2.6.1"
219
+ : version === "2.5"
220
+ ? "2.5"
221
+ : version === "2.1"
222
+ ? "2.1"
223
+ : version === "2.0-draft"
224
+ ? "2.0-draft"
225
+ : "2.0";
223
226
  }
224
227
  function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
225
228
  const keyword = tokens[0]?.value.toLowerCase();
@@ -1563,6 +1566,8 @@ function schemaVersionRank(version) {
1563
1566
  return 2;
1564
1567
  case "2.5":
1565
1568
  return 3;
1569
+ case "2.6.1":
1570
+ return 4;
1566
1571
  }
1567
1572
  }
1568
1573
  function preprocessAtlasSource(source) {
@@ -7,6 +7,7 @@ export declare function upgradeDocumentToDraftV2(document: WorldOrbitDocument, o
7
7
  schemaVersion: "2.0-draft";
8
8
  format: "worldorbit";
9
9
  sourceVersion: import("./types.js").WorldOrbitDocumentVersion;
10
+ theme: import("./types.js").NormalizedTheme | null;
10
11
  system: WorldOrbitAtlasSystem | null;
11
12
  groups: import("./types.js").WorldOrbitGroup[];
12
13
  relations: import("./types.js").WorldOrbitRelation[];
@@ -18,9 +18,10 @@ export function upgradeDocumentToV2(document, options = {}) {
18
18
  }
19
19
  return {
20
20
  format: "worldorbit",
21
- version: "2.5",
22
- schemaVersion: "2.5",
21
+ version: "2.6.1",
22
+ schemaVersion: "2.6.1",
23
23
  sourceVersion: document.version,
24
+ theme: document.theme ?? null,
24
25
  system,
25
26
  groups: structuredClone(document.groups ?? []),
26
27
  relations: structuredClone(document.relations ?? []),
@@ -51,6 +52,7 @@ export function materializeAtlasDocument(document, options = {}) {
51
52
  format: "worldorbit",
52
53
  version: "1.0",
53
54
  schemaVersion: document.version,
55
+ theme: document.theme ?? null,
54
56
  system,
55
57
  groups: structuredClone(document.groups ?? []),
56
58
  relations: structuredClone(document.relations ?? []),
@@ -39,16 +39,18 @@ export function formatDocument(document, options = {}) {
39
39
  const useDraft = schema === "2.0" ||
40
40
  schema === "2.1" ||
41
41
  schema === "2.5" ||
42
+ schema === "2.6.1" ||
42
43
  schema === "2.0-draft" ||
43
44
  document.version === "2.0" ||
44
45
  document.version === "2.1" ||
45
46
  document.version === "2.5" ||
47
+ document.version === "2.6.1" ||
46
48
  document.version === "2.0-draft";
47
49
  if (useDraft) {
48
50
  if (schema === "2.0-draft") {
49
51
  const legacyDraftDocument = document.version === "2.0-draft"
50
52
  ? document
51
- : document.version === "2.0" || document.version === "2.1" || document.version === "2.5"
53
+ : document.version === "2.0" || document.version === "2.1" || document.version === "2.5" || document.version === "2.6.1"
52
54
  ? {
53
55
  ...document,
54
56
  version: "2.0-draft",
@@ -57,7 +59,7 @@ export function formatDocument(document, options = {}) {
57
59
  : upgradeDocumentToDraftV2(document);
58
60
  return formatDraftDocument(legacyDraftDocument);
59
61
  }
60
- const atlasDocument = document.version === "2.0" || document.version === "2.1" || document.version === "2.5"
62
+ const atlasDocument = document.version === "2.0" || document.version === "2.1" || document.version === "2.5" || document.version === "2.6.1"
61
63
  ? document
62
64
  : document.version === "2.0-draft"
63
65
  ? {
@@ -66,7 +68,7 @@ export function formatDocument(document, options = {}) {
66
68
  schemaVersion: "2.0",
67
69
  }
68
70
  : upgradeDocumentToV2(document);
69
- if ((schema === "2.0" || schema === "2.1" || schema === "2.5") && atlasDocument.version !== schema) {
71
+ if ((schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6.1") && atlasDocument.version !== schema) {
70
72
  return formatAtlasDocument({
71
73
  ...atlasDocument,
72
74
  version: schema,
@@ -22,7 +22,7 @@ export function detectWorldOrbitSchemaVersion(source) {
22
22
  return "2.1";
23
23
  }
24
24
  if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
25
- return "2.5";
25
+ return "2.6.1";
26
26
  }
27
27
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
28
28
  return "2.0";
@@ -87,7 +87,8 @@ export function loadWorldOrbitSourceWithDiagnostics(source) {
87
87
  if (schemaVersion === "2.0" ||
88
88
  schemaVersion === "2.0-draft" ||
89
89
  schemaVersion === "2.1" ||
90
- schemaVersion === "2.5") {
90
+ schemaVersion === "2.5" ||
91
+ schemaVersion === "2.6.1") {
91
92
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
92
93
  }
93
94
  let ast;
@@ -11,6 +11,7 @@ const URL_SCHEME_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*:/;
11
11
  export function normalizeDocument(ast) {
12
12
  let system = null;
13
13
  const objects = [];
14
+ const theme = ast.theme ? normalizeTheme(ast.theme) : null;
14
15
  for (const node of ast.objects) {
15
16
  const normalized = normalizeObject(node);
16
17
  if (node.objectType === "system") {
@@ -27,6 +28,7 @@ export function normalizeDocument(ast) {
27
28
  format: "worldorbit",
28
29
  version: "1.0",
29
30
  schemaVersion: "1.0",
31
+ theme,
30
32
  system,
31
33
  groups: [],
32
34
  relations: [],
@@ -34,6 +36,40 @@ export function normalizeDocument(ast) {
34
36
  objects,
35
37
  };
36
38
  }
39
+ function normalizeTheme(node) {
40
+ const styles = {};
41
+ for (const block of node.blocks) {
42
+ const fieldMap = collectFields(block.fields);
43
+ styles[block.target] = normalizeThemeProperties(fieldMap);
44
+ }
45
+ return {
46
+ preset: node.preset,
47
+ styles,
48
+ };
49
+ }
50
+ function normalizeThemeProperties(fieldMap) {
51
+ const result = {};
52
+ for (const [key, field] of fieldMap.entries()) {
53
+ if (field.values.length === 1) {
54
+ const rawValue = field.values[0];
55
+ if (rawValue === "true") {
56
+ result[key] = true;
57
+ continue;
58
+ }
59
+ if (rawValue === "false") {
60
+ result[key] = false;
61
+ continue;
62
+ }
63
+ const num = Number(rawValue);
64
+ if (!Number.isNaN(num) && rawValue.trim() !== "") {
65
+ result[key] = num;
66
+ continue;
67
+ }
68
+ }
69
+ result[key] = field.values.join(" ");
70
+ }
71
+ return result;
72
+ }
37
73
  function normalizeObject(node) {
38
74
  const mergedFields = [...node.inlineFields, ...node.blockFields];
39
75
  validateFieldCompatibility(node.objectType, mergedFields);
@@ -4,9 +4,14 @@ import { getIndent, tokenizeLineDetailed } from "./tokenize.js";
4
4
  export function parseWorldOrbit(source) {
5
5
  const lines = source.split(/\r?\n/);
6
6
  const objects = [];
7
+ let themeNode = null;
7
8
  let currentObject = null;
8
9
  let inInfoBlock = false;
10
+ let inThemeBlock = false;
9
11
  let infoIndent = null;
12
+ let themeIndent = null;
13
+ let themeBlockIndent = null;
14
+ let currentThemeBlock = null;
10
15
  for (let index = 0; index < lines.length; index++) {
11
16
  const rawLine = lines[index];
12
17
  const lineNumber = index + 1;
@@ -23,12 +28,49 @@ export function parseWorldOrbit(source) {
23
28
  }
24
29
  if (indent === 0) {
25
30
  inInfoBlock = false;
31
+ inThemeBlock = false;
26
32
  infoIndent = null;
33
+ themeIndent = null;
34
+ themeBlockIndent = null;
35
+ currentThemeBlock = null;
36
+ if (tokens.length >= 1 && tokens[0].value === "theme") {
37
+ inThemeBlock = true;
38
+ themeIndent = 0;
39
+ themeNode = {
40
+ type: "theme",
41
+ preset: tokens.length >= 2 ? tokens[1].value : null,
42
+ blocks: [],
43
+ location: { line: lineNumber, column: tokens[0].column },
44
+ };
45
+ continue;
46
+ }
27
47
  const objectNode = parseObjectHeader(tokens, lineNumber);
28
48
  objects.push(objectNode);
29
49
  currentObject = objectNode;
30
50
  continue;
31
51
  }
52
+ if (inThemeBlock) {
53
+ if (tokens.length >= 2 && tokens[0].value === "preset" && (!themeBlockIndent || indent <= themeBlockIndent)) {
54
+ if (themeNode) {
55
+ themeNode.preset = tokens[1].value;
56
+ }
57
+ continue;
58
+ }
59
+ if (currentThemeBlock && themeBlockIndent !== null && indent > themeBlockIndent) {
60
+ currentThemeBlock.fields.push(parseThemeField(tokens, lineNumber));
61
+ }
62
+ else {
63
+ themeBlockIndent = indent;
64
+ currentThemeBlock = {
65
+ type: "theme-block",
66
+ target: tokens[0].value,
67
+ fields: [],
68
+ location: { line: lineNumber, column: tokens[0].column },
69
+ };
70
+ themeNode?.blocks.push(currentThemeBlock);
71
+ }
72
+ continue;
73
+ }
32
74
  if (!currentObject) {
33
75
  throw new WorldOrbitError("Indented line without parent object", lineNumber, indent + 1);
34
76
  }
@@ -49,6 +91,7 @@ export function parseWorldOrbit(source) {
49
91
  }
50
92
  return {
51
93
  type: "document",
94
+ theme: themeNode,
52
95
  objects,
53
96
  };
54
97
  }
@@ -120,6 +163,17 @@ function parseField(tokens, line) {
120
163
  location: { line, column: tokens[0].column },
121
164
  };
122
165
  }
166
+ function parseThemeField(tokens, line) {
167
+ if (tokens.length < 2) {
168
+ throw new WorldOrbitError("Invalid theme field line", line, tokens[0]?.column ?? 1);
169
+ }
170
+ return {
171
+ type: "field",
172
+ key: tokens[0].value,
173
+ values: tokens.slice(1).map((token) => token.value),
174
+ location: { line, column: tokens[0].column },
175
+ };
176
+ }
123
177
  function parseInfoEntry(tokens, line) {
124
178
  if (tokens.length < 2) {
125
179
  throw new WorldOrbitError("Invalid info entry", line, tokens[0]?.column ?? 1);
@@ -1064,6 +1064,7 @@ function parseViewpointGroups(value, document, relationships, objectMap) {
1064
1064
  return splitListValue(value).map((entry) => {
1065
1065
  if (document.schemaVersion === "2.1" ||
1066
1066
  document.schemaVersion === "2.5" ||
1067
+ document.schemaVersion === "2.6.1" ||
1067
1068
  document.groups.some((group) => group.id === entry)) {
1068
1069
  return entry;
1069
1070
  }
@@ -2,7 +2,7 @@ export type WorldOrbitObjectType = "system" | "star" | "planet" | "moon" | "belt
2
2
  export type PlacementMode = "orbit" | "at" | "surface" | "free";
3
3
  export type Unit = "au" | "km" | "m" | "ly" | "pc" | "kpc" | "re" | "rj" | "sol" | "me" | "mj" | "s" | "min" | "h" | "d" | "y" | "ky" | "my" | "gy" | "K" | "deg";
4
4
  export type WorldOrbitDocumentVersion = "1.0";
5
- export type WorldOrbitAtlasDocumentVersion = "2.0" | "2.1" | "2.5";
5
+ export type WorldOrbitAtlasDocumentVersion = "2.0" | "2.1" | "2.5" | "2.6.1";
6
6
  export type WorldOrbitDraftDocumentVersion = "2.0-draft";
7
7
  export type WorldOrbitAnyDocumentVersion = WorldOrbitDocumentVersion | WorldOrbitAtlasDocumentVersion | WorldOrbitDraftDocumentVersion;
8
8
  export type ViewProjection = "topdown" | "isometric" | "orthographic" | "perspective";
@@ -34,8 +34,21 @@ export interface TokenizeOptions {
34
34
  line?: number;
35
35
  columnOffset?: number;
36
36
  }
37
+ export interface AstThemeNode {
38
+ type: "theme";
39
+ preset: string | null;
40
+ blocks: AstThemeBlockNode[];
41
+ location: AstSourceLocation;
42
+ }
43
+ export interface AstThemeBlockNode {
44
+ type: "theme-block";
45
+ target: string;
46
+ fields: AstFieldNode[];
47
+ location: AstSourceLocation;
48
+ }
37
49
  export interface AstDocument {
38
50
  type: "document";
51
+ theme: AstThemeNode | null;
39
52
  objects: AstObjectNode[];
40
53
  }
41
54
  export interface AstObjectNode {
@@ -59,10 +72,15 @@ export interface AstInfoEntryNode {
59
72
  value: string;
60
73
  location: AstSourceLocation;
61
74
  }
75
+ export interface NormalizedTheme {
76
+ preset: string | null;
77
+ styles: Record<string, Record<string, NormalizedValue>>;
78
+ }
62
79
  export interface WorldOrbitDocument {
63
80
  format: "worldorbit";
64
81
  version: WorldOrbitDocumentVersion;
65
82
  schemaVersion: WorldOrbitAnyDocumentVersion;
83
+ theme: NormalizedTheme | null;
66
84
  system: WorldOrbitSystem | null;
67
85
  groups: WorldOrbitGroup[];
68
86
  relations: WorldOrbitRelation[];
@@ -74,6 +92,7 @@ export interface WorldOrbitAtlasDocument {
74
92
  version: WorldOrbitAtlasDocumentVersion;
75
93
  schemaVersion: WorldOrbitAtlasDocumentVersion;
76
94
  sourceVersion: WorldOrbitDocumentVersion;
95
+ theme: NormalizedTheme | null;
77
96
  system: WorldOrbitAtlasSystem | null;
78
97
  groups: WorldOrbitGroup[];
79
98
  relations: WorldOrbitRelation[];
@@ -86,6 +105,7 @@ export interface WorldOrbitDraftDocument {
86
105
  version: WorldOrbitDraftDocumentVersion;
87
106
  schemaVersion: WorldOrbitDraftDocumentVersion;
88
107
  sourceVersion: WorldOrbitDocumentVersion;
108
+ theme: NormalizedTheme | null;
89
109
  system: WorldOrbitAtlasSystem | null;
90
110
  groups: WorldOrbitGroup[];
91
111
  relations: WorldOrbitRelation[];