Working on menus
This commit is contained in:
12
q
Normal file
12
q
Normal file
@ -0,0 +1,12 @@
|
||||
We are currently working on top bar menu items for this web editor. We are working on the "Share" functionality.
|
||||
When clicking the share button, here is what should happen:
|
||||
|
||||
- the code for the currently opened file is getting compressed and generates an URL link that you can send to other people.
|
||||
- when clicking on this link, the web editor opens with the same file code loaded in it. It acts like an import. It will prompt you to rename the file eventually to avoid name conflicts or accidental overwriting.
|
||||
- when clicking "Share", it opens up a popup that shows you the generated link with a "Copy to clipboard" button next to it.
|
||||
|
||||
Ultrathink and do.
|
||||
|
||||
Currently, clicking on the Share button does absolutely nothing. There is no feedback at all, and no errors either in the console of whatever.
|
||||
|
||||
Explore.
|
||||
231
src/App.svelte
231
src/App.svelte
@ -3,7 +3,9 @@
|
||||
import MenuBar from "./lib/components/ui/MenuBar.svelte";
|
||||
import MenuItem from "./lib/components/ui/MenuItem.svelte";
|
||||
import MenuAction from "./lib/components/ui/MenuAction.svelte";
|
||||
import MenuSeparator from "./lib/components/ui/MenuSeparator.svelte";
|
||||
import AboutDialog from "./lib/components/ui/AboutDialog.svelte";
|
||||
import AudioVolumeControl from "./lib/components/ui/AudioVolumeControl.svelte";
|
||||
import EditorWithLogs from "./lib/components/editor/EditorWithLogs.svelte";
|
||||
import EditorSettings from "./lib/components/editor/EditorSettings.svelte";
|
||||
import FileBrowser from "./lib/components/ui/FileBrowser.svelte";
|
||||
@ -26,7 +28,11 @@
|
||||
Save,
|
||||
Share2,
|
||||
Activity,
|
||||
FileStack,
|
||||
FilePlus,
|
||||
Copy,
|
||||
Download,
|
||||
Archive,
|
||||
Upload,
|
||||
PanelLeftOpen,
|
||||
PanelRightOpen,
|
||||
CircleStop,
|
||||
@ -49,6 +55,7 @@
|
||||
let analyserNode = $state<AnalyserNode | null>(null);
|
||||
let interpreterLogs = $state<LogEntry[]>([]);
|
||||
let editorWithLogsRef: EditorWithLogs;
|
||||
let fileInputRef: HTMLInputElement;
|
||||
|
||||
let logsUnsubscribe: (() => void) | undefined;
|
||||
|
||||
@ -56,6 +63,49 @@
|
||||
await fileManager.init();
|
||||
await editorState.refreshFileCache();
|
||||
|
||||
// Check for shared file in URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const sharedData = urlParams.get('d');
|
||||
const version = urlParams.get('v');
|
||||
|
||||
if (sharedData && version) {
|
||||
try {
|
||||
const result = await fileManager.importFilesFromUrl(window.location.href);
|
||||
if (result.success && result.data.length > 0) {
|
||||
// Open the imported file
|
||||
await editorState.openFile(result.data[0].id);
|
||||
// Clean the URL for better UX
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
|
||||
// Setup keyboard shortcuts and exit
|
||||
logsUnsubscribe = csoundLogs.subscribe((logs) => {
|
||||
interpreterLogs = logs;
|
||||
});
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === '.') {
|
||||
e.preventDefault();
|
||||
handleStop();
|
||||
}
|
||||
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to import shared file:', error);
|
||||
// Fall through to normal startup
|
||||
}
|
||||
}
|
||||
|
||||
// Try to restore open tabs
|
||||
const openTabIds = loadOpenTabs();
|
||||
const currentFileId = loadCurrentFileId();
|
||||
@ -100,6 +150,11 @@
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
}
|
||||
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'r') {
|
||||
e.preventDefault();
|
||||
handleExecuteFile();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
@ -175,6 +230,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
function triggerEvalBlock() {
|
||||
editorWithLogsRef?.executeBlock();
|
||||
}
|
||||
|
||||
function triggerEvalSelection() {
|
||||
editorWithLogsRef?.executeSelection();
|
||||
}
|
||||
|
||||
function triggerEvalFile() {
|
||||
editorWithLogsRef?.executeFile();
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
await editorState.save();
|
||||
}
|
||||
@ -190,6 +257,8 @@
|
||||
console.error("Failed to copy to clipboard:", err);
|
||||
}
|
||||
uiState.showShare(result.data);
|
||||
} else {
|
||||
console.error("Failed to generate share URL:", result.error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,6 +270,100 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDuplicateFile() {
|
||||
if (!editorState.currentFile) return;
|
||||
|
||||
const currentFile = editorState.currentFile;
|
||||
const newTitle = `${currentFile.title.replace(/\.orc$/, '')} copy.orc`;
|
||||
|
||||
const result = await fileManager.createFile({
|
||||
title: newTitle,
|
||||
content: currentFile.content
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
await editorState.openFile(result.data.id);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleExportFile() {
|
||||
if (!editorState.currentFile) return;
|
||||
|
||||
const file = editorState.currentFile;
|
||||
const blob = new Blob([file.content], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = file.title;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
async function handleExportAllFiles() {
|
||||
const allFilesResult = await fileManager.getAllFiles();
|
||||
if (!allFilesResult.success || allFilesResult.data.length === 0) return;
|
||||
|
||||
const JSZip = (await import('https://cdn.jsdelivr.net/npm/jszip@3.10.1/+esm')).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
for (const file of allFilesResult.data) {
|
||||
zip.file(file.title, file.content);
|
||||
}
|
||||
|
||||
const blob = await zip.generateAsync({ type: 'blob' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'oldboy-files.zip';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function handleImportFiles() {
|
||||
fileInputRef.click();
|
||||
}
|
||||
|
||||
async function onFileInputChange(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
if (!input.files || input.files.length === 0) return;
|
||||
|
||||
const supportedExtensions = ['.orc', '.csd', '.sco'];
|
||||
let firstImportedId: string | null = null;
|
||||
|
||||
for (const file of Array.from(input.files)) {
|
||||
const extension = file.name.substring(file.name.lastIndexOf('.'));
|
||||
|
||||
if (!supportedExtensions.includes(extension.toLowerCase())) {
|
||||
console.warn(`Skipping unsupported file: ${file.name}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await file.text();
|
||||
const result = await fileManager.createFile({
|
||||
title: file.name,
|
||||
content: content
|
||||
});
|
||||
|
||||
if (result.success && !firstImportedId) {
|
||||
firstImportedId = result.data.id;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to import ${file.name}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (firstImportedId) {
|
||||
await editorState.openFile(firstImportedId);
|
||||
}
|
||||
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
const theme = themes[$editorSettings.theme];
|
||||
if (theme) {
|
||||
@ -265,12 +428,22 @@
|
||||
|
||||
<div class="app-container">
|
||||
<MenuBar onLogoClick={() => uiState.showAboutDialog()}>
|
||||
{#snippet rightControls()}
|
||||
<AudioVolumeControl {csound} />
|
||||
{/snippet}
|
||||
|
||||
<MenuItem label="File">
|
||||
<MenuAction
|
||||
label="New File"
|
||||
icon={FileStack}
|
||||
icon={FilePlus}
|
||||
onclick={handleNewFile}
|
||||
/>
|
||||
<MenuAction
|
||||
label="Duplicate"
|
||||
icon={Copy}
|
||||
disabled={!editorState.currentFileId}
|
||||
onclick={handleDuplicateFile}
|
||||
/>
|
||||
<MenuAction
|
||||
label="Save"
|
||||
icon={Save}
|
||||
@ -278,6 +451,24 @@
|
||||
disabled={!editorState.hasUnsavedChanges}
|
||||
onclick={handleSave}
|
||||
/>
|
||||
<MenuSeparator />
|
||||
<MenuAction
|
||||
label="Import File(s)"
|
||||
icon={Upload}
|
||||
onclick={handleImportFiles}
|
||||
/>
|
||||
<MenuAction
|
||||
label="Export File"
|
||||
icon={Download}
|
||||
disabled={!editorState.currentFileId}
|
||||
onclick={handleExportFile}
|
||||
/>
|
||||
<MenuAction
|
||||
label="Export All Files"
|
||||
icon={Archive}
|
||||
onclick={handleExportAllFiles}
|
||||
/>
|
||||
<MenuSeparator />
|
||||
<MenuAction
|
||||
label="Share..."
|
||||
icon={Share2}
|
||||
@ -301,6 +492,25 @@
|
||||
disabled={!$running}
|
||||
onclick={handleStop}
|
||||
/>
|
||||
<MenuSeparator />
|
||||
<MenuAction
|
||||
label="Eval Block"
|
||||
shortcut={navigator.platform.includes("Mac") ? "Cmd+E" : "Ctrl+E"}
|
||||
disabled={!editorState.currentFileId}
|
||||
onclick={triggerEvalBlock}
|
||||
/>
|
||||
<MenuAction
|
||||
label="Eval Selection"
|
||||
shortcut="Alt+E"
|
||||
disabled={!editorState.currentFileId}
|
||||
onclick={triggerEvalSelection}
|
||||
/>
|
||||
<MenuAction
|
||||
label="Eval File"
|
||||
shortcut={navigator.platform.includes("Mac") ? "Cmd+Shift+E" : "Ctrl+Shift+E"}
|
||||
disabled={!editorState.currentFileId}
|
||||
onclick={triggerEvalFile}
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem label="Visuals">
|
||||
@ -379,10 +589,10 @@
|
||||
<ResizablePopup
|
||||
bind:visible={uiState.sharePopupVisible}
|
||||
title="Share Project"
|
||||
x={typeof window !== "undefined" ? window.innerWidth / 2 - 300 : 300}
|
||||
y={typeof window !== "undefined" ? window.innerHeight / 2 - 100 : 200}
|
||||
width={600}
|
||||
height={200}
|
||||
x={typeof window !== "undefined" ? window.innerWidth / 2 - 350 : 300}
|
||||
y={typeof window !== "undefined" ? window.innerHeight / 2 - 125 : 200}
|
||||
width={700}
|
||||
height={250}
|
||||
minWidth={400}
|
||||
minHeight={150}
|
||||
>
|
||||
@ -464,6 +674,15 @@
|
||||
onClose={() => uiState.hideAboutDialog()}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
bind:this={fileInputRef}
|
||||
onchange={onFileInputChange}
|
||||
accept=".orc,.csd,.sco"
|
||||
multiple
|
||||
style="display: none;"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@ -157,6 +157,7 @@
|
||||
height: 12px;
|
||||
background: var(--accent-color);
|
||||
cursor: pointer;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
@ -165,6 +166,7 @@
|
||||
background: var(--accent-color);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
input[type="range"]:hover {
|
||||
|
||||
@ -46,6 +46,18 @@
|
||||
editorRef?.handleExecute();
|
||||
}
|
||||
|
||||
export function executeFile() {
|
||||
editorRef?.handleExecuteFile();
|
||||
}
|
||||
|
||||
export function executeBlock() {
|
||||
editorRef?.handleExecuteBlock();
|
||||
}
|
||||
|
||||
export function executeSelection() {
|
||||
editorRef?.handleExecuteSelection();
|
||||
}
|
||||
|
||||
let editorHeight = $state(70);
|
||||
let isResizing = $state(false);
|
||||
let startY = $state(0);
|
||||
|
||||
119
src/lib/components/ui/AudioVolumeControl.svelte
Normal file
119
src/lib/components/ui/AudioVolumeControl.svelte
Normal file
@ -0,0 +1,119 @@
|
||||
<script lang="ts">
|
||||
import { Volume2 } from 'lucide-svelte';
|
||||
import type { CsoundStore } from '../../csound';
|
||||
|
||||
interface Props {
|
||||
csound: CsoundStore;
|
||||
}
|
||||
|
||||
let { csound }: Props = $props();
|
||||
|
||||
let volume = $state(100);
|
||||
let previousVolume = $state(100);
|
||||
|
||||
function handleVolumeChange(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
volume = parseInt(target.value, 10);
|
||||
csound.setVolume(volume / 100);
|
||||
if (volume > 0) {
|
||||
previousVolume = volume;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMute() {
|
||||
if (volume > 0) {
|
||||
previousVolume = volume;
|
||||
volume = 0;
|
||||
csound.setVolume(0);
|
||||
} else {
|
||||
volume = previousVolume;
|
||||
csound.setVolume(previousVolume / 100);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="volume-control">
|
||||
<button class="mute-button" onclick={toggleMute} type="button">
|
||||
<Volume2 size={16} />
|
||||
</button>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
value={volume}
|
||||
oninput={handleVolumeChange}
|
||||
class="volume-slider"
|
||||
/>
|
||||
<span class="volume-percentage">{volume}%</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.volume-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.mute-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.mute-button:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.volume-slider {
|
||||
width: 100px;
|
||||
height: 4px;
|
||||
background: var(--border-color);
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.volume-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: var(--accent-color);
|
||||
cursor: pointer;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.volume-slider::-moz-range-thumb {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: var(--accent-color);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.volume-slider:hover::-webkit-slider-thumb {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.volume-slider:hover::-moz-range-thumb {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.volume-percentage {
|
||||
font-size: 12px;
|
||||
min-width: 36px;
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
@ -4,10 +4,11 @@
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
rightControls?: Snippet;
|
||||
onLogoClick?: () => void;
|
||||
}
|
||||
|
||||
let { children, onLogoClick }: Props = $props();
|
||||
let { children, rightControls, onLogoClick }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="menu-bar">
|
||||
@ -18,6 +19,11 @@
|
||||
</button>
|
||||
{@render children()}
|
||||
</div>
|
||||
{#if rightControls}
|
||||
<div class="menu-bar-right">
|
||||
{@render rightControls()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@ -38,6 +44,12 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.menu-bar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.logo-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -51,7 +63,6 @@
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-fast);
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.logo-button:hover {
|
||||
|
||||
@ -32,7 +32,10 @@
|
||||
</button>
|
||||
|
||||
<MenuDropdown {isOpen} onClose={closeMenu} anchorElement={buttonElement}>
|
||||
<div onclick={closeMenu}>
|
||||
<div onclick={(e) => {
|
||||
// Let the MenuAction handle the click first, then close the menu
|
||||
setTimeout(closeMenu, 0);
|
||||
}}>
|
||||
{@render children()}
|
||||
</div>
|
||||
</MenuDropdown>
|
||||
|
||||
9
src/lib/components/ui/MenuSeparator.svelte
Normal file
9
src/lib/components/ui/MenuSeparator.svelte
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="menu-separator"></div>
|
||||
|
||||
<style>
|
||||
.menu-separator {
|
||||
height: 1px;
|
||||
background-color: var(--border-color);
|
||||
margin: 4px 0;
|
||||
}
|
||||
</style>
|
||||
@ -32,6 +32,7 @@ export class CsoundEngine {
|
||||
private scopeNode: AnalyserNode | null = null;
|
||||
private audioNode: AudioNode | null = null;
|
||||
private audioContext: AudioContext | null = null;
|
||||
private gainNode: GainNode | null = null;
|
||||
private useCsound7: boolean;
|
||||
|
||||
constructor(options: CsoundEngineOptions = {}) {
|
||||
@ -139,6 +140,7 @@ export class CsoundEngine {
|
||||
this.csound.on('onAudioNodeCreated', (node: AudioNode) => {
|
||||
this.audioNode = node;
|
||||
this.audioContext = node.context as AudioContext;
|
||||
this.setupGainNode();
|
||||
this.log('Audio node created and captured');
|
||||
});
|
||||
|
||||
@ -510,6 +512,26 @@ export class CsoundEngine {
|
||||
return this.audioContext;
|
||||
}
|
||||
|
||||
private setupGainNode(): void {
|
||||
if (!this.audioNode || !this.audioContext || this.gainNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.gainNode = this.audioContext.createGain();
|
||||
this.gainNode.gain.value = 1.0;
|
||||
|
||||
this.audioNode.disconnect();
|
||||
this.audioNode.connect(this.gainNode);
|
||||
this.gainNode.connect(this.audioContext.destination);
|
||||
|
||||
this.log('Gain node created and connected');
|
||||
} catch (error) {
|
||||
console.error('Failed to setup gain node:', error);
|
||||
this.log('Error setting up gain node: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
private setupAnalyser(): void {
|
||||
if (!this.audioNode || !this.audioContext) {
|
||||
this.log('Warning: Audio node not available yet');
|
||||
@ -517,13 +539,19 @@ export class CsoundEngine {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.gainNode) {
|
||||
this.setupGainNode();
|
||||
}
|
||||
|
||||
this.scopeNode = this.audioContext.createAnalyser();
|
||||
this.scopeNode.fftSize = 2048;
|
||||
this.scopeNode.smoothingTimeConstant = 0.3;
|
||||
|
||||
this.audioNode.disconnect();
|
||||
this.audioNode.connect(this.scopeNode);
|
||||
if (this.gainNode) {
|
||||
this.gainNode.disconnect();
|
||||
this.gainNode.connect(this.scopeNode);
|
||||
this.scopeNode.connect(this.audioContext.destination);
|
||||
}
|
||||
|
||||
this.log('Analyser node created and connected');
|
||||
this.options.onAnalyserNodeCreated?.(this.scopeNode);
|
||||
@ -537,6 +565,20 @@ export class CsoundEngine {
|
||||
return this.scopeNode;
|
||||
}
|
||||
|
||||
setVolume(value: number): void {
|
||||
if (!this.gainNode) {
|
||||
return;
|
||||
}
|
||||
this.gainNode.gain.value = Math.max(0, Math.min(1, value));
|
||||
}
|
||||
|
||||
getVolume(): number {
|
||||
if (!this.gainNode) {
|
||||
return 1.0;
|
||||
}
|
||||
return this.gainNode.gain.value;
|
||||
}
|
||||
|
||||
private log(message: string): void {
|
||||
this.options.onMessage?.(message);
|
||||
}
|
||||
|
||||
@ -42,6 +42,8 @@ export interface CsoundStore {
|
||||
getAudioContext: () => AudioContext | null;
|
||||
getAnalyserNode: () => AnalyserNode | null;
|
||||
onAnalyserNodeCreated: (callback: (node: AnalyserNode) => void) => () => void;
|
||||
setVolume: (value: number) => void;
|
||||
getVolume: () => number;
|
||||
destroy: () => Promise<void>;
|
||||
}
|
||||
|
||||
@ -357,6 +359,19 @@ export function createCsoundStore(options: CsoundStoreOptions = {}): CsoundStore
|
||||
return () => analyserNodeListeners.delete(callback);
|
||||
},
|
||||
|
||||
setVolume(value: number): void {
|
||||
if (engine) {
|
||||
engine.setVolume(value);
|
||||
}
|
||||
},
|
||||
|
||||
getVolume(): number {
|
||||
if (engine) {
|
||||
return engine.getVolume();
|
||||
}
|
||||
return 1.0;
|
||||
},
|
||||
|
||||
async destroy() {
|
||||
if (engine) {
|
||||
await engine.destroy();
|
||||
|
||||
@ -251,9 +251,9 @@ export class FileManager {
|
||||
const files: File[] = [];
|
||||
|
||||
for (const id of fileIds) {
|
||||
const file = await this.db.get(id);
|
||||
if (file) {
|
||||
files.push(file);
|
||||
const result = await this.getFile(id);
|
||||
if (result.success) {
|
||||
files.push(result.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user