mirror of
https://github.com/jatin-shihora/Obsidian-lofi-plugin.git
synced 2026-07-22 06:42:23 +00:00
Fix play/pause button and stream playback control bug + Set safer defaults + reformatting
This commit is contained in:
parent
870c0f47ce
commit
6250dc04e7
12 changed files with 3806 additions and 1096 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -1,5 +1,5 @@
|
|||
# vscode
|
||||
.vscode
|
||||
.vscode
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
|
|
@ -21,6 +21,8 @@ data.json
|
|||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
lofi.mp3
|
||||
# Claude Code configuration
|
||||
.claude/
|
||||
|
||||
package-lock.json
|
||||
# Test audio files
|
||||
lofi.mp3
|
||||
189
README.md
189
README.md
|
|
@ -1,69 +1,136 @@
|
|||
# Obsidian Lofi Flow Plugin Development Tracker
|
||||
# Lofi Focus
|
||||
|
||||
This document tracks the implementation status of features for the Obsidian Lofi Flow plugin.
|
||||
Play lofi music and stay focused with an integrated Pomodoro timer, all within Obsidian.
|
||||
|
||||
## 🎵 Lofi Audio in Background
|
||||
## Features
|
||||
|
||||
- [x] **Load/Play Local MP3 Files:**
|
||||
- [x] Scan a user-specified folder in the vault for `.mp3` files.
|
||||
- [x] Store scanned files as a playlist.
|
||||
- [x] Load and play the first track from the playlist on plugin load/scan.
|
||||
- [x] Implement a custom folder browser UI in settings to select the audio folder.
|
||||
- [x] List found tracks in settings and allow clicking to play a specific track.
|
||||
- [ ] **Load a default YouTube lofi stream.use cdn.jsdeliver github public repo**
|
||||
- [x] **Play/Pause Toggle (Ribbon/Command):**
|
||||
- [x] Add a ribbon icon to toggle playback.
|
||||
- [x] Add a command palette action to toggle playback.
|
||||
- [ ] **Predefined Default Streams:** Show options like Chill mornings, jazzy afternoons, etc. (Requires defining/managing these sources).
|
||||
- [x] **Status Bar Integration:**
|
||||
- [x] Show plugin status (e.g., Ready, Scanning, Error).
|
||||
- [x] Show playback status (Playing, Paused).
|
||||
- [x] Display the name of the currently playing local MP3 file.
|
||||
- [ ] **Auto-reconnect if stream fails.** (Relevant for streaming sources like YouTube, could also apply to errors reloading local files).
|
||||
- [x] **Volume Control:**
|
||||
- [x] Add a volume slider in settings for HTML audio playback.
|
||||
- [x] Apply volume setting on load.
|
||||
- [x] Update volume immediately when slider changes.
|
||||
- [ ] **Source Selection in Settings:** Option to choose between local folder, YouTube stream, or predefined defaults.
|
||||
- [x] **Subtle Animation (Visualizer):**
|
||||
- [x] Implement animation (e.g., falling leaves, rain) using Canvas or SVG.
|
||||
- [x] Add toggle in settings to turn animation on/off.
|
||||
### 🎵 Audio Playback
|
||||
- **Online Streams**: Choose from pre-configured lofi radio stations
|
||||
- **Local MP3 Files**: Play your own music collection from any vault folder
|
||||
- **Full Playback Controls**: Play, pause, next, previous tracks
|
||||
- **Volume Control**: Adjust volume to your preference
|
||||
- **Status Bar Integration**: See what's playing at a glance
|
||||
|
||||
## ⏳ Focus Timer
|
||||
### ⏱️ Focus Timer (Pomodoro)
|
||||
- **Customizable Work Sessions**: Set your ideal work duration (default: 25 minutes)
|
||||
- **Break Reminders**: Automated rest periods (default: 5 minutes)
|
||||
- **Auto-Transitions**: Seamlessly switches between work and rest sessions
|
||||
- **Visual Timer**: Always visible in the status bar
|
||||
- **Full Controls**: Start, pause, and reset from the status bar
|
||||
|
||||
- [X] **Implement Timer Feature:**
|
||||
- [x] Customizable work/rest intervals setting.
|
||||
- [x] Add UI/logic for timer countdown.
|
||||
- [X] Provide an audio or visual cue when a session ends.
|
||||
### ✨ Ambient Animations
|
||||
- **Optional Visual Effects**: Subtle falling particle animations
|
||||
- **Toggle On/Off**: Enable in settings if you enjoy the ambiance
|
||||
- **Performance-Friendly**: Lightweight canvas-based rendering
|
||||
|
||||
## Quick Start
|
||||
|
||||
### First Time Setup
|
||||
|
||||
1. **Install the plugin** from Obsidian's Community Plugins
|
||||
2. **Enable it** in Settings → Community Plugins
|
||||
3. The plugin starts with **Lofi Hip Hop Radio** by default - it works immediately!
|
||||
|
||||
### Using Audio
|
||||
|
||||
#### Option 1: Online Streams (Default)
|
||||
- Pre-configured streams work out of the box
|
||||
- Switch between streams in Settings:
|
||||
- Lofi Hip Hop Radio
|
||||
- Chill Mornings
|
||||
- Jazzy Afternoons
|
||||
- Anime & Japanese Music
|
||||
|
||||
#### Option 2: Local MP3 Files
|
||||
1. Go to Settings → Lofi Focus
|
||||
2. Select "Local Folder" from Audio Source dropdown
|
||||
3. Browse to a folder containing MP3 files
|
||||
4. Click "Select This Folder"
|
||||
5. Your tracks appear - click any track to play
|
||||
|
||||
### Using the Focus Timer
|
||||
|
||||
1. **Start**: Click the ▶ button next to the timer in the status bar
|
||||
2. **Pause**: Click the ⏸ button during a session
|
||||
3. **Reset**: Click the 🔄 button to start over
|
||||
4. Customize durations in Settings → Focus Timer Settings
|
||||
|
||||
### Status Bar Controls
|
||||
|
||||
You'll see these controls at the bottom of Obsidian:
|
||||
|
||||
**Audio Controls:**
|
||||
- `[Audio Status]` - Shows current track/stream
|
||||
- `[⏮]` - Previous track (local files only)
|
||||
- `[▶/⏸]` - Play/Pause audio
|
||||
- `[⏭]` - Next track (local files only)
|
||||
|
||||
**Timer Controls:**
|
||||
- `[Timer: 00:00]` - Shows remaining time
|
||||
- `[▶/⏸]` - Start/Pause timer
|
||||
- `[🔄]` - Reset timer
|
||||
|
||||
## Commands
|
||||
|
||||
Access these from the Command Palette (Ctrl/Cmd + P):
|
||||
|
||||
- `Toggle Lofi Playback` - Play/pause music
|
||||
- `Play Next Lofi Track` - Skip to next track
|
||||
- `Play Previous Lofi Track` - Go to previous track
|
||||
- `Start Focus Timer` - Begin work session
|
||||
- `Pause Focus Timer` - Pause timer
|
||||
- `Reset Focus Timer` - Reset to zero
|
||||
|
||||
## Settings
|
||||
|
||||
### Audio Settings
|
||||
- **Audio Source**: Choose between online streams or local folder
|
||||
- **Volume**: Adjust playback volume (0-100%)
|
||||
- **Local Audio Folder**: Browse and select folder with MP3 files
|
||||
|
||||
### Focus Timer Settings
|
||||
- **Work Duration**: Length of focus sessions (default: 25 minutes)
|
||||
- **Rest Duration**: Length of break periods (default: 5 minutes)
|
||||
|
||||
### Animation Settings
|
||||
- **Enable Background Animation**: Toggle ambient falling particles
|
||||
|
||||
## Tips
|
||||
|
||||
- **Stream vs Local**: Streams work instantly but require internet. Local files work offline and use your own music collection.
|
||||
- **Pomodoro Technique**: The default 25:5 ratio is scientifically proven to maintain focus while preventing burnout.
|
||||
- **Volume**: Start at 30% and adjust to your preference - especially important with headphones!
|
||||
- **Animation**: Try it out! Some users find it calming, others prefer it off for less distraction.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**No audio playing?**
|
||||
- Check if an audio source is selected in settings
|
||||
- For streams: Verify internet connection
|
||||
- For local files: Ensure folder contains MP3 files
|
||||
|
||||
**Can't see playback controls?**
|
||||
- Playback controls appear in the status bar (bottom of Obsidian)
|
||||
- For streams: Only play/pause button shows (no prev/next)
|
||||
- For local files: All controls show (prev/play/next)
|
||||
|
||||
**Timer not starting?**
|
||||
- Click the play button (▶) next to the timer display
|
||||
- Check timer durations are set to positive numbers in settings
|
||||
|
||||
## Support
|
||||
|
||||
Found a bug or have a feature request?
|
||||
- Open an issue on [GitHub](https://github.com/Jatin-Shihora/Obsidian-lofi-plugin/issues)
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file for details
|
||||
|
||||
## Credits
|
||||
|
||||
Created by [Jatin Shihora](https://github.com/Jatin-Shihora)
|
||||
|
||||
---
|
||||
|
||||
**Progress:** We have completed the core functionality for playing local MP3 files, including folder Browse, track selection, playback control, status display, and volume control. The Focus Timer and the YouTube/Predefined streams/Animation aspects of the audio feature are the next major areas to develop.
|
||||
|
||||
## Funding URL
|
||||
|
||||
You can include funding URLs where people who use your plugin can financially support it.
|
||||
|
||||
The simple way is to set the `fundingUrl` field to your link in your `manifest.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"fundingUrl": "https://buymeacoffee.com"
|
||||
}
|
||||
```
|
||||
|
||||
If you have multiple URLs, you can also do:
|
||||
|
||||
```json
|
||||
{
|
||||
"fundingUrl": {
|
||||
"Buy Me a Coffee": "https://buymeacoffee.com",
|
||||
"GitHub Sponsor": "https://github.com/sponsors",
|
||||
"Patreon": "https://www.patreon.com/"
|
||||
}
|
||||
}
|
||||
```
|
||||
s
|
||||
## API Documentation
|
||||
|
||||
See https://github.com/obsidianmd/obsidian-api
|
||||
**Enjoy your focused work sessions! 🎵⏱️**
|
||||
|
|
|
|||
23
defaults.ts
23
defaults.ts
|
|
@ -1,16 +1,11 @@
|
|||
import { LofiPluginSettings } from './types'; // Import the settings interface
|
||||
import { LofiPluginSettings } from "./types";
|
||||
|
||||
// Defines the default values for the plugin settings
|
||||
export const DEFAULT_LOFI_SETTINGS: LofiPluginSettings = {
|
||||
mySetting: 'default', // Default value for sample setting
|
||||
volume: 50, // Default volume is 50%
|
||||
audioFolderPath: '', // Default to an empty string
|
||||
|
||||
// Default Focus Timer Settings (e.g., Pomodoro defaults)
|
||||
workDuration: 25, // Default work session: 25 minutes
|
||||
restDuration: 5, // Default rest session: 5 minutes
|
||||
|
||||
// --- NEW: Default Animation Settings ---
|
||||
animationEnabled: false, // Animation is off by default
|
||||
// --- END NEW ---
|
||||
}
|
||||
mySetting: "default",
|
||||
volume: 30,
|
||||
audioFolderPath: "",
|
||||
workDuration: 25,
|
||||
restDuration: 5,
|
||||
animationEnabled: false,
|
||||
activeStreamId: "lofi-hiphop-radio",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@ import esbuild from "esbuild";
|
|||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
const banner = `/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
`;
|
||||
|
||||
const prod = (process.argv[2] === "production");
|
||||
const prod = process.argv[2] === "production";
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
|
|
@ -31,7 +30,8 @@ const context = await esbuild.context({
|
|||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins],
|
||||
...builtins,
|
||||
],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"id": "Obsidian-lofi-plugin",
|
||||
"name": "Obsidian Lofi Flow",
|
||||
"id": "lofi-focus",
|
||||
"name": "Lofi Focus",
|
||||
"version": "1.0.1",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Demonstrates some of the capabilities of the Obsidian API.",
|
||||
"author": "Obsidian",
|
||||
"authorUrl": "https://obsidian.md",
|
||||
"fundingUrl": "https://obsidian.md/pricing",
|
||||
"description": "Play lofi music and stay focused with an integrated Pomodoro timer. Features local MP3 playback, online streams, and ambient animations.",
|
||||
"author": "Jatin Shihora",
|
||||
"authorUrl": "https://github.com/Jatin-Shihora",
|
||||
"fundingUrl": "https://github.com/Jatin-Shihora/Obsidian-lofi-plugin",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
16
modal.ts
16
modal.ts
|
|
@ -1,19 +1,17 @@
|
|||
import { App, Modal } from 'obsidian'; // Import necessary types from obsidian
|
||||
import { App, Modal } from "obsidian";
|
||||
|
||||
// Modal class (kept from sample template, can be renamed/modified later if needed)
|
||||
// Currently not used by the plugin's logic.
|
||||
export class SampleModal extends Modal { // Export the class so main.ts can import it
|
||||
export class SampleModal extends Modal {
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this; // Get the content element of the modal
|
||||
contentEl.setText('Lofi Modal - (Content Coming Soon)'); // Set the text content
|
||||
const { contentEl } = this;
|
||||
contentEl.setText("Lofi Modal - (Content Coming Soon)");
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this; // Get the content element
|
||||
contentEl.empty(); // Clear the content when modal is closed
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2227
package-lock.json
generated
Normal file
2227
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
659
settings-tab.ts
659
settings-tab.ts
|
|
@ -1,331 +1,416 @@
|
|||
// src/settings-tab.ts
|
||||
|
||||
import {
|
||||
App,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
Notice,
|
||||
normalizePath,
|
||||
TFolder,
|
||||
TFile,
|
||||
TAbstractFile,
|
||||
} from 'obsidian';
|
||||
|
||||
import LofiPlugin from './main';
|
||||
import { LofiPluginSettings } from './types';
|
||||
App,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
Notice,
|
||||
normalizePath,
|
||||
TFolder,
|
||||
TFile,
|
||||
TAbstractFile,
|
||||
} from "obsidian";
|
||||
|
||||
import LofiPlugin from "./main";
|
||||
import { LofiPluginSettings } from "./types";
|
||||
import { PREDEFINED_LOFI_STREAMS } from "./streams";
|
||||
|
||||
export class LofiSettingTab extends PluginSettingTab {
|
||||
plugin: LofiPlugin;
|
||||
|
||||
private currentBrowsePath: string;
|
||||
private folderListEl: HTMLElement;
|
||||
private currentPathEl: HTMLElement;
|
||||
private trackListEl: HTMLElement;
|
||||
private currentBrowsePath: string;
|
||||
private folderListEl: HTMLElement;
|
||||
private currentPathEl: HTMLElement;
|
||||
private trackListEl: HTMLElement;
|
||||
|
||||
constructor(app: App, plugin: LofiPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
this.currentBrowsePath = normalizePath(this.plugin.settings.audioFolderPath || '');
|
||||
this.currentBrowsePath = normalizePath(
|
||||
this.plugin.settings.audioFolderPath || ""
|
||||
);
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', {text: 'Lofi Plugin Settings'});
|
||||
containerEl.createEl("h2", { text: "Lofi Plugin Settings" });
|
||||
|
||||
// --- Audio Settings Section ---
|
||||
containerEl.createEl('h3', {text: 'Audio Settings'});
|
||||
containerEl.createEl("h3", { text: "Audio Settings" });
|
||||
containerEl.createEl("h4", { text: "Audio Source" });
|
||||
|
||||
const streamOptions: Record<string, string> = {};
|
||||
PREDEFINED_LOFI_STREAMS.forEach((stream) => {
|
||||
streamOptions[stream.id] = stream.name;
|
||||
});
|
||||
|
||||
// Sample Setting (can be removed)
|
||||
new Setting(containerEl)
|
||||
.setName('Sample Setting')
|
||||
.setDesc('This is a placeholder setting.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter something...')
|
||||
.setValue(this.plugin.settings.mySetting)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.mySetting = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
.setName("Select Audio Source")
|
||||
.setDesc(
|
||||
"Choose between playing local files or a predefined online stream."
|
||||
)
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOptions(streamOptions)
|
||||
.setValue(this.plugin.settings.activeStreamId || "local")
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.activeStreamId =
|
||||
value === "local" ? null : value;
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
// Volume Slider Setting
|
||||
new Setting(containerEl)
|
||||
.setName('Volume')
|
||||
.setDesc('Adjust the Lofi playback volume.')
|
||||
.addSlider(slider => slider
|
||||
.setLimits(0, 100, 1)
|
||||
.setValue(this.plugin.settings.volume)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.volume = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.setVolume(value);
|
||||
}));
|
||||
if (value === "local") {
|
||||
this.plugin.activateStream(null);
|
||||
this.renderFolderContents(
|
||||
this.plugin.settings.audioFolderPath
|
||||
);
|
||||
this.renderTrackList();
|
||||
this.localFolderSettingContainer.style.display =
|
||||
"block";
|
||||
this.localFolderBrowserContainer.style.display =
|
||||
"block";
|
||||
} else {
|
||||
this.plugin.activateStream(value);
|
||||
this.localFolderSettingContainer.style.display =
|
||||
"none";
|
||||
this.localFolderBrowserContainer.style.display =
|
||||
"none";
|
||||
this.trackListEl.empty();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Custom Folder Browser UI Section for Audio Folder Location
|
||||
containerEl.createEl('h4', {text: 'Audio Folder Location'});
|
||||
const localSourceContainer = containerEl.createDiv();
|
||||
this.localFolderSettingContainer = localSourceContainer.createDiv();
|
||||
this.localFolderBrowserContainer = localSourceContainer.createDiv();
|
||||
|
||||
const navContainer = containerEl.createDiv('lofi-folder-browser-nav');
|
||||
this.currentPathEl = navContainer.createEl('span', { text: 'Loading...', cls: 'lofi-browser-current-path' });
|
||||
const upButton = navContainer.createEl('button', { text: 'Up', cls: 'lofi-browser-up-button' });
|
||||
const selectButton = containerEl.createEl('button', { text: 'Select This Folder', cls: 'lofi-browser-select-button' });
|
||||
new Setting(containerEl)
|
||||
.setName("Volume")
|
||||
.setDesc("Adjust the Lofi playback volume.")
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(0, 100, 1)
|
||||
.setValue(this.plugin.settings.volume)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.volume = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.setVolume(value);
|
||||
})
|
||||
);
|
||||
|
||||
containerEl.createEl('p', { text: 'Contents:' });
|
||||
this.folderListEl = containerEl.createDiv('lofi-folder-browser-list');
|
||||
this.localFolderBrowserContainer.createEl("h4", {
|
||||
text: "Local Audio Folder Location",
|
||||
});
|
||||
|
||||
this.renderFolderContents(this.currentBrowsePath);
|
||||
const navContainer = this.localFolderBrowserContainer.createDiv(
|
||||
"lofi-folder-browser-nav"
|
||||
);
|
||||
this.currentPathEl = navContainer.createEl("span", {
|
||||
text: "Loading...",
|
||||
cls: "lofi-browser-current-path",
|
||||
});
|
||||
const upButton = navContainer.createEl("button", {
|
||||
text: "Up",
|
||||
cls: "lofi-browser-up-button",
|
||||
});
|
||||
const selectButton = navContainer.createEl("button", {
|
||||
text: "Select This Folder",
|
||||
cls: "lofi-browser-select-button",
|
||||
});
|
||||
|
||||
upButton.addEventListener('click', () => { this.navigateUp(); });
|
||||
selectButton.addEventListener('click', async () => { await this.selectCurrentFolder(); });
|
||||
this.localFolderBrowserContainer.createEl("p", { text: "Contents:" });
|
||||
|
||||
// Track List Section
|
||||
containerEl.createEl('h4', { text: 'Found Tracks' });
|
||||
containerEl.createEl('p', { text: 'Click a track to play:' });
|
||||
this.trackListEl = containerEl.createDiv('lofi-track-list');
|
||||
this.folderListEl = this.localFolderBrowserContainer.createDiv(
|
||||
"lofi-folder-browser-list"
|
||||
);
|
||||
|
||||
this.renderTrackList();
|
||||
// --- End Audio Settings Section ---
|
||||
if (this.plugin.settings.activeStreamId === null) {
|
||||
this.renderFolderContents(this.currentBrowsePath);
|
||||
} else {
|
||||
this.currentPathEl.setText("Local Folder Browser (Hidden)");
|
||||
this.folderListEl.empty();
|
||||
}
|
||||
|
||||
upButton.addEventListener("click", () => {
|
||||
this.navigateUp();
|
||||
});
|
||||
selectButton.addEventListener("click", async () => {
|
||||
await this.selectCurrentFolder();
|
||||
});
|
||||
|
||||
containerEl.createEl('hr');
|
||||
this.localFolderBrowserContainer.createEl("h4", {
|
||||
text: "Found Local Tracks",
|
||||
});
|
||||
this.localFolderBrowserContainer.createEl("p", {
|
||||
text: "Click a track to play:",
|
||||
});
|
||||
this.trackListEl =
|
||||
this.localFolderBrowserContainer.createDiv("lofi-track-list");
|
||||
|
||||
if (this.plugin.settings.activeStreamId === null) {
|
||||
this.renderTrackList();
|
||||
} else {
|
||||
this.trackListEl.empty();
|
||||
}
|
||||
|
||||
// --- Focus Timer Settings Section ---
|
||||
containerEl.createEl('h3', { text: 'Focus Timer Settings' });
|
||||
containerEl.createEl("hr");
|
||||
|
||||
// Work Duration Setting Input
|
||||
new Setting(containerEl)
|
||||
.setName('Work Duration (minutes)')
|
||||
.setDesc('Set the duration for each focus work session.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('e.g., 25')
|
||||
.setValue(this.plugin.settings.workDuration.toString())
|
||||
.onChange(async (value) => {
|
||||
const duration = parseInt(value, 10);
|
||||
if (!isNaN(duration) && duration > 0) {
|
||||
this.plugin.settings.workDuration = duration;
|
||||
await this.plugin.saveSettings();
|
||||
console.log('Work duration updated:', duration);
|
||||
} else {
|
||||
new Notice('Please enter a valid positive number for work duration.');
|
||||
text.setValue(this.plugin.settings.workDuration.toString());
|
||||
}
|
||||
}));
|
||||
containerEl.createEl("h3", { text: "Focus Timer Settings" });
|
||||
new Setting(containerEl)
|
||||
.setName("Work Duration (minutes)")
|
||||
.setDesc("Set the duration for each focus work session.")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g., 25")
|
||||
.setValue(this.plugin.settings.workDuration.toString())
|
||||
.onChange(async (value) => {
|
||||
const duration = parseInt(value, 10);
|
||||
if (!isNaN(duration) && duration > 0) {
|
||||
this.plugin.settings.workDuration = duration;
|
||||
await this.plugin.saveSettings();
|
||||
} else {
|
||||
new Notice(
|
||||
"Please enter a valid positive number for work duration."
|
||||
);
|
||||
text.setValue(
|
||||
this.plugin.settings.workDuration.toString()
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
new Setting(containerEl)
|
||||
.setName("Rest Duration (minutes)")
|
||||
.setDesc("Set the duration for each short rest session.")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g., 5")
|
||||
.setValue(this.plugin.settings.restDuration.toString())
|
||||
.onChange(async (value) => {
|
||||
const duration = parseInt(value, 10);
|
||||
if (!isNaN(duration) && duration > 0) {
|
||||
this.plugin.settings.restDuration = duration;
|
||||
await this.plugin.saveSettings();
|
||||
} else {
|
||||
new Notice(
|
||||
"Please enter a valid positive number for rest duration."
|
||||
);
|
||||
text.setValue(
|
||||
this.plugin.settings.restDuration.toString()
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Rest Duration Setting Input
|
||||
new Setting(containerEl)
|
||||
.setName('Rest Duration (minutes)')
|
||||
.setDesc('Set the duration for each short rest session.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('e.g., 5')
|
||||
.setValue(this.plugin.settings.restDuration.toString())
|
||||
.onChange(async (value) => {
|
||||
const duration = parseInt(value, 10);
|
||||
if (!isNaN(duration) && duration > 0) {
|
||||
this.plugin.settings.restDuration = duration;
|
||||
await this.plugin.saveSettings();
|
||||
console.log('Rest duration updated:', duration);
|
||||
} else {
|
||||
new Notice('Please enter a valid positive number for rest duration.');
|
||||
text.setValue(this.plugin.settings.restDuration.toString());
|
||||
}
|
||||
}));
|
||||
// --- End Focus Timer Settings Section ---
|
||||
containerEl.createEl("hr");
|
||||
|
||||
containerEl.createEl('hr');
|
||||
|
||||
// --- NEW: Animation Settings Section ---
|
||||
containerEl.createEl('h3', { text: 'Animation Settings' });
|
||||
|
||||
// Animation Enabled Toggle
|
||||
new Setting(containerEl)
|
||||
.setName('Enable Background Animation')
|
||||
.setDesc('Toggle the subtle background animation (e.g., falling leaves).')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.animationEnabled) // Set initial state from settings
|
||||
.onChange(async (value) => { // Function called when toggle state changes
|
||||
this.plugin.settings.animationEnabled = value; // Update setting in memory
|
||||
await this.plugin.saveSettings(); // Save settings to disk
|
||||
|
||||
if (value) {
|
||||
// If animation is enabled, start it
|
||||
this.plugin.startAnimation(); // Call the plugin method
|
||||
} else {
|
||||
// If animation is disabled, stop it
|
||||
this.plugin.stopAnimation(); // Call the plugin method
|
||||
}
|
||||
}));
|
||||
// --- END NEW ---
|
||||
containerEl.createEl("h3", { text: "Animation Settings" });
|
||||
new Setting(containerEl)
|
||||
.setName("Enable Background Animation")
|
||||
.setDesc(
|
||||
"Toggle the subtle background animation (e.g., falling circles)."
|
||||
)
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.animationEnabled)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.animationEnabled = value;
|
||||
await this.plugin.saveSettings();
|
||||
if (value) {
|
||||
this.plugin.startAnimation();
|
||||
} else {
|
||||
this.plugin.stopAnimation();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (this.plugin.settings.activeStreamId !== null) {
|
||||
this.localFolderSettingContainer.style.display = "none";
|
||||
this.localFolderBrowserContainer.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// --- Methods for Custom Folder Browser ---
|
||||
private async renderFolderContents(folderPath: string): Promise<void> { /* ... existing code ... */
|
||||
this.folderListEl.empty();
|
||||
this.currentPathEl.setText(`Current Path: ${folderPath || '/'}`);
|
||||
private localFolderSettingContainer: HTMLElement;
|
||||
private localFolderBrowserContainer: HTMLElement;
|
||||
|
||||
try {
|
||||
let folder: TFolder | null;
|
||||
private async renderFolderContents(folderPath: string): Promise<void> {
|
||||
this.folderListEl.empty();
|
||||
this.currentPathEl.setText(`Current Path: ${folderPath || "/"}`);
|
||||
try {
|
||||
let folder: TFolder | null;
|
||||
if (folderPath === "" || folderPath === "/") {
|
||||
folder = this.app.vault.getRoot();
|
||||
} else {
|
||||
const fileOrFolder =
|
||||
this.app.vault.getAbstractFileByPath(folderPath);
|
||||
if (fileOrFolder instanceof TFolder) {
|
||||
folder = fileOrFolder;
|
||||
} else {
|
||||
console.error("Invalid path or not a folder:", folderPath);
|
||||
this.folderListEl.createEl("div", {
|
||||
text: "Invalid folder path.",
|
||||
cls: "lofi-browser-error",
|
||||
});
|
||||
if (
|
||||
folderPath === this.currentBrowsePath &&
|
||||
folderPath !== ""
|
||||
) {
|
||||
new Notice(
|
||||
`The saved folder path "${folderPath}" is invalid. Displaying vault root instead.`
|
||||
);
|
||||
this.currentBrowsePath = "";
|
||||
this.renderFolderContents("");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (folder.parent) {
|
||||
const upButtonEl = this.folderListEl.createDiv(
|
||||
"lofi-browser-list-item"
|
||||
);
|
||||
upButtonEl.addClass("lofi-browser-item-folder");
|
||||
upButtonEl.createEl("span", {
|
||||
text: "⬆️ ..",
|
||||
cls: "lofi-browser-item-name",
|
||||
});
|
||||
upButtonEl.style.cursor = "pointer";
|
||||
upButtonEl.addEventListener("click", () => {
|
||||
this.navigateUp();
|
||||
});
|
||||
}
|
||||
const sortedChildren = folder.children.sort((a, b) => {
|
||||
const isAFolder = a instanceof TFolder;
|
||||
const isBFolder = b instanceof TFolder;
|
||||
if (isAFolder && !isBFolder) return -1;
|
||||
if (!isAFolder && isBFolder) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
for (const child of sortedChildren) {
|
||||
const itemEl = this.folderListEl.createDiv(
|
||||
"lofi-browser-list-item"
|
||||
);
|
||||
const iconEl = itemEl.createEl("span", {
|
||||
cls: "lofi-browser-item-icon",
|
||||
});
|
||||
const itemNameEl = itemEl.createEl("span", {
|
||||
text: child.name,
|
||||
cls: "lofi-browser-item-name",
|
||||
});
|
||||
if (child instanceof TFolder) {
|
||||
iconEl.setText("📁");
|
||||
itemEl.addClass("lofi-browser-item-folder");
|
||||
itemEl.style.cursor = "pointer";
|
||||
itemEl.addEventListener("click", () => {
|
||||
this.navigateToFolder(child.path);
|
||||
});
|
||||
} else if (child instanceof TFile) {
|
||||
iconEl.setText("📄");
|
||||
itemEl.addClass("lofi-browser-item-file");
|
||||
itemEl.style.cursor = "default";
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error rendering folder contents:", error);
|
||||
this.folderListEl.createEl("div", {
|
||||
text: "Error loading contents.",
|
||||
cls: "lofi-browser-error",
|
||||
});
|
||||
this.currentPathEl.setText(`Current Path: Error`);
|
||||
}
|
||||
}
|
||||
|
||||
if (folderPath === '' || folderPath === '/') {
|
||||
folder = this.app.vault.getRoot();
|
||||
} else {
|
||||
const fileOrFolder = this.app.vault.getAbstractFileByPath(folderPath);
|
||||
if (fileOrFolder instanceof TFolder) {
|
||||
folder = fileOrFolder;
|
||||
} else {
|
||||
console.error('Invalid path or not a folder:', folderPath);
|
||||
this.folderListEl.createEl('div', { text: 'Invalid folder path.', cls: 'lofi-browser-error' });
|
||||
if (folderPath === this.currentBrowsePath && folderPath !== '') {
|
||||
new Notice(`The saved folder path "${folderPath}" is invalid. Displaying vault root instead.`);
|
||||
this.currentBrowsePath = '';
|
||||
this.renderFolderContents('');
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
private navigateToFolder(folderPath: string): void {
|
||||
this.currentBrowsePath = normalizePath(folderPath);
|
||||
this.renderFolderContents(this.currentBrowsePath);
|
||||
}
|
||||
|
||||
if (folder.parent) {
|
||||
const upButtonEl = this.folderListEl.createDiv('lofi-browser-list-item');
|
||||
upButtonEl.addClass('lofi-browser-item-folder');
|
||||
upButtonEl.createEl('span', { text: '⬆️ ..', cls: 'lofi-browser-item-name' });
|
||||
upButtonEl.style.cursor = 'pointer';
|
||||
private navigateUp(): void {
|
||||
if (this.currentBrowsePath === "" || this.currentBrowsePath === "/") {
|
||||
return;
|
||||
}
|
||||
const currentFolder = this.app.vault.getAbstractFileByPath(
|
||||
this.currentBrowsePath
|
||||
);
|
||||
if (currentFolder instanceof TFolder && currentFolder.parent) {
|
||||
const parentPath = currentFolder.parent.path;
|
||||
this.currentBrowsePath = normalizePath(parentPath);
|
||||
this.renderFolderContents(this.currentBrowsePath);
|
||||
} else {
|
||||
this.currentBrowsePath = "";
|
||||
this.renderFolderContents("");
|
||||
}
|
||||
}
|
||||
|
||||
upButtonEl.addEventListener('click', () => {
|
||||
this.navigateUp();
|
||||
});
|
||||
}
|
||||
private async selectCurrentFolder(): Promise<void> {
|
||||
const folder =
|
||||
this.currentBrowsePath === ""
|
||||
? this.app.vault.getRoot()
|
||||
: this.app.vault.getAbstractFileByPath(this.currentBrowsePath);
|
||||
if (
|
||||
this.currentBrowsePath === "" ||
|
||||
this.currentBrowsePath === "/" ||
|
||||
folder instanceof TFolder
|
||||
) {
|
||||
const path = normalizePath(this.currentBrowsePath);
|
||||
this.plugin.settings.audioFolderPath = path;
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
const sortedChildren = folder.children.sort((a, b) => {
|
||||
const isAFolder = a instanceof TFolder;
|
||||
const isBFolder = b instanceof TFolder;
|
||||
this.renderTrackList();
|
||||
|
||||
if (isAFolder && !isBFolder) return -1;
|
||||
if (!isAFolder && isBFolder) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
new Notice(`Audio folder set to: "${path || "/"}"`);
|
||||
} else {
|
||||
console.error(
|
||||
"Cannot select invalid path as folder:",
|
||||
this.currentBrowsePath
|
||||
);
|
||||
new Notice("Cannot select current path: Not a valid folder.");
|
||||
this.currentBrowsePath = "";
|
||||
this.renderFolderContents("");
|
||||
this.renderTrackList();
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Rendering contents for "${folderPath || '/'}". Items found: ${sortedChildren.length}`);
|
||||
private renderTrackList(): void {
|
||||
this.trackListEl.empty();
|
||||
const playlist = this.plugin.playlist;
|
||||
if (playlist.length === 0) {
|
||||
this.trackListEl.createEl("div", {
|
||||
text: "No MP3 files found in the selected folder.",
|
||||
cls: "lofi-track-list-empty",
|
||||
});
|
||||
return;
|
||||
}
|
||||
playlist.forEach((trackVaultPath, index) => {
|
||||
const trackItemEl = this.trackListEl.createDiv(
|
||||
"lofi-track-list-item"
|
||||
);
|
||||
const trackName =
|
||||
trackVaultPath.split("/").pop() || "Unknown Track";
|
||||
trackItemEl.createEl("span", {
|
||||
text: `${index + 1}. ${trackName}`,
|
||||
cls: "lofi-track-name",
|
||||
});
|
||||
trackItemEl.addClass("lofi-track-item-clickable");
|
||||
trackItemEl.style.cursor = "pointer";
|
||||
trackItemEl.addEventListener("click", () => {
|
||||
this.plugin.playTrackByPath(trackVaultPath);
|
||||
this.updateTrackListPlayingState(
|
||||
this.plugin.getCurrentTrackIndex()
|
||||
);
|
||||
});
|
||||
if (index === this.plugin.getCurrentTrackIndex()) {
|
||||
trackItemEl.addClass("lofi-track-item-playing");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const child of sortedChildren) {
|
||||
const itemEl = this.folderListEl.createDiv('lofi-browser-list-item');
|
||||
const iconEl = itemEl.createEl('span', { cls: 'lofi-browser-item-icon' });
|
||||
const itemNameEl = itemEl.createEl('span', { text: child.name, cls: 'lofi-browser-item-name' });
|
||||
|
||||
if (child instanceof TFolder) {
|
||||
iconEl.setText('📁');
|
||||
itemEl.addClass('lofi-browser-item-folder');
|
||||
itemEl.style.cursor = 'pointer';
|
||||
|
||||
itemEl.addEventListener('click', () => {
|
||||
this.navigateToFolder(child.path);
|
||||
});
|
||||
console.log(`Rendering folder item: ${child.path}. Adding click listener.`);
|
||||
|
||||
} else if (child instanceof TFile) {
|
||||
iconEl.setText('📄');
|
||||
itemEl.addClass('lofi-browser-item-file');
|
||||
itemEl.style.cursor = 'default';
|
||||
console.log(`Rendering file item: ${child.path}.`);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error rendering folder contents:', error);
|
||||
this.folderListEl.createEl('div', { text: 'Error loading contents.', cls: 'lofi-browser-error' });
|
||||
this.currentPathEl.setText(`Current Path: Error`);
|
||||
}
|
||||
}
|
||||
|
||||
private navigateToFolder(folderPath: string): void {
|
||||
this.currentBrowsePath = normalizePath(folderPath);
|
||||
this.renderFolderContents(this.currentBrowsePath);
|
||||
}
|
||||
|
||||
private navigateUp(): void {
|
||||
if (this.currentBrowsePath === '' || this.currentBrowsePath === '/') {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFolder = this.app.vault.getAbstractFileByPath(this.currentBrowsePath);
|
||||
|
||||
if (currentFolder instanceof TFolder && currentFolder.parent) {
|
||||
const parentPath = currentFolder.parent.path;
|
||||
this.currentBrowsePath = normalizePath(parentPath);
|
||||
this.renderFolderContents(this.currentBrowsePath);
|
||||
} else {
|
||||
console.warn('Could not determine parent for path:', this.currentBrowsePath, '. Navigating to root as fallback.');
|
||||
this.currentBrowsePath = '';
|
||||
this.renderFolderContents('');
|
||||
}
|
||||
}
|
||||
|
||||
private async selectCurrentFolder(): Promise<void> {
|
||||
const folder = this.currentBrowsePath === '' ? this.app.vault.getRoot() : this.app.vault.getAbstractFileByPath(this.currentBrowsePath);
|
||||
|
||||
if (this.currentBrowsePath === '' || this.currentBrowsePath === '/' || folder instanceof TFolder) {
|
||||
const path = normalizePath(this.currentBrowsePath);
|
||||
this.plugin.settings.audioFolderPath = path;
|
||||
await this.plugin.saveSettings();
|
||||
await this.plugin.scanAudioFolder(path);
|
||||
|
||||
this.renderTrackList(); // Re-render track list after scan
|
||||
|
||||
new Notice(`Audio folder set to: "${path || '/'}"`);
|
||||
console.log('Audio folder setting saved:', path);
|
||||
|
||||
} else {
|
||||
console.error('Cannot select invalid path as folder:', this.currentBrowsePath);
|
||||
new Notice('Cannot select current path: Not a valid folder.');
|
||||
this.currentBrowsePath = '';
|
||||
this.renderFolderContents('');
|
||||
this.renderTrackList();
|
||||
}
|
||||
}
|
||||
// --- End Methods for Custom Folder Browser ---
|
||||
|
||||
|
||||
// --- Methods for Track List Display and Interaction (defined within LofiSettingTab) ---
|
||||
|
||||
private renderTrackList(): void {
|
||||
this.trackListEl.empty();
|
||||
|
||||
const playlist = this.plugin.playlist;
|
||||
|
||||
if (playlist.length === 0) {
|
||||
this.trackListEl.createEl('div', { text: 'No MP3 files found in the selected folder.', cls: 'lofi-track-list-empty' });
|
||||
} else {
|
||||
playlist.forEach((trackVaultPath, index) => {
|
||||
const trackItemEl = this.trackListEl.createDiv('lofi-track-list-item');
|
||||
const trackName = trackVaultPath.split('/').pop() || 'Unknown Track';
|
||||
|
||||
trackItemEl.createEl('span', { text: `${index + 1}. ${trackName}`, cls: 'lofi-track-name' });
|
||||
trackItemEl.addClass('lofi-track-item-clickable');
|
||||
trackItemEl.style.cursor = 'pointer';
|
||||
|
||||
trackItemEl.addEventListener('click', () => {
|
||||
this.plugin.playTrackByPath(trackVaultPath);
|
||||
this.updateTrackListPlayingState(this.plugin.getCurrentTrackIndex());
|
||||
});
|
||||
|
||||
if (index === this.plugin.getCurrentTrackIndex()) {
|
||||
trackItemEl.addClass('lofi-track-item-playing');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private updateTrackListPlayingState(playingIndex: number): void {
|
||||
const items = this.trackListEl.querySelectorAll('.lofi-track-list-item');
|
||||
|
||||
items.forEach((item, index) => {
|
||||
item.removeClass('lofi-track-item-playing');
|
||||
if (index === playingIndex) {
|
||||
item.addClass('lofi-track-item-playing');
|
||||
}
|
||||
});
|
||||
}
|
||||
// --- End Methods for Track List Display and Interaction ---
|
||||
}
|
||||
private updateTrackListPlayingState(playingIndex: number): void {
|
||||
const items = this.trackListEl.querySelectorAll(
|
||||
".lofi-track-list-item"
|
||||
);
|
||||
items.forEach((item, index) => {
|
||||
item.removeClass("lofi-track-item-playing");
|
||||
if (index === playingIndex) {
|
||||
item.addClass("lofi-track-item-playing");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
46
streams.ts
Normal file
46
streams.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
export interface LofiStream {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export const PREDEFINED_LOFI_STREAMS: LofiStream[] = [
|
||||
{
|
||||
id: "local",
|
||||
name: "Local Folder",
|
||||
url: "",
|
||||
description: "Use MP3 files from the configured local folder.",
|
||||
},
|
||||
{
|
||||
id: "chill-mornings",
|
||||
name: "Chill Mornings",
|
||||
url: "https://antares.dribbcast.com/proxy/s8280/stream",
|
||||
description: "A mellow mix for starting your day.",
|
||||
},
|
||||
{
|
||||
id: "jazzy-afternoons",
|
||||
name: "Jazzy Afternoons",
|
||||
url: "https://relay0.r-a-d.io/main.mp3",
|
||||
description: "Smooth jazz vibes for a productive afternoon.",
|
||||
},
|
||||
{
|
||||
id: "japanese-music",
|
||||
name: "Anime & Japanese Music",
|
||||
url: "http://tsuiokuyo.ddns.net:8764/lossy",
|
||||
description: "Broadcasting anime and Japanese music.",
|
||||
},
|
||||
{
|
||||
id: "lofi-hiphop-radio",
|
||||
name: "Lofi Hip Hop Radio",
|
||||
url: "https://streams.fluxfm.de/Chillhop/mp3-128/streams.fluxfm.de/",
|
||||
description: "24/7 lofi hip hop beats.",
|
||||
},
|
||||
];
|
||||
|
||||
export function getStreamById(id: string | null): LofiStream | undefined {
|
||||
if (id === null || id === "local") {
|
||||
return PREDEFINED_LOFI_STREAMS.find((stream) => stream.id === "local");
|
||||
}
|
||||
return PREDEFINED_LOFI_STREAMS.find((stream) => stream.id === id);
|
||||
}
|
||||
352
styles.css
352
styles.css
|
|
@ -12,136 +12,148 @@ If your plugin does not need CSS, delete this file.
|
|||
|
||||
/* Overall container for the navigation elements (path and Up button) */
|
||||
.lofi-folder-browser-nav {
|
||||
display: flex; /* Arrange path and button side-by-side */
|
||||
align-items: center; /* Vertically align items */
|
||||
gap: 10px; /* Space between the path element and the Up button */
|
||||
margin-bottom: 10px; /* Space below the navigation bar */
|
||||
padding-bottom: 8px; /* Space below the border */
|
||||
border-bottom: 1px solid var(--background-modifier-border); /* Add a separating line */
|
||||
flex-wrap: wrap; /* Allow items to wrap on smaller screens */
|
||||
display: flex; /* Arrange path and button side-by-side */
|
||||
align-items: center; /* Vertically align items */
|
||||
gap: 10px; /* Space between the path element and the Up button */
|
||||
margin-bottom: 10px; /* Space below the navigation bar */
|
||||
padding-bottom: 8px; /* Space below the border */
|
||||
border-bottom: 1px solid var(--background-modifier-border); /* Add a separating line */
|
||||
flex-wrap: wrap; /* Allow items to wrap on smaller screens */
|
||||
}
|
||||
|
||||
/* Element displaying the current path */
|
||||
.lofi-browser-current-path {
|
||||
flex-grow: 1; /* Allow the path text to take up available space */
|
||||
font-weight: bold; /* Make the path text stand out */
|
||||
color: var(--text-normal); /* Use normal text color from theme */
|
||||
word-break: break-all; /* Prevent long paths from overflowing */
|
||||
flex-grow: 1; /* Allow the path text to take up available space */
|
||||
font-weight: bold; /* Make the path text stand out */
|
||||
color: var(--text-normal); /* Use normal text color from theme */
|
||||
word-break: break-all; /* Prevent long paths from overflowing */
|
||||
}
|
||||
|
||||
/* Style for the 'Up' button */
|
||||
.lofi-browser-up-button {
|
||||
/* Use Obsidian's button styling variables */
|
||||
padding: 4px 10px; /* Adjust padding */
|
||||
cursor: pointer; /* Indicate clickable */
|
||||
border: 1px solid var(--interactive-accent); /* Accent color border */
|
||||
border-radius: var(--button-border-radius); /* Rounded corners */
|
||||
background-color: var(--background-primary-alt); /* Background color from theme */
|
||||
color: var(--text-normal); /* Text color from theme */
|
||||
font-size: var(--font-ui-small); /* Match smaller UI font */
|
||||
line-height: 1; /* Adjust line height */
|
||||
transition: background-color 0.15s ease; /* Smooth hover effect */
|
||||
/* Use Obsidian's button styling variables */
|
||||
padding: 4px 10px; /* Adjust padding */
|
||||
cursor: pointer; /* Indicate clickable */
|
||||
border: 1px solid var(--interactive-accent); /* Accent color border */
|
||||
border-radius: var(--button-border-radius); /* Rounded corners */
|
||||
background-color: var(
|
||||
--background-primary-alt
|
||||
); /* Background color from theme */
|
||||
color: var(--text-normal); /* Text color from theme */
|
||||
font-size: var(--font-ui-small); /* Match smaller UI font */
|
||||
line-height: 1; /* Adjust line height */
|
||||
transition: background-color 0.15s ease; /* Smooth hover effect */
|
||||
}
|
||||
|
||||
/* Hover effect for the 'Up' button */
|
||||
.lofi-browser-up-button:hover {
|
||||
background-color: var(--background-modifier-hover); /* Hover background color */
|
||||
background-color: var(
|
||||
--background-modifier-hover
|
||||
); /* Hover background color */
|
||||
}
|
||||
|
||||
/* Style for the 'Select This Folder' button */
|
||||
.lofi-browser-select-button {
|
||||
/* Similar styling to the Up button */
|
||||
display: block; /* Make it a block element to take full width */
|
||||
width: 100%; /* Full width */
|
||||
margin-top: 10px; /* Space above the select button */
|
||||
padding: 6px 10px; /* Adjust padding */
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--interactive-accent);
|
||||
border-radius: var(--button-border-radius);
|
||||
background-color: var(--interactive-accent); /* Use accent color for primary action */
|
||||
color: var(--text-inverted); /* Inverted text color for accent background */
|
||||
font-size: var(--font-ui-normal); /* Normal UI font size */
|
||||
line-height: 1;
|
||||
transition: background-color 0.15s ease;
|
||||
/* Similar styling to the Up button */
|
||||
display: block; /* Make it a block element to take full width */
|
||||
width: 100%; /* Full width */
|
||||
margin-top: 10px; /* Space above the select button */
|
||||
padding: 6px 10px; /* Adjust padding */
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--interactive-accent);
|
||||
border-radius: var(--button-border-radius);
|
||||
background-color: var(
|
||||
--interactive-accent
|
||||
); /* Use accent color for primary action */
|
||||
color: var(--text-inverted); /* Inverted text color for accent background */
|
||||
font-size: var(--font-ui-normal); /* Normal UI font size */
|
||||
line-height: 1;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
/* Hover effect for the 'Select This Folder' button */
|
||||
.lofi-browser-select-button:hover {
|
||||
background-color: var(--interactive-accent-hover); /* Darker accent color on hover */
|
||||
background-color: var(
|
||||
--interactive-accent-hover
|
||||
); /* Darker accent color on hover */
|
||||
}
|
||||
|
||||
|
||||
/* Container for the list of folder and file items */
|
||||
.lofi-folder-browser-list {
|
||||
border: 1px solid var(--background-modifier-border); /* Border around the list */
|
||||
max-height: 250px; /* Set a maximum height to make the list scrollable */
|
||||
overflow-y: auto; /* Add a vertical scrollbar when content exceeds max height */
|
||||
padding: 5px; /* Padding inside the list container */
|
||||
border-radius: var(--input-border-radius); /* Match input field corner radius */
|
||||
background-color: var(--background-secondary); /* Slightly different background for the list area */
|
||||
border: 1px solid var(--background-modifier-border); /* Border around the list */
|
||||
max-height: 250px; /* Set a maximum height to make the list scrollable */
|
||||
overflow-y: auto; /* Add a vertical scrollbar when content exceeds max height */
|
||||
padding: 5px; /* Padding inside the list container */
|
||||
border-radius: var(
|
||||
--input-border-radius
|
||||
); /* Match input field corner radius */
|
||||
background-color: var(
|
||||
--background-secondary
|
||||
); /* Slightly different background for the list area */
|
||||
}
|
||||
|
||||
/* Style for each individual item (folder or file) in the list */
|
||||
.lofi-browser-list-item {
|
||||
display: flex; /* Arrange icon and name side-by-side */
|
||||
align-items: center; /* Vertically align icon and name */
|
||||
padding: 4px 8px; /* Padding inside each list item */
|
||||
margin-bottom: 2px; /* Space between list items */
|
||||
border-radius: var(--button-border-radius); /* Small rounded corners */
|
||||
transition: background-color 0.1s ease; /* Smooth hover effect */
|
||||
display: flex; /* Arrange icon and name side-by-side */
|
||||
align-items: center; /* Vertically align icon and name */
|
||||
padding: 4px 8px; /* Padding inside each list item */
|
||||
margin-bottom: 2px; /* Space between list items */
|
||||
border-radius: var(--button-border-radius); /* Small rounded corners */
|
||||
transition: background-color 0.1s ease; /* Smooth hover effect */
|
||||
}
|
||||
|
||||
/* Style for the icon span within a list item */
|
||||
.lofi-browser-item-icon {
|
||||
margin-right: 8px; /* Space between icon and name */
|
||||
/* Optional: Adjust icon size or color */
|
||||
font-size: 1.1em;
|
||||
color: var(--icon-color); /* Use theme icon color */
|
||||
margin-right: 8px; /* Space between icon and name */
|
||||
/* Optional: Adjust icon size or color */
|
||||
font-size: 1.1em;
|
||||
color: var(--icon-color); /* Use theme icon color */
|
||||
}
|
||||
|
||||
/* Style for the name span within a list item */
|
||||
.lofi-browser-item-name {
|
||||
flex-grow: 1; /* Allow the name to take up available space */
|
||||
user-select: none; /* Prevent text selection when clicking */
|
||||
white-space: nowrap; /* Prevent name from wrapping */
|
||||
overflow: hidden; /* Hide overflowing text */
|
||||
text-overflow: ellipsis; /* Show ellipsis (...) for truncated names */
|
||||
flex-grow: 1; /* Allow the name to take up available space */
|
||||
user-select: none; /* Prevent text selection when clicking */
|
||||
white-space: nowrap; /* Prevent name from wrapping */
|
||||
overflow: hidden; /* Hide overflowing text */
|
||||
text-overflow: ellipsis; /* Show ellipsis (...) for truncated names */
|
||||
}
|
||||
|
||||
/* Style specific to folder items (clickable) */
|
||||
.lofi-browser-item-folder {
|
||||
cursor: pointer; /* Indicate clickable */
|
||||
/* background-color: var(--background-secondary); /* Optional: Different background for folders */
|
||||
cursor: pointer; /* Indicate clickable */
|
||||
/* background-color: var(--background-secondary); /* Optional: Different background for folders */
|
||||
}
|
||||
|
||||
/* Hover effect for folder items */
|
||||
.lofi-browser-item-folder:hover {
|
||||
background-color: var(--background-modifier-hover); /* Hover background color */
|
||||
color: var(--text-accent); /* Optional: Change text color on hover */
|
||||
background-color: var(
|
||||
--background-modifier-hover
|
||||
); /* Hover background color */
|
||||
color: var(--text-accent); /* Optional: Change text color on hover */
|
||||
}
|
||||
|
||||
/* Style specific to file items (not clickable for navigation) */
|
||||
.lofi-browser-item-file {
|
||||
opacity: 0.8; /* Slightly dim file items to distinguish from folders */
|
||||
cursor: default; /* Explicitly show default cursor */
|
||||
opacity: 0.8; /* Slightly dim file items to distinguish from folders */
|
||||
cursor: default; /* Explicitly show default cursor */
|
||||
}
|
||||
|
||||
/* Style for error messages within the browser */
|
||||
.lofi-browser-error {
|
||||
color: var(--text-error); /* Use error text color (red) */
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
color: var(--text-error); /* Use error text color (red) */
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* --- End Styles for the Custom Folder Browser UI --- */
|
||||
|
||||
|
||||
/* You can keep your existing styles for the ribbon icon etc. below this */
|
||||
|
||||
.lofi-plugin-ribbon-icon {
|
||||
/* Add your styles for the ribbon icon here */
|
||||
/* For example: */
|
||||
/* fill: purple; */
|
||||
/* Add your styles for the ribbon icon here */
|
||||
/* For example: */
|
||||
/* fill: purple; */
|
||||
}
|
||||
|
||||
/* Styles for the status bar item if you want to customize it */
|
||||
|
|
@ -152,59 +164,62 @@ If your plugin does not need CSS, delete this file.
|
|||
/* --- Styles for the Track List --- */
|
||||
|
||||
.lofi-track-list {
|
||||
border: 1px solid var(--background-modifier-border); /* Border around the list */
|
||||
max-height: 150px; /* Set a max height to make it scrollable */
|
||||
overflow-y: auto; /* Add scrollbar if content exceeds height */
|
||||
padding: 5px; /* Padding inside */
|
||||
border-radius: var(--input-border-radius); /* Match other elements */
|
||||
background-color: var(--background-secondary); /* Same background as folder list */
|
||||
margin-top: 10px; /* Space above the list */
|
||||
border: 1px solid var(--background-modifier-border); /* Border around the list */
|
||||
max-height: 150px; /* Set a max height to make it scrollable */
|
||||
overflow-y: auto; /* Add scrollbar if content exceeds height */
|
||||
padding: 5px; /* Padding inside */
|
||||
border-radius: var(--input-border-radius); /* Match other elements */
|
||||
background-color: var(
|
||||
--background-secondary
|
||||
); /* Same background as folder list */
|
||||
margin-top: 10px; /* Space above the list */
|
||||
}
|
||||
|
||||
.lofi-track-list-empty {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
color: var(--text-muted); /* Muted text color */
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
color: var(--text-muted); /* Muted text color */
|
||||
}
|
||||
|
||||
.lofi-track-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
margin-bottom: 2px;
|
||||
border-radius: var(--button-border-radius);
|
||||
transition: background-color 0.1s ease;
|
||||
cursor: pointer; /* Indicate clickable */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
margin-bottom: 2px;
|
||||
border-radius: var(--button-border-radius);
|
||||
transition: background-color 0.1s ease;
|
||||
cursor: pointer; /* Indicate clickable */
|
||||
}
|
||||
|
||||
.lofi-track-list-item:hover {
|
||||
background-color: var(--background-modifier-hover); /* Hover effect */
|
||||
background-color: var(--background-modifier-hover); /* Hover effect */
|
||||
}
|
||||
|
||||
/* Style for the track name text */
|
||||
.lofi-track-name {
|
||||
flex-grow: 1; /* Allow name to take space */
|
||||
white-space: nowrap; /* Prevent name from wrapping */
|
||||
overflow: hidden; /* Hide overflowing text */
|
||||
text-overflow: ellipsis; /* Show ellipsis (...) for truncated names */
|
||||
flex-grow: 1; /* Allow name to take space */
|
||||
white-space: nowrap; /* Prevent name from wrapping */
|
||||
overflow: hidden; /* Hide overflowing text */
|
||||
text-overflow: ellipsis; /* Show ellipsis (...) for truncated names */
|
||||
}
|
||||
|
||||
|
||||
/* Style for the currently playing track item */
|
||||
.lofi-track-item-playing {
|
||||
background-color: var(--interactive-accent); /* Use accent color for the playing track */
|
||||
color: var(--text-inverted); /* Inverted text color */
|
||||
font-weight: bold; /* Bold text for playing track */
|
||||
background-color: var(
|
||||
--interactive-accent
|
||||
); /* Use accent color for the playing track */
|
||||
color: var(--text-inverted); /* Inverted text color */
|
||||
font-weight: bold; /* Bold text for playing track */
|
||||
}
|
||||
|
||||
.lofi-track-item-playing .lofi-track-name {
|
||||
/* Ensure name color is inverted too */
|
||||
color: var(--text-inverted);
|
||||
/* Ensure name color is inverted too */
|
||||
color: var(--text-inverted);
|
||||
}
|
||||
|
||||
.lofi-track-item-playing:hover {
|
||||
/* Adjust hover effect for the playing item */
|
||||
background-color: var(--interactive-accent-hover);
|
||||
/* Adjust hover effect for the playing item */
|
||||
background-color: var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
/* styles.css (add these below the track list styles) */
|
||||
|
|
@ -212,39 +227,43 @@ If your plugin does not need CSS, delete this file.
|
|||
/* --- Styles for Playback Controls --- */
|
||||
|
||||
.lofi-playback-controls {
|
||||
margin-top: 10px; /* Space above the controls */
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--background-modifier-border); /* Separator line */
|
||||
margin-top: 10px; /* Space above the controls */
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--background-modifier-border); /* Separator line */
|
||||
}
|
||||
|
||||
.lofi-playback-buttons-container {
|
||||
display: flex; /* Arrange buttons in a row */
|
||||
gap: 10px; /* Space between buttons */
|
||||
justify-content: center; /* Center the buttons */
|
||||
align-items: center;
|
||||
margin-bottom: 10px; /* Space below the button row */
|
||||
display: flex; /* Arrange buttons in a row */
|
||||
gap: 10px; /* Space between buttons */
|
||||
justify-content: center; /* Center the buttons */
|
||||
align-items: center;
|
||||
margin-bottom: 10px; /* Space below the button row */
|
||||
}
|
||||
|
||||
.lofi-playback-button {
|
||||
/* Basic button styling - similar to navigation buttons */
|
||||
padding: 5px 12px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--interactive-accent);
|
||||
border-radius: var(--button-border-radius);
|
||||
background-color: var(--background-primary-alt);
|
||||
color: var(--text-normal);
|
||||
font-size: var(--font-ui-normal);
|
||||
line-height: 1;
|
||||
transition: background-color 0.15s ease;
|
||||
/* Basic button styling - similar to navigation buttons */
|
||||
padding: 5px 12px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--interactive-accent);
|
||||
border-radius: var(--button-border-radius);
|
||||
background-color: var(--background-primary-alt);
|
||||
color: var(--text-normal);
|
||||
font-size: var(--font-ui-normal);
|
||||
line-height: 1;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.lofi-playback-button:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
/* Optional: Specific styles for Prev/Next if needed */
|
||||
.lofi-prev-button { /* ... */ }
|
||||
.lofi-next-button { /* ... */ }
|
||||
.lofi-prev-button {
|
||||
/* ... */
|
||||
}
|
||||
.lofi-next-button {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
/* Optional: Style for shuffle toggle if added later */
|
||||
/* .lofi-shuffle-toggle .setting-item-control { ... } */
|
||||
|
|
@ -255,17 +274,17 @@ If your plugin does not need CSS, delete this file.
|
|||
|
||||
/* Style for each individual status bar item used as a control button */
|
||||
.status-bar-item.lofi-control-button {
|
||||
/* Make it look like a button/icon */
|
||||
cursor: pointer;
|
||||
padding: 0 8px; /* Add some padding around the text/icon */
|
||||
/* Inherit text/color from status bar */
|
||||
/* Optional: Add subtle background/border on hover */
|
||||
border-radius: 4px; /* Small rounded corners */
|
||||
transition: background-color 0.1s ease;
|
||||
/* Make it look like a button/icon */
|
||||
cursor: pointer;
|
||||
padding: 0 8px; /* Add some padding around the text/icon */
|
||||
/* Inherit text/color from status bar */
|
||||
/* Optional: Add subtle background/border on hover */
|
||||
border-radius: 4px; /* Small rounded corners */
|
||||
transition: background-color 0.1s ease;
|
||||
}
|
||||
|
||||
.status-bar-item.lofi-control-button:hover {
|
||||
background-color: var(--background-modifier-hover); /* Highlight on hover */
|
||||
background-color: var(--background-modifier-hover); /* Highlight on hover */
|
||||
}
|
||||
|
||||
/* Specific styles if needed (e.g., different colors) */
|
||||
|
|
@ -276,7 +295,6 @@ If your plugin does not need CSS, delete this file.
|
|||
/* If using SVG icons, you might need styles for the svg element inside */
|
||||
/* .status-bar-item.lofi-control-button svg { fill: var(--icon-color); width: 18px; height: 18px; vertical-align: middle; } */
|
||||
|
||||
|
||||
/* Style for the main status bar text item */
|
||||
/* .status-bar-item:not(.lofi-control-button) { ... } */
|
||||
|
||||
|
|
@ -286,22 +304,22 @@ If your plugin does not need CSS, delete this file.
|
|||
|
||||
/* Style for the timer display text element */
|
||||
.status-bar-item.lofi-timer-display {
|
||||
/* Inherit font/color from status bar */
|
||||
font-weight: bold; /* Make the time display prominent */
|
||||
margin-left: 15px; /* Add space to the left to separate from audio controls */
|
||||
padding: 0 8px; /* Add padding */
|
||||
/* Optional: Add a border or background */
|
||||
/* border: 1px solid var(--background-modifier-border); */
|
||||
/* border-radius: var(--button-border-radius); */
|
||||
/* background-color: var(--background-secondary); */
|
||||
/* Inherit font/color from status bar */
|
||||
font-weight: bold; /* Make the time display prominent */
|
||||
margin-left: 15px; /* Add space to the left to separate from audio controls */
|
||||
padding: 0 8px; /* Add padding */
|
||||
/* Optional: Add a border or background */
|
||||
/* border: 1px solid var(--background-modifier-border); */
|
||||
/* border-radius: var(--button-border-radius); */
|
||||
/* background-color: var(--background-secondary); */
|
||||
}
|
||||
|
||||
/* Style for the timer control buttons */
|
||||
/* Re-uses .lofi-control-button from audio controls, adds specific styles */
|
||||
.status-bar-item.lofi-timer-control-button {
|
||||
/* Any styles specific to timer buttons if they differ from audio controls */
|
||||
/* e.g., different color */
|
||||
/* color: var(--interactive-accent); */
|
||||
/* Any styles specific to timer buttons if they differ from audio controls */
|
||||
/* e.g., different color */
|
||||
/* color: var(--interactive-accent); */
|
||||
}
|
||||
|
||||
/* Specific styles for Play/Pause timer button */
|
||||
|
|
@ -310,28 +328,26 @@ If your plugin does not need CSS, delete this file.
|
|||
/* Specific styles for Reset timer button */
|
||||
/* .status-bar-item.lofi-timer-reset-button { ... } */
|
||||
|
||||
|
||||
/* Ensure default status bar items still look normal */
|
||||
/* .status-bar-item:not(.lofi-control-button):not(.lofi-timer-display) { ... } */
|
||||
|
||||
|
||||
/* styles.css (add these below other styles) */
|
||||
|
||||
/* --- Styles for Status Bar Playback Controls (Audio) --- */
|
||||
|
||||
/* Style for each individual status bar item used as a control button */
|
||||
.status-bar-item.lofi-control-button {
|
||||
cursor: pointer;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.1s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.1s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.status-bar-item.lofi-control-button:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
/* Specific styles if needed */
|
||||
|
|
@ -341,32 +357,30 @@ If your plugin does not need CSS, delete this file.
|
|||
/* .status-bar-item.lofi-timer-play-pause-button { ... } */
|
||||
/* .status-bar-item.lofi-timer-reset-button { ... } */
|
||||
|
||||
|
||||
/* Style for the main status bar text item (Audio Status) */
|
||||
/* .status-bar-item:not(.lofi-control-button):not(.lofi-timer-display) { ... } */
|
||||
|
||||
|
||||
/* --- Styles for Timer Status Bar Items --- */
|
||||
|
||||
/* Style for the timer display text element */
|
||||
.status-bar-item.lofi-timer-display {
|
||||
font-weight: bold;
|
||||
margin-left: 15px;
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
margin-left: 15px;
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* --- NEW: Styles for Animation Canvas --- */
|
||||
.lofi-animation-canvas {
|
||||
/* Styles are primarily set in main.ts, but defining the class is good practice */
|
||||
/* These styles ensure it acts as a fixed overlay */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0; /* Or 1, below most Obsidian UI elements */
|
||||
pointer-events: none; /* Crucial: allows clicks/interaction with UI elements behind the canvas */
|
||||
/* Styles are primarily set in main.ts, but defining the class is good practice */
|
||||
/* These styles ensure it acts as a fixed overlay */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0; /* Or 1, below most Obsidian UI elements */
|
||||
pointer-events: none; /* Crucial: allows clicks/interaction with UI elements behind the canvas */
|
||||
}
|
||||
/* --- END NEW --- */
|
||||
/* --- END NEW --- */
|
||||
|
|
|
|||
22
types.ts
22
types.ts
|
|
@ -1,15 +1,9 @@
|
|||
// Defines the structure of your plugin's settings saved to data.json
|
||||
export interface LofiPluginSettings {
|
||||
mySetting: string; // Sample setting (can be removed later if not used)
|
||||
volume: number; // Volume setting (0-100)
|
||||
audioFolderPath: string; // Setting for the folder containing audio files (vault path)
|
||||
|
||||
// Focus Timer Settings
|
||||
workDuration: number; // Duration of the work session in minutes
|
||||
restDuration: number; // Duration of the rest session in minutes
|
||||
|
||||
// --- NEW: Animation Settings ---
|
||||
animationEnabled: boolean; // Toggle to enable/disable the animation
|
||||
// Add settings for animation type (leaves, rain), density, speed, etc. later
|
||||
// --- END NEW ---
|
||||
}
|
||||
mySetting: string;
|
||||
volume: number;
|
||||
audioFolderPath: string;
|
||||
workDuration: number;
|
||||
restDuration: number;
|
||||
animationEnabled: boolean;
|
||||
activeStreamId: string | null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue