UNPKG

@busy-web/ember-date-time

Version:

Date time & and range picker for ember addon

265 lines (223 loc) 8.17 kB
/** * @module Utils * */ import { isNone } from '@ember/utils'; import { assert } from '@ember/debug'; import { get, set } from '@ember/object'; import Base from './clock/base'; import { HOUR_FLAG } from './constant'; import dataArray, { /*getHourMinute,*/ getAttrs, createPoints } from './clock/data'; import render from './clock/render'; import SVG from './clock/svg'; import { metaName, elementName } from './clock/string'; import { onMoveStart, onMove, onMoveStop } from './clock/movement'; import { angleOfLine, getSliceDegree, getLineFromDegree, getSliceFromDegree, getBoundsCenter, createSVGPath } from './clock/math'; /** * @class Clock */ class Clock extends dataArray(render(Base)) { /** * @public * @constructor * @params component {class} instance of the component class (this) reference * @params key {string} the key to reference this clocks meta * @params start {number} the number the clock should start with 0, 1... * @params end {number} the number the clock should end with 12, 59... * @params rounder {number} a number to round the clock digits by. 0 to render all numbers. 5 would render 5, 10, 15... */ constructor(component, key, start=0, end=0, min, max, rounder=0, selectRounder=0) { assert("component is required to create a Clock meta object", !isNone(component)); let { points, renderSize } = createPoints(start, end, rounder, selectRounder); super(points); this.setProps({ component, key, name: metaName(key), elname: elementName(key), start, end, min, max, rounder, selectRounder, renderSize, lastTime: null, lastGroup: null }); } get type() { return this.__key; } get points() { return this.__data; } get elementName() { return this.__elname; } get length() { return this.__renderSize; } get container() { if (isNone(this.__container) || !this.__container.length) { if (!isNone(this.__component.$()) && this.__component.$().length) { this.__container = this.__component.$(this.__elname); } } return this.__container; } get svg() { if (!this.__snap) { const el = this.container.find('svg'); this.__snap = new SVG(el.get(0), { rounder: this.__rounder, selectRounder: this.__selectRounder, min: this.__min, max: this.__max }); } return this.__snap; } renderDOM(timestamp) { if (!isNone(this.container)) { this.__lastTime = timestamp; const height = this.container.height(); const width = this.container.width(); this.setBounds(0, 0, height, width); this.filterEach('isRender', p => { this.svg.minMaxHandler(this.type, p.num, this.__min, this.__max, timestamp); }); } } click(cb) { this.__clickHandler = (evt) => this.clickHandler(evt, cb); this.svg.snap.click(this.__clickHandler); } unclick() { this.svg.snap.unclick(this.__clickHandler); } clickHandler(evt, cb=function(){}) { evt = evt || window.event; // use the svg element as our target element const target = this.container.find('svg').get(0); // get the main clock center x and y const faceAttrs = getAttrs(this.svg.face, ['cx', 'cy']); const cx = parseInt(faceAttrs.cx, 10); const cy = parseInt(faceAttrs.cy, 10); // calculate offset because any browser other than chrome // is not caluculating it correctly const rect = target.getBoundingClientRect(); const offsetX = evt.clientX - rect.left; const offsetY = evt.clientY - rect.top; // use offsetX and offsetY to get local point to clock let angle = angleOfLine(offsetX, offsetY, cx, cy); let point = this.getPointFromDegree(angle); // call the callback with the number selected cb(point.num); } cleanup() { if (!isNone(this.container)) { this.svg.clean(this.__start, this.__end); } } selectPlot(num) { if (!isNone(this.container)) { this.svg.selectPlot(num); } } unselectPlot(num) { if (!isNone(this.container)) { this.svg.unselectPlot(num); } } setBounds(minX, minY, width, height) { // hide box until calculations have been made // safari fix this.svg.__el.style.display = "none"; this.svg.snap.attr({ viewBox: `${minX} ${minY} ${width} ${height}` }); const faceCoords = getBoundsCenter(width, height); this.svg.face.attr({ cx: faceCoords.cx, cy: faceCoords.cy, r: faceCoords.r }); this.svg.pivot.attr({ cx: faceCoords.cx, cy: faceCoords.cy, r: ( faceCoords.r * 0.0283 ) }); const plotR = this.type === HOUR_FLAG ? (faceCoords.r * 0.14) : (faceCoords.r * 0.12); // radius for the plot circles that hold the numbers const padding = (width * 0.0306); // padding to keep plot circles in the clock face const armLen = (faceCoords.r - plotR) - padding; // length for the arms on the clock const pathLen = (armLen - (plotR * 2)); this.filterEach('isRender', point => { const { arm, plot, path, text } = this.svg.at(point.num); const degree = getSliceDegree(this.points.length, point.num); const armCoords = getLineFromDegree(degree, armLen, faceCoords.cx, faceCoords.cy); // xy coords for the plot circle center arm.attr(armCoords); plot.attr({ cx: armCoords.x2, cy: armCoords.y2, r: plotR }); // calculate text position if there is a text // at this number if (!isNone(text)) { // show box long enough to get text box size this.svg.__el.style.display = ""; const bounds = text.node.getBBox(); this.svg.__el.style.display = "none"; let tx = (armCoords.x2 - (bounds.width / 2)); let ty = (armCoords.y2 + (bounds.height / 3)); let tAttr = `translate(${tx}, ${ty})`; text.attr('transform', tAttr); } // calculate section position for click areas on minutes if (!isNone(path)) { const pt = createSVGPath(this.length, degree, faceCoords.cx, faceCoords.cy, pathLen, faceCoords.r); // build the path string path.attr(pt); } }); // show box this.svg.__el.style.display = ""; } handleDrag(num, cb) { const faceAttrs = getAttrs(this.svg.face, ['cx', 'cy']); const angle = getSliceDegree(this.points.length, num); const { plot, arm, text } = this.svg.at(num); const plotAttrs = getAttrs(plot, ['cx', 'cy']); if (!isNone(this.__lastGroup)) { this.__lastGroup.undrag(); } let group; if (!isNone(text)) { group = this.svg.snap.g(arm, plot, text); } else { group = this.svg.snap.g(arm, plot); } let point; group.drag( onMove(angle, parseFloat(faceAttrs.cx), parseFloat(faceAttrs.cy), parseFloat(plotAttrs.cx), parseFloat(plotAttrs.cy), ang => { point = this.getPointFromDegree(ang); }), onMoveStart(text, 'selected', this.svg.snap), onMoveStop(this, (el, evt) => { if (!point) { // use offsetX and offsetY to get local point to clock let angle = angleOfLine(evt.offsetX, evt.offsetY, faceAttrs.cx, faceAttrs.cy); point = this.getPointFromDegree(angle); } cb(point.num); }) ); this.__lastGroup = group; } getPointFromDegree(degree) { let len = this.__end; if (this.__start === 0) { len = len + 1; } // get round num for rounding by 1, 5, 10, 15, 30 const round = this.__selectRounder || 1; // get slice num for clock let slice = getSliceFromDegree(len, degree); // round the slice based on the selected round number slice = Math.round(slice / round) * round; // make sure max and min numbers are correct if (this.__end < slice) { slice = this.__start; } else if (this.__start > slice) { slice = this.__end; } // subtract the offset for the array index if (this.__start !== 0) { slice = slice - this.__start; } return this.objectAt(slice); } } export function createMetaFor(component, key, start, end, min, max, rounder, selectRounder) { const clock = new Clock(component, key, start, end, min, max, rounder, selectRounder); set(component, metaName(key), clock); } export function metaFor(component, key) { return get(component, metaName(key)); }