open-board-format 1.0.4 → 1.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.
- package/dist/index.d.mts +25 -25
- package/dist/index.mjs +93 -17
- package/package.json +7 -7
package/dist/index.d.mts
CHANGED
|
@@ -63,7 +63,7 @@ declare const OBFLicenseSchema: z.ZodObject<{
|
|
|
63
63
|
author_name: z.ZodOptional<z.ZodString>;
|
|
64
64
|
author_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
65
65
|
author_email: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodEmail, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
66
|
-
}, z.core.$
|
|
66
|
+
}, z.core.$loose>;
|
|
67
67
|
type OBFLicense = z.infer<typeof OBFLicenseSchema>;
|
|
68
68
|
/**
|
|
69
69
|
* Common properties for media resources (images and sounds).
|
|
@@ -87,8 +87,8 @@ declare const OBFMediaSchema: z.ZodObject<{
|
|
|
87
87
|
author_name: z.ZodOptional<z.ZodString>;
|
|
88
88
|
author_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
89
89
|
author_email: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodEmail, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
90
|
-
}, z.core.$
|
|
91
|
-
}, z.core.$
|
|
90
|
+
}, z.core.$loose>>;
|
|
91
|
+
}, z.core.$loose>;
|
|
92
92
|
type OBFMedia = z.infer<typeof OBFMediaSchema>;
|
|
93
93
|
/**
|
|
94
94
|
* Reference to a symbol in a proprietary symbol set (e.g., SymbolStix).
|
|
@@ -96,7 +96,7 @@ type OBFMedia = z.infer<typeof OBFMediaSchema>;
|
|
|
96
96
|
declare const OBFSymbolInfoSchema: z.ZodObject<{
|
|
97
97
|
set: z.ZodString;
|
|
98
98
|
filename: z.ZodString;
|
|
99
|
-
}, z.core.$
|
|
99
|
+
}, z.core.$loose>;
|
|
100
100
|
type OBFSymbolInfo = z.infer<typeof OBFSymbolInfoSchema>;
|
|
101
101
|
/**
|
|
102
102
|
* Image resource, extending {@link OBFMediaSchema} with optional
|
|
@@ -122,12 +122,12 @@ declare const OBFImageSchema: z.ZodIntersection<z.ZodObject<{
|
|
|
122
122
|
author_name: z.ZodOptional<z.ZodString>;
|
|
123
123
|
author_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
124
124
|
author_email: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodEmail, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
125
|
-
}, z.core.$
|
|
126
|
-
}, z.core.$
|
|
125
|
+
}, z.core.$loose>>;
|
|
126
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
127
127
|
symbol: z.ZodOptional<z.ZodObject<{
|
|
128
128
|
set: z.ZodString;
|
|
129
129
|
filename: z.ZodString;
|
|
130
|
-
}, z.core.$
|
|
130
|
+
}, z.core.$loose>>;
|
|
131
131
|
width: z.ZodOptional<z.ZodNumber>;
|
|
132
132
|
height: z.ZodOptional<z.ZodNumber>;
|
|
133
133
|
}, z.core.$strip>>;
|
|
@@ -149,8 +149,8 @@ declare const OBFSoundSchema: z.ZodObject<{
|
|
|
149
149
|
author_name: z.ZodOptional<z.ZodString>;
|
|
150
150
|
author_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
151
151
|
author_email: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodEmail, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
152
|
-
}, z.core.$
|
|
153
|
-
}, z.core.$
|
|
152
|
+
}, z.core.$loose>>;
|
|
153
|
+
}, z.core.$loose>;
|
|
154
154
|
type OBFSound = z.infer<typeof OBFSoundSchema>;
|
|
155
155
|
/**
|
|
156
156
|
* Reference to another board, resolved by ID, path, or URL.
|
|
@@ -161,7 +161,7 @@ declare const OBFLoadBoardSchema: z.ZodObject<{
|
|
|
161
161
|
data_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
162
162
|
url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
163
163
|
path: z.ZodOptional<z.ZodString>;
|
|
164
|
-
}, z.core.$
|
|
164
|
+
}, z.core.$loose>;
|
|
165
165
|
type OBFLoadBoard = z.infer<typeof OBFLoadBoardSchema>;
|
|
166
166
|
/**
|
|
167
167
|
* Interactive element on a board, optionally linked to images, sounds, and actions.
|
|
@@ -180,14 +180,14 @@ declare const OBFButtonSchema: z.ZodObject<{
|
|
|
180
180
|
data_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
181
181
|
url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
182
182
|
path: z.ZodOptional<z.ZodString>;
|
|
183
|
-
}, z.core.$
|
|
183
|
+
}, z.core.$loose>>;
|
|
184
184
|
background_color: z.ZodOptional<z.ZodString>;
|
|
185
185
|
border_color: z.ZodOptional<z.ZodString>;
|
|
186
186
|
top: z.ZodOptional<z.ZodNumber>;
|
|
187
187
|
left: z.ZodOptional<z.ZodNumber>;
|
|
188
188
|
width: z.ZodOptional<z.ZodNumber>;
|
|
189
189
|
height: z.ZodOptional<z.ZodNumber>;
|
|
190
|
-
}, z.core.$
|
|
190
|
+
}, z.core.$loose>;
|
|
191
191
|
type OBFButton = z.infer<typeof OBFButtonSchema>;
|
|
192
192
|
/**
|
|
193
193
|
* Row-and-column layout that arranges buttons by their IDs.
|
|
@@ -196,7 +196,7 @@ declare const OBFGridSchema: z.ZodObject<{
|
|
|
196
196
|
rows: z.ZodNumber;
|
|
197
197
|
columns: z.ZodNumber;
|
|
198
198
|
order: z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>, z.ZodString>, z.ZodNull]>>>;
|
|
199
|
-
}, z.core.$
|
|
199
|
+
}, z.core.$loose>;
|
|
200
200
|
type OBFGrid = z.infer<typeof OBFGridSchema>;
|
|
201
201
|
/**
|
|
202
202
|
* Root object of an `.obf` file: the complete definition of a single communication board.
|
|
@@ -219,14 +219,14 @@ declare const OBFBoardSchema: z.ZodObject<{
|
|
|
219
219
|
data_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
220
220
|
url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
221
221
|
path: z.ZodOptional<z.ZodString>;
|
|
222
|
-
}, z.core.$
|
|
222
|
+
}, z.core.$loose>>;
|
|
223
223
|
background_color: z.ZodOptional<z.ZodString>;
|
|
224
224
|
border_color: z.ZodOptional<z.ZodString>;
|
|
225
225
|
top: z.ZodOptional<z.ZodNumber>;
|
|
226
226
|
left: z.ZodOptional<z.ZodNumber>;
|
|
227
227
|
width: z.ZodOptional<z.ZodNumber>;
|
|
228
228
|
height: z.ZodOptional<z.ZodNumber>;
|
|
229
|
-
}, z.core.$
|
|
229
|
+
}, z.core.$loose>>;
|
|
230
230
|
url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
231
231
|
name: z.ZodOptional<z.ZodString>;
|
|
232
232
|
description_html: z.ZodOptional<z.ZodString>;
|
|
@@ -234,7 +234,7 @@ declare const OBFBoardSchema: z.ZodObject<{
|
|
|
234
234
|
rows: z.ZodNumber;
|
|
235
235
|
columns: z.ZodNumber;
|
|
236
236
|
order: z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>, z.ZodString>, z.ZodNull]>>>;
|
|
237
|
-
}, z.core.$
|
|
237
|
+
}, z.core.$loose>;
|
|
238
238
|
images: z.ZodOptional<z.ZodArray<z.ZodIntersection<z.ZodObject<{
|
|
239
239
|
id: z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>, z.ZodString>;
|
|
240
240
|
data: z.ZodOptional<z.ZodString>;
|
|
@@ -249,12 +249,12 @@ declare const OBFBoardSchema: z.ZodObject<{
|
|
|
249
249
|
author_name: z.ZodOptional<z.ZodString>;
|
|
250
250
|
author_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
251
251
|
author_email: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodEmail, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
252
|
-
}, z.core.$
|
|
253
|
-
}, z.core.$
|
|
252
|
+
}, z.core.$loose>>;
|
|
253
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
254
254
|
symbol: z.ZodOptional<z.ZodObject<{
|
|
255
255
|
set: z.ZodString;
|
|
256
256
|
filename: z.ZodString;
|
|
257
|
-
}, z.core.$
|
|
257
|
+
}, z.core.$loose>>;
|
|
258
258
|
width: z.ZodOptional<z.ZodNumber>;
|
|
259
259
|
height: z.ZodOptional<z.ZodNumber>;
|
|
260
260
|
}, z.core.$strip>>>>;
|
|
@@ -272,8 +272,8 @@ declare const OBFBoardSchema: z.ZodObject<{
|
|
|
272
272
|
author_name: z.ZodOptional<z.ZodString>;
|
|
273
273
|
author_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
274
274
|
author_email: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodEmail, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
275
|
-
}, z.core.$
|
|
276
|
-
}, z.core.$
|
|
275
|
+
}, z.core.$loose>>;
|
|
276
|
+
}, z.core.$loose>>>;
|
|
277
277
|
license: z.ZodOptional<z.ZodObject<{
|
|
278
278
|
type: z.ZodString;
|
|
279
279
|
copyright_notice_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
@@ -281,9 +281,9 @@ declare const OBFBoardSchema: z.ZodObject<{
|
|
|
281
281
|
author_name: z.ZodOptional<z.ZodString>;
|
|
282
282
|
author_url: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodURL, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
283
283
|
author_email: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodEmail, z.ZodLiteral<"">]>, z.ZodTransform<string | undefined, string>>>;
|
|
284
|
-
}, z.core.$
|
|
284
|
+
}, z.core.$loose>>;
|
|
285
285
|
strings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
286
|
-
}, z.core.$
|
|
286
|
+
}, z.core.$loose>;
|
|
287
287
|
type OBFBoard = z.infer<typeof OBFBoardSchema>;
|
|
288
288
|
/**
|
|
289
289
|
* Table of contents for an `.obz` package, mapping resource IDs to their archive paths.
|
|
@@ -295,8 +295,8 @@ declare const OBFManifestSchema: z.ZodObject<{
|
|
|
295
295
|
boards: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
296
296
|
images: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
297
297
|
sounds: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
298
|
-
}, z.core.$
|
|
299
|
-
}, z.core.$
|
|
298
|
+
}, z.core.$loose>;
|
|
299
|
+
}, z.core.$loose>;
|
|
300
300
|
type OBFManifest = z.infer<typeof OBFManifestSchema>;
|
|
301
301
|
//#endregion
|
|
302
302
|
//#region src/obf.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { unzip as unzip$1, zip as zip$1 } from "fflate";
|
|
3
|
-
|
|
4
3
|
//#region src/schema.ts
|
|
5
4
|
/**
|
|
6
5
|
* Open Board Format (OBF) Zod Schemas
|
|
@@ -58,12 +57,18 @@ const OBFButtonActionSchema = z.union([OBFSpellingActionSchema, OBFSpecialtyActi
|
|
|
58
57
|
/**
|
|
59
58
|
* License terms and attribution for a resource.
|
|
60
59
|
*/
|
|
61
|
-
const OBFLicenseSchema = z.
|
|
60
|
+
const OBFLicenseSchema = z.looseObject({
|
|
61
|
+
/** Type of the license, e.g., 'CC-BY-SA'. */
|
|
62
62
|
type: z.string(),
|
|
63
|
+
/** URL to the license terms. */
|
|
63
64
|
copyright_notice_url: OBFOptionalUrlSchema,
|
|
65
|
+
/** Source URL of the resource. */
|
|
64
66
|
source_url: OBFOptionalUrlSchema,
|
|
67
|
+
/** Name of the author. */
|
|
65
68
|
author_name: z.string().optional(),
|
|
69
|
+
/** URL of the author's webpage. */
|
|
66
70
|
author_url: OBFOptionalUrlSchema,
|
|
71
|
+
/** Email address of the author. */
|
|
67
72
|
author_email: OBFOptionalEmailSchema
|
|
68
73
|
});
|
|
69
74
|
/**
|
|
@@ -74,20 +79,29 @@ const OBFLicenseSchema = z.object({
|
|
|
74
79
|
* 2. path
|
|
75
80
|
* 3. url
|
|
76
81
|
*/
|
|
77
|
-
const OBFMediaSchema = z.
|
|
82
|
+
const OBFMediaSchema = z.looseObject({
|
|
83
|
+
/** Unique identifier for the media resource. */
|
|
78
84
|
id: OBFIDSchema,
|
|
85
|
+
/** Data URI containing the media data. */
|
|
79
86
|
data: z.string().optional(),
|
|
87
|
+
/** Path to the media file within an .obz package. */
|
|
80
88
|
path: z.string().optional(),
|
|
89
|
+
/** Data URL to fetch the media programmatically. */
|
|
81
90
|
data_url: OBFOptionalUrlSchema,
|
|
91
|
+
/** URL to the media resource. */
|
|
82
92
|
url: OBFOptionalUrlSchema,
|
|
93
|
+
/** MIME type of the media, e.g., 'image/png', 'audio/mpeg'. */
|
|
83
94
|
content_type: z.string().optional(),
|
|
95
|
+
/** Licensing information for the media. */
|
|
84
96
|
license: OBFLicenseSchema.optional()
|
|
85
97
|
});
|
|
86
98
|
/**
|
|
87
99
|
* Reference to a symbol in a proprietary symbol set (e.g., SymbolStix).
|
|
88
100
|
*/
|
|
89
|
-
const OBFSymbolInfoSchema = z.
|
|
101
|
+
const OBFSymbolInfoSchema = z.looseObject({
|
|
102
|
+
/** Name of the symbol set, e.g., 'symbolstix'. */
|
|
90
103
|
set: z.string(),
|
|
104
|
+
/** Filename of the symbol within the set. */
|
|
91
105
|
filename: z.string()
|
|
92
106
|
});
|
|
93
107
|
/**
|
|
@@ -101,8 +115,11 @@ const OBFSymbolInfoSchema = z.object({
|
|
|
101
115
|
* 4. `symbol`
|
|
102
116
|
*/
|
|
103
117
|
const OBFImageSchema = OBFMediaSchema.and(z.object({
|
|
118
|
+
/** Information about a symbol from a proprietary symbol set. */
|
|
104
119
|
symbol: OBFSymbolInfoSchema.optional(),
|
|
120
|
+
/** Width of the image in pixels. */
|
|
105
121
|
width: z.number().optional(),
|
|
122
|
+
/** Height of the image in pixels. */
|
|
106
123
|
height: z.number().optional()
|
|
107
124
|
}));
|
|
108
125
|
/**
|
|
@@ -112,70 +129,112 @@ const OBFSoundSchema = OBFMediaSchema;
|
|
|
112
129
|
/**
|
|
113
130
|
* Reference to another board, resolved by ID, path, or URL.
|
|
114
131
|
*/
|
|
115
|
-
const OBFLoadBoardSchema = z.
|
|
132
|
+
const OBFLoadBoardSchema = z.looseObject({
|
|
133
|
+
/** Unique identifier of the board to load. */
|
|
116
134
|
id: OBFOptionalIDSchema,
|
|
135
|
+
/** Name of the board to load. */
|
|
117
136
|
name: z.string().optional(),
|
|
137
|
+
/** Data URL to fetch the board programmatically. */
|
|
118
138
|
data_url: OBFOptionalUrlSchema,
|
|
139
|
+
/** URL to access the board via a web browser. */
|
|
119
140
|
url: OBFOptionalUrlSchema,
|
|
141
|
+
/** Path to the board within an .obz package. */
|
|
120
142
|
path: z.string().optional()
|
|
121
143
|
});
|
|
122
144
|
/**
|
|
123
145
|
* Interactive element on a board, optionally linked to images, sounds, and actions.
|
|
124
146
|
*/
|
|
125
|
-
const OBFButtonSchema = z.
|
|
147
|
+
const OBFButtonSchema = z.looseObject({
|
|
148
|
+
/** Unique identifier for the button. */
|
|
126
149
|
id: OBFIDSchema,
|
|
150
|
+
/** Label text displayed on the button. */
|
|
127
151
|
label: z.string().optional(),
|
|
152
|
+
/** Alternative text for vocalization when the button is activated. */
|
|
128
153
|
vocalization: z.string().optional(),
|
|
154
|
+
/** Identifier of the image associated with the button. */
|
|
129
155
|
image_id: OBFOptionalIDSchema,
|
|
156
|
+
/** Identifier of the sound associated with the button. */
|
|
130
157
|
sound_id: OBFOptionalIDSchema,
|
|
158
|
+
/** Action associated with the button. */
|
|
131
159
|
action: OBFButtonActionSchema.optional(),
|
|
160
|
+
/** List of multiple actions for the button, executed in order. */
|
|
132
161
|
actions: z.array(OBFButtonActionSchema).optional(),
|
|
162
|
+
/** Information to load another board when this button is activated. */
|
|
133
163
|
load_board: OBFLoadBoardSchema.optional(),
|
|
164
|
+
/** Background color of the button in 'rgb' or 'rgba' format. */
|
|
134
165
|
background_color: z.string().optional(),
|
|
166
|
+
/** Border color of the button in 'rgb' or 'rgba' format. */
|
|
135
167
|
border_color: z.string().optional(),
|
|
168
|
+
/** Vertical position for absolute positioning (0.0 to 1.0). */
|
|
136
169
|
top: z.number().min(0).max(1).optional(),
|
|
170
|
+
/** Horizontal position for absolute positioning (0.0 to 1.0). */
|
|
137
171
|
left: z.number().min(0).max(1).optional(),
|
|
172
|
+
/** Width of the button for absolute positioning (0.0 to 1.0). */
|
|
138
173
|
width: z.number().min(0).max(1).optional(),
|
|
174
|
+
/** Height of the button for absolute positioning (0.0 to 1.0). */
|
|
139
175
|
height: z.number().min(0).max(1).optional()
|
|
140
176
|
});
|
|
141
177
|
/**
|
|
142
178
|
* Row-and-column layout that arranges buttons by their IDs.
|
|
143
179
|
*/
|
|
144
|
-
const OBFGridSchema = z.
|
|
180
|
+
const OBFGridSchema = z.looseObject({
|
|
181
|
+
/** Number of rows in the grid. */
|
|
145
182
|
rows: z.number().int().min(1),
|
|
183
|
+
/** Number of columns in the grid. */
|
|
146
184
|
columns: z.number().int().min(1),
|
|
185
|
+
/**
|
|
186
|
+
* 2D array representing the order of buttons by their IDs.
|
|
187
|
+
* Each sub-array corresponds to a row, and each element is a button ID or null for empty slots.
|
|
188
|
+
*/
|
|
147
189
|
order: z.array(z.array(z.union([OBFIDSchema, z.null()])))
|
|
148
190
|
}).refine((g) => g.order.length === g.rows, { message: "Grid order length must match rows" }).refine((g) => g.order.every((row) => row.length === g.columns), { message: "Each grid row must have length equal to columns" });
|
|
149
191
|
/**
|
|
150
192
|
* Root object of an `.obf` file: the complete definition of a single communication board.
|
|
151
193
|
*/
|
|
152
|
-
const OBFBoardSchema = z.
|
|
194
|
+
const OBFBoardSchema = z.looseObject({
|
|
195
|
+
/** Format version of the Open Board Format, e.g., 'open-board-0.1'. */
|
|
153
196
|
format: OBFFormatVersionSchema,
|
|
197
|
+
/** Unique identifier for the board. */
|
|
154
198
|
id: OBFIDSchema,
|
|
199
|
+
/** Locale of the board as a BCP 47 language tag, e.g., 'en', 'en-US'. */
|
|
155
200
|
locale: OBFLocaleCodeSchema.optional(),
|
|
201
|
+
/** List of buttons on the board. */
|
|
156
202
|
buttons: z.array(OBFButtonSchema),
|
|
203
|
+
/** URL where the board can be accessed or downloaded. */
|
|
157
204
|
url: OBFOptionalUrlSchema,
|
|
205
|
+
/** Name of the board. */
|
|
158
206
|
name: z.string().optional(),
|
|
207
|
+
/** Description of the board in HTML format. */
|
|
159
208
|
description_html: z.string().optional(),
|
|
209
|
+
/** Grid layout information for arranging buttons. */
|
|
160
210
|
grid: OBFGridSchema,
|
|
211
|
+
/** List of images used in the board. */
|
|
161
212
|
images: z.array(OBFImageSchema).optional(),
|
|
213
|
+
/** List of sounds used in the board. */
|
|
162
214
|
sounds: z.array(OBFSoundSchema).optional(),
|
|
215
|
+
/** Licensing information for the board. */
|
|
163
216
|
license: OBFLicenseSchema.optional(),
|
|
217
|
+
/** String translations for multiple locales. */
|
|
164
218
|
strings: OBFStringsSchema.optional()
|
|
165
219
|
});
|
|
166
220
|
/**
|
|
167
221
|
* Table of contents for an `.obz` package, mapping resource IDs to their archive paths.
|
|
168
222
|
*/
|
|
169
|
-
const OBFManifestSchema = z.
|
|
223
|
+
const OBFManifestSchema = z.looseObject({
|
|
224
|
+
/** Format version of the Open Board Format, e.g., 'open-board-0.1'. */
|
|
170
225
|
format: OBFFormatVersionSchema,
|
|
226
|
+
/** Path to the root board within the .obz package. */
|
|
171
227
|
root: z.string(),
|
|
172
|
-
paths
|
|
228
|
+
/** Mapping of IDs to paths for boards, images, and sounds. */
|
|
229
|
+
paths: z.looseObject({
|
|
230
|
+
/** Mapping of board IDs to their file paths. */
|
|
173
231
|
boards: z.record(z.string(), z.string()),
|
|
232
|
+
/** Mapping of image IDs to their file paths. */
|
|
174
233
|
images: z.record(z.string(), z.string()),
|
|
234
|
+
/** Mapping of sound IDs to their file paths. */
|
|
175
235
|
sounds: z.record(z.string(), z.string()).optional()
|
|
176
236
|
})
|
|
177
237
|
});
|
|
178
|
-
|
|
179
238
|
//#endregion
|
|
180
239
|
//#region src/obf.ts
|
|
181
240
|
const UTF8_BOM = "";
|
|
@@ -245,7 +304,6 @@ function validateOBF(data) {
|
|
|
245
304
|
function stringifyOBF(board) {
|
|
246
305
|
return JSON.stringify(board, null, 2);
|
|
247
306
|
}
|
|
248
|
-
|
|
249
307
|
//#endregion
|
|
250
308
|
//#region src/zip.ts
|
|
251
309
|
/**
|
|
@@ -313,7 +371,6 @@ function isZip(archive) {
|
|
|
313
371
|
const bytes = new Uint8Array(archive);
|
|
314
372
|
return bytes.length >= ZIP_MAGIC.length && ZIP_MAGIC.every((byte, index) => bytes[index] === byte);
|
|
315
373
|
}
|
|
316
|
-
|
|
317
374
|
//#endregion
|
|
318
375
|
//#region src/obz.ts
|
|
319
376
|
/**
|
|
@@ -383,15 +440,18 @@ function parseManifest(json) {
|
|
|
383
440
|
* @throws {Error} If `rootBoardId` does not match any of the supplied boards.
|
|
384
441
|
*/
|
|
385
442
|
async function createOBZ(boards, rootBoardId, resources) {
|
|
443
|
+
if (!boards.some((board) => board.id === rootBoardId)) throw new Error(`Invalid OBZ: rootBoardId "${rootBoardId}" does not match any supplied board`);
|
|
386
444
|
const entries = /* @__PURE__ */ new Map();
|
|
387
445
|
const boardPaths = Object.fromEntries(boards.map((board) => [board.id, `boards/${board.id}.obf`]));
|
|
446
|
+
const imagePaths = collectMediaPaths(boards, "images");
|
|
447
|
+
const soundPaths = collectMediaPaths(boards, "sounds");
|
|
388
448
|
const manifest = OBFManifestSchema.parse({
|
|
389
449
|
format: "open-board-0.1",
|
|
390
450
|
root: `boards/${rootBoardId}.obf`,
|
|
391
451
|
paths: {
|
|
392
452
|
boards: boardPaths,
|
|
393
|
-
images:
|
|
394
|
-
sounds: {}
|
|
453
|
+
images: imagePaths,
|
|
454
|
+
...Object.keys(soundPaths).length > 0 ? { sounds: soundPaths } : {}
|
|
395
455
|
}
|
|
396
456
|
});
|
|
397
457
|
const encoder = new TextEncoder();
|
|
@@ -404,6 +464,23 @@ async function createOBZ(boards, rootBoardId, resources) {
|
|
|
404
464
|
const compressed = await zip(entries);
|
|
405
465
|
return new Blob([new Uint8Array(compressed)], { type: "application/zip" });
|
|
406
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Walk every board's media collection and produce the `{ id -> path }` map
|
|
469
|
+
* the spec calls "redundant but still required" for the OBZ manifest.
|
|
470
|
+
*
|
|
471
|
+
* Throws when two boards declare the same media id with conflicting paths
|
|
472
|
+
* — a silent OBZ that points at a non-existent file is worse than a clear error.
|
|
473
|
+
*/
|
|
474
|
+
function collectMediaPaths(boards, kind) {
|
|
475
|
+
const paths = {};
|
|
476
|
+
for (const board of boards) for (const media of board[kind] ?? []) {
|
|
477
|
+
if (media.path === void 0) continue;
|
|
478
|
+
const existing = paths[media.id];
|
|
479
|
+
if (existing !== void 0 && existing !== media.path) throw new Error(`Invalid OBZ: ${kind} id "${media.id}" maps to conflicting paths "${existing}" and "${media.path}"`);
|
|
480
|
+
paths[media.id] = media.path;
|
|
481
|
+
}
|
|
482
|
+
return paths;
|
|
483
|
+
}
|
|
407
484
|
function extractManifest(entries) {
|
|
408
485
|
const manifestBytes = entries.get("manifest.json");
|
|
409
486
|
if (!manifestBytes) throw new Error("Invalid OBZ: missing manifest.json");
|
|
@@ -419,6 +496,5 @@ function extractBoards(manifest, entries) {
|
|
|
419
496
|
}
|
|
420
497
|
return boards;
|
|
421
498
|
}
|
|
422
|
-
|
|
423
499
|
//#endregion
|
|
424
|
-
export { OBFBoardSchema, OBFButtonActionSchema, OBFButtonSchema, OBFFormatVersionSchema, OBFGridSchema, OBFIDSchema, OBFImageSchema, OBFLicenseSchema, OBFLoadBoardSchema, OBFLocaleCodeSchema, OBFLocalizedStringsSchema, OBFManifestSchema, OBFMediaSchema, OBFSoundSchema, OBFSpecialtyActionSchema, OBFSpellingActionSchema, OBFStringsSchema, OBFSymbolInfoSchema, createOBZ, extractOBZ, isZip, loadOBF, loadOBZ, parseManifest, parseOBF, stringifyOBF, unzip, validateOBF, zip };
|
|
500
|
+
export { OBFBoardSchema, OBFButtonActionSchema, OBFButtonSchema, OBFFormatVersionSchema, OBFGridSchema, OBFIDSchema, OBFImageSchema, OBFLicenseSchema, OBFLoadBoardSchema, OBFLocaleCodeSchema, OBFLocalizedStringsSchema, OBFManifestSchema, OBFMediaSchema, OBFSoundSchema, OBFSpecialtyActionSchema, OBFSpellingActionSchema, OBFStringsSchema, OBFSymbolInfoSchema, createOBZ, extractOBZ, isZip, loadOBF, loadOBZ, parseManifest, parseOBF, stringifyOBF, unzip, validateOBF, zip };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-board-format",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"description": "Parse, validate, and create Open Board Format (OBF/OBZ) files for AAC applications.",
|
|
6
6
|
"author": "Shay Cojocaru <shayc@outlook.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
"prepublishOnly": "npm run build"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@types/node": "^25.
|
|
39
|
-
"bumpp": "^
|
|
40
|
-
"tsdown": "^0.
|
|
41
|
-
"typescript": "
|
|
42
|
-
"vitest": "^4.
|
|
38
|
+
"@types/node": "^25.7.0",
|
|
39
|
+
"bumpp": "^11.1.0",
|
|
40
|
+
"tsdown": "^0.22.0",
|
|
41
|
+
"typescript": "~6.0.3",
|
|
42
|
+
"vitest": "^4.1.6"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"fflate": "^0.8.2",
|
|
46
|
-
"zod": "^4.3
|
|
46
|
+
"zod": "^4.4.3"
|
|
47
47
|
}
|
|
48
48
|
}
|