Two halves of one pass; TODO-FIXES.md lists every finding. The audit fixed, among ~90 items: MP4 export dead-ending and locking the UI behind a stuck "Downloading..." button; height keyframes storing the width; shadow defaults and keyframes storing undefined because fabric's get() is not a path getter; every letter animation writing to the last letter; keyframe times drifting on each drag because data-time was layer-relative on expanded rows; O(n^2 log n) playback, now indexed once per frame; and a save() that rebuilt the record canvas on every edit, now debounced. Exports also lost audio-layer sound outright: MediaRecorder records only the first audio track, so every source now mixes through one AudioContext into one destination. The new half is src/js/render.js, an offline renderer. Real-time capture was the root cause of dropped frames on heavy scenes and smeared video layers: a bare currentTime assignment is async, so drawing straight after it captures the previous frame. Each frame is now seeked, awaited on 'seeked', drawn, and pushed through a VideoEncoder; audio is mixed in one OfflineAudioContext pass and encoded to Opus. webm-writer2.js gained a second Opus track and lost three bugs, including a MAX_CLUSTER_DURATION_MSEC of ~58 days that overflowed the signed 16-bit block timecode past ~32s. Real-time capture remains the fallback: the hand-rolled muxer's output is decoded in a <video> element before being handed over, and any failure returns null so record() falls back transparently. test/webm-muxer.test.js parses the muxer output with an EBML reader in plain Node. It caught the fixed-256-byte header overflow that inspection missed. Not verified in a browser: node --check passes on every script and the muxer test passes, but a running Chrome held the Playwright profile lock, so the manual pass listed at the end of TODO-FIXES.md is still outstanding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1111 lines
45 KiB
JavaScript
1111 lines
45 KiB
JavaScript
var API_KEY = 'PIXABAY_API';
|
|
var GOOGLE_FONTS_API_KEY = 'GOOGLE_FONTS_API_KEY';
|
|
|
|
// for legacy browsers
|
|
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
var oldsrc, oldobj;
|
|
var oldtimelinepos;
|
|
var speed = 1;
|
|
var page = 1;
|
|
var checkstatus = false;
|
|
let db = new Localbase('db');
|
|
var wip = false;
|
|
var paused = true;
|
|
var currenttime = 0;
|
|
var timelinetime = 5;
|
|
const offset_left = 20;
|
|
var duration = 30000;
|
|
var keyframes = [];
|
|
var p_keyframes = [];
|
|
var props = [
|
|
'left',
|
|
'top',
|
|
'scaleX',
|
|
'scaleY',
|
|
'width',
|
|
'height',
|
|
'angle',
|
|
'opacity',
|
|
'fill',
|
|
'strokeWidth',
|
|
'stroke',
|
|
'shadow.color',
|
|
'shadow.opacity',
|
|
'shadow.offsetX',
|
|
'shadow.offsetY',
|
|
'shadow.blur',
|
|
'charSpacing',
|
|
'lineHeight',
|
|
];
|
|
var objects = [];
|
|
var o_slider, o_letter_slider, o_line_slider;
|
|
var colormode = 'fill';
|
|
var spaceDown = false;
|
|
var selectedkeyframe;
|
|
var undo = [];
|
|
var undoarr = [];
|
|
var redo = [];
|
|
var groups = [];
|
|
var redoarr = [];
|
|
var state;
|
|
var statearr = [];
|
|
var recording = false;
|
|
var canvasrecord;
|
|
var clipboard;
|
|
var focus = false;
|
|
var editingpanel = false;
|
|
var files = [];
|
|
var re = /(?:\.([^.]+))?$/;
|
|
var filelist = [];
|
|
var spacehold = false;
|
|
var spacerelease = false;
|
|
var tempselection;
|
|
var line_h, line_v;
|
|
var tempgroup = [];
|
|
var editinggroup = false;
|
|
var tempgroupid;
|
|
var fontPicker;
|
|
var fonts = [];
|
|
var seeking = false;
|
|
var setting = false;
|
|
var handtool = false;
|
|
var canvasx = 0;
|
|
var canvasy = 0;
|
|
var overCanvas = false;
|
|
var draggingPanel = false;
|
|
var cropping = false;
|
|
var cropobj;
|
|
var cropscalex;
|
|
var cropscaley;
|
|
var croptop;
|
|
var cropleft;
|
|
var layer_count = 1;
|
|
var lockmovement = false;
|
|
var shiftx = 0;
|
|
var shifty = 0;
|
|
var editinglayer = false;
|
|
var editingproject = false;
|
|
var shiftkeys = [];
|
|
var shiftdown = false;
|
|
var cliptype = 'object';
|
|
var chromaslider, noiseslider, blurslider;
|
|
var isChrome =
|
|
window.chrome && Object.values(window.chrome).length !== 0;
|
|
var eyeDropper;
|
|
if (isChrome && typeof EyeDropper !== 'undefined') {
|
|
eyeDropper = new EyeDropper();
|
|
}
|
|
var presets = [
|
|
{
|
|
name: 'Dribbble shot',
|
|
id: 'dribbble',
|
|
width: 1600,
|
|
height: 1200,
|
|
},
|
|
{ name: 'Facebook post', id: 'facebook', width: 1280, height: 720 },
|
|
{
|
|
name: 'Facebook ad',
|
|
id: 'facebook-ad',
|
|
width: 1080,
|
|
height: 1080,
|
|
},
|
|
{ name: 'Youtube video', id: 'youtube', width: 1920, height: 1080 },
|
|
{
|
|
name: 'Instagram video',
|
|
id: 'instagram-id',
|
|
width: 1080,
|
|
height: 1920,
|
|
},
|
|
{
|
|
name: 'Instagram stories',
|
|
id: 'instagram-stories',
|
|
width: 1080,
|
|
height: 1920,
|
|
},
|
|
{ name: 'Twitter video', id: 'twitter', width: 1280, height: 720 },
|
|
{ name: 'Snapchat ad', id: 'snapchat', width: 1080, height: 1920 },
|
|
{
|
|
name: 'LinkedIn video',
|
|
id: 'linkedin',
|
|
width: 1920,
|
|
height: 1080,
|
|
},
|
|
{
|
|
name: 'Product Hunt thumbnail',
|
|
id: 'product-hunt',
|
|
width: 600,
|
|
height: 600,
|
|
},
|
|
{
|
|
name: 'Pinterest ad',
|
|
id: 'pinterest',
|
|
width: 1080,
|
|
height: 1920,
|
|
},
|
|
];
|
|
var activepreset = 'custom';
|
|
var uploaded_images = [];
|
|
var uploaded_videos = [];
|
|
var uploading = false;
|
|
var background_audio = false;
|
|
var temp_audio = false;
|
|
var background_key;
|
|
var sliders = [];
|
|
var hovertime = 0;
|
|
var animatedtext = [];
|
|
|
|
// Get list of fonts.
|
|
// Both API keys are placeholders in the repository - replace them to enable
|
|
// the Google Fonts list and the Pixabay browser.
|
|
const HAS_FONTS_KEY =
|
|
GOOGLE_FONTS_API_KEY && GOOGLE_FONTS_API_KEY != 'GOOGLE_FONTS_API_KEY';
|
|
const HAS_PIXABAY_KEY = API_KEY && API_KEY != 'PIXABAY_API';
|
|
if (HAS_FONTS_KEY) {
|
|
$.ajax({
|
|
url:
|
|
'https://www.googleapis.com/webfonts/v1/webfonts?key=' +
|
|
GOOGLE_FONTS_API_KEY +
|
|
'&sort=alpha',
|
|
type: 'GET',
|
|
dataType: 'json', // added data type
|
|
success: function (response) {
|
|
response.items.forEach(function (item) {
|
|
fonts.push(item.family);
|
|
});
|
|
},
|
|
error: function () {
|
|
console.warn('Could not load the Google Fonts list');
|
|
},
|
|
});
|
|
}
|
|
|
|
// Panel variants
|
|
const canvas_panel =
|
|
'<div id="canvas-properties" class="panel-section"><p class="property-title">Canvas settings</p><table><tr><th class="name-col">Preset</th><th class="value-col"><select id="preset"></select></th></tr><tr><th class="name-col">Size</th><th class="value-col"><div id="canvas-w" class="property-input" data-label="W"><input type="number" min=1 value=1000></div><div id="canvas-h" class="property-input" data-label="H"><input type="number" value=1000 min=1></div></th></tr><tr><th class="name-col">Color</th><th class="value-col"><div id="canvas-color" class="object-color"><div id="color-side" class="color-picker"></div><input value="#FFFFFF" disabled="disabled"></div><div id="canvas-color-opacity" class="property-input" data-label="%"><input type="number" value=100></div></th></tr><tr><th class="name-col">Duration</th><th class="value-col" id="duration-cell"><div id="canvas-duration" class="property-input" data-label="s"><input type="number" value=15.00></div></th></tr></table></div>';
|
|
const object_panel =
|
|
'<div id="layout-properties" class="panel-section"><p class="property-title">Layout</p><table><tr><th class="name-col">Position</th><th class="value-col"><div id="object-x" class="property-input" data-label="X"><input type="number" value=1000></div><div id="object-y" class="property-input" data-label="Y"><input value=1000 type="number"></div></th></tr><tr><th class="name-col">Size</th><th class="value-col"><div id="object-w" class="property-input" data-label="W"><input type="number" min=1 value=1000></div><div id="object-h" class="property-input" data-label="H"><input type="number" value=1000 min=1></div></th></tr><tr><th class="name-col">Rotation</th><th class="value-col" id="duration-cell"><div id="object-r" class="property-input" data-label="°"><input type="number" min=0 max=360 value=0></div></th></tr></table></div>';
|
|
const back_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Layer</p><table><tr><th class="name-col">Opacity</th><th class="value-col"><div id="select-opacity"></div><div id="object-o" class="property-input" data-label="%"><input type="number" value=100></div></th></tr><tr><th class="name-col">Mask</th><th class="value-col"><select id="masks"><option value="none">None</option></select></th></tr></table></div>';
|
|
const image_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Layer</p><table><tr><th class="name-col">Opacity</th><th class="value-col"><div id="select-opacity"></div><div id="object-o" class="property-input" data-label="%"><input type="number" value=100></div></th></tr><tr><th class="name-col">Mask</th><th class="value-col"><select id="masks"><option value="none">None</option></select></th></tr></table></div>';
|
|
const selection_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Layer</p><table><tr><th class="name-col">Opacity</th><th class="value-col"><div id="select-opacity"></div><div id="object-o" class="property-input" data-label="%"><input type="number" value=100></div></th></tr><tr><th class="name-col">Group</th><th class="value-col"><div id="group-objects">Group selection</div></th></tr></table></div>';
|
|
const group_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Layer</p><table><tr><th class="name-col">Opacity</th><th class="value-col"><div id="select-opacity"></div><div id="object-o" class="property-input" data-label="%"><input type="number" value=100></div></th></tr><tr><th class="name-col">Mask</th><th class="value-col"><select id="masks"><option value="none">None</option></select></th></tr><tr><th class="name-col">Group</th><th class="value-col"><div id="ungroup-objects">Ungroup selection</div></th></tr></table></div>';
|
|
const other_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Layer</p><table><tr><th class="name-col">Opacity</th><th class="value-col"><div id="select-opacity"></div><div id="object-o" class="property-input" data-label="%"><input type="number" value=100></div></th></tr><tr><th class="name-col">Mask</th><th class="value-col"><select id="masks"><option value="none">None</option></select></th></tr></table></div>';
|
|
const shape_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Rectangle</p><table><tr><th class="name-col">Color</th><th class="value-col"><div id="object-color-fill" class="object-color"><div id="color-fill-side" class="color-picker"></div><input value="#FFFFFF" disabled="disabled"></div><div id="object-color-fill-opacity" class="property-input" data-label="%"><input type="number" value=100></div></th></tr><tr><th class="name-col">Radius</th><th class="value-col" id="duration-cell"><div id="object-corners" class="property-input" data-label="px"><input type="number" value=0 min=0></div></th></tr></table></div>';
|
|
const path_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Shape</p><table><tr><th class="name-col">Color</th><th class="value-col"><div id="object-color-fill" class="object-color"><div id="color-fill-side" class="color-picker"></div><input value="#FFFFFF" disabled="disabled"></div><div id="object-color-fill-opacity" class="property-input" data-label="%"><input type="number" value=100></div></th></tr></table></div>';
|
|
const text_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Text</p><table><tr><th class="name-col">Font</th><th class="value-col"><select id="font-picker"></select></th></tr><tr><th class="name-col">Align</th><th class="value-col"><div class="align-text" id="align-text-left"><img src="assets/align-text-left.svg"></div><div class="align-text" id="align-text-center"><img src="assets/align-text-center.svg"></div><div class="align-text" id="align-text-right"><img src="assets/align-text-right.svg"></div><div class="align-text" id="align-text-justify"><img src="assets/align-text-justify.svg"></div></th></tr><tr><th class="name-col">Format</th><th class="value-col"><div class="format-text" id="format-bold"><img src="assets/bold.svg"></div><div class="format-text" id="format-italic"><img src="assets/italic.svg"></div><div class="format-text" id="format-underline"><img src="assets/underline.svg"></div><div class="format-text" id="format-strike"><img src="assets/strike.svg"></div></th></tr><tr><th class="name-col">Color</th><th class="value-col"><div id="object-color-fill" class="object-color"><div id="color-fill-side" class="color-picker"></div><input value="#FFFFFF" disabled="disabled"></div><div id="object-color-fill-opacity" class="property-input" data-label="%"><input type="number" value=100></div></th></tr><tr><th class="name-col">Letter</th><th class="value-col"><div id="select-letter"></div><div id="text-h" class="property-input" data-label="%"><input type="number" value=1></div></th></tr><tr><th class="name-col">Line</th><th class="value-col"><div id="select-line"></div><div id="text-v" class="property-input" data-label="%"><input type="number" value=1></div></th></tr></table></div>';
|
|
const stroke_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Stroke</p><table><tr><th class="name-col">Type</th><th class="value-col left-col"><div class="line-join" id="miter"><img src="assets/miter.svg"></div><div class="line-join" id="bevel"><img src="assets/bevel.svg"></div><div class="line-join" id="round"><img src="assets/round.svg"></div><div class="line-join" id="small-dash"><img src="assets/dash2.svg"></div></th></tr><tr><th class="name-col">Color</th><th class="value-col"><div id="object-color-stroke" class="object-color"><div id="color-stroke-side" class="color-picker"></div><input value="#FFFFFF" disabled="disabled"></div><div id="object-color-stroke-opacity" class="property-input" data-label="%"><input type="number" value=100></div></th></tr><tr><th class="name-col">Width</th><th class="value-col" id="duration-cell"><div id="object-stroke" class="property-input" data-label="px"><input type="number" min=0 value=0></div></th></tr></table></div>';
|
|
const shadow_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Shadow</p><table><tr><th class="name-col">Offset</th><th class="value-col"><div id="object-shadow-x" class="property-input" data-label="X"><input type="number" value=0></div><div id="object-shadow-y" class="property-input" data-label="Y"><input value=0 type="number"></div></th></tr><tr><th class="name-col">Color</th><th class="value-col"><div id="object-color-shadow" class="object-color"><div id="color-shadow-side" class="color-picker"></div><input value="#FFFFFF" disabled="disabled"></div><div id="object-color-shadow-opacity" class="property-input" data-label="%"><input type="number" value=100></div></th></tr><tr><th class="name-col">Blur</th><th class="value-col" id="duration-cell"><div id="object-blur" class="property-input" data-label="px"><input type="number" value=0 min=0></div></th></tr></table></div>';
|
|
const image_more_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Image</p><div id="image-buttons"><div id="filters-button"><img src="assets/filters.svg"> Edit filters</div><div id="crop-image"><img src="assets/crop-icon.svg">Crop image</div></div></div></hr>';
|
|
const video_more_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Video</p><div id="image-buttons"><div id="filters-button" class="filters-video"><img src="assets/filters.svg"> Edit filters</div></div></div></hr>';
|
|
const animated_text_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Text</p><table><tr><th class="name-col">Content</th><th class="value-col" id="duration-cell"><div id="animated-text" class="property-input" data-label=""><input id="animatedinput" type="text" value="text"><div id="animatedset">Set</div></div></th></tr><tr><th class="name-col">Font</th><th class="value-col"><select id="font-picker"></select></th></tr><tr><th class="name-col">Color</th><th class="value-col"><div id="text-color" class="object-color"><div id="color-text-side" class="color-picker"></div><input value="#FFFFFF" disabled="disabled"></div><div id="color-text-opacity" class="property-input" data-label="%"><input type="number" value=100></div></th></tr></table></div>';
|
|
const start_animation_panel =
|
|
'<hr><div id="back-properties" class="panel-section"><p class="property-title">Start animation</p><table><tr><th class="name-col">Preset</th><th class="value-col"><select id="preset-picker"></select></th></tr><tr><th class="name-col">Easing</th><th class="value-col"><select id="easing-picker"><option value="linear">Linear</option><option value="easeInQuad">Ease in</option><option value="easeOutQuad">Ease out</option><option value="easeinOutQuad">Ease in-out</option><option value="easeOutInQuad">Ease out-in</option><option value="easeInBounce">Ease in bounce</option><option value="easeOutBounce">Ease out bounce</option><option value="easeinOutBounce">Ease in-out bounce</option><option value="easeOutInBouce">Ease out-in bounce</option><option value="easeOutInBouce">Ease out-in bounce</option><option value="easeInSine">Ease in sine</option><option value="easeOutSine">Ease out sine</option><option value="easeinOutSine">Ease in-out sine</option><option value="easeOutInSine">Ease out-in sine</option><option value="easeOutInSine">Ease out-in sine</option><option value="easeInCubic">Ease in cubic</option><option value="easeOutCubic">Ease out cubic</option><option value="easeinOutCubic">Ease in-out cubic</option><option value="easeOutInCubic">Ease out-in cubic</option><option value="easeOutInCubic">Ease out-in cubic</option></select></th></tr><tr><th class="name-col">Order</th><th class="value-col"><div id="order-toggle"><div id="order-backward" class="order-toggle-item">Backward</div><div id="order-forward" class="order-toggle-item order-toggle-item-active">Forward</div></div></th></tr><tr><th class="name-col">Order</th><th class="value-col"><div id="order-toggle"><div id="type-letters" class="order-toggle-item-2">Letters</div><div id="type-words" class="order-toggle-item-2 order-toggle-item-active-2">Words</div></div></th></tr><tr><th class="name-col">Duration</th><th class="value-col" id="duration-cell"><div id="animated-text-duration" class="property-input" data-label="s"><input id="durationinput" type="number" value="0"></div></th></tr></table></div>';
|
|
const audio_panel =
|
|
'<div id="layout-properties" class="panel-section"><p class="property-title">Audio</p><table><tr><th class="name-col">Volume</th><th class="value-col" id="duration-cell"><div id="object-volume" class="property-input" data-label="%"><input type="number" value=0></div></th></tr></table></div>';
|
|
|
|
// Browser variants
|
|
const shape_browser =
|
|
'<div id="search-fixed"><p class="property-title">Objects</p><img id="collapse" src="assets/collapse.svg"><div id="browser-search"><input placeholder="Search..."><img src="assets/search.svg" id="search-icon"><img src="assets/delete.svg" id="delete-search"><div id="search-button">Go</div></div></div><div id="shapes-cont"><p class="row-title">Shapes</p><div class="gallery-row" id="shapes-row"></div><p class="row-title">Emojis</p><div class="gallery-row" id="emojis-row"></div></div>';
|
|
const image_browser =
|
|
'<div id="search-fixed"><p class="property-title">Images</p><img id="collapse" src="assets/collapse.svg"><div id="browser-search"><input placeholder="Search..."><a href="https://pixabay.com" target="_blank" id="pixabay"><img src="assets/pixabay.svg"></a><img src="assets/search.svg" id="search-icon"><img src="assets/delete.svg" id="delete-search"><div id="search-button">Go</div></div></div><div id="shapes-cont"><div id="landing"><div id="landing-text">Browse millions of high quality images from Pixabay. Use the search bar above or choose from popular categories below.</div><div id="categories"></div></div><div id="images-grid"></div></div>';
|
|
const text_browser =
|
|
'<div id="search-fixed"><p class="property-title">Text</p><img id="collapse" src="assets/collapse.svg"><div id="browser-search"><input placeholder="Search..."><img src="assets/search.svg" id="search-icon"><img src="assets/delete.svg" id="delete-search"><div id="search-button">Go</div></div></div><div id="shapes-cont"><p class="row-title">Basic text</p><div id="heading-text" data-font="Inter" class="add-text noselect">Add a heading</div><div id="subheading-text" data-font="Inter" class="add-text noselect">Add a subheading</div><div id="body-text" data-font="Inter" class="add-text noselect">Add body text</div></div>';
|
|
const video_browser =
|
|
'<div id="search-fixed"><p class="property-title">Videos</p><img id="collapse" src="assets/collapse.svg"><div id="browser-search"><input placeholder="Search..."><a href="https://pixabay.com" target="_blank" id="pixabay"><img src="assets/pixabay.svg"></a><img src="assets/search.svg" id="search-icon"><img src="assets/delete.svg" id="delete-search"><div id="search-button">Go</div></div></div><div id="shapes-cont"><div id="landing"><div id="landing-text">Browse millions of high quality images from Pixabay. Use the search bar above or choose from popular categories below.</div><div id="categories"></div></div><div id="images-grid"></div></div>';
|
|
const upload_browser =
|
|
'<div id="search-fixed"><p class="property-title">Uploads</p><div id="upload-button"><img src="assets/upload.svg"> Upload media</div><img id="collapse" src="assets/collapse.svg"><div id="upload-tabs"><div id="images-tab" class="upload-tab upload-tab-active">Images</div><div id="videos-tab" class="upload-tab">Videos</div></div></div><div id="images-grid"></div>';
|
|
const audio_browser =
|
|
'<div id="search-fixed" class="audio-browser"><p class="property-title">Audio</p><div id="audio-upload-button"><img src="assets/upload.svg"> Upload audio</div><img id="collapse" src="assets/collapse.svg"></div><div id="audio-list-parent"><div id="landing-text" class="audio-landing-text">Audio provided by Pixabay. Browse millions of assets from Pixabay by <a href="https://pixabay.com/music/" target="_blank">clicking here.</a></div><div id="audio-list"></div></div>';
|
|
|
|
// Text animation list
|
|
var text_animation_list = [
|
|
{ name: 'fade in', label: 'Fade in', src: 'assets/fade-in.svg' },
|
|
{
|
|
name: 'typewriter',
|
|
label: 'Typewriter',
|
|
src: 'assets/typewriter.svg',
|
|
},
|
|
{
|
|
name: 'slide top',
|
|
label: 'Slide top',
|
|
src: 'assets/slide-top.svg',
|
|
},
|
|
{
|
|
name: 'slide bottom',
|
|
label: 'Slide bottom',
|
|
src: 'assets/slide-bottom.svg',
|
|
},
|
|
{
|
|
name: 'slide left',
|
|
label: 'Slide left',
|
|
src: 'assets/slide-left.svg',
|
|
},
|
|
{
|
|
name: 'slide right',
|
|
label: 'Slide right',
|
|
src: 'assets/slide-right.svg',
|
|
},
|
|
{ name: 'scale', label: 'Scale', src: 'assets/scale.svg' },
|
|
{ name: 'shrink', label: 'Shrink', src: 'assets/shrink.svg' },
|
|
];
|
|
|
|
// Shapes list
|
|
var shape_grid_items = [
|
|
'assets/shapes/rectangle.svg',
|
|
'assets/shapes/circle.svg',
|
|
'assets/shapes/triangle.svg',
|
|
'assets/shapes/polygon.svg',
|
|
'assets/shapes/star.svg',
|
|
'assets/thingy.svg',
|
|
'assets/shapes/heart.svg',
|
|
'assets/shapes/arrow.svg',
|
|
];
|
|
var emoji_items = [
|
|
'assets/twemojis/laughing-emoji.png',
|
|
'assets/twemojis/crying-emoji.png',
|
|
'assets/twemojis/surprised-emoji.png',
|
|
'assets/twemojis/smiling-emoji.png',
|
|
'assets/twemojis/tongue-emoji.png',
|
|
'assets/twemojis/heart-eyes-emoji.png',
|
|
'assets/twemojis/heart-kiss-emoji.png',
|
|
'assets/twemojis/sunglasses-cool-emoji.png',
|
|
'assets/twemojis/ghost-emoji.png',
|
|
'assets/twemojis/skull-emoji.png',
|
|
'assets/twemojis/mindblown-emoji.png',
|
|
'assets/twemojis/bomb-emoji.png',
|
|
'assets/twemojis/hundred-100-points-emoji.png',
|
|
'assets/twemojis/thought-balloon-emoji.png',
|
|
'assets/twemojis/wave-emoji.png',
|
|
'assets/twemojis/point-emoji.png',
|
|
'assets/twemojis/thumbs-up-emoji.png',
|
|
'assets/twemojis/clap-emoji.png',
|
|
'assets/twemojis/raising-hands-emoji.png',
|
|
'assets/twemojis/praying-hands-emoji.png',
|
|
'assets/twemojis/nail-polish-emoji.png',
|
|
'assets/twemojis/eyes-emoji.png',
|
|
'assets/twemojis/cat-face-emoji.png',
|
|
'assets/twemojis/dog-face-emoji.png',
|
|
'assets/twemojis/rose-emoji.png',
|
|
'assets/twemojis/tulip-emoji.png',
|
|
'assets/twemojis/pizza-emoji.png',
|
|
'assets/twemojis/construction-emoji.png',
|
|
'assets/twemojis/plane-emoji.png',
|
|
'assets/twemojis/rocket-emoji.png',
|
|
'assets/twemojis/clock-emoji.png',
|
|
'assets/twemojis/star-emoji.png',
|
|
'assets/twemojis/sun-emoji.png',
|
|
'assets/twemojis/moon-emoji.png',
|
|
'assets/twemojis/fire-emoji.png',
|
|
'assets/twemojis/sparkles-emoji.png',
|
|
'assets/twemojis/party-popper-emoji.png',
|
|
'assets/twemojis/gift-emoji.png',
|
|
'assets/twemojis/trophy-emoji.png',
|
|
'assets/twemojis/target-emoji.png',
|
|
'assets/twemojis/gem-emoji.png',
|
|
'assets/twemojis/money-emoji.png',
|
|
'assets/twemojis/pencil-emoji.png',
|
|
'assets/twemojis/graph-emoji.png',
|
|
'assets/twemojis/wip-emoji.png',
|
|
'assets/twemojis/winking-face-emoji.png',
|
|
'assets/twemojis/pleading-face-emoji.png',
|
|
'assets/twemojis/thinking-face-emoji.png',
|
|
];
|
|
|
|
// Image list
|
|
var image_grid_items = [
|
|
'https://images.unsplash.com/photo-1609153259378-a8b23c766aec?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1866&q=80',
|
|
'https://images.unsplash.com/photo-1614435082296-ef0cbdb16b70?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
|
|
'https://images.unsplash.com/photo-1614432254115-7e756705e910?ixid=MXwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxMHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=900&q=60',
|
|
'https://images.unsplash.com/photo-1614423234685-544477464e15?ixid=MXwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw4fHx8ZW58MHx8fA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=900&q=60',
|
|
'https://images.unsplash.com/photo-1614357235247-99fabbee67f9?ixid=MXwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxNHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=900&q=60',
|
|
'https://images.unsplash.com/photo-1614373371549-c7d2e4885f17?ixid=MXwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxOXx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=900&q=60',
|
|
];
|
|
var image_categories = [
|
|
{ name: 'background', image: 'assets/background.png' },
|
|
{ name: 'wallpaper', image: 'assets/wallpaper.png' },
|
|
{ name: 'nature', image: 'assets/nature.png' },
|
|
{ name: 'summer', image: 'assets/summer.png' },
|
|
{ name: 'beach', image: 'assets/beach.png' },
|
|
{ name: 'space', image: 'assets/space.png' },
|
|
{ name: 'office', image: 'assets/office.png' },
|
|
{ name: 'food', image: 'assets/food.png' },
|
|
];
|
|
|
|
// Video list
|
|
var video_categories = [
|
|
{ name: 'rain', image: 'assets/rain.png' },
|
|
{ name: 'cars', image: 'assets/cars.png' },
|
|
{ name: 'meditation', image: 'assets/meditation.png' },
|
|
{ name: 'forest', image: 'assets/forest.png' },
|
|
{ name: 'animals', image: 'assets/animals.png' },
|
|
{ name: 'street', image: 'assets/street.png' },
|
|
{ name: 'travel', image: 'assets/travel.png' },
|
|
{ name: 'work', image: 'assets/work.png' },
|
|
];
|
|
|
|
// Audio list
|
|
var audio_items = [
|
|
{
|
|
name: 'Lofi Study',
|
|
desc: 'FASSounds',
|
|
duration: '2:27',
|
|
thumb: 'assets/audio/lofi-thumb.png',
|
|
src: 'assets/audio/lofi.mp3',
|
|
link: 'https://pixabay.com/users/fassounds-3433550/',
|
|
},
|
|
{
|
|
name: 'Stomping Rock (Four Shots)',
|
|
desc: 'AlexGrohl',
|
|
duration: '1:59',
|
|
thumb: 'assets/audio/stomping-rock-thumb.png',
|
|
src: 'assets/audio/stomping-rock.mp3',
|
|
link: 'https://pixabay.com/users/alexgrohl-25289918/',
|
|
},
|
|
{
|
|
name: 'Everything Feels New',
|
|
desc: 'EvgenyBardyuzha',
|
|
duration: '1:06',
|
|
thumb: 'assets/audio/everything-feels-new-thumb.png',
|
|
src: 'assets/audio/everything-feels-new.mp3',
|
|
link: 'https://pixabay.com/users/evgenybardyuzha-25235210/',
|
|
},
|
|
{
|
|
name: 'Both of Us',
|
|
desc: 'madiRFAN',
|
|
duration: '2:48',
|
|
thumb: 'assets/audio/both-of-us-thumb.png',
|
|
src: 'assets/audio/both-of-us.mp3',
|
|
link: 'https://pixabay.com/users/madirfan-50411/',
|
|
},
|
|
{
|
|
name: 'The Podcast Intro',
|
|
desc: 'Music Unlimited',
|
|
duration: '1:51',
|
|
thumb: 'assets/audio/the-podcast-intro-thumb.png',
|
|
src: 'assets/audio/the-podcast-intro.mp3',
|
|
link: 'https://pixabay.com/users/music_unlimited-27600023/',
|
|
},
|
|
{
|
|
name: 'Epic Cinematic Trailer',
|
|
desc: 'PavelYudin',
|
|
duration: '2:27',
|
|
thumb: 'assets/audio/epic-cinematic-trailer-thumb.png',
|
|
src: 'assets/audio/epic-cinematic-trailer.mp3',
|
|
link: 'https://pixabay.com/users/pavelyudin-27739282/',
|
|
},
|
|
{
|
|
name: 'Inspirational Background',
|
|
desc: 'AudioCoffee',
|
|
duration: '2:19',
|
|
thumb: 'assets/audio/inspirational-background-thumb.png',
|
|
src: 'assets/audio/inspirational-background.mp3',
|
|
link: 'https://pixabay.com/users/audiocoffee-27005420/',
|
|
},
|
|
{
|
|
name: 'Tropical Summer Music',
|
|
desc: 'Music Unlimited',
|
|
duration: '2:35',
|
|
thumb: 'assets/audio/tropical-summer-music-thumb.png',
|
|
src: 'assets/audio/tropical-summer-music.mp3',
|
|
link: 'https://pixabay.com/users/music_unlimited-27600023/',
|
|
},
|
|
];
|
|
|
|
// Text list
|
|
var text_items = {
|
|
sansserif: [
|
|
{ name: 'Roboto', fontname: 'Roboto' },
|
|
{ name: 'Montserrat', fontname: 'Montserrat' },
|
|
{ name: 'Poppins', fontname: 'Poppins' },
|
|
],
|
|
serif: [
|
|
{ name: 'Playfair Display', fontname: 'Playfair Display' },
|
|
{ name: 'Merriweather', fontname: 'Merriweather' },
|
|
{ name: 'IBM Plex Serif', fontname: 'IBM Plex Serif' },
|
|
],
|
|
monospace: [
|
|
{ name: 'Roboto Mono', fontname: 'Roboto Mono' },
|
|
{ name: 'Inconsolata', fontname: 'Inconsolata' },
|
|
{ name: 'Source Code Pro', fontname: 'Source Code Pro' },
|
|
],
|
|
handwriting: [
|
|
{ name: 'Dancing Script', fontname: 'Dancing Script' },
|
|
{ name: 'Pacifico', fontname: 'Pacifico' },
|
|
{ name: 'Indie Flower', fontname: 'Indie Flower' },
|
|
],
|
|
display: [
|
|
{ name: 'Lobster', fontname: 'Lobster' },
|
|
{ name: 'Bebas Neue', fontname: 'Bebas Neue' },
|
|
{ name: 'Titan One', fontname: 'Titan One' },
|
|
],
|
|
};
|
|
|
|
// Without a Google Fonts key the font pickers would be empty, so fall back
|
|
// to the families that are already bundled in the text browser.
|
|
// (Declared here because it reads text_items, defined just above.)
|
|
if (!HAS_FONTS_KEY) {
|
|
Object.keys(text_items).forEach(function (group) {
|
|
text_items[group].forEach(function (item) {
|
|
if (fonts.indexOf(item.fontname) == -1) {
|
|
fonts.push(item.fontname);
|
|
}
|
|
});
|
|
});
|
|
['Inter', 'Syne'].forEach(function (name) {
|
|
if (fonts.indexOf(name) == -1) {
|
|
fonts.push(name);
|
|
}
|
|
});
|
|
fonts.sort();
|
|
}
|
|
|
|
WebFont.load({
|
|
google: {
|
|
families: ['Syne'],
|
|
},
|
|
active: () => {},
|
|
});
|
|
|
|
var webglBackend;
|
|
try {
|
|
webglBackend = new fabric.WebglFilterBackend();
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
var canvas2dBackend = new fabric.Canvas2dFilterBackend();
|
|
|
|
fabric.filterBackend = fabric.initFilterBackend();
|
|
// Only take over the backend if WebGL actually initialized, otherwise filters
|
|
// would silently break on machines without a usable WebGL context.
|
|
if (webglBackend) {
|
|
fabric.filterBackend = webglBackend;
|
|
}
|
|
|
|
// Lottie support
|
|
fabric.Lottie = fabric.util.createClass(fabric.Image, {
|
|
type: 'lottie',
|
|
lockRotation: true,
|
|
lockSkewingX: true,
|
|
lockSkewingY: true,
|
|
srcFromAttribute: false,
|
|
|
|
initialize: function (path, options) {
|
|
if (!options.width) options.width = 480;
|
|
if (!options.height) options.height = 480;
|
|
|
|
this.path = path;
|
|
this.tmpCanvasEl = fabric.util.createCanvasElement();
|
|
this.tmpCanvasEl.width = options.width;
|
|
this.tmpCanvasEl.height = options.height;
|
|
|
|
this.lottieItem = bodymovin.loadAnimation({
|
|
renderer: 'canvas',
|
|
loop: false,
|
|
autoplay: false,
|
|
path: path,
|
|
rendererSettings: {
|
|
context: this.tmpCanvasEl.getContext('2d'),
|
|
preserveAspectRatio: 'xMidYMid meet',
|
|
},
|
|
});
|
|
|
|
this.lottieItem.addEventListener('enterFrame', (e) => {
|
|
if (this.canvas) {
|
|
this.canvas.requestRenderAll();
|
|
}
|
|
});
|
|
|
|
this.lottieItem.addEventListener('DOMLoaded', () => {
|
|
this.lottieItem.goToAndStop(currenttime, false);
|
|
this.lottieItem.duration =
|
|
this.lottieItem.getDuration(false) * 1000;
|
|
if (this.canvas) {
|
|
this.canvas.requestRenderAll();
|
|
}
|
|
canvas.renderAll();
|
|
canvas.fire('lottie:loaded', { any: 'payload' });
|
|
});
|
|
|
|
this.callSuper('initialize', this.tmpCanvasEl, options);
|
|
},
|
|
|
|
goToSeconds: function (seconds) {
|
|
this.lottieItem.goToAndStop(seconds, false);
|
|
if (this.canvas) {
|
|
this.canvas.requestRenderAll();
|
|
}
|
|
},
|
|
goToFrame: function (frame) {
|
|
this.lottieItem.goToAndStop(frame, true);
|
|
},
|
|
getDuration: function () {
|
|
return this.lottieItem.getDuration(false);
|
|
},
|
|
play: function () {
|
|
this.lottieItem.play();
|
|
},
|
|
pause: function () {
|
|
this.lottieItem.pause();
|
|
},
|
|
getSrc: function () {
|
|
return this.path;
|
|
},
|
|
});
|
|
|
|
fabric.Lottie.fromObject = function (_object, callback) {
|
|
const object = fabric.util.object.clone(_object);
|
|
fabric.Image.prototype._initFilters.call(
|
|
object,
|
|
object.filters,
|
|
function (filters) {
|
|
object.filters = filters || [];
|
|
fabric.Image.prototype._initFilters.call(
|
|
object,
|
|
[object.resizeFilter],
|
|
function (resizeFilters) {
|
|
object.resizeFilter = resizeFilters[0];
|
|
fabric.util.enlivenObjects(
|
|
[object.clipPath],
|
|
function (enlivedProps) {
|
|
object.clipPath = enlivedProps[0];
|
|
const fabricLottie = new fabric.Lottie(
|
|
object.src,
|
|
object
|
|
);
|
|
callback(fabricLottie, false);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
};
|
|
|
|
// Initialize canvas
|
|
var canvas = new fabric.Canvas('canvas', {
|
|
preserveObjectStacking: true,
|
|
backgroundColor: '#FFF',
|
|
stateful: true,
|
|
});
|
|
canvas.selection = false;
|
|
canvas.controlsAboveOverlay = true;
|
|
|
|
// Customize controls
|
|
fabric.Object.prototype.set({
|
|
transparentCorners: false,
|
|
borderColor: '#51B9F9',
|
|
cornerColor: '#FFF',
|
|
borderScaleFactor: 2.5,
|
|
cornerStyle: 'circle',
|
|
cornerStrokeColor: '#0E98FC',
|
|
borderOpacityWhenMoving: 1,
|
|
});
|
|
|
|
canvas.selectionColor = 'rgba(46, 115, 252, 0.11)';
|
|
canvas.selectionBorderColor = 'rgba(98, 155, 255, 0.81)';
|
|
canvas.selectionLineWidth = 1.5;
|
|
|
|
var img = document.createElement('img');
|
|
img.src = 'assets/middlecontrol.svg';
|
|
|
|
var img2 = document.createElement('img');
|
|
img2.src = 'assets/middlecontrolhoz.svg';
|
|
|
|
var img3 = document.createElement('img');
|
|
img3.src = 'assets/edgecontrol.svg';
|
|
|
|
var img4 = document.createElement('img');
|
|
img4.src = 'assets/rotateicon.svg';
|
|
|
|
function renderIcon(ctx, left, top, styleOverride, fabricObject) {
|
|
const wsize = 20;
|
|
const hsize = 25;
|
|
ctx.save();
|
|
ctx.translate(left, top);
|
|
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
|
|
ctx.drawImage(img, -wsize / 2, -hsize / 2, wsize, hsize);
|
|
ctx.restore();
|
|
}
|
|
function renderIconHoz(ctx, left, top, styleOverride, fabricObject) {
|
|
const wsize = 25;
|
|
const hsize = 20;
|
|
ctx.save();
|
|
ctx.translate(left, top);
|
|
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
|
|
ctx.drawImage(img2, -wsize / 2, -hsize / 2, wsize, hsize);
|
|
ctx.restore();
|
|
}
|
|
function renderIconEdge(ctx, left, top, styleOverride, fabricObject) {
|
|
const wsize = 25;
|
|
const hsize = 25;
|
|
ctx.save();
|
|
ctx.translate(left, top);
|
|
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
|
|
ctx.drawImage(img3, -wsize / 2, -hsize / 2, wsize, hsize);
|
|
ctx.restore();
|
|
}
|
|
|
|
function renderIconRotate(
|
|
ctx,
|
|
left,
|
|
top,
|
|
styleOverride,
|
|
fabricObject
|
|
) {
|
|
const wsize = 40;
|
|
const hsize = 40;
|
|
ctx.save();
|
|
ctx.translate(left, top);
|
|
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
|
|
ctx.drawImage(img4, -wsize / 2, -hsize / 2, wsize, hsize);
|
|
ctx.restore();
|
|
}
|
|
function resetControls() {
|
|
fabric.Object.prototype.controls.ml = new fabric.Control({
|
|
x: -0.5,
|
|
y: 0,
|
|
offsetX: -1,
|
|
cursorStyleHandler:
|
|
fabric.controlsUtils.scaleSkewCursorStyleHandler,
|
|
actionHandler: fabric.controlsUtils.scalingXOrSkewingY,
|
|
getActionName: fabric.controlsUtils.scaleOrSkewActionName,
|
|
render: renderIcon,
|
|
});
|
|
|
|
fabric.Object.prototype.controls.mr = new fabric.Control({
|
|
x: 0.5,
|
|
y: 0,
|
|
offsetX: 1,
|
|
cursorStyleHandler:
|
|
fabric.controlsUtils.scaleSkewCursorStyleHandler,
|
|
actionHandler: fabric.controlsUtils.scalingXOrSkewingY,
|
|
getActionName: fabric.controlsUtils.scaleOrSkewActionName,
|
|
render: renderIcon,
|
|
});
|
|
|
|
fabric.Object.prototype.controls.mb = new fabric.Control({
|
|
x: 0,
|
|
y: 0.5,
|
|
offsetY: 1,
|
|
cursorStyleHandler:
|
|
fabric.controlsUtils.scaleSkewCursorStyleHandler,
|
|
actionHandler: fabric.controlsUtils.scalingYOrSkewingX,
|
|
getActionName: fabric.controlsUtils.scaleOrSkewActionName,
|
|
render: renderIconHoz,
|
|
});
|
|
|
|
fabric.Object.prototype.controls.mt = new fabric.Control({
|
|
x: 0,
|
|
y: -0.5,
|
|
offsetY: -1,
|
|
cursorStyleHandler:
|
|
fabric.controlsUtils.scaleSkewCursorStyleHandler,
|
|
actionHandler: fabric.controlsUtils.scalingYOrSkewingX,
|
|
getActionName: fabric.controlsUtils.scaleOrSkewActionName,
|
|
render: renderIconHoz,
|
|
});
|
|
|
|
fabric.Object.prototype.controls.tl = new fabric.Control({
|
|
x: -0.5,
|
|
y: -0.5,
|
|
cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
|
|
actionHandler: fabric.controlsUtils.scalingEqually,
|
|
render: renderIconEdge,
|
|
});
|
|
|
|
fabric.Object.prototype.controls.tr = new fabric.Control({
|
|
x: 0.5,
|
|
y: -0.5,
|
|
cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
|
|
actionHandler: fabric.controlsUtils.scalingEqually,
|
|
render: renderIconEdge,
|
|
});
|
|
|
|
fabric.Object.prototype.controls.bl = new fabric.Control({
|
|
x: -0.5,
|
|
y: 0.5,
|
|
cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
|
|
actionHandler: fabric.controlsUtils.scalingEqually,
|
|
render: renderIconEdge,
|
|
});
|
|
|
|
fabric.Object.prototype.controls.br = new fabric.Control({
|
|
x: 0.5,
|
|
y: 0.5,
|
|
cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
|
|
actionHandler: fabric.controlsUtils.scalingEqually,
|
|
render: renderIconEdge,
|
|
});
|
|
|
|
fabric.Object.prototype.controls.mtr = new fabric.Control({
|
|
x: 0,
|
|
y: 0.5,
|
|
cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler,
|
|
actionHandler: fabric.controlsUtils.rotationWithSnapping,
|
|
offsetY: 30,
|
|
withConnecton: false,
|
|
actionName: 'rotate',
|
|
render: renderIconRotate,
|
|
});
|
|
}
|
|
resetControls();
|
|
var textBoxControls = (fabric.Textbox.prototype.controls = {});
|
|
|
|
textBoxControls.mtr = fabric.Object.prototype.controls.mtr;
|
|
textBoxControls.tr = fabric.Object.prototype.controls.tr;
|
|
textBoxControls.br = fabric.Object.prototype.controls.br;
|
|
textBoxControls.tl = fabric.Object.prototype.controls.tl;
|
|
textBoxControls.bl = fabric.Object.prototype.controls.bl;
|
|
textBoxControls.mt = fabric.Object.prototype.controls.mt;
|
|
textBoxControls.mb = fabric.Object.prototype.controls.mb;
|
|
|
|
textBoxControls.ml = new fabric.Control({
|
|
x: -0.5,
|
|
y: 0,
|
|
offsetX: -1,
|
|
cursorStyleHandler:
|
|
fabric.controlsUtils.scaleSkewCursorStyleHandler,
|
|
actionHandler: fabric.controlsUtils.changeWidth,
|
|
actionName: 'resizing',
|
|
render: renderIcon,
|
|
});
|
|
|
|
textBoxControls.mr = new fabric.Control({
|
|
x: 0.5,
|
|
y: 0,
|
|
offsetX: 1,
|
|
cursorStyleHandler:
|
|
fabric.controlsUtils.scaleSkewCursorStyleHandler,
|
|
actionHandler: fabric.controlsUtils.changeWidth,
|
|
actionName: 'resizing',
|
|
render: renderIcon,
|
|
});
|
|
|
|
// Get any object by ID
|
|
fabric.Canvas.prototype.getItemById = function (name) {
|
|
function search(list) {
|
|
for (var i = 0; i < list.length; i++) {
|
|
const item = list[i];
|
|
if (item.id && item.id === name) {
|
|
return item;
|
|
}
|
|
// Recurse so groups nested more than one level deep are found too
|
|
if (item._objects && item._objects.length > 0) {
|
|
const found = search(item._objects);
|
|
if (found) {
|
|
return found;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
return search(this.getObjects());
|
|
};
|
|
|
|
// Create the artboard
|
|
var a_width = 600;
|
|
var a_height = 500;
|
|
var artboard = new fabric.Rect({
|
|
left: canvas.get('width') / 2 - a_width / 2,
|
|
top: canvas.get('height') / 2 - a_height / 2,
|
|
width: a_width,
|
|
height: a_height,
|
|
absolutePositioned: true,
|
|
rx: 0,
|
|
ry: 0,
|
|
fill: '#FFF',
|
|
hasControls: true,
|
|
transparentCorners: false,
|
|
borderColor: '#0E98FC',
|
|
cornerColor: '#0E98FC',
|
|
cursorWidth: 1,
|
|
cursorDuration: 1,
|
|
cursorDelay: 250,
|
|
id: 'overlay',
|
|
});
|
|
canvas.renderAll();
|
|
|
|
// Clip canvas to the artboard
|
|
canvas.clipPath = artboard;
|
|
canvas.renderAll();
|
|
|
|
// Initialize color picker (fill)
|
|
var o_fill = Pickr.create({
|
|
el: '#color-picker-fill',
|
|
theme: 'nano',
|
|
inline: true,
|
|
useAsButton: true,
|
|
swatches: null,
|
|
default: '#FFFFFF',
|
|
showAlways: true,
|
|
components: {
|
|
preview: true,
|
|
opacity: true,
|
|
hue: true,
|
|
interaction: {
|
|
hex: true,
|
|
rgba: true,
|
|
hsla: false,
|
|
hsva: false,
|
|
cmyk: false,
|
|
input: true,
|
|
clear: false,
|
|
save: false,
|
|
},
|
|
},
|
|
});
|
|
|
|
// Color picker events
|
|
o_fill
|
|
.on('init', (instance) => {
|
|
o_fill.hide();
|
|
})
|
|
.on('change', (instance) => {
|
|
if (canvas.getActiveObject()) {
|
|
const object = canvas.getActiveObject();
|
|
if (colormode == 'fill') {
|
|
$('#object-color-fill input').val(
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
$('#object-color-fill-opacity input').val(
|
|
Math.round(o_fill.getColor().toRGBA()[3] * 100 * 100) / 100
|
|
);
|
|
$('#color-fill-side').css(
|
|
'background-color',
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
object.set('fill', o_fill.getColor().toRGBA().toString());
|
|
if (!seeking && !setting) {
|
|
newKeyframe(
|
|
'fill',
|
|
object,
|
|
currenttime,
|
|
object.get('fill'),
|
|
true
|
|
);
|
|
}
|
|
} else if (colormode == 'stroke') {
|
|
$('#object-color-stroke input').val(
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
$('#object-color-stroke-opacity input').val(
|
|
Math.round(o_fill.getColor().toRGBA()[3] * 100 * 100) / 100
|
|
);
|
|
$('#color-stroke-side').css(
|
|
'background-color',
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
object.set('stroke', o_fill.getColor().toRGBA().toString());
|
|
if (!seeking && !setting) {
|
|
newKeyframe(
|
|
'stroke',
|
|
object,
|
|
currenttime,
|
|
object.get('stroke'),
|
|
true
|
|
);
|
|
newKeyframe(
|
|
'strokeWidth',
|
|
object,
|
|
currenttime,
|
|
object.get('strokeWidth'),
|
|
true
|
|
);
|
|
}
|
|
} else if (colormode == 'shadow') {
|
|
$('#object-color-shadow input').val(
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
$('#object-color-shadow-opacity input').val(
|
|
Math.round(o_fill.getColor().toRGBA()[3] * 100 * 100) / 100
|
|
);
|
|
$('#color-shadow-side').css(
|
|
'background-color',
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
object.set(
|
|
'shadow',
|
|
new fabric.Shadow({
|
|
color: o_fill.getColor().toRGBA().toString(),
|
|
offsetX: object.shadow.offsetX,
|
|
offsetY: object.shadow.offsetY,
|
|
blur: object.shadow.blur,
|
|
opacity: object.shadow.opacity,
|
|
})
|
|
);
|
|
if (!seeking && !setting) {
|
|
newKeyframe(
|
|
'shadow.color',
|
|
object,
|
|
currenttime,
|
|
object.shadow.color,
|
|
true
|
|
);
|
|
}
|
|
} else if (colormode == 'chroma') {
|
|
var obj = canvas.getActiveObject();
|
|
$('#chroma-color input').val(
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
$('#color-chroma-side').css(
|
|
'background-color',
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
if (obj.filters.find((x) => x.type == 'RemoveColor')) {
|
|
obj.filters.find((x) => x.type == 'RemoveColor').color =
|
|
o_fill.getColor().toRGBA().toString();
|
|
}
|
|
updateChromaValues();
|
|
} else if (colormode == 'text') {
|
|
var obj = canvas.getActiveObject();
|
|
$('#text-color input').val(
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
$('#color-text-side').css(
|
|
'background-color',
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
animatedtext
|
|
.find((x) => x.id == obj.id)
|
|
.setProps(
|
|
{ fill: o_fill.getColor().toRGBA().toString() },
|
|
canvas
|
|
);
|
|
}
|
|
canvas.renderAll();
|
|
} else {
|
|
if (colormode == 'back') {
|
|
$('#canvas-color input').val(
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
$('#canvas-color-opacity input').val(
|
|
Math.round(o_fill.getColor().toRGBA()[3] * 100 * 100) / 100
|
|
);
|
|
$('#color-side').css(
|
|
'background-color',
|
|
o_fill.getColor().toHEXA().toString().substring(0, 7)
|
|
);
|
|
canvas.setBackgroundColor(
|
|
o_fill.getColor().toRGBA().toString()
|
|
);
|
|
canvas.renderAll();
|
|
}
|
|
}
|
|
})
|
|
.on('show', (instance) => {
|
|
$('.pcr-current-color').html(
|
|
"<img id='eyedropper' src='assets/eyedropper.svg'>"
|
|
);
|
|
});
|
|
|
|
var f = fabric.Image.filters;
|
|
|
|
// Canvas recorder initialization
|
|
var canvasrecord = new fabric.Canvas('canvasrecord', {
|
|
preserveObjectStacking: true,
|
|
backgroundColor: '#FFF',
|
|
width: artboard.width,
|
|
height: artboard.height,
|
|
// Objects are repositioned to artboard-relative coordinates while
|
|
// recording. Their cached aCoords are not always refreshed in step, and
|
|
// fabric's offscreen culling would then skip drawing them entirely,
|
|
// producing blank frames.
|
|
skipOffscreen: false,
|
|
});
|
|
|
|
var timelineslider = document.getElementById('timeline-zoom');
|
|
var t_slider = new RangeSlider(timelineslider, {
|
|
design: '2d',
|
|
theme: 'default',
|
|
handle: 'round',
|
|
popup: null,
|
|
showMinMaxLabels: false,
|
|
unit: '%',
|
|
min: 5,
|
|
max: 47,
|
|
value: 47,
|
|
onmove: function (x) {
|
|
setTimelineZoom(-1 * (x - 51));
|
|
},
|
|
onfinish: function (x) {
|
|
setTimelineZoom(-1 * (x - 51));
|
|
},
|
|
onstart: function (x) {},
|
|
});
|
|
|
|
const selectbox = new SelectionArea({
|
|
class: 'selection-area',
|
|
selectables: ['.keyframe'],
|
|
container: '#timeline',
|
|
// Query selectors for elements from where a selection can be started from.
|
|
startareas: ['html'],
|
|
|
|
// Query selectors for elements which will be used as boundaries for the selection.
|
|
boundaries: ['#timeline'],
|
|
|
|
startThreshold: 10,
|
|
|
|
allowTouch: true,
|
|
|
|
intersect: 'touch',
|
|
|
|
overlap: 'invert',
|
|
|
|
// Configuration in case a selectable gets just clicked.
|
|
singleTap: {
|
|
allow: false,
|
|
intersect: 'native',
|
|
},
|
|
|
|
// Scroll configuration.
|
|
scrolling: {
|
|
speedDivider: 10,
|
|
manualSpeed: 750,
|
|
},
|
|
});
|
|
|
|
selectbox
|
|
.on('beforestart', (evt) => {
|
|
if (
|
|
$(evt.event.target).hasClass('keyframe') ||
|
|
$(evt.event.target).attr('id') == 'seekbar' ||
|
|
$(evt.event.target).parent().hasClass('main-row') ||
|
|
$(evt.event.target).hasClass('main-row') ||
|
|
$(evt.event.target).hasClass('trim-row') ||
|
|
evt.event.which === 3
|
|
) {
|
|
return false;
|
|
}
|
|
})
|
|
.on('start', (evt) => {})
|
|
.on('move', (evt) => {})
|
|
.on('stop', (evt) => {
|
|
$('.keyframe-selected').removeClass('keyframe-selected');
|
|
shiftkeys = [];
|
|
if (evt.store.selected.length == 0) {
|
|
$('.keyframe-selected').removeClass('keyframe-selected');
|
|
} else {
|
|
canvas.discardActiveObject();
|
|
canvas.renderAll();
|
|
evt.store.selected.forEach(function (key) {
|
|
shiftkeys.push({
|
|
keyframe: key,
|
|
offset: $(key).offset().left,
|
|
});
|
|
$(key).addClass('keyframe-selected');
|
|
});
|
|
}
|
|
});
|