// Import the SunCalc library to calculate sun position and light phases import SunCalc from "suncalc"; // Documentation: https://www.npmjs.com/package/suncalc import { App, Plugin, PluginSettingTab, Setting, debounce } from "obsidian"; /** * SDNS: Smart DayNight Switcher */ interface SDNSPluginSettings { latitude: string; longitude: string; dawnOffset: string; duskOffset: string; } const DEFAULT_SETTINGS: SDNSPluginSettings = { latitude: "51.507351", longitude: "-0.127758", dawnOffset: "0", duskOffset: "0", }; export default class SDNSPlugin extends Plugin { private timeout: ReturnType | null; settings: SDNSPluginSettings; defaultColorScheme: "dark" | "light"; async onload() { await this.loadSettings(); this.addSettingTab(new SDNSPluginSettingTab(this.app, this)); this.saveDefaultColorScheme(); this.checkAndSwitchColorScheme(); } onunload() { if (this.timeout !== null) { clearTimeout(this.timeout); this.timeout = null; } this.setColorScheme(this.defaultColorScheme); } async loadSettings() { this.settings = Object.assign( {}, DEFAULT_SETTINGS, await this.loadData() ); } async saveSettings() { await this.saveData(this.settings); this.checkAndSwitchColorScheme(); } checkAndSwitchColorScheme() { const now = Date.now(); const { dawn, sunsetStart: dusk } = SunCalc.getTimes( new Date(), +this.settings.latitude, +this.settings.longitude ); const tomorrowDawn = SunCalc.getTimes( new Date(now + 24 * 60 * 60 * 1000), +this.settings.latitude, +this.settings.longitude ).dawn; const times = { dawn: calcWithOffset( dawn.getTime() as number, this.settings.dawnOffset ), dusk: calcWithOffset( dusk.getTime() as number, this.settings.duskOffset ), tomorrowDawn: calcWithOffset( tomorrowDawn.getTime(), this.settings.duskOffset ), }; let checkDelay: number; if (now < dawn) { this.setColorScheme("dark"); checkDelay = times.dawn - now; } else if (now >= dawn && now < dusk) { this.setColorScheme("light"); checkDelay = times.dusk - now; } else { this.setColorScheme("dark"); checkDelay = times.tomorrowDawn - now; } if (this.timeout) { clearTimeout(this.timeout); } this.timeout = setTimeout( this.checkAndSwitchColorScheme.bind(this), checkDelay ); } setColorScheme(targetColorScheme: "dark" | "light") { switch (targetColorScheme) { case "dark": document.body.classList.remove("theme-light"); document.body.classList.add("theme-dark"); break; case "light": document.body.classList.remove("theme-dark"); document.body.classList.add("theme-light"); break; } this.app.workspace.trigger("css-change"); } saveDefaultColorScheme() { const getColorScheme = () => document.body.classList.contains("theme-dark") ? "dark" : "light"; this.defaultColorScheme = getColorScheme(); } } class SDNSPluginSettingTab extends PluginSettingTab { plugin: SDNSPlugin; constructor(app: App, plugin: SDNSPlugin) { super(app, plugin); this.plugin = plugin; } display(): void { const { containerEl } = this; containerEl.empty(); containerEl.createEl("p", { text: "Please enter the latitude and longitude of your approximate location (coords of any city within ±200 km)", }); new Setting(containerEl) .setName("Latitude") .setDesc( "Please enter the latitude of your approximate location (within ±200 km)" ) .addText((text) => text .setPlaceholder("Latitude") .setValue(this.plugin.settings.latitude) .onChange( debounce(async (value) => { this.plugin.settings.latitude = value; await this.plugin.saveSettings(); }, 300) ) ); new Setting(containerEl) .setName("Longitude") .setDesc( "Please enter the longitude of your approximate location (within ±200 km)" ) .addText((text) => text .setPlaceholder("Longitude") .setValue(this.plugin.settings.longitude) .onChange( debounce(async (value) => { this.plugin.settings.longitude = value; await this.plugin.saveSettings(); }, 300) ) ); const addAdditionalDescription = () => { const additionalDescription = containerEl.createEl("p"); additionalDescription.createEl("small", { text: "To easily find your latitude and longitude, you can use any simple online service, such as ", }); additionalDescription.createEl("small").createEl("a", { href: "https://www.latlong.net/", text: "latlong.net", }); additionalDescription.createEl("small", { text: ", " }); additionalDescription.createEl("small").createEl("a", { href: "https://www.gps-coordinates.net/", text: "gps-coordinates.net", }); additionalDescription.createEl("small", { text: ", or any other similar tools available on the web.", }); additionalDescription.createEl("br"); additionalDescription.createEl("br"); additionalDescription.createEl("small", { text: "* Obsidian does not provide developers with access to geolocation, so this plugin cannot automatically determine your coordinates.", }); additionalDescription.createEl("br"); additionalDescription.createEl("small", { text: "However, " }); additionalDescription.createEl("small").createEl("strong", { text: "to accurately calculate sunrise and sunset times in your location", }); additionalDescription.createEl("small", { text: ", the formula needs an approximate location (within ±200 km) of where you are.", }); }; addAdditionalDescription(); new Setting(containerEl) .setName("Dawn Offset") .setDesc( "* Optional. Enter offset in minutes. Use minus before number to make negative offset" ) .addText((dawnOffset) => dawnOffset .setPlaceholder("0") .setValue(this.plugin.settings.dawnOffset) .onChange( debounce(async (value) => { this.plugin.settings.dawnOffset = value; await this.plugin.saveSettings(); }, 300) ) ); new Setting(containerEl) .setName("Dusk Offset") .setDesc( "* Optional. Enter offset in minutes. Use minus before number to make negative offset" ) .addText((duskOffset) => duskOffset .setPlaceholder("0") .setValue(this.plugin.settings.duskOffset) .onChange( debounce(async (value) => { this.plugin.settings.duskOffset = value; await this.plugin.saveSettings(); }, 300) ) ); const addSchedule = () => { const dynamicContent = containerEl.createEl("div"); const scheduleHeader = dynamicContent.createEl("h4", { text: "Schedule:", }); scheduleHeader.setCssStyles({ borderTop: "1px solid var(--background-modifier-border)", margin: "1em auto 1em", paddingTop: "1em", }); const table = dynamicContent.createEl("table", { attr: { style: "width: 100%; text-align: center;" }, }); const tbody = table.createEl("tbody"); const headerRow = tbody.createEl("tr"); headerRow.createEl("th", { text: "Date" }); const dawnTh = headerRow.createEl("th"); dawnTh.createEl("span", { text: "Dawn" }); dawnTh.createEl("br"); dawnTh.createEl("small", { text: "(Light mode will be enabled)" }); const duskTh = headerRow.createEl("th"); duskTh.createEl("span", { text: "Dusk" }); duskTh.createEl("br"); duskTh.createEl("small", { text: "(Dark mode will be enabled)" }); const date = new Date(); for (let i = 0; i <= 30; i++) { const { dawn: rawDawn, sunsetStart: rawDusk } = SunCalc.getTimes( date, +this.plugin.settings.latitude, +this.plugin.settings.longitude ); const dawn = new Date( calcWithOffset( rawDawn.getTime(), this.plugin.settings.dawnOffset ) ); const dusk = new Date( calcWithOffset( rawDusk.getTime(), this.plugin.settings.duskOffset ) ); const dateStr = `${date.getDate()} ${date.toLocaleString( "en-US", { month: "short", } )} ${date.getFullYear()}`; const dawnStr = `${String(dawn.getHours()).padStart( 2, "0" )}:${String(dawn.getMinutes()).padStart(2, "0")}`; const duskStr = `${String(dusk.getHours()).padStart( 2, "0" )}:${String(dusk.getMinutes()).padStart(2, "0")}`; const row = tbody.createEl("tr"); row.createEl("td", { text: dateStr }); row.createEl("td", { text: dawnStr }); row.createEl("td", { text: duskStr }); date.setDate(date.getDate() + 1); } }; addSchedule(); } } function calcWithOffset(initialTimestamp: number, offsetStr: string) { const millisecondsInOneMinute = 1000 * 60; const offset: number = Number(offsetStr); if (isNaN(offset)) { return initialTimestamp; } return initialTimestamp + offset * millisecondsInOneMinute; }