annotate test_create/test_core.js @ 2908:9c26e579e64d

Test create supports template XMLs. Added ITU-R BS.1116 template.
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Tue, 01 Aug 2017 09:13:57 +0100
parents a1e294297380
children e2b241c88b74
rev   line source
nicholas@2885 1 /* globals document, angular, window, Promise, XMLHttpRequest, Specification, XMLSerializer, Blob, DOMParser, FileReader, $*/
nicholas@2851 2 function get(url) {
nicholas@2851 3 // Return a new promise.
nicholas@2851 4 return new Promise(function (resolve, reject) {
nicholas@2851 5 // Do the usual XHR stuff
nicholas@2851 6 var req = new XMLHttpRequest();
nicholas@2851 7 req.open('GET', url);
nickjillings@1370 8
nicholas@2851 9 req.onload = function () {
nicholas@2851 10 // This is called even on 404 etc
nicholas@2851 11 // so check the status
nicholas@2851 12 if (req.status == 200) {
nicholas@2851 13 // Resolve the promise with the response text
nicholas@2851 14 resolve(req.response);
nicholas@2851 15 } else {
nicholas@2851 16 // Otherwise reject with the status text
nicholas@2851 17 // which will hopefully be a meaningful error
nicholas@2851 18 reject(Error(req.statusText));
nicholas@2851 19 }
nicholas@2851 20 };
nicholas@2851 21
nicholas@2851 22 // Handle network errors
nicholas@2851 23 req.onerror = function () {
nicholas@2851 24 reject(Error("Network Error"));
nicholas@2851 25 };
nicholas@2851 26
nicholas@2851 27 // Make the request
nicholas@2851 28 req.send();
nicholas@2851 29 });
nickjillings@1370 30 }
nickjillings@1370 31
nicholas@2851 32 var AngularInterface = angular.module("creator", []);
nickjillings@1370 33
nicholas@2851 34 var specification = new Specification();
nickjillings@1370 35
nicholas@2535 36 window.onload = function () {
nicholas@2851 37 // Get the test interface specifications
nicholas@2884 38 $(function () {
nicholas@2884 39 $('[data-toggle="popover"]').popover();
nicholas@2884 40 });
nicholas@2851 41 };
nicholas@2535 42
nicholas@2861 43 function handleFiles(event) {
nicholas@2861 44 var s = angular.element(event.currentTarget).scope();
nicholas@2861 45 s.handleFiles(event);
nicholas@2861 46 s.$apply();
nicholas@2861 47 }
nicholas@2861 48
nicholas@2851 49 AngularInterface.controller("view", ['$scope', '$element', '$window', function ($s, $e, $w) {
nicholas@2851 50 $s.popupVisible = true;
nicholas@2864 51 $s.testSpecifications = {};
nicholas@2864 52
nicholas@2864 53 (function () {
nicholas@2864 54 new Promise(function (resolve, reject) {
nicholas@2864 55 var xml = new XMLHttpRequest();
nicholas@2864 56 xml.open("GET", "test_create/interfaces/specifications.json");
nicholas@2864 57 xml.onload = function () {
nicholas@2864 58 if (xml.status === 200) {
nicholas@2864 59 resolve(xml.responseText);
nicholas@2864 60 return;
nicholas@2864 61 }
nicholas@2864 62 reject(xml.status);
nicholas@2864 63 };
nicholas@2864 64 xml.onerror = function () {
nicholas@2864 65 reject(new Error("Network Error"));
nicholas@2864 66 };
nicholas@2864 67 xml.send();
nicholas@2864 68 }).then(JSON.parse).then(function (data) {
nicholas@2864 69 $s.testSpecifications = data;
nicholas@2864 70 $s.$apply();
nicholas@2865 71 });
nicholas@2864 72 })();
nickjillings@1370 73
nicholas@2851 74 $s.showPopup = function () {
nicholas@2851 75 $s.popupVisible = true;
nicholas@2851 76 };
nicholas@2851 77 $s.hidePopup = function () {
nicholas@2851 78 $s.popupVisible = false;
nicholas@2851 79 };
nicholas@2851 80 $s.globalSchema = undefined;
nicholas@2851 81 get("xml/test-schema.xsd").then(function (text) {
nicholas@2851 82 specification.processSchema(text);
nicholas@2851 83 $s.globalSchema = specification.getSchema();
nicholas@2851 84 });
nicholas@2851 85 $s.specification = specification;
nicholas@2886 86 $s.selectedTestPrototype = undefined;
nicholas@2886 87 $s.setTestPrototype = function (obj) {
nicholas@2886 88 $s.selectedTestPrototype = obj;
nicholas@2886 89 $w.specification.interface = obj.interface;
nicholas@2886 90 }
nicholas@2857 91
nicholas@2857 92 $s.addPage = function () {
nicholas@2857 93 $s.specification.createNewPage();
nicholas@2859 94 };
nicholas@2860 95
nicholas@2862 96 $s.removePage = function (page) {
nicholas@2862 97 var index = $s.specification.pages.findIndex(function (a) {
nicholas@2862 98 return a == page;
nicholas@2862 99 });
nicholas@2862 100 if (index === -1) {
nicholas@2862 101 throw ("Invalid Page");
nicholas@2862 102 }
nicholas@2862 103 $s.specification.pages.splice(index, 1);
nicholas@2862 104 };
nicholas@2862 105
nicholas@2860 106 $s.exportXML = function () {
nicholas@2860 107 var s = new XMLSerializer();
nicholas@2860 108 var doc = specification.encode();
nicholas@2891 109 var xmlstr = s.serializeToString(doc);
nicholas@2860 110 var bb = new Blob([s.serializeToString(doc)], {
nicholas@2860 111 type: 'application/xml'
nicholas@2860 112 });
nicholas@2860 113 var dnlk = window.URL.createObjectURL(bb);
nicholas@2891 114 var a = document.createElement("a");
nicholas@2891 115 a.href = dnlk;
nicholas@2891 116 a.download = "test.xml";
nicholas@2891 117 a.click();
nicholas@2891 118 window.URL.revokeObjectURL(dnlk);
nicholas@2861 119 };
nicholas@2887 120 $s.validated = false;
nicholas@2887 121 $s.showValidationMessages = false;
nicholas@2887 122 $s.validate = function () {
nicholas@2887 123 var s = new XMLSerializer();
nicholas@2887 124 var Module = {
nicholas@2887 125 xml: s.serializeToString(specification.encode()),
nicholas@2887 126 schema: specification.getSchemaString(),
nicholas@2887 127 arguments: ["--noout", "--schema", 'test-schema.xsd', 'document.xml']
nicholas@2887 128 };
nicholas@2887 129 var xmllint = validateXML(Module);
nicholas@2887 130 console.log(xmllint);
nicholas@2887 131 if (xmllint != 'document.xml validates\n') {
nicholas@2887 132 $s.validated = false;
nicholas@2887 133 var list = $e[0].querySelector("#validation-error-list");
nicholas@2887 134 while (list.firstChild) {
nicholas@2887 135 list.removeChild(list.firstChild);
nicholas@2887 136 }
nicholas@2887 137 var errors = xmllint.split('\n');
nicholas@2887 138 errors = errors.slice(0, errors.length - 2);
nicholas@2887 139 errors.forEach(function (str) {
nicholas@2887 140 var li = document.createElement("li");
nicholas@2887 141 li.textContent = str;
nicholas@2887 142 list.appendChild(li);
nicholas@2887 143 });
nicholas@2887 144 } else {
nicholas@2887 145 $s.validated = true;
nicholas@2887 146 }
nicholas@2887 147 $s.showValidationMessages = true;
nicholas@2887 148 }
nicholas@2887 149 $s.hideValidationMessages = function () {
nicholas@2887 150 $s.showValidationMessages = false;
nicholas@2887 151 }
nicholas@2851 152 }]);
nickjillings@1370 153
nicholas@2851 154 AngularInterface.controller("introduction", ['$scope', '$element', '$window', function ($s, $e, $w) {
nicholas@2851 155 $s.state = 0;
n@2908 156 $s.selected = undefined;
nicholas@2851 157 $s.next = function () {
nicholas@2851 158 $s.state++;
nicholas@2861 159 if ($s.state > 1 || $s.file) {
nicholas@2851 160 $s.hidePopup();
n@2908 161 $s.initialise($s.selected);
nicholas@2851 162 }
nicholas@2851 163 };
nicholas@2851 164 $s.back = function () {
nicholas@2851 165 $s.state--;
nicholas@2851 166 };
nicholas@2851 167 $s.mouseover = function (name) {
nicholas@2864 168 var obj = $s.testSpecifications.interfaces.find(function (i) {
nicholas@2851 169 return i.name == name;
nicholas@2851 170 });
nicholas@2851 171 if (obj) {
nicholas@2851 172 $s.description = obj.description.en;
nicholas@2851 173 }
nicholas@2851 174 };
nicholas@2851 175 $s.initialise = function (name) {
nicholas@2885 176 var obj = $s.testSpecifications.interfaces.find(function (i) {
nicholas@2851 177 return i.name == name;
nicholas@2851 178 });
nicholas@2886 179 if (obj === undefined) {
nicholas@2886 180 throw ("Cannot find specification");
nicholas@2886 181 }
n@2908 182 if (typeof obj.template === "string") {
n@2908 183 get(obj.template).then(function (data) {
n@2908 184 $s.parseFile(data);
n@2908 185 }, function (err) {})
n@2908 186 } else {
n@2908 187 $s.setTestPrototype(obj);
n@2908 188 }
nicholas@2851 189 };
n@2908 190 $s.select = function (name) {
n@2908 191 $s.selected = name;
n@2908 192 }
n@2908 193 // Get the test interface specifications
nicholas@2861 194 $s.file = undefined;
nicholas@2851 195 $s.description = "";
nicholas@2861 196
n@2908 197 $s.parseFile = function (f) {
n@2908 198 var p = new DOMParser();
n@2908 199 specification.decode(p.parseFromString(f, "text/xml"));
n@2908 200 $s.$apply();
n@2908 201 }
n@2908 202
nicholas@2861 203 $s.handleFiles = function ($event) {
nicholas@2861 204 $s.file = $event.currentTarget.files[0];
nicholas@2861 205 var r = new FileReader();
nicholas@2861 206 r.onload = function () {
n@2908 207 $s.parseFile(r.result);
nicholas@2862 208 };
nicholas@2861 209 r.readAsText($s.file);
nicholas@2861 210 };
nicholas@2851 211 }]);
nickjillings@1370 212
nicholas@2851 213 AngularInterface.controller("setup", ['$scope', '$element', '$window', function ($s, $e, $w) {
nicholas@2851 214 function initialise() {
nicholas@2851 215 if ($s.globalSchema) {
nicholas@2851 216 $s.schema = $s.globalSchema.querySelector("[name=setup]");
nickjillings@1385 217 }
nickjillings@1370 218 }
nicholas@2851 219 $s.schema = undefined;
nicholas@2851 220 $s.attributes = [];
nickjillings@1370 221
nicholas@2851 222 $s.$watch("globalSchema", initialise);
nicholas@2853 223 $s.$watch("specification.metrics.enabled.length", function () {
nicholas@2853 224 var metricsNode = document.getElementById("metricsNode");
nicholas@2853 225 if (!$s.specification.metrics) {
nicholas@2853 226 return;
nicholas@2853 227 }
nicholas@2853 228 metricsNode.querySelectorAll("input").forEach(function (DOM) {
nicholas@2853 229 DOM.checked = false;
nicholas@2853 230 });
nicholas@2853 231 $s.specification.metrics.enabled.forEach(function (metric) {
nicholas@2853 232 var DOM = metricsNode.querySelector("[value=" + metric + "]");
nicholas@2853 233 if (DOM) {
nicholas@2853 234 DOM.checked = true;
nicholas@2853 235 }
nicholas@2853 236 });
nicholas@2855 237 });
nicholas@2853 238
nicholas@2853 239 $s.enableMetric = function ($event) {
nicholas@2853 240 var metric = $event.currentTarget.value;
nicholas@2853 241 var index = specification.metrics.enabled.findIndex(function (a) {
nicholas@2853 242 return a == metric;
nicholas@2853 243 });
nicholas@2853 244 if ($event.currentTarget.checked) {
nicholas@2853 245 if (index == -1) {
nicholas@2853 246 specification.metrics.enabled.push(metric);
nicholas@2853 247 }
nicholas@2853 248 } else {
nicholas@2853 249 if (index >= 0) {
nicholas@2853 250 specification.metrics.enabled.splice(index, 1);
nicholas@2853 251 }
nicholas@2853 252 }
nicholas@2855 253 };
nicholas@2886 254
nicholas@2886 255 $s.configure = function () {}
nicholas@2886 256
nicholas@2886 257 $s.$watch("selectedTestPrototype", $s.configure);
n@2907 258
n@2907 259 $s.placeholder = function (name) {
n@2908 260 if ($s.schema) {
n@2908 261 var spec = $s.schema.querySelector("attribute[name=\"" + name + "\"]") || $w.specification.schema.querySelector("attribute[name=\"" + name + "\"]");
n@2908 262 var attr = spec.getAttribute("default");
n@2908 263 if (attr === undefined) {
n@2908 264 return "";
n@2908 265 }
n@2908 266 return attr;
n@2907 267 }
n@2907 268 }
nicholas@2851 269 }]);
nicholas@2853 270
nicholas@2858 271 AngularInterface.controller("survey", ['$scope', '$element', '$window', function ($s, $e, $w) {
nicholas@2858 272 $s.addSurveyEntry = function () {
nicholas@2858 273 $s.survey.addOption();
nicholas@2858 274 };
nicholas@2858 275 $s.removeSurveyEntry = function (entry) {
nicholas@2858 276 var index = $s.survey.options.findIndex(function (a) {
nicholas@2858 277 return a == entry;
nicholas@2858 278 });
nicholas@2858 279 if (index === -1) {
nicholas@2858 280 throw ("Invalid Entry");
nicholas@2858 281 }
nicholas@2858 282 $s.survey.options.splice(index, 1);
nicholas@2858 283 };
nicholas@2858 284 }]);
nicholas@2858 285
nicholas@2853 286 AngularInterface.controller("surveyOption", ['$scope', '$element', '$window', function ($s, $e, $w) {
nicholas@2853 287
nicholas@2853 288 $s.removeOption = function (option) {
nicholas@2853 289 var index = $s.opt.options.findIndex(function (a) {
nicholas@2853 290 return a == option;
nicholas@2853 291 });
nicholas@2853 292 if (index === -1) {
nicholas@2853 293 throw ("Invalid option");
nicholas@2853 294 }
nicholas@2853 295 $s.opt.options.splice(index, 1);
nicholas@2853 296 };
nicholas@2853 297 $s.addOption = function () {
nicholas@2853 298 $s.opt.options.push({
nicholas@2853 299 name: "",
nicholas@2853 300 text: ""
nicholas@2853 301 });
nicholas@2855 302 };
nicholas@2857 303
nicholas@2857 304 $s.addCondition = function () {
nicholas@2857 305 $s.opt.conditions.push({
nicholas@2857 306 check: "equals",
nicholas@2857 307 value: "",
nicholas@2857 308 jumpToOnPass: undefined,
nicholas@2857 309 jumpToOnFail: undefined
nicholas@2857 310 });
nicholas@2857 311 };
nicholas@2857 312
nicholas@2857 313 $s.removeCondition = function (condition) {
nicholas@2857 314 var index = $s.opt.conditions.findIndex(function (c) {
nicholas@2857 315 return c == condition;
nicholas@2857 316 });
nicholas@2857 317 if (index === -1) {
nicholas@2857 318 throw ("Invalid Condition");
nicholas@2857 319 }
nicholas@2857 320 $s.opt.conditions.splice(index, 1);
nicholas@2857 321 };
nicholas@2855 322 }]);
nicholas@2855 323
nicholas@2855 324 AngularInterface.controller("interfaceNode", ['$scope', '$element', '$window', function ($s, $e, $w) {
nicholas@2855 325 $s.$watch("interface.options.length", function () {
nicholas@2855 326 if (!$s.interface || !$s.interface.options) {
nicholas@2855 327 return;
nicholas@2855 328 }
nicholas@2855 329 var options = $e[0].querySelector(".interfaceOptions").querySelectorAll(".attribute");
nicholas@2855 330 options.forEach(function (option) {
nicholas@2855 331 var name = option.getAttribute("name");
nicholas@2855 332 var index = $s.interface.options.findIndex(function (io) {
nicholas@2855 333 return io.name == name;
nicholas@2855 334 });
nicholas@2855 335 option.querySelector("input").checked = (index >= 0);
nicholas@2855 336 if (name == "scalerange" && index >= 0) {
nicholas@2855 337 option.querySelector("[name=min]").value = $s.interface.options[index].min;
nicholas@2855 338 option.querySelector("[name=max]").value = $s.interface.options[index].max;
nicholas@2855 339 }
nicholas@2855 340 });
nicholas@2855 341 });
nicholas@2855 342 $s.enableInterfaceOption = function ($event) {
nicholas@2855 343 var name = $event.currentTarget.parentElement.getAttribute("name");
nicholas@2855 344 var type = $event.currentTarget.parentElement.getAttribute("type");
nicholas@2855 345 var index = $s.interface.options.findIndex(function (io) {
nicholas@2855 346 return io.name == name;
nicholas@2855 347 });
nicholas@2855 348 if (index == -1 && $event.currentTarget.checked) {
nicholas@2855 349 var obj = $s.interface.options.push({
nicholas@2855 350 name: name,
nicholas@2855 351 type: type
nicholas@2855 352 });
nicholas@2855 353 if (name == "scalerange") {
nicholas@2855 354 obj.min = $event.currentTarget.parentElement.querySelector("[name=min]").value;
nicholas@2855 355 obj.max = $event.currentTarget.parentElement.querySelector("[name=max]").value;
nicholas@2855 356 }
nicholas@2855 357 } else if (index >= 0 && !$event.currentTarget.checked) {
nicholas@2855 358 $s.interface.options.splice(index, 1);
nicholas@2855 359 }
nicholas@2857 360 };
nicholas@2886 361 $s.scales = [];
nicholas@2857 362 $s.removeScale = function (scale) {
nicholas@2857 363 var index = $s.interface.scales.findIndex(function (s) {
nicholas@2857 364 return s == scale;
nicholas@2857 365 });
nicholas@2857 366 if (index >= 0) {
nicholas@2857 367 $s.interface.scales.splice(index, 1);
nicholas@2857 368 }
nicholas@2857 369 };
nicholas@2857 370 $s.addScale = function () {
nicholas@2857 371 $s.interface.scales.push({
nicholas@2857 372 position: undefined,
nicholas@2857 373 text: undefined
nicholas@2857 374 });
nicholas@2857 375 };
nicholas@2864 376 $s.clearScales = function () {
nicholas@2864 377 $s.interface.scales = [];
nicholas@2864 378 };
nicholas@2876 379 $s.useScales = function (scale) {
nicholas@2864 380 $s.clearScales();
nicholas@2876 381 scale.scales.forEach(function (s) {
nicholas@2864 382 $s.interface.scales.push(s);
nicholas@2864 383 });
nicholas@2886 384 $s.selectedScale = scale.name;
nicholas@2864 385 };
nicholas@2886 386 $s.selectedScale = undefined;
nicholas@2886 387
nicholas@2886 388 $s.configure = function () {
nicholas@2886 389 if ($s.selectedTestPrototype === undefined) {
nicholas@2886 390 return;
nicholas@2886 391 }
nicholas@2886 392 if ($s.selectedTestPrototype.checks && $s.selectedTestPrototype.checks.length >= 1) {
nicholas@2886 393 $s.selectedTestPrototype.checks.forEach(function (entry) {
nicholas@2886 394 var dom = $e[0].querySelector("[name=\"" + entry.name + "\"] input");
nicholas@2886 395 if (entry.support == "none") {
nicholas@2886 396 dom.checked = false;
nicholas@2886 397 dom.disabled = true;
nicholas@2886 398 }
nicholas@2886 399 });
nicholas@2886 400 }
nicholas@2886 401 if ($s.selectedTestPrototype.show && $s.selectedTestPrototype.show.length >= 1) {
nicholas@2886 402 $s.selectedTestPrototype.show.forEach(function (entry) {
nicholas@2886 403 var dom = $e[0].querySelector("[name=\"" + entry.name + "\"] input");
nicholas@2886 404 if (entry.support == "none") {
nicholas@2886 405 dom.checked = false;
nicholas@2886 406 dom.disabled = true;
nicholas@2886 407 }
nicholas@2886 408 });
nicholas@2886 409 }
nicholas@2886 410 if ($s.interface !== specification.interfaces) {
nicholas@2886 411 // Page specific interface nodes
nicholas@2886 412 if ($s.selectedTestPrototype.hasScales !== undefined && ($s.selectedTestPrototype.hasScales == "false" || $s.selectedTestPrototype.hasScales == false)) {
nicholas@2886 413 var elem = $e[0].querySelector("[name=\"scale-selection\"]")
nicholas@2886 414 elem.style.visibility = "hidden";
nicholas@2886 415 elem.style.height = "0px";
nicholas@2886 416 }
nicholas@2886 417 if ($s.selectedTestPrototype.scales && $s.selectedTestPrototype.show.length >= 1) {
nicholas@2886 418 $s.scales = [];
nicholas@2886 419 $s.selectedTestPrototype.scales.forEach(function (scalename) {
nicholas@2886 420 var obj = $s.testSpecifications.scales.find(function (a) {
nicholas@2886 421 return a.name == scalename;
nicholas@2886 422 });
nicholas@2886 423 $s.scales.push(obj);
nicholas@2886 424 });
nicholas@2886 425 if ($s.selectedTestPrototype.scales.includes($s.selectedScale) == false) {
nicholas@2886 426 $s.clearScales();
nicholas@2886 427 }
nicholas@2886 428 if ($s.scales.length == 1) {
nicholas@2886 429 $s.clearScales();
nicholas@2886 430 $s.useScales($s.scales[0]);
nicholas@2886 431 }
nicholas@2886 432 } else {
nicholas@2886 433 $s.scales = $s.testSpecifications.scales;
nicholas@2886 434 }
nicholas@2886 435 }
nicholas@2886 436 };
nicholas@2886 437
nicholas@2886 438 $s.$watch("selectedTestPrototype", $s.configure);
nicholas@2886 439 $s.configure();
nicholas@2853 440 }]);
nicholas@2859 441 AngularInterface.controller("page", ['$scope', '$element', '$window', function ($s, $e, $w) {
n@2907 442 $s.schema = $w.specification.schema.querySelector("element[name=\"page\"]");
n@2907 443 $s.page.label = "default";
nicholas@2859 444 $s.addInterface = function () {
nicholas@2859 445 $s.page.addInterface();
nicholas@2859 446 };
nicholas@2859 447 $s.removeInterface = function (node) {
nicholas@2859 448 var index = $s.page.interfaces.findIndex(function (a) {
nicholas@2859 449 return a == node;
nicholas@2859 450 });
nicholas@2859 451 if (index === -1) {
nicholas@2859 452 throw ("Invalid node");
nicholas@2859 453 }
nicholas@2859 454 $s.page.interfaces.splice(index, 1);
nicholas@2859 455 };
nicholas@2859 456
nicholas@2859 457 $s.addCommentQuestion = function () {
nicholas@2859 458 $s.page.addCommentQuestion();
nicholas@2859 459 };
nicholas@2859 460 $s.removeCommentQuestion = function (node) {
nicholas@2859 461 var index = $s.page.commentQuestions.findIndex(function (a) {
nicholas@2859 462 return a == node;
nicholas@2859 463 });
nicholas@2859 464 if (index === -1) {
nicholas@2859 465 throw ("Invalid node");
nicholas@2859 466 }
nicholas@2859 467 $s.page.commentQuestions.splice(index, 1);
nicholas@2859 468 };
nicholas@2859 469 $s.addAudioElement = function () {
nicholas@2859 470 $s.page.addAudioElement();
nicholas@2859 471 };
nicholas@2859 472 $s.removeAudioElement = function (element) {
nicholas@2859 473 var index = $s.page.audioElements.findIndex(function (a) {
nicholas@2859 474 return a == element;
nicholas@2859 475 });
nicholas@2859 476 if (index === -1) {
nicholas@2859 477 throw ("Invalid node");
nicholas@2859 478 }
nicholas@2859 479 $s.page.audioElements.splice(index, 1);
nicholas@2859 480 };
n@2907 481
n@2907 482 $s.placeholder = function (name) {
n@2907 483 var spec = $s.schema.querySelector("attribute[name=\"" + name + "\"]") || $w.specification.schema.querySelector("attribute[name=\"" + name + "\"]");
n@2907 484 var attr = spec.getAttribute("default");
n@2907 485 if (attr === undefined) {
n@2907 486 return "";
n@2907 487 }
n@2907 488 return attr;
n@2907 489 }
nicholas@2859 490 }]);