Fix play/pause button and stream playback control bug + Set safer defaults + reformatting

This commit is contained in:
Jatin-Shihora 2025-10-29 13:26:17 +05:30
parent 870c0f47ce
commit 6250dc04e7
12 changed files with 3806 additions and 1096 deletions

8
.gitignore vendored
View file

@ -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
View file

@ -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! 🎵⏱️**

View file

@ -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",
};

View file

@ -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",

1340
main.ts

File diff suppressed because it is too large Load diff

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -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 --- */

View file

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