@flourish/sdk 3.17.2 → 3.19.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 (43) hide show
  1. package/README.md +1 -0
  2. package/README.md~ +473 -0
  3. package/RELEASE_NOTES.md +12 -0
  4. package/RELEASE_NOTES.md~ +372 -0
  5. package/bin/{flourish → flourish.js} +31 -42
  6. package/lib/cmd/assign-version-number.js +4 -6
  7. package/lib/cmd/build.js +6 -10
  8. package/lib/cmd/delete.js +4 -6
  9. package/lib/cmd/help.js +3 -5
  10. package/lib/cmd/history.js +4 -6
  11. package/lib/cmd/list.js +4 -6
  12. package/lib/cmd/login.js +5 -7
  13. package/lib/cmd/logout.js +3 -5
  14. package/lib/cmd/new.js +3 -5
  15. package/lib/cmd/publish.js +9 -10
  16. package/lib/cmd/register.js +5 -7
  17. package/lib/cmd/run.js +3 -5
  18. package/lib/cmd/upgrade/1-convert-config-to-yaml.js +6 -8
  19. package/lib/cmd/upgrade/2-convert-index-html.js +3 -4
  20. package/lib/cmd/upgrade/3-add-build-config.js +3 -4
  21. package/lib/cmd/upgrade/4-remove-autoheight-config.js +3 -4
  22. package/lib/cmd/upgrade/index.js +5 -7
  23. package/lib/cmd/version.js +5 -13
  24. package/lib/cmd/whoami.js +4 -6
  25. package/lib/log.js +7 -7
  26. package/lib/sdk.js +43 -54
  27. package/lib/sdk.js~ +540 -0
  28. package/lib/validate_config.js +7 -1
  29. package/lib/validate_config.js~ +437 -0
  30. package/package-lock.json~ +2669 -0
  31. package/package.json +5 -5
  32. package/package.json~ +53 -0
  33. package/server/comms_js.js +1 -0
  34. package/server/data.js +40 -5
  35. package/server/index.js~ +540 -0
  36. package/server/views/index.html +9 -5
  37. package/site/embedded.js +1 -1
  38. package/site/images/flourish_in_canva.png +0 -0
  39. package/site/images/share_image.jpg +0 -0
  40. package/site/script.js +2 -2
  41. package/site/sdk.css +1 -1
  42. package/test/lib/validate_config.js +11 -2
  43. package/test/lib/validate_config.js~ +1006 -0
