diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml new file mode 100644 index 0000000000..f4ea1a75fd --- /dev/null +++ b/.github/workflows/pages.yaml @@ -0,0 +1,69 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: ["master"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Create redirect index.html + run: | + cat > index.html << 'EOF' + + + + + + + MagicMirror² Demo + + + + +

Redirecting to MagicMirror² Demo...

+ + + EOF + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: "." + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/demo/README.md b/docs/demo/README.md new file mode 100644 index 0000000000..af5947ce81 --- /dev/null +++ b/docs/demo/README.md @@ -0,0 +1,128 @@ +# MagicMirror² Browser Demo + +A browser-based demo version of [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror). + +## Live Demo + +**Try it now:** [https://magicmirrororg.github.io/MagicMirror/docs/demo/](https://magicmirrororg.github.io/MagicMirror/docs/demo/) + +## What makes this special? + +This demo runs **real MagicMirror² code** from the `master` branch in your browser. It's not a simplified recreation - it uses the actual: + +- Core architecture (`Module.register()`, `Class.extend()`) +- Original default modules (clock, weather, compliments, calendar, newsfeed) +- Real CSS styling +- Authentic module system and lifecycle + +**The only difference:** Server components are mocked with demo data instead of making real API calls. + +This means you're seeing the real MagicMirror² experience, just without needing a server or API keys! + +## Running locally + +```bash +# IMPORTANT: Server must run from the MagicMirror root directory! +cd /path/to/MagicMirror + +# With Python 3 +python -m http.server 8080 + +# Or with Node.js +npx http-server -p 8080 + +# Open in browser +open http://localhost:8080/docs/demo/ +``` + +**Why from root?** The demo uses real files with relative paths (`../../js/`, `../../modules/`) - the server must run from the repository root. + +## Structure + +``` +demo/ +├── index.html # Loads real modules +├── config.js # Demo configuration +├── css/ +│ └── demo.css # Demo banner styling only +└── js/ + ├── demo-mocks.js # Mocks for Log, Translator, Socket.io + ├── demo-loader.js # Overrides API calls with mock data + └── demo-main.js # Initializes MM like the original +``` + +## How it works + +1. **index.html** loads the real MagicMirror core files: + - `js/class.js` - Base class system + - `js/module.js` - Module architecture + - Original modules from `modules/default/` + +2. **demo-mocks.js** creates replacements for server components: + - `window.Log` for logging + - `window.Translator` for translations + - `window.MM` for module manager + - Mock data for weather, calendar, news + +3. **demo-loader.js** extends modules during registration: + - Intercepts `Module.register()` + - Overrides `start()` methods + - Replaces API calls with mock data + +4. **demo-main.js** starts the system: + - Reads `config.modules` + - Creates module instances + - Inserts DOM like the original + +## Customization + +### Change configuration + +Edit [config.js](config.js): + +```javascript +modules: [ + { + module: "clock", + position: "top_left", + config: { + /* ... */ + } + } + // More modules... +]; +``` + +### Change mock data + +Edit [js/demo-mocks.js](js/demo-mocks.js): + +```javascript +const mockWeatherData = { + current: { + temperature: 8 + // ... + } +}; +``` + +## Limitations + +As a pure browser demo: + +- ❌ No real API calls (CORS, API keys) +- ❌ No node_helper.js modules +- ❌ No third-party modules (but easy to add!) +- ❌ No Electron features + +## Adding new modules + +1. Add script tag in [index.html](index.html): + +```html + +``` + +2. Add module to config in [config.js](config.js) + +3. If API calls needed: Add mock function in [demo-loader.js](js/demo-loader.js) diff --git a/docs/demo/config.js b/docs/demo/config.js new file mode 100644 index 0000000000..d1d548d790 --- /dev/null +++ b/docs/demo/config.js @@ -0,0 +1,68 @@ +/* MagicMirror² Demo Configuration */ +let config = { + language: "en", + timeFormat: 12, + units: "metric", + basePath: "/", + + modules: [ + { + module: "clock", + position: "top_left", + config: { + displaySeconds: true, + showDate: true, + showWeek: true, + dateFormat: "dddd, MMMM Do, YYYY" + } + }, + { + module: "compliments", + position: "lower_third", + config: { + compliments: { + morning: ["Good morning!", "Enjoy your day!", "Nice to see you!"], + afternoon: ["Hello!", "Looking good!", "Beautiful afternoon!"], + evening: ["Good evening!", "Have a nice evening!", "Time to relax!"] + } + } + }, + { + module: "weather", + position: "top_right", + config: { + weatherProvider: "openweathermap", + type: "current", + location: "Berlin", + locationID: "2950159", + apiKey: "DEMO_KEY" + } + }, + { + module: "calendar", + position: "top_left", + header: "Calendar", + config: { + maximumEntries: 5, + calendars: [] + } + }, + { + module: "newsfeed", + position: "bottom_bar", + config: { + feeds: [ + { + title: "Demo News", + url: "about:blank" + } + ], + showSourceTitle: true, + showPublishDate: false + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { module.exports = config; } diff --git a/docs/demo/css/demo.css b/docs/demo/css/demo.css new file mode 100644 index 0000000000..3c150097fb --- /dev/null +++ b/docs/demo/css/demo.css @@ -0,0 +1,47 @@ +/* Demo Banner */ +#demo-banner { + position: fixed; + top: 0; + left: 0; + right: 0; + background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); + color: #fff; + padding: 8px 20px; + text-align: center; + z-index: 10000; + font-size: 14px; + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + border-bottom: 1px solid #333; +} + +#demo-banner a { + color: #4fc3f7; + text-decoration: none; +} + +#demo-banner a:hover { + text-decoration: underline; +} + +#demo-banner button { + background: none; + border: none; + color: #fff; + font-size: 20px; + cursor: pointer; + padding: 0 5px; + margin-left: 10px; + opacity: 0.7; +} + +#demo-banner button:hover { + opacity: 1; +} + +/* Adjust body margin for banner */ +body { + margin-top: calc(var(--gap-body-top) + 40px); +} diff --git a/docs/demo/index.html b/docs/demo/index.html new file mode 100644 index 0000000000..e31d392773 --- /dev/null +++ b/docs/demo/index.html @@ -0,0 +1,107 @@ + + + + + + MagicMirror² Demo + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 🪞 MagicMirror² Demo - GitHub + +
+ + +
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/demo/js/demo-loader.js b/docs/demo/js/demo-loader.js new file mode 100644 index 0000000000..c443b8488f --- /dev/null +++ b/docs/demo/js/demo-loader.js @@ -0,0 +1,263 @@ +/* MagicMirror² Demo - Module Loader + * Loads and initializes real MagicMirror modules for the demo mode + */ + +(function () { + "use strict"; + + // Extend Module class with demo-specific overrides + const originalModuleRegister = Module.register; + + Module.register = function (name, moduleDefinition) { + console.log("Registering module:", name); + + // Override specific methods for demo mode + const originalStart = moduleDefinition.start; + moduleDefinition.start = async function () { + console.log(`Starting module: ${name}`); + + // Call original start if exists + if (originalStart) { + await originalStart.call(this); + } + + // Module-specific demo setups AFTER starting (so weatherProvider exists) + if (name === "weather") { + setupWeatherDemo.call(this); + } else if (name === "calendar") { + setupCalendarDemo.call(this); + } else if (name === "newsfeed") { + setupNewsfeedDemo.call(this); + } + }; + + // Call original register + return originalModuleRegister.call(this, name, moduleDefinition); + }; + + // ============================================ + // Weather Demo Setup + // ============================================ + /** + * + */ + function setupWeatherDemo () { + const self = this; + const provider = this.weatherProvider; + + if (!provider) { + console.error("Weather provider not initialized"); + return; + } + + console.log("Weather: Setting up mock data"); + + // Override getDom to render without nunjucks template + this.getDom = function () { + const wrapper = document.createElement("div"); + + const current = this.weatherProvider?.currentWeather(); + if (!current) { + wrapper.innerHTML = "Lade Wetter..."; + return wrapper; + } + + // Temperature display + const tempWrapper = document.createElement("div"); + tempWrapper.className = "large light"; + + const icon = document.createElement("span"); + icon.className = `wi weathericon wi-${current.weatherType}`; + tempWrapper.appendChild(icon); + + const temp = document.createElement("span"); + temp.className = "bright"; + temp.innerHTML = ` ${current.temperature.toFixed(1)}°`; + tempWrapper.appendChild(temp); + + wrapper.appendChild(tempWrapper); + + // Additional info + if (!this.config.onlyTemp) { + const detailsWrapper = document.createElement("div"); + detailsWrapper.className = "normal medium"; + + // Wind + const wind = document.createElement("span"); + wind.innerHTML = ` ${current.windSpeed.toFixed(0)} m/s`; + detailsWrapper.appendChild(wind); + + // Humidity + if (current.humidity) { + const humidity = document.createElement("span"); + humidity.innerHTML = ` ${current.humidity}%`; + detailsWrapper.appendChild(humidity); + } + + wrapper.appendChild(detailsWrapper); + } + + return wrapper; + }; + + // Override fetch methods to use mock data + provider.fetchCurrentWeather = function () { + console.log("Weather: Using mock current weather data"); + + // Use the real WeatherObject class + const current = new WeatherObject(); + current.date = moment(); + current.temperature = mockData.weather.current.temperature; + current.feelsLikeTemp = mockData.weather.current.feelsLike; + current.weatherType = mockData.weather.current.weatherType; + current.humidity = mockData.weather.current.humidity; + current.windSpeed = mockData.weather.current.windSpeed; + current.windFromDirection = mockData.weather.current.windDirection; + current.sunrise = moment(mockData.weather.current.sunrise); + current.sunset = moment(mockData.weather.current.sunset); + + this.setCurrentWeather(current); + this.updateAvailable(); + }; + + provider.fetchWeatherForecast = function () { + console.log("Weather: Using mock forecast data"); + + const forecasts = mockData.weather.forecast.map((f) => { + const fc = new WeatherObject(); + fc.date = moment(f.date); + fc.minTemperature = f.tempMin; + fc.maxTemperature = f.tempMax; + fc.weatherType = f.weatherType; + fc.precipitationAmount = f.precipitation; + return fc; + }); + + this.setWeatherForecast(forecasts); + this.updateAvailable(); + }; + + provider.fetchWeatherHourly = function () { + console.log("Weather: Mock hourly data not implemented"); + }; + } + + // ============================================ + // Calendar Demo Setup + // ============================================ + /** + * + */ + function setupCalendarDemo () { + const self = this; + + // Mark as loaded + this.loaded = true; + + // Override addCalendar to prevent actual fetching + this.addCalendar = function () {}; + + // Simulate calendar events + setTimeout(() => { + const events = mockData.calendar.map((e) => ({ + title: e.title, + startDate: e.startDate, + endDate: e.endDate || e.startDate, + fullDayEvent: e.fullDayEvent || false, + location: "", + geo: null, + description: "", + today: e.startDate >= Date.now() && e.startDate < Date.now() + 86400000 + })); + + // Sort by start date + events.sort((a, b) => a.startDate - b.startDate); + + // Set events directly on the module in the expected format + self.calendarData = { + mock_calendar: { + events: events + } + }; + + // Broadcast calendar events + self.broadcastEvents(events); + + // Update display + self.updateDom(300); + }, 500); + } + + // ============================================ + // Newsfeed Demo Setup + // ============================================ + /** + * + */ + function setupNewsfeedDemo () { + const self = this; + let currentIndex = 0; + + // Override getDom to render without nunjucks template + this.getDom = function () { + const wrapper = document.createElement("div"); + wrapper.className = "newsfeed"; + + if (!this.newsItems || this.newsItems.length === 0) { + wrapper.innerHTML = "Loading news..."; + return wrapper; + } + + const item = this.newsItems[this.activeItem || 0]; + if (!item) { + return wrapper; + } + + const titleWrapper = document.createElement("div"); + titleWrapper.className = "newsfeed-title"; + + if (this.config.showSourceTitle && item.sourceTitle) { + const source = document.createElement("span"); + source.className = "newsfeed-source"; + source.innerHTML = `${item.sourceTitle}: `; + titleWrapper.appendChild(source); + } + + const title = document.createElement("span"); + title.innerHTML = item.title; + titleWrapper.appendChild(title); + + wrapper.appendChild(titleWrapper); + + if (this.config.showDescription && item.description) { + const desc = document.createElement("div"); + desc.className = "newsfeed-desc"; + desc.innerHTML = item.description; + wrapper.appendChild(desc); + } + + return wrapper; + }; + + // Override startFetch + this.startFetch = function () { + console.log("Newsfeed: Using mock data"); + }; + + // Simulate news items cycling + const updateNews = () => { + const item = mockData.news[currentIndex % mockData.news.length]; + self.newsItems = [item]; + self.activeItem = 0; + self.loaded = true; + self.updateDom(self.config.animationSpeed || 1000); + + currentIndex++; + setTimeout(updateNews, self.config.updateInterval || 10000); + }; + + setTimeout(updateNews, 500); + } + + console.log("Demo loader initialized"); +}()); diff --git a/docs/demo/js/demo-main.js b/docs/demo/js/demo-main.js new file mode 100644 index 0000000000..b91c423a26 --- /dev/null +++ b/docs/demo/js/demo-main.js @@ -0,0 +1,198 @@ +/* MagicMirror² Demo - Main Application + * Initializes the demo with real MagicMirror modules + */ + +(function () { + "use strict"; + + console.log("MagicMirror² Demo starting..."); + + // Position mapping (convert underscores to spaces for CSS classes) + const positionMap = { + top_bar: "top bar", + top_left: "top left", + top_center: "top center", + top_right: "top right", + upper_third: "upper third", + middle_center: "middle center", + lower_third: "lower third", + bottom_bar: "bottom bar", + bottom_left: "bottom left", + bottom_center: "bottom center", + bottom_right: "bottom right", + fullscreen_above: "fullscreen above", + fullscreen_below: "fullscreen below" + }; + + // Helper: Get container element for position + /** + * + * @param position + */ + function getContainer (position) { + const mappedPosition = positionMap[position] || position; + const classes = mappedPosition.replace(/_/g, " "); + const regions = document.querySelectorAll(".region"); + + for (const region of regions) { + if (region.className.includes(classes)) { + const container = region.querySelector(".container"); + if (container) return container; + } + } + + console.warn(`Container not found for position: ${position}`); + return null; + } + + // Helper: Create module DOM wrapper + /** + * + * @param module + */ + function createModuleWrapper (module) { + const wrapper = document.createElement("div"); + wrapper.id = module.identifier; + wrapper.className = `module ${module.name}`; + + if (module.data.classes) { + wrapper.className += ` ${module.data.classes}`; + } + + // Add header + const header = document.createElement("header"); + header.className = "module-header"; + wrapper.appendChild(header); + + // Add content + const content = document.createElement("div"); + content.className = "module-content"; + wrapper.appendChild(content); + + return wrapper; + } + + // Helper: Update module DOM + /** + * + * @param module + */ + async function updateModuleDom (module) { + const wrapper = document.getElementById(module.identifier); + if (!wrapper) return; + + const content = wrapper.querySelector(".module-content"); + const header = wrapper.querySelector(".module-header"); + + // Update header + if (typeof module.getHeader === "function") { + const headerText = module.getHeader(); + header.innerHTML = headerText || ""; + header.style.display = headerText ? "block" : "none"; + } + + // Update content + try { + const dom = await module.getDom(); + content.innerHTML = ""; + content.appendChild(dom); + } catch (error) { + console.error(`Error updating module ${module.name}:`, error); + } + } + + // Initialize modules + /** + * + */ + async function initModules () { + console.log("Initializing modules from config..."); + + const moduleInstances = []; + let moduleId = 0; + + for (const moduleConfig of config.modules) { + const moduleName = moduleConfig.module; + const moduleData = { + ...moduleConfig, + name: moduleName, + identifier: `module_${moduleId}_${moduleName}`, + hidden: false, + index: moduleId, + classes: moduleConfig.classes || `${moduleName}` + }; + + console.log(`Creating module: ${moduleName}`); + + // Check if module is registered + if (typeof Module === "undefined" || !Module.definitions || !Module.definitions[moduleName]) { + console.warn(`Module ${moduleName} not registered yet, skipping for now`); + continue; + } + + // Create module instance (Module.create already returns an instance, not a class!) + const instance = Module.create(moduleName); + if (!instance) { + console.error(`Failed to create module: ${moduleName}`); + continue; + } + + instance.setData(moduleData); + instance.setConfig(moduleConfig.config || {}); + + // Store instance + moduleInstances.push(instance); + MM.modules.push(instance); + + // Create and insert DOM + const container = getContainer(moduleConfig.position); + if (container) { + const wrapper = createModuleWrapper(instance); + container.appendChild(wrapper); + + // Override updateDom for this instance + instance.updateDom = function (speed) { + updateModuleDom(instance); + }; + } + + moduleId++; + } + + // Start all modules + console.log("Starting modules..."); + for (const module of moduleInstances) { + try { + if (typeof module.start === "function") { + await module.start(); + } + await updateModuleDom(module); + } catch (error) { + console.error(`Error starting module ${module.name}:`, error); + } + } + + console.log("MagicMirror² Demo ready! ✨"); + } + + // Start when DOM is ready + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => { + // Wait a bit for all modules to register + setTimeout(initModules, 100); + }); + } else { + setTimeout(initModules, 100); + } + + // Keyboard shortcuts + document.addEventListener("keydown", (e) => { + if (e.key === "Escape") { + const banner = document.getElementById("demo-banner"); + if (banner) { + banner.style.display = banner.style.display === "none" ? "flex" : "none"; + } + } + }); + +}()); diff --git a/docs/demo/js/demo-mocks.js b/docs/demo/js/demo-mocks.js new file mode 100644 index 0000000000..def0cfc927 --- /dev/null +++ b/docs/demo/js/demo-mocks.js @@ -0,0 +1,284 @@ +/* MagicMirror² Demo - Mock Implementations + * Simulates server components and APIs for the browser demo + */ + +// ============================================ +// Global Logging Mock +// ============================================ +window.Log = { + info (...args) { console.info("[INFO]", ...args); }, + log (...args) { console.log("[LOG]", ...args); }, + error (...args) { console.error("[ERROR]", ...args); }, + warn (...args) { console.warn("[WARN]", ...args); }, + debug (...args) { console.debug("[DEBUG]", ...args); } +}; + +// ============================================ +// Translator Mock +// ============================================ +window.Translator = { + translations: { + LOADING: "Loading …", + DAYBEFOREYESTERDAY: "Day Before Yesterday", + YESTERDAY: "Yesterday", + TODAY: "Today", + TOMORROW: "Tomorrow", + DAYAFTERTOMORROW: "Day After Tomorrow", + RUNNING: "ends in", + EMPTY: "No upcoming events.", + WEEK: "Week {weekNumber}", + WEEK_SHORT: "W{weekNumber}" + }, + coreTranslations: {}, + + loadCoreTranslations (lang) { + return Promise.resolve(); + }, + + translate (module, key, variables) { + // Handle being called with just (key, variables) or (module, key, variables) + if (typeof module === "string") { + variables = key; + key = module; + } + + let translation = this.translations[key] || key; + + // Replace variables in translation + if (variables && typeof variables === "object") { + Object.keys(variables).forEach((varKey) => { + translation = translation.replace(`{${varKey}}`, variables[varKey]); + }); + } + + return translation; + }, + + load (module, file, force, callback) { + if (callback) callback(); + return Promise.resolve(); + } +}; + +// ============================================ +// Nunjucks Mock +// ============================================ +if (!window.nunjucks) { + window.nunjucks = {}; +} + +// Mock WebLoader for module.js compatibility +window.nunjucks.WebLoader = function (baseURL, opts) { + this.baseURL = baseURL; + this.async = opts && opts.async; + + this.getSource = function (name, callback) { + // Return empty template for demo + if (callback) { + callback(null, { src: "", path: name, noCache: false }); + } + return { src: "", path: name, noCache: false }; + }; +}; + +// Mock Environment +window.nunjucks.Environment = function (loader, opts) { + this.loader = loader; + this.opts = opts || {}; + this.filters = {}; + + this.addFilter = function (name, fn) { + this.filters[name] = fn; + return this; + }; + + this.renderString = function (template, data, callback) { + const result = template; // Simple passthrough for demo + if (callback) { + callback(null, result); + return; + } + return result; + }; + + this.render = function (name, data, callback) { + const result = `
Template: ${name}
`; // Mock render + if (callback) { + callback(null, result); + return; + } + return result; + }; +}; + +// Mock runtime +window.nunjucks.runtime = { + markSafe (val) { + return val; + } +}; + +// Mock configure +window.nunjucks.configure = function (baseURL, opts) { + return new window.nunjucks.Environment( + new window.nunjucks.WebLoader(baseURL, opts), + opts + ); +}; + +// ============================================ +// Socket.IO Mock +// ============================================ +window.io = function () { + return { + on (event, callback) { + console.log("Socket event registered:", event); + }, + emit (event, data) { + console.log("Socket emit:", event, data); + } + }; +}; + +// ============================================ +// MMSocket Mock (for module.js socket() method) +// ============================================ +window.MMSocket = function (moduleName) { + this.moduleName = moduleName; + this.notificationCallback = null; + + this.setNotificationCallback = function (callback) { + this.notificationCallback = callback; + }; + + this.sendNotification = function (notification, payload) { + console.log(`Socket notification from ${moduleName}:`, notification, payload); + // In demo mode, socket notifications are ignored + }; + + return this; +}; + +// ============================================ +// Module Manager Mock +// ============================================ +window.MM = { + modules: [], + + getModules () { + return this.modules; + }, + + sendNotification (notification, payload, sender) { + console.log("Notification:", notification, payload); + // Broadcast to all modules + this.modules.forEach((module) => { + if (module !== sender && typeof module.notificationReceived === "function") { + module.notificationReceived(notification, payload, sender); + } + }); + } +}; + +// ============================================ +// Mock Weather Data Provider +// ============================================ +const mockWeatherData = { + current: { + temperature: 8, + feelsLike: 5, + weatherType: "cloudy", + humidity: 75, + windSpeed: 15, + windDirection: 230, + sunrise: new Date(Date.now() - 3 * 60 * 60 * 1000), + sunset: new Date(Date.now() + 5 * 60 * 60 * 1000) + }, + forecast: [ + { date: Date.now() + 86400000, tempMin: 4, tempMax: 9, weatherType: "rain", precipitation: 5 }, + { date: Date.now() + 2 * 86400000, tempMin: 2, tempMax: 7, weatherType: "rain", precipitation: 8 }, + { date: Date.now() + 3 * 86400000, tempMin: 3, tempMax: 11, weatherType: "cloudy", precipitation: 2 }, + { date: Date.now() + 4 * 86400000, tempMin: 5, tempMax: 10, weatherType: "cloudy", precipitation: 0 }, + { date: Date.now() + 5 * 86400000, tempMin: 1, tempMax: 6, weatherType: "snow", precipitation: 10 } + ] +}; + +// ============================================ +// Mock Calendar Events +// ============================================ +const mockCalendarEvents = [ + { + title: "Team Meeting", + startDate: Date.now() + 2 * 60 * 60 * 1000, + endDate: Date.now() + 3 * 60 * 60 * 1000, + fullDayEvent: false + }, + { + title: "Dentist Appointment", + startDate: Date.now() + 5 * 60 * 60 * 1000, + endDate: Date.now() + 6 * 60 * 60 * 1000, + fullDayEvent: false + }, + { + title: "Sarah's Birthday", + startDate: Date.now() + 86400000, + fullDayEvent: true + }, + { + title: "Project Deadline", + startDate: Date.now() + 2 * 86400000, + endDate: Date.now() + 2 * 86400000 + 60 * 60 * 1000, + fullDayEvent: false + }, + { + title: "Yoga Class", + startDate: Date.now() + 3 * 86400000 + 18 * 60 * 60 * 1000, + endDate: Date.now() + 3 * 86400000 + 19 * 60 * 60 * 1000, + fullDayEvent: false + } +]; + +// ============================================ +// Mock News Feed +// ============================================ +const mockNewsItems = [ + { + title: "New Technologies Revolutionize Daily Life", + description: "Innovative solutions are changing our daily habits.", + url: "#", + pubdate: Date.now() - 2 * 60 * 60 * 1000 + }, + { + title: "Weather Forecast: Cold Wave Expected", + description: "Meteorologists warn of dropping temperatures.", + url: "#", + pubdate: Date.now() - 4 * 60 * 60 * 1000 + }, + { + title: "Local Events This Weekend", + description: "An overview of events in your city.", + url: "#", + pubdate: Date.now() - 6 * 60 * 60 * 1000 + }, + { + title: "Scientists Make Groundbreaking Discovery", + description: "New findings could change everything.", + url: "#", + pubdate: Date.now() - 8 * 60 * 60 * 1000 + }, + { + title: "Sports: Exciting Games This Weekend", + description: "Results from the most important matches.", + url: "#", + pubdate: Date.now() - 10 * 60 * 60 * 1000 + } +]; + +// Export for use in modules +window.mockData = { + weather: mockWeatherData, + calendar: mockCalendarEvents, + news: mockNewsItems +}; + +console.log("Demo mocks loaded successfully"); diff --git a/eslint.config.mjs b/eslint.config.mjs index 897777943a..2a929d9617 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,7 +9,7 @@ import stylistic from "@stylistic/eslint-plugin"; import vitest from "eslint-plugin-vitest"; export default defineConfig([ - globalIgnores(["config/**", "modules/**/*", "!modules/default/**", "js/positions.js"]), + globalIgnores(["config/**", "modules/**/*", "!modules/default/**", "js/positions.js", "docs/demo/**"]), { files: ["**/*.js"], languageOptions: {