Skip to content

Commit

Permalink
Merge pull request #28 from fleetbase/dev-v0.2.4
Browse files Browse the repository at this point in the history
v0.2.4
  • Loading branch information
roncodes authored Nov 9, 2023
2 parents 3804941 + 1fa4551 commit c4c9c18
Show file tree
Hide file tree
Showing 23 changed files with 846 additions and 11 deletions.
18 changes: 18 additions & 0 deletions addon/components/drawer.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div role="dialog" aria-modal="true" class="next-drawer {{if this.isMinimized 'drawer-is-minimized'}} {{if this.isResizing 'drawer-is-resizing'}} {{if this.isOpen 'drawer-is-open'}} {{if this.noBackdrop 'drawer-no-backdrop'}} {{if @fullHeight 'drawer-full-height'}} {{@overlayClass}}" {{did-insert this.setupComponent}}>
{{#if this._rendered}}
<div class="next-drawer-panel-container {{@containerClass}}" {{did-insert (fn this.setupNode "drawerContainer")}}>
{{#if this.isResizable}}
<div class="gutter" {{did-insert (fn this.setupNode "gutter")}} {{on "mousedown" this.startResize}}>
{{#if @notchEnabled}}
<div class="notch" {{on "dblclick" this.toggleMinimize}}>
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</div>
{{/if}}
</div>
{{/if}}
<div class="next-drawer-panel" {{did-insert (fn this.setupNode "drawerPanel")}} {{set-height this.height}} ...attributes>{{yield this.context}}</div>
</div>
{{/if}}
</div>
244 changes: 244 additions & 0 deletions addon/components/drawer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { later } from '@ember/runloop';
import getWithDefault from '@fleetbase/ember-core/utils/get-with-default';

/**
* DrawerComponent provides a UI drawer element with several features such as
* open, close, toggle, and resize capabilities.
*/
export default class DrawerComponent extends Component {
/** Node of the drawer element. */
@tracked drawerNode;

/** Node of the drawer container element. */
@tracked drawerContainerNode;

/** Node of the drawer panel element. */
@tracked drawerPanelNode;

/** Node of the gutter element. */
@tracked gutterNode;

/** Indicates if the backdrop is not present. */
@tracked noBackdrop = true;

/** Indicates if the drawer is open. */
@tracked isOpen = true;

/** Indicates if the drawer is resizable. */
@tracked isResizable = true;

/** Indicates if the drawer is currently being resized. */
@tracked isResizing = false;

/** Indicates if the drawer is minimized. */
@tracked isMinimized = false;

/** Current X position of the mouse. */
@tracked mouseX = 0;

/** Current Y position of the mouse. */
@tracked mouseY = 0;

/** Height of the drawer. */
@tracked height = 300;

/** Indicates if the drawer has been rendered. */
@tracked _rendered = false;

/** Context object providing drawer control functions and state. */
context = {
toggle: this.toggle,
open: this.open,
close: this.close,
toggleMinimize: this.toggleMinimize,
minimize: this.minimize,
maximize: this.maximize,
isOpen: this.isOpen,
};

/** Context object providing drawer control functions and state. */
context = {
toggle: this.toggle,
open: this.open,
close: this.close,
toggleMinimize: this.toggleMinimize,
minimize: this.minimize,
maximize: this.maximize,
isOpen: this.isOpen,
};

/**
* Sets up the component, establishes default properties, and calls the onLoad callback if provided.
* @param {HTMLElement} element - The element to be used as the drawer node.
*/
@action setupComponent(element) {
this.drawerNode = element;
this.height = getWithDefault(this.args, 'height', this.height);
this.isMinimized = getWithDefault(this.args, 'isMinimized', this.isMinimized);

later(
this,
() => {
this.isOpen = getWithDefault(this.args, 'isOpen', this.isOpen);
this.isResizable = getWithDefault(this.args, 'isResizable', this.isResizable);
this.noBackdrop = getWithDefault(this.args, 'noBackdrop', this.noBackdrop);

if (typeof this.args.onLoad === 'function') {
this.args.onLoad(this.context);
}

this._rendered = true;
},
300
);
}

/**
* Assigns a DOM node to a tracked property.
* @param {string} property - The property name to which the node will be assigned.
* @param {HTMLElement} node - The DOM node to be tracked.
*/
@action setupNode(property, node) {
this[`${property}Node`] = node;
}

/** Toggles the open state of the drawer. */
@action toggle() {
this.isOpen = !this.isOpen;
}

/** Opens the drawer. */
@action open() {
this.isOpen = true;
}

/** Closes the drawer. */
@action close() {
this.isOpen = false;
}

/** Toggles the minimized state of the drawer. */
@action toggleMinimize() {
this.isMinimized = !this.isMinimized;
}

/** Minimizes the drawer. */
@action minimize() {
this.isMinimized = true;
}

/** Maximizes the drawer. */
@action maximize() {
this.isMinimized = false;
}

/**
* Starts the resize process for the drawer.
* @param {MouseEvent} event - The mouse event that initiates the resize.
*/
@action startResize(event) {
const disableResize = getWithDefault(this.args, 'disableResize', false);
const onResizeStart = getWithDefault(this.args, 'onResizeStart', null);
const { drawerPanelNode, isResizable } = this;

if (disableResize === true || !isResizable || !drawerPanelNode) {
return;
}

// if minimized undo
if (this.isMinimized) {
return this.maximize();
}

const bounds = drawerPanelNode.getBoundingClientRect();

// Set the overlay width/height
this.overlayWidth = bounds.width;
this.overlayHeight = bounds.height;

// Start resizing
this.isResizing = true;

// Get the current mouse position
this.mouseX = event.clientX;
this.mouseY = event.clientY;

// Attach the listeners
document.addEventListener('mousemove', this.resize);
document.addEventListener('mouseup', this.stopResize);

// Send up event
if (typeof onResizeStart === 'function') {
onResizeStart({ event, drawerPanelNode });
}
}

/**
* Resizes the drawer during a mousemove event.
* @param {MouseEvent} event - The mouse event that triggers the resize.
*/
@action resize(event) {
const disableResize = getWithDefault(this.args, 'disableResize', false);
const onResize = getWithDefault(this.args, 'onResize', null);
const { drawerPanelNode, isResizable } = this;

if (disableResize === true || !isResizable || !drawerPanelNode) {
return;
}

const dy = event.clientY - this.mouseY;
const multiplier = -1;
const height = dy * multiplier + this.overlayHeight;
const minResizeHeight = getWithDefault(this.args, 'minResizeHeight', 0);
const maxResizeHeight = getWithDefault(this.args, 'maxResizeHeight', 600);

// Min resize height
if (height <= minResizeHeight) {
drawerPanelNode.style.height = `${minResizeHeight}px`;
return;
}

// Max resize height
if (height >= maxResizeHeight) {
drawerPanelNode.style.height = `${maxResizeHeight}px`;
return;
}

// Style changes
drawerPanelNode.style.userSelect = 'none';
drawerPanelNode.style.height = `${height}px`;
document.body.style.cursor = 'row-resize';

// Send callback
if (typeof onResize === 'function') {
onResize({ event, drawerPanelNode });
}
}

/**
* Stops the resizing process and cleans up event listeners.
* @param {MouseEvent} event - The mouse event that ends the resize.
*/
@action stopResize(event) {
const onResizeEnd = getWithDefault(this.args, 'onResizeEnd', null);
const { drawerPanelNode } = this;

// End resizing
this.isResizing = false;

// Remove style changes
document.body.style.removeProperty('cursor');
drawerPanelNode.style.userSelect = 'auto';

// Remove the handlers of `mousemove` and `mouseup`
document.removeEventListener('mousemove', this.resize);
document.removeEventListener('mouseup', this.stopResize);

if (typeof onResizeEnd === 'function') {
onResizeEnd({ event, drawerPanelNode });
}
}
}
1 change: 1 addition & 0 deletions addon/components/full-calendar.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div id="fleetbase-full-calendar" class="fleetbase-full-calendar" {{did-insert this.setupCalendar}}></div>
110 changes: 110 additions & 0 deletions addon/components/full-calendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { classify } from '@ember/string';
import { Calendar } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';

export default class FullCalendarComponent extends Component {
/**
* @var {HTMLElement} calendarEl
*/
@tracked calendarEl;

/**
* @var {Calendar} calendar
* @package @fullcalendar/core
*/
@tracked calendar;

/**
* Default events to trigger for
* @var {Array}
*/
@tracked events = ['dateClick', 'drop', 'eventReceive', 'eventClick', 'eventDragStop', 'eventDrop', 'eventAdd', 'eventChange', 'eventRemove'];

/**
* Tracked calendar event listeners
* @var {Array}
*/
@tracked _listeners = [];

/**
* Initializes and renders the calendar component
*
* @param {HTMLElement} calendarEl
*/
@action setupCalendar(calendarEl) {
// track calendar htmlelement
this.calendarEl = calendarEl;

// get events
let events = this.args.events || [];

// initialize calendar
this.calendar = new Calendar(calendarEl, {
events,
plugins: [dayGridPlugin, interactionPlugin],
initialView: 'dayGridMonth',
editable: true,
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: '',
},
});

// trigger callback on initialize
if (typeof this.args.onInit === 'function') {
this.args.onInit(this.calendar);
}

// render calendar
this.calendar.render();

// listen for events
this.createCalendarEventListeners();
}

triggerCalendarEvent(eventName, ...params) {
if (typeof this[eventName] === 'function') {
this[eventName](...params);
}

if (typeof this.args[eventName] === 'function') {
this.args[eventName](...params);
}
}

createCalendarEventListeners() {
for (let i = 0; i < this.events.length; i++) {
const eventName = this.events.objectAt(i);
const callbackName = `on${classify(eventName)}`;

if (typeof this.args[callbackName] === 'function') {
// track for destroy purposes
this._listeners.pushObject({
eventName,
callbackName,
});

// create listener
this.calendar.on(eventName, this.triggerCalendarEvent.bind(this, callbackName));
}
}

// check for custom events
// @todo
}

destroyCalendarEventListeners() {
for (let i = 0; i < this._listeners.length; i++) {
const listener = this._listeners.objectAt(i);
const { eventName, callbackName } = listener;

// kill listener
this.calendar.off(eventName, this.triggerCalendarEvent.bind(this, callbackName));
}
}
}
3 changes: 3 additions & 0 deletions addon/components/full-calendar/draggable.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="fleetbase-full-calendar-draggable" data-event={{@eventData}} {{did-insert this.makeDraggable}} ...attributes>
{{yield}}
</div>
Loading

0 comments on commit c4c9c18

Please sign in to comment.