Skip to content

Commit 31b91a8

Browse files
committed
enable schema.yaml to be loaded directly by default
1 parent 3773c98 commit 31b91a8

5 files changed

Lines changed: 107 additions & 32 deletions

File tree

lib/Toolbar.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,17 @@ class Toolbar {
215215
);
216216
}
217217

218-
// reload the context with the new locale
218+
// reload the context with the new locale.
219+
// For file-uploaded schemas the template_path is 'local/<ClassName>'
220+
// and there is no server path to fetch from. Pass the cached default
221+
// schema so buildTemplateFromUploadedSchema() is used (which now
222+
// extracts extensions.locales) rather than a failing server fetch.
219223
// console.log('reload 1: translationSelect change');
224+
const forced_schema_for_locale = current_template.startsWith('local/')
225+
? this.context.template.default.schema
226+
: null;
220227
await this.context
221-
.reload(current_template, language_update)
228+
.reload(current_template, language_update, forced_schema_for_locale)
222229
.then((context) => {
223230
for (const dh in context.dhs) {
224231
if (typeof _dh_data_cache[dh] !== 'undefined') {
@@ -1342,9 +1349,14 @@ export function titleOverText(enm) {
13421349
}
13431350

13441351
// RELOAD THE INTERFACE BY INTERACTING WITH THE CONTEXT
1352+
// For menu loads (file === null), don't pass forced_schema: Template.create
1353+
// will fetch the schema fresh from the server, preserving all locale data
1354+
// embedded in schema.extensions.locales. Passing forced_schema for menu
1355+
// loads caused buildTemplateFromUploadedSchema() to strip locales, making
1356+
// the first language switch silently fail (supportsLocale = false).
13451357
console.log('reload 3: loadSelectedTemplate');
13461358
this.context
1347-
.reload(template_path, null, schema)
1359+
.reload(template_path, null, file ? schema : null)
13481360
.then(this.restartInterface.bind(this));
13491361

13501362
// SETUP MODAL EVENTS

lib/utils/templates.js

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
import { deepMerge } from './objects';
1111
import { fetchFileAsync } from './files';
12-
import { menu, getSchema } from 'schemas'; // located in /web/schemas.js
12+
import { menu, getSchema, getSchemaYaml } from 'schemas'; // located in /web/schemas.js
13+
import { fetchAndProcessYaml, processYamlSchema } from './schema_induction';
1314

1415
// Retrieves template given in browser URL by template= , OR returns first menu item Object.keys(menu)[0]
1516
export function getTemplatePathInScope() {
@@ -31,42 +32,57 @@ export function getTemplatePathInScope() {
3132
return templatePath;
3233
}
3334

34-
// Running locally on user's computer so get schema from schemas.json
35-
//E.g. window.location === http://localhost:8000
36-
// However this may have to be reengineered when we do local running of app
37-
// but with URL access to web for schema(s).
35+
// Fetches schema, preferring schema.yaml at runtime (avoids build step) and
36+
// falling back to pre-built schema.json when YAML is unavailable.
3837
async function fetchSchema(path) {
3938

40-
if (window.location.href.startsWith('http://localhost:') || window.location.protocol.startsWith('file')) {
41-
const schema_path = path.replace(/\/templates\/(.+)\/schema.json/, '$1');
42-
return await getSchema(schema_path);
43-
}
44-
else if (window.location.protocol.startsWith('http')) {
45-
46-
// PROBLEM: local HTTPS server in /dist folder doesn't work
47-
// path e.g. /templates/canada_covid19/schema.json
48-
return await fetchFileAsync(path);
39+
// For any HTTP protocol (localhost or production), schema.yaml and schema.json
40+
// are both served as static files from web/dist/templates/ via CopyPlugin.
41+
// Fetch YAML first; if that fails, fetch JSON directly — no webpack bundle needed.
42+
if (window.location.protocol.startsWith('http')) {
43+
const yamlPath = path.replace(/schema\.json$/, 'schema.yaml');
44+
const yamlUrl = new URL(yamlPath, window.location.href).href;
45+
try {
46+
return await fetchAndProcessYaml(yamlUrl);
47+
} catch (e) {
48+
console.warn('schema_induction: YAML load failed, falling back to schema.json:', e.message);
49+
}
50+
// JSON static-file fallback
51+
try {
52+
const response = await fetch(path);
53+
if (response.ok) return await response.json();
54+
} catch (e) {
55+
console.warn('fetchSchema: schema.json fetch also failed:', e.message);
56+
}
57+
return null;
4958
}
5059

60+
// file:// protocol: browsers block fetch() for file:// origins.
61+
// Try webpack-bundled schema.json first; if absent, try webpack-bundled
62+
// schema.yaml (raw text, bundled as asset/source) processed synchronously.
63+
const schema_path = path.replace(/\/templates\/(.+)\/schema.json/, '$1');
64+
const jsonSchema = await getSchema(schema_path);
65+
if (jsonSchema) return jsonSchema;
66+
67+
const yamlText = await getSchemaYaml(schema_path);
68+
if (yamlText) return processYamlSchema(yamlText);
69+
5170
return null;
5271
}
5372

5473
//Used only in this script in create()
5574
function buildTemplateFromUploadedSchema(schema) {
56-
const name = schema.name || ''; // Brand new schema
75+
const name = schema.name || '';
5776
const locales = {};
5877

59-
if (schema.in_language) {
60-
const languages = Array.isArray(schema.in_language)
61-
? schema.in_language
62-
: [schema.in_language];
63-
64-
// PROBABLY IGNORE THESE SINCE UPLOADED SCHEMA DOESN'T COME WITH locales
65-
languages.forEach((locale) => {
66-
locales[locale] = { schema };
67-
locales;
78+
// Extract any locales embedded in the schema via extensions.locales.value,
79+
// using the same deep-merge logic as the server-fetch path in create().
80+
// This applies to both uploaded .yaml files and to schemas passed by
81+
// loadSelectedTemplate when the file was loaded from disk.
82+
Object.entries(schema.extensions?.locales?.value || {})
83+
.forEach(([locale, locale_schema]) => {
84+
locales[locale] = { schema: deepMerge(schema, locale_schema) };
6885
});
69-
}
7086

7187
return {
7288
name,

web/schemas.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
11
import menu_ from './templates/menu.json';
22

33
export const menu = menu_;
4-
// The build process encodes all templates/ schema.son into one file.
4+
5+
// Webpack bundles all templates/*/schema.json files as lazy chunks.
6+
// Returns null gracefully when a template has no schema.json (file:// fallback).
57
export const getSchema = async (schema_folder) => {
6-
return (await import(`./templates/${schema_folder}/schema.json`)).default;
8+
try {
9+
return (await import(`./templates/${schema_folder}/schema.json`)).default;
10+
} catch (e) {
11+
return null;
12+
}
13+
};
14+
15+
// Webpack bundles all templates/*/schema.yaml files as raw-text lazy chunks
16+
// (asset/source rule in webpack.schemas.js / webpack.config.js).
17+
// Used as a file:// fallback when schema.json is not in the bundle.
18+
export const getSchemaYaml = async (schema_folder) => {
19+
try {
20+
return (await import(`./templates/${schema_folder}/schema.yaml`)).default;
21+
} catch (e) {
22+
return null;
23+
}
724
};
25+
826
export const getExportFormats = async (schema_folder) => {
927
return (await import(`./templates/${schema_folder}/export.js`)).default;
1028
};

web/webpack.config.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,18 @@ module.exports = (env, argv) => {
3131
template: './index.html',
3232
}),
3333
new CopyPlugin({
34-
// Covers all schema.json including locales
34+
// Covers all schema.json and schema.yaml including locales
3535
patterns: [
3636
{
3737
context: 'templates',
3838
from: '**/schema.json',
3939
to: 'templates/[path][name][ext]',
40-
},
40+
},
41+
{
42+
context: 'templates',
43+
from: '**/schema.yaml',
44+
to: 'templates/[path][name][ext]',
45+
},
4146
{
4247
context: 'images',
4348
from: '**/*.gif',
@@ -51,6 +56,12 @@ module.exports = (env, argv) => {
5156
],
5257
module: {
5358
rules: [
59+
{
60+
// Bundle schema.yaml as raw text for the file:// fallback (getSchemaYaml).
61+
// Only active in dev mode where schemas.js is bundled directly.
62+
test: /schema\.yaml$/,
63+
type: 'asset/source',
64+
},
5465
{ test: /\.xlsx$/, loader: 'webpack-xlsx-loader' },
5566
{
5667
test: /\.(c|d|t)sv$/, // load all .csv, .dsv, .tsv files with dsv-loader

web/webpack.schemas.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ module.exports = {
66
entry: {
77
schemas: './schemas.js',
88
},
9+
module: {
10+
rules: [
11+
{
12+
// Bundle schema.yaml files as raw text strings (asset/source) so that
13+
// getSchemaYaml() can return them without fetch() in file:// contexts.
14+
test: /schema\.yaml$/,
15+
type: 'asset/source',
16+
},
17+
],
18+
},
919
output: {
1020
path: path.resolve(__dirname, 'dist', 'dist-schemas'),
1121
filename: '[name].js',
@@ -28,6 +38,14 @@ module.exports = {
2838
from: '**/schema.yaml',
2939
to: '../templates/[path][name][ext]',
3040
},
41+
{
42+
// schema.json as a static file so the HTTP JSON fallback in
43+
// fetchSchema() can fetch it directly without the webpack bundle.
44+
context: 'templates',
45+
from: '**/schema.json',
46+
to: '../templates/[path][name][ext]',
47+
noErrorOnMissing: true,
48+
},
3149
{
3250
context: 'templates',
3351
from: '**/exampleInput/*.*',

0 commit comments

Comments
 (0)