hwm_build_manager

Менеджер билдов для HWM

  1. // ==UserScript==
  2. // @name hwm_build_manager
  3. // @author Chie (http://www.heroeswm.ru/pl_info.php?id=645888)
  4. // @icon https://s.gravatar.com/avatar/5fd0059ad34d082dfbd50cfdeb9aab6a
  5. // @description Менеджер билдов для HWM
  6. // @namespace https://github.com/betula/hwm_build_manager
  7. // @homepageURL https://github.com/betula/hwm_build_manager
  8. // @include http://*heroeswm.ru/*
  9. // @include http://178.248.235.15/*
  10. // @include http://*lordswm.com/*
  11. // @version 1.0.2
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15.  
  16. // Build Manager 1.0.2
  17.  
  18. class ServiceContainer {
  19. constructor() {
  20. this.instances = {};
  21. }
  22. _service(ctor) {
  23. let { name } = ctor;
  24. if (!this.instances[name]) {
  25. this.instances[name] = new ctor(this);
  26. }
  27. return this.instances[name];
  28. }
  29. get manager() { return this._service(ManagerService) }
  30. get fraction() { return this._service(FractionService) }
  31. get inventory() { return this._service(InventoryService) }
  32. get attribute() { return this._service(AttributeService) }
  33. get army() { return this._service(ArmyService) }
  34. get skill() { return this._service(SkillService) }
  35. get current() { return this._service(CurrentService) }
  36. get change() { return this._service(ChangeService) }
  37. get import() { return this._service(ImportService) }
  38. }
  39.  
  40. class ManagerService {
  41. constructor(services) {
  42. this.services = services;
  43. this._storage = new LocalStorageArrayDriver('BM_MANAGER');
  44. this.items = [];
  45. this._restore();
  46. }
  47. createNew() {
  48. let nextNumber = 1;
  49. for (let item of this.items) {
  50. let match = item.name.match(/^Новый билд (\d+)$/);
  51. if (match) {
  52. nextNumber = Math.max(nextNumber, (parseInt(match[1]) || 0) + 1);
  53. }
  54. }
  55.  
  56. let item = {
  57. id: uniqid(),
  58. name: `Новый билд ${nextNumber}`,
  59. fraction: this.services.fraction.default.id,
  60. inventory: this.services.inventory.default.id,
  61. attribute: this.services.attribute.default,
  62. army: this.services.army.default,
  63. skill: this.services.skill.default
  64. }
  65.  
  66. this.items.push(item);
  67. this._store();
  68.  
  69. return item;
  70. }
  71. remove(item) {
  72. let { founded, index } = this._searchByItem(item);
  73. if (!founded) return null;
  74. let items = this.items;
  75. items = items.slice(0, index).concat( items.slice(index + 1) );
  76. this.items = items;
  77. this._store();
  78. if (index === items.length) {
  79. return items[index - 1]
  80. }
  81. return items[index];
  82. }
  83. duplicate(item) {
  84. let { founded } = this._searchByItem(item);
  85. if (!founded) return null;
  86.  
  87. let duplicate = deepCopy(item);
  88. duplicate.id = uniqid();
  89. duplicate.name += ' копия';
  90. this.items.push(duplicate);
  91. this._store()
  92. return duplicate;
  93. }
  94. update(updatedItem) {
  95. let { founded, index } = this._searchById(updatedItem.id);
  96. if (!founded) return null;
  97.  
  98. this.items[index] = updatedItem;
  99. this._store();
  100. this.services.current.mayBeOnlyNameUpdated(updatedItem);
  101.  
  102. return updatedItem;
  103. }
  104. searchEquals(item) {
  105. let { founded, index } = this._searchById(item.id);
  106. if (founded) {
  107. if (deepEquals(item, this.items[index])) {
  108. return {
  109. founded,
  110. index
  111. }
  112. }
  113. }
  114. return {
  115. founded: false
  116. }
  117. }
  118.  
  119. serialize() {
  120. return JSON.stringify(this.items);
  121. }
  122.  
  123.  
  124. unserialize(value) {
  125. const INVALID = 'invalid';
  126.  
  127. const Checker = {
  128. string(value) {
  129. if (typeof value !== 'string' || value.length === 0) throw INVALID;
  130. },
  131. number(value) {
  132. if (typeof value !== 'number') throw INVALID;
  133. },
  134. array(value) {
  135. if (!Array.isArray(value)) throw INVALID;
  136. },
  137. object(value) {
  138. if (!value || typeof value !== 'object') throw INVALID;
  139. },
  140. keys(value, keys) {
  141. if (!keys.every({}.hasOwnProperty.bind(value))) throw INVALID;
  142. for (let key of Object.keys(value)) {
  143. Checker.enum(key, keys);
  144. }
  145. },
  146. enum(value, values) {
  147. if (Array.isArray(value)) {
  148. for (let once of value) {
  149. Checker.enum(once, values);
  150. }
  151. } else {
  152. if (Array.isArray(values)) {
  153. if (values.indexOf(value) === -1) throw INVALID;
  154. } else {
  155. if (!values.hasOwnProperty(value)) throw INVALID;
  156. }
  157. }
  158. },
  159. length(value, len) {
  160. if (value.length !== len) throw INVALID;
  161. }
  162.  
  163. };
  164.  
  165. let items = [];
  166.  
  167. try {
  168. items = JSON.parse(value);
  169. Checker.array(items);
  170.  
  171. for (let item of items) {
  172. Checker.object(item);
  173. Checker.keys(item, [ 'id', 'name', 'fraction', 'inventory', 'attribute', 'army', 'skill' ]);
  174.  
  175. let { id, name, fraction, inventory, attribute, army, skill } = item;
  176.  
  177. Checker.string(id);
  178. Checker.string(name);
  179. Checker.enum(fraction, this.services.fraction.map);
  180. Checker.enum(inventory, this.services.inventory.map);
  181.  
  182. Checker.object(attribute);
  183. Checker.keys(attribute, this.services.attribute.list);
  184. Object.values(attribute).forEach(Checker.number);
  185.  
  186. Checker.array(army);
  187. Checker.length(army, 7);
  188. army.forEach(Checker.number);
  189.  
  190. Checker.array(skill);
  191. Checker.enum(skill, this.services.skill.map);
  192. }
  193.  
  194. } catch(e) {
  195. return false;
  196. }
  197.  
  198. this.items = items;
  199. this._store();
  200. return true;
  201. }
  202. _searchById(id) {
  203. const items = this.items;
  204. let founded = false;
  205. let index;
  206. for (index = 0; index < items.length; index++) {
  207. if (items[index].id === id) {
  208. founded = true;
  209. break;
  210. }
  211. }
  212. return founded ? { founded, index } : { founded: false };
  213. }
  214. _searchByItem(item) {
  215. let index = this.items.indexOf(item);
  216. return index != -1
  217. ? { founded: true, index }
  218. : { founded: false };
  219. }
  220. _restore() {
  221. this.items = this._storage.fetch();
  222. }
  223. _store() {
  224. this._storage.put(this.items);
  225. }
  226. }
  227.  
  228. class CurrentService {
  229.  
  230. constructor(services) {
  231. this.services = services;
  232. this._storage = new LocalStorageDriver('BM_CURRENT');
  233. this._restore();
  234. }
  235.  
  236. get item() {
  237. return this._item;
  238. }
  239.  
  240. mayBeOnlyNameUpdated(updatedItem) {
  241. let current = this._item;
  242. if (!current) return;
  243. if (current.id !== updatedItem.id) return;
  244. let clone = deepCopy(current);
  245. clone.name = updatedItem.name;
  246. if (!deepEquals(clone, updatedItem)) return;
  247. current.name = updatedItem.name;
  248. this._store();
  249. }
  250. isExpired() {
  251. if (!this._item) return false;
  252. return !this.services.manager.searchEquals(this._item).founded;
  253. }
  254.  
  255. change(item, force) {
  256. item = deepCopy(item);
  257.  
  258. const change = () => {
  259. if (!item) return item;
  260. if (force || !this._item) {
  261. return this.services.change.force(item);
  262. }
  263. return this.services.change.diff(this._item, item);
  264. };
  265.  
  266. return this.changeQueue = (this.changeQueue || Promise.resolve())
  267. .then(change, change)
  268. .then((item) => {
  269. this._update(item);
  270. return item;
  271. }, (e) => {
  272. this._update();
  273. return Promise.reject(e);
  274. });
  275. }
  276. equals(item) {
  277. return deepEquals(this._item, item);
  278. }
  279. _update(item = null) {
  280. this._item = item;
  281. this._store();
  282. }
  283. _restore() {
  284. this._item = this._storage.fetch();
  285. }
  286. _store() {
  287. this._storage.put(this._item);
  288. }
  289.  
  290. }
  291.  
  292. class ChangeService {
  293.  
  294. constructor(services) {
  295. this.services = services;
  296. this.cache = {};
  297. }
  298.  
  299. force(to) {
  300. const promise = Promise.resolve()
  301. .then(() => this._fraction(to.fraction))
  302. .then(() => this._skill(to.skill))
  303. .then(() => this._army(to.army))
  304. .then(() => this._inventory(to.inventory))
  305. .then(() => this._reset())
  306. .then(() => this._attribute(to.attribute))
  307. .then(() => to);
  308. return PromiseMDecorator(promise);
  309. }
  310.  
  311. diff(from, to) {
  312. const fractionChanged = from.fraction !== to.fraction;
  313. const skillChanged = fractionChanged || !deepEquals(from.skill, to.skill);
  314. const armyChanged = fractionChanged || !deepEquals(from.army, to.army);
  315. const inventoryChanged = from.inventory !== to.inventory;
  316. const attributeChanged = !deepEquals(from.attribute, to.attribute);
  317.  
  318. let serial = Promise.resolve();
  319. if (fractionChanged) {
  320. serial = serial.then(() => this._fraction(to.fraction, from.fraction))
  321. }
  322. if (skillChanged) {
  323. serial = serial.then(() => this._skill(to.skill))
  324. }
  325. if (armyChanged) {
  326. serial = serial.then(() => this._army(to.army))
  327. }
  328. if (inventoryChanged) {
  329. serial = serial.then(() => this._inventory(to.inventory))
  330. }
  331. if (attributeChanged) {
  332. serial = serial
  333. .then(() => this._reset())
  334. .then(() => this._attribute(to.attribute))
  335. }
  336. serial = serial.then(() => to);
  337. return PromiseMDecorator(serial);
  338. }
  339.  
  340. _fraction(to, from) {
  341. to = this.services.fraction.map[to];
  342. let prepare = Promise.resolve();
  343.  
  344. if (from) {
  345. from = this.services.fraction.map[from];
  346. } else {
  347. prepare = this.services.import.getFraction().then((id) => {
  348. from = this.services.fraction.map[id];
  349. })
  350. }
  351.  
  352. const change = (data) => {
  353. return httpPlainRequest('FORM', '/castle.php', data)
  354. }
  355.  
  356. return prepare.then(() => {
  357.  
  358. if (from.fract !== to.fract) {
  359. let promise = change({ fract: to.fract });
  360. if (to.classid !== '0') {
  361. return promise.then(() => {
  362. return change({ classid: to.classid })
  363. });
  364. }
  365. return promise
  366. }
  367.  
  368. if (from.classid !== to.classid) {
  369. return change({ classid: to.classid })
  370. }
  371.  
  372. })
  373. }
  374.  
  375. _skill(list) {
  376. let data = {
  377. rand: Math.random(),
  378. setstats: 1,
  379. }
  380.  
  381. for (let i = 0; i < list.length; i++) {
  382. data[`param${i}`] = list[i];
  383. }
  384.  
  385. return httpPlainRequest('FORM', '/skillwheel.php', data);
  386. }
  387.  
  388. _army(list) {
  389. let data = {
  390. rand: Math.random(),
  391. }
  392.  
  393. for (let i = 0; i < list.length; i++) {
  394. data[`countv${i+1}`] = list[i];
  395. }
  396.  
  397. return httpPlainRequest('FORM', '/army_apply.php', data);
  398. }
  399.  
  400. _inventory(id) {
  401. let struct = this.services.inventory.map[id];
  402. let data = {
  403. r: Date.now() + String(Math.random()).slice(2)
  404. };
  405. data[struct.type] = struct.value;
  406. return httpPlainRequest('GET', '/inventory.php', data);
  407. }
  408.  
  409. _reset() {
  410. let resetLinkPromise = Promise.resolve(this.cache.resetLink);
  411.  
  412. if (!this.cache.resetLink) {
  413. resetLinkPromise = httpPlainRequest('GET', '/home.php').then((html) => {
  414. let m = html.match(/shop\.php\?b=reset_tube&reset=2&sign=[0-9a-f]+/);
  415. if (!m) return null;
  416. return this.cache.resetLink = '/' + m[0];
  417. })
  418. }
  419.  
  420. return resetLinkPromise.then((url) => (url ? httpPlainRequest('GET', url) : null));
  421. }
  422.  
  423. _attribute(obj) {
  424. const getTotal = () => {
  425. return httpPlainRequest('GET', '/home.php').then((html) => {
  426. let m = html.match(/href="home\.php\?increase_all=knowledge"(?:.|\n)*?(\d+)\<\/td/);
  427. if (!m) return null;
  428. return parseInt(m[1]) || null;
  429. });
  430. }
  431.  
  432. const increase = (name, count) => {
  433. let serial = Promise.resolve();
  434. for (let i = 0; i < count; i++) {
  435. serial = serial.then(() => httpPlainRequest('GET', `/home.php?increase=${name}`));
  436. }
  437. return serial;
  438. }
  439.  
  440. const increaseAll = (name) => {
  441. return httpPlainRequest('GET', `/home.php?increase_all=${name}`);
  442. }
  443.  
  444. const distribute = (total) => {
  445. total = total || 0;
  446.  
  447. let list = [];
  448. for (let name of Object.keys(obj)) {
  449. list.push({ name, value: obj[name] })
  450. }
  451. list.sort((a, b) => a.value - b.value);
  452.  
  453. let serial = Promise.resolve();
  454.  
  455. let used = 0;
  456. list.slice(0, -1).forEach(({ name, value }) => {
  457. if (used >= total) return;
  458. if (value === 0) return;
  459.  
  460. let v = Math.min(total - used, value);
  461. used += value;
  462. serial = serial.then(() => increase(name, v));
  463. });
  464.  
  465. if (total > used) {
  466. let { name, value } = list[ list.length - 1 ];
  467. if (value > 0) {
  468. if (value < total - used) {
  469. serial = serial.then(() => increase(name, value));
  470. } else {
  471. serial = serial.then(() => increaseAll(name));
  472. }
  473. }
  474. }
  475.  
  476. return serial;
  477. }
  478.  
  479. return getTotal().then(distribute);
  480. }
  481.  
  482. }
  483.  
  484. class ImportService {
  485.  
  486. constructor(services) {
  487. this.services = services;
  488. }
  489.  
  490. getInventoryNamesIfAvailable() {
  491. if (location.pathname.match(/^\/inventory\.php/)) {
  492. let list = [];
  493. let nodes = document.querySelectorAll('a[href*="inventory.php?all_on"]');
  494. for (let node of nodes) {
  495. let [ _, type, value ] = node.getAttribute('href').match(/(all_on)=(\d)/);
  496. let name = node.innerText;
  497. list.push({
  498. type,
  499. value,
  500. name
  501. });
  502. }
  503. return list;
  504. }
  505. }
  506.  
  507. getArmy() {
  508. return httpPlainRequest('GET', '/army.php').then((html) => {
  509. let m = html.match(/\<param name="FlashVars" value="param=\d+\|M([^"]+)/);
  510. if (!m) return null;
  511. let chunks = m[1].split(';M');
  512. let items = [];
  513.  
  514. for (let chunk of chunks) {
  515. items.push( parseInt(chunk.split(':')[1].substr(57,3)) || 0 );
  516. }
  517.  
  518. for (let i = items.length; i < this.services.army.iterator.length; i++) {
  519. items.push(0);
  520. }
  521.  
  522. return items;
  523. });
  524. }
  525.  
  526. getFraction() {
  527. return httpPlainRequest('GET', '/castle.php').then((html) => {
  528. let dict = {};
  529. for (let { fract, classid } of this.services.fraction.list) {
  530. if (!dict[fract]) {
  531. dict[fract] = {};
  532. }
  533. dict[fract][classid] = true;
  534. }
  535.  
  536. const extractFract = () => {
  537. let m = html.match(/\<select name='fract'((?:.|[\r\n])+?)\<\/select/);
  538. if (!m) return null;
  539.  
  540. let available = {};
  541. m[1].replace(/value=(\d)/g, (_, id) => {
  542. available[id] = true;
  543. });
  544.  
  545. for (let id of Object.keys(dict)) {
  546. if (!available[id]) return id;
  547. }
  548. }
  549.  
  550. const extractClassid = (fract) => {
  551. let m = html.match(/\<select name='classid'((?:.|[\r\n])+?)\<\/select/);
  552. if (!m) return null;
  553.  
  554. let available = {};
  555. m[1].replace(/value=(\d)/g, (_, id) => {
  556. available[id] = true;
  557. });
  558.  
  559. for (let id of Object.keys(dict[fract])) {
  560. if (!available[id]) return id;
  561. }
  562. }
  563.  
  564. let fract = extractFract();
  565. if (!fract) return null;
  566.  
  567. let classidList = Object.keys(dict[fract]);
  568. let classid;
  569.  
  570. if (classidList.length === 1) {
  571. classid = classidList[0];
  572.  
  573. } else {
  574. classid = extractClassid(fract);
  575. if (!classid) return null;
  576. }
  577.  
  578. return `${fract}${classid}`
  579. })
  580. }
  581.  
  582. getSkill() {
  583. return httpPlainRequest('GET', '/skillwheel.php').then((html) => {
  584. let m = html.match(/\<param name="FlashVars" value='param=.+?;builds=([^']+)/);
  585. if (!m) return null;
  586. let rows = m[1].split('$');
  587. let items = [];
  588.  
  589. for (let r of rows) {
  590. let row = r.split('|');
  591. if (row.length != 10) continue;
  592.  
  593. let id = row[0];
  594. let has = row[8];
  595.  
  596. if (has === '1') {
  597. items.push(id);
  598. }
  599. }
  600.  
  601. return items;
  602. })
  603. }
  604.  
  605. }
  606.  
  607. class FractionService {
  608. constructor() {
  609. this.list = [
  610. { fract: '1', classid: '0', name: 'Рыцарь' },
  611. { fract: '1', classid: '1', name: 'Рыцарь света' },
  612. { fract: '2', classid: '0', name: 'Некромант' },
  613. { fract: '2', classid: '1', name: 'Некромант - повелитель смерти' },
  614. { fract: '3', classid: '0', name: 'Маг' },
  615. { fract: '3', classid: '1', name: 'Маг-разрушитель' },
  616. { fract: '4', classid: '0', name: 'Эльф' },
  617. { fract: '4', classid: '1', name: 'Эльф-заклинатель' },
  618. { fract: '5', classid: '0', name: 'Варвар' },
  619. { fract: '5', classid: '1', name: 'Варвар крови' },
  620. { fract: '5', classid: '2', name: 'Варвар-шаман' },
  621. { fract: '6', classid: '0', name: 'Темный эльф' },
  622. { fract: '6', classid: '1', name: 'Темный эльф-укротитель' },
  623. { fract: '7', classid: '0', name: 'Демон' },
  624. { fract: '7', classid: '1', name: 'Демон тьмы' },
  625. { fract: '8', classid: '0', name: 'Гном' },
  626. { fract: '9', classid: '0', name: 'Степной варвар' }
  627. ];
  628. this.map = {};
  629. for (let item of this.list) {
  630. item.id = item.fract + item.classid;
  631. this.map[item.id] = item;
  632. }
  633. }
  634. get default() {
  635. return this.list[0];
  636. }
  637. }
  638.  
  639. class InventoryService {
  640. constructor(services) {
  641. this.services = services;
  642. this._storage = new LocalStorageArrayDriver('BM_INVENTORY');
  643. this.loaded = false;
  644. this.list = [
  645. { type: 'all_off', value: '100', name: 'Снять все' },
  646. { type: 'all_on', value: '1', name: 'Набор 1' },
  647. { type: 'all_on', value: '2', name: 'Набор 2' },
  648. { type: 'all_on', value: '3', name: 'Набор 3' },
  649. { type: 'all_on', value: '4', name: 'Набор 4' },
  650. { type: 'all_on', value: '5', name: 'Набор 5' }
  651. ]
  652. this.map = {};
  653. for (let item of this.list) {
  654. item.id = item.type + item.value;
  655. this.map[item.id] = item;
  656. }
  657. this._restore();
  658. }
  659. _restore() {
  660. let list = this._storage.fetch();
  661. for (let item of list) {
  662. let id = item.type + item.value;
  663. if (this.map[id]) {
  664. this.map[id].name = item.name;
  665. }
  666. }
  667. }
  668. syncNamesIfAvailable() {
  669. let list = this.services.import.getInventoryNamesIfAvailable();
  670. if (list) {
  671. for (let { type, value, name } of list) {
  672. let id = type + value;
  673. if (this.map[id]) {
  674. this.map[id].name = name;
  675. }
  676. }
  677. this._storage.put(list);
  678. }
  679. }
  680. get default() {
  681. return this.list[0];
  682. }
  683. }
  684.  
  685. class AttributeService {
  686. get list() {
  687. return Object.keys(this.default);
  688. }
  689. get default() {
  690. return {
  691. attack: 0,
  692. defence: 0,
  693. power: 0,
  694. knowledge: 0
  695. }
  696. }
  697. }
  698.  
  699. class ArmyService {
  700. get iterator() {
  701. return this.default;
  702. }
  703. get default() {
  704. return [ 0, 0, 0, 0, 0, 0, 0 ]
  705. }
  706. }
  707.  
  708.  
  709.  
  710. class SkillService {
  711. constructor() {
  712. this.table = [
  713. { id: "attack", name: "Нападение", list: [
  714. { id: "attack1", name: "Основы нападения", main: true },
  715. { id: "attack2", name: "Развитое нападение", main: true },
  716. { id: "attack3", name: "Искусное нападение", main: true },
  717. { id: "battle_frenzy", name: "Боевое безумие" },
  718. { id: "retribution", name: "Воздаяние" },
  719. { id: "nature_wrath", name: "Лесная ярость" },
  720. { id: "power_of_speed", name: "Мастерство скорости" },
  721. { id: "excruciating_strike", name: "Мощный удар" },
  722. { id: "archery", name: "Стрельба" },
  723. { id: "tactics", name: "Тактика" },
  724. { id: "cold_steel", name: "Холодная сталь" },
  725. ]}, { id: "defense", name: "Защита", list: [
  726. { id: "defense1", name: "Основы защиты", main: true },
  727. { id: "defense2", name: "Развитая защита", main: true },
  728. { id: "defense3", name: "Искусная защита", main: true },
  729. { id: "last_stand", name: "Битва до последнего" },
  730. { id: "stand_your_ground", name: "Глухая оборона" },
  731. { id: "preparation", name: "Готовность" },
  732. { id: "power_of_endurance", name: "Сила камня" },
  733. { id: "resistance", name: "Сопротивление" },
  734. { id: "protection", name: "Сопротивление магии" },
  735. { id: "vitality", name: "Стойкость" },
  736. { id: "evasion", name: "Уклонение" },
  737. ]}, { id: "luck", name: "Удача", list: [
  738. { id: "luck1", name: "Призрачная удача", main: true },
  739. { id: "luck2", name: "Большая удача", main: true },
  740. { id: "luck3", name: "Постоянная удача", main: true },
  741. { id: "magic_resistance", name: "Магическое сопротивление" },
  742. { id: "piercing_luck", name: "Пронзающая удача" },
  743. { id: "soldier_luck", name: "Солдатская удача" },
  744. { id: "warlock_luck", name: "Удачливый чародей" },
  745. { id: "swarming_gate", name: "Широкие врата ада" },
  746. { id: "elven_luck", name: "Эльфийская удача" },
  747. ]}, { id: "leadership", name: "Лидерство", list: [
  748. { id: "leadership1", name: "Основы лидерства", main: true },
  749. { id: "leadership2", name: "Развитое лидерство", main: true },
  750. { id: "leadership3", name: "Искусное лидерство", main: true },
  751. { id: "aura_of_swiftness", name: "Аура скорости" },
  752. { id: "divine_guidance", name: "Воодушевление" },
  753. { id: "battle_commander", name: "Лесной лидер" },
  754. { id: "recruitment", name: "Сбор войск" },
  755. { id: "empathy", name: "Сопереживание" },
  756. ]}, { id: "enlightenment", name: "Образование", list: [
  757. { id: "enlightenment1", name: "Начальное образование", main: true },
  758. { id: "enlightenment2", name: "Среднее образование", main: true },
  759. { id: "enlightenment3", name: "Высшее образование", main: true },
  760. { id: "graduate", name: "Выпускник" },
  761. { id: "wizard_reward", name: "Колдовская награда" },
  762. { id: "know_your_enemy", name: "Лесное коварство" },
  763. { id: "lord_of_the_undead", name: "Повелитель мёртвых" },
  764. { id: "intelligence", name: "Притяжение маны" },
  765. { id: "dark_revelation", name: "Тёмное откровение" },
  766. { id: "arcane_exaltation", name: "Хранитель тайного" },
  767. ]}, { id: "dark", name: "Магия Тьмы", list: [
  768. { id: "dark1", name: "Основы магии Тьмы", main: true },
  769. { id: "dark2", name: "Сильная магия Тьмы", main: true },
  770. { id: "dark3", name: "Искусная магия Тьмы", main: true },
  771. { id: "weakening_strike", name: "Ослабляющий удар" },
  772. { id: "fallen_knight", name: "Падший рыцарь" },
  773. { id: "master_of_pain", name: "Повелитель боли" },
  774. { id: "master_of_curses", name: "Повелитель проклятий" },
  775. { id: "master_of_mind", name: "Повелитель разума" },
  776. ]}, { id: "destructive", name: "Магия Хаоса", list: [
  777. { id: "destructive1", name: "Основы магии Хаоса", main: true },
  778. { id: "destructive2", name: "Сильная магия Хаоса", main: true },
  779. { id: "destructive3", name: "Искусная магия Хаоса", main: true },
  780. { id: "searing_fires", name: "Иссушающее пламя" },
  781. { id: "sap_magic", name: "Истощение магии" },
  782. { id: "fiery_wrath", name: "Огненная ярость" },
  783. { id: "master_of_storms", name: "Повелитель бурь" },
  784. { id: "master_of_fire", name: "Повелитель огня" },
  785. { id: "master_of_ice", name: "Повелитель холода" },
  786. { id: "secrets_of_destruction", name: "Тайны хаоса" },
  787. ]}, { id: "light", name: "Магия Света", list: [
  788. { id: "light1", name: "Основы магии Света", main: true },
  789. { id: "light2", name: "Сильная магия Света", main: true },
  790. { id: "light3", name: "Искусная магия Света", main: true },
  791. { id: "master_of_blessings", name: "Дарующий благословение" },
  792. { id: "master_of_abjuration", name: "Дарующий защиту" },
  793. { id: "fire_resistance", name: "Защита от огня" },
  794. { id: "master_of_wrath", name: "Повелитель ярости" },
  795. { id: "twilight", name: "Сумерки" },
  796. { id: "refined_mana", name: "Тайны света" },
  797. ]}, { id: "summon", name: "Магия Природы", list: [
  798. { id: "summon1", name: "Основы магии Природы", main: true },
  799. { id: "summon2", name: "Сильная магия Природы", main: true },
  800. { id: "summon3", name: "Искусная магия Природы", main: true },
  801. { id: "master_of_conjuration", name: "Повелитель волшебства" },
  802. { id: "master_of_life", name: "Повелитель жизни" },
  803. { id: "master_of_obstacles", name: "Повелитель препятствий" },
  804. ]}, { id: "sorcery", name: "Чародейство", list: [
  805. { id: "sorcery1", name: "Основы чародейства", main: true },
  806. { id: "sorcery2", name: "Развитое чародейство", main: true },
  807. { id: "sorcery3", name: "Искусное чародейство", main: true },
  808. { id: "mana_regeneration", name: "Восполнение маны" },
  809. { id: "boneward", name: "Защита от магии хаоса" },
  810. { id: "erratic_mana", name: "Изменчивая мана" },
  811. { id: "magic_insight", name: "Мудрость" },
  812. { id: "arcane_brillance", name: "Тайное откровение" },
  813. { id: "arcane_excellence", name: "Тайное преимущество" },
  814. { id: "arcane_training", name: "Тайные знания" },
  815. ]}, { id: "special", name: "Фракция", list: [
  816. { id: "hellfire", name: "Адское пламя" },
  817. { id: "magic_mirror", name: "Волшебное зеркало" },
  818. { id: "runeadv", name: "Дополнительные руны" },
  819. { id: "necr_soul", name: "Духовная связь" },
  820. { id: "zakarrow", name: "Заколдованная стрела" },
  821. { id: "nomagicdamage", name: "Контроль магии" },
  822. { id: "elf_shot", name: "Ливень из стрел" },
  823. { id: "benediction", name: "Молитва" },
  824. { id: "knight_mark", name: "Надзор" },
  825. { id: "memoryblood", name: "Память нашей Крови" },
  826. { id: "cre_master", name: "Повелитель существ" },
  827. { id: "consumecorpse", name: "Поглощение трупов" },
  828. { id: "barb_skill", name: "Пробивающая мощь" },
  829. { id: "powerraise", name: "Совершенное Поднятие мертвецов" },
  830. { id: "dark_blood", name: "Тёмная кровь" },
  831. { id: "dark_power", name: "Тёмная сила" },
  832. { id: "save_rage", name: "Упорство ярости" },
  833. ]}
  834. ];
  835. this.map = {};
  836. for (let section of this.table) {
  837. for (let item of section.list) {
  838. this.map[item.id] = item;
  839. }
  840. }
  841. }
  842. get default() {
  843. return [];
  844. }
  845. }
  846.  
  847.  
  848. styles(`
  849. .mb-editor-name__box {
  850. margin: 5px 0 0 0;
  851. }
  852. .mb-editor-name__block-label {
  853. display: inline-block;
  854. width: 110px;
  855. }
  856. .mb-editor-name__block-input {
  857. width: 526px;
  858. }
  859. `);
  860. class EditorNameComponent {
  861. view({ attrs: { value, onchange } }) {
  862.  
  863. return m('.mb-editor-name__box', [
  864. m('.mb-editor-name__block', [
  865. m('.mb-editor-name__block-label', 'Название:'),
  866. m('input.mb-editor-name__block-input', { oninput: m.withAttr('value', onchange), value })
  867. ])
  868. ])
  869. }
  870. }
  871.  
  872.  
  873. styles(`
  874. .mb-editor-fraction__box {
  875. margin: 5px 0;
  876. }
  877. .mb-editor-fraction__block-label {
  878. display: inline-block;
  879. width: 110px;
  880. }
  881. .mb-editor-fraction__import-button {
  882. display: inline-block;
  883. margin-left: 4px;
  884. cursor: pointer;
  885. }
  886. .mb-editor-fraction__import-button:hover {
  887. text-decoration: underline;
  888. }
  889. .mb-editor-fraction__import-button--importing {
  890. animation: mb-fraction-import-animation 1s infinite linear;
  891. cursor: wait;
  892. }
  893. .mb-editor-fraction__import-button--importing:hover {
  894. text-decoration: none;
  895. }
  896. @keyframes mb-fraction-import-animation {
  897. 0% { transform: rotate(0deg); }
  898. 100% { transform: rotate(359deg); }
  899. }
  900. `);
  901. class EditorFractionComponent {
  902. constructor({ attrs: { services }}) {
  903. this.services = services;
  904. }
  905.  
  906. view({ attrs: { value, onchange } }) {
  907.  
  908. const importAction = () => {
  909. if (this.importing) return;
  910. this.importing = true;
  911. const finish = () => { this.importing = false };
  912. this.services.import.getFraction()
  913. .then((val) => {
  914. if (val) {
  915. onchange(val);
  916. }
  917. })
  918. .then(finish, finish);
  919. };
  920.  
  921. const importButton = () => {
  922. return m('.mb-editor-fraction__import-button', { onclick: importAction, class: this.importing ? 'mb-editor-fraction__import-button--importing' : '' }, '<')
  923. };
  924.  
  925. return m('.mb-editor-fraction__box', [
  926. m('.mb-editor-fraction__block', [
  927. m('.mb-editor-fraction__block-label', 'Фракция:'),
  928. m('select',
  929. { oninput: m.withAttr('value', onchange), value: value },
  930. this.services.fraction.list.map(({ id, name }) => {
  931. return m('option', { key: id, value: id }, name);
  932. })),
  933. importButton()
  934. ])
  935. ])
  936. }
  937. }
  938.  
  939. styles(`
  940. .mb-editor-inventory__box {
  941. margin: 5px 0;
  942. }
  943. .mb-editor-inventory__block-label {
  944. display: inline-block;
  945. width: 110px;
  946. }
  947. `);
  948. class EditorInventoryComponent {
  949. constructor({ attrs: { services }}) {
  950. this.services = services;
  951. }
  952.  
  953. view({ attrs: { value, onchange } }) {
  954.  
  955. return m('.mb-editor-inventory__box', [
  956. m('.mb-editor-inventory__block', [
  957. m('.mb-editor-inventory__block-label', 'Набор оружия:'),
  958. m('select',
  959. { oninput: m.withAttr('value', onchange), value: value },
  960. this.services.inventory.list.map(({ id, name }) => {
  961. return m('option', { key: id, value: id }, name);
  962. }))
  963. ])
  964. ])
  965. }
  966. }
  967.  
  968. styles(`
  969. .mb-editor-attribute__box {
  970. margin: 5px 0;
  971. }
  972. .mb-editor-attribute__block-label {
  973. display: inline-block;
  974. width: 110px;
  975. }
  976. .mb-editor-attribute__block-input {
  977. width: 20px;
  978. display: inline-block;
  979. }
  980. `);
  981. class EditorAttributeComponent {
  982. constructor({ attrs: { services }}) {
  983. this.services = services;
  984. }
  985. view({ attrs: { value, onchange } }) {
  986. let changeAction = (name, val) => {
  987. onchange(Object.assign({}, value, { [name]: parseInt(val) || 0 }));
  988. }
  989. return m('.mb-editor-attribute__box', [
  990. m('.mb-editor-attribute__block', [
  991. m('.mb-editor-attribute__block-label', 'Аттрибуты:'),
  992. this.services.attribute.list.map((name) => {
  993. return m('input.mb-editor-attribute__block-input',
  994. { key: name, oninput: m.withAttr('value', (value) => { changeAction(name, value) }), value: value[name] || 0 });
  995. })
  996. ])
  997. ])
  998. }
  999. }
  1000.  
  1001. styles(`
  1002. .mb-editor-army__box {
  1003. margin: 5px 0;
  1004. }
  1005. .mb-editor-army__block-label {
  1006. display: inline-block;
  1007. width: 110px;
  1008. }
  1009. .mb-editor-army__block-controls {
  1010. display: inline-block;
  1011. }
  1012. .mb-editor-army__block-input {
  1013. width: 24px;
  1014. display: inline-block;
  1015. }
  1016. .mb-editor-army__block-input:nth-child(1),
  1017. .mb-editor-army__block-input:nth-child(2),
  1018. .mb-editor-army__block-input:nth-child(3),
  1019. .mb-editor-army__block-input:nth-child(4){
  1020. width: 30px;
  1021. }
  1022. .mb-editor-army__block-import-button {
  1023. margin-left: 4px;
  1024. display: inline-block;
  1025. cursor: pointer;
  1026. }
  1027. .mb-editor-army__block-import-button:hover {
  1028. text-decoration: underline;
  1029. }
  1030. .mb-editor-army__block-import-button--importing {
  1031. animation: mb-army-import-animation 1s infinite linear;
  1032. cursor: wait;
  1033. }
  1034. .mb-editor-army__block-import-button--importing:hover {
  1035. text-decoration: none;
  1036. }
  1037. @keyframes mb-army-import-animation {
  1038. 0% { transform: rotate(0deg); }
  1039. 100% { transform: rotate(359deg); }
  1040. }
  1041. `);
  1042. class EditorArmyComponent {
  1043. constructor({ attrs: { services }}) {
  1044. this.services = services;
  1045. }
  1046. view({ attrs: { value, onchange } }) {
  1047. const changeAction = (index, val) => {
  1048. let data = value.slice();
  1049. data[index] = parseInt(val) || 0;
  1050. onchange(data);
  1051. };
  1052. const importAction = () => {
  1053. if (this.importing) return;
  1054. this.importing = true;
  1055. const finish = () => { this.importing = false };
  1056. this.services.import.getArmy()
  1057. .then((val) => {
  1058. if (val) {
  1059. onchange(val);
  1060. }
  1061. })
  1062. .then(finish, finish);
  1063. };
  1064. const importButton = () => {
  1065. return m('.mb-editor-army__block-import-button', { onclick: importAction, class: this.importing ? 'mb-editor-army__block-import-button--importing' : '' }, '<')
  1066. };
  1067. return m('.mb-editor-army__box', [
  1068. m('.mb-editor-army__block', [
  1069. m('.mb-editor-army__block-label', 'Армия:'),
  1070. m('.mb-editor-army__block-controls',
  1071. this.services.army.iterator.map((_, index) => {
  1072. return m('input.mb-editor-army__block-input',
  1073. { key: index, oninput: m.withAttr('value', (value) => { changeAction(index, value) }), value: value[index] || 0 })
  1074. })
  1075. ),
  1076. importButton()
  1077. ])
  1078. ])
  1079. }
  1080. }
  1081.  
  1082. styles(`
  1083. .mb-editor-skill__box {
  1084. margin: 5px 0;
  1085. }
  1086. .mb-editor-skill__select {
  1087. display: inline-block;
  1088. }
  1089. .mb-editor-skill__option--main {
  1090. font-weight: bold;
  1091. }
  1092. .mb-editor-skill__list-item-name {
  1093. display: inline-block;
  1094. margin-right: 4px;
  1095. }
  1096. .mb-editor-skill__list-item-button {
  1097. display: inline-block;
  1098. cursor: pointer;
  1099. }
  1100. .mb-editor-skill__list-item-button:hover {
  1101. text-decoration: underline;
  1102. }
  1103. .mb-editor-skill__import-button {
  1104. display: inline-block;
  1105. margin-left: 4px;
  1106. cursor: pointer;
  1107. }
  1108. .mb-editor-skill__import-button:hover {
  1109. text-decoration: underline;
  1110. }
  1111. .mb-editor-skill__import-button--importing {
  1112. animation: mb-skill-import-animation 1s infinite linear;
  1113. cursor: wait;
  1114. }
  1115. .mb-editor-skill__import-button--importing:hover {
  1116. text-decoration: none;
  1117. }
  1118. @keyframes mb-skill-import-animation {
  1119. 0% { transform: rotate(0deg); }
  1120. 100% { transform: rotate(359deg); }
  1121. }
  1122. `);
  1123. class EditorSkillComponent {
  1124. constructor({ attrs: { services }}) {
  1125. this.services = services;
  1126. }
  1127. view({ attrs: { value, onchange } }) {
  1128. const removeAction = (id) => {
  1129. let p = value.indexOf(id);
  1130. onchange(value.slice(0, p).concat( value.slice(p + 1) ));
  1131. };
  1132. const importAction = () => {
  1133. if (this.importing) return;
  1134. this.importing = true;
  1135. const finish = () => { this.importing = false };
  1136. this.services.import.getSkill()
  1137. .then((val) => {
  1138. if (val) {
  1139. onchange(val);
  1140. }
  1141. })
  1142. .then(finish, finish);
  1143. };
  1144. const importButton = () => {
  1145. return m('.mb-editor-skill__import-button', { onclick: importAction, class: this.importing ? 'mb-editor-skill__import-button--importing' : '' }, '<')
  1146. };
  1147. const list = () => {
  1148. return m('.mb-editor-skill__list', value.map((id) => {
  1149. return m('.mb-editor-skill__list-item', [
  1150. m('.mb-editor-skill__list-item-name', this.services.skill.map[id].name),
  1151. m('.mb-editor-skill__list-item-button', { onclick: () => { removeAction(id) } }, '[х]')
  1152. ])
  1153. }))
  1154. }
  1155. const select = () => {
  1156. return m('select.mb-editor-skill__select',
  1157. { oninput: m.withAttr('value', (id) => { onchange(value.concat(id)) }) }, [
  1158. m('option', 'Навыки:'),
  1159. this.services.skill.table.map(({ id, name, list }) => {
  1160. return m('optgroup', { key: id, label: name }, list.map(({ id, name, main }) => {
  1161. if (value.indexOf(id) !== -1) return null;
  1162. return m('option', { key: id, value: id, class: main ? 'mb-editor-skill__option--main': '' }, name)
  1163. }))
  1164. })
  1165. ])
  1166. };
  1167. return m('.mb-editor-skill__box', [
  1168. m('.mb-editor-skill__select-block', [
  1169. select(),
  1170. importButton()
  1171. ]),
  1172. list()
  1173. ])
  1174. }
  1175. }
  1176.  
  1177.  
  1178. styles(`
  1179. .mb-editor__section {
  1180. padding: 5px 6px;
  1181. display: table;
  1182. }
  1183. .mb-editor__buttons {
  1184. padding: 3px 5px 4px 5px;
  1185. border-top: 1px #5D413A solid;
  1186. background: #F5F3EA;
  1187. height: 16px;
  1188. }
  1189. .mb-editor__save-button {
  1190. font-weight: bold;
  1191. cursor: pointer;
  1192. display: inline-block;
  1193. margin-right: 8px;
  1194. }
  1195. .mb-editor__cancel-button {
  1196. cursor: pointer;
  1197. display: inline-block;
  1198. }
  1199. .mb-editor__close-button {
  1200. cursor: pointer;
  1201. display: inline-block;
  1202. }
  1203. .mb-editor__save-button:hover,
  1204. .mb-editor__cancel-button:hover,
  1205. .mb-editor__close-button:hover {
  1206. text-decoration: underline;
  1207. }
  1208. .mb-editor__section-column {
  1209. float: left;
  1210. margin-right: 30px;
  1211. }
  1212. .mb-editor__section-column:last-child {
  1213. margin-right: 0;
  1214. }
  1215. `);
  1216. class EditorComponent {
  1217. constructor({ attrs: { services }}) {
  1218. this.services = services;
  1219. }
  1220. _updateOriginItem(item) {
  1221. if (this.originItem !== item) {
  1222. this.originItem = item;
  1223. this.item = deepCopy(item);
  1224. }
  1225. }
  1226.  
  1227. cancel() {
  1228. this.item = deepCopy(this.originItem);
  1229. }
  1230. view({ attrs: { item: originItem, onchange, onclose } }) {
  1231. this._updateOriginItem(originItem);
  1232.  
  1233. let item = this.item;
  1234. let services = this.services;
  1235. const closeAction = () => {
  1236. onclose();
  1237. }
  1238. const buttons = () => {
  1239. let controls;
  1240. if (deepEquals(this.item, originItem)) {
  1241. controls = m('.mb-editor__close-button', { onclick: closeAction }, 'Закрыть');
  1242.  
  1243. } else {
  1244. controls = [
  1245. m('.mb-editor__save-button', { onclick: () => { onchange(item) }}, 'Сохранить'),
  1246. m('.mb-editor__cancel-button', { onclick: this.cancel.bind(this) }, 'Отменить')
  1247. ];
  1248. }
  1249. return m('.mb-editor__buttons', controls);
  1250. };
  1251. return m('.mb-editor__box', [
  1252. m('.mb-editor__section', [
  1253. m(EditorNameComponent, { value: item.name, onchange: (value) => { item.name = value } }),
  1254. m('.mb-editor__section-column', [
  1255. m(EditorFractionComponent, { services, value: item.fraction, onchange: (value) => { item.fraction = value } }),
  1256. m(EditorInventoryComponent, { services, value: item.inventory, onchange: (value) => { item.inventory = value } }),
  1257. m(EditorAttributeComponent, { services, value: item.attribute, onchange: (value) => { item.attribute = value } }),
  1258. m(EditorArmyComponent, { services, value: item.army, onchange: (value) => { item.army = value } }),
  1259. ]),
  1260. m('.mb-editor__section-column', [
  1261. m(EditorSkillComponent, { services, value: item.skill, onchange: (value) => { item.skill = value } }),
  1262. ])
  1263. ]),
  1264. buttons()
  1265. ])
  1266. }
  1267. }
  1268.  
  1269.  
  1270.  
  1271.  
  1272. styles(`
  1273. .mb-export-popup__box {
  1274. position: absolute;
  1275. top: 2px;
  1276. left: 2px;
  1277. border: 1px #5D413A solid;
  1278. }
  1279. .mb-export-popup__header {
  1280. background: #F5F3EA;
  1281. border-bottom: 1px #5D413A solid;
  1282. text-align: right;
  1283. }
  1284. .mb-export-popup__close-button {
  1285. display: inline-block;
  1286. cursor: pointer;
  1287. color: rgb(89, 44, 8);
  1288. padding: 4px 6px;
  1289. }
  1290. .mb-export-popup__close-button:hover {
  1291. text-decoration: underline;
  1292. }
  1293. .mb-export-popup__body {
  1294. background: #fff;
  1295. }
  1296. .mb-export-popup__body textarea {
  1297. resize: none;
  1298. box-sizing: border-box;
  1299. width: 580px;
  1300. height: 300px;
  1301. font-size: 11px;
  1302. padding: 3px 5px;
  1303. margin: 0;
  1304. border: none;
  1305. }
  1306. .mb-export-popup__footer {
  1307. padding: 3px 5px 4px 5px;
  1308. border-top: 1px #5D413A solid;
  1309. background: #F5F3EA;
  1310. height: 16px;
  1311. }
  1312. `);
  1313. class ExportPopup {
  1314. constructor({ attrs: { services }}) {
  1315. this.services = services;
  1316. }
  1317.  
  1318. oncreate({ dom, attrs: { onclose } }) {
  1319. this.releaseClickOutEventListener = nextTickAddClickOutEventListener(dom, onclose);
  1320. }
  1321. onbeforeremove() {
  1322. this.releaseClickOutEventListener();
  1323. }
  1324.  
  1325. view({ attrs: { onclose }}) {
  1326. return m('.mb-export-popup__box', [
  1327. m('.mb-export-popup__header', [
  1328. m('.mb-export-popup__close-button', { onclick: onclose }, 'Закрыть')
  1329. ]),
  1330. m('.mb-export-popup__body', [
  1331. m('textarea', { readonly: true, value: this.services.manager.serialize() })
  1332. ]),
  1333. m('.mb-export-popup__footer')
  1334. ])
  1335. }
  1336.  
  1337. };
  1338.  
  1339. styles(`
  1340. .mb-import-popup__box {
  1341. position: absolute;
  1342. top: 2px;
  1343. left: 2px;
  1344. border: 1px #5D413A solid;
  1345. }
  1346. .mb-import-popup__header {
  1347. background: #F5F3EA;
  1348. border-bottom: 1px #5D413A solid;
  1349. text-align: right;
  1350. }
  1351. .mb-import-popup__close-button,
  1352. .mb-import-popup__import-button {
  1353. display: inline-block;
  1354. cursor: pointer;
  1355. color: rgb(89, 44, 8);
  1356. padding: 4px 6px;
  1357. }
  1358. .mb-import-popup__close-button:hover,
  1359. .mb-import-popup__import-button:hover {
  1360. text-decoration: underline;
  1361. }
  1362. .mb-import-popup__import-button {
  1363. font-weight: bold;
  1364. }
  1365. .mb-import-popup__body {
  1366. background: #fff;
  1367. }
  1368. .mb-import-popup__body textarea {
  1369. resize: none;
  1370. box-sizing: border-box;
  1371. width: 580px;
  1372. height: 300px;
  1373. font-size: 11px;
  1374. padding: 3px 5px;
  1375. margin: 0;
  1376. border: none;
  1377. }
  1378. .mb-import-popup__footer {
  1379. border-top: 1px #5D413A solid;
  1380. background: #F5F3EA;
  1381. }
  1382. .mb-import-popup__import-error-message {
  1383. color: red;
  1384. margin-left: 10px;
  1385. display: inline-block;
  1386. padding: 4px 6px;
  1387. }
  1388. `);
  1389. class ImportPopup {
  1390.  
  1391. constructor({ attrs: { services }}) {
  1392. this.services = services;
  1393. this.invalid = false;
  1394. }
  1395.  
  1396. oncreate({ dom, attrs: { onclose } }) {
  1397. this.releaseClickOutEventListener = nextTickAddClickOutEventListener(dom, onclose);
  1398. }
  1399. onbeforeremove() {
  1400. this.releaseClickOutEventListener();
  1401. }
  1402.  
  1403. view({ attrs: { onclose, onimport }}) {
  1404.  
  1405. const onchange = (value) => {
  1406. this.data = value;
  1407. this.invalid = false;
  1408. }
  1409.  
  1410. const importAction = () => {
  1411. if (this.services.manager.unserialize(this.data)) {
  1412. this.data = null;
  1413. onimport();
  1414. } else {
  1415. this.invalid = true;
  1416. }
  1417. }
  1418.  
  1419. return m('.mb-import-popup__box', [
  1420. m('.mb-import-popup__header', [
  1421. m('.mb-import-popup__close-button', { onclick: onclose }, 'Закрыть')
  1422. ]),
  1423. m('.mb-import-popup__body', [
  1424. m('textarea', { value: this.data, oninput: m.withAttr('value', onchange) })
  1425. ]),
  1426. m('.mb-import-popup__footer', [
  1427. m('.mb-import-popup__import-button', { onclick: importAction }, 'Импорт'),
  1428. this.invalid
  1429. ? m('.mb-import-popup__import-error-message', 'Некорректный формат данных')
  1430. : null
  1431. ])
  1432. ])
  1433. }
  1434.  
  1435. };
  1436.  
  1437. styles(`
  1438. .mb-manager__box {
  1439. width: 650px;
  1440. border: 1px #5D413A solid;
  1441. background: #fff;
  1442. position: absolute;
  1443. left: 3px;
  1444. top: 3px;
  1445. z-index: 3;
  1446. box-sizing: border-box;
  1447. }
  1448. .mb-manager__header {
  1449. background: #F5F3EA;
  1450. border-bottom: 1px #5D413A solid;
  1451. position: relative;
  1452. display: flex;
  1453. align-items: center;
  1454. justify-content: space-between;
  1455. }
  1456. .mb-manager__header-button {
  1457. display: inline-block;
  1458. cursor: pointer;
  1459. color: rgb(89, 44, 8);
  1460. padding: 4px 0 4px 6px;
  1461. }
  1462. .mb-manager__header-button:last-child {
  1463. padding-right: 6px;
  1464. }
  1465. .mb-manager__header-button:hover {
  1466. text-decoration: underline;
  1467. }
  1468. .mb-manager__header-space {
  1469. width: 20px;
  1470. display: inline-block;
  1471. }
  1472. .mb-manager__list {
  1473. max-height: 72px;
  1474. overflow: auto;
  1475. border-bottom: 1px #5D413A solid;
  1476. margin-bottom: -1px;
  1477. white-space: nowrap;
  1478. }
  1479. .mb-manager__list-item {
  1480. padding: 0 6px;
  1481. cursor: pointer;
  1482. margin: 4px 0;
  1483. display: table;
  1484. }
  1485. .mb-manager__list-item:hover {
  1486. text-decoration: underline;
  1487. }
  1488. .mb-manager__list-item--selected {
  1489. color: rgb(255, 0, 0);
  1490. text-decoration: underline;
  1491. }
  1492. .mb-manager__list-empty {
  1493. text-align: center;
  1494. border-bottom: 1px #5D413A solid;
  1495. padding: 15px 0;
  1496. margin-bottom: -1px;
  1497. }
  1498. .mb-manager__confirm-remove {
  1499. height: 72px;
  1500. display: flex;
  1501. align-items: center;
  1502. justify-content: center;
  1503. flex-direction: column;
  1504. }
  1505. .mb-manager__confirm-remove-buttons {
  1506. margin-top: 7px;
  1507. }
  1508. .mb-manager__confirm-remove-button {
  1509. display: inline-block;
  1510. margin: 4px 4px;
  1511. cursor: pointer;
  1512. }
  1513. .mb-manager__confirm-remove-button:hover {
  1514. text-decoration: underline;
  1515. }
  1516. .mb-manager__confirm-remove-button--no {
  1517. font-weight: bold;
  1518. }
  1519. .mb-manager__confirm-remove-button:hover:not(.mb-manager__confirm-remove-button--no) {
  1520. color: red;
  1521. }
  1522. .mg-manager__body {
  1523. margin-top: 1px;
  1524. }
  1525. `);
  1526. class ManagerComponent {
  1527.  
  1528. constructor({ attrs: { services }}) {
  1529. this.services = services;
  1530. this._initSelected();
  1531. this.exportPopup = false;
  1532. this.importPopup = false;
  1533. }
  1534. _initSelected() {
  1535. let current = this.services.current.item;
  1536. if (current) {
  1537. let { founded, index } = this.services.manager.searchEquals(current);
  1538. if (founded) {
  1539. this.selected = this.services.manager.items[index];
  1540. return;
  1541. }
  1542. }
  1543. this.selected = this.services.manager.items[0];
  1544. }
  1545.  
  1546. createNew() {
  1547. this.selected = this.services.manager.createNew();
  1548. }
  1549. selectItem(item) {
  1550. this.selected = item;
  1551. }
  1552. removeSelected() {
  1553. this.confirmRemove = true;
  1554. }
  1555. confirmRemoveOk() {
  1556. this.selected = this.services.manager.remove(this.selected);
  1557. this.confirmRemove = false;
  1558. }
  1559. confirmRemoveCancel() {
  1560. this.confirmRemove = false;
  1561. }
  1562. duplicateSelected() {
  1563. this.selected = this.services.manager.duplicate(this.selected);
  1564. }
  1565. updateItem(item) {
  1566. this.selected = this.services.manager.update(item);
  1567. }
  1568. view({ attrs: { onclose } }) {
  1569. const closeAction = () => {
  1570. onclose();
  1571. };
  1572. const exportCloseAction = () => {
  1573. this.exportPopup = false;
  1574. };
  1575.  
  1576. const importCloseAction = () => {
  1577. this.importPopup = false;
  1578. };
  1579.  
  1580. const importAction = () => {
  1581. this.importPopup = false;
  1582. this.selected = null;
  1583. };
  1584.  
  1585. const headerLeft = () => {
  1586. let controls = [];
  1587. if (!this.confirmRemove) {
  1588. controls.push(
  1589. m('.mb-manager__header-button', { onclick: this.createNew.bind(this) }, 'Новый')
  1590. );
  1591.  
  1592. if (this.selected) {
  1593. controls.push(
  1594. m('.mb-manager__header-button', { onclick: this.duplicateSelected.bind(this) }, 'Копия'),
  1595. m('.mb-manager__header-button', { onclick: this.removeSelected.bind(this) }, 'Удалить')
  1596. )
  1597. }
  1598. }
  1599. return m('.mb-manager__header-left', controls);
  1600. };
  1601. const headerRight = () => {
  1602. let controls = [];
  1603. if (!this.confirmRemove) {
  1604. if (this.services.manager.items.length > 0) {
  1605. controls.push(
  1606. m('.mb-manager__header-button', { onclick: () => { this.exportPopup = true } }, 'Экспорт')
  1607. )
  1608. }
  1609. controls.push(
  1610. m('.mb-manager__header-button', { onclick: () => { this.importPopup = true } }, 'Импорт'),
  1611. m('.mb-manager__header-space')
  1612. )
  1613. }
  1614. controls.push(
  1615. m('.mb-manager__header-button', { onclick: closeAction }, 'Закрыть')
  1616. )
  1617.  
  1618. return m('.mb-manager__header-right', controls);
  1619. };
  1620. const confirmRemove = () => {
  1621. if (!this.confirmRemove) return null;
  1622. return m('.mb-manager__confirm-remove', [
  1623. m('.mb-manager__confirm-remove-message', [
  1624. `Удалить "${this.selected.name}"?`
  1625. ]),
  1626. m('.mb-manager__confirm-remove-buttons', [
  1627. m('.mb-manager__confirm-remove-button.mb-manager__confirm-remove-button--no', { onclick: this.confirmRemoveCancel.bind(this) }, 'Нет'),
  1628. m('.mb-manager__confirm-remove-button', { onclick: this.confirmRemoveOk.bind(this) }, 'Да')
  1629. ]),
  1630. ])
  1631. };
  1632. const list = () => {
  1633. if (this.confirmRemove) return null;
  1634. let items = this.services.manager.items;
  1635. if (items.length === 0) {
  1636. return m('.mb-manager__list-empty', 'Список пуст')
  1637. }
  1638. return m('.mb-manager__list', items.map((item) => {
  1639. return m('.mb-manager__list-item', {
  1640. key: item.id,
  1641. class: (this.selected || {}).id === item.id ? 'mb-manager__list-item--selected' : '',
  1642. onclick: () => { this.selectItem(item) }
  1643. }, item.name)
  1644. }))
  1645. };
  1646. const body = () => {
  1647. if (this.confirmRemove) return null;
  1648. if (!this.selected) return null;
  1649. return m('.mb-manager__body', [
  1650. m(EditorComponent, { services: this.services, item: this.selected, onchange: this.updateItem.bind(this), onclose: closeAction })
  1651. ]);
  1652. };
  1653. const popups = () => {
  1654. if (this.confirmRemove) return null;
  1655.  
  1656. return [
  1657. this.exportPopup
  1658. ? m(ExportPopup, { services: this.services, onclose: exportCloseAction })
  1659. : null,
  1660. this.importPopup
  1661. ? m(ImportPopup, { services: this.services, onclose: importCloseAction, onimport: importAction })
  1662. : null
  1663. ]
  1664. }
  1665. return m('.mb-manager__box', [
  1666. m('.mb-manager__header', [
  1667. headerLeft(),
  1668. headerRight()
  1669. ]),
  1670. confirmRemove(),
  1671. list(),
  1672. body(),
  1673. popups(),
  1674. ])
  1675. }
  1676. }
  1677.  
  1678. styles(`
  1679. .mb-selector__handler {
  1680. display: flex;
  1681. cursor: pointer;
  1682. }
  1683. .mb-selector__handler--changing {
  1684. cursor: wait;
  1685. }
  1686. .mb-selector__info {
  1687. padding: 2px 6px 4px 5px;
  1688. background: #6b6b69;
  1689. color: #f5c137;
  1690. white-space: nowrap;
  1691. border: 1px solid #f5c137;
  1692. border-left: none;
  1693. }
  1694. .mb-selector__info-error {
  1695. color: red;
  1696. }
  1697. .mb-selector__triangle-box {
  1698. background: #6b6b69;
  1699. color: #f5c137;
  1700. border: 1px solid #f5c137;
  1701. border-left: none;
  1702. padding: 2px 8px 4px 5px;
  1703. box-sizing: border-box;
  1704. position: relative;
  1705. }
  1706. .mb-selector__triangle-box:before {
  1707. content: "\\00a0";
  1708. }
  1709. .mb-selector__triangle {
  1710. width: 0;
  1711. height: 0;
  1712. border-left: 5px solid transparent;
  1713. border-right: 5px solid transparent;
  1714. border-top: 5px solid #f5c137;
  1715. position: absolute;
  1716. left: 3px;
  1717. top: 8px;
  1718. }
  1719. .mb-selector__triangle--up {
  1720. transform: rotate(180deg);
  1721. }
  1722. .mb-selector__list-handler {
  1723. z-index: 3;
  1724. position: relative;
  1725. }
  1726. .mb-selector__list-box {
  1727. position: absolute;
  1728. top: 0;
  1729. left: 0;
  1730. border: 1px #5D413A solid;
  1731. background: #fff;
  1732. }
  1733. .mb-selector__list-item {
  1734. padding: 0 6px;
  1735. cursor: pointer;
  1736. margin: 4px 0;
  1737. display: table;
  1738. white-space: nowrap;
  1739. }
  1740. .mb-selector__list-item:hover .mb-selector__list-item-name {
  1741. text-decoration: underline;
  1742. }
  1743. .mb-selector__list-item-name--current {
  1744. color: rgb(255, 0, 0);
  1745. text-decoration: underline;
  1746. cursor: default;
  1747. }
  1748. .mb-selector__list-item-name {
  1749. display: inline-block;
  1750. }
  1751. .mb-selector__list-item-force {
  1752. display: inline-block;
  1753. margin-left: 5px;
  1754. }
  1755. .mb-selector__list-item-force:hover {
  1756. text-decoration: underline;
  1757. }
  1758. .mb-selector__list-footer {
  1759. display: block;
  1760. border-top: 1px #5D413A solid;
  1761. padding: 4px 6px;
  1762. margin: 0;
  1763. background: #F5F3EA;
  1764. }
  1765. .mb-selector__list-button-cancel {
  1766. cursor: pointer;
  1767. display: inline-block;
  1768. }
  1769. .mb-selector__list-button-cancel:hover {
  1770. text-decoration: underline;
  1771. }
  1772. `);
  1773. class SelectorComponent {
  1774. constructor({ attrs: { services }}) {
  1775. this.services = services;
  1776. this.dropped = false;
  1777. this.changing = false;
  1778. this.error = false;
  1779.  
  1780. }
  1781.  
  1782. oncreate({ dom }) {
  1783. this.releaseClickOutEventListener = addClickOutEventListener(dom, () => {
  1784. this.dropped = false;
  1785. });
  1786. }
  1787. onbeforeremove() {
  1788. this.releaseClickOutEventListener();
  1789. }
  1790.  
  1791. drop() {
  1792. if (this.changing) return;
  1793. this.dropped = !this.dropped;
  1794. }
  1795. view() {
  1796. let items = this.services.manager.items;
  1797. if (!items.length) return null;
  1798. let current = this.services.current.item;
  1799. const selectAction = (item, force) => {
  1800. if (item === current && !force) return;
  1801. this.dropped = false;
  1802. this.changing = true;
  1803. this.services.current.change(item, force)
  1804. .then(() => {
  1805. this.changing = false;
  1806. this.error = false;
  1807. }, (e) => {
  1808. console.error(e);
  1809. this.changing = false;
  1810. this.error = true;
  1811. });
  1812. };
  1813. const list = () => {
  1814. if (!this.dropped) return null;
  1815. const box = m('.mb-selector__list-box', [
  1816. m('.mb-selector__list',
  1817. items.map((item) => {
  1818. return m('.mb-selector__list-item', [
  1819. m('.mb-selector__list-item-name',
  1820. this.services.current.equals(item)
  1821. ? { class: 'mb-selector__list-item-name--current' }
  1822. : { onclick: () => { selectAction(item) } },
  1823. item.name),
  1824. m('.mb-selector__list-item-force', { onclick: () => { selectAction(item, true) }}, '[*]')
  1825. ])
  1826. })),
  1827. current
  1828. ? m('.mb-selector__list-footer',
  1829. m('.mb-selector__list-button-cancel', { onclick: () => { selectAction(null); } }, 'Сбросить'))
  1830. : null,
  1831. ]);
  1832. return m('.mb-selector__list-handler', box);
  1833. };
  1834. const info = () => {
  1835. if (!this.changing && !current && !this.error) return null;
  1836. const text = () => {
  1837. if (this.changing) {
  1838. return 'Смена билда...'
  1839. } else if (this.error) {
  1840. return m('.mb-selector__info-error', 'Ошибка смены билда!')
  1841. } else {
  1842. return [
  1843. this.services.current.isExpired() ? '*' : '',
  1844. current.name
  1845. ]
  1846. }
  1847. };
  1848. return m('.mb-selector__info', text());
  1849. };
  1850. return m('.mb-selector__box', [
  1851. m('.mb-selector__handler', { onclick: this.drop.bind(this), class: this.changing ? 'mb-selector__handler--changing' : '' }, [
  1852. info(),
  1853. m('.mb-selector__triangle-box',
  1854. m('.mb-selector__triangle', { class: this.dropped ? 'mb-selector__triangle--up' : '' }))
  1855. ]),
  1856. list()
  1857. ])
  1858. }
  1859. }
  1860.  
  1861.  
  1862. styles(`
  1863. .mb-app__handler {
  1864. display: flex;
  1865. }
  1866. .mb-app__handler-editor-button {
  1867. background: #6b6b69;
  1868. color: #f5c137;
  1869. border: 1px solid #f5c137;
  1870. padding: 2px 6px 4px 6px;
  1871. cursor: pointer;
  1872. }
  1873. `);
  1874. class AppComponent {
  1875. constructor() {
  1876. this.manager = false;
  1877. this.services = new ServiceContainer();
  1878. this.services.inventory.syncNamesIfAvailable();
  1879. }
  1880. view() {
  1881. return m('.mb-app__box', [
  1882. m('.mb-app__handler', [
  1883. m('.mb-app__handler-editor-button',
  1884. { onclick: () => { this.manager = true } },
  1885. '+'),
  1886. m(SelectorComponent, { services: this.services })
  1887. ]),
  1888. this.manager
  1889. ? m(ManagerComponent, { services: this.services, onclose: () => { this.manager = false } })
  1890. : null
  1891. ]);
  1892. }
  1893. }
  1894.  
  1895.  
  1896. function mount() {
  1897. let container = document.querySelector('body table table td');
  1898. if (!container) return
  1899. let checkAllowedPage = document.querySelector('a[href*="home.php"]');
  1900. if (!checkAllowedPage) return;
  1901. let root = document.createElement('div');
  1902. root.style.position = 'absolute';
  1903. root.style.top = '0';
  1904. root.style.left = '0';
  1905. container.style.position = 'relative';
  1906. container.appendChild(root);
  1907. m.mount(root, AppComponent);
  1908. }
  1909.  
  1910. function styles(content = null, flush = false) {
  1911. let task = styles.task || {};
  1912. if (content) {
  1913. if (task.scheduled) {
  1914. task.content += content;
  1915. }
  1916. else {
  1917. let task = styles.task = {
  1918. content,
  1919. scheduled: true
  1920. }
  1921. task.timer = setTimeout(finish, 0, task)
  1922. }
  1923. }
  1924. if (flush && task.scheduled) {
  1925. clearInterval(task.timer);
  1926. finish(task);
  1927. }
  1928. function finish(task) {
  1929. let head = document.querySelector('head');
  1930. head.insertAdjacentHTML('beforeEnd',
  1931. `<style type="text/css">${task.content}</style>`);
  1932. task.scheduled = false;
  1933. }
  1934. }
  1935.  
  1936. function uniqid() {
  1937. return Date.now().toString(36) + Math.random().toString(36).slice(1);
  1938. }
  1939.  
  1940. function deepCopy(value) {
  1941. if (!value) return value;
  1942. if (value instanceof Array) {
  1943. return value.map(deepCopy);
  1944. }
  1945. if (typeof value === 'object') {
  1946. let obj = {};
  1947. for (let key of Object.keys(value)) {
  1948. obj[key] = deepCopy(value[key])
  1949. }
  1950. return obj;
  1951. }
  1952. return value;
  1953. }
  1954.  
  1955. function deepEquals(a, b) {
  1956. if (a === b) return true;
  1957. if (a instanceof Array && b instanceof Array) {
  1958. if (a.length !== b.length) return false;
  1959. for (let i = 0; i < a.length; i++) {
  1960. if (!deepEquals(a[i], b[i])) return false;
  1961. }
  1962. return true;
  1963. }
  1964. if (!a || !b) return false;
  1965. if (typeof a === 'object' && typeof b === 'object') {
  1966. let keys = Object.keys(a);
  1967. if (keys.length !== Object.keys(b).length) return false;
  1968. for (let key of keys) {
  1969. if (!b.hasOwnProperty(key)) return false;
  1970. if (!deepEquals(a[key], b[key])) return false;
  1971. }
  1972. return true;
  1973. }
  1974. return false;
  1975. }
  1976.  
  1977. function addClickOutEventListener(dom, fn) {
  1978. const listener = (event) => {
  1979. let node = event.target;
  1980. while(node && node.parentNode) {
  1981. if (node === dom) {
  1982. return;
  1983. }
  1984. node = node.parentNode;
  1985. }
  1986. fn();
  1987. m.redraw();
  1988. };
  1989. const body = document.body;
  1990. body.addEventListener('click', listener);
  1991. return () => {
  1992. body.removeEventListener('click', listener);
  1993. }
  1994. }
  1995.  
  1996. function nextTickAddClickOutEventListener(dom, fn) {
  1997.  
  1998. let releaseEventListener = null;
  1999. let timeout = setTimeout(() => {
  2000. timeout = null;
  2001. releaseEventListener = addClickOutEventListener(dom, fn);
  2002. });
  2003.  
  2004. return () => {
  2005. if (timeout) clearTimeout(timeout);
  2006. if (releaseEventListener) releaseEventListener();
  2007. }
  2008. }
  2009.  
  2010. class LocalStorageDriver {
  2011. constructor(key) {
  2012. this.key = key;
  2013. }
  2014. fetch() {
  2015. let text = localStorage.getItem(this.key);
  2016. let data;
  2017. try {
  2018. data = JSON.parse(text);
  2019. }
  2020. catch(e) {
  2021. data = null;
  2022. }
  2023. return data;
  2024. }
  2025. put(data) {
  2026. localStorage.setItem(this.key, JSON.stringify(data));
  2027. }
  2028. }
  2029.  
  2030. class LocalStorageArrayDriver extends LocalStorageDriver {
  2031. fetch() {
  2032. let data = super.fetch();
  2033. if (!Array.isArray(data)) {
  2034. data = [];
  2035. }
  2036. return data;
  2037. }
  2038. }
  2039.  
  2040. function PromiseMDecorator(promise) {
  2041. const proxyWithRedraw = (data) => {
  2042. setTimeout(m.redraw.bind(m));
  2043. return data;
  2044. }
  2045. return promise.then(
  2046. proxyWithRedraw,
  2047. (data) => proxyWithRedraw(Promise.reject(data))
  2048. );
  2049. }
  2050.  
  2051. function httpPlainRequest(method, url, data) {
  2052. if (method === 'FORM') {
  2053. let form = new FormData();
  2054. for (let key of Object.keys(data)) {
  2055. form.append(key, data[key]);
  2056. }
  2057. data = form;
  2058. method = 'POST';
  2059. }
  2060.  
  2061. return m.request({ method, url, data,
  2062. extract: ({ responseText }) => responseText
  2063. });
  2064. }
  2065.  
  2066. function main() {
  2067. try {
  2068. styles(null, true);
  2069. mount();
  2070. }
  2071. catch(e) {
  2072. console.error(e);
  2073. }
  2074. }
  2075.  
  2076. setTimeout(main);
  2077.  
  2078.  
  2079.  
  2080. // Mithrill 1.1.1
  2081.  
  2082. ;(function() {
  2083. "use strict"
  2084. function Vnode(tag, key, attrs0, children, text, dom) {
  2085. return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, _state: undefined, events: undefined, instance: undefined, skip: false}
  2086. }
  2087. Vnode.normalize = function(node) {
  2088. if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)
  2089. if (node != null && typeof node !== "object") return Vnode("#", undefined, undefined, node === false ? "" : node, undefined, undefined)
  2090. return node
  2091. }
  2092. Vnode.normalizeChildren = function normalizeChildren(children) {
  2093. for (var i = 0; i < children.length; i++) {
  2094. children[i] = Vnode.normalize(children[i])
  2095. }
  2096. return children
  2097. }
  2098. var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
  2099. var selectorCache = {}
  2100. var hasOwn = {}.hasOwnProperty
  2101. function compileSelector(selector) {
  2102. var match, tag = "div", classes = [], attrs = {}
  2103. while (match = selectorParser.exec(selector)) {
  2104. var type = match[1], value = match[2]
  2105. if (type === "" && value !== "") tag = value
  2106. else if (type === "#") attrs.id = value
  2107. else if (type === ".") classes.push(value)
  2108. else if (match[3][0] === "[") {
  2109. var attrValue = match[6]
  2110. if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
  2111. if (match[4] === "class") classes.push(attrValue)
  2112. else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true
  2113. }
  2114. }
  2115. if (classes.length > 0) attrs.className = classes.join(" ")
  2116. return selectorCache[selector] = {tag: tag, attrs: attrs}
  2117. }
  2118. function execSelector(state, attrs, children) {
  2119. var hasAttrs = false, childList, text
  2120. var className = attrs.className || attrs.class
  2121. for (var key in state.attrs) {
  2122. if (hasOwn.call(state.attrs, key)) {
  2123. attrs[key] = state.attrs[key]
  2124. }
  2125. }
  2126. if (className !== undefined) {
  2127. if (attrs.class !== undefined) {
  2128. attrs.class = undefined
  2129. attrs.className = className
  2130. }
  2131. if (state.attrs.className != null) {
  2132. attrs.className = state.attrs.className + " " + className
  2133. }
  2134. }
  2135. for (var key in attrs) {
  2136. if (hasOwn.call(attrs, key) && key !== "key") {
  2137. hasAttrs = true
  2138. break
  2139. }
  2140. }
  2141. if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") {
  2142. text = children[0].children
  2143. } else {
  2144. childList = children
  2145. }
  2146. return Vnode(state.tag, attrs.key, hasAttrs ? attrs : undefined, childList, text)
  2147. }
  2148. function hyperscript(selector) {
  2149. // Because sloppy mode sucks
  2150. var attrs = arguments[1], start = 2, children
  2151. if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") {
  2152. throw Error("The selector must be either a string or a component.");
  2153. }
  2154. if (typeof selector === "string") {
  2155. var cached = selectorCache[selector] || compileSelector(selector)
  2156. }
  2157. if (attrs == null) {
  2158. attrs = {}
  2159. } else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) {
  2160. attrs = {}
  2161. start = 1
  2162. }
  2163. if (arguments.length === start + 1) {
  2164. children = arguments[start]
  2165. if (!Array.isArray(children)) children = [children]
  2166. } else {
  2167. children = []
  2168. while (start < arguments.length) children.push(arguments[start++])
  2169. }
  2170. var normalized = Vnode.normalizeChildren(children)
  2171. if (typeof selector === "string") {
  2172. return execSelector(cached, attrs, normalized)
  2173. } else {
  2174. return Vnode(selector, attrs.key, attrs, normalized)
  2175. }
  2176. }
  2177. hyperscript.trust = function(html) {
  2178. if (html == null) html = ""
  2179. return Vnode("<", undefined, undefined, html, undefined, undefined)
  2180. }
  2181. hyperscript.fragment = function(attrs1, children) {
  2182. return Vnode("[", attrs1.key, attrs1, Vnode.normalizeChildren(children), undefined, undefined)
  2183. }
  2184. var m = hyperscript
  2185. /** @constructor */
  2186. var PromisePolyfill = function(executor) {
  2187. if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`")
  2188. if (typeof executor !== "function") throw new TypeError("executor must be a function")
  2189. var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false)
  2190. var instance = self._instance = {resolvers: resolvers, rejectors: rejectors}
  2191. var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
  2192. function handler(list, shouldAbsorb) {
  2193. return function execute(value) {
  2194. var then
  2195. try {
  2196. if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") {
  2197. if (value === self) throw new TypeError("Promise can't be resolved w/ itself")
  2198. executeOnce(then.bind(value))
  2199. }
  2200. else {
  2201. callAsync(function() {
  2202. if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value)
  2203. for (var i = 0; i < list.length; i++) list[i](value)
  2204. resolvers.length = 0, rejectors.length = 0
  2205. instance.state = shouldAbsorb
  2206. instance.retry = function() {execute(value)}
  2207. })
  2208. }
  2209. }
  2210. catch (e) {
  2211. rejectCurrent(e)
  2212. }
  2213. }
  2214. }
  2215. function executeOnce(then) {
  2216. var runs = 0
  2217. function run(fn) {
  2218. return function(value) {
  2219. if (runs++ > 0) return
  2220. fn(value)
  2221. }
  2222. }
  2223. var onerror = run(rejectCurrent)
  2224. try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)}
  2225. }
  2226. executeOnce(executor)
  2227. }
  2228. PromisePolyfill.prototype.then = function(onFulfilled, onRejection) {
  2229. var self = this, instance = self._instance
  2230. function handle(callback, list, next, state) {
  2231. list.push(function(value) {
  2232. if (typeof callback !== "function") next(value)
  2233. else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
  2234. })
  2235. if (typeof instance.retry === "function" && state === instance.state) instance.retry()
  2236. }
  2237. var resolveNext, rejectNext
  2238. var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject})
  2239. handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false)
  2240. return promise
  2241. }
  2242. PromisePolyfill.prototype.catch = function(onRejection) {
  2243. return this.then(null, onRejection)
  2244. }
  2245. PromisePolyfill.resolve = function(value) {
  2246. if (value instanceof PromisePolyfill) return value
  2247. return new PromisePolyfill(function(resolve) {resolve(value)})
  2248. }
  2249. PromisePolyfill.reject = function(value) {
  2250. return new PromisePolyfill(function(resolve, reject) {reject(value)})
  2251. }
  2252. PromisePolyfill.all = function(list) {
  2253. return new PromisePolyfill(function(resolve, reject) {
  2254. var total = list.length, count = 0, values = []
  2255. if (list.length === 0) resolve([])
  2256. else for (var i = 0; i < list.length; i++) {
  2257. (function(i) {
  2258. function consume(value) {
  2259. count++
  2260. values[i] = value
  2261. if (count === total) resolve(values)
  2262. }
  2263. if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") {
  2264. list[i].then(consume, reject)
  2265. }
  2266. else consume(list[i])
  2267. })(i)
  2268. }
  2269. })
  2270. }
  2271. PromisePolyfill.race = function(list) {
  2272. return new PromisePolyfill(function(resolve, reject) {
  2273. for (var i = 0; i < list.length; i++) {
  2274. list[i].then(resolve, reject)
  2275. }
  2276. })
  2277. }
  2278. if (typeof window !== "undefined") {
  2279. if (typeof window.Promise === "undefined") window.Promise = PromisePolyfill
  2280. var PromisePolyfill = window.Promise
  2281. } else if (typeof global !== "undefined") {
  2282. if (typeof global.Promise === "undefined") global.Promise = PromisePolyfill
  2283. var PromisePolyfill = global.Promise
  2284. } else {
  2285. }
  2286. var buildQueryString = function(object) {
  2287. if (Object.prototype.toString.call(object) !== "[object Object]") return ""
  2288. var args = []
  2289. for (var key0 in object) {
  2290. destructure(key0, object[key0])
  2291. }
  2292. return args.join("&")
  2293. function destructure(key0, value) {
  2294. if (Array.isArray(value)) {
  2295. for (var i = 0; i < value.length; i++) {
  2296. destructure(key0 + "[" + i + "]", value[i])
  2297. }
  2298. }
  2299. else if (Object.prototype.toString.call(value) === "[object Object]") {
  2300. for (var i in value) {
  2301. destructure(key0 + "[" + i + "]", value[i])
  2302. }
  2303. }
  2304. else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : ""))
  2305. }
  2306. }
  2307. var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i")
  2308. var _8 = function($window, Promise) {
  2309. var callbackCount = 0
  2310. var oncompletion
  2311. function setCompletionCallback(callback) {oncompletion = callback}
  2312. function finalizer() {
  2313. var count = 0
  2314. function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
  2315. return function finalize(promise0) {
  2316. var then0 = promise0.then
  2317. promise0.then = function() {
  2318. count++
  2319. var next = then0.apply(promise0, arguments)
  2320. next.then(complete, function(e) {
  2321. complete()
  2322. if (count === 0) throw e
  2323. })
  2324. return finalize(next)
  2325. }
  2326. return promise0
  2327. }
  2328. }
  2329. function normalize(args, extra) {
  2330. if (typeof args === "string") {
  2331. var url = args
  2332. args = extra || {}
  2333. if (args.url == null) args.url = url
  2334. }
  2335. return args
  2336. }
  2337. function request(args, extra) {
  2338. var finalize = finalizer()
  2339. args = normalize(args, extra)
  2340. var promise0 = new Promise(function(resolve, reject) {
  2341. if (args.method == null) args.method = "GET"
  2342. args.method = args.method.toUpperCase()
  2343. var useBody = (args.method === "GET" || args.method === "TRACE") ? false : (typeof args.useBody === "boolean" ? args.useBody : true)
  2344. if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify
  2345. if (typeof args.deserialize !== "function") args.deserialize = deserialize
  2346. if (typeof args.extract !== "function") args.extract = extract
  2347. args.url = interpolate(args.url, args.data)
  2348. if (useBody) args.data = args.serialize(args.data)
  2349. else args.url = assemble(args.url, args.data)
  2350. var xhr = new $window.XMLHttpRequest(),
  2351. aborted = false,
  2352. _abort = xhr.abort
  2353. xhr.abort = function abort() {
  2354. aborted = true
  2355. _abort.call(xhr)
  2356. }
  2357. xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined)
  2358. if (args.serialize === JSON.stringify && useBody) {
  2359. xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8")
  2360. }
  2361. if (args.deserialize === deserialize) {
  2362. xhr.setRequestHeader("Accept", "application/json, text/*")
  2363. }
  2364. if (args.withCredentials) xhr.withCredentials = args.withCredentials
  2365. for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) {
  2366. xhr.setRequestHeader(key, args.headers[key])
  2367. }
  2368. if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr
  2369. xhr.onreadystatechange = function() {
  2370. // Don't throw errors on xhr.abort().
  2371. if(aborted) return
  2372. if (xhr.readyState === 4) {
  2373. try {
  2374. var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args))
  2375. if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) {
  2376. resolve(cast(args.type, response))
  2377. }
  2378. else {
  2379. var error = new Error(xhr.responseText)
  2380. for (var key in response) error[key] = response[key]
  2381. reject(error)
  2382. }
  2383. }
  2384. catch (e) {
  2385. reject(e)
  2386. }
  2387. }
  2388. }
  2389. if (useBody && (args.data != null)) xhr.send(args.data)
  2390. else xhr.send()
  2391. })
  2392. return args.background === true ? promise0 : finalize(promise0)
  2393. }
  2394. function jsonp(args, extra) {
  2395. var finalize = finalizer()
  2396. args = normalize(args, extra)
  2397. var promise0 = new Promise(function(resolve, reject) {
  2398. var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
  2399. var script = $window.document.createElement("script")
  2400. $window[callbackName] = function(data) {
  2401. script.parentNode.removeChild(script)
  2402. resolve(cast(args.type, data))
  2403. delete $window[callbackName]
  2404. }
  2405. script.onerror = function() {
  2406. script.parentNode.removeChild(script)
  2407. reject(new Error("JSONP request failed"))
  2408. delete $window[callbackName]
  2409. }
  2410. if (args.data == null) args.data = {}
  2411. args.url = interpolate(args.url, args.data)
  2412. args.data[args.callbackKey || "callback"] = callbackName
  2413. script.src = assemble(args.url, args.data)
  2414. $window.document.documentElement.appendChild(script)
  2415. })
  2416. return args.background === true? promise0 : finalize(promise0)
  2417. }
  2418. function interpolate(url, data) {
  2419. if (data == null) return url
  2420. var tokens = url.match(/:[^\/]+/gi) || []
  2421. for (var i = 0; i < tokens.length; i++) {
  2422. var key = tokens[i].slice(1)
  2423. if (data[key] != null) {
  2424. url = url.replace(tokens[i], data[key])
  2425. }
  2426. }
  2427. return url
  2428. }
  2429. function assemble(url, data) {
  2430. var querystring = buildQueryString(data)
  2431. if (querystring !== "") {
  2432. var prefix = url.indexOf("?") < 0 ? "?" : "&"
  2433. url += prefix + querystring
  2434. }
  2435. return url
  2436. }
  2437. function deserialize(data) {
  2438. try {return data !== "" ? JSON.parse(data) : null}
  2439. catch (e) {throw new Error(data)}
  2440. }
  2441. function extract(xhr) {return xhr.responseText}
  2442. function cast(type0, data) {
  2443. if (typeof type0 === "function") {
  2444. if (Array.isArray(data)) {
  2445. for (var i = 0; i < data.length; i++) {
  2446. data[i] = new type0(data[i])
  2447. }
  2448. }
  2449. else return new type0(data)
  2450. }
  2451. return data
  2452. }
  2453. return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback}
  2454. }
  2455. var requestService = _8(window, PromisePolyfill)
  2456. var coreRenderer = function($window) {
  2457. var $doc = $window.document
  2458. var $emptyFragment = $doc.createDocumentFragment()
  2459. var nameSpace = {
  2460. svg: "http://www.w3.org/2000/svg",
  2461. math: "http://www.w3.org/1998/Math/MathML"
  2462. }
  2463. var onevent
  2464. function setEventCallback(callback) {return onevent = callback}
  2465. function getNameSpace(vnode) {
  2466. return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag]
  2467. }
  2468. //create
  2469. function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
  2470. for (var i = start; i < end; i++) {
  2471. var vnode = vnodes[i]
  2472. if (vnode != null) {
  2473. createNode(parent, vnode, hooks, ns, nextSibling)
  2474. }
  2475. }
  2476. }
  2477. function createNode(parent, vnode, hooks, ns, nextSibling) {
  2478. var tag = vnode.tag
  2479. if (typeof tag === "string") {
  2480. vnode.state = {}
  2481. if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
  2482. switch (tag) {
  2483. case "#": return createText(parent, vnode, nextSibling)
  2484. case "<": return createHTML(parent, vnode, nextSibling)
  2485. case "[": return createFragment(parent, vnode, hooks, ns, nextSibling)
  2486. default: return createElement(parent, vnode, hooks, ns, nextSibling)
  2487. }
  2488. }
  2489. else return createComponent(parent, vnode, hooks, ns, nextSibling)
  2490. }
  2491. function createText(parent, vnode, nextSibling) {
  2492. vnode.dom = $doc.createTextNode(vnode.children)
  2493. insertNode(parent, vnode.dom, nextSibling)
  2494. return vnode.dom
  2495. }
  2496. function createHTML(parent, vnode, nextSibling) {
  2497. var match1 = vnode.children.match(/^\s*?<(\w+)/im) || []
  2498. var parent1 = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match1[1]] || "div"
  2499. var temp = $doc.createElement(parent1)
  2500. temp.innerHTML = vnode.children
  2501. vnode.dom = temp.firstChild
  2502. vnode.domSize = temp.childNodes.length
  2503. var fragment = $doc.createDocumentFragment()
  2504. var child
  2505. while (child = temp.firstChild) {
  2506. fragment.appendChild(child)
  2507. }
  2508. insertNode(parent, fragment, nextSibling)
  2509. return fragment
  2510. }
  2511. function createFragment(parent, vnode, hooks, ns, nextSibling) {
  2512. var fragment = $doc.createDocumentFragment()
  2513. if (vnode.children != null) {
  2514. var children = vnode.children
  2515. createNodes(fragment, children, 0, children.length, hooks, null, ns)
  2516. }
  2517. vnode.dom = fragment.firstChild
  2518. vnode.domSize = fragment.childNodes.length
  2519. insertNode(parent, fragment, nextSibling)
  2520. return fragment
  2521. }
  2522. function createElement(parent, vnode, hooks, ns, nextSibling) {
  2523. var tag = vnode.tag
  2524. var attrs2 = vnode.attrs
  2525. var is = attrs2 && attrs2.is
  2526. ns = getNameSpace(vnode) || ns
  2527. var element = ns ?
  2528. is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) :
  2529. is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag)
  2530. vnode.dom = element
  2531. if (attrs2 != null) {
  2532. setAttrs(vnode, attrs2, ns)
  2533. }
  2534. insertNode(parent, element, nextSibling)
  2535. if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
  2536. setContentEditable(vnode)
  2537. }
  2538. else {
  2539. if (vnode.text != null) {
  2540. if (vnode.text !== "") element.textContent = vnode.text
  2541. else vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
  2542. }
  2543. if (vnode.children != null) {
  2544. var children = vnode.children
  2545. createNodes(element, children, 0, children.length, hooks, null, ns)
  2546. setLateAttrs(vnode)
  2547. }
  2548. }
  2549. return element
  2550. }
  2551. function initComponent(vnode, hooks) {
  2552. var sentinel
  2553. if (typeof vnode.tag.view === "function") {
  2554. vnode.state = Object.create(vnode.tag)
  2555. sentinel = vnode.state.view
  2556. if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
  2557. sentinel.$$reentrantLock$$ = true
  2558. } else {
  2559. vnode.state = void 0
  2560. sentinel = vnode.tag
  2561. if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
  2562. sentinel.$$reentrantLock$$ = true
  2563. vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode)
  2564. }
  2565. vnode._state = vnode.state
  2566. if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
  2567. initLifecycle(vnode._state, vnode, hooks)
  2568. vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
  2569. if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
  2570. sentinel.$$reentrantLock$$ = null
  2571. }
  2572. function createComponent(parent, vnode, hooks, ns, nextSibling) {
  2573. initComponent(vnode, hooks)
  2574. if (vnode.instance != null) {
  2575. var element = createNode(parent, vnode.instance, hooks, ns, nextSibling)
  2576. vnode.dom = vnode.instance.dom
  2577. vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
  2578. insertNode(parent, element, nextSibling)
  2579. return element
  2580. }
  2581. else {
  2582. vnode.domSize = 0
  2583. return $emptyFragment
  2584. }
  2585. }
  2586. //update
  2587. function updateNodes(parent, old, vnodes, recycling, hooks, nextSibling, ns) {
  2588. if (old === vnodes || old == null && vnodes == null) return
  2589. else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
  2590. else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
  2591. else {
  2592. if (old.length === vnodes.length) {
  2593. var isUnkeyed = false
  2594. for (var i = 0; i < vnodes.length; i++) {
  2595. if (vnodes[i] != null && old[i] != null) {
  2596. isUnkeyed = vnodes[i].key == null && old[i].key == null
  2597. break
  2598. }
  2599. }
  2600. if (isUnkeyed) {
  2601. for (var i = 0; i < old.length; i++) {
  2602. if (old[i] === vnodes[i]) continue
  2603. else if (old[i] == null && vnodes[i] != null) createNode(parent, vnodes[i], hooks, ns, getNextSibling(old, i + 1, nextSibling))
  2604. else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes)
  2605. else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns)
  2606. }
  2607. return
  2608. }
  2609. }
  2610. recycling = recycling || isRecyclable(old, vnodes)
  2611. if (recycling) {
  2612. var pool = old.pool
  2613. old = old.concat(old.pool)
  2614. }
  2615. var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map
  2616. while (oldEnd >= oldStart && end >= start) {
  2617. var o = old[oldStart], v = vnodes[start]
  2618. if (o === v && !recycling) oldStart++, start++
  2619. else if (o == null) oldStart++
  2620. else if (v == null) start++
  2621. else if (o.key === v.key) {
  2622. var shouldRecycle = (pool != null && oldStart >= old.length - pool.length) || ((pool == null) && recycling)
  2623. oldStart++, start++
  2624. updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), shouldRecycle, ns)
  2625. if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
  2626. }
  2627. else {
  2628. var o = old[oldEnd]
  2629. if (o === v && !recycling) oldEnd--, start++
  2630. else if (o == null) oldEnd--
  2631. else if (v == null) start++
  2632. else if (o.key === v.key) {
  2633. var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
  2634. updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
  2635. if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling))
  2636. oldEnd--, start++
  2637. }
  2638. else break
  2639. }
  2640. }
  2641. while (oldEnd >= oldStart && end >= start) {
  2642. var o = old[oldEnd], v = vnodes[end]
  2643. if (o === v && !recycling) oldEnd--, end--
  2644. else if (o == null) oldEnd--
  2645. else if (v == null) end--
  2646. else if (o.key === v.key) {
  2647. var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
  2648. updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
  2649. if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
  2650. if (o.dom != null) nextSibling = o.dom
  2651. oldEnd--, end--
  2652. }
  2653. else {
  2654. if (!map) map = getKeyMap(old, oldEnd)
  2655. if (v != null) {
  2656. var oldIndex = map[v.key]
  2657. if (oldIndex != null) {
  2658. var movable = old[oldIndex]
  2659. var shouldRecycle = (pool != null && oldIndex >= old.length - pool.length) || ((pool == null) && recycling)
  2660. updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
  2661. insertNode(parent, toFragment(movable), nextSibling)
  2662. old[oldIndex].skip = true
  2663. if (movable.dom != null) nextSibling = movable.dom
  2664. }
  2665. else {
  2666. var dom = createNode(parent, v, hooks, ns, nextSibling)
  2667. nextSibling = dom
  2668. }
  2669. }
  2670. end--
  2671. }
  2672. if (end < start) break
  2673. }
  2674. createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
  2675. removeNodes(old, oldStart, oldEnd + 1, vnodes)
  2676. }
  2677. }
  2678. function updateNode(parent, old, vnode, hooks, nextSibling, recycling, ns) {
  2679. var oldTag = old.tag, tag = vnode.tag
  2680. if (oldTag === tag) {
  2681. vnode.state = old.state
  2682. vnode._state = old._state
  2683. vnode.events = old.events
  2684. if (!recycling && shouldNotUpdate(vnode, old)) return
  2685. if (typeof oldTag === "string") {
  2686. if (vnode.attrs != null) {
  2687. if (recycling) {
  2688. vnode.state = {}
  2689. initLifecycle(vnode.attrs, vnode, hooks)
  2690. }
  2691. else updateLifecycle(vnode.attrs, vnode, hooks)
  2692. }
  2693. switch (oldTag) {
  2694. case "#": updateText(old, vnode); break
  2695. case "<": updateHTML(parent, old, vnode, nextSibling); break
  2696. case "[": updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns); break
  2697. default: updateElement(old, vnode, recycling, hooks, ns)
  2698. }
  2699. }
  2700. else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns)
  2701. }
  2702. else {
  2703. removeNode(old, null)
  2704. createNode(parent, vnode, hooks, ns, nextSibling)
  2705. }
  2706. }
  2707. function updateText(old, vnode) {
  2708. if (old.children.toString() !== vnode.children.toString()) {
  2709. old.dom.nodeValue = vnode.children
  2710. }
  2711. vnode.dom = old.dom
  2712. }
  2713. function updateHTML(parent, old, vnode, nextSibling) {
  2714. if (old.children !== vnode.children) {
  2715. toFragment(old)
  2716. createHTML(parent, vnode, nextSibling)
  2717. }
  2718. else vnode.dom = old.dom, vnode.domSize = old.domSize
  2719. }
  2720. function updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns) {
  2721. updateNodes(parent, old.children, vnode.children, recycling, hooks, nextSibling, ns)
  2722. var domSize = 0, children = vnode.children
  2723. vnode.dom = null
  2724. if (children != null) {
  2725. for (var i = 0; i < children.length; i++) {
  2726. var child = children[i]
  2727. if (child != null && child.dom != null) {
  2728. if (vnode.dom == null) vnode.dom = child.dom
  2729. domSize += child.domSize || 1
  2730. }
  2731. }
  2732. if (domSize !== 1) vnode.domSize = domSize
  2733. }
  2734. }
  2735. function updateElement(old, vnode, recycling, hooks, ns) {
  2736. var element = vnode.dom = old.dom
  2737. ns = getNameSpace(vnode) || ns
  2738. if (vnode.tag === "textarea") {
  2739. if (vnode.attrs == null) vnode.attrs = {}
  2740. if (vnode.text != null) {
  2741. vnode.attrs.value = vnode.text //FIXME handle0 multiple children
  2742. vnode.text = undefined
  2743. }
  2744. }
  2745. updateAttrs(vnode, old.attrs, vnode.attrs, ns)
  2746. if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
  2747. setContentEditable(vnode)
  2748. }
  2749. else if (old.text != null && vnode.text != null && vnode.text !== "") {
  2750. if (old.text.toString() !== vnode.text.toString()) old.dom.firstChild.nodeValue = vnode.text
  2751. }
  2752. else {
  2753. if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)]
  2754. if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
  2755. updateNodes(element, old.children, vnode.children, recycling, hooks, null, ns)
  2756. }
  2757. }
  2758. function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) {
  2759. if (recycling) {
  2760. initComponent(vnode, hooks)
  2761. } else {
  2762. vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
  2763. if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
  2764. if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks)
  2765. updateLifecycle(vnode._state, vnode, hooks)
  2766. }
  2767. if (vnode.instance != null) {
  2768. if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling)
  2769. else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns)
  2770. vnode.dom = vnode.instance.dom
  2771. vnode.domSize = vnode.instance.domSize
  2772. }
  2773. else if (old.instance != null) {
  2774. removeNode(old.instance, null)
  2775. vnode.dom = undefined
  2776. vnode.domSize = 0
  2777. }
  2778. else {
  2779. vnode.dom = old.dom
  2780. vnode.domSize = old.domSize
  2781. }
  2782. }
  2783. function isRecyclable(old, vnodes) {
  2784. if (old.pool != null && Math.abs(old.pool.length - vnodes.length) <= Math.abs(old.length - vnodes.length)) {
  2785. var oldChildrenLength = old[0] && old[0].children && old[0].children.length || 0
  2786. var poolChildrenLength = old.pool[0] && old.pool[0].children && old.pool[0].children.length || 0
  2787. var vnodesChildrenLength = vnodes[0] && vnodes[0].children && vnodes[0].children.length || 0
  2788. if (Math.abs(poolChildrenLength - vnodesChildrenLength) <= Math.abs(oldChildrenLength - vnodesChildrenLength)) {
  2789. return true
  2790. }
  2791. }
  2792. return false
  2793. }
  2794. function getKeyMap(vnodes, end) {
  2795. var map = {}, i = 0
  2796. for (var i = 0; i < end; i++) {
  2797. var vnode = vnodes[i]
  2798. if (vnode != null) {
  2799. var key2 = vnode.key
  2800. if (key2 != null) map[key2] = i
  2801. }
  2802. }
  2803. return map
  2804. }
  2805. function toFragment(vnode) {
  2806. var count0 = vnode.domSize
  2807. if (count0 != null || vnode.dom == null) {
  2808. var fragment = $doc.createDocumentFragment()
  2809. if (count0 > 0) {
  2810. var dom = vnode.dom
  2811. while (--count0) fragment.appendChild(dom.nextSibling)
  2812. fragment.insertBefore(dom, fragment.firstChild)
  2813. }
  2814. return fragment
  2815. }
  2816. else return vnode.dom
  2817. }
  2818. function getNextSibling(vnodes, i, nextSibling) {
  2819. for (; i < vnodes.length; i++) {
  2820. if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom
  2821. }
  2822. return nextSibling
  2823. }
  2824. function insertNode(parent, dom, nextSibling) {
  2825. if (nextSibling && nextSibling.parentNode) parent.insertBefore(dom, nextSibling)
  2826. else parent.appendChild(dom)
  2827. }
  2828. function setContentEditable(vnode) {
  2829. var children = vnode.children
  2830. if (children != null && children.length === 1 && children[0].tag === "<") {
  2831. var content = children[0].children
  2832. if (vnode.dom.innerHTML !== content) vnode.dom.innerHTML = content
  2833. }
  2834. else if (vnode.text != null || children != null && children.length !== 0) throw new Error("Child node of a contenteditable must be trusted")
  2835. }
  2836. //remove
  2837. function removeNodes(vnodes, start, end, context) {
  2838. for (var i = start; i < end; i++) {
  2839. var vnode = vnodes[i]
  2840. if (vnode != null) {
  2841. if (vnode.skip) vnode.skip = false
  2842. else removeNode(vnode, context)
  2843. }
  2844. }
  2845. }
  2846. function removeNode(vnode, context) {
  2847. var expected = 1, called = 0
  2848. if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") {
  2849. var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode)
  2850. if (result != null && typeof result.then === "function") {
  2851. expected++
  2852. result.then(continuation, continuation)
  2853. }
  2854. }
  2855. if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeremove === "function") {
  2856. var result = vnode._state.onbeforeremove.call(vnode.state, vnode)
  2857. if (result != null && typeof result.then === "function") {
  2858. expected++
  2859. result.then(continuation, continuation)
  2860. }
  2861. }
  2862. continuation()
  2863. function continuation() {
  2864. if (++called === expected) {
  2865. onremove(vnode)
  2866. if (vnode.dom) {
  2867. var count0 = vnode.domSize || 1
  2868. if (count0 > 1) {
  2869. var dom = vnode.dom
  2870. while (--count0) {
  2871. removeNodeFromDOM(dom.nextSibling)
  2872. }
  2873. }
  2874. removeNodeFromDOM(vnode.dom)
  2875. if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && typeof vnode.tag === "string") { //TODO test custom elements
  2876. if (!context.pool) context.pool = [vnode]
  2877. else context.pool.push(vnode)
  2878. }
  2879. }
  2880. }
  2881. }
  2882. }
  2883. function removeNodeFromDOM(node) {
  2884. var parent = node.parentNode
  2885. if (parent != null) parent.removeChild(node)
  2886. }
  2887. function onremove(vnode) {
  2888. if (vnode.attrs && typeof vnode.attrs.onremove === "function") vnode.attrs.onremove.call(vnode.state, vnode)
  2889. if (typeof vnode.tag !== "string" && typeof vnode._state.onremove === "function") vnode._state.onremove.call(vnode.state, vnode)
  2890. if (vnode.instance != null) onremove(vnode.instance)
  2891. else {
  2892. var children = vnode.children
  2893. if (Array.isArray(children)) {
  2894. for (var i = 0; i < children.length; i++) {
  2895. var child = children[i]
  2896. if (child != null) onremove(child)
  2897. }
  2898. }
  2899. }
  2900. }
  2901. //attrs2
  2902. function setAttrs(vnode, attrs2, ns) {
  2903. for (var key2 in attrs2) {
  2904. setAttr(vnode, key2, null, attrs2[key2], ns)
  2905. }
  2906. }
  2907. function setAttr(vnode, key2, old, value, ns) {
  2908. var element = vnode.dom
  2909. if (key2 === "key" || key2 === "is" || (old === value && !isFormAttribute(vnode, key2)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key2)) return
  2910. var nsLastIndex = key2.indexOf(":")
  2911. if (nsLastIndex > -1 && key2.substr(0, nsLastIndex) === "xlink") {
  2912. element.setAttributeNS("http://www.w3.org/1999/xlink", key2.slice(nsLastIndex + 1), value)
  2913. }
  2914. else if (key2[0] === "o" && key2[1] === "n" && typeof value === "function") updateEvent(vnode, key2, value)
  2915. else if (key2 === "style") updateStyle(element, old, value)
  2916. else if (key2 in element && !isAttribute(key2) && ns === undefined && !isCustomElement(vnode)) {
  2917. if (key2 === "value") {
  2918. var normalized0 = "" + value // eslint-disable-line no-implicit-coercion
  2919. //setting input[value] to same value by typing on focused element moves cursor to end in Chrome
  2920. if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return
  2921. //setting select[value] to same value while having select open blinks select dropdown in Chrome
  2922. if (vnode.tag === "select") {
  2923. if (value === null) {
  2924. if (vnode.dom.selectedIndex === -1 && vnode.dom === $doc.activeElement) return
  2925. } else {
  2926. if (old !== null && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return
  2927. }
  2928. }
  2929. //setting option[value] to same value while having select open blinks select dropdown in Chrome
  2930. if (vnode.tag === "option" && old != null && vnode.dom.value === normalized0) return
  2931. }
  2932. // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur.
  2933. if (vnode.tag === "input" && key2 === "type") {
  2934. element.setAttribute(key2, value)
  2935. return
  2936. }
  2937. element[key2] = value
  2938. }
  2939. else {
  2940. if (typeof value === "boolean") {
  2941. if (value) element.setAttribute(key2, "")
  2942. else element.removeAttribute(key2)
  2943. }
  2944. else element.setAttribute(key2 === "className" ? "class" : key2, value)
  2945. }
  2946. }
  2947. function setLateAttrs(vnode) {
  2948. var attrs2 = vnode.attrs
  2949. if (vnode.tag === "select" && attrs2 != null) {
  2950. if ("value" in attrs2) setAttr(vnode, "value", null, attrs2.value, undefined)
  2951. if ("selectedIndex" in attrs2) setAttr(vnode, "selectedIndex", null, attrs2.selectedIndex, undefined)
  2952. }
  2953. }
  2954. function updateAttrs(vnode, old, attrs2, ns) {
  2955. if (attrs2 != null) {
  2956. for (var key2 in attrs2) {
  2957. setAttr(vnode, key2, old && old[key2], attrs2[key2], ns)
  2958. }
  2959. }
  2960. if (old != null) {
  2961. for (var key2 in old) {
  2962. if (attrs2 == null || !(key2 in attrs2)) {
  2963. if (key2 === "className") key2 = "class"
  2964. if (key2[0] === "o" && key2[1] === "n" && !isLifecycleMethod(key2)) updateEvent(vnode, key2, undefined)
  2965. else if (key2 !== "key") vnode.dom.removeAttribute(key2)
  2966. }
  2967. }
  2968. }
  2969. }
  2970. function isFormAttribute(vnode, attr) {
  2971. return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === $doc.activeElement
  2972. }
  2973. function isLifecycleMethod(attr) {
  2974. return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate"
  2975. }
  2976. function isAttribute(attr) {
  2977. return attr === "href" || attr === "list" || attr === "form" || attr === "width" || attr === "height"// || attr === "type"
  2978. }
  2979. function isCustomElement(vnode){
  2980. return vnode.attrs.is || vnode.tag.indexOf("-") > -1
  2981. }
  2982. function hasIntegrationMethods(source) {
  2983. return source != null && (source.oncreate || source.onupdate || source.onbeforeremove || source.onremove)
  2984. }
  2985. //style
  2986. function updateStyle(element, old, style) {
  2987. if (old === style) element.style.cssText = "", old = null
  2988. if (style == null) element.style.cssText = ""
  2989. else if (typeof style === "string") element.style.cssText = style
  2990. else {
  2991. if (typeof old === "string") element.style.cssText = ""
  2992. for (var key2 in style) {
  2993. element.style[key2] = style[key2]
  2994. }
  2995. if (old != null && typeof old !== "string") {
  2996. for (var key2 in old) {
  2997. if (!(key2 in style)) element.style[key2] = ""
  2998. }
  2999. }
  3000. }
  3001. }
  3002. //event
  3003. function updateEvent(vnode, key2, value) {
  3004. var element = vnode.dom
  3005. var callback = typeof onevent !== "function" ? value : function(e) {
  3006. var result = value.call(element, e)
  3007. onevent.call(element, e)
  3008. return result
  3009. }
  3010. if (key2 in element) element[key2] = typeof value === "function" ? callback : null
  3011. else {
  3012. var eventName = key2.slice(2)
  3013. if (vnode.events === undefined) vnode.events = {}
  3014. if (vnode.events[key2] === callback) return
  3015. if (vnode.events[key2] != null) element.removeEventListener(eventName, vnode.events[key2], false)
  3016. if (typeof value === "function") {
  3017. vnode.events[key2] = callback
  3018. element.addEventListener(eventName, vnode.events[key2], false)
  3019. }
  3020. }
  3021. }
  3022. //lifecycle
  3023. function initLifecycle(source, vnode, hooks) {
  3024. if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode)
  3025. if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode))
  3026. }
  3027. function updateLifecycle(source, vnode, hooks) {
  3028. if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
  3029. }
  3030. function shouldNotUpdate(vnode, old) {
  3031. var forceVnodeUpdate, forceComponentUpdate
  3032. if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old)
  3033. if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeupdate === "function") forceComponentUpdate = vnode._state.onbeforeupdate.call(vnode.state, vnode, old)
  3034. if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) {
  3035. vnode.dom = old.dom
  3036. vnode.domSize = old.domSize
  3037. vnode.instance = old.instance
  3038. return true
  3039. }
  3040. return false
  3041. }
  3042. function render(dom, vnodes) {
  3043. if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.")
  3044. var hooks = []
  3045. var active = $doc.activeElement
  3046. var namespace = dom.namespaceURI
  3047. // First time0 rendering into a node clears it out
  3048. if (dom.vnodes == null) dom.textContent = ""
  3049. if (!Array.isArray(vnodes)) vnodes = [vnodes]
  3050. updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace)
  3051. dom.vnodes = vnodes
  3052. for (var i = 0; i < hooks.length; i++) hooks[i]()
  3053. if ($doc.activeElement !== active) active.focus()
  3054. }
  3055. return {render: render, setEventCallback: setEventCallback}
  3056. }
  3057. function throttle(callback) {
  3058. //60fps translates to 16.6ms, round it down since setTimeout requires int
  3059. var time = 16
  3060. var last = 0, pending = null
  3061. var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
  3062. return function() {
  3063. var now = Date.now()
  3064. if (last === 0 || now - last >= time) {
  3065. last = now
  3066. callback()
  3067. }
  3068. else if (pending === null) {
  3069. pending = timeout(function() {
  3070. pending = null
  3071. callback()
  3072. last = Date.now()
  3073. }, time - (now - last))
  3074. }
  3075. }
  3076. }
  3077. var _11 = function($window) {
  3078. var renderService = coreRenderer($window)
  3079. renderService.setEventCallback(function(e) {
  3080. if (e.redraw !== false) redraw()
  3081. })
  3082. var callbacks = []
  3083. function subscribe(key1, callback) {
  3084. unsubscribe(key1)
  3085. callbacks.push(key1, throttle(callback))
  3086. }
  3087. function unsubscribe(key1) {
  3088. var index = callbacks.indexOf(key1)
  3089. if (index > -1) callbacks.splice(index, 2)
  3090. }
  3091. function redraw() {
  3092. for (var i = 1; i < callbacks.length; i += 2) {
  3093. callbacks[i]()
  3094. }
  3095. }
  3096. return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
  3097. }
  3098. var redrawService = _11(window)
  3099. requestService.setCompletionCallback(redrawService.redraw)
  3100. var _16 = function(redrawService0) {
  3101. return function(root, component) {
  3102. if (component === null) {
  3103. redrawService0.render(root, [])
  3104. redrawService0.unsubscribe(root)
  3105. return
  3106. }
  3107. if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode")
  3108. var run0 = function() {
  3109. redrawService0.render(root, Vnode(component))
  3110. }
  3111. redrawService0.subscribe(root, run0)
  3112. redrawService0.redraw()
  3113. }
  3114. }
  3115. m.mount = _16(redrawService)
  3116. var Promise = PromisePolyfill
  3117. var parseQueryString = function(string) {
  3118. if (string === "" || string == null) return {}
  3119. if (string.charAt(0) === "?") string = string.slice(1)
  3120. var entries = string.split("&"), data0 = {}, counters = {}
  3121. for (var i = 0; i < entries.length; i++) {
  3122. var entry = entries[i].split("=")
  3123. var key5 = decodeURIComponent(entry[0])
  3124. var value = entry.length === 2 ? decodeURIComponent(entry[1]) : ""
  3125. if (value === "true") value = true
  3126. else if (value === "false") value = false
  3127. var levels = key5.split(/\]\[?|\[/)
  3128. var cursor = data0
  3129. if (key5.indexOf("[") > -1) levels.pop()
  3130. for (var j = 0; j < levels.length; j++) {
  3131. var level = levels[j], nextLevel = levels[j + 1]
  3132. var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10))
  3133. var isValue = j === levels.length - 1
  3134. if (level === "") {
  3135. var key5 = levels.slice(0, j).join()
  3136. if (counters[key5] == null) counters[key5] = 0
  3137. level = counters[key5]++
  3138. }
  3139. if (cursor[level] == null) {
  3140. cursor[level] = isValue ? value : isNumber ? [] : {}
  3141. }
  3142. cursor = cursor[level]
  3143. }
  3144. }
  3145. return data0
  3146. }
  3147. var coreRouter = function($window) {
  3148. var supportsPushState = typeof $window.history.pushState === "function"
  3149. var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout
  3150. function normalize1(fragment0) {
  3151. var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
  3152. if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data
  3153. return data
  3154. }
  3155. var asyncId
  3156. function debounceAsync(callback0) {
  3157. return function() {
  3158. if (asyncId != null) return
  3159. asyncId = callAsync0(function() {
  3160. asyncId = null
  3161. callback0()
  3162. })
  3163. }
  3164. }
  3165. function parsePath(path, queryData, hashData) {
  3166. var queryIndex = path.indexOf("?")
  3167. var hashIndex = path.indexOf("#")
  3168. var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length
  3169. if (queryIndex > -1) {
  3170. var queryEnd = hashIndex > -1 ? hashIndex : path.length
  3171. var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd))
  3172. for (var key4 in queryParams) queryData[key4] = queryParams[key4]
  3173. }
  3174. if (hashIndex > -1) {
  3175. var hashParams = parseQueryString(path.slice(hashIndex + 1))
  3176. for (var key4 in hashParams) hashData[key4] = hashParams[key4]
  3177. }
  3178. return path.slice(0, pathEnd)
  3179. }
  3180. var router = {prefix: "#!"}
  3181. router.getPath = function() {
  3182. var type2 = router.prefix.charAt(0)
  3183. switch (type2) {
  3184. case "#": return normalize1("hash").slice(router.prefix.length)
  3185. case "?": return normalize1("search").slice(router.prefix.length) + normalize1("hash")
  3186. default: return normalize1("pathname").slice(router.prefix.length) + normalize1("search") + normalize1("hash")
  3187. }
  3188. }
  3189. router.setPath = function(path, data, options) {
  3190. var queryData = {}, hashData = {}
  3191. path = parsePath(path, queryData, hashData)
  3192. if (data != null) {
  3193. for (var key4 in data) queryData[key4] = data[key4]
  3194. path = path.replace(/:([^\/]+)/g, function(match2, token) {
  3195. delete queryData[token]
  3196. return data[token]
  3197. })
  3198. }
  3199. var query = buildQueryString(queryData)
  3200. if (query) path += "?" + query
  3201. var hash = buildQueryString(hashData)
  3202. if (hash) path += "#" + hash
  3203. if (supportsPushState) {
  3204. var state = options ? options.state : null
  3205. var title = options ? options.title : null
  3206. $window.onpopstate()
  3207. if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path)
  3208. else $window.history.pushState(state, title, router.prefix + path)
  3209. }
  3210. else $window.location.href = router.prefix + path
  3211. }
  3212. router.defineRoutes = function(routes, resolve, reject) {
  3213. function resolveRoute() {
  3214. var path = router.getPath()
  3215. var params = {}
  3216. var pathname = parsePath(path, params, params)
  3217. var state = $window.history.state
  3218. if (state != null) {
  3219. for (var k in state) params[k] = state[k]
  3220. }
  3221. for (var route0 in routes) {
  3222. var matcher = new RegExp("^" + route0.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
  3223. if (matcher.test(pathname)) {
  3224. pathname.replace(matcher, function() {
  3225. var keys = route0.match(/:[^\/]+/g) || []
  3226. var values = [].slice.call(arguments, 1, -2)
  3227. for (var i = 0; i < keys.length; i++) {
  3228. params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
  3229. }
  3230. resolve(routes[route0], params, path, route0)
  3231. })
  3232. return
  3233. }
  3234. }
  3235. reject(path, params)
  3236. }
  3237. if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
  3238. else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
  3239. resolveRoute()
  3240. }
  3241. return router
  3242. }
  3243. var _20 = function($window, redrawService0) {
  3244. var routeService = coreRouter($window)
  3245. var identity = function(v) {return v}
  3246. var render1, component, attrs3, currentPath, lastUpdate
  3247. var route = function(root, defaultRoute, routes) {
  3248. if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
  3249. var run1 = function() {
  3250. if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3)))
  3251. }
  3252. var bail = function(path) {
  3253. if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
  3254. else throw new Error("Could not resolve default route " + defaultRoute)
  3255. }
  3256. routeService.defineRoutes(routes, function(payload, params, path) {
  3257. var update = lastUpdate = function(routeResolver, comp) {
  3258. if (update !== lastUpdate) return
  3259. component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
  3260. attrs3 = params, currentPath = path, lastUpdate = null
  3261. render1 = (routeResolver.render || identity).bind(routeResolver)
  3262. run1()
  3263. }
  3264. if (payload.view || typeof payload === "function") update({}, payload)
  3265. else {
  3266. if (payload.onmatch) {
  3267. Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
  3268. update(payload, resolved)
  3269. }, bail)
  3270. }
  3271. else update(payload, "div")
  3272. }
  3273. }, bail)
  3274. redrawService0.subscribe(root, run1)
  3275. }
  3276. route.set = function(path, data, options) {
  3277. if (lastUpdate != null) options = {replace: true}
  3278. lastUpdate = null
  3279. routeService.setPath(path, data, options)
  3280. }
  3281. route.get = function() {return currentPath}
  3282. route.prefix = function(prefix0) {routeService.prefix = prefix0}
  3283. route.link = function(vnode1) {
  3284. vnode1.dom.setAttribute("href", routeService.prefix + vnode1.attrs.href)
  3285. vnode1.dom.onclick = function(e) {
  3286. if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
  3287. e.preventDefault()
  3288. e.redraw = false
  3289. var href = this.getAttribute("href")
  3290. if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length)
  3291. route.set(href, undefined, undefined)
  3292. }
  3293. }
  3294. route.param = function(key3) {
  3295. if(typeof attrs3 !== "undefined" && typeof key3 !== "undefined") return attrs3[key3]
  3296. return attrs3
  3297. }
  3298. return route
  3299. }
  3300. m.route = _20(window, redrawService)
  3301. m.withAttr = function(attrName, callback1, context) {
  3302. return function(e) {
  3303. callback1.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
  3304. }
  3305. }
  3306. var _28 = coreRenderer(window)
  3307. m.render = _28.render
  3308. m.redraw = redrawService.redraw
  3309. m.request = requestService.request
  3310. m.jsonp = requestService.jsonp
  3311. m.parseQueryString = parseQueryString
  3312. m.buildQueryString = buildQueryString
  3313. m.version = "1.1.1"
  3314. m.vnode = Vnode
  3315. if (typeof module !== "undefined") module["exports"] = m
  3316. else window.m = m
  3317. }());