@@ -0,0 +1,540 @@
1
+ "use strict";
2
+
3
+ // Modules
4
+ const crypto = require("crypto"),
5
+ fs = require("fs"),
6
+ path = require("path"),
7
+
8
+ cross_spawn = require("cross-spawn"),
9
+ chokidar = require("chokidar"),
10
+ d3_dsv = require("d3-dsv"),
11
+ express = require("express"),
12
+ handlebars = require("handlebars"),
13
+ shell_quote = require("shell-quote"),
14
+ ws = require("ws"),
15
+ yaml = require("js-yaml"),
16
+
17
+ columns = require("./columns"),
18
+ comms_js = require("./comms_js"),
19
+ data_utils = require("./data"),
20
+ index_html = require("./index_html"),
21
+ json = require("./json"),
22
+
23
+ log = require("../lib/log"),
24
+ sdk = require("../lib/sdk");
25
+
26
+ const TA = require("parse5/lib/tree-adapters/default.js");
27
+
28
+ // Generate a static prefix randomly
29
+ //
30
+ // Use a different prefix for /preview, to catch the situation where the template
31
+ // developer mistakenly prepends a / to the static prefix.
32
+ const static_prefix = crypto.randomBytes(15).toString("base64").replace(/[+/]/g, (c) => ({ "/": "_", "+": "-" })[c]),
33
+ preview_static_prefix = crypto.randomBytes(15).toString("base64").replace(/[+/]/g, (c) => ({ "/": "_", "+": "-" })[c]);
34
+
35
+ function loadFile(path_parts, options) {
36
+ return new Promise(function(resolve, reject) {
37
+ const file_path = path.join(...path_parts),
38
+ filename = path_parts[path_parts.length - 1];
39
+
40
+ function fail(message, error) {
41
+ if ("default" in options) {
42
+ log.warn(message, `Proceeding without ${filename}...`);
43
+ if (typeof options.default === "function") {
44
+ return resolve(options.default());
45
+ }
46
+ return resolve(options.default);
47
+ }
48
+ log.problem(message, error.message);
49
+ reject(error);
50
+ }
51
+
52
+ function succeed(result) {
53
+ if (!options.silentSuccess) log.success(`Loaded ${filename}`);
54
+ resolve(result);
55
+ }
56
+
57
+ fs.readFile(file_path, "utf8", function(error, loaded_text) {
58
+ if (error) return fail(`Failed to load ${file_path}`, error);
59
+ switch (options.type) {
60
+ case "json":
61
+ try { return succeed(JSON.parse(loaded_text)); }
62
+ catch (error) {
63
+ return fail(`Uh-oh! There's a problem with your ${filename} file.`, error);
64
+ }
65
+
66
+ case "yaml":
67
+ try { return succeed(yaml.safeLoad(loaded_text)); }
68
+ catch (error) {
69
+ return fail(`Uh-oh! There's a problem with your ${filename} file.`, error);
70
+ }
71
+
72
+ default:
73
+ return succeed(loaded_text);
74
+ }
75
+ });
76
+ });
77
+ }
78
+
79
+ function loadSDKTemplate() {
80
+ return loadFile([__dirname, "views", "index.html"], { silentSuccess: true })
81
+ .then((template_text) => handlebars.compile(template_text));
82
+ }
83
+
84
+ function loadTemplateText(template_dir) {
85
+ return loadFile([template_dir, "index.html"], {
86
+ default: () => loadFile([__dirname, "views", "default_template_index.html"], {
87
+ silentSuccess: true
88
+ })
89
+ });
90
+ }
91
+
92
+ function loadJavaScript(template_dir) {
93
+ return loadFile([template_dir, "template.js"], {});
94
+ }
95
+
96
+ function loadSettings(template_dir) {
97
+ return sdk.readAndValidateConfig(template_dir);
98
+ }
99
+
100
+ function listDataTables(template_dir) {
101
+ return new Promise(function(resolve, reject) {
102
+ fs.readdir(path.join(template_dir, "data"), function(error, filenames) {
103
+ if (error) {
104
+ if (error.code === "ENOENT") return resolve([]);
105
+ return reject(error);
106
+ }
107
+
108
+ const data_files = [];
109
+ for (let filename of filenames) {
110
+ if (!filename.endsWith(".csv")) continue;
111
+
112
+ var name = filename.substr(0, filename.length - 4);
113
+ data_files.push(name);
114
+ }
115
+ resolve(data_files);
116
+ });
117
+ });
118
+ }
119
+
120
+ function getData(template_dir, data_tables) {
121
+ return Promise.all(data_tables.map((data_table) => getDataTable(template_dir, data_table)))
122
+ .then((data_array) => {
123
+ const data_by_name = {};
124
+ const column_types_by_name = {};
125
+ for (var i = 0; i < data_tables.length; i++) {
126
+ data_by_name[data_tables[i]] = data_array[i];
127
+ }
128
+ for (const data_table in data_by_name) {
129
+ const data = data_by_name[data_table];
130
+ column_types_by_name[data_table] = data_utils.transposeNestedArray(data)
131
+ .map(function(d, i) { return { type_id: data_utils.interpretColumn(d)[0].id, index: i }; });
132
+ }
133
+ return { data: data_by_name, column_types_by_name };
134
+ });
135
+ }
136
+
137
+ function getDataTable(template_dir, data_table) {
138
+ return new Promise(function(resolve, reject) {
139
+ fs.readFile(path.join(template_dir, "data", data_table + ".csv"), "utf8", function(error, csv_text) {
140
+ if (error) return reject(error);
141
+ if (csv_text.charAt(0) === "\uFEFF") csv_text = csv_text.substr(1);
142
+ resolve(d3_dsv.csvParseRows(csv_text));
143
+ });
144
+ });
145
+ }
146
+
147
+ function parseDataBindings(data_bindings, data_tables) {
148
+ if (!data_bindings) return {1: {}};
149
+
150
+ // Use the names as ids
151
+ const name_by_id = {};
152
+ for (let name of data_tables) name_by_id[name] = name;
153
+
154
+ // Collect parsed bindings by dataset
155
+ const data_bindings_by_dataset = {};
156
+ for (let binding of data_bindings) {
157
+ let dataset = binding.dataset;
158
+ if (!dataset) continue;
159
+
160
+ if (!data_bindings_by_dataset[dataset]) data_bindings_by_dataset[dataset] = {};
161
+ data_bindings_by_dataset[dataset][binding.key] = columns.parseDataBinding(binding, name_by_id);
162
+ }
163
+ return { 1: data_bindings_by_dataset };
164
+ }
165
+
166
+ function documentFragment(elements) {
167
+ const fragment = TA.createDocumentFragment();
168
+ for (const element of elements) {
169
+ TA.appendChild(fragment, element);
170
+ }
171
+ return fragment;
172
+ }
173
+
174
+ function scriptElementInline(code) {
175
+ const element = TA.createElement("script", "http://www.w3.org/1999/xhtml", []);
176
+ TA.insertText(element, code);
177
+ return element;
178
+ }
179
+
180
+ function scriptElementExternal(url) {
181
+ return TA.createElement("script", "http://www.w3.org/1999/xhtml", [{ name: "src", value: url }]);
182
+ }
183
+
184
+
185
+ function loadTemplate(template_dir, sdk_template, build_failed) {
186
+ return Promise.all([
187
+ listDataTables(template_dir),
188
+ loadSettings(template_dir),
189
+ ])
190
+ .then(([data_tables, settings]) => {
191
+ const data_bindings = parseDataBindings(settings.data, data_tables);
192
+ return Promise.all([
193
+ settings, data_bindings, data_tables,
194
+ previewInitJs(template_dir, data_bindings["1"], data_tables),
195
+ loadTemplateText(template_dir),
196
+ loadJavaScript(template_dir)
197
+ ]);
198
+ })
199
+ .then(([
200
+ settings, data_bindings, data_tables,
201
+ preview_init_js, template_text, template_js
202
+ ]) => {
203
+ const page_params = {
204
+ // Always use ID of 1 for SDK
205
+ visualisation: { id: 1, can_edit: true },
206
+ visualisation_js: "new Flourish.Visualisation('1', 0," + json.safeStringify({
207
+ data_bindings: data_bindings,
208
+ data_tables: data_tables,
209
+ }) + ")",
210
+ settings: json.safeStringify(settings.settings || []),
211
+ data_bindings: json.safeStringify(settings.data || []),
212
+ template_name: settings.name || "Untitled template",
213
+ template_version: settings.version,
214
+ template_author: settings.author || "",
215
+ build_failed: build_failed && build_failed.size > 0
216
+ };
217
+
218
+ const script = documentFragment([
219
+ scriptElementInline("window.Flourish = " + json.safeStringify({
220
+ static_prefix, environment: "sdk"
221
+ }) + ";"),
222
+ scriptElementExternal("/template.js"),
223
+ scriptElementExternal("/comms.js"),
224
+ scriptElementExternal("/embedded.js"),
225
+ ]);
226
+
227
+ const preview_script = documentFragment([
228
+ scriptElementInline("window.Flourish = " + json.safeStringify({
229
+ static_prefix: preview_static_prefix, environment: "sdk"
230
+ }) + ";"),
231
+ scriptElementExternal("/template.js"),
232
+ scriptElementExternal("/comms.js"),
233
+ scriptElementExternal("/embedded.js"),
234
+ scriptElementExternal("/talk_to_server.js"),
235
+ scriptElementInline("_Flourish_talkToServer();"),
236
+ scriptElementInline(preview_init_js),
237
+ ]);
238
+
239
+ return Promise.all([
240
+ sdk_template(page_params),
241
+ index_html.render(template_text, {
242
+ title: "Flourish SDK template preview blah",
243
+ static: static_prefix,
244
+ parsed_script: script
245
+ }),
246
+ index_html.render(template_text, {
247
+ title: "Flourish SDK template preview flerm",
248
+ static: preview_static_prefix,
249
+ parsed_script: preview_script,
250
+ }),
251
+ template_js,
252
+ sdk.buildRules(template_dir),
253
+ ]);
254
+ })
255
+ .then(([sdk_rendered, template_rendered, preview_rendered, template_js, build_rules]) => ({
256
+ sdk_rendered, template_rendered, preview_rendered, template_js, build_rules
257
+ }));
258
+ }
259
+
260
+ function previewInitJs(template_dir, data_bindings, data_tables) {
261
+ return getData(template_dir, data_tables).then(({data, column_types_by_name}) => {
262
+ const prepared_data = {};
263
+ for (let dataset in data_bindings) {
264
+ prepared_data[dataset] = data_utils.extractData(data_bindings[dataset], data, column_types_by_name);
265
+ }
266
+
267
+ const column_names = {};
268
+ const type_information = {};
269
+ for (let dataset in prepared_data) {
270
+ column_names[dataset] = prepared_data[dataset].column_names;
271
+ type_information[dataset] = prepared_data[dataset].type_information;
272
+ }
273
+
274
+ return `
275
+ var _Flourish_data_column_names = ${json.safeStringify(column_names)},
276
+ _Flourish_data_type_information = ${json.safeStringify(type_information)},
277
+ _Flourish_data = ${json.javaScriptStringify(prepared_data)};
278
+ for (var _Flourish_dataset in _Flourish_data) {
279
+ window.template.data[_Flourish_dataset] = _Flourish_data[_Flourish_dataset];
280
+ window.template.data[_Flourish_dataset].column_names = _Flourish_data_column_names[_Flourish_dataset];
281
+ window.template.data[_Flourish_dataset].type_information = _Flourish_data_type_information[_Flourish_dataset];
282
+ }
283
+ window.template.draw();
284
+ `;
285
+ });
286
+ }
287
+
288
+ function tryToOpen(url) {
289
+ // If it’s available and works, use /usr/bin/open to open
290
+ // the URL. If not just prompt the user to open it.
291
+ try {
292
+ cross_spawn.spawn("/usr/bin/open", [url])
293
+ .on("exit", function(exit_code) {
294
+ if (exit_code != 0) {
295
+ log.success("Now open " + url + " in your web browser!");
296
+ }
297
+ else {
298
+ log.success("Opened browser window to " + url);
299
+ }
300
+ })
301
+ .on("error", function() {
302
+ log.success("Now open " + url + " in your web browser!");
303
+ });
304
+ }
305
+ catch (error) {
306
+ log.success("Now open " + url + " in your web browser!");
307
+ }
308
+ }
309
+
310
+ function isPrefix(a, b) {
311
+ if (a.length > b.length) return false;
312
+ for (let i = 0; i < a.length; i++) {
313
+ if (a[i] !== b[i]) return false;
314
+ }
315
+ return true;
316
+ }
317
+
318
+ function splitPath(p) {
319
+ return p.split(path.sep).filter(c => c != "");
320
+ }
321
+
322
+
323
+ module.exports = function(template_dir, options) {
324
+ let app = express(),
325
+ reloadPreview,
326
+
327
+ template;
328
+
329
+ // Editor and settings/bindings
330
+ app.get("/", function (req, res) {
331
+ log.success("Loading main page in browser");
332
+ res.header("Content-Type", "text/html; charset=utf-8")
333
+ .send(template.sdk_rendered);
334
+ });
335
+
336
+ app.get("/template.js", function (req, res) {
337
+ res.header("Content-Type", "application/javascript").send(template.template_js);
338
+ });
339
+
340
+ app.get("/template.js.map", function (req, res) {
341
+ res.sendFile(path.resolve(template_dir, "template.js.map"));
342
+ });
343
+
344
+ app.get("/comms.js", function (req, res) {
345
+ res.header("Content-Type", "application/javascript").send(comms_js.withoutOriginCheck + comms_js.validate);
346
+ });
347
+
348
+ app.get("/thumbnail", function (req, res) {
349
+ const jpg_path = path.resolve(template_dir, "thumbnail.jpg"),
350
+ png_path = path.resolve(template_dir, "thumbnail.png");
351
+ if (fs.existsSync(jpg_path)) {
352
+ return res.header("Content-Type", "image/jpeg").sendFile(jpg_path);
353
+ }
354
+ if (fs.existsSync(png_path)) {
355
+ return res.header("Content-Type", "image/jpeg").sendFile(png_path);
356
+ }
357
+ return res.status(404).send("thumbnail not found");
358
+ });
359
+
360
+ app.get("/template/1/embed/", function(req, res) {
361
+ res.header("Content-Type", "text/html; charset=utf-8")
362
+ .send(template.template_rendered);
363
+ });
364
+
365
+ // API for accessing data tables
366
+ app.get("/api/data_table/:id/csv", function(req, res) {
367
+ res.status(200).header("Content-Type", "text/csv")
368
+ .sendFile(path.resolve(template_dir, "data", req.params.id + ".csv"));
369
+ });
370
+
371
+ // Preview not in an iframe
372
+ app.get("/preview", function(req, res) {
373
+ res.header("Content-Type", "text/html; charset=utf-8")
374
+ .send(template.preview_rendered);
375
+ });
376
+ app.use(`/${preview_static_prefix}/`, express.static(path.join(template_dir, "static")));
377
+
378
+ // Static files
379
+ app.use("/", express.static(path.join(__dirname, "..", "site")));
380
+ app.use(`/template/1/embed/${static_prefix}/`, express.static(path.join(template_dir, "static")));
381
+
382
+
383
+ function startServer(sdk_template, template_) {
384
+ template = template_;
385
+
386
+ // Run the server
387
+ const listen_hostname = options.listen || "localhost";
388
+ const server = app.listen(options.port, listen_hostname, function() {
389
+ const url = "http://" + listen_hostname + ":" + options.port + "/";
390
+ log.info(`Running server at ${url}`);
391
+
392
+ // Set up the WebSocket server and the reloadPreview() function
393
+ const sockets = new Set();
394
+ const websocket_server = new ws.Server({ server });
395
+ websocket_server.on("connection", function(socket) {
396
+ sockets.add(socket);
397
+ socket.on("close", function() { sockets.delete(socket); });
398
+ });
399
+ reloadPreview = function() {
400
+ for (let socket of sockets) socket.close();
401
+ };
402
+
403
+ watchForChanges(sdk_template);
404
+ if (options.open) tryToOpen(url);
405
+ })
406
+ .on("error", function(error) {
407
+ if (error.code === "EADDRINUSE") {
408
+ log.die("Another process is already listening on port " + options.port,
409
+ "Perhaps you’re already running flourish in another terminal?",
410
+ "You can use the --port option to listen on a different port");
411
+ }
412
+ log.die("Failed to start server", error.message);
413
+ });
414
+ }
415
+
416
+ let build_failed = new Set(),
417
+ rebuilding = new Set();
418
+ function watchForChanges(sdk_template) {
419
+ // Watch for file changes. If something changes, tell the page to reload itself
420
+ // If the source code has changed, rebuild it.
421
+
422
+ let reload_timer = null;
423
+ function reloadTemplate() {
424
+ if (rebuilding.size > 0) {
425
+ log.info("Not reloading template while rebuild is in progress.");
426
+ return;
427
+ }
428
+ if (reload_timer) {
429
+ clearTimeout(reload_timer);
430
+ reload_timer = null;
431
+ }
432
+ reload_timer = setTimeout(_reloadTemplate, 50);
433
+ }
434
+ function _reloadTemplate() {
435
+ reload_timer = null;
436
+ log.info("Reloading...");
437
+ loadTemplate(template_dir, sdk_template, build_failed)
438
+ .then((template_) => {
439
+ template = template_;
440
+ log.info("Template reloaded. Trying to reload preview.");
441
+ reloadPreview();
442
+ })
443
+ .catch((error) => {
444
+ log.problem("Failed to reload template", error.message);
445
+ });
446
+ }
447
+
448
+ // Run any custom watchers
449
+ if (template.build_rules) {
450
+ for (const build_rule of template.build_rules) {
451
+ if ("watch" in build_rule) {
452
+ const command_parts = shell_quote.parse(build_rule.watch),
453
+ prog = command_parts[0],
454
+ args = command_parts.slice(1);
455
+
456
+ const env = process.env;
457
+ env.NODE_ENV = "development";
458
+
459
+ log.info(`Running watcher command: ${build_rule.watch}`);
460
+ cross_spawn.spawn(prog, args, { cwd: template_dir, stdio: "inherit", env });
461
+ }
462
+ }
463
+ }
464
+
465
+ const chokidar_opts = { ignoreInitial: true, disableGlobbing: true, cwd: template_dir };
466
+ chokidar.watch(".", chokidar_opts).on("all", function(event_type, filename) {
467
+ const path_parts = filename.split(path.sep);
468
+
469
+ let should_reload = false;
470
+ if (sdk.TEMPLATE_SPECIAL.has(path_parts[0])) {
471
+ if (rebuilding.size > 0) return log.warn(`Rebuild in progress, ignoring change to ${filename}`);
472
+ log.info("Detected change to file: " + filename);
473
+ should_reload = true;
474
+ }
475
+
476
+ const build_commands = new Map();
477
+ if (template.build_rules) {
478
+ for (const build_rule of template.build_rules) {
479
+ if ((build_rule.directory && isPrefix(splitPath(build_rule.directory), path_parts))
480
+ || (build_rule.files && build_rule.files.indexOf(filename) != -1))
481
+ {
482
+ build_commands.set(build_rule.key, build_rule.script);
483
+ }
484
+ }
485
+ }
486
+
487
+ if (build_commands.size > 0) {
488
+ const build_commands_to_run = [];
489
+ for (const [key, command] of build_commands) {
490
+ if (rebuilding.has(key)) continue;
491
+ rebuilding.add(key);
492
+ if (reload_timer) {
493
+ clearTimeout(reload_timer);
494
+ reload_timer = null;
495
+ }
496
+ log.info("Detected change to file: " + filename, "Running build for " + key);
497
+ build_commands_to_run.push(
498
+ sdk.runBuildCommand(template_dir, command, "development")
499
+ .then(() => {
500
+ rebuilding.delete(key);
501
+ build_failed.delete(key);
502
+ }, (error) => {
503
+ rebuilding.delete(key);
504
+ build_failed.add(key);
505
+ throw error;
506
+ })
507
+ );
508
+ }
509
+
510
+ Promise.all(build_commands_to_run)
511
+ .then(() => {
512
+ if (rebuilding.size == 0) {
513
+ log.success("Build process complete.");
514
+ reloadTemplate();
515
+ }
516
+ })
517
+ .catch((error) => {
518
+ if (build_failed.size > 0 && rebuilding.size == 0) {
519
+ reloadTemplate(); // To pass the build_failed flags
520
+ }
521
+ });
522
+ }
523
+ else if (should_reload) reloadTemplate();
524
+ });
525
+ }
526
+
527
+ loadSDKTemplate()
528
+ .then((sdk_template) => {
529
+ return Promise.all([
530
+ sdk_template, loadTemplate(template_dir, sdk_template)
531
+ ]);
532
+ })
533
+ .then(([sdk_template, template]) => {
534
+ startServer(sdk_template, template);
535
+ })
536
+ .catch((error) => {
537
+ if (options.debug) log.problem("Failed to start server", error.message, error.stack);
538
+ else log.problem("Failed to start server", error.message);
539
+ });
540
+ };
@@ -18,7 +18,7 @@
18
18
  <div class="row editor-bar">
19
19
  <div id="preview-menu" class="row-menu left no-select">
20
20
 
21
- <a id="full-preview" href="preview" target="_blank" class="menu-item clickable popup" data-popup-head="Full preview in new window">
21
+ <a id="full-preview" href="preview" target="_blank" class="menu-item clickable popup" data-popup-head="Full preview in new window" aria-label="Full preview in new window">
22
22
  <i class="fa fa-expand"></i>
23
23
  </a>
24
24
  <div id="editor-previews">
@@ -35,8 +35,8 @@
35
35
  <i class="fa fa-cog"></i>
36
36
  </span>
37
37
  <span id="editor-custom-inputs">
38
- <input id="editor-custom-width" type="number" min="100" data-target="custom" class="clickable selected"> x
39
- <input id="editor-custom-height" type="number" min="100" data-target="custom" class="clickable selected" placeholder="auto">
38
+ <input id="editor-custom-width" type="number" min="100" data-target="custom" class="clickable selected" aria-label="Custom width"> x
39
+ <input id="editor-custom-height" type="number" min="100" data-target="custom" class="clickable selected" placeholder="auto" aria-label="Custom height">
40
40
  </span>
41
41
  </div>
42
42
  </div>
@@ -50,12 +50,12 @@
50
50
  <div id="visualisation" class="full-screen-ready">
