feat(All):Initial

This commit is contained in:
2025-10-04 23:36:07 +08:00
commit 2b4e5d2668
1321 changed files with 415958 additions and 0 deletions
@@ -0,0 +1,62 @@
// bundle entry point
export * from './timeline';
export * from './timelineEventsEmitter';
export * from './settings/timelineConsts';
// @ public timeline models.
export * from './models/timelineRanged';
export * from './models/timelineModel';
export * from './models/timelineRow';
export * from './models/timelineKeyframe';
// @public styles
export * from './settings/timelineOptions';
export * from './settings/styles/timelineKeyframeStyle';
export * from './settings/styles/timelineRowStyle';
export * from './settings/styles/timelineStyle';
export * from './settings/styles/timelineGroupStyle';
export * from './utils/timelineStyleUtils';
export * from './utils/timelineUtils';
export * from './utils/timelineElement';
// @private helper containers.
export * from './utils/timelineSelectable';
export * from './utils/timelineCutBoundsRectResults';
export * from './utils/timelineSelectionResults';
export * from './utils/timelinePoint';
export * from './utils/timelineMouseData';
export * from './utils/timelineElementDragState';
export * from './utils/timelineDraggableData';
// @private virtual model
export * from './viewModels/timelineGroupViewModel';
export * from './viewModels/timelineKeyframeViewModel';
export * from './viewModels/timelineRowViewModel';
export * from './viewModels/timelineViewModel';
// @public events
export * from './utils/events/timelineKeyframeChangedEvent';
export * from './utils/events/timelineTimeChangedEvent';
export * from './utils/events/timelineSelectedEvent';
export * from './utils/events/timelineScrollEvent';
export * from './utils/events/timelineClickEvent';
export * from './utils/events/timelineDragEvent';
// @public enums
export * from './enums/timelineKeyframeShape';
export * from './enums/timelineInteractionMode';
export * from './enums/timelineScrollSource';
export * from './enums/timelineElementType';
export * from './enums/timelineCursorType';
export * from './enums/timelineCapShape';
export * from './enums/timelineEventSource';
export * from './enums/timelineSelectionMode';
export * from './enums/timelineEvents';
// @private defaults are exposed:
export * from './settings/defaults/defaultTimelineStyle';
export * from './settings/defaults/defaultTimelineRowStyle';
export * from './settings/defaults/defaultTimelineOptions';
export * from './settings/defaults/defaultTimelineKeyframeStyle';
export * from './settings/defaults/defaultTimelineConsts';
export * from './settings/defaults/defaultGroupStyle';
@@ -0,0 +1,5 @@
export enum TimelineCapShape {
None = 'none',
Triangle = 'triangle',
Rect = 'rect',
}
@@ -0,0 +1,37 @@
export enum TimelineCursorType {
Alias = 'alias',
AllScroll = 'all-scroll',
Auto = 'auto',
Cell = 'cell',
ContextMenu = 'context-menu',
ColResize = 'col-resize',
Copy = 'copy',
Crosshair = 'crosshair',
Default = 'default',
EResize = 'e-resize',
EWResize = 'ew-resize',
Grab = 'grab',
Grabbing = 'grabbing',
Help = 'help',
Move = 'move',
NResize = 'n-resize',
NEResize = 'ne-resize',
NESWResize = 'nesw-resize',
NSResize = 'ns-resize',
NWResize = 'nw-resize',
NWSEResize = 'nwse-resize',
NoDrop = 'no-drop',
None = 'none',
NotAllowed = 'not-allowed',
Pointer = 'pointer',
Progress = 'progress',
RowResize = 'row-resize',
SResize = 's-resize',
SEResize = 'se-resize',
SWResize = 'sw-resize',
Text = 'text',
WResize = 'w-resize',
Wait = 'wait',
ZoomIn = 'zoom-in',
ZoomOut = 'zoom-out',
}
@@ -0,0 +1,22 @@
/**
* Internal element type.
*/
export enum TimelineElementType {
/**
* Timeline
*/
Timeline = 'timeline',
/**
* Keyframes
*/
Keyframe = 'keyframe',
/**
* Keyframes connected and presenting one group.
*/
Group = 'group',
/**
* Timeline row.
*/
Row = 'row',
None = 'none',
}
@@ -0,0 +1,14 @@
export enum TimelineEventSource {
/**
* Changed by user interaction events.
*/
User = 'user',
/**
* Changed programmatically.
*/
Programmatically = 'programmatically',
/**
* Changed by the set time function.
*/
SetTimeMethod = 'setTimeMethod',
}
@@ -0,0 +1,17 @@
/**
* Event names of the component.
*/
export enum TimelineEvents {
Selected = 'selected',
TimeChanged = 'timechanged',
KeyframeChanged = 'keyframeChanged',
DragStarted = 'dragStarted',
Drag = 'drag',
DragFinished = 'dragFinished',
Scroll = 'scroll',
ScrollFinished = 'scrollFinished',
ContextMenu = 'onContextMenu',
DoubleClick = 'doubleClick',
MouseDown = 'mouseDown',
Zoom = 'zoom',
}
@@ -0,0 +1,25 @@
export enum TimelineInteractionMode {
/**
* Keyframe selection tool selecting single or group of keyframes.
*/
Selection = 'selection',
/**
* Pan tool with the possibility to select keyframes.
*/
Pan = 'pan',
/**
* Allow only pan without any keyframes interaction.
* Timeline still can be moved and controlled by option 'timelineDraggable'.
*/
NonInteractivePan = 'nonInteractivePan',
/**
* Zoom tool.
*/
Zoom = 'zoom',
/**
* No iteraction, except moving a timeline.
* Timeline still can be moved and controlled by option 'timelineDraggable'.
*/
None = 'none',
}
@@ -0,0 +1,6 @@
export enum TimelineKeyframeShape {
None = 'none',
Rhomb = 'rhomb',
Circle = 'circle',
Rect = 'rect',
}
@@ -0,0 +1,5 @@
export enum TimelineScrollSource {
DefaultMode = 'none',
ZoomMode = 'zoom',
ScrollBySelection = 'scrollBySelection',
}
@@ -0,0 +1,9 @@
/**
* Timeline selection event type.
*/
export enum TimelineSelectionEventSource {
/**
* Keyframe selection is performed.
*/
Keyframes = 'keyframes',
}
@@ -0,0 +1,17 @@
/**
* Timeline selection mode.
*/
export enum TimelineSelectionMode {
/**
* Select new items. deselect changed.
*/
Normal = 'normal',
/**
* Append current selection.
*/
Append = 'append',
/**
* Revert selection of a specified nodes.
*/
Revert = 'revert',
}
@@ -0,0 +1,28 @@
import { TimelineKeyframeStyle } from '../settings/styles/timelineKeyframeStyle';
import { TimelineGroupStyle } from '../settings/styles/timelineGroupStyle';
/**
* Timeline group view model.
*/
export interface TimelineGroup {
/**
* Group style.
*/
style: TimelineGroupStyle;
/**
* Child keyframes style.
*/
keyframesStyle?: TimelineKeyframeStyle;
/**
* Whether group is draggable.
* Considered to be false when really set as false.
*/
draggable?: boolean;
/**
* Whether group keyframes are draggable.
*/
keyframesDraggable?: boolean;
/**
* Whether group is hidden.
*/
hidden?: boolean;
}
@@ -0,0 +1,29 @@
import { TimelineKeyframeStyle } from '../settings/styles/timelineKeyframeStyle';
import { TimelineSelectable } from '../utils/timelineSelectable';
import { TimelineRanged } from './timelineRanged';
import { TimelineGroup } from './timelineGroup';
export interface TimelineKeyframe extends TimelineSelectable, TimelineRanged {
/**
* Keyframe value.
*/
val: number;
/**
* Related keyframe group.
* Timeline keyframes groups are rendered as one instance.
*/
group?: string | TimelineGroup;
/**
* Keyframe style.
*/
style?: TimelineKeyframeStyle;
/**
* Whether keyframe is hidden.
*/
hidden?: boolean;
/**
* Whether group is draggable.
* Considered to be false when really set as false.
*/
draggable?: boolean;
}
@@ -0,0 +1,4 @@
import { TimelineRow } from './timelineRow';
export interface TimelineModel {
rows: TimelineRow[];
}
@@ -0,0 +1,10 @@
export interface TimelineRanged {
/**
* min
*/
min?: number | null;
/**
* max.
*/
max?: number | null;
}
@@ -0,0 +1,14 @@
import { TimelineKeyframe } from './timelineKeyframe';
import { TimelineRowStyle } from '../settings/styles/timelineRowStyle';
import { TimelineRanged } from './timelineRanged';
export interface TimelineRow extends TimelineRanged {
style?: TimelineRowStyle;
keyframes?: TimelineKeyframe[] | null;
hidden?: boolean;
/**
* Whether group keyframes are draggable.
*/
keyframesDraggable?: boolean;
groupsDraggable?: boolean;
}
@@ -0,0 +1,23 @@
import { TimelineGroupStyle } from '../styles/timelineGroupStyle';
export const defaultRowHeight = 24;
const margin = 4;
export const defaultGroupStyle = {
height: 'auto',
marginTop: margin,
/**
* Default group fill color.
*/
fillColor: '#094771',
/**
* 组默认文本设置
*/
text: {
label: "",
isStroke:false,
font: '10px sans-serif',
textAlign: 'center',
textBaseline: 'middle',
direction:"inherit",
fillColor: '#fff'
}
} as TimelineGroupStyle;
@@ -0,0 +1,44 @@
import { TimelineConsts } from '../timelineConsts';
export const defaultTimelineConsts: TimelineConsts = {
/**
* Private. Auto pan speed.
*/
autoPanSpeed: 50,
/**
* Private. scroll speed when mouse drag is used (from 0 to 1)
*/
scrollByDragSpeed: 0.12,
/**
* Private. Determine whether item was clicked.
*/
clickDetectionMs: 120,
/**
* Private. Timeout to detect double click.
*/
doubleClickTimeoutMs: 400,
/**
* Private. Time in ms used to refresh scrollbars when pan is finished.
*/
scrollFinishedTimeoutMs: 500,
/**
* Private. Auto pan padding
*/
autoPanByScrollPadding: 10,
/**
* Private. Click threshold
*/
clickThreshold: 3,
/**
* Private. Private.Click min radius for the elements detection.
*/
clickDetectionMinRadius: 2,
/**
* Private. Skip some auto pan/scroll actions if they are executed more rapid than this value.
*/
autoPanSpeedLimit: 10,
/**
* Private. Default auto size for the group. It's percents.
*/
defaultGroupHeight: 0.7,
} as TimelineConsts;
@@ -0,0 +1,19 @@
import { TimelineKeyframeShape } from '../../enums/timelineKeyframeShape';
import { TimelineKeyframeStyle } from '../styles/timelineKeyframeStyle';
export const defaultTimelineKeyframeStyle = {
/**
* keyframe fill color.
*/
fillColor: 'DarkOrange',
shape: TimelineKeyframeShape.Rhomb,
/**
* Selected keyframe fill color.
*/
selectedFillColor: 'red',
strokeColor: 'black',
selectedStrokeColor: 'black',
strokeThickness: 0.2,
height: 'auto',
width: 'auto',
} as TimelineKeyframeStyle;
@@ -0,0 +1,117 @@
import { TimelineOptions } from '../timelineOptions';
import { defaultGroupStyle } from './defaultGroupStyle';
import { defaultTimelineKeyframeStyle } from './defaultTimelineKeyframeStyle';
import { defaultTimelineRowStyle } from './defaultTimelineRowStyle';
import { defaultTimelineStyle } from './defaultTimelineStyle';
export const defaultTimelineOptions = {
/**
* Snap all selected keyframes as a bundle during the drag.
*/
snapAllKeyframesOnMove: false,
/**
* Check whether snapping is enabled.
*/
snapEnabled: true,
/**
* Timeline style.
*/
timelineStyle: defaultTimelineStyle,
/**
* approximate step for the timeline in pixels for 1 second
*/
stepPx: 120,
/**
* Number of units that should fit into one stepPx. (1 second by a default)
*/
stepVal: 1000,
stepSmallPx: 30,
/**
* Snap step in units. from 0 to stepVal
*/
snapStep: 200,
/**
* additional left margin in pixels to start the line gauge from.
*/
leftMargin: 25,
headerFillColor: '#101011',
fillColor: '#101011',
labelsColor: '#D5D5D5',
/**
* Header gauge tick color.
*/
tickColor: '#D5D5D5',
/**
* Selection rectangle color.
*/
selectionColor: 'White',
/**
* Default rows style.
* Can be overridden by setting style individually for each row.
*/
rowsStyle: defaultTimelineRowStyle,
/**
* Style for the all keyframes in a current row.
* Individual keyframe can have own style.
*/
keyframesStyle: defaultTimelineKeyframeStyle,
/**
* Style of the groups.
*/
groupsStyle: defaultGroupStyle,
/**
* Header height in pixels
*/
headerHeight: 30,
font: '11px sans-serif',
/**
* Default zoom level = 1. where screen pixels are equals to the corresponding stepVal stepPx.
*/
zoom: 1,
/**
* Default zoom speed.
*/
zoomSpeed: 0.1,
/**
* Max zoom value.
*/
zoomMin: 0.1,
/**
* Min zoom value.
*/
zoomMax: 8,
/**
* Set this to true in a MAC OS environment: The Meta key will be used instead of the Ctrl key.
*/
controlKeyIsMetaKey: false,
/**
* Access the scroll container via this class for e.g. scroll bar styling.
*/
scrollContainerClass: 'scroll-container',
/**
* keyframes group is draggable.
*/
groupsDraggable: true,
/**
* keyframes are draggable.
*/
keyframesDraggable: true,
/**
* Timeline can be dragged or position can be changed by user interaction. Default: true
*/
timelineDraggable: true,
/**
* Start drawing timeline from this min point.
* Bounds for the keyframe dragging.
*/
min: 0,
/**
* Max bounds timeline can navigate to.
*/
max: Number.MAX_VALUE,
} as TimelineOptions;
@@ -0,0 +1,21 @@
import { TimelineRowStyle } from '../styles/timelineRowStyle';
import { defaultGroupStyle, defaultRowHeight } from './defaultGroupStyle';
import { defaultTimelineKeyframeStyle } from './defaultTimelineKeyframeStyle';
export const defaultTimelineRowStyle = {
/**
* Row height in pixels.
*/
height: defaultRowHeight,
marginBottom: 2,
fillColor: '#252526',
/**
* Style for the all keyframes in a current row.
* Individual keyframe can have own style.
*/
keyframesStyle: defaultTimelineKeyframeStyle,
/**
* Style of the groups.
*/
groupsStyle: defaultGroupStyle,
} as TimelineRowStyle;
@@ -0,0 +1,22 @@
import { TimelineCapShape } from '../../enums/timelineCapShape';
import { TimelineCursorType } from '../../enums/timelineCursorType';
import { TimelineStyle } from '../styles/timelineStyle';
export const defaultTimelineStyle = {
width: 2,
marginTop: 15,
marginBottom: 0,
strokeColor: 'DarkOrange',
fillColor: 'DarkOrange',
capStyle: {
width: 4,
height: 10,
/**
* Draw timeline rectangular cap.
*/
capType: TimelineCapShape.Rect,
strokeColor: 'DarkOrange',
fillColor: 'DarkOrange',
},
cursor: TimelineCursorType.EWResize,
} as TimelineStyle;
@@ -0,0 +1,27 @@
import { TimelineCapShape } from '../../enums/timelineCapShape';
/**
* Timeline active/current value indicator style.
*/
export interface TimelineCapStyle {
/**
* Cap style width in pixels.
*/
width?: number;
/**
* Cap style height in pixels.
*/
height?: number;
/**
* Cap stroke color.
*/
strokeColor?: string;
/**
* Cap fill color.
*/
fillColor?: string;
/**
* Cap type
*/
capType?: TimelineCapShape;
}
@@ -0,0 +1,56 @@
import { TimelineCursorType } from '../../enums/timelineCursorType';
import { TimelineKeyframeStyle } from '../styles/timelineKeyframeStyle';
/**
* Timeline group style.
*/
export interface TimelineGroupStyle {
/**
* Keyframes style height in pixels.
* 'auto' to automatically calculate.
*/
height?: number | string;
/**
* Group stroke color.
*/
strokeColor?: string;
/**
* Group stroke thickness.
*/
strokeThickness?: number | null;
/**
* Group border radius. See canvas roundRect official documentation.
*/
radii?: number | DOMPointInit | Iterable<number | DOMPointInit>;
/**
* Group fill color.
*/
fillColor?: string;
/**
* Group mouse over cursor style.
*/
cursor?: TimelineCursorType;
/**
* Margin top in px or 'auto' to center element.
*/
marginTop?: number | string;
/**
* Style for all the keyframes in the current group.
*/
keyframesStyle?: TimelineKeyframeStyle;
/**
* 组默认文本设置
*/
text?: {
label?: string,
isStroke?: boolean,
font?: string,
textAlign?: "start" | "end" | "left" | "right" | "center",
textBaseline?: "top" | "hanging" | "middle" | "alphabetic" | "ideographic" | "bottom",
direction?: "ltr" | "rtl" | "inherit",
fillColor?: string,
}
}
@@ -0,0 +1,41 @@
import { TimelineCursorType } from '../../enums/timelineCursorType';
import { TimelineKeyframeShape } from '../../enums/timelineKeyframeShape';
export interface TimelineKeyframeStyle {
/**
* Timeline cursor style.
*/
cursor?: TimelineCursorType;
/**
* Timeline keyframe shape
*/
shape?: TimelineKeyframeShape;
/**
* keyframe size, number or text 'auto'
*/
height?: number | string;
/**
* keyframe size, number or text 'auto'
*/
width?: number | string;
/**
* Keyframe fill color
*/
fillColor?: string | null;
/**
* Keyframe selected fill color.
*/
selectedFillColor?: string | null;
/**
* Keyframe stroke color.
*/
strokeColor?: string | null;
/**
* Keyframe selected stroke color.
*/
selectedStrokeColor?: string | null;
/**
* Keyframe stroke Thickness.
*/
strokeThickness?: number | null;
}
@@ -0,0 +1,29 @@
import { TimelineGroupStyle } from './timelineGroupStyle';
import { TimelineKeyframeStyle } from './timelineKeyframeStyle';
/**
* Style of the row.
*/
export interface TimelineRowStyle {
/**
* Size of the row in pixels.
*/
height?: number;
/**
* Track fill color.
*/
fillColor?: string;
/**
* Row margin bottom in pixels between tracks/rows.
*/
marginBottom?: number;
/**
* Style for the all keyframes in a current row.
* Individual keyframe can have own style.
*/
keyframesStyle?: TimelineKeyframeStyle;
/**
* Style of the groups. Keyframe groups can be also styles separately.
*/
groupsStyle?: TimelineGroupStyle;
}
@@ -0,0 +1,33 @@
import { TimelineCursorType } from '../../enums/timelineCursorType';
import { TimelineCapStyle } from './timelineCapStyle';
/**
* Timeline active/current value indicator style.
*/
export interface TimelineStyle {
width?: number;
/**
* Margin top in pixels.
*/
marginTop?: number;
/**
* Margin bottom in pixels.
*/
marginBottom?: number;
/**
* Timeline top cap style.
*/
capStyle?: TimelineCapStyle;
/**
* Timeline indicator stroke color.
*/
strokeColor?: string;
/**
* Timeline fill color.
*/
fillColor?: string;
/**
* Timeline cursor.
*/
cursor?: TimelineCursorType;
}
@@ -0,0 +1,47 @@
/**
* Internal components consts.
* Still can be changed thru private property _consts but this is discouraged.
*/
export interface TimelineConsts {
/**
* Auto pan speed.
*/
autoPanSpeed: number;
/**
* scroll speed when mouse drag is used (from 0 to 1)
*/
scrollByDragSpeed: number;
/**
* Determine whether item was clicked.
*/
clickDetectionMs: number;
/**
* Timeout to detect double click.
*/
doubleClickTimeoutMs: number;
/**
* Time in ms used to refresh scrollbars when pan is finished.
*/
scrollFinishedTimeoutMs: number;
/**
* Auto pan padding
*/
autoPanByScrollPadding: number;
/**
* Click threshold
*/
clickThreshold: number;
/**
* Click min radius for the elements detection.
*/
clickDetectionMinRadius: number;
/**
* Default auto size for the group. It's percents.
*/
autoPanSpeedLimit: number;
/**
* Default auto size for the group. It's percents.
*/
defaultGroupHeight: number;
}
@@ -0,0 +1,116 @@
import { TimelineRowStyle } from './styles/timelineRowStyle';
import { TimelineStyle } from './styles/timelineStyle';
import { TimelineRanged } from '../models/timelineRanged';
export interface TimelineOptions extends TimelineRanged {
/**
* Id 或时间轴容器的 HTMLElement
*/
id?: string | HTMLElement | null;
/**
* 检查是否启用了捕捉
*/
snapEnabled?: boolean;
/**
* 拖动时将所有选定的关键帧合并为一个包.
*/
snapAllKeyframesOnMove?: boolean;
/**
* 以像素为单位的时间轴的近似步长为1秒
*/
stepPx?: number;
/**
* 一个步骤中应该包含的点的个数。
*/
stepVal?: number;
stepSmallPx?: number;
/**
* 以单位表示。从0到stepVal
*/
snapStep?: number;
/**
* 以像素为单位增加左页边距,从该值开始计算行距.
*/
leftMargin?: number;
/**
* 组件header背景颜色.
*/
headerFillColor?: string;
/**
* 组件背景颜色.
*/
fillColor?: string;
/**
* 标题标签颜色.
*/
labelsColor?: string;
/**
* 标题刻度条颜色.
*/
tickColor?: string;
/**
* 选择矩形颜色.
*/
selectionColor?: string;
/**
* 标题高度(以像素为单位)。
*/
headerHeight?: number;
/**
* Header ticks font
*/
font?: string;
/**
* Default zoom level = 1. where screen pixels are equals to the corresponding stepVal stepPx.
*/
zoom?: number;
/**
* Default zoom speed.
*/
zoomSpeed?: number;
/**
* Max zoom value.
*/
zoomMin?: number;
/**
* Min zoom value.
*/
zoomMax?: number;
/**
* Set this to true in a MAC OS environment: The Meta key will be used instead of the Ctrl key.
*/
controlKeyIsMetaKey?: boolean;
/**
* Access the scroll container via this class for e.g. scroll bar styling.
*/
scrollContainerClass?: string;
/**
* Default rows style.
* Can be overridden by setting style individually for each row.
*/
rowsStyle?: TimelineRowStyle;
/**
* Timeline indicator style.
*/
timelineStyle?: TimelineStyle;
/**
* keyframes group is draggable. Default: true
*/
groupsDraggable?: boolean;
/**
* keyframes group is draggable. Default: true
*/
keyframesDraggable?: boolean;
/**
* Timeline can be dragged or position can be changed by user interaction. Default: true
*/
timelineDraggable?: boolean;
/**
* Array of the denominators used to determine 'beautiful' numbers to be rendered for the gauge.
* Default: [1, 2, 5, 10];
*/
denominators?: number[];
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,63 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
interface TimelineEvent {
topic: string;
callback: (args: any) => void;
}
/**
* Timeline events emitter.
*/
export class TimelineEventsEmitter {
/**
* Active events subscriptions.
*/
_subscriptions: TimelineEvent[] = [];
/**
* Subscribe event.
* @param topic event name.
* @param callback callback to be added.
*/
on<T>(topic: string, callback: (args: T) => void): boolean {
if (!callback) {
return false;
}
this._subscriptions.push({
topic: topic,
callback: callback,
} as TimelineEvent);
return true;
}
/**
* Remove an event from the subscriptions list.
*/
off<T>(topic: string, callback: (args: T) => void): boolean {
const before = this._subscriptions.length;
this._subscriptions = this._subscriptions.filter((event) => {
return event && event.callback != callback && event.topic != topic;
});
return before !== this._subscriptions.length;
}
/**
* Unsubscribe all
*/
offAll(): void {
// Remove all callbacks from array.
this._subscriptions.length = 0;
}
/**
* Emit event.
* @param topic Event name.
* @param args Event arguments.
*/
emit<T>(topic: string, args: T): void {
this._subscriptions.forEach((event) => {
if (event?.topic === topic && event?.callback) {
event.callback(args);
}
});
}
}
@@ -0,0 +1,16 @@
/**
* Timeline base preventable event.
*/
export class TimelineBaseEvent {
private _prevented = false;
/**
* Prevent default click logic.
*/
preventDefault(): void {
this._prevented = true;
}
isPrevented(): boolean {
return this._prevented;
}
}
@@ -0,0 +1,31 @@
import { TimelineElement } from '../timelineElement';
import { TimelinePoint } from '../timelinePoint';
import { TimelineBaseEvent } from './timelineBaseEvent';
export class TimelineClickEvent extends TimelineBaseEvent {
args: MouseEvent | TouchEvent | null = null;
/**
* Clicked screen position.
*/
get pos(): DOMPoint | null {
return this.point?.pos || null;
}
get val(): number {
if (this.point) {
return this.point.val;
}
return NaN;
}
/**
* All elements located under current mouse activity.
*/
elements: TimelineElement[] = [];
/**
* Element that selected as target under the click.
*/
target: TimelineElement | null = null;
/**
* Timeline current active drag position.
*/
point: TimelinePoint | null = null;
}
@@ -0,0 +1,26 @@
import { TimelinePoint } from '../timelinePoint';
import { TimelineBaseEvent } from './timelineBaseEvent';
import { TimelineElementDragState } from '../timelineElementDragState';
export class TimelineDragEvent extends TimelineBaseEvent {
args: MouseEvent | TouchEvent | null = null;
get pos(): DOMPoint | null {
return this.point?.pos || null;
}
/**
* Elements to be dragged as a group.
*/
elements: TimelineElementDragState[] | null = null;
/**
* Target element
*/
target: TimelineElementDragState | null = null;
/**
* Timeline current active drag position.
*/
point: TimelinePoint | null = null;
/**
* Timeline previous drag position.
*/
prevPoint: TimelinePoint | null = null;
}
@@ -0,0 +1,22 @@
import { TimelineBaseEvent } from './timelineBaseEvent';
import { TimelineElement } from '../timelineElement';
import { TimelineEventSource } from '../../enums/timelineEventSource';
export class TimelineKeyframeChangedEvent extends TimelineBaseEvent {
/**
* Value to be used.
*/
val!: number;
/**
* Previous value.
*/
prevVal!: number;
/**
* Target element
*/
target!: TimelineElement;
/**
* Event source.
*/
source!: TimelineEventSource;
}
@@ -0,0 +1,11 @@
export interface TimelineScrollEvent {
args: MouseEvent;
/**
* Whether scroll was component or user initiated.
*/
scrollProgrammatically: boolean;
scrollLeft: number;
scrollTop: number;
scrollHeight: number;
scrollWidth: number;
}
@@ -0,0 +1,17 @@
import { TimelineSelectionEventSource } from '../../enums/timelineSelectionEventSource';
import { TimelineKeyframe } from '../../models/timelineKeyframe';
export class TimelineSelectedEvent {
/**
* Selected Keyframes list.
*/
selected: TimelineKeyframe[] = [];
/**
* Changed selection.
*/
changed: TimelineKeyframe[] = [];
/**
* Selection mode.
*/
mode: TimelineSelectionEventSource = TimelineSelectionEventSource.Keyframes;
}
@@ -0,0 +1,17 @@
import { TimelineBaseEvent } from './timelineBaseEvent';
import { TimelineEventSource } from '../../enums/timelineEventSource';
export class TimelineTimeChangedEvent extends TimelineBaseEvent {
/**
* New value to be set if not prevented.
*/
val = 0;
/**
* previous value, that actually set at the moment.
*/
prevVal = 0;
/**
* Source of the change.
*/
source: TimelineEventSource = TimelineEventSource.User;
}
@@ -0,0 +1,8 @@
/**
* Helper container. Cut bounds method results.
*/
export interface TimelineCutBoundsRectResults {
rect: DOMRect;
overlapY: boolean;
overlapX: boolean;
}
@@ -0,0 +1,33 @@
import { TimelineElementType } from '../enums/timelineElementType';
import { TimelineElementDragState } from './timelineElementDragState';
/**
* Information about current drag state.
*/
export class TimelineDraggableData {
/**
* Whether position was changed.
*/
changed = false;
/**
* Drag initial click target.
*/
target!: TimelineElementDragState;
/**
* Elements to be dragged.
* Can be multiple elements, but drag will be started with target.
*/
elements!: TimelineElementDragState[];
/**
* Dragging type.
*/
type: TimelineElementType = TimelineElementType.None;
/**
* Prev value.
*/
val!: number;
/**
* Prev value.
*/
prevVal!: number;
}
@@ -0,0 +1,34 @@
import { TimelineKeyframe } from '../models/timelineKeyframe';
import { TimelineElementType } from '../enums/timelineElementType';
import { TimelineRow } from '../models/timelineRow';
import { TimelineGroup } from '../models/timelineGroup';
/**
* Timeline clickable element.
*/
export interface TimelineElement {
type: TimelineElementType;
/**
* Timeline value,
*/
val: number;
/**
* Related keyframe model.
*/
keyframe?: TimelineKeyframe;
/**
* Related row model.
*/
row?: TimelineRow;
/**
* Current group.
*/
group?: string | TimelineGroup;
/**
* List of the selected, grouped, row keyframes.
*/
keyframes?: TimelineKeyframe[];
}
@@ -0,0 +1,12 @@
import { TimelineElement } from './timelineElement';
export interface TimelineElementDragState extends TimelineElement {
/**
* Value that preserved before dragging is started.
*/
startedVal: number;
/**
* Value before change.
*/
prevVal: number;
}
@@ -0,0 +1,15 @@
import { TimelinePoint } from './timelinePoint';
/**
* Mouse data helper container.
*/
export interface TimelineMouseData extends TimelinePoint {
/**
* Originated event args.
*/
args: TouchEvent | MouseEvent;
/**
* Click radius.
*/
radius?: number;
}
@@ -0,0 +1,22 @@
/**
* Timeline values interface.
*/
export interface TimelinePoint {
/**
* Value used for the visualization.
* It can be snapped or original.
*/
val: number;
/**
* Snapped value if snapping is enabled. In other case corresponding to original value.
*/
snapVal: number;
/**
* Unsnapped original value.
*/
originalVal: number;
/**
* Screen click point.
*/
pos: DOMPoint;
}
@@ -0,0 +1,7 @@
/**
* Timeline selectable element.
*/
export interface TimelineSelectable {
selected?: boolean;
selectable?: boolean;
}
@@ -0,0 +1,16 @@
import { TimelineKeyframe } from '../models/timelineKeyframe';
export interface TimelineSelectionResults {
/**
* Changed nodes.
*/
changed: TimelineKeyframe[];
/**
* Selected nodes.
*/
selected: TimelineKeyframe[];
/**
* Whether data was changed.
*/
selectionChanged: boolean;
}
@@ -0,0 +1,415 @@
import { TimelineOptions } from '../settings/timelineOptions';
import { TimelineRowStyle } from '../settings/styles/timelineRowStyle';
import { TimelineKeyframeShape } from '../enums/timelineKeyframeShape';
import { TimelineUtils } from './timelineUtils';
import { TimelineGroupStyle } from '../settings/styles/timelineGroupStyle';
import { TimelineKeyframe } from '../models/timelineKeyframe';
import { TimelineGroup } from '../models/timelineGroup';
import { TimelineRow } from '../models/timelineRow';
import { defaultGroupStyle } from '../settings/defaults/defaultGroupStyle';
import { defaultTimelineKeyframeStyle } from '../settings/defaults/defaultTimelineKeyframeStyle';
import { defaultTimelineRowStyle } from '../settings/defaults/defaultTimelineRowStyle';
const undefinedConst = typeof undefined;
// TODO: create merged style for each element instead of getting per property.
export class TimelineStyleUtils {
static getGroup(groupModel: TimelineGroup | string | null | undefined): TimelineGroup | null {
const style = groupModel;
if (style && typeof style === 'string') {
return null;
}
return (style || null) as TimelineGroup;
}
static getGroupStyle(groupModel: TimelineGroup | string | null | undefined): TimelineGroupStyle | null {
return TimelineStyleUtils.getGroup(groupModel)?.style || null;
}
static getFirstSet<T>(defaultValue: T, ...params: Array<T | undefined | null>): T {
return TimelineStyleUtils.getValue(defaultValue, false, ...params);
}
/**
* Get first value set or default.
* @param defaultValue default value in a case when no value is set.
* @param returnFalseIfAnyFalse - find first negative bool and return false.
* @param params collection of values to check.
* @returns value.
*/
static getValue<T>(defaultValue: T, returnFalseIfAnyFalse = false, ...params: Array<T | undefined | null>): T {
const valuesFound: T[] = [];
let found = false;
params.forEach((value: T | undefined | null) => {
if (found) {
return;
}
if (typeof value === undefinedConst) {
return;
} else if (typeof value === 'number') {
if (!TimelineUtils.isNumber(value)) {
return;
}
} else if (typeof value === 'boolean') {
valuesFound.push(value as T);
// No need to search for other values. First false is turning off current bool functionality.
if (returnFalseIfAnyFalse && value === false) {
found = true;
}
return;
} else if (!value) {
return;
}
valuesFound.push(value as T);
});
const toReturn = valuesFound && valuesFound.length > 0 ? valuesFound[0] : defaultValue;
if (found) {
return false as T;
}
return TimelineStyleUtils.getValueOrDefault(toReturn, defaultValue) as T;
}
static getValueOrDefault<T>(value: T, defaultValue: T): T | undefined {
if (typeof value === undefinedConst) {
return defaultValue;
} else if (typeof value == 'boolean') {
// variable is a boolean
return value as T;
} else if (typeof value == 'number') {
// variable is a boolean
if (value || value === 0) {
return value as T;
} else {
return defaultValue;
}
}
return value || defaultValue;
}
static keyframeWidth(
keyframe: TimelineKeyframe | null | undefined,
group: TimelineGroup | string | null | undefined,
rowStyle: TimelineRowStyle | null | undefined,
options: TimelineOptions | null | undefined,
): number | string {
return TimelineStyleUtils.getFirstSet(
// default value
defaultTimelineKeyframeStyle.width || '',
keyframe?.style?.width,
// exact style
TimelineStyleUtils.getGroupStyle(group)?.keyframesStyle?.width,
rowStyle?.keyframesStyle?.width,
rowStyle?.groupsStyle?.keyframesStyle?.width,
// global styles
options?.rowsStyle?.groupsStyle?.keyframesStyle?.width,
// default keyframe style
options?.rowsStyle?.keyframesStyle?.width,
);
}
static keyframeHeight(
keyframe: TimelineKeyframe | null | undefined,
group: TimelineGroup | string | null | undefined,
rowStyle: TimelineRowStyle | null | undefined,
options: TimelineOptions | null | undefined,
): number | string {
return TimelineStyleUtils.getFirstSet(
// default value
defaultTimelineKeyframeStyle.height || '',
keyframe?.style?.height,
// exact style
TimelineStyleUtils.getGroupStyle(group)?.keyframesStyle?.height,
rowStyle?.keyframesStyle?.height,
rowStyle?.groupsStyle?.keyframesStyle?.height,
// global styles
options?.rowsStyle?.groupsStyle?.keyframesStyle?.height,
// default keyframe style
options?.rowsStyle?.keyframesStyle?.height,
);
}
static keyframeShape(keyframe: TimelineKeyframe | null, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null, options: TimelineOptions | null): TimelineKeyframeShape {
const defaultValue = defaultTimelineKeyframeStyle.shape || TimelineKeyframeShape.Rhomb;
return TimelineStyleUtils.getFirstSet(
// default value
defaultValue,
// exact style
keyframe?.style?.shape,
// style set by keyframe group
TimelineStyleUtils.getGroupStyle(group)?.keyframesStyle?.shape,
// style from the keyframe group
rowStyle?.groupsStyle?.keyframesStyle?.shape,
// style set by keyframe style
rowStyle?.keyframesStyle?.shape,
// style set by keyframe group style, applied when group is set
group ? options?.rowsStyle?.groupsStyle?.keyframesStyle?.shape : undefined,
// Style set by global options
options?.rowsStyle?.keyframesStyle?.shape,
);
}
static keyframeFillColor(keyframe: TimelineKeyframe | null, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null, options: TimelineOptions | null): string {
const defaultValue = defaultTimelineKeyframeStyle.fillColor || '';
return TimelineStyleUtils.getFirstSet(
// default value
defaultValue,
// exact style
keyframe?.style?.fillColor,
// style set by keyframe group
TimelineStyleUtils.getGroupStyle(group)?.keyframesStyle?.fillColor,
// style from the keyframe group
rowStyle?.groupsStyle?.keyframesStyle?.fillColor,
// style set by keyframe style
rowStyle?.keyframesStyle?.fillColor,
// style set by keyframe group style, applied when group is set
group ? options?.rowsStyle?.groupsStyle?.keyframesStyle?.fillColor : undefined,
// Style set by global options
options?.rowsStyle?.keyframesStyle?.fillColor,
);
}
static keyframeSelectedFillColor(keyframe: TimelineKeyframe | null, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null, options: TimelineOptions | null): string {
const defaultValue = defaultTimelineKeyframeStyle.selectedFillColor || '';
return TimelineStyleUtils.getFirstSet(
// default value
defaultValue,
// exact style
keyframe?.style?.selectedFillColor,
// style set by keyframe group
TimelineStyleUtils.getGroupStyle(group)?.keyframesStyle?.selectedFillColor,
// style from the keyframe group
rowStyle?.groupsStyle?.keyframesStyle?.selectedFillColor,
// style set by keyframe style
rowStyle?.keyframesStyle?.selectedFillColor,
// style set by keyframe group style, applied when group is set
group ? options?.rowsStyle?.groupsStyle?.keyframesStyle?.selectedFillColor : undefined,
// Style set by global options
options?.rowsStyle?.keyframesStyle?.selectedFillColor,
);
}
static keyframeStrokeThickness(keyframe: TimelineKeyframe | null, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null, options: TimelineOptions | null): number {
const defaultValue = defaultTimelineKeyframeStyle.strokeThickness || 0;
return TimelineStyleUtils.getFirstSet(
// default value
defaultValue,
// exact style
keyframe?.style?.strokeThickness,
// style set by keyframe group
TimelineStyleUtils.getGroupStyle(group)?.keyframesStyle?.strokeThickness,
// style from the keyframe group
rowStyle?.groupsStyle?.keyframesStyle?.strokeThickness,
// style set by keyframe style
rowStyle?.keyframesStyle?.strokeThickness,
// style set by keyframe group style, applied when group is set
group ? options?.rowsStyle?.groupsStyle?.keyframesStyle?.strokeThickness : undefined,
// Style set by global options
options?.rowsStyle?.keyframesStyle?.strokeThickness,
);
}
static keyframeStrokeColor(keyframe: TimelineKeyframe | null, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null, options: TimelineOptions | null): string {
const defaultValue = defaultTimelineKeyframeStyle.strokeColor || '';
return TimelineStyleUtils.getFirstSet(
// default value
defaultValue,
// exact style
keyframe?.style?.strokeColor,
// style set by keyframe group
TimelineStyleUtils.getGroupStyle(group)?.keyframesStyle?.strokeColor,
// style from the keyframe group
rowStyle?.groupsStyle?.keyframesStyle?.strokeColor,
// style set by keyframe style
rowStyle?.keyframesStyle?.strokeColor,
// style set by keyframe group style, applied when group is set
group ? options?.rowsStyle?.groupsStyle?.keyframesStyle?.strokeColor : undefined,
// Style set by global options
options?.rowsStyle?.keyframesStyle?.strokeColor,
);
}
static keyframeSelectedStrokeColor(keyframe: TimelineKeyframe | null, group: TimelineGroup | string | null, rowStyle: TimelineRowStyle | null, options: TimelineOptions | null): string {
const defaultValue = defaultTimelineKeyframeStyle.selectedStrokeColor || '';
return TimelineStyleUtils.getFirstSet(
// default value
defaultValue,
// exact style
keyframe?.style?.selectedStrokeColor,
// style set by keyframe group
TimelineStyleUtils.getGroupStyle(group)?.keyframesStyle?.selectedStrokeColor,
// style from the keyframe group
rowStyle?.groupsStyle?.keyframesStyle?.selectedStrokeColor,
// style set by keyframe style
rowStyle?.keyframesStyle?.selectedStrokeColor,
// style set by keyframe group style, applied when group is set
group ? options?.rowsStyle?.groupsStyle?.keyframesStyle?.selectedStrokeColor : undefined,
// Style set by global options
options?.rowsStyle?.keyframesStyle?.selectedStrokeColor,
);
}
static groupHeight(options: TimelineOptions | null | undefined, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null | undefined): number | string {
return TimelineStyleUtils.getFirstSet(
// default value
defaultGroupStyle.height || 'auto',
// exact group style
TimelineStyleUtils.getGroupStyle(group)?.height,
// Row row style
rowStyle?.groupsStyle?.height,
// global styles
options?.rowsStyle?.groupsStyle?.height,
);
}
static groupMarginTop(options: TimelineOptions | null | undefined, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null | undefined): number | string {
return TimelineStyleUtils.getFirstSet(
// default value
defaultGroupStyle.marginTop || 'auto',
// exact style
TimelineStyleUtils.getGroupStyle(group)?.marginTop,
// Row row style
rowStyle?.groupsStyle?.marginTop,
// global styles
options?.rowsStyle?.groupsStyle?.marginTop,
);
}
static groupFillColor(options: TimelineOptions | null | undefined, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null | undefined): string {
return TimelineStyleUtils.getFirstSet(
// default value
defaultGroupStyle.fillColor || '',
// exact style
TimelineStyleUtils.getGroupStyle(group)?.fillColor,
// Row row style
rowStyle?.groupsStyle?.fillColor,
// global styles
options?.rowsStyle?.groupsStyle?.fillColor,
);
}
static groupStrokeColor(options: TimelineOptions | null | undefined, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null | undefined): string {
return TimelineStyleUtils.getFirstSet(
// default value
defaultGroupStyle.strokeColor || '',
// exact style
TimelineStyleUtils.getGroupStyle(group)?.strokeColor,
// Row row style
rowStyle?.groupsStyle?.strokeColor,
// global styles
options?.rowsStyle?.groupsStyle?.strokeColor,
);
}
static groupStrokeThickness(options: TimelineOptions | null | undefined, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null | undefined): number {
return (
TimelineStyleUtils.getFirstSet(
// default value
defaultGroupStyle.strokeThickness || '',
// exact style
TimelineStyleUtils.getGroupStyle(group)?.strokeThickness,
// Row row style
rowStyle?.groupsStyle?.strokeThickness,
// global styles
options?.rowsStyle?.groupsStyle?.strokeThickness,
) || 0
);
}
static groupsRadii(
options: TimelineOptions | null | undefined,
group: TimelineGroup | string | null | undefined,
rowStyle: TimelineRowStyle | null | undefined,
): number | DOMPointInit | Iterable<number | DOMPointInit> {
return (
TimelineStyleUtils.getFirstSet(
// default value
defaultGroupStyle.radii || '',
// exact style
TimelineStyleUtils.getGroupStyle(group)?.radii,
// Row row style
rowStyle?.groupsStyle?.radii,
// global styles
options?.rowsStyle?.groupsStyle?.radii,
) || 0
);
}
static groupText(options: TimelineOptions | null | undefined, group: TimelineGroup | string | null | undefined, rowStyle: TimelineRowStyle | null | undefined): TimelineGroupStyle["text"] {
return TimelineStyleUtils.getFirstSet(
// default value
defaultGroupStyle.text || {
label:""
},
// exact style
TimelineStyleUtils.getGroupStyle(group)?.text,
// Row row style
rowStyle?.groupsStyle?.text,
// global styles
options?.rowsStyle?.groupsStyle?.text,
);
}
/**
* Get current row height from styles
*/
static getRowHeight(rowStyle: TimelineRowStyle | null, options: TimelineOptions | null): number {
const defaultValue = defaultTimelineRowStyle.height || 0;
return TimelineStyleUtils.getFirstSet(
// default value
defaultValue,
// exact style
rowStyle?.height,
// Style set by global options
options?.rowsStyle?.height,
);
}
static getRowMarginBottom(rowStyle: TimelineRowStyle | null, options: TimelineOptions | null): number {
const defaultValue = defaultTimelineRowStyle.marginBottom || 0;
return TimelineStyleUtils.getFirstSet(
// default value
defaultValue,
// exact style
rowStyle?.marginBottom,
// Style set by global options
options?.rowsStyle?.marginBottom,
);
}
static getRowFillColor(rowStyle: TimelineRowStyle | null, options: TimelineOptions | null): string {
const defaultValue = defaultTimelineRowStyle.fillColor || '';
return TimelineStyleUtils.getFirstSet(
// default value
defaultValue,
// exact style
rowStyle?.fillColor,
// Style set by global options
options?.rowsStyle?.fillColor,
);
}
static headerHeight(options: TimelineOptions | null, defaultRowHeight = 30): number {
return options?.headerHeight || defaultRowHeight;
}
static keyframeDraggable(keyframe: TimelineKeyframe | null, group: TimelineGroup | string | null, row: TimelineRow | null, options: TimelineOptions | null, defaultValue = true): boolean {
const findFirstNegativeBool = true;
const boolResult = TimelineStyleUtils.getValue<boolean>(
defaultValue,
findFirstNegativeBool,
// Keyframe settings
keyframe?.draggable,
// Group settings
TimelineStyleUtils.getGroup(group)?.keyframesDraggable,
// Row settings
row?.keyframesDraggable,
// Start from global settings first.
options?.keyframesDraggable,
);
return boolResult;
}
static groupDraggable(group: TimelineGroup | string | null | undefined, row: TimelineRow | null, options: TimelineOptions): boolean {
const findFirstNegativeBool = true;
const boolResult = TimelineStyleUtils.getValue<boolean>(
true,
findFirstNegativeBool,
// Group settings
TimelineStyleUtils.getGroup(group)?.draggable,
// Row settings
row?.groupsDraggable,
// Start from global settings first.
options?.groupsDraggable,
);
return boolResult;
}
}
@@ -0,0 +1,304 @@
import { TimelineRanged } from '../models/timelineRanged';
import { TimelineOptions } from '../settings/timelineOptions';
const defaultDenominators = [1, 2, 5, 10];
export class TimelineUtils {
static drawLine(ctx: CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number): void {
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
}
/**
* Check is valid number.
*/
static isNumber(val?: number | null): boolean {
if (typeof val === 'number' && (val || val === 0) && !isNaN(val) && Number.isFinite(val)) {
return true;
}
return false;
}
static deleteElement<T>(array: Array<T>, element: T): Array<T> {
const index: number = array.indexOf(element);
if (index !== -1) {
return array.splice(index, 1);
}
return array;
}
/**
* Check rectangle overlap x,y
*/
static isOverlap(x: number, y: number, rectangle: DOMRect): boolean {
if (!rectangle) {
return false;
}
if (rectangle.x <= x && rectangle.x + rectangle.width >= x && rectangle.y <= y && rectangle.y + rectangle.height >= y) {
return true;
}
return false;
}
/**
* Find beautiful step for the header line gauge.
*/
static findGoodStep(originalStep: number, divisionCheck = 0, denominators: number[] = defaultDenominators): number {
if (!denominators) {
denominators = defaultDenominators;
}
if (originalStep <= 0 || isNaN(originalStep) || !Number.isFinite(originalStep)) {
return originalStep;
}
let step = originalStep;
let lastDistance:number | null = null;
const pow = TimelineUtils.getPowArgument(originalStep);
for (let i = 0; i < denominators.length; i++) {
const denominator = denominators[i];
const calculatedStep = denominator * Math.pow(10, pow);
if (divisionCheck && divisionCheck % calculatedStep != 0) {
continue;
}
const distance = TimelineUtils.getDistance(originalStep, calculatedStep);
if (distance == 0 || (distance <= 0.1 && pow > 0)) {
lastDistance = distance;
step = calculatedStep;
break;
} else if (!lastDistance || lastDistance > distance) {
lastDistance = distance;
step = calculatedStep;
}
}
return step;
}
/**
* Keep value in min, max bounds.
*/
static keepInBounds(value: number, min: number | null | undefined = null, max: number | null | undefined = null): number {
if (TimelineUtils.isNumber(value)) {
if (TimelineUtils.isNumber(min) && (min || min === 0)) {
value = Math.max(value, min);
}
if (TimelineUtils.isNumber(max) && (max || max === 0)) {
value = Math.min(value, max);
}
}
return value;
}
static setMinMax(to: TimelineRanged, from: TimelineRanged | null, shrink = false): TimelineRanged {
if (!from || !to) {
return to;
}
const fromMin = from ? from.min : Number.NaN;
const toMin = to.min;
const isFromMinNumber = (fromMin || fromMin === 0) && TimelineUtils.isNumber(fromMin);
const isToMinNumber = (toMin || toMin === 0) && TimelineUtils.isNumber(toMin);
// get absolute min and max bounds:
if (isFromMinNumber && isToMinNumber) {
to.min = shrink ? Math.min(fromMin, toMin) : Math.max(fromMin, toMin);
} else if (isFromMinNumber) {
to.min = fromMin;
}
const fromMax = from ? from.max : Number.NaN;
const toMax = to.max;
const isFromMaxNumber = (fromMax || fromMax === 0) && TimelineUtils.isNumber(fromMax);
const isToMaxNumber = (toMax || toMax === 0) && TimelineUtils.isNumber(toMax);
if (isFromMaxNumber && isToMaxNumber) {
to.max = shrink ? Math.max(fromMax, toMax) : Math.min(fromMax, toMax);
} else if (isFromMaxNumber) {
to.max = fromMax;
}
return to;
}
static shrinkSelf(rect: DOMRect, value: number): DOMRect {
if (!rect) {
return rect;
}
rect.x -= value;
rect.y -= value;
rect.width += value;
rect.height += value;
return rect;
}
/**
* Check whether rectangle intersects another rectangle
*/
static isRectIntersects(rect: DOMRect, rect2: DOMRect, touch = false): boolean {
if (!rect || !rect2) {
console.log('Rectangles cannot be empty');
return false;
}
const right = rect2.x + rect2.width;
const bottom = rect2.y + rect2.height;
if (touch) {
if (
// Left
rect.x <= right &&
// Right
rect2.x <= rect.x + rect.width &&
// Top
rect.y <= bottom &&
// Bottom
rect2.y <= rect.y + rect.height
) {
return true;
} else {
return false;
}
} else {
if (
// Left
rect.x < right &&
// Right
rect2.x < rect.x + rect.width &&
// Top
rect.y < bottom &&
// Bottom
rect2.y < rect.y + rect.height
) {
return true;
} else {
return false;
}
}
}
static getDistance(x1: number, y1: number, x2?: number, y2?: number): number {
if (x2 != undefined && y2 != undefined) {
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
} else {
return Math.abs(x1 - y1);
}
}
/**
* Get sign of the number. 1 or -1.
*/
static sign(p: number): number {
return p >= 0 ? 1 : -1;
}
/**
* Clear browser text selection.
*/
static clearBrowserSelection(): void {
if (!window) {
return;
}
if (window.getSelection) {
const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
}
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const doc: any = window.document;
if (doc.selection) {
doc.selection.empty();
}
}
}
static getPowArgument(toCheck: number): number {
if (!toCheck || toCheck === 0 || !isFinite(toCheck)) {
return 1;
}
// some optimization for numbers:
if (toCheck >= 10 && toCheck < 100) {
return 1;
} else if (toCheck >= 100 && toCheck < 1000) {
return 2;
} else if (toCheck >= 1000 && toCheck < 10000) {
return 3;
}
toCheck = Math.abs(toCheck);
let category = 0;
const s = this.sign(toCheck);
if (toCheck > 1) {
while (toCheck >= 1) {
toCheck = Math.floor(toCheck / 10.0);
category++;
}
return s * category - 1;
} else if (toCheck > 0.0) {
// Get number of zeros before the number.
const zerosCount = Math.floor(Math.log(toCheck) / Math.log(10) + 1) - 1;
return zerosCount;
} else {
return 1;
}
}
static deepClone = <T>(previousOptions: T): T => {
return JSON.parse(JSON.stringify(previousOptions)) as T;
};
static cloneOptions = (previousOptions: TimelineOptions): TimelineOptions => {
const clonedValue = JSON.parse(
JSON.stringify(previousOptions, (key, value) => {
// No need to clone HTML element passed as ID.
return key === 'id' ? undefined : value;
}),
);
clonedValue.id = previousOptions.id;
return clonedValue;
};
/**
* Merge options. New keys will be added.
*/
static mergeOptions(previousOptions: TimelineOptions, newOptions: TimelineOptions): TimelineOptions {
newOptions = newOptions || ({} as TimelineOptions);
// Apply incoming options to default. (override default)
// Deep clone default options:
const toArg = TimelineUtils.cloneOptions(previousOptions);
// Merge options with the default.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mergeOptionsDeep = (to: any, from: any): void => {
if (!to || !from) {
return;
}
// eslint-disable-next-line prefer-const
for (let key in from) {
if (Object.prototype.hasOwnProperty.call(from, key)) {
if (from[key] !== undefined) {
if (typeof from[key] === 'object') {
if (key === 'id') {
if (from[key] && from[key] !== to[key]) {
to[key] = from[key];
}
} else {
if (!to[key]) {
to[key] = from[key];
} else {
mergeOptionsDeep(to[key], from[key]);
}
}
} else {
to[key] = from[key];
}
}
}
}
};
mergeOptionsDeep(toArg, newOptions);
return toArg;
}
/**
* Format numbers with len
*/
static timePadZero(num: number, len = 2): string {
let str = String(num);
const threshold = Math.pow(10, len - 1);
if (num < threshold) {
while (String(threshold).length > str.length) {
str = `0${num}`;
}
}
return str;
}
}
@@ -0,0 +1,27 @@
import { TimelineGroup } from '../models/timelineGroup';
import { TimelineKeyframeViewModel } from './timelineKeyframeViewModel';
export interface TimelineGroupViewModel {
/**
* Size of the element in screen coordinates.
*/
size: DOMRect | null;
/**
* From value in the timeline keyframes collection.
*/
min: number;
/**
* To value in the timeline keyframes collection.
*/
max: number;
/**
* Related Group Model.
*/
groupModel: string | TimelineGroup;
/**
* Related Grouped keyframes view models.
*/
keyframesViewModels: TimelineKeyframeViewModel[];
}
@@ -0,0 +1,27 @@
import { TimelineKeyframeShape } from '../enums/timelineKeyframeShape';
import { TimelineKeyframe } from '../models/timelineKeyframe';
import { TimelineGroupViewModel } from './timelineGroupViewModel';
import { TimelineRowViewModel } from './timelineRowViewModel';
/**
* Timeline calculated view model.
*/
export interface TimelineKeyframeViewModel {
/**
* Size of the keyframe.
*/
size: DOMRect;
shape: TimelineKeyframeShape;
/**
* Related keyframe model.
*/
model: TimelineKeyframe;
/**
* Related calculated parent row view model.
*/
rowViewModel: TimelineRowViewModel;
/**
* Related calculated parent group view model.
*/
groupViewModel: TimelineGroupViewModel;
}
@@ -0,0 +1,33 @@
import { TimelineRow } from '../models/timelineRow';
import { TimelineGroupViewModel } from './timelineGroupViewModel';
import { TimelineKeyframeViewModel } from './timelineKeyframeViewModel';
export interface TimelineRowViewModel {
/**
* Screen coordinates of the element.
*/
size: DOMRect;
/**
* Related row model.
*/
model: TimelineRow;
/**
* Current row index.
*/
index: number;
/**
* Row margin bottom
*/
marginBottom: number;
/**
* Collection of the keyframes groups view models exists in the current row.
*/
groupsViewModels: TimelineGroupViewModel[];
/**
* All keyframes in the current row.
*/
keyframesViewModels: TimelineKeyframeViewModel[];
min: number | null;
max: number | null;
}
@@ -0,0 +1,20 @@
import { TimelineKeyframeViewModel } from './timelineKeyframeViewModel';
import { TimelineRowViewModel } from './timelineRowViewModel';
export interface TimelineViewModel {
/**
* Screen coordinates of the element.
*/
size: DOMRect;
/**
* Keyframes view models.
*/
keyframesViewModels: TimelineKeyframeViewModel[];
/**
* Collection of the rows sizes.
*/
rowsViewModels: TimelineRowViewModel[];
min: number | null;
max: number | null;
}