Merge branch 'main' into release

This commit is contained in:
Skyler Lehmkuhl 2025-01-15 21:15:49 -05:00
commit 25248f5088
5 changed files with 290 additions and 87 deletions

View File

@ -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": [

View File

@ -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() {

View File

@ -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';
}); });

View File

@ -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 {

View File

@ -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) => {