@flourish/sdk 3.15.1 → 3.17.2

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/server/data.js CHANGED
@@ -6,6 +6,12 @@
6
6
 
7
7
  Object.defineProperty(exports, '__esModule', { value: true });
8
8
 
9
+ var createInterpreter = require('@flourish/interpreter');
10
+
11
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
12
+
13
+ var createInterpreter__default = /*#__PURE__*/_interopDefaultLegacy(createInterpreter);
14
+
9
15
  // Polyfills for IE11 and Edge
10
16
 
11
17
  // Add findIndex method to Array
@@ -37,17 +43,47 @@ if (!Array.prototype.findIndex) {
37
43
  });
38
44
  }
39
45
 
40
- function extractData(data_binding, data_by_id) {
46
+ function extractData(data_binding, data_by_id, column_types_by_id, template_data_binding) {
41
47
  var columns = [];
42
48
  var data_table_ids = [];
43
49
  var num_rows = 0;
44
50
  var dataset = [];
51
+ var interpreters_by_id = {};
45
52
  dataset.column_names = {};
53
+ dataset.metadata = {};
54
+
55
+ function getInterpretationIds(data_table_id, column_index) {
56
+ if (!interpreters_by_id[data_table_id]) return {};
57
+ var by_column_index = interpreters_by_id[data_table_id];
58
+ if (!by_column_index[column_index]) return {};
59
+ return by_column_index[column_index];
60
+ }
61
+
62
+ function getInterpreter(data_table_id, column_index) {
63
+ const { type_id } = getInterpretationIds(data_table_id, column_index);
64
+ if (type_id) return createInterpreter__default["default"].getInterpretation(type_id);
65
+ }
66
+
67
+ for (var data_table_id in column_types_by_id) {
68
+ var lookup = {};
69
+ var column_types = column_types_by_id[data_table_id];
70
+ if (!column_types) continue;
71
+
72
+ for (let i = 0; i < column_types.length; i++) {
73
+ const type_id = column_types[i].type_id;
74
+ const of_id = column_types[i].output_format_id;
75
+ const output_format_id = (!of_id || of_id === "auto") ? type_id : of_id;
76
+ lookup[column_types[i].index] = { type_id, output_format_id };
77
+ }
78
+ interpreters_by_id[data_table_id] = lookup;
79
+ }
46
80
 
47
81
  for (var key in data_binding) {
82
+ if (data_binding[key] === null) continue;
48
83
  if (data_binding[key].columns === undefined && data_binding[key].column === undefined) continue;
49
84
 
50
85
  var b = data_binding[key];
86
+ b.template_data_binding = template_data_binding[key];
51
87
  b.key = key;
52
88
 
53
89
  if (!(b.data_table_id in data_by_id)) {
@@ -68,11 +104,30 @@ function extractData(data_binding, data_by_id) {
68
104
  var column_count = data_table[0].length;
69
105
  b.columns = b.columns.filter(function(i) { return i < column_count; });
70
106
  dataset.column_names[key] = b.columns.map(function(i) {
71
- return data_by_id[b.data_table_id][0][i];
107
+ return data_table[0][i];
108
+ });
109
+ dataset.metadata[key] = b.columns.map(function(i) {
110
+ const { type_id, output_format_id } = getInterpretationIds(b.data_table_id, i);
111
+ if (type_id) {
112
+ return {
113
+ type: type_id.split("$")[0],
114
+ type_id,
115
+ output_format_id: output_format_id
116
+ };
117
+ }
118
+ return null;
72
119
  });
73
120
  }
74
121
  else if ("column" in b && b.column != null) {
75
122
  dataset.column_names[key] = data_table[0][b.column];
123
+ const { type_id, output_format_id } = getInterpretationIds(b.data_table_id, b.column);
124
+ if (type_id) {
125
+ dataset.metadata[key] = {
126
+ type: type_id.split("$")[0],
127
+ type_id,
128
+ output_format_id: output_format_id
129
+ };
130
+ }
76
131
  }
77
132
  else {
78
133
  throw new Error("Data binding includes no column(s) specification: " + JSON.stringify(b));
@@ -80,11 +135,22 @@ function extractData(data_binding, data_by_id) {
80
135
 
81
136
  if (data_table_ids.indexOf(b.data_table_id) == -1) {
82
137
  data_table_ids.push(b.data_table_id);
83
- num_rows = Math.max(num_rows, data_by_id[b.data_table_id].length - 1);
138
+ num_rows = Math.max(num_rows, data_table.length - 1);
84
139
  }
85
140
  columns.push(b);
86
141
  }
87
142
 
143
+ function parse(b, column_index, string_value) {
144
+ if (!b.template_data_binding.data_type) return string_value;
145
+ var interpreter = getInterpreter(b.data_table_id, column_index);
146
+ var result = interpreter ? interpreter.parse(string_value) : string_value;
147
+
148
+ // We require our marshalled data to be JSON-serialisable,
149
+ // therefore we convert NaNs to null here.
150
+ if (Number.isNaN(result)) result = null;
151
+ return result;
152
+ }
153
+
88
154
  for (var i = 0; i < num_rows; i++) {
89
155
  var o = {};
90
156
  for (var j = 0; j < columns.length; j++) {
@@ -95,22 +161,29 @@ function extractData(data_binding, data_by_id) {
95
161
  if ("columns" in b && b.columns != null) {
96
162
  o[b.key] = b.columns
97
163
  .filter(function(c) { return c < table[i+1].length; })
98
- .map(function(c) { return table[i+1][c]; });
164
+ .map(function(c) { return parse(b, c, table[i+1][c]); });
99
165
  }
100
166
  else if ("column" in b && b.column != null) {
101
- if (b.column >= table[i+1].length) o[b.key] = "";
102
- else o[b.key] = table[i+1][b.column];
167
+ if (b.column >= table[i+1].length) o[b.key] = parse(b, b.column, "");
168
+ else o[b.key] = parse(b, b.column, table[i+1][b.column]);
103
169
  }
104
170
  }
105
171
  dataset.push(o);
106
172
  }
107
-
108
173
  return dataset;
109
174
  }
110
175
 
176
+ function getColumnTypesForData(data) {
177
+ return transposeNestedArray(data)
178
+ .map(function(d, i) {
179
+ const type_id = interpretColumn(d)[0].id;
180
+ return { type_id: type_id, index: i, output_format_id: type_id };
181
+ });
182
+ }
183
+
111
184
  function trimTrailingEmptyRows(data) {
112
185
  for (var i = data.length; i-- > 1;) {
113
- if (!data[i] || !data[i].length || data[i].findIndex(function(col) { return col !== null && col !== ""; }) == -1) {
186
+ if (!data[i] || !data[i].length || (Array.isArray(data[i]) && data[i].findIndex(function(col) { return col !== null && col !== ""; }) == -1)) {
114
187
  data.splice(i, 1);
115
188
  }
116
189
  else break;
@@ -118,6 +191,17 @@ function trimTrailingEmptyRows(data) {
118
191
  return data;
119
192
  }
120
193
 
194
+ function dropReturnCharacters(data) {
195
+ for (const row of data) {
196
+ for (let i = 0; i < row.length; i++) {
197
+ // Replace new-line character and surrounding whitespace with single space
198
+ // This fixes issue with pasting cells containing long strings from Excel into HoT
199
+ row[i] = row[i].replace(/(\r\n|\n|\r)/g, " ");
200
+ }
201
+ }
202
+ return data;
203
+ }
204
+
121
205
  function trimWhitespace(data) {
122
206
  data.forEach(function(row) {
123
207
  for (var i=0; i < row.length; i++) {
@@ -127,6 +211,59 @@ function trimWhitespace(data) {
127
211
  return data;
128
212
  }
129
213
 
214
+
215
+ var ERROR_STRINGS = ["#DIV/0", "#N/A", "#NAME?", "#NULL!", "#NUM!", "#REF!", "#VALUE!", "#ERROR!"];
216
+ var interpreter = createInterpreter__default["default"]().nMax(Infinity).nFailingValues(8).failureFraction(0.1);
217
+
218
+
219
+ function stripCommonFixes(str) {
220
+ str = str || "";
221
+ return str.replace(/[€£$¥%º]/g, "");
222
+ }
223
+
224
+
225
+ function transposeNestedArray(nested_array) {
226
+ var n_inner = nested_array.length;
227
+ var n_outer = nested_array[0].length;
228
+ var transposed_array = [];
229
+
230
+ for (var i = 0; i < n_outer; i++) {
231
+ var data = [];
232
+ for (var j = 0; j < n_inner; j++) {
233
+ data.push(nested_array[j][i]);
234
+ }
235
+ transposed_array.push(data);
236
+ }
237
+
238
+ return transposed_array;
239
+ }
240
+
241
+
242
+ function getSlicedData(arr) {
243
+ const n = arr.length;
244
+ if (n > 100) return arr.slice(10, n - 10);
245
+ if (n > 50) return arr.slice(5, n - 5);
246
+ if (n > 30) return arr.slice(4, n - 4);
247
+ if (n > 20) return arr.slice(3, n - 3);
248
+ if (n > 10) return arr.slice(2, n - 2);
249
+ if (n > 1) return arr.slice(1, n);
250
+ return arr.slice(0, 1);
251
+ }
252
+
253
+
254
+ function interpretColumn(arr) {
255
+ var idata = arr.filter(function(d) {
256
+ return d && !ERROR_STRINGS.includes(d.trim());
257
+ })
258
+ .map(stripCommonFixes);
259
+ return interpreter(idata);
260
+ }
261
+
262
+ exports.dropReturnCharacters = dropReturnCharacters;
130
263
  exports.extractData = extractData;
264
+ exports.getColumnTypesForData = getColumnTypesForData;
265
+ exports.getSlicedData = getSlicedData;
266
+ exports.interpretColumn = interpretColumn;
267
+ exports.transposeNestedArray = transposeNestedArray;
131
268
  exports.trimTrailingEmptyRows = trimTrailingEmptyRows;
132
269
  exports.trimWhitespace = trimWhitespace;
package/server/index.js CHANGED
@@ -9,7 +9,6 @@ const crypto = require("crypto"),
9
9
  chokidar = require("chokidar"),
10
10
  d3_dsv = require("d3-dsv"),
11
11
  express = require("express"),
12
- handlebars = require("handlebars"),
13
12
  shell_quote = require("shell-quote"),
14
13
  ws = require("ws"),
15
14
  yaml = require("js-yaml"),
@@ -23,6 +22,9 @@ const crypto = require("crypto"),
23
22
  log = require("../lib/log"),
24
23
  sdk = require("../lib/sdk");
25
24
 
25
+ const { allowInsecurePrototypeAccess } = require("@handlebars/allow-prototype-access");
26
+ const handlebars = allowInsecurePrototypeAccess(require("handlebars"));
27
+
26
28
  const TA = require("parse5/lib/tree-adapters/default.js");
27
29
 
28
30
  // Generate a static prefix randomly
@@ -94,7 +96,7 @@ function loadJavaScript(template_dir) {
94
96
  }
95
97
 
96
98
  function loadSettings(template_dir) {
97
- return sdk.readAndValidateConfig(template_dir);
99
+ return sdk.readAndValidateConfig(template_dir).then(({config}) => config);
98
100
  }
99
101
 
100
102
  function listDataTables(template_dir) {
@@ -121,10 +123,15 @@ function getData(template_dir, data_tables) {
121
123
  return Promise.all(data_tables.map((data_table) => getDataTable(template_dir, data_table)))
122
124
  .then((data_array) => {
123
125
  const data_by_name = {};
126
+ const column_types_by_name = {};
124
127
  for (var i = 0; i < data_tables.length; i++) {
125
128
  data_by_name[data_tables[i]] = data_array[i];
126
129
  }
127
- return data_by_name;
130
+ for (const data_table in data_by_name) {
131
+ const data = data_by_name[data_table];
132
+ column_types_by_name[data_table] = data_utils.getColumnTypesForData(data);
133
+ }
134
+ return { data: data_by_name, column_types_by_name };
128
135
  });
129
136
  }
130
137
 
@@ -139,7 +146,12 @@ function getDataTable(template_dir, data_table) {
139
146
  }
140
147
 
141
148
  function parseDataBindings(data_bindings, data_tables) {
142
- if (!data_bindings) return {1: {}};
149
+ if (!data_bindings) {
150
+ return {
151
+ data_bindings: {1: {}},
152
+ template_data_bindings: {1: {}}
153
+ };
154
+ }
143
155
 
144
156
  // Use the names as ids
145
157
  const name_by_id = {};
@@ -147,14 +159,20 @@ function parseDataBindings(data_bindings, data_tables) {
147
159
 
148
160
  // Collect parsed bindings by dataset
149
161
  const data_bindings_by_dataset = {};
162
+ const template_data_bindings_by_dataset = {};
150
163
  for (let binding of data_bindings) {
151
164
  let dataset = binding.dataset;
152
165
  if (!dataset) continue;
153
166
 
154
167
  if (!data_bindings_by_dataset[dataset]) data_bindings_by_dataset[dataset] = {};
168
+ if (!template_data_bindings_by_dataset[dataset]) template_data_bindings_by_dataset[dataset] = {};
169
+ template_data_bindings_by_dataset[dataset][binding.key] = binding;
155
170
  data_bindings_by_dataset[dataset][binding.key] = columns.parseDataBinding(binding, name_by_id);
156
171
  }
157
- return { 1: data_bindings_by_dataset };
172
+ return {
173
+ data_bindings: { 1: data_bindings_by_dataset },
174
+ template_data_bindings: { 1: template_data_bindings_by_dataset }
175
+ };
158
176
  }
159
177
 
160
178
  function documentFragment(elements) {
@@ -176,23 +194,24 @@ function scriptElementExternal(url) {
176
194
  }
177
195
 
178
196
 
179
- function loadTemplate(template_dir, sdk_template, build_failed) {
197
+ function loadTemplate(template_dir, sdk_template, build_failed, options) {
180
198
  return Promise.all([
181
199
  listDataTables(template_dir),
182
200
  loadSettings(template_dir),
183
201
  ])
184
202
  .then(([data_tables, settings]) => {
185
- const data_bindings = parseDataBindings(settings.data, data_tables);
203
+ const { data_bindings, template_data_bindings } = parseDataBindings(settings.data, data_tables);
186
204
  return Promise.all([
187
205
  settings, data_bindings, data_tables,
188
- previewInitJs(template_dir, data_bindings["1"], data_tables),
206
+ previewInitJs(template_dir, template_data_bindings["1"], data_bindings["1"], data_tables),
189
207
  loadTemplateText(template_dir),
190
- loadJavaScript(template_dir)
208
+ loadJavaScript(template_dir),
209
+ getPublicUrlPrefix(options)
191
210
  ]);
192
211
  })
193
212
  .then(([
194
213
  settings, data_bindings, data_tables,
195
- preview_init_js, template_text, template_js
214
+ preview_init_js, template_text, template_js, public_url_prefix
196
215
  ]) => {
197
216
  const page_params = {
198
217
  // Always use ID of 1 for SDK
@@ -206,12 +225,13 @@ function loadTemplate(template_dir, sdk_template, build_failed) {
206
225
  template_name: settings.name || "Untitled template",
207
226
  template_version: settings.version,
208
227
  template_author: settings.author || "",
209
- build_failed: build_failed && build_failed.size > 0
228
+ build_failed: build_failed && build_failed.size > 0,
229
+ public_url_prefix
210
230
  };
211
231
 
212
232
  const script = documentFragment([
213
233
  scriptElementInline("window.Flourish = " + json.safeStringify({
214
- static_prefix, environment: "sdk"
234
+ static_prefix, environment: "sdk", is_read_only: false
215
235
  }) + ";"),
216
236
  scriptElementExternal("/template.js"),
217
237
  scriptElementExternal("/comms.js"),
@@ -220,7 +240,7 @@ function loadTemplate(template_dir, sdk_template, build_failed) {
220
240
 
221
241
  const preview_script = documentFragment([
222
242
  scriptElementInline("window.Flourish = " + json.safeStringify({
223
- static_prefix: preview_static_prefix, environment: "sdk"
243
+ static_prefix: preview_static_prefix, environment: "preview", is_read_only: true
224
244
  }) + ";"),
225
245
  scriptElementExternal("/template.js"),
226
246
  scriptElementExternal("/comms.js"),
@@ -251,24 +271,31 @@ function loadTemplate(template_dir, sdk_template, build_failed) {
251
271
  }));
252
272
  }
253
273
 
254
- function previewInitJs(template_dir, data_bindings, data_tables) {
255
- return getData(template_dir, data_tables).then((data) => {
274
+ function previewInitJs(template_dir, template_data_bindings, data_bindings, data_tables) {
275
+ return getData(template_dir, data_tables).then(({data, column_types_by_name}) => {
256
276
  const prepared_data = {};
257
277
  for (let dataset in data_bindings) {
258
- prepared_data[dataset] = data_utils.extractData(data_bindings[dataset], data);
278
+ prepared_data[dataset] = data_utils.extractData(
279
+ data_bindings[dataset], data, column_types_by_name,
280
+ template_data_bindings[dataset],
281
+ );
259
282
  }
260
283
 
261
284
  const column_names = {};
285
+ const metadata = {};
262
286
  for (let dataset in prepared_data) {
263
287
  column_names[dataset] = prepared_data[dataset].column_names;
288
+ metadata[dataset] = prepared_data[dataset].metadata;
264
289
  }
265
290
 
266
291
  return `
267
292
  var _Flourish_data_column_names = ${json.safeStringify(column_names)},
268
- _Flourish_data = ${json.safeStringify(prepared_data)};
293
+ _Flourish_data_metadata = ${json.safeStringify(metadata)},
294
+ _Flourish_data = ${json.javaScriptStringify(prepared_data)};
269
295
  for (var _Flourish_dataset in _Flourish_data) {
270
296
  window.template.data[_Flourish_dataset] = _Flourish_data[_Flourish_dataset];
271
297
  window.template.data[_Flourish_dataset].column_names = _Flourish_data_column_names[_Flourish_dataset];
298
+ window.template.data[_Flourish_dataset].metadata = _Flourish_data_metadata[_Flourish_dataset];
272
299
  }
273
300
  window.template.draw();
274
301
  `;
@@ -309,11 +336,16 @@ function splitPath(p) {
309
336
  return p.split(path.sep).filter(c => c != "");
310
337
  }
311
338
 
339
+ function getPublicUrlPrefix(options) {
340
+ return sdk.request(options, "config.json")
341
+ .then((config) => {
342
+ return config.PUBLIC_BUCKET_PREFIX;
343
+ });
344
+ }
312
345
 
313
346
  module.exports = function(template_dir, options) {
314
347
  let app = express(),
315
348
  reloadPreview,
316
-
317
349
  template;
318
350
 
319
351
  // Editor and settings/bindings
@@ -424,7 +456,7 @@ module.exports = function(template_dir, options) {
424
456
  function _reloadTemplate() {
425
457
  reload_timer = null;
426
458
  log.info("Reloading...");
427
- loadTemplate(template_dir, sdk_template, build_failed)
459
+ loadTemplate(template_dir, sdk_template, build_failed, options)
428
460
  .then((template_) => {
429
461
  template = template_;
430
462
  log.info("Template reloaded. Trying to reload preview.");
@@ -517,7 +549,7 @@ module.exports = function(template_dir, options) {
517
549
  loadSDKTemplate()
518
550
  .then((sdk_template) => {
519
551
  return Promise.all([
520
- sdk_template, loadTemplate(template_dir, sdk_template)
552
+ sdk_template, loadTemplate(template_dir, sdk_template, undefined, options)
521
553
  ]);
522
554
  })
523
555
  .then(([sdk_template, template]) => {
@@ -8,7 +8,7 @@ const parse5 = require("parse5"),
8
8
  const TA = require("parse5/lib/tree-adapters/default.js");
9
9
 
10
10
  function findChild(node, nodeName, ok_if_not_found) {
11
- for (let child of TA.getChildNodes(node)) {
11
+ for (const child of TA.getChildNodes(node)) {
12
12
  if (child.nodeName == nodeName) return child;
13
13
  }
14
14
  if (ok_if_not_found) return null;
@@ -27,26 +27,12 @@ function findBody(document) {
27
27
  return findChild(findHtmlNode(document), "body");
28
28
  }
29
29
 
30
- function addAriaHiddenAttributeToBody(document) {
31
- const body = findBody(document);
32
- let found = false;
33
- for (const attr of body.attrs) {
34
- if (attr.name == "aria-hidden") {
35
- attr.value = "true";
36
- found = true;
37
- }
38
- }
39
- if (!found) {
40
- body.attrs.push({ name: "aria-hidden", value: "true" });
41
- }
42
- }
43
-
44
30
  function replaceTitle(document, title) {
45
31
  const head = findHead(document);
46
32
  let title_node = findChild(head, "title", true);
47
33
 
48
34
  if (title_node) {
49
- for (let child of TA.getChildNodes(title_node)) {
35
+ for (const child of TA.getChildNodes(title_node)) {
50
36
  TA.detachNode(child);
51
37
  }
52
38
  }
@@ -60,14 +46,24 @@ function replaceTitle(document, title) {
60
46
 
61
47
  function appendFragmentToBody(document, fragment) {
62
48
  const body = findBody(document);
63
- for (let child of TA.getChildNodes(fragment)) {
49
+ for (const child of TA.getChildNodes(fragment)) {
64
50
  TA.appendChild(body, child);
65
51
  }
66
52
  }
67
53
 
54
+ function insertOembedLink(document, oembed_url) {
55
+ const head = findHead(document);
56
+ const link_node = TA.createElement("link", head.namespaceURI, [
57
+ { name: "rel", value: "alternate" },
58
+ { name: "type", value: "application/json+oembed" },
59
+ { name: "href", value: oembed_url }
60
+ ]);
61
+ TA.appendChild(head, link_node);
62
+ }
63
+
68
64
  function insertCanonicalLink(document, canonical_url) {
69
65
  const head = findHead(document);
70
- let link_node = TA.createElement("link", head.namespaceURI, [
66
+ const link_node = TA.createElement("link", head.namespaceURI, [
71
67
  { name: "rel", value: "canonical" },
72
68
  { name: "href", value: canonical_url }
73
69
  ]);
@@ -83,7 +79,7 @@ function rewriteLinks(document, static_prefix) {
83
79
  // ... or relative self-links
84
80
  if (url == "" || url == ".") return url;
85
81
 
86
- return URL.resolve(static_prefix, url);
82
+ return URL.resolve(static_prefix, url); // eslint-disable-line node/no-deprecated-api
87
83
  });
88
84
 
89
85
  return rewriter.rewriteDocument(document);
@@ -93,14 +89,12 @@ function render(template_text, params) {
93
89
  const document = parse5.parse(template_text),
94
90
  script_fragment = params.parsed_script || parse5.parseFragment(params.script);
95
91
 
96
- addAriaHiddenAttributeToBody(document);
97
92
  replaceTitle(document, params.title);
98
93
  if (params.canonical_url) insertCanonicalLink(document, params.canonical_url);
94
+ if (params.oembed_url) insertOembedLink(document, params.oembed_url);
99
95
  appendFragmentToBody(document, script_fragment);
100
96
  return rewriteLinks(document, params.static)
101
97
  .then(parse5.serialize.bind(parse5));
102
98
  }
103
99
 
104
- module.exports = {
105
- render
106
- };
100
+ exports.render = render;
package/server/json.js CHANGED
@@ -20,8 +20,33 @@ function safeStringify(obj) {
20
20
  return raw.replace(/[\u2028\u2029<]/g, escapeChar);
21
21
  }
22
22
 
23
+ function javaScriptStringify(v) {
24
+ var type = typeof v;
25
+ if (v == null) {
26
+ // Catches both null and undefined
27
+ return "null";
28
+ }
29
+ else if (type === "number" || type === "boolean" || type === "bigint" || type === "string" || type === "symbol") {
30
+ return safeStringify(v);
31
+ }
32
+ if (Array.isArray(v)) {
33
+ return "[" + v.map(javaScriptStringify).join(",") + "]";
34
+ }
35
+ else if (v instanceof Date) {
36
+ return "new Date(" + v.getTime() + ")";
37
+ }
38
+ else if (Object.prototype.toString.call(v) === "[object Object]") {
39
+ return "{" + Object.keys(v).map(function (k) {
40
+ return safeStringify(k) + ":" + javaScriptStringify(v[k]);
41
+ }) + "}";
42
+ }
43
+ else {
44
+ throw new Error("javaScriptStringify couldn't handle " + type + " object: " + v);
45
+ }
46
+ }
47
+
23
48
  function stringifyDataset(dataset) {
24
- return "(function(array, fields){ array.column_names = fields; return array; })(" + safeStringify(dataset) + ", " + safeStringify(dataset.column_names) + ")";
49
+ return "(function(array, column_names, metadata){ array.column_names = column_names; array.metadata = metadata; return array; })(" + javaScriptStringify(dataset) + ", " + safeStringify(dataset.column_names) + ", " + safeStringify(dataset.metadata) + ")";
25
50
  }
26
51
 
27
52
  function stringifyPreparedData(data) {
@@ -36,5 +61,6 @@ function stringifyPreparedData(data) {
36
61
  return s;
37
62
  }
38
63
 
64
+ exports.javaScriptStringify = javaScriptStringify;
39
65
  exports.safeStringify = safeStringify;
40
66
  exports.stringifyPreparedData = stringifyPreparedData;
@@ -7,6 +7,6 @@
7
7
  html, body { height: 100%; margin: 0; overflow: hidden; }
8
8
  </style>
9
9
  </head>
10
- <body aria-hidden="true">
10
+ <body>
11
11
  </body>
12
12
  </html>
@@ -50,8 +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">
54
- <iframe id="preview" sandbox="allow-same-origin allow-scripts allow-downloads" src="about:blank"></iframe>
53
+ <img class="loading-spinner" src="/images/bosh.svg" data-testid="loading-indicator">
54
+ <div class="preview-overlay">
55
+ <p class="empty-label"></p>
56
+ <p class="empty-details"></p>
57
+ </div>
58
+ <iframe id="preview" sandbox="allow-same-origin allow-scripts allow-downloads" src="about:blank" data-testid="visualisation-iframe"></iframe>
55
59
  <div id="resize-overlay"></div>
56
60
  <div id="resize-handle-container">
57
61
  <div id="resize-handle"></div>
@@ -68,6 +72,7 @@
68
72
  </div>
69
73
 
70
74
  <div class="template-settings"></div>
75
+ <div class="detailed-settings"></div>
71
76
  </div>
72
77
  </div>
73
78
  </div>
@@ -87,8 +92,8 @@
87
92
  </div>
88
93
 
89
94
  <script>
90
- Flourish.initSDK({{{ visualisation_js }}}, {{{ settings }}}, {{{ data_bindings }}});
91
- Flourish.app.preview_pane.loadTemplate();
95
+ const sdk = Flourish.initSDK({{{ visualisation_js }}}, {{{ settings }}}, {{{ data_bindings }}}, "{{ public_url_prefix }}");
96
+ Flourish.app.preview_pane.loadTemplate(() => sdk.loadFromUrlHash());
92
97
  Flourish.talkToServer();
93
98
  </script>
94
99
  </body>
package/site/embedded.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(){"use strict";var t,o,n,i,r,a;function e(){if(null==t){var e=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}();t="referrer"in e?/^https:\/\/medium.com\//.test(e.referrer):!("auto"in e)}return t}function s(e){var t=e||window.innerWidth;return 999<t?650:599<t?575:400}function d(e,t){if(window.top!==window.self){var n=window;if("srcdoc"==n.location.pathname&&(n=n.parent),o)return e=parseInt(e,10),void n.parent.postMessage({sentinel:"amp",type:"embed-size",height:e},"*");var i={sender:"Flourish",context:"iframe.resize",method:"resize",height:e,src:n.location.toString()};if(t)for(var r in t)i[r]=t[r];n.parent.postMessage(JSON.stringify(i),"*")}}function u(){return(-1!==navigator.userAgent.indexOf("Safari")||-1!==navigator.userAgent.indexOf("iPhone"))&&-1==navigator.userAgent.indexOf("Chrome")}function h(r){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$/))){var e;try{e=JSON.parse(t.data)}catch(e){return void console.warn("Unexpected non-JSON message: "+JSON.stringify(t.data))}if("Flourish"===e.sender){for(var n=document.querySelectorAll("iframe"),i=0;i<n.length;i++)if(n[i].contentWindow==t.source||n[i].contentWindow==t.source.parent)return void r(e,n[i]);console.warn("could not find frame",e)}}}),u()&&(window.addEventListener("resize",f),f())}function f(){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"),r=window.getComputedStyle(n),o=n.offsetWidth-parseFloat(r.paddingLeft)-parseFloat(r.paddingRight);i.style.width=o+"px"}}}function l(e,t,n,i){var r;n&&"number"==typeof n?(r=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]*$/)&&(r=parseFloat(n)),i&&"number"==typeof i&&(i+="px");var o=document.createElement("iframe");return o.setAttribute("scrolling","no"),o.setAttribute("frameborder","0"),t.appendChild(o),n?o.style.width=n:u()?o.style.width=t.offsetWidth+"px":o.style.width="100%",!i&&(e.match(/\?/)?e+="&auto=1":e+="?auto=1",i=s(r||o.offsetWidth)+"px"),i&&("%"===i.charAt(i.length-1)&&(i=parseFloat(i)/100*t.parentNode.offsetHeight+"px"),o.style.height=i),o.setAttribute("src",e),o}function c(){var e;Flourish.fixed_height||(null!=n?e=n:a&&(e=r.getHeightForBreakpoint()),e!==window.innerHeight&&r.notifyParentWindow(e))}function g(){c(),window.addEventListener("resize",c)}i=window.top===window.self,r=i?null:(o="#amp=1"==window.location.hash,{createEmbedIframe:l,isFixedHeight:e,getHeightForBreakpoint:s,startEventListeners:h,notifyParentWindow:d,isSafari:u}),a=!0,Flourish.warn=function(e){if("string"==typeof e&&(e={message:e}),i||"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(i||"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.setHeight=function(e){Flourish.fixed_height||(a=null==(n=e),c())},Flourish.checkHeight=function(){if(!i){var e=Flourish.__container_height;null!=e?(Flourish.fixed_height=!0,r.notifyParentWindow(e)):r.isFixedHeight()?Flourish.fixed_height=!0:(Flourish.fixed_height=!1,c())}},Flourish.fixed_height=i||r.isFixedHeight(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",g):g()}();
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()}()}();
2
2
  //# sourceMappingURL=embedded.js.map
@@ -0,0 +1,6 @@
1
+ <svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="0.5" y="1.5" width="9" height="8" rx="1.5" stroke="white"/>
3
+ <line x1="1" y1="4.5" x2="9" y2="4.5" stroke="white"/>
4
+ <line x1="2.5" x2="2.13636" y2="2" stroke="white" stroke-linejoin="round"/>
5
+ <line x1="7.5" x2="7.13636" y2="2" stroke="white" stroke-linejoin="round"/>
6
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="12" height="5" viewBox="0 0 12 5" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M0 4.90895V4.00607H1.04704V1.22155H0.144158V0.531108C0.407183 0.480526 0.629742 0.419828 0.811836 0.349014C0.99393 0.278199 1.17097 0.19221 1.34294 0.0910468H2.16237V4.00607H3.06525V4.90895H0Z" fill="white"/>
3
+ <path d="M4.10122 4.90895V4.27162C4.40977 3.98331 4.6905 3.71522 4.94341 3.46737C5.20137 3.21447 5.4214 2.97926 5.6035 2.76176C5.79065 2.5392 5.93481 2.33434 6.03597 2.14719C6.14219 1.95498 6.1953 1.77289 6.1953 1.60091C6.1953 1.36318 6.13461 1.18361 6.01321 1.06222C5.89181 0.935761 5.72489 0.872534 5.51245 0.872534C5.33541 0.872534 5.17861 0.923116 5.04204 1.02428C4.90547 1.12038 4.77649 1.23419 4.65509 1.36571L4.04811 0.766313C4.28584 0.513404 4.52864 0.323723 4.77649 0.197268C5.02434 0.0657561 5.32024 0 5.6642 0C5.90193 0 6.1169 0.0379362 6.30911 0.113809C6.50638 0.184623 6.67583 0.288316 6.81746 0.424886C6.95909 0.556399 7.06784 0.715731 7.14371 0.902883C7.21958 1.09004 7.25752 1.29995 7.25752 1.53263C7.25752 1.73495 7.21452 1.94234 7.12854 2.15478C7.04255 2.36216 6.92621 2.57208 6.77952 2.78452C6.63789 2.99191 6.4735 3.20182 6.28635 3.41426C6.10426 3.62165 5.91457 3.8265 5.71731 4.02883C5.8387 4.01366 5.97274 4.00101 6.11943 3.9909C6.27118 3.97572 6.40522 3.96813 6.52155 3.96813H7.49272V4.90895H4.10122Z" fill="white"/>
4
+ <path d="M9.98462 5C9.59008 5 9.25624 4.9393 8.9831 4.81791C8.70996 4.69145 8.48487 4.52453 8.30783 4.31715L8.82377 3.61153C8.97045 3.75316 9.12978 3.8695 9.30176 3.96055C9.4788 4.05159 9.67101 4.09712 9.87839 4.09712C10.1161 4.09712 10.3058 4.04906 10.4474 3.95296C10.5891 3.8518 10.6599 3.71017 10.6599 3.52807C10.6599 3.42185 10.6396 3.32575 10.5992 3.23976C10.5638 3.15377 10.498 3.08295 10.4019 3.02731C10.3058 2.96662 10.1743 2.92109 10.0074 2.89074C9.84046 2.85534 9.62549 2.83763 9.36246 2.83763V2.04856C9.57996 2.04856 9.75953 2.03338 9.90116 2.00303C10.0478 1.97269 10.1642 1.92969 10.2502 1.87405C10.3412 1.81335 10.4044 1.74507 10.4399 1.6692C10.4803 1.58826 10.5005 1.49975 10.5005 1.40364C10.5005 1.23672 10.45 1.10774 10.3488 1.01669C10.2476 0.920587 10.101 0.872534 9.90874 0.872534C9.73677 0.872534 9.57996 0.91047 9.43833 0.986343C9.30176 1.06222 9.1576 1.16591 9.00586 1.29742L8.4444 0.614567C8.66696 0.422357 8.89964 0.273141 9.14243 0.16692C9.39028 0.0556399 9.66342 0 9.96185 0C10.2097 0 10.4348 0.0303489 10.6371 0.0910468C10.8445 0.146687 11.019 0.232676 11.1606 0.349014C11.3073 0.460293 11.4211 0.596864 11.5021 0.758725C11.583 0.920586 11.6235 1.10774 11.6235 1.32018C11.6235 1.57309 11.5526 1.78806 11.411 1.9651C11.2744 2.13708 11.0772 2.27871 10.8192 2.38998V2.42033C11.0974 2.50126 11.325 2.64036 11.5021 2.83763C11.6842 3.02984 11.7752 3.28275 11.7752 3.59636C11.7752 3.81892 11.7272 4.01872 11.6311 4.19575C11.5349 4.36773 11.406 4.51442 11.2441 4.63581C11.0822 4.75215 10.8926 4.8432 10.6751 4.90895C10.4576 4.96965 10.2274 5 9.98462 5Z" fill="white"/>
5
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="9" height="10" viewBox="0 0 9 10" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M8.94856 5.94937C8.92747 5.82279 8.85363 5.71731 8.73759 5.65402L6.70173 4.64136C6.42747 5.20043 6.06882 5.89663 5.58359 6.74051C5.35152 7.14136 4.94013 7.38397 4.47599 7.38397C4.01186 7.38397 3.58992 7.14136 3.35785 6.74051C2.91481 5.98102 2.57726 5.34811 2.32409 4.82068L1.18485 5.01056C1.05827 5.03165 0.952785 5.11604 0.900042 5.22153L0.0350632 7.173C-0.049325 7.35233 0.0245146 7.57385 0.193291 7.65823L4.38105 9.95781C4.4338 9.98946 4.49709 10 4.56038 10C4.58148 10 4.61312 10 4.63422 9.98946L8.21017 9.25106C8.3262 9.22997 8.42114 9.15613 8.47388 9.05064C8.52662 8.94516 8.52662 8.82912 8.48443 8.72364L7.78823 7.16245L8.83253 6.26583C8.92747 6.20254 8.96966 6.07596 8.94856 5.94937Z" fill="white"/>
3
+ <path d="M4.92955 6.37131C5.58356 5.23207 6.7228 3.13291 6.7228 2.24684C6.7228 1.00211 5.71014 0 4.47596 0C3.24178 0 2.22913 1.01266 2.22913 2.24684C2.22913 3.13291 3.36837 5.23207 4.02237 6.37131C4.2228 6.71941 4.72913 6.71941 4.92955 6.37131ZM3.43166 2.23629C3.43166 1.65612 3.90634 1.19198 4.47596 1.19198C5.04558 1.19198 5.52027 1.66667 5.52027 2.23629C5.52027 2.81646 5.04558 3.28059 4.47596 3.28059C3.90634 3.28059 3.43166 2.81646 3.43166 2.23629Z" fill="white"/>
4
+ </svg>