FEAT: Launch with custom config location
FEAT: Generate banner FEAT: Start blender with banner
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
<script>
|
||||
import { deleteVersion, launchBlenderVersion } from '$lib/library';
|
||||
import { toggleFavourite } from '$lib/library';
|
||||
import {
|
||||
deleteVersion,
|
||||
launchBlenderVersion,
|
||||
toggleFavourite,
|
||||
registerVersion
|
||||
} from '$lib/library';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Menu from '$lib/components/Menu.svelte';
|
||||
let { version } = $props();
|
||||
@@ -44,8 +48,10 @@
|
||||
<button
|
||||
type="button"
|
||||
class="menu-item"
|
||||
onclick={close}
|
||||
onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && close()}>Option 3</button
|
||||
onclick={() => registerVersion(version) && close()}
|
||||
onkeydown={(e) =>
|
||||
(e.key === 'Enter' || e.key === ' ') && registerVersion(version) && close()}
|
||||
>Default</button
|
||||
>
|
||||
{/snippet}
|
||||
</Menu>
|
||||
|
||||
+71
-18
@@ -7,7 +7,11 @@ import {
|
||||
import { stat, mkdir, readDir, copyFile, rename, remove } from '@tauri-apps/plugin-fs';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import baseicon from '$lib/assets/baseicon.png';
|
||||
import europa from '$lib/assets/fonts/Europa-Mono-Medium.otf';
|
||||
import { BASE_LIBRARY_DIR } from './settings';
|
||||
import figlet from 'figlet';
|
||||
import shadow from 'figlet/fonts/Classy';
|
||||
figlet.parseFont('Big', shadow);
|
||||
|
||||
/**
|
||||
* Create a custom icon with text overlay
|
||||
@@ -19,45 +23,32 @@ export async function createIcon(iconText) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Load the base icon image
|
||||
const img = new Image();
|
||||
img.src = baseicon;
|
||||
|
||||
// Wait for image to load
|
||||
await new Promise((resolve) => {
|
||||
img.onload = resolve;
|
||||
});
|
||||
|
||||
// Set canvas dimensions to match image
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
|
||||
// Draw the base icon
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
// Set text properties
|
||||
ctx.fillStyle = '#7d70ba';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
// Load the custom font
|
||||
const fontFace = new FontFace(
|
||||
'Europa-Mono',
|
||||
`url(${baseicon.replace('baseicon.png', 'fonts/Europa-Mono-Medium.otf')})`
|
||||
);
|
||||
const fontFace = new FontFace('Europa-Mono', `url(${europa})`);
|
||||
await fontFace.load();
|
||||
document.fonts.add(fontFace);
|
||||
|
||||
// Set font size based on canvas dimensions
|
||||
const fontSize = canvas.width / 6;
|
||||
ctx.font = `${fontSize}px Europa-Mono`;
|
||||
// Set font size based on canvas dimensions
|
||||
|
||||
// Add version text in the center
|
||||
const text = iconText || 'XXX';
|
||||
ctx.fillText(text, 150, 150);
|
||||
|
||||
// Convert canvas to blob
|
||||
return new Promise((resolve) => {
|
||||
canvas.toBlob((blob) => {
|
||||
resolve(blob);
|
||||
@@ -65,6 +56,71 @@ export async function createIcon(iconText) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom banner with version number next to the baseIcon image
|
||||
* @param {string} versionText - Version text to display
|
||||
* @returns {Promise<Blob>} Promise resolving to a PNG blob of the banner
|
||||
*/
|
||||
export async function createBanner(versionText) {
|
||||
// Create canvas element for banner
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Set banner dimensions
|
||||
canvas.width = 600;
|
||||
canvas.height = 90;
|
||||
|
||||
// Draw background
|
||||
ctx.fillStyle = '#171a21';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Load and draw base icon
|
||||
const img = new Image();
|
||||
img.src = baseicon;
|
||||
|
||||
await new Promise((resolve) => {
|
||||
img.onload = resolve;
|
||||
});
|
||||
|
||||
// Calculate icon position (centered vertically, left-aligned)
|
||||
const iconSize = 70;
|
||||
const iconX = 20;
|
||||
const iconY = (canvas.height - iconSize) / 2;
|
||||
|
||||
// Add version text
|
||||
ctx.fillStyle = '#f5f5f5';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
const fontFace = new FontFace('Europa-Mono', `url(${europa})`);
|
||||
await fontFace.load();
|
||||
document.fonts.add(fontFace);
|
||||
const fontSize = 10;
|
||||
ctx.font = `${fontSize}px Europa-Mono`;
|
||||
|
||||
// Position text to the right of the icon
|
||||
const textX = 20;
|
||||
const textY = canvas.height / 2;
|
||||
|
||||
const bannerText = await figlet.text(versionText + ' - Base', { font: 'Big' });
|
||||
const lines = bannerText.split('\n');
|
||||
const lineHeight = fontSize * 1.2;
|
||||
const startY = textY - (lines.length * lineHeight) / 2 + lineHeight / 2;
|
||||
|
||||
ctx.font = `${fontSize}px monospace`;
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
ctx.fillText(line, textX, startY + index * lineHeight);
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
canvas.toBlob((blob) => {
|
||||
resolve(blob);
|
||||
}, 'image/png');
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Create library directory structure if it doesn't exist
|
||||
* @param {string} libraryDir - Library directory path
|
||||
@@ -72,13 +128,10 @@ export async function createIcon(iconText) {
|
||||
*/
|
||||
export async function ensureLibraryStructure(libraryDir) {
|
||||
try {
|
||||
// Create main library directory
|
||||
const baseDir = await join(libraryDir, BASE_LIBRARY_DIR);
|
||||
// Create blender subdirectory
|
||||
const blenderDir = await join(baseDir, 'blender');
|
||||
await mkdir(blenderDir, { recursive: true });
|
||||
|
||||
// Create templates subdirectory
|
||||
const templatesDir = await join(baseDir, 'templates');
|
||||
await mkdir(templatesDir, { recursive: true });
|
||||
|
||||
@@ -99,7 +152,7 @@ export async function ensureLibraryStructure(libraryDir) {
|
||||
*/
|
||||
export async function selectDirectory(currentPath) {
|
||||
try {
|
||||
// Use currentPath if provided, otherwise use app data directory
|
||||
// Use currentPath if proided, otherwise use app data directory
|
||||
let defaultPath = await appDataDir();
|
||||
if (currentPath) {
|
||||
defaultPath = currentPath;
|
||||
|
||||
+74
-5
@@ -1,5 +1,5 @@
|
||||
import { currentSettings } from '$lib/settings.js';
|
||||
import { createIcon } from './file_utils';
|
||||
import { createIcon, createBanner } from './file_utils';
|
||||
import { platform } from '@tauri-apps/plugin-os';
|
||||
import { join, localDataDir } from '@tauri-apps/api/path';
|
||||
import { exists, stat, readDir, writeFile, remove } from '@tauri-apps/plugin-fs';
|
||||
@@ -22,6 +22,20 @@ import { updateDownloadProgress } from './download';
|
||||
export const currentInstalledVersions = writable([]);
|
||||
const favouritesStore = new LazyStore('versions.json');
|
||||
|
||||
export async function generateVersionBanner(version) {
|
||||
const bannerPath = `${version.path}/banner.png`;
|
||||
if (await exists(bannerPath)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const banner = await createBanner(version.version);
|
||||
const bannerBuffer = new Uint8Array(await banner.arrayBuffer());
|
||||
await writeFile(bannerPath, bannerBuffer);
|
||||
} catch (error) {
|
||||
console.error(`Failed to generate banner for version: ${version.version}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create icon and write it to disk
|
||||
* @param {blenderVersion} version
|
||||
@@ -39,7 +53,6 @@ export async function generateVersionIcon(version) {
|
||||
}
|
||||
try {
|
||||
const newIcon = await createIcon(version.version);
|
||||
const iconPath = `${version.path}/icon.png`;
|
||||
const iconBuffer = new Uint8Array(await newIcon.arrayBuffer());
|
||||
await writeFile(iconPath, iconBuffer);
|
||||
} catch (error) {
|
||||
@@ -286,6 +299,7 @@ export async function getInstalledVersions() {
|
||||
|
||||
//check if version has an icon, if not generate one
|
||||
await generateVersionIcon(newVersion);
|
||||
await generateVersionBanner(newVersion);
|
||||
// create desktop file if autoCreateShortcuts is enabled
|
||||
if (settings.autoCreateShortcuts) {
|
||||
await createDesktopFile(newVersion);
|
||||
@@ -314,11 +328,11 @@ export async function getInstalledVersions() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a specific Blender version.
|
||||
* Register a Blender version with the system.
|
||||
* @param {blenderVersion} version - The version object to launch
|
||||
* @returns {Promise<{success: boolean, message: string}>} Result of launch attempt
|
||||
*/
|
||||
export async function launchBlenderVersion(version) {
|
||||
export async function registerVersion(version) {
|
||||
try {
|
||||
if (!version || !version.executable) {
|
||||
return {
|
||||
@@ -338,7 +352,62 @@ export async function launchBlenderVersion(version) {
|
||||
};
|
||||
}
|
||||
// Launch the executable
|
||||
invoke('launch_binary', { path: version.executable, args: [] });
|
||||
invoke('launch_binary', { path: version.executable, args: ['--register'] });
|
||||
} catch (error) {
|
||||
console.error('Error registering Blender:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
success: false,
|
||||
message: `Registration failed: ${errorMessage}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a specific Blender version.
|
||||
* @param {blenderVersion} version - The version object to launch
|
||||
* @returns {Promise<{success: boolean, message: string}>} Result of launch attempt
|
||||
*/
|
||||
export async function launchBlenderVersion(version, template = null) {
|
||||
try {
|
||||
if (!version || !version.executable) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Invalid version or executable not found'
|
||||
};
|
||||
}
|
||||
|
||||
// Check if executable exists
|
||||
try {
|
||||
await stat(version.executable);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {
|
||||
success: false,
|
||||
message: `Executable not found: ${version.executable}`
|
||||
};
|
||||
}
|
||||
|
||||
const settings = get(currentSettings);
|
||||
const libraryDir = settings.libraryDir;
|
||||
|
||||
if (!libraryDir) {
|
||||
return {
|
||||
success: false,
|
||||
message: `couldn't find library: ${version.executable}`
|
||||
};
|
||||
}
|
||||
const blenderConfigPath = await join(libraryDir, BASE_LIBRARY_DIR, 'config', version.version);
|
||||
|
||||
// Launch the executable
|
||||
invoke('launch_binary', {
|
||||
path: version.executable,
|
||||
args: [],
|
||||
env: {
|
||||
BLENDER_USER_RESOURCES: blenderConfigPath,
|
||||
BLENDER_CUSTOM_SPLASH_BANNER: await join(version.path, 'banner.png')
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error launching Blender:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
Reference in New Issue
Block a user