51
51
  <div id="visualisation-inner" class="editor-core">
52
52
  <div class="preview-holder">
53
- <img class="loading-spinner" src="/images/bosh.svg" data-testid="loading-indicator">
53
+ <img class="loading-spinner" src="/images/bosh.svg" data-testid="loading-indicator" alt="">
54
54
  <div class="preview-overlay">
55
55
  <p class="empty-label"></p>
56
56
  <p class="empty-details"></p>
57
57
  </div>
58
- <iframe id="preview" sandbox="allow-same-origin allow-scripts allow-downloads" src="about:blank" data-testid="visualisation-iframe"></iframe>
58
+ <iframe id="preview" sandbox="allow-same-origin allow-scripts allow-downloads" src="about:blank" data-testid="visualisation-iframe" aria-label="Visualisation preview"></iframe>
59
59
  <div id="resize-overlay"></div>
60
60
  <div id="resize-handle-container">
61
61
  <div id="resize-handle"></div>
@@ -73,6 +73,10 @@
73
73
 
74
74
  <div class="template-settings"></div>
75
75
  <div class="detailed-settings"></div>
76
+ <div class="template-settings-search">
77
+ <i class="fa fa-search"></i>
78
+ <input type="search" placeholder="Search for setting" />
79
+ </div>
76
80
  </div>
