Licitator 1.0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

786 lines
33 KiB

5 years ago
  1. /**
  2. * @license
  3. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  4. * This code may only be used under the BSD style license found at
  5. * http://polymer.github.io/LICENSE.txt
  6. * The complete set of authors may be found at
  7. * http://polymer.github.io/AUTHORS.txt
  8. * The complete set of contributors may be found at
  9. * http://polymer.github.io/CONTRIBUTORS.txt
  10. * Code distributed by Google as part of the polymer project is also
  11. * subject to an additional IP rights grant found at
  12. * http://polymer.github.io/PATENTS.txt
  13. */
  14. /**
  15. * @license
  16. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  17. * This code may only be used under the BSD style license found at
  18. * http://polymer.github.io/LICENSE.txt
  19. * The complete set of authors may be found at
  20. * http://polymer.github.io/AUTHORS.txt
  21. * The complete set of contributors may be found at
  22. * http://polymer.github.io/CONTRIBUTORS.txt
  23. * Code distributed by Google as part of the polymer project is also
  24. * subject to an additional IP rights grant found at
  25. * http://polymer.github.io/PATENTS.txt
  26. */
  27. /**
  28. * An expression marker with embedded unique key to avoid collision with
  29. * possible text in templates.
  30. */
  31. const marker = `{{lit-${String(Math.random()).slice(2)}}}`;
  32. /**
  33. * Used to clone existing node instead of each time creating new one which is
  34. * slower
  35. */
  36. const markerNode = document.createComment('');
  37. /**
  38. * Used to clone existing node instead of each time creating new one which is
  39. * slower
  40. */
  41. const emptyTemplateNode = document.createElement('template');
  42. /**
  43. * Used to clone text node instead of each time creating new one which is slower
  44. */
  45. const emptyTextNode = document.createTextNode('');
  46. // Detect event listener options support. If the `capture` property is read
  47. // from the options object, then options are supported. If not, then the third
  48. // argument to add/removeEventListener is interpreted as the boolean capture
  49. // value so we should only pass the `capture` property.
  50. let eventOptionsSupported = false;
  51. // Wrap into an IIFE because MS Edge <= v41 does not support having try/catch
  52. // blocks right into the body of a module
  53. (() => {
  54. try {
  55. const options = {
  56. get capture() {
  57. eventOptionsSupported = true;
  58. return false;
  59. }
  60. };
  61. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  62. window.addEventListener('test', options, options);
  63. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  64. window.removeEventListener('test', options, options);
  65. }
  66. catch (_e) {
  67. // noop
  68. }
  69. })();
  70. /**
  71. * @license
  72. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  73. * This code may only be used under the BSD style license found at
  74. * http://polymer.github.io/LICENSE.txt
  75. * The complete set of authors may be found at
  76. * http://polymer.github.io/AUTHORS.txt
  77. * The complete set of contributors may be found at
  78. * http://polymer.github.io/CONTRIBUTORS.txt
  79. * Code distributed by Google as part of the polymer project is also
  80. * subject to an additional IP rights grant found at
  81. * http://polymer.github.io/PATENTS.txt
  82. */
  83. // IMPORTANT: do not change the property name or the assignment expression.
  84. // This line will be used in regexes to search for lit-html usage.
  85. // TODO(justinfagnani): inject version number at build time
  86. const isBrowser = typeof window !== 'undefined';
  87. if (isBrowser) {
  88. // If we run in the browser set version
  89. (window['litHtmlVersions'] || (window['litHtmlVersions'] = [])).push('1.1.7');
  90. }
  91. /**
  92. * Used to clone existing node instead of each time creating new one which is
  93. * slower
  94. */
  95. const emptyTemplateNode$1 = document.createElement('template');
  96. class Action {
  97. constructor() {
  98. this.isAction = true;
  99. }
  100. }
  101. Action.prototype.isAction = true;
  102. const defaultOptions = {
  103. element: document.createTextNode(''),
  104. axis: 'xy',
  105. threshold: 10,
  106. onDown(data) { },
  107. onMove(data) { },
  108. onUp(data) { },
  109. onWheel(data) { }
  110. };
  111. /**
  112. * Selection plugin
  113. *
  114. * @copyright Rafal Pospiech <https://neuronet.io>
  115. * @author Rafal Pospiech <neuronet.io@gmail.com>
  116. * @package gantt-schedule-timeline-calendar
  117. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  118. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  119. */
  120. function Selection(options = {}) {
  121. let vido, state, api, schedule;
  122. const pluginPath = 'config.plugin.selection';
  123. const rectClassName = 'gantt-schedule-timeline-caledar__plugin-selection-rect';
  124. const rect = document.createElement('div');
  125. rect.classList.add(rectClassName);
  126. rect.style.visibility = 'hidden';
  127. rect.style.left = '0px';
  128. rect.style.top = '0px';
  129. rect.style.width = '0px';
  130. rect.style.height = '0px';
  131. rect.style.background = 'rgba(0, 119, 192, 0.2)';
  132. rect.style.border = '2px dashed rgba(0, 119, 192, 0.75)';
  133. rect.style.position = 'absolute';
  134. rect.style['user-select'] = 'none';
  135. rect.style['pointer-events'] = 'none';
  136. const defaultOptions = {
  137. grid: false,
  138. items: true,
  139. rows: false,
  140. horizontal: true,
  141. vertical: true,
  142. rectStyle: {},
  143. selecting() { },
  144. deselecting() { },
  145. selected() { },
  146. deselected() { },
  147. canSelect(type, currently, all) {
  148. return currently;
  149. },
  150. canDeselect(type, currently, all) {
  151. return [];
  152. },
  153. getApi() { }
  154. };
  155. options = Object.assign(Object.assign({}, defaultOptions), options);
  156. for (const styleProp in options.rectStyle) {
  157. rect.style[styleProp] = options.rectStyle[styleProp];
  158. }
  159. const selecting = {
  160. fromX: -1,
  161. fromY: -1,
  162. toX: -1,
  163. toY: -1,
  164. startX: -1,
  165. startY: -1,
  166. startCell: false,
  167. selecting: false
  168. };
  169. const selectionTypesIdGetters = {
  170. 'chart-timeline-grid-row': props => props.row.id,
  171. 'chart-timeline-grid-row-block': props => props.id,
  172. 'chart-timeline-items-row': props => props.row.id,
  173. 'chart-timeline-items-row-item': props => props.item.id
  174. };
  175. function getEmptyContainer() {
  176. return {
  177. 'chart-timeline-grid-rows': [],
  178. 'chart-timeline-grid-row-blocks': [],
  179. 'chart-timeline-items-rows': [],
  180. 'chart-timeline-items-row-items': []
  181. };
  182. }
  183. function markSelecting(nowSelecting, addToPrevious = false) {
  184. if (addToPrevious) {
  185. state.update(`${pluginPath}.selecting`, selecting => {
  186. for (const name in selecting) {
  187. nowSelecting[name].forEach(id => {
  188. if (!selecting[name].includes()) {
  189. selecting[name].push(id);
  190. }
  191. });
  192. }
  193. return selecting;
  194. });
  195. }
  196. else {
  197. state.update(`${pluginPath}.selecting`, nowSelecting);
  198. }
  199. state.update('config.chart.items', function updateItems(items) {
  200. const now = nowSelecting['chart-timeline-items-row-items'];
  201. for (const itemId in items) {
  202. const item = items[itemId];
  203. if (now.includes(item.id)) {
  204. item.selecting = true;
  205. }
  206. else {
  207. item.selecting = false;
  208. }
  209. }
  210. return items;
  211. }, { only: ['selecting'] });
  212. state.update('_internal.chart.grid.rowsWithBlocks', function updateRowsWithBlocks(rowsWithBlocks) {
  213. const nowBlocks = nowSelecting['chart-timeline-grid-row-blocks'];
  214. const nowRows = nowSelecting['chart-timeline-grid-rows'];
  215. if (rowsWithBlocks)
  216. for (const row of rowsWithBlocks) {
  217. if (nowRows.includes(row.id)) {
  218. row.selecting = true;
  219. }
  220. else {
  221. row.selecting = false;
  222. }
  223. for (const block of row.blocks) {
  224. if (nowBlocks.includes(block.id)) {
  225. block.selecting = true;
  226. }
  227. else {
  228. block.selecting = false;
  229. }
  230. }
  231. }
  232. return rowsWithBlocks;
  233. });
  234. }
  235. /**
  236. * Clear selection
  237. * @param {boolean} clear
  238. */
  239. function clearSelection(clear = false, onlySelecting = false) {
  240. let selectingState;
  241. if (onlySelecting) {
  242. state.update(pluginPath, currently => {
  243. selectingState = {
  244. selecting: {
  245. 'chart-timeline-grid-rows': [],
  246. 'chart-timeline-grid-row-blocks': [],
  247. 'chart-timeline-items-rows': [],
  248. 'chart-timeline-items-row-items': []
  249. },
  250. selected: currently.selected
  251. };
  252. return selectingState;
  253. });
  254. }
  255. else {
  256. state.update(pluginPath, currently => {
  257. selectingState = {
  258. selecting: {
  259. 'chart-timeline-grid-rows': [],
  260. 'chart-timeline-grid-row-blocks': [],
  261. 'chart-timeline-items-rows': [],
  262. 'chart-timeline-items-row-items': []
  263. },
  264. selected: {
  265. 'chart-timeline-grid-rows': clear
  266. ? []
  267. : options.canDeselect('chart-timeline-grid-rows', currently.selected['chart-timeline-grid-rows'], currently),
  268. 'chart-timeline-grid-row-blocks': clear
  269. ? []
  270. : options.canDeselect('chart-timeline-grid-row-blocks', currently.selected['chart-timeline-grid-row-blocks'], currently),
  271. 'chart-timeline-items-rows': clear
  272. ? []
  273. : options.canDeselect('chart-timeline-items-rows', currently.selected['chart-timeline-items-rows'], currently),
  274. 'chart-timeline-items-row-items': clear
  275. ? []
  276. : options.canDeselect('chart-timeline-items-row-items', currently.selected['chart-timeline-items-row-items'], currently)
  277. }
  278. };
  279. return selectingState;
  280. });
  281. state.update('_internal.chart.grid.rowsWithBlocks', function clearRowsWithBlocks(rowsWithBlocks) {
  282. if (rowsWithBlocks)
  283. for (const row of rowsWithBlocks) {
  284. for (const block of row.blocks) {
  285. block.selected = selectingState.selected['chart-timeline-grid-row-blocks'].includes(block.id);
  286. block.selecting = false;
  287. }
  288. }
  289. return rowsWithBlocks;
  290. });
  291. state.update('config.chart.items', items => {
  292. if (items) {
  293. for (const itemId in items) {
  294. const item = items[itemId];
  295. item.selected = selectingState.selected['chart-timeline-items-row-items'].includes(itemId);
  296. item.selecting = false;
  297. }
  298. }
  299. return items;
  300. });
  301. }
  302. }
  303. let previousSelect;
  304. function markSelected(addToPrevious = false) {
  305. selecting.selecting = false;
  306. rect.style.visibility = 'hidden';
  307. const currentSelect = cloneSelection(state.get(pluginPath));
  308. const select = {};
  309. if (addToPrevious) {
  310. state.update(pluginPath, value => {
  311. const selected = Object.assign({}, value.selecting);
  312. for (const name in value.selected) {
  313. for (const id of selected[name]) {
  314. if (!value.selected[name].includes(id)) {
  315. value.selected[name].push(id);
  316. }
  317. }
  318. }
  319. select.selected = Object.assign({}, value.selected);
  320. select.selecting = getEmptyContainer();
  321. return select;
  322. });
  323. }
  324. else {
  325. state.update(pluginPath, value => {
  326. select.selected = Object.assign({}, value.selecting);
  327. select.selecting = getEmptyContainer();
  328. return select;
  329. });
  330. }
  331. const elements = state.get('_internal.elements');
  332. for (const type in selectionTypesIdGetters) {
  333. if (elements[type + 's'])
  334. for (const element of elements[type + 's']) {
  335. if (currentSelect.selecting[type + 's'].includes(element.vido.id)) {
  336. options.deselecting(element.vido, type);
  337. }
  338. }
  339. }
  340. state.update('config.chart.items', function updateItems(items) {
  341. for (const itemId in items) {
  342. const item = items[itemId];
  343. if (currentSelect.selecting['chart-timeline-items-row-items'].includes(item.id)) {
  344. item.selected = true;
  345. if (typeof item.selected === 'undefined' || !item.selected) {
  346. options.selected(item, 'chart-timeline-items-row-item');
  347. }
  348. }
  349. else if (addToPrevious && previousSelect.selected['chart-timeline-items-row-items'].includes(item.id)) {
  350. item.selected = true;
  351. }
  352. else {
  353. item.selected = false;
  354. if (currentSelect.selected['chart-timeline-items-row-items'].includes(item.id)) {
  355. options.deselected(item, 'chart-timeline-items-row-item');
  356. }
  357. }
  358. }
  359. return items;
  360. });
  361. state.update('_internal.chart.grid.rowsWithBlocks', function updateRowsWithBlocks(rowsWithBlocks) {
  362. if (rowsWithBlocks)
  363. for (const row of rowsWithBlocks) {
  364. for (const block of row.blocks) {
  365. if (currentSelect.selecting['chart-timeline-grid-row-blocks'].includes(block.id)) {
  366. if (typeof block.selected === 'undefined' || !block.selected) {
  367. options.selected(block, 'chart-timeline-grid-row-block');
  368. }
  369. block.selected = true;
  370. }
  371. else if (addToPrevious && previousSelect.selected['chart-timeline-grid-row-blocks'].includes(block.id)) {
  372. block.selected = true;
  373. }
  374. else {
  375. if (currentSelect.selected['chart-timeline-grid-row-blocks'].includes(block.id)) {
  376. options.deselected(block, 'chart-timeline-grid-row-block');
  377. }
  378. block.selected = false;
  379. }
  380. }
  381. }
  382. return rowsWithBlocks;
  383. });
  384. }
  385. /**
  386. * Clone current selection state
  387. * @param {object} currentSelect
  388. * @returns {object} currentSelect cloned
  389. */
  390. function cloneSelection(currentSelect) {
  391. const result = {};
  392. result.selecting = Object.assign({}, currentSelect.selecting);
  393. result.selecting['chart-timeline-grid-rows'] = currentSelect.selecting['chart-timeline-grid-rows'].slice();
  394. result.selecting['chart-timeline-grid-row-blocks'] = currentSelect.selecting['chart-timeline-grid-row-blocks'].slice();
  395. result.selecting['chart-timeline-items-rows'] = currentSelect.selecting['chart-timeline-items-rows'].slice();
  396. result.selecting['chart-timeline-items-row-items'] = currentSelect.selecting['chart-timeline-items-row-items'].slice();
  397. result.selected = Object.assign({}, currentSelect.selected);
  398. result.selected['chart-timeline-grid-rows'] = currentSelect.selected['chart-timeline-grid-rows'].slice();
  399. result.selected['chart-timeline-grid-row-blocks'] = currentSelect.selected['chart-timeline-grid-row-blocks'].slice();
  400. result.selected['chart-timeline-items-rows'] = currentSelect.selected['chart-timeline-items-rows'].slice();
  401. result.selected['chart-timeline-items-row-items'] = currentSelect.selected['chart-timeline-items-row-items'].slice();
  402. return result;
  403. }
  404. /**
  405. * Selection action class
  406. */
  407. class SelectionAction extends Action {
  408. /**
  409. * Selection action constructor
  410. * @param {Element} element
  411. * @param {object|any} data
  412. */
  413. constructor(element, data) {
  414. super();
  415. const api = {};
  416. api.clearSelection = clearSelection;
  417. this.unsub = data.state.subscribeAll(['_internal.elements.chart-timeline', '_internal.chart.dimensions.width'], bulk => {
  418. const chartTimeline = state.get('_internal.elements.chart-timeline');
  419. if (chartTimeline === undefined)
  420. return;
  421. this.chartTimeline = chartTimeline;
  422. if (!this.chartTimeline.querySelector('.' + rectClassName)) {
  423. this.chartTimeline.insertAdjacentElement('beforeend', rect);
  424. }
  425. const bounding = this.chartTimeline.getBoundingClientRect();
  426. this.left = bounding.left;
  427. this.top = bounding.top;
  428. });
  429. /**
  430. * Save and swap coordinates if needed
  431. * @param {MouseEvent} ev
  432. */
  433. const saveAndSwapIfNeeded = (ev) => {
  434. const normalized = vido.api.normalizePointerEvent(ev);
  435. const currentX = normalized.x - this.left;
  436. const currentY = normalized.y - this.top;
  437. if (currentX <= selecting.startX) {
  438. selecting.fromX = currentX;
  439. selecting.toX = selecting.startX;
  440. }
  441. else {
  442. selecting.fromX = selecting.startX;
  443. selecting.toX = currentX;
  444. }
  445. if (currentY <= selecting.startY) {
  446. selecting.fromY = currentY;
  447. selecting.toY = selecting.startY;
  448. }
  449. else {
  450. selecting.fromY = selecting.startY;
  451. selecting.toY = currentY;
  452. }
  453. };
  454. /**
  455. * Is rectangle inside other rectangle ?
  456. * @param {DOMRect} boundingRect
  457. * @param {DOMRect} rectBoundingRect
  458. * @returns {boolean}
  459. */
  460. const isInside = (boundingRect, rectBoundingRect) => {
  461. let horizontal = false;
  462. let vertical = false;
  463. if ((boundingRect.left > rectBoundingRect.left && boundingRect.left < rectBoundingRect.right) ||
  464. (boundingRect.right > rectBoundingRect.left && boundingRect.right < rectBoundingRect.right) ||
  465. (boundingRect.left <= rectBoundingRect.left && boundingRect.right >= rectBoundingRect.right)) {
  466. horizontal = true;
  467. }
  468. if ((boundingRect.top > rectBoundingRect.top && boundingRect.top < rectBoundingRect.bottom) ||
  469. (boundingRect.bottom > rectBoundingRect.top && boundingRect.bottom < rectBoundingRect.bottom) ||
  470. (boundingRect.top <= rectBoundingRect.top && boundingRect.bottom >= rectBoundingRect.bottom)) {
  471. vertical = true;
  472. }
  473. return horizontal && vertical;
  474. };
  475. /**
  476. * Get selecting elements
  477. * @param {DOMRect} rectBoundingRect
  478. * @param {Element[]} elements
  479. * @param {string} type
  480. * @returns {string[]}
  481. */
  482. function getSelecting(rectBoundingRect, elements, type, getId) {
  483. const selectingResult = [];
  484. const currentlySelectingData = [];
  485. const all = elements[type + 's'];
  486. if (!all)
  487. return [];
  488. const currentAll = state.get(pluginPath);
  489. const currentSelecting = currentAll.selecting[type + 's'];
  490. for (const element of all) {
  491. const boundingRect = element.getBoundingClientRect();
  492. if (isInside(boundingRect, rectBoundingRect)) {
  493. currentlySelectingData.push(element.vido);
  494. const canSelect = options.canSelect(type, currentlySelectingData, currentAll);
  495. if (canSelect.includes(element.vido)) {
  496. if (!currentSelecting.includes(getId(element.vido))) {
  497. options.selecting(element.vido, type);
  498. }
  499. selectingResult.push(getId(element.vido));
  500. }
  501. else {
  502. currentlySelectingData.unshift();
  503. }
  504. }
  505. else {
  506. if (currentSelecting.includes(getId(element.vido))) {
  507. options.deselecting(element.vido, type);
  508. }
  509. }
  510. }
  511. return selectingResult;
  512. }
  513. function trackSelection(ev, virtually = false) {
  514. const movement = state.get('config.plugin.ItemMovement.movement');
  515. const moving = movement && (movement.moving || movement.waiting);
  516. if (!selecting.selecting || moving) {
  517. if (moving) {
  518. selecting.selecting = false;
  519. }
  520. return;
  521. }
  522. clearSelection(false, true);
  523. saveAndSwapIfNeeded(ev);
  524. rect.style.left = selecting.fromX + 'px';
  525. rect.style.top = selecting.fromY + 'px';
  526. rect.style.width = selecting.toX - selecting.fromX + 'px';
  527. rect.style.height = selecting.toY - selecting.fromY + 'px';
  528. if (!virtually) {
  529. rect.style.visibility = 'visible';
  530. }
  531. const rectBoundingRect = rect.getBoundingClientRect();
  532. const elements = state.get('_internal.elements');
  533. const nowSelecting = {};
  534. for (const type in selectionTypesIdGetters) {
  535. nowSelecting[type + 's'] = getSelecting(rectBoundingRect, elements, type, selectionTypesIdGetters[type]);
  536. }
  537. markSelecting(nowSelecting, ev.ctrlKey);
  538. }
  539. /**
  540. * End select
  541. * @param {Event} ev
  542. */
  543. const endSelect = ev => {
  544. if (selecting.selecting) {
  545. ev.stopPropagation();
  546. ev.preventDefault();
  547. const normalized = vido.api.normalizePointerEvent(ev);
  548. if (selecting.startX === normalized.x - this.left && selecting.startY === normalized.y - this.top) {
  549. selecting.selecting = false;
  550. rect.style.visibility = 'hidden';
  551. return;
  552. }
  553. }
  554. else {
  555. const itemClass = '.gantt-schedule-timeline-calendar__chart-timeline-items-row-item';
  556. const isItem = !!ev.target.closest(itemClass);
  557. if (!isItem) {
  558. if (!ev.ctrlKey)
  559. clearSelection();
  560. }
  561. else {
  562. markSelected(ev.ctrlKey);
  563. }
  564. return;
  565. }
  566. markSelected(ev.ctrlKey);
  567. };
  568. /**
  569. * Mouse down event handler
  570. * @param {MouseEvent} ev
  571. */
  572. this.mouseDown = ev => {
  573. const movement = state.get('config.plugin.ItemMovement.movement');
  574. const moving = movement && movement.moving;
  575. if ((ev.type === 'mousedown' && ev.button !== 0) || moving) {
  576. return;
  577. }
  578. const normalized = vido.api.normalizePointerEvent(ev);
  579. selecting.selecting = true;
  580. selecting.fromX = normalized.x - this.left;
  581. selecting.fromY = normalized.y - this.top;
  582. selecting.startX = selecting.fromX;
  583. selecting.startY = selecting.fromY;
  584. previousSelect = cloneSelection(state.get(pluginPath));
  585. const itemClass = '.gantt-schedule-timeline-calendar__chart-timeline-items-row-item';
  586. const isItem = !!ev.target.closest(itemClass);
  587. if (!isItem) {
  588. if (!ev.ctrlKey)
  589. clearSelection();
  590. }
  591. };
  592. /**
  593. * Mouse move event handler
  594. * @param {MouseEvent} ev
  595. */
  596. this.mouseMove = ev => {
  597. trackSelection(ev);
  598. };
  599. /**
  600. * Mouse up event handler
  601. * @param {MouseEvent} ev
  602. */
  603. this.mouseUp = ev => {
  604. if (selecting.selecting) {
  605. endSelect(ev);
  606. }
  607. };
  608. element.addEventListener('mousedown', this.mouseDown);
  609. element.addEventListener('touchstart', this.mouseDown);
  610. document.addEventListener('mousemove', this.mouseMove);
  611. document.addEventListener('touchmove', this.mouseMove);
  612. document.addEventListener('mouseup', this.mouseUp);
  613. document.addEventListener('touchend', this.mouseUp);
  614. options.getApi(api);
  615. }
  616. destroy(element) {
  617. document.removeEventListener('mouseup', this.mouseUp);
  618. document.removeEventListener('touchend', this.mouseUp);
  619. document.removeEventListener('mousemove', this.mouseMove);
  620. document.removeEventListener('touchmove', this.mouseMove);
  621. element.removeEventListener('mousedown', this.mouseDown);
  622. element.removeEventListener('touchstart', this.mouseDown);
  623. this.unsub();
  624. }
  625. }
  626. /**
  627. * Update selection
  628. * @param {any} data
  629. * @param {HTMLElement} element
  630. * @param {boolean} selecting
  631. * @param {boolean} selected
  632. * @param {string} classNameSelecting
  633. * @param {string} classNameSelected
  634. */
  635. function updateSelection(element, selecting, selected, classNameSelecting, classNameSelected) {
  636. if (selecting && !element.classList.contains(classNameSelecting)) {
  637. element.classList.add(classNameSelecting);
  638. }
  639. else if (!selecting && element.classList.contains(classNameSelecting)) {
  640. element.classList.remove(classNameSelecting);
  641. }
  642. if (selected && !element.classList.contains(classNameSelected)) {
  643. element.classList.add(classNameSelected);
  644. }
  645. else if (!selected && element.classList.contains(classNameSelected)) {
  646. element.classList.remove(classNameSelected);
  647. }
  648. }
  649. /**
  650. * Grid row block action
  651. * @param {HTMLElement} element
  652. * @param {object} data
  653. * @returns {object} with update and destroy functions
  654. */
  655. class GridBlockAction extends Action {
  656. constructor(element, data) {
  657. super();
  658. this.classNameSelecting = api.getClass('chart-timeline-grid-row-block') + '--selecting';
  659. this.classNameSelected = api.getClass('chart-timeline-grid-row-block') + '--selected';
  660. updateSelection(element, data.selecting, data.selected, this.classNameSelecting, this.classNameSelected);
  661. }
  662. update(element, data) {
  663. updateSelection(element, data.selecting, data.selected, this.classNameSelecting, this.classNameSelected);
  664. }
  665. destroy(element, changedData) {
  666. element.classList.remove(this.classNameSelecting);
  667. element.classList.remove(this.classNameSelected);
  668. }
  669. }
  670. /**
  671. * Item action
  672. * @param {Element} element
  673. * @param {object} data
  674. * @returns {object} with update and destroy functions
  675. */
  676. class ItemAction extends Action {
  677. constructor(element, data) {
  678. super();
  679. this.data = data;
  680. this.element = element;
  681. this.classNameSelecting = api.getClass('chart-timeline-items-row-item') + '--selecting';
  682. this.classNameSelected = api.getClass('chart-timeline-items-row-item') + '--selected';
  683. this.data = data;
  684. this.element = element;
  685. this.onPointerDown = this.onPointerDown.bind(this);
  686. element.addEventListener('mousedown', this.onPointerDown);
  687. element.addEventListener('touchstart', this.onPointerDown);
  688. updateSelection(element, data.item.selecting, data.item.selected, this.classNameSelecting, this.classNameSelected);
  689. }
  690. onPointerDown(ev) {
  691. previousSelect = cloneSelection(state.get(pluginPath));
  692. selecting.selecting = true;
  693. this.data.item.selected = true;
  694. const container = getEmptyContainer();
  695. container['chart-timeline-items-row-items'].push(this.data.item.id);
  696. markSelecting(container);
  697. markSelected(ev.ctrlKey);
  698. updateSelection(this.element, this.data.item.selecting, this.data.item.selected, this.classNameSelecting, this.classNameSelected);
  699. }
  700. update(element, data) {
  701. updateSelection(element, data.item.selecting, data.item.selected, this.classNameSelecting, this.classNameSelected);
  702. this.data = data;
  703. }
  704. destroy(element, data) {
  705. element.classList.remove(this.classNameSelecting);
  706. element.classList.remove(this.classNameSelected);
  707. element.removeEventListener('mousedown', this.onPointerDown);
  708. element.removeEventListener('touchstart', this.onPointerDown);
  709. }
  710. }
  711. /**
  712. * On block create handler
  713. * @param {object} block
  714. * @returns {object} block
  715. */
  716. function onBlockCreate(block) {
  717. const selectedBlocks = state.get('config.plugin.selection.selected.chart-timeline-grid-row-blocks');
  718. for (const selectedBlock of selectedBlocks) {
  719. if (selectedBlock === block.id) {
  720. block.selected = true;
  721. return block;
  722. }
  723. }
  724. block.selected = false;
  725. block.selecting = false;
  726. return block;
  727. }
  728. return function initialize(mainVido) {
  729. vido = mainVido;
  730. state = vido.state;
  731. api = vido.api;
  732. schedule = vido.schedule;
  733. if (typeof state.get(pluginPath) === 'undefined') {
  734. state.update(pluginPath, {
  735. selecting: {
  736. 'chart-timeline-grid-rows': [],
  737. 'chart-timeline-grid-row-blocks': [],
  738. 'chart-timeline-items-rows': [],
  739. 'chart-timeline-items-row-items': []
  740. },
  741. selected: {
  742. 'chart-timeline-grid-rows': [],
  743. 'chart-timeline-grid-row-blocks': [],
  744. 'chart-timeline-items-rows': [],
  745. 'chart-timeline-items-row-items': []
  746. }
  747. });
  748. }
  749. state.update('config.chart.items', items => {
  750. if (items)
  751. for (const itemId in items) {
  752. const item = items[itemId];
  753. if (typeof item.selecting === 'undefined') {
  754. item.selecting = false;
  755. }
  756. if (typeof item.selected === 'undefined') {
  757. item.selected = false;
  758. }
  759. }
  760. return items;
  761. });
  762. state.update('config.actions.chart-timeline', actions => {
  763. actions.push(SelectionAction);
  764. return actions;
  765. });
  766. state.update('config.actions.chart-timeline-grid-row-block', actions => {
  767. actions.push(GridBlockAction);
  768. return actions;
  769. });
  770. state.update('config.actions.chart-timeline-items-row-item', actions => {
  771. actions.push(ItemAction);
  772. return actions;
  773. });
  774. state.update('config.chart.grid.block.onCreate', onCreate => {
  775. onCreate.push(onBlockCreate);
  776. return onCreate;
  777. });
  778. };
  779. }
  780. export default Selection;