Templates #1

Merged
valerio merged 5 commits from templating into master 2026-07-25 17:34:46 +00:00
9 changed files with 77 additions and 24 deletions
Showing only changes of commit b1e3644e9d - Show all commits

View File

@ -2,12 +2,16 @@
import { getCurrentWindow } from '@tauri-apps/api/window'; import { getCurrentWindow } from '@tauri-apps/api/window';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { addTemplateFile } from '$lib/file_utils'; import { addTemplateFile } from '$lib/file_utils';
import Popup from '$lib/components/Popup.svelte';
let dragging = $state(false); let dragging = $state(false);
let hasBlend = $state(false); let hasBlend = $state(false);
let dropzoneEl = $state(null); let dropzoneEl = $state(null);
let { version } = $props(); let { version } = $props();
let successPopup;
let errorPopup;
onMount(() => { onMount(() => {
const win = getCurrentWindow(); const win = getCurrentWindow();
let unlisten = () => {}; let unlisten = () => {};
@ -47,9 +51,14 @@
const blendFiles = payload.paths.filter((p) => p.endsWith('.blend')); const blendFiles = payload.paths.filter((p) => p.endsWith('.blend'));
if (blendFiles.length > 0) { if (blendFiles.length > 0) {
console.log('Dropped Blender files:', blendFiles); console.log('Dropped Blender files:', blendFiles);
blendFiles.forEach((file) => { blendFiles.forEach(async (file) => {
if (version) { if (version) {
addTemplateFile(version, file); const result = await addTemplateFile(version, file);
if (result.success) {
successPopup.pop(result.message, 3000);
} else {
errorPopup.pop(result.message, 4000);
}
} }
}); });
} }
@ -66,6 +75,9 @@
<div class="dropzone" class:dragging bind:this={dropzoneEl}>Drop Blender Files</div> <div class="dropzone" class:dragging bind:this={dropzoneEl}>Drop Blender Files</div>
<Popup bind:this={successPopup} type="confirm" />
<Popup bind:this={errorPopup} type="error" />
<style> <style>
.dropzone { .dropzone {
height: 3rem; height: 3rem;

View File

@ -1,10 +1,12 @@
<script> <script>
import { fade, fly } from 'svelte/transition'; import { fade, fly } from 'svelte/transition';
let { content = '', type = 'confirm' } = $props(); let { type = 'confirm' } = $props();
let open = $state(false); let open = $state(false);
let message = $state('');
export function pop(resetTimeout = 0) { export function pop(msg = '', resetTimeout = 0) {
message = msg;
open = true; open = true;
if (resetTimeout > 0) { if (resetTimeout > 0) {
setTimeout(() => { setTimeout(() => {
@ -17,7 +19,7 @@
{#if open} {#if open}
<div in:fly={{ duration: 200, y: 100 }} out:fade class="popup {type}"> <div in:fly={{ duration: 200, y: 100 }} out:fade class="popup {type}">
<div class="popup-content"> <div class="popup-content">
<p>{content}</p> <p>{message}</p>
</div> </div>
</div> </div>
{/if} {/if}
@ -35,4 +37,7 @@
.confirm { .confirm {
background-color: var(--light-accent); background-color: var(--light-accent);
} }
.error {
background-color: var(--accent);
}
</style> </style>

View File

@ -14,7 +14,7 @@
async function handleDownload() { async function handleDownload() {
let result = await downloadBlenderVersion(selectedLink); let result = await downloadBlenderVersion(selectedLink);
if (result.success) { if (result.success) {
popup?.pop(2000); popup?.pop('Blender ' + selectedLink.version + ' has been installed', 2000);
} }
} }
@ -29,8 +29,7 @@
let popup = $state(); let popup = $state();
</script> </script>
<Popup bind:this={popup} content={'Blender ' + selectedLink.version + ' has been installed'} <Popup bind:this={popup}></Popup>
></Popup>
<div class="download" class:full-width={downloading || installed}> <div class="download" class:full-width={downloading || installed}>
<div class="selectVersion {downloading}"> <div class="selectVersion {downloading}">
<select class:disabled={downloading && !installed} bind:value={linkIndex}> <select class:disabled={downloading && !installed} bind:value={linkIndex}>

View File

@ -6,6 +6,7 @@
import { deleteTemplate } from '$lib/library'; import { deleteTemplate } from '$lib/library';
import { launchBlenderVersion } from '$lib/library'; import { launchBlenderVersion } from '$lib/library';
import { notify } from '$lib/notification.js';
let { template, version } = $props(); let { template, version } = $props();
let templateName = $derived( let templateName = $derived(
template template
@ -22,13 +23,18 @@
*/ */
async function handleDelete(template) { async function handleDelete(template) {
const prompt = await show({ const prompt = await show({
title: 'Delete', title: 'Delete Template',
message: `This will remove Blender ${version.version} and all associated files from your system.`, message: `This will delete the "${templateName}" template.`,
buttons: ['Cancel', 'Continue'], buttons: ['Cancel', 'Continue'],
type: 'warning' type: 'warning'
}); });
if (prompt == 'Continue') { if (prompt == 'Continue') {
deleteTemplate(version, template); const result = await deleteTemplate(version, template);
if (result.success) {
notify(result.message, 3000);
} else {
notify(result.message, 4000);
}
} }
} }
</script> </script>

View File

@ -1,5 +1,4 @@
<script> <script>
import Popup from '$lib/components/Popup.svelte';
import { import {
deleteVersion, deleteVersion,
launchBlenderVersion, launchBlenderVersion,
@ -12,9 +11,9 @@
import MenuItem from '$lib/components/MenuItem.svelte'; import MenuItem from '$lib/components/MenuItem.svelte';
import { show } from '$lib/components/Dialog.svelte'; import { show } from '$lib/components/Dialog.svelte';
import { notify } from '$lib/notification.js';
let { version, selectedVersion } = $props(); let { version, selectedVersion } = $props();
let popup = $state();
let selected = $derived(version.version === selectedVersion?.version); let selected = $derived(version.version === selectedVersion?.version);
@ -26,7 +25,12 @@
type: 'warning' type: 'warning'
}); });
if (prompt == 'Continue') { if (prompt == 'Continue') {
deleteVersion(version); const result = await deleteVersion(version);
if (result.success) {
notify(result.message, 3000);
} else {
notify(result.message, 4000);
}
} }
} }
@ -38,13 +42,11 @@
type: 'info' type: 'info'
}); });
if (prompt == 'Continue') { if (prompt == 'Continue') {
popup.pop(1500); notify('Version ' + version.version + ' is now the default on your system', 1500);
registerVersion(version); registerVersion(version);
} }
} }
</script> </script>
<Popup bind:this={popup} content="Version {version.version} is now the default on your system" />
<div <div
role="button" role="button"
tabindex="0" tabindex="0"

View File

@ -4,7 +4,7 @@ import {
basename as pathBasename, basename as pathBasename,
dirname as pathDirname dirname as pathDirname
} from '@tauri-apps/api/path'; } from '@tauri-apps/api/path';
import { stat, mkdir, readDir, copyFile, rename, remove } from '@tauri-apps/plugin-fs'; import { stat, mkdir, readDir, copyFile, rename, remove, exists } from '@tauri-apps/plugin-fs';
import { open } from '@tauri-apps/plugin-dialog'; import { open } from '@tauri-apps/plugin-dialog';
import baseicon from '$lib/assets/baseicon.png'; import baseicon from '$lib/assets/baseicon.png';
import europa from '$lib/assets/fonts/Europa-Mono-Medium.otf'; import europa from '$lib/assets/fonts/Europa-Mono-Medium.otf';
@ -124,19 +124,25 @@ export async function addTemplateFile(version, filePath) {
const newFileName = await pathBasename(filePath); const newFileName = await pathBasename(filePath);
const templateName = newFileName.replace(/\.blend$/i, ''); const templateName = newFileName.replace(/\.blend$/i, '');
const newTemplatePath = await join(await getTemplatesDir(version), templateName); const newTemplatePath = await join(await getTemplatesDir(version), templateName);
await ensureDirectory(newTemplatePath)
// Check for duplicate template name
if (await exists(newTemplatePath)) {
return { success: false, message: `Template "${templateName}" already exists` };
}
const newTemplateFile = await join(newTemplatePath, 'startup.blend'); const newTemplateFile = await join(newTemplatePath, 'startup.blend');
await ensureDirectory(newTemplatePath);
try { try {
await copyFile(filePath, newTemplateFile); await copyFile(filePath, newTemplateFile);
} catch (error) { } catch (error) {
console.error('Error adding template file:', error); console.error('Error adding template file:', error);
return; return { success: false, message: `Failed to add template: ${error.message || error}` };
} }
await getInstalledVersions(); await getInstalledVersions();
console.log('adding ', filePath, ' to ', newTemplateFile); return { success: true, message: `Template "${templateName}" added` };
} }
/** /**

View File

@ -255,8 +255,10 @@ export async function deleteTemplate(version, template) {
try { try {
await removeTemplate(templatePath); await removeTemplate(templatePath);
await getInstalledVersions(); await getInstalledVersions();
return { success: true, message: `Template "${template}" deleted` };
} catch (error) { } catch (error) {
console.error('Failed to delete template', error); console.error('Failed to delete template', error);
return { success: false, message: `Failed to delete template: ${error.message || error}` };
} }
} }
@ -282,9 +284,10 @@ export async function deleteVersion(version) {
installedVersionsStore.set(filteredVersions); installedVersionsStore.set(filteredVersions);
console.log(`Successfully deleted version ${version.version}`); console.log(`Successfully deleted version ${version.version}`);
return { success: true, message: `Blender ${version.version} deleted` };
} catch (error) { } catch (error) {
console.error(`Failed to delete version ${version.version}:`, error); console.error(`Failed to delete version ${version.version}:`, error);
throw error; return { success: false, message: `Failed to delete Blender ${version.version}: ${error.message || error}` };
} }
} }

12
src/lib/notification.js Normal file
View File

@ -0,0 +1,12 @@
import { writable } from 'svelte/store';
export const notification = writable(null);
/**
* Show a notification popup.
* @param {string} message - The message to display
* @param {number} [timeout=3000] - Auto-dismiss timeout in milliseconds
*/
export function notify(message, timeout = 3000) {
notification.set({ message, timeout });
}

View File

@ -18,6 +18,7 @@
import Popup from '$lib/components/Popup.svelte'; import Popup from '$lib/components/Popup.svelte';
import Dialog from '$lib/components/Dialog.svelte'; import Dialog from '$lib/components/Dialog.svelte';
import Splash from '$lib/components/Splash.svelte'; import Splash from '$lib/components/Splash.svelte';
import { notification } from '$lib/notification.js';
// states // states
let fadeInSettings = { duration: 100 }; let fadeInSettings = { duration: 100 };
@ -27,10 +28,17 @@
let installedVersions = $state([]); let installedVersions = $state([]);
let downloadTasks = $state([]); let downloadTasks = $state([]);
let settings = $state(); let settings = $state();
let notificationPopup;
let initialized = $state(false); let initialized = $state(false);
let version = $state(''); let version = $state('');
let loadingState = $state('loading'); // load, update, initialized let loadingState = $state('loading'); // load, update, initialized
notification.subscribe((n) => {
if (n) {
notificationPopup?.pop(n.message, n.timeout);
}
});
async function initStoresListeners() { async function initStoresListeners() {
currentSettings.subscribe((value) => { currentSettings.subscribe((value) => {
settings = value; settings = value;
@ -68,7 +76,7 @@
}); });
</script> </script>
<Popup content="Are you sure you want to delete this file?" type="confirm" /> <Popup bind:this={notificationPopup} />
<Dialog /> <Dialog />
<div id="main"> <div id="main">
@ -194,7 +202,7 @@
#tabcontent { #tabcontent {
background-color: var(--light); background-color: var(--light);
box-sizing: border-box; box-sizing: border-box;
height: calc(100vh - 5rem); height: calc(100vh - 3rem);
padding: 1rem; padding: 1rem;
max-height: 100%; max-height: 100%;
} }