77
81
  </div>
78
82
  </div>
package/site/embedded.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(){"use strict";var e,i,t=!1;function n(e){if(t&&window.top!==window.self){var i=window;"srcdoc"===i.location.pathname&&(i=i.parent);var n,o=(n={},window._Flourish_template_id&&(n.template_id=window._Flourish_template_id),window.Flourish&&window.Flourish.app&&window.Flourish.app.loaded_template_id&&(n.template_id=window.Flourish.app.loaded_template_id),window._Flourish_visualisation_id&&(n.visualisation_id=window._Flourish_visualisation_id),window.Flourish&&window.Flourish.app&&window.Flourish.app.loaded_visualisation&&(n.visualisation_id=window.Flourish.app.loaded_visualisation.id),window.Flourish&&window.Flourish.app&&window.Flourish.app.story&&(n.story_id=window.Flourish.app.story.id,n.slide_count=window.Flourish.app.story.slides.length),window.Flourish&&window.Flourish.app&&window.Flourish.app.current_slide&&(n.slide_index=window.Flourish.app.current_slide.index+1),n),r={sender:"Flourish",method:"customerAnalytics"};for(var a in o)o.hasOwnProperty(a)&&(r[a]=o[a]);for(var a in e)e.hasOwnProperty(a)&&(r[a]=e[a]);i.parent.postMessage(JSON.stringify(r),"*")}}function o(e){if("function"!=typeof e)throw new Error("Analytics callback is not a function");window.Flourish._analytics_listeners.push(e)}function r(){t=!0;[{event_name:"click",action_name:"click",use_capture:!0},{event_name:"keydown",action_name:"key_down",use_capture:!0},{event_name:"mouseenter",action_name:"mouse_enter",use_capture:!1},{event_name:"mouseleave",action_name:"mouse_leave",use_capture:!1}].forEach((function(e){document.body.addEventListener(e.event_name,(function(){n({action:e.action_name})}),e.use_capture)}))}function a(){if(null==e){var i=function(){var e=window.location;"about:srcdoc"==e.href&&(e=window.parent.location);var i={};return function(e,t,n){for(;n=t.exec(e);)i[decodeURIComponent(n[1])]=decodeURIComponent(n[2])}(e.search.substring(1).replace(/\+/g,"%20"),/([^&=]+)=?([^&]*)/g),i}();e="referrer"in i?/^https:\/\/medium.com\//.test(i.referrer):!("auto"in i)}return e}function s(e){var i=e||window.innerWidth;return i>999?650:i>599?575:400}function l(e,t){if(window.top!==window.self){var n=window;if("srcdoc"==n.location.pathname&&(n=n.parent),i)return e=parseInt(e,10),void n.parent.postMessage({sentinel:"amp",type:"embed-size",height:e},"*");var o={sender:"Flourish",context:"iframe.resize",method:"resize",height:e,src:n.location.toString()};if(t)for(var r in t)o[r]=t[r];n.parent.postMessage(JSON.stringify(o),"*")}}function d(){return(-1!==navigator.userAgent.indexOf("Safari")||-1!==navigator.userAgent.indexOf("iPhone"))&&-1==navigator.userAgent.indexOf("Chrome")}function u(e){window.addEventListener("message",(function(i){if(null!=i.source&&(i.origin===document.location.origin||i.origin.match(/\/\/localhost:\d+$|\/\/flourish-api\.com$|\.flourish\.(?:local(:\d+)?|net|rocks|studio)$|\.uri\.sh$/))){var t;try{t=JSON.parse(i.data)}catch(e){return void console.warn("Unexpected non-JSON message: "+JSON.stringify(i.data))}if("Flourish"===t.sender){for(var n=document.querySelectorAll("iframe"),o=0;o<n.length;o++)if(n[o].contentWindow==i.source||n[o].contentWindow==i.source.parent)return void e(t,n[o]);console.warn("could not find frame",t)}}})),d()&&(window.addEventListener("resize",h),h())}function h(){for(var e=document.querySelectorAll(".flourish-embed"),i=0;i<e.length;i++){var t=e[i];if(!t.getAttribute("data-width")){var n=t.querySelector("iframe");if(n){var o=window.getComputedStyle(t),r=t.offsetWidth-parseFloat(o.paddingLeft)-parseFloat(o.paddingRight);n.style.width=r+"px"}}}}function c(e,i,t,n,o){var r=document.createElement("iframe");if(r.setAttribute("scrolling","no"),r.setAttribute("frameborder","0"),r.setAttribute("title","Interactive or visual content"),r.setAttribute("sandbox","allow-same-origin allow-forms allow-scripts allow-downloads allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"),i.appendChild(r),r.offsetParent||"fixed"===getComputedStyle(r).position)f(e,i,r,t,n,o);else{var a={embed_url:e,container:i,iframe:r,width:t,height:n,play_on_load:o};if(window._flourish_poll_items?window._flourish_poll_items.push(a):window._flourish_poll_items=[a],window._flourish_poll_items.length>1)return r;var s=setInterval((function(){window._flourish_poll_items=window._flourish_poll_items.filter((function(e){return!e.iframe.offsetParent||(f(e.embed_url,e.container,e.iframe,e.width,e.height,e.play_on_load),!1)})),window._flourish_poll_items.length||clearInterval(s)}),500)}return r}function f(e,i,t,n,o,r){var a;return n&&"number"==typeof n?(a=n,n+="px"):n&&n.match(/^[ \t\r\n\f]*([+-]?\d+|\d*\.\d+(?:[eE][+-]?\d+)?)(?:\\?[Pp]|\\0{0,4}[57]0(?:\r\n|[ \t\r\n\f])?)(?:\\?[Xx]|\\0{0,4}[57]8(?:\r\n|[ \t\r\n\f])?)[ \t\r\n\f]*$/)&&(a=parseFloat(n)),o&&"number"==typeof o&&(o+="px"),n?t.style.width=n:d()?t.style.width=i.offsetWidth+"px":t.style.width="100%",!!o||(e.match(/\?/)?e+="&auto=1":e+="?auto=1",o=s(a||t.offsetWidth)+"px"),o&&("%"===o.charAt(o.length-1)&&(o=parseFloat(o)/100*i.parentNode.offsetHeight+"px"),t.style.height=o),t.setAttribute("src",e+(r?"#play-on-load":"")),t}function w(e){return!Array.isArray(e)&&"object"==typeof e&&null!=e}function p(e,i){for(var t in i)w(e[t])&&w(i[t])?p(e[t],i[t]):e[t]=i[t];return e}!function(){var e,t=window.top===window.self,h=t?null:(i="#amp=1"==window.location.hash,{createEmbedIframe:c,isFixedHeight:a,getHeightForBreakpoint:s,startEventListeners:u,notifyParentWindow:l,isSafari:d,initCustomerAnalytics:r,addAnalyticsListener:o,sendCustomerAnalyticsMessage:n}),f=!0;function w(){var i;Flourish.fixed_height||(null!=e?i=e:f&&(i=h.getHeightForBreakpoint()),i!==window.innerHeight&&h.notifyParentWindow(i))}function m(){w(),window.addEventListener("resize",w)}Flourish.warn=function(e){if("string"==typeof e&&(e={message:e}),t||"editor"!==Flourish.environment)console.warn(e.message);else{var i={sender:"Flourish",method:"warn",message:e.message,explanation:e.explanation};window.parent.postMessage(JSON.stringify(i),"*")}},Flourish.uploadImage=function(e){if(t||"story_editor"!==Flourish.environment)throw"Invalid upload request";var i={sender:"Flourish",method:"request-upload",name:e.name,accept:e.accept};window.parent.postMessage(JSON.stringify(i),"*")},Flourish.setSetting=function(e,i){if("editor"===Flourish.environment||"sdk"===Flourish.environment){var t={sender:"Flourish",method:"setSetting",name:e,value:i};window.parent.postMessage(JSON.stringify(t),"*")}else if("story_editor"===Flourish.environment){var n={};n[e]=i,p(window.template.state,function(e){var i={};for(var t in e){for(var n=i,o=t.indexOf("."),r=0;o>=0;o=t.indexOf(".",r=o+1)){var a=t.substring(r,o);a in n||(n[a]={}),n=n[a]}n[t.substring(r)]=e[t]}return i}(n))}},Flourish.setHeight=function(i){Flourish.fixed_height||(e=i,f=null==i,w())},Flourish.checkHeight=function(){if(!t){var e=Flourish.__container_height;null!=e?(Flourish.fixed_height=!0,h.notifyParentWindow(e)):h.isFixedHeight()?Flourish.fixed_height=!0:(Flourish.fixed_height=!1,w())}},Flourish.fixed_height=t||h.isFixedHeight(),Flourish.enableCustomerAnalytics=function(){h&&h.initCustomerAnalytics()},"loading"===document.readyState?document.addEventListener("DOMContentLoaded",m):m()}()}();
1
+ !function(){"use strict";var e,t,n=!1;function i(e){if(n&&window.top!==window.self){var t=window;"srcdoc"===t.location.pathname&&(t=t.parent);var i,o=(i={},window._Flourish_template_id&&(i.template_id=window._Flourish_template_id),window.Flourish&&window.Flourish.app&&window.Flourish.app.loaded_template_id&&(i.template_id=window.Flourish.app.loaded_template_id),window._Flourish_visualisation_id&&(i.visualisation_id=window._Flourish_visualisation_id),window.Flourish&&window.Flourish.app&&window.Flourish.app.loaded_visualisation&&(i.visualisation_id=window.Flourish.app.loaded_visualisation.id),window.Flourish&&window.Flourish.app&&window.Flourish.app.story&&(i.story_id=window.Flourish.app.story.id,i.slide_count=window.Flourish.app.story.slides.length),window.Flourish&&window.Flourish.app&&window.Flourish.app.current_slide&&(i.slide_index=window.Flourish.app.current_slide.index+1),i),r={sender:"Flourish",method:"customerAnalytics"};for(var a in o)o.hasOwnProperty(a)&&(r[a]=o[a]);for(var a in e)e.hasOwnProperty(a)&&(r[a]=e[a]);t.parent.postMessage(JSON.stringify(r),"*")}}function o(e){if("function"!=typeof e)throw new Error("Analytics callback is not a function");window.Flourish._analytics_listeners.push(e)}function r(){n=!0;[{event_name:"click",action_name:"click",use_capture:!0},{event_name:"keydown",action_name:"key_down",use_capture:!0},{event_name:"mouseenter",action_name:"mouse_enter",use_capture:!1},{event_name:"mouseleave",action_name:"mouse_leave",use_capture:!1}].forEach((function(e){document.body.addEventListener(e.event_name,(function(){i({action:e.action_name})}),e.use_capture)}))}function a(){if(null==e){var t=function(){var e=window.location;"about:srcdoc"==e.href&&(e=window.parent.location);var t={};return function(e,n,i){for(;i=n.exec(e);)t[decodeURIComponent(i[1])]=decodeURIComponent(i[2])}(e.search.substring(1).replace(/\+/g,"%20"),/([^&=]+)=?([^&]*)/g),t}();e="referrer"in t?/^https:\/\/medium.com\//.test(t.referrer):!("auto"in t)}return e}function s(e){var t=e||window.innerWidth;return t>999?650:t>599?575:400}function l(e,n){if(window.top!==window.self){var i=window;if("srcdoc"==i.location.pathname&&(i=i.parent),t)return e=parseInt(e,10),void i.parent.postMessage({sentinel:"amp",type:"embed-size",height:e},"*");var o={sender:"Flourish",context:"iframe.resize",method:"resize",height:e,src:i.location.toString()};if(n)for(var r in n)o[r]=n[r];i.parent.postMessage(JSON.stringify(o),"*")}}function d(){return(-1!==navigator.userAgent.indexOf("Safari")||-1!==navigator.userAgent.indexOf("iPhone"))&&-1==navigator.userAgent.indexOf("Chrome")}function u(e){return"string"==typeof e||e instanceof String}function c(e){return"warn"!==e.method?(console.warn("BUG: validateWarnMessage called for method"+e.method),!1):!(null!=e.message&&!u(e.message))&&!(null!=e.explanation&&!u(e.explanation))}function h(e){return"resize"!==e.method?(console.warn("BUG: validateResizeMessage called for method"+e.method),!1):!!u(e.src)&&(!!u(e.context)&&!!("number"==typeof(t=e.height)?!isNaN(t)&&t>=0:u(t)&&/\d/.test(t)&&/^[0-9]*(\.[0-9]*)?(cm|mm|Q|in|pc|pt|px|em|ex|ch|rem|lh|vw|vh|vmin|vmax|%)?$/i.test(t)));var t}function f(e){throw new Error("Validation for setSetting is not implemented yet; see issue #4328")}function w(e){return"customerAnalytics"===e.method||(console.warn("BUG: validateCustomerAnalyticsMessage called for method"+e.method),!1)}function p(e){return"request-upload"!==e.method?(console.warn("BUG: validateResizeMessage called for method"+e.method),!1):!!u(e.name)&&!(null!=e.accept&&!u(e.accept))}function m(e,t){var n=function(e){for(var t={warn:c,resize:h,setSetting:f,customerAnalytics:w,"request-upload":p},n={},i=0;i<e.length;i++){var o=e[i];if(!t[o])throw new Error("No validator found for method "+o);n[o]=t[o]}return n}(t);window.addEventListener("message",(function(t){if(null!=t.source&&(t.origin===document.location.origin||t.origin.match(/\/\/localhost:\d+$|\/\/flourish-api\.com$|\.flourish\.(?:local(:\d+)?|net|rocks|studio)$|\.uri\.sh$|\/\/flourish-user-templates\.com$/))){var i;try{i=JSON.parse(t.data)}catch(e){return void console.warn("Unexpected non-JSON message: "+JSON.stringify(t.data))}if("Flourish"===i.sender)if(i.method)if(Object.prototype.hasOwnProperty.call(n,i.method))if(n[i.method](i)){for(var o=document.querySelectorAll("iframe"),r=0;r<o.length;r++)if(o[r].contentWindow==t.source||o[r].contentWindow==t.source.parent)return void e(i,o[r]);console.warn("could not find frame",i)}else console.warn("Validation failed for the message",i);else console.warn("No validator implemented for message",i);else console.warn("The 'method' property was missing from message",i)}})),d()&&(window.addEventListener("resize",g),g())}function g(){for(var e=document.querySelectorAll(".flourish-embed"),t=0;t<e.length;t++){var n=e[t];if(!n.getAttribute("data-width")){var i=n.querySelector("iframe");if(i){var o=window.getComputedStyle(n),r=n.offsetWidth-parseFloat(o.paddingLeft)-parseFloat(o.paddingRight);i.style.width=r+"px"}}}}function v(e,t,n,i,o){var r=document.createElement("iframe");if(r.setAttribute("scrolling","no"),r.setAttribute("frameborder","0"),r.setAttribute("title","Interactive or visual content"),r.setAttribute("sandbox","allow-same-origin allow-forms allow-scripts allow-downloads allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"),t.appendChild(r),r.offsetParent||"fixed"===getComputedStyle(r).position)_(e,t,r,n,i,o);else{var a={embed_url:e,container:t,iframe:r,width:n,height:i,play_on_load:o};if(window._flourish_poll_items?window._flourish_poll_items.push(a):window._flourish_poll_items=[a],window._flourish_poll_items.length>1)return r;var s=setInterval((function(){window._flourish_poll_items=window._flourish_poll_items.filter((function(e){return!e.iframe.offsetParent||(_(e.embed_url,e.container,e.iframe,e.width,e.height,e.play_on_load),!1)})),window._flourish_poll_items.length||clearInterval(s)}),500)}return r}function _(e,t,n,i,o,r){var a;return i&&"number"==typeof i?(a=i,i+="px"):i&&i.match(/^[ \t\r\n\f]*([+-]?\d+|\d*\.\d+(?:[eE][+-]?\d+)?)(?:\\?[Pp]|\\0{0,4}[57]0(?:\r\n|[ \t\r\n\f])?)(?:\\?[Xx]|\\0{0,4}[57]8(?:\r\n|[ \t\r\n\f])?)[ \t\r\n\f]*$/)&&(a=parseFloat(i)),o&&"number"==typeof o&&(o+="px"),i?n.style.width=i:d()?n.style.width=t.offsetWidth+"px":n.style.width="100%",!!o||(e.match(/\?/)?e+="&auto=1":e+="?auto=1",o=s(a||n.offsetWidth)+"px"),o&&("%"===o.charAt(o.length-1)&&(o=parseFloat(o)/100*t.parentNode.offsetHeight+"px"),n.style.height=o),n.setAttribute("src",e+(r?"#play-on-load":"")),n}function y(e){return!Array.isArray(e)&&"object"==typeof e&&null!=e}function F(e,t){for(var n in t)y(e[n])&&y(t[n])?F(e[n],t[n]):e[n]=t[n];return e}!function(){var e,n=window.top===window.self,u=n?null:(t="#amp=1"==window.location.hash,{createEmbedIframe:v,isFixedHeight:a,getHeightForBreakpoint:s,startEventListeners:m,notifyParentWindow:l,isSafari:d,initCustomerAnalytics:r,addAnalyticsListener:o,sendCustomerAnalyticsMessage:i}),c=!0;function h(){var t;Flourish.fixed_height||(null!=e?t=e:c&&(t=u.getHeightForBreakpoint()),t!==window.innerHeight&&u.notifyParentWindow(t))}function f(){h(),window.addEventListener("resize",h)}Flourish.warn=function(e){if("string"==typeof e&&(e={message:e}),n||"editor"!==Flourish.environment)console.warn(e.message);else{var t={sender:"Flourish",method:"warn",message:e.message,explanation:e.explanation};window.parent.postMessage(JSON.stringify(t),"*")}},Flourish.uploadImage=function(e){if(n||"story_editor"!==Flourish.environment)throw"Invalid upload request";var t={sender:"Flourish",method:"request-upload",name:e.name,accept:e.accept};window.parent.postMessage(JSON.stringify(t),"*")},Flourish.setSetting=function(e,t){if("editor"===Flourish.environment||"sdk"===Flourish.environment){var n={sender:"Flourish",method:"setSetting",name:e,value:t};window.parent.postMessage(JSON.stringify(n),"*")}else if("story_editor"===Flourish.environment){var i={};i[e]=t,F(window.template.state,function(e){var t={};for(var n in e){for(var i=t,o=n.indexOf("."),r=0;o>=0;o=n.indexOf(".",r=o+1)){var a=n.substring(r,o);a in i||(i[a]={}),i=i[a]}i[n.substring(r)]=e[n]}return t}(i))}},Flourish.setHeight=function(t){Flourish.fixed_height||(e=t,c=null==t,h())},Flourish.checkHeight=function(){if(!n){var e=Flourish.__container_height;null!=e?(Flourish.fixed_height=!0,u.notifyParentWindow(e)):u.isFixedHeight()?Flourish.fixed_height=!0:(Flourish.fixed_height=!1,h())}},Flourish.fixed_height=n||u.isFixedHeight(),Flourish.enableCustomerAnalytics=function(){u&&u.initCustomerAnalytics()},"loading"===document.readyState?document.addEventListener("DOMContentLoaded",f):f()}()}();
2
2
  //# sourceMappingURL=embedded.js.map
Binary file