Merge branch 'main' into release
This commit is contained in:
commit
25248f5088
|
|
@ -1,12 +1,11 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Lightningbeam",
|
"productName": "Lightningbeam",
|
||||||
"version": "0.7.9-alpha",
|
"version": "0.7.10-alpha",
|
||||||
"identifier": "org.lightningbeam.core",
|
"identifier": "org.lightningbeam.core",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../src"
|
"frontendDist": "../src"
|
||||||
},
|
},
|
||||||
|
|
||||||
"app": {
|
"app": {
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
"windows": [
|
"windows": [
|
||||||
|
|
|
||||||
|
|
@ -1109,13 +1109,16 @@ class Bezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON(json) {
|
static fromJSON(json) {
|
||||||
return new Bezier(...json)
|
return json[8] ? new Bezier(...json.slice(0, 8)).setColor(json[8]) : new Bezier(...json.slice(0, 8));
|
||||||
}
|
}
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return [this.points[0].x, this.points[0].y,
|
return [
|
||||||
|
this.points[0].x, this.points[0].y,
|
||||||
this.points[1].x, this.points[1].y,
|
this.points[1].x, this.points[1].y,
|
||||||
this.points[2].x, this.points[2].y,
|
this.points[2].x, this.points[2].y,
|
||||||
this.points[3].x, this.points[3].y]
|
this.points[3].x, this.points[3].y,
|
||||||
|
this.color
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
static getUtils() {
|
static getUtils() {
|
||||||
|
|
|
||||||
248
src/main.js
248
src/main.js
|
|
@ -585,6 +585,8 @@ let actions = {
|
||||||
},
|
},
|
||||||
execute: (action) => {
|
execute: (action) => {
|
||||||
let shape = pointerList[action.shape];
|
let shape = pointerList[action.shape];
|
||||||
|
console.log(action.shape)
|
||||||
|
console.log(pointerList)
|
||||||
shape.fillStyle = action.newColor;
|
shape.fillStyle = action.newColor;
|
||||||
},
|
},
|
||||||
rollback: (action) => {
|
rollback: (action) => {
|
||||||
|
|
@ -685,6 +687,8 @@ let actions = {
|
||||||
player: player,
|
player: player,
|
||||||
start: action.frameNum,
|
start: action.frameNum,
|
||||||
img: img,
|
img: img,
|
||||||
|
src: action.audiosrc,
|
||||||
|
uuid: action.uuid
|
||||||
};
|
};
|
||||||
pointerList[action.uuid] = soundObj;
|
pointerList[action.uuid] = soundObj;
|
||||||
newAudioLayer.sounds[action.uuid] = soundObj;
|
newAudioLayer.sounds[action.uuid] = soundObj;
|
||||||
|
|
@ -1530,6 +1534,7 @@ let actions = {
|
||||||
for (let shapeIdx of action.shapes) {
|
for (let shapeIdx of action.shapes) {
|
||||||
let shape = pointerList[shapeIdx];
|
let shape = pointerList[shapeIdx];
|
||||||
frame.addShape(shape);
|
frame.addShape(shape);
|
||||||
|
shape.translate(action.position.x, action.position.y);
|
||||||
group.getFrame(0).removeShape(shape);
|
group.getFrame(0).removeShape(shape);
|
||||||
}
|
}
|
||||||
for (let objectIdx of action.objects) {
|
for (let objectIdx of action.objects) {
|
||||||
|
|
@ -2590,51 +2595,49 @@ class Layer extends Widget {
|
||||||
child[key] = frame.keys[child.idx][key];
|
child[key] = frame.keys[child.idx][key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (!context.objectStack.includes(child)) {
|
||||||
}
|
if (keyframe) {
|
||||||
}
|
if (child.goToFrame != undefined) {
|
||||||
for (let child of this.children) {
|
child.setFrameNum(child.goToFrame - 1)
|
||||||
if (!context.objectStack.includes(child)) {
|
if (child.playFromFrame) {
|
||||||
if (keyframe) {
|
child.playing = true
|
||||||
if (child.goToFrame != undefined) {
|
} else {
|
||||||
child.setFrameNum(child.goToFrame - 1)
|
child.playing = false
|
||||||
if (child.playFromFrame) {
|
}
|
||||||
child.playing = true
|
child.playing = true
|
||||||
} else {
|
}
|
||||||
child.playing = false
|
|
||||||
}
|
}
|
||||||
child.playing = true
|
if (child.playing) {
|
||||||
}
|
let lastFrame = 0;
|
||||||
}
|
for (let i = this.frameNum; i >= 0; i--) {
|
||||||
if (child.playing) {
|
if (
|
||||||
let lastFrame = 0;
|
this.frames[i] &&
|
||||||
for (let i = this.frameNum; i >= 0; i--) {
|
this.frames[i].keys[child.idx] &&
|
||||||
if (
|
this.frames[i].keys[child.idx].playFromFrame
|
||||||
this.frames[i] &&
|
) {
|
||||||
this.frames[i].keys[child.idx] &&
|
lastFrame = i;
|
||||||
this.frames[i].keys[child.idx].playFromFrame
|
break;
|
||||||
) {
|
}
|
||||||
lastFrame = i;
|
}
|
||||||
break;
|
child.setFrameNum(this.frameNum - lastFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
child.setFrameNum(this.frameNum - lastFrame);
|
const transform = ctx.getTransform()
|
||||||
|
ctx.translate(child.x, child.y)
|
||||||
|
ctx.scale(child.scale_x, child.scale_y)
|
||||||
|
ctx.rotate(child.rotation)
|
||||||
|
child.draw(ctx)
|
||||||
|
if (context.selection.includes(child)) {
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.strokeStyle = "#00ffff";
|
||||||
|
ctx.beginPath();
|
||||||
|
let bbox = child.bbox()
|
||||||
|
ctx.rect(bbox.x.min-child.x, bbox.y.min-child.y, bbox.x.max-bbox.x.min, bbox.y.max-bbox.y.min)
|
||||||
|
ctx.stroke()
|
||||||
|
}
|
||||||
|
ctx.setTransform(transform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const transform = ctx.getTransform()
|
|
||||||
ctx.translate(child.x, child.y)
|
|
||||||
ctx.scale(child.scale_x, child.scale_y)
|
|
||||||
ctx.rotate(child.rotation)
|
|
||||||
child.draw(ctx)
|
|
||||||
if (context.selection.includes(child)) {
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
ctx.strokeStyle = "#00ffff";
|
|
||||||
ctx.beginPath();
|
|
||||||
let bbox = child.bbox()
|
|
||||||
ctx.rect(bbox.x.min-child.x, bbox.y.min-child.y, bbox.x.max-bbox.x.min, bbox.y.max-bbox.y.min)
|
|
||||||
ctx.stroke()
|
|
||||||
}
|
|
||||||
ctx.setTransform(transform)
|
|
||||||
}
|
}
|
||||||
if (this.activeShape) {
|
if (this.activeShape) {
|
||||||
this.activeShape.draw(cxt)
|
this.activeShape.draw(cxt)
|
||||||
|
|
@ -2865,14 +2868,49 @@ class AudioLayer {
|
||||||
const audioLayer = new AudioLayer(json.idx, json.name);
|
const audioLayer = new AudioLayer(json.idx, json.name);
|
||||||
// TODO: load audiolayer from json
|
// TODO: load audiolayer from json
|
||||||
audioLayer.sounds = {};
|
audioLayer.sounds = {};
|
||||||
|
for (let id in json.sounds) {
|
||||||
|
const jsonSound = json.sounds[id]
|
||||||
|
const img = new Image();
|
||||||
|
img.className = "audioWaveform";
|
||||||
|
const player = new Tone.Player().toDestination();
|
||||||
|
player.load(jsonSound.src)
|
||||||
|
.then(() => {
|
||||||
|
generateWaveform(img, player.buffer, 50, 25, config.framerate);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Handle any errors that occur during the load or waveform generation
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
let soundObj = {
|
||||||
|
player: player,
|
||||||
|
start: jsonSound.start,
|
||||||
|
img: img,
|
||||||
|
src: jsonSound.src,
|
||||||
|
uuid: jsonSound.uuid
|
||||||
|
};
|
||||||
|
pointerList[jsonSound.uuid] = soundObj;
|
||||||
|
audioLayer.sounds[jsonSound.uuid] = soundObj;
|
||||||
|
// TODO: change start time
|
||||||
|
audioLayer.track.add(0, jsonSound.uuid);
|
||||||
|
}
|
||||||
audioLayer.audible = json.audible;
|
audioLayer.audible = json.audible;
|
||||||
return audioLayer;
|
return audioLayer;
|
||||||
}
|
}
|
||||||
toJSON(randomizeUuid = false) {
|
toJSON(randomizeUuid = false) {
|
||||||
|
console.log(this.sounds)
|
||||||
const json = {};
|
const json = {};
|
||||||
json.type = "AudioLayer";
|
json.type = "AudioLayer";
|
||||||
// TODO: build json from audiolayer
|
// TODO: build json from audiolayer
|
||||||
json.sounds = {};
|
json.sounds = {};
|
||||||
|
for (let id in this.sounds) {
|
||||||
|
const sound = this.sounds[id]
|
||||||
|
json.sounds[id] = {
|
||||||
|
start: sound.start,
|
||||||
|
src: sound.src,
|
||||||
|
uuid: sound.uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
json.audible = this.audible;
|
json.audible = this.audible;
|
||||||
if (randomizeUuid) {
|
if (randomizeUuid) {
|
||||||
json.idx = uuidv4();
|
json.idx = uuidv4();
|
||||||
|
|
@ -4135,7 +4173,7 @@ async function greet() {
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
rootPane = document.querySelector("#root");
|
rootPane = document.querySelector("#root");
|
||||||
rootPane.appendChild(createPane(panes.toolbar));
|
rootPane.appendChild(createPane(panes.toolbar));
|
||||||
rootPane.addEventListener("mousemove", (e) => {
|
rootPane.addEventListener("pointermove", (e) => {
|
||||||
mouseEvent = e;
|
mouseEvent = e;
|
||||||
});
|
});
|
||||||
let [_toolbar, panel] = splitPane(
|
let [_toolbar, panel] = splitPane(
|
||||||
|
|
@ -4268,6 +4306,9 @@ window.addEventListener("keydown", (e) => {
|
||||||
function playPause() {
|
function playPause() {
|
||||||
playing = !playing;
|
playing = !playing;
|
||||||
if (playing) {
|
if (playing) {
|
||||||
|
if (context.activeObject.currentFrameNum >= context.activeObject.maxFrame - 1) {
|
||||||
|
context.activeObject.setFrameNum(0);
|
||||||
|
}
|
||||||
for (let audioLayer of context.activeObject.audioLayers) {
|
for (let audioLayer of context.activeObject.audioLayers) {
|
||||||
if (audioLayer.audible) {
|
if (audioLayer.audible) {
|
||||||
for (let i in audioLayer.sounds) {
|
for (let i in audioLayer.sounds) {
|
||||||
|
|
@ -4369,7 +4410,7 @@ async function _save(path) {
|
||||||
// console.log(action.name);
|
// console.log(action.name);
|
||||||
// }
|
// }
|
||||||
const fileData = {
|
const fileData = {
|
||||||
version: "1.7.5",
|
version: "1.7.6",
|
||||||
width: config.fileWidth,
|
width: config.fileWidth,
|
||||||
height: config.fileHeight,
|
height: config.fileHeight,
|
||||||
fps: config.framerate,
|
fps: config.framerate,
|
||||||
|
|
@ -4543,6 +4584,60 @@ async function _open(path, returnJson = false) {
|
||||||
undoStack.push(action);
|
undoStack.push(action);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (file.version < "1.7.6") {
|
||||||
|
function restoreLineColors(obj) {
|
||||||
|
// Step 1: Create colorMapping dictionary
|
||||||
|
const colorMapping = (obj.actions || []).reduce((map, action) => {
|
||||||
|
if (action.name === "addShape" && action.action.curves.length > 0) {
|
||||||
|
map[action.action.uuid] = action.action.curves[0].color;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Step 2: Recursive pass to add colors from colorMapping back to curves
|
||||||
|
function recurse(item) {
|
||||||
|
if (item?.curves && item.idx && colorMapping[item.idx]) {
|
||||||
|
item.curves.forEach(curve => {
|
||||||
|
if (Array.isArray(curve)) curve.push(colorMapping[item.idx]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Object.values(item).forEach(value => {
|
||||||
|
if (typeof value === 'object' && value !== null) recurse(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
recurse(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreLineColors(file)
|
||||||
|
|
||||||
|
function restoreAudio(obj) {
|
||||||
|
const audioSrcMapping = (obj.actions || []).reduce((map, action) => {
|
||||||
|
if (action.name === "addAudio") {
|
||||||
|
map[action.action.layeruuid] = action.action;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
function recurse(item) {
|
||||||
|
if (item.type=="AudioLayer" && audioSrcMapping[item.idx]) {
|
||||||
|
const action = audioSrcMapping[item.idx]
|
||||||
|
item.sounds[action.uuid] = {
|
||||||
|
start: action.frameNum,
|
||||||
|
src: action.audiosrc,
|
||||||
|
uuid: action.uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.values(item).forEach(value => {
|
||||||
|
if (typeof value === 'object' && value !== null) recurse(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
recurse(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreAudio(file)
|
||||||
|
}
|
||||||
// disabled for now
|
// disabled for now
|
||||||
// for (let action of file.actions) {
|
// for (let action of file.actions) {
|
||||||
// undoStack.push(action)
|
// undoStack.push(action)
|
||||||
|
|
@ -4841,7 +4936,7 @@ function createProgressModal() {
|
||||||
// Create text to show the current frame info
|
// Create text to show the current frame info
|
||||||
const progressText = document.createElement('p');
|
const progressText = document.createElement('p');
|
||||||
progressText.id = 'progressText';
|
progressText.id = 'progressText';
|
||||||
progressText.innerText = 'Rendering frame 0 of 0';
|
progressText.innerText = 'Initializing...';
|
||||||
|
|
||||||
// Append elements to modalContent
|
// Append elements to modalContent
|
||||||
modalContent.appendChild(progressBar);
|
modalContent.appendChild(progressBar);
|
||||||
|
|
@ -4865,6 +4960,8 @@ async function setupVideoExport(ext, path, canvas, exportContext) {
|
||||||
let muxer;
|
let muxer;
|
||||||
let videoEncoder;
|
let videoEncoder;
|
||||||
let videoConfig;
|
let videoConfig;
|
||||||
|
let audioEncoder;
|
||||||
|
let audioConfig;
|
||||||
const frameTimeMicroseconds = parseInt(1_000_000 / config.framerate)
|
const frameTimeMicroseconds = parseInt(1_000_000 / config.framerate)
|
||||||
const oldContext = context;
|
const oldContext = context;
|
||||||
context = exportContext;
|
context = exportContext;
|
||||||
|
|
@ -4894,6 +4991,14 @@ async function setupVideoExport(ext, path, canvas, exportContext) {
|
||||||
height: config.fileHeight,
|
height: config.fileHeight,
|
||||||
bitrate: bitrate,
|
bitrate: bitrate,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Todo: add configuration for mono/stereo
|
||||||
|
audioConfig = {
|
||||||
|
codec: 'mp4a.40.2', // AAC codec
|
||||||
|
sampleRate: 44100,
|
||||||
|
numberOfChannels: 2, // Mono
|
||||||
|
bitrate: 64000,
|
||||||
|
};
|
||||||
} else if (ext === "webm") {
|
} else if (ext === "webm") {
|
||||||
target = new WebMMuxer.ArrayBufferTarget();
|
target = new WebMMuxer.ArrayBufferTarget();
|
||||||
muxer = new WebMMuxer.Muxer({
|
muxer = new WebMMuxer.Muxer({
|
||||||
|
|
@ -4914,6 +5019,13 @@ async function setupVideoExport(ext, path, canvas, exportContext) {
|
||||||
bitrate: bitrate,
|
bitrate: bitrate,
|
||||||
bitrateMode: "constant",
|
bitrateMode: "constant",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
audioConfig = {
|
||||||
|
codec: 'opus', // Use Opus codec for WebM
|
||||||
|
sampleRate: 48000,
|
||||||
|
numberOfChannels: 2,
|
||||||
|
bitrate: 64000,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the video encoder
|
// Initialize the video encoder
|
||||||
|
|
@ -4924,18 +5036,12 @@ async function setupVideoExport(ext, path, canvas, exportContext) {
|
||||||
|
|
||||||
videoEncoder.configure(videoConfig);
|
videoEncoder.configure(videoConfig);
|
||||||
|
|
||||||
async function finishEncoding() {
|
// audioEncoder = new AudioEncoder({
|
||||||
const progressText = document.getElementById('progressText');
|
// output: (chunk, meta) => muxer.addAudioChunk(chunk, meta),
|
||||||
progressText.innerText = 'Finalizing...';
|
// error: (e) => console.error(e),
|
||||||
const progressBar = document.getElementById('progressBar');
|
// });
|
||||||
progressBar.value = 100;
|
|
||||||
await videoEncoder.flush();
|
// audioEncoder.configure(audioConfig)
|
||||||
muxer.finalize();
|
|
||||||
await writeFile(path, new Uint8Array(target.buffer));
|
|
||||||
const modal = document.getElementById('progressModal');
|
|
||||||
modal.style.display = 'none';
|
|
||||||
document.querySelector("body").style.cursor = "default";
|
|
||||||
}
|
|
||||||
|
|
||||||
const processFrame = async (currentFrame) => {
|
const processFrame = async (currentFrame) => {
|
||||||
if (currentFrame < root.maxFrame) {
|
if (currentFrame < root.maxFrame) {
|
||||||
|
|
@ -5319,7 +5425,7 @@ function stage() {
|
||||||
// stageWrapper.appendChild(stage)
|
// stageWrapper.appendChild(stage)
|
||||||
// stageWrapper.appendChild(selectionRect)
|
// stageWrapper.appendChild(selectionRect)
|
||||||
// scroller.appendChild(stageWrapper)
|
// scroller.appendChild(stageWrapper)
|
||||||
stage.addEventListener("mousedown", (e) => {
|
stage.addEventListener("pointerdown", (e) => {
|
||||||
let mouse = getMousePos(stage, e);
|
let mouse = getMousePos(stage, e);
|
||||||
root.handleMouseEvent("mousedown", mouse.x, mouse.y)
|
root.handleMouseEvent("mousedown", mouse.x, mouse.y)
|
||||||
mouse = context.activeObject.transformMouse(mouse);
|
mouse = context.activeObject.transformMouse(mouse);
|
||||||
|
|
@ -5693,8 +5799,8 @@ function stage() {
|
||||||
updateMenu();
|
updateMenu();
|
||||||
updateInfopanel();
|
updateInfopanel();
|
||||||
};
|
};
|
||||||
stage.addEventListener("mouseup", stage.mouseup);
|
stage.addEventListener("pointerup", stage.mouseup);
|
||||||
stage.addEventListener("mousemove", (e) => {
|
stage.addEventListener("pointermove", (e) => {
|
||||||
let mouse = getMousePos(stage, e);
|
let mouse = getMousePos(stage, e);
|
||||||
root.handleMouseEvent("mousemove", mouse.x, mouse.y)
|
root.handleMouseEvent("mousemove", mouse.x, mouse.y)
|
||||||
mouse = context.activeObject.transformMouse(mouse);
|
mouse = context.activeObject.transformMouse(mouse);
|
||||||
|
|
@ -6104,7 +6210,7 @@ function toolbar() {
|
||||||
colorCvs.colorSelectorWidget.draw(ctx)
|
colorCvs.colorSelectorWidget.draw(ctx)
|
||||||
|
|
||||||
};
|
};
|
||||||
colorCvs.addEventListener("mousedown", (e) => {
|
colorCvs.addEventListener("pointerdown", (e) => {
|
||||||
colorCvs.clickedMainGradient = false;
|
colorCvs.clickedMainGradient = false;
|
||||||
colorCvs.clickedHueGradient = false;
|
colorCvs.clickedHueGradient = false;
|
||||||
colorCvs.clickedAlphaGradient = false;
|
colorCvs.clickedAlphaGradient = false;
|
||||||
|
|
@ -6114,7 +6220,7 @@ function toolbar() {
|
||||||
colorCvs.draw();
|
colorCvs.draw();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("mouseup", (e) => {
|
window.addEventListener("pointerup", (e) => {
|
||||||
let mouse = getMousePos(colorCvs, e);
|
let mouse = getMousePos(colorCvs, e);
|
||||||
colorCvs.clickedMainGradient = false;
|
colorCvs.clickedMainGradient = false;
|
||||||
colorCvs.clickedHueGradient = false;
|
colorCvs.clickedHueGradient = false;
|
||||||
|
|
@ -6123,13 +6229,13 @@ function toolbar() {
|
||||||
colorCvs.colorSelectorWidget.handleMouseEvent("mouseup", mouse.x, mouse.y)
|
colorCvs.colorSelectorWidget.handleMouseEvent("mouseup", mouse.x, mouse.y)
|
||||||
if (e.target != colorCvs) {
|
if (e.target != colorCvs) {
|
||||||
colorCvs.style.display = "none";
|
colorCvs.style.display = "none";
|
||||||
window.removeEventListener("mousemove", evtListener);
|
window.removeEventListener("pointermove", evtListener);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
colorCvs.style.display = "block";
|
colorCvs.style.display = "block";
|
||||||
}
|
}
|
||||||
evtListener = window.addEventListener("mousemove", (e) => {
|
evtListener = window.addEventListener("pointermove", (e) => {
|
||||||
let mouse = getMousePos(colorCvs, e);
|
let mouse = getMousePos(colorCvs, e);
|
||||||
colorCvs.colorSelectorWidget.handleMouseEvent("mousemove", mouse.x, mouse.y)
|
colorCvs.colorSelectorWidget.handleMouseEvent("mousemove", mouse.x, mouse.y)
|
||||||
colorCvs.draw()
|
colorCvs.draw()
|
||||||
|
|
@ -6260,7 +6366,7 @@ function timeline() {
|
||||||
updateLayers();
|
updateLayers();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
timeline_cvs.addEventListener("mousedown", (e) => {
|
timeline_cvs.addEventListener("pointerdown", (e) => {
|
||||||
let mouse = getMousePos(timeline_cvs, e, true, true);
|
let mouse = getMousePos(timeline_cvs, e, true, true);
|
||||||
mouse.y += timeline_cvs.offsetY;
|
mouse.y += timeline_cvs.offsetY;
|
||||||
if (mouse.x > layerWidth) {
|
if (mouse.x > layerWidth) {
|
||||||
|
|
@ -6392,7 +6498,7 @@ function timeline() {
|
||||||
}
|
}
|
||||||
updateLayers();
|
updateLayers();
|
||||||
});
|
});
|
||||||
timeline_cvs.addEventListener("mouseup", (e) => {
|
timeline_cvs.addEventListener("pointerup", (e) => {
|
||||||
let mouse = getMousePos(timeline_cvs, e);
|
let mouse = getMousePos(timeline_cvs, e);
|
||||||
mouse.y += timeline_cvs.offsetY;
|
mouse.y += timeline_cvs.offsetY;
|
||||||
if (mouse.x > layerWidth || timeline_cvs.draggingFrames) {
|
if (mouse.x > layerWidth || timeline_cvs.draggingFrames) {
|
||||||
|
|
@ -6412,7 +6518,7 @@ function timeline() {
|
||||||
updateMenu();
|
updateMenu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
timeline_cvs.addEventListener("mousemove", (e) => {
|
timeline_cvs.addEventListener("pointermove", (e) => {
|
||||||
let mouse = getMousePos(timeline_cvs, e);
|
let mouse = getMousePos(timeline_cvs, e);
|
||||||
mouse.y += timeline_cvs.offsetY;
|
mouse.y += timeline_cvs.offsetY;
|
||||||
if (mouse.x > layerWidth || timeline_cvs.draggingFrames) {
|
if (mouse.x > layerWidth || timeline_cvs.draggingFrames) {
|
||||||
|
|
@ -6704,7 +6810,7 @@ function splitPane(div, percent, horiz, newPane = undefined) {
|
||||||
div.className = "vertical-grid";
|
div.className = "vertical-grid";
|
||||||
}
|
}
|
||||||
div.setAttribute("lb-percent", percent); // TODO: better attribute name
|
div.setAttribute("lb-percent", percent); // TODO: better attribute name
|
||||||
div.addEventListener("mousedown", function (event) {
|
div.addEventListener("pointerdown", function (event) {
|
||||||
// Check if the clicked element is the parent itself and not a child element
|
// Check if the clicked element is the parent itself and not a child element
|
||||||
if (event.target === event.currentTarget) {
|
if (event.target === event.currentTarget) {
|
||||||
if (event.button === 0) {
|
if (event.button === 0) {
|
||||||
|
|
@ -6728,7 +6834,7 @@ function splitPane(div, percent, horiz, newPane = undefined) {
|
||||||
splitIndicator.style.flexDirection =
|
splitIndicator.style.flexDirection =
|
||||||
direction == "vertical" ? "column" : "row";
|
direction == "vertical" ? "column" : "row";
|
||||||
document.body.appendChild(splitIndicator);
|
document.body.appendChild(splitIndicator);
|
||||||
splitIndicator.addEventListener("mousemove", (e) => {
|
splitIndicator.addEventListener("pointermove", (e) => {
|
||||||
const { clientX: mouseX, clientY: mouseY } = e;
|
const { clientX: mouseX, clientY: mouseY } = e;
|
||||||
const rect = splitIndicator.getBoundingClientRect();
|
const rect = splitIndicator.getBoundingClientRect();
|
||||||
|
|
||||||
|
|
@ -6789,12 +6895,12 @@ function splitPane(div, percent, horiz, newPane = undefined) {
|
||||||
createPane(panes.timeline),
|
createPane(panes.timeline),
|
||||||
);
|
);
|
||||||
document.body.removeChild(splitIndicator);
|
document.body.removeChild(splitIndicator);
|
||||||
document.removeEventListener("mousemove", splitListener);
|
document.removeEventListener("pointermove", splitListener);
|
||||||
setTimeout(updateUI, 20);
|
setTimeout(updateUI, 20);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const splitListener = document.addEventListener("mousemove", (e) => {
|
const splitListener = document.addEventListener("pointermove", (e) => {
|
||||||
const mouseX = e.clientX;
|
const mouseX = e.clientX;
|
||||||
const mouseY = e.clientY;
|
const mouseY = e.clientY;
|
||||||
|
|
||||||
|
|
@ -6848,7 +6954,7 @@ function splitPane(div, percent, horiz, newPane = undefined) {
|
||||||
console.log("Right-click on the element");
|
console.log("Right-click on the element");
|
||||||
// Your custom logic here
|
// Your custom logic here
|
||||||
});
|
});
|
||||||
div.addEventListener("mousemove", function (event) {
|
div.addEventListener("pointermove", function (event) {
|
||||||
// Check if the clicked element is the parent itself and not a child element
|
// Check if the clicked element is the parent itself and not a child element
|
||||||
if (event.currentTarget.getAttribute("dragging") == "true") {
|
if (event.currentTarget.getAttribute("dragging") == "true") {
|
||||||
const frac = getMousePositionFraction(event, event.currentTarget);
|
const frac = getMousePositionFraction(event, event.currentTarget);
|
||||||
|
|
@ -6856,7 +6962,7 @@ function splitPane(div, percent, horiz, newPane = undefined) {
|
||||||
updateAll();
|
updateAll();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
div.addEventListener("mouseup", (event) => {
|
div.addEventListener("pointerup", (event) => {
|
||||||
event.currentTarget.setAttribute("dragging", false);
|
event.currentTarget.setAttribute("dragging", false);
|
||||||
// event.currentTarget.style.userSelect = 'auto';
|
// event.currentTarget.style.userSelect = 'auto';
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -490,7 +490,7 @@ button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#overlay {
|
#overlay, #saveOverlay {
|
||||||
display: none; /* Hidden by default */
|
display: none; /* Hidden by default */
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -502,7 +502,7 @@ button {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scoped styles for the dialog */
|
/* Scoped styles for the dialog */
|
||||||
#newFileDialog {
|
#newFileDialog, #saveDialog {
|
||||||
display: none; /* Hidden by default */
|
display: none; /* Hidden by default */
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
|
@ -517,19 +517,19 @@ button {
|
||||||
z-index: 1000; /* Make sure it's in front of other elements */
|
z-index: 1000; /* Make sure it's in front of other elements */
|
||||||
}
|
}
|
||||||
|
|
||||||
#newFileDialog .dialog-label {
|
#newFileDialog .dialog-label, #saveDialog label {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 10px 0 5px;
|
margin: 10px 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#newFileDialog .dialog-input {
|
#newFileDialog .dialog-input, #saveDialog input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
border: 1px solid #aaa;
|
border: 1px solid #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
#newFileDialog .dialog-button {
|
#newFileDialog .dialog-button, #saveDialog button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
|
@ -632,11 +632,11 @@ button {
|
||||||
background-color: #0f0f0f69;
|
background-color: #0f0f0f69;
|
||||||
}
|
}
|
||||||
|
|
||||||
#newFileDialog {
|
#newFileDialog, #saveDialog {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
border: 1px solid #333;
|
border: 1px solid #333;
|
||||||
}
|
}
|
||||||
#newFileDialog .dialog-input {
|
#newFileDialog .dialog-input, #saveDialog input {
|
||||||
border: 1px solid #333;
|
border: 1px solid #333;
|
||||||
}
|
}
|
||||||
#recentFilesList li:hover {
|
#recentFilesList li:hover {
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,107 @@ if (!window.__TAURI__) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function promptForFilename(filters, defaultFilename = '') {
|
||||||
|
function createLabel(text, forId) {
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.setAttribute('for', forId);
|
||||||
|
label.textContent = text;
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Create and style modal dynamically
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
const modalContent = document.createElement('div');
|
||||||
|
const filenameInput = document.createElement('input');
|
||||||
|
const fileFilter = document.createElement('select');
|
||||||
|
const submitBtn = document.createElement('button');
|
||||||
|
const cancelBtn = document.createElement('button');
|
||||||
|
|
||||||
|
// Append elements
|
||||||
|
modal.appendChild(modalContent);
|
||||||
|
modalContent.appendChild(createLabel('Enter filename:', 'filenameInput'));
|
||||||
|
modalContent.appendChild(filenameInput);
|
||||||
|
modalContent.appendChild(createLabel('Select file type:', 'fileFilter'));
|
||||||
|
modalContent.appendChild(fileFilter);
|
||||||
|
modalContent.appendChild(submitBtn);
|
||||||
|
modalContent.appendChild(cancelBtn);
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
// Style modal elements
|
||||||
|
modal.id = "saveOverlay";
|
||||||
|
modal.style.display = 'block';
|
||||||
|
modalContent.id = "saveDialog";
|
||||||
|
modalContent.style.display = 'block';
|
||||||
|
[filenameInput, fileFilter].forEach(el => Object.assign(el.style, {
|
||||||
|
width: '100%', padding: '10px', marginBottom: '10px'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Populate filter dropdown and set default filename
|
||||||
|
filters.forEach(filter => fileFilter.add(new Option(filter.name, filter.extensions[0])));
|
||||||
|
filenameInput.value = defaultFilename
|
||||||
|
const extension = defaultFilename.split('.').pop();
|
||||||
|
filenameInput.focus()
|
||||||
|
filenameInput.setSelectionRange(0, defaultFilename.length - extension.length - 1); // Select only the base filename
|
||||||
|
|
||||||
|
// Update extension based on selected filter
|
||||||
|
fileFilter.addEventListener('change', () => updateFilename(true));
|
||||||
|
filenameInput.addEventListener('input', () => updateFilename(false));
|
||||||
|
|
||||||
|
function updateFilename(reselect) {
|
||||||
|
const base = filenameInput.value.split('.')[0];
|
||||||
|
filenameInput.value = `${base}.${fileFilter.value}`;
|
||||||
|
if (reselect) {
|
||||||
|
filenameInput.focus()
|
||||||
|
filenameInput.setSelectionRange(0, base.length); // Select only the base filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle buttons
|
||||||
|
submitBtn.textContent = 'Submit';
|
||||||
|
cancelBtn.textContent = 'Cancel';
|
||||||
|
submitBtn.onclick = () => {
|
||||||
|
const chosenFilename = filenameInput.value;
|
||||||
|
if (!chosenFilename) reject(new Error('Filename missing.'));
|
||||||
|
resolve(chosenFilename);
|
||||||
|
modal.remove();
|
||||||
|
};
|
||||||
|
cancelBtn.onclick = () => {
|
||||||
|
reject(new Error('User canceled.'));
|
||||||
|
modal.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close modal if clicked outside
|
||||||
|
window.addEventListener('click', (e) => {
|
||||||
|
if (e.target === modal) {
|
||||||
|
reject(new Error('User clicked outside.'));
|
||||||
|
modal.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.__TAURI__ = {
|
window.__TAURI__ = {
|
||||||
core: {
|
core: {
|
||||||
invoke: () => {}
|
invoke: () => {}
|
||||||
},
|
},
|
||||||
fs: {
|
fs: {
|
||||||
writeFile: () => {},
|
writeFile: (path, contents) => {
|
||||||
|
// Create a Blob from the contents
|
||||||
|
const blob = new Blob([contents]);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
link.href = url;
|
||||||
|
link.download = path; // Use the file name from the path
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// Clean up by removing the link and revoking the object URL
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
},
|
||||||
readFile: () => {},
|
readFile: () => {},
|
||||||
writeTextFile: async (path, contents) => {
|
writeTextFile: async (path, contents) => {
|
||||||
// Create a Blob from the contents
|
// Create a Blob from the contents
|
||||||
|
|
@ -169,8 +264,8 @@ if (!window.__TAURI__) {
|
||||||
input.click();
|
input.click();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
save: async () => {
|
save: async (params) => {
|
||||||
return prompt("Filename", "untitled.beam")
|
return await promptForFilename(params.filters, params.defaultPath)
|
||||||
},
|
},
|
||||||
message: () => {},
|
message: () => {},
|
||||||
confirm: () => {},
|
confirm: () => {},
|
||||||
|
|
@ -178,7 +273,7 @@ if (!window.__TAURI__) {
|
||||||
path: {
|
path: {
|
||||||
documentDir: () => {},
|
documentDir: () => {},
|
||||||
join: (...segments) => {
|
join: (...segments) => {
|
||||||
return segments.filter(segment => segment.length > 0) // Remove empty strings
|
return segments.filter(segment => (segment && segment.length > 0)) // Remove empty strings
|
||||||
.join('/')
|
.join('/')
|
||||||
},
|
},
|
||||||
basename: (path) => {
|
basename: (path) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue