| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 | const subManager = new SubsManager();const { calculateIndex, enableClickOnTouch } = Utils;BlazeComponent.extendComponent({  onCreated() {    this.isBoardReady = new ReactiveVar(false);    // The pattern we use to manually handle data loading is described here:    // https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager    // XXX The boardId should be readed from some sort the component "props",    // unfortunatly, Blaze doesn't have this notion.    this.autorun(() => {      const currentBoardId = Session.get('currentBoard');      if (!currentBoardId)        return;      const handle = subManager.subscribe('board', currentBoardId);      Tracker.nonreactive(() => {        Tracker.autorun(() => {          this.isBoardReady.set(handle.ready());        });      });    });  },  onlyShowCurrentCard() {    return Utils.isMiniScreen() && Session.get('currentCard');  },}).register('board');BlazeComponent.extendComponent({  onCreated() {    this.showOverlay = new ReactiveVar(false);    this.draggingActive = new ReactiveVar(false);    this._isDragging = false;    // Used to set the overlay    this.mouseHasEnterCardDetails = false;  },  onRendered() {    const boardComponent = this;    const $swimlanesDom = boardComponent.$('.js-swimlanes');    $swimlanesDom.sortable({      tolerance: 'pointer',      appendTo: '.board-canvas',      helper: 'clone',      handle: '.js-swimlane-header',      items: '.js-swimlane:not(.placeholder)',      placeholder: 'swimlane placeholder',      distance: 7,      start(evt, ui) {        ui.placeholder.height(ui.helper.height());        EscapeActions.executeUpTo('popup-close');        boardComponent.setIsDragging(true);      },      stop(evt, ui) {        // To attribute the new index number, we need to get the DOM element        // of the previous and the following card -- if any.        const prevSwimlaneDom = ui.item.prev('.js-swimlane').get(0);        const nextSwimlaneDom = ui.item.next('.js-swimlane').get(0);        const sortIndex = calculateIndex(prevSwimlaneDom, nextSwimlaneDom, 1);        $swimlanesDom.sortable('cancel');        const swimlaneDomElement = ui.item.get(0);        const swimlane = Blaze.getData(swimlaneDomElement);        Swimlanes.update(swimlane._id, {          $set: {            sort: sortIndex.base,          },        });        boardComponent.setIsDragging(false);      },    });    // ugly touch event hotfix    enableClickOnTouch('.js-swimlane:not(.placeholder)');    function userIsMember() {      return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();    }    // If there is no data in the board (ie, no lists) we autofocus the list    // creation form by clicking on the corresponding element.    const currentBoard = Boards.findOne(Session.get('currentBoard'));    if (userIsMember() && currentBoard.lists().count() === 0) {      boardComponent.openNewListForm();    }  },  isViewSwimlanes() {    const currentUser = Meteor.user();    if (!currentUser) return false;    return (currentUser.profile.boardView === 'board-view-swimlanes');  },  isViewLists() {    const currentUser = Meteor.user();    if (!currentUser) return true;    return (currentUser.profile.boardView === 'board-view-lists');  },  isViewCalendar() {    const currentUser = Meteor.user();    if (!currentUser) return true;    return (currentUser.profile.boardView === 'board-view-cal');  },  openNewListForm() {    if (this.isViewSwimlanes()) {      this.childComponents('swimlane')[0]        .childComponents('addListAndSwimlaneForm')[0].open();    } else if (this.isViewLists()) {      this.childComponents('listsGroup')[0]        .childComponents('addListForm')[0].open();    }  },  events() {    return [{      // XXX The board-overlay div should probably be moved to the parent      // component.      'mouseenter .board-overlay'() {        if (this.mouseHasEnterCardDetails) {          this.showOverlay.set(false);        }      },      'mouseup'() {        if (this._isDragging) {          this._isDragging = false;        }      },    }];  },  // XXX Flow components allow us to avoid creating these two setter methods by  // exposing a public API to modify the component state. We need to investigate  // best practices here.  setIsDragging(bool) {    this.draggingActive.set(bool);  },  scrollLeft(position = 0) {    const swimlanes = this.$('.js-swimlanes');    swimlanes && swimlanes.animate({      scrollLeft: position,    });  },}).register('boardBody');BlazeComponent.extendComponent({  onRendered() {    this.autorun(function(){      $('#calendar-view').fullCalendar('refetchEvents');    });  },  calendarOptions() {    return {      id: 'calendar-view',      defaultView: 'agendaDay',      editable: true,      timezone: 'local',      header: {        left: 'title   today prev,next',        center: 'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,timelineMonth timelineYear',        right: '',      },      // height: 'parent', nope, doesn't work as the parent might be small      height: 'auto',      /* TODO: lists as resources: https://fullcalendar.io/docs/vertical-resource-view */      navLinks: true,      nowIndicator: true,      businessHours: {        // days of week. an array of zero-based day of week integers (0=Sunday)        dow: [ 1, 2, 3, 4, 5 ], // Monday - Friday        start: '8:00',        end: '18:00',      },      locale: TAPi18n.getLanguage(),      events(start, end, timezone, callback) {        const currentBoard = Boards.findOne(Session.get('currentBoard'));        const events = [];        currentBoard.cardsInInterval(start.toDate(), end.toDate()).forEach(function(card){          events.push({            id: card._id,            title: card.title,            start: card.startAt,            end: card.endAt,            allDay: Math.abs(card.endAt.getTime() - card.startAt.getTime()) / 1000 === 24*3600,            url: FlowRouter.url('card', {              boardId: currentBoard._id,              slug: currentBoard.slug,              cardId: card._id,            }),          });        });        callback(events);      },      eventResize(event, delta, revertFunc) {        let isOk = false;        const card = Cards.findOne(event.id);        if (card) {          card.setEnd(event.end.toDate());          isOk = true;        }        if (!isOk) {          revertFunc();        }      },      eventDrop(event, delta, revertFunc) {        let isOk = false;        const card = Cards.findOne(event.id);        if (card) {          // TODO: add a flag for allDay events          if (!event.allDay) {            card.setStart(event.start.toDate());            card.setEnd(event.end.toDate());            isOk = true;          }        }        if (!isOk) {          revertFunc();        }      },    };  },}).register('calendarView');
 |