Compare commits
11 Commits
v-0.1.0
...
feat/preac
| Author | SHA1 | Date | |
|---|---|---|---|
| ff6670c98c | |||
| d805531369 | |||
| 8cc17a5a17 | |||
| d4c1884471 | |||
| 377be6e8f6 | |||
| c60cb24dd4 | |||
| 2f3949c2e3 | |||
| 9b818b05f9 | |||
| 008d2321e5 | |||
| 3aff7ffed6 | |||
| 9e80818fc5 |
@@ -4,8 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -18,29 +16,3 @@ jobs:
|
|||||||
run: npm install
|
run: npm install
|
||||||
- name: Run build
|
- name: Run build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
release:
|
|
||||||
if: gitea.event_name == 'tag'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Setup zip
|
|
||||||
run: sudo apt-get install zip -y
|
|
||||||
- name: Install JS dependencies
|
|
||||||
run: npm install
|
|
||||||
- name: Run build
|
|
||||||
run: npm run build
|
|
||||||
- name: Prepare release
|
|
||||||
run: |
|
|
||||||
bash scripts/prepare_release.sh
|
|
||||||
mv dist vision-start/
|
|
||||||
mv manifest.json vision-start/
|
|
||||||
- name: Create zip archive
|
|
||||||
run: zip -r vision-start-${{ gitea.ref_name }}.zip vision-start
|
|
||||||
- name: Release zip
|
|
||||||
uses: akkuman/gitea-release-action@v1
|
|
||||||
with:
|
|
||||||
name: ${{ gitea.ref_name }}
|
|
||||||
tag_name: ${{ gitea.ref_name }}
|
|
||||||
files: vision-start-${{ gitea.ref_name }}.zip
|
|
||||||
90
.gitea/workflows/release.yaml
Normal file
90
.gitea/workflows/release.yaml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
name: Build and Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
zip-file: vision-start-${{ gitea.ref_name }}.zip
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup required tools
|
||||||
|
run: sudo apt-get install zip jq curl -y
|
||||||
|
- name: Install JS dependencies
|
||||||
|
run: npm install
|
||||||
|
- name: Run build
|
||||||
|
run: npm run build
|
||||||
|
- name: Prepare release
|
||||||
|
run: |
|
||||||
|
bash scripts/prepare_release.sh
|
||||||
|
mv dist vision-start/
|
||||||
|
mv manifest.json vision-start/
|
||||||
|
- name: Create zip archive
|
||||||
|
run: zip -r vision-start-${{ gitea.ref_name }}.zip vision-start
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: release-zip
|
||||||
|
path: vision-start-${{ gitea.ref_name }}.zip
|
||||||
|
|
||||||
|
virus-total-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
outputs:
|
||||||
|
analysis-url: ${{ steps.vt-check.outputs.analysis-url }}
|
||||||
|
detection-ratio: ${{ steps.vt-check.outputs.detection-ratio }}
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup required tools
|
||||||
|
run: sudo apt-get install jq curl -y
|
||||||
|
- name: Download artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: release-zip
|
||||||
|
- name: Run VirusTotal check
|
||||||
|
id: vt-check
|
||||||
|
env:
|
||||||
|
virustotal_apikey: ${{ secrets.VIRUSTOTAL_APIKEY }}
|
||||||
|
VIRUS_TOTAL_FILE: vision-start-${{ gitea.ref_name }}.zip
|
||||||
|
run: |
|
||||||
|
# Run the VirusTotal check script and capture output
|
||||||
|
bash scripts/check_virustotal.sh > vt_output.txt 2>&1
|
||||||
|
|
||||||
|
# Extract analysis URL and detection ratio from output
|
||||||
|
ANALYSIS_URL=$(grep "Analysis URL:" vt_output.txt | cut -d' ' -f3- || echo "Not available")
|
||||||
|
DETECTION_RATIO=$(grep "Detection ratio:" vt_output.txt | cut -d' ' -f3- || echo "Not available")
|
||||||
|
|
||||||
|
# Set outputs for next job
|
||||||
|
echo "analysis-url=$ANALYSIS_URL" >> $GITEA_OUTPUT
|
||||||
|
echo "detection-ratio=$DETECTION_RATIO" >> $GITEA_OUTPUT
|
||||||
|
|
||||||
|
# Display the full output
|
||||||
|
cat vt_output.txt
|
||||||
|
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build, virus-total-check]
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Download artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: release-zip
|
||||||
|
- name: Release zip
|
||||||
|
uses: akkuman/gitea-release-action@v1
|
||||||
|
with:
|
||||||
|
body: |
|
||||||
|
This is the release for version ${{ gitea.ref_name }}.
|
||||||
|
|
||||||
|
**Virus Total Analysis URL:** ${{ needs.virus-total-check.outputs.analysis-url }}
|
||||||
|
**Virus Total Detection Ratio:** ${{ needs.virus-total-check.outputs.detection-ratio }}
|
||||||
|
name: ${{ gitea.ref_name }}
|
||||||
|
tag_name: ${{ gitea.ref_name }}
|
||||||
|
files: vision-start-${{ gitea.ref_name }}.zip
|
||||||
0
.gitignore
vendored
Executable file → Normal file
0
.gitignore
vendored
Executable file → Normal file
106
App.tsx
Executable file → Normal file
106
App.tsx
Executable file → Normal file
@@ -1,27 +1,24 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import ConfigurationModal from './components/ConfigurationModal';
|
import ConfigurationModal from './components/ConfigurationModal';
|
||||||
import ServerWidget from './components/ServerWidget';
|
import ServerWidget from './components/ServerWidget';
|
||||||
import { DEFAULT_CATEGORIES } from './constants';
|
import { DEFAULT_CATEGORIES } from './constants';
|
||||||
import { Category, Website, Wallpaper, Config } from './types';
|
import { Category, Website, Config } from './types';
|
||||||
import WebsiteEditModal from './components/WebsiteEditModal';
|
import WebsiteEditModal from './components/WebsiteEditModal';
|
||||||
import CategoryEditModal from './components/CategoryEditModal';
|
import CategoryEditModal from './components/CategoryEditModal';
|
||||||
import Header from './components/layout/Header';
|
import Header from './components/layout/Header';
|
||||||
import EditButton from './components/layout/EditButton';
|
import EditButton from './components/layout/EditButton';
|
||||||
import ConfigurationButton from './components/layout/ConfigurationButton';
|
import ConfigurationButton from './components/layout/ConfigurationButton';
|
||||||
import CategoryGroup from './components/layout/CategoryGroup';
|
import CategoryGroup from './components/layout/CategoryGroup';
|
||||||
|
import Wallpaper from './components/Wallpaper';
|
||||||
import { baseWallpapers } from './components/utils/baseWallpapers';
|
|
||||||
|
|
||||||
const defaultConfig: Config = {
|
const defaultConfig: Config = {
|
||||||
title: 'Vision Start',
|
title: 'Vision Start',
|
||||||
subtitle: 'Your personal portal to the web.',
|
currentWallpapers: ['Abstract'],
|
||||||
backgroundUrls: ['https://i.imgur.com/C6ynAtX.jpeg'],
|
|
||||||
wallpaperFrequency: '1d',
|
wallpaperFrequency: '1d',
|
||||||
wallpaperBlur: 0,
|
wallpaperBlur: 0,
|
||||||
wallpaperBrightness: 100,
|
wallpaperBrightness: 100,
|
||||||
wallpaperOpacity: 100,
|
wallpaperOpacity: 100,
|
||||||
titleSize: 'medium',
|
titleSize: 'medium',
|
||||||
subtitleSize: 'medium',
|
|
||||||
alignment: 'middle',
|
alignment: 'middle',
|
||||||
horizontalAlignment: 'middle',
|
horizontalAlignment: 'middle',
|
||||||
clock: {
|
clock: {
|
||||||
@@ -60,9 +57,6 @@ const App: React.FC = () => {
|
|||||||
const storedConfig = localStorage.getItem('config');
|
const storedConfig = localStorage.getItem('config');
|
||||||
if (storedConfig) {
|
if (storedConfig) {
|
||||||
const parsedConfig = JSON.parse(storedConfig);
|
const parsedConfig = JSON.parse(storedConfig);
|
||||||
if (!parsedConfig.backgroundUrls) {
|
|
||||||
parsedConfig.backgroundUrls = [parsedConfig.backgroundUrl].filter(Boolean);
|
|
||||||
}
|
|
||||||
return { ...defaultConfig, ...parsedConfig };
|
return { ...defaultConfig, ...parsedConfig };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -70,62 +64,20 @@ const App: React.FC = () => {
|
|||||||
}
|
}
|
||||||
return { ...defaultConfig };
|
return { ...defaultConfig };
|
||||||
});
|
});
|
||||||
const [userWallpapers, setUserWallpapers] = useState<Wallpaper[]>(() => {
|
|
||||||
const storedUserWallpapers = localStorage.getItem('userWallpapers');
|
|
||||||
return storedUserWallpapers ? JSON.parse(storedUserWallpapers) : [];
|
|
||||||
});
|
|
||||||
const [currentWallpaper, setCurrentWallpaper] = useState<string>('');
|
|
||||||
|
|
||||||
const allWallpapers = [...baseWallpapers, ...userWallpapers];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getFrequencyInMs = (frequency: string) => {
|
|
||||||
const value = parseInt(frequency.slice(0, -1));
|
|
||||||
const unit = frequency.slice(-1);
|
|
||||||
if (unit === 'h') return value * 60 * 60 * 1000;
|
|
||||||
if (unit === 'd') return value * 24 * 60 * 60 * 1000;
|
|
||||||
return 24 * 60 * 60 * 1000; // Default to 1 day
|
|
||||||
};
|
|
||||||
|
|
||||||
const wallpaperState = JSON.parse(localStorage.getItem('wallpaperState') || '{}');
|
|
||||||
const lastChanged = wallpaperState.lastChanged ? new Date(wallpaperState.lastChanged).getTime() : 0;
|
|
||||||
const frequency = getFrequencyInMs(config.wallpaperFrequency);
|
|
||||||
|
|
||||||
const updateWallpaper = () => {
|
|
||||||
const availableWallpapers = allWallpapers.filter(w => config.backgroundUrls.includes(w.url || w.base64));
|
|
||||||
if (availableWallpapers.length > 0) {
|
|
||||||
const currentWallpaperFromState = allWallpapers.find(w => w.name === wallpaperState.current);
|
|
||||||
const currentIndex = currentWallpaperFromState ? availableWallpapers.findIndex(w => w.name === currentWallpaperFromState.name) : -1;
|
|
||||||
const nextIndex = (currentIndex + 1) % availableWallpapers.length;
|
|
||||||
const newWallpaper = availableWallpapers[nextIndex];
|
|
||||||
const newWallpaperUrl = newWallpaper.url || newWallpaper.base64;
|
|
||||||
setCurrentWallpaper(newWallpaperUrl || '');
|
|
||||||
localStorage.setItem('wallpaperState', JSON.stringify({ current: newWallpaper.name, lastChanged: new Date().toISOString() }));
|
|
||||||
} else {
|
|
||||||
setCurrentWallpaper('');
|
|
||||||
localStorage.removeItem('wallpaperState');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentWallpaperDetails = allWallpapers.find(w => w.name === wallpaperState.current);
|
|
||||||
const isCurrentWallpaperValid = currentWallpaperDetails && config.backgroundUrls.includes(currentWallpaperDetails.url || currentWallpaperDetails.base64 || '');
|
|
||||||
|
|
||||||
if (!isCurrentWallpaperValid || Date.now() - lastChanged > frequency) {
|
|
||||||
updateWallpaper();
|
|
||||||
} else if (currentWallpaperDetails) {
|
|
||||||
setCurrentWallpaper(currentWallpaperDetails.url || currentWallpaperDetails.base64 || '');
|
|
||||||
} else {
|
|
||||||
// Fallback for when there's no valid wallpaper state
|
|
||||||
updateWallpaper();
|
|
||||||
}
|
|
||||||
}, [config.backgroundUrls, config.wallpaperFrequency, allWallpapers]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem('categories', JSON.stringify(categories));
|
|
||||||
localStorage.setItem('config', JSON.stringify(config));
|
localStorage.setItem('config', JSON.stringify(config));
|
||||||
}, [categories, config]);
|
}, [config]);
|
||||||
|
|
||||||
const handleSaveConfig = (newConfig: any) => {
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem('categories', JSON.stringify(categories));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving categories to localStorage', error);
|
||||||
|
}
|
||||||
|
}, [categories]);
|
||||||
|
|
||||||
|
const handleSaveConfig = (newConfig: Config) => {
|
||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
setIsConfigModalOpen(false);
|
setIsConfigModalOpen(false);
|
||||||
};
|
};
|
||||||
@@ -136,10 +88,11 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
const handleSaveWebsite = (website: Partial<Website>) => {
|
const handleSaveWebsite = (website: Partial<Website>) => {
|
||||||
if (editingWebsite) {
|
if (editingWebsite) {
|
||||||
|
const idToUpdate = website.id ?? editingWebsite.id;
|
||||||
const newCategories = categories.map(category => ({
|
const newCategories = categories.map(category => ({
|
||||||
...category,
|
...category,
|
||||||
websites: category.websites.map(w =>
|
websites: category.websites.map(w =>
|
||||||
w.id === website.id ? { ...w, ...website } : w
|
w.id === idToUpdate ? { ...w, ...website, id: idToUpdate } : w
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
setCategories(newCategories);
|
setCategories(newCategories);
|
||||||
@@ -201,7 +154,7 @@ const App: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMoveWebsite = (website: Website, direction: 'left' | 'right') => {
|
const handleMoveWebsite = (website: Website, direction: 'left' | 'right') => {
|
||||||
const categoryIndex = categories.findIndex(c => c.id === website.categoryId);
|
const categoryIndex = categories.findIndex(cat => cat.websites.some(w => w.id === website.id));
|
||||||
if (categoryIndex === -1) return;
|
if (categoryIndex === -1) return;
|
||||||
|
|
||||||
const category = categories[categoryIndex];
|
const category = categories[categoryIndex];
|
||||||
@@ -259,14 +212,13 @@ const App: React.FC = () => {
|
|||||||
<main
|
<main
|
||||||
className={`min-h-screen w-full flex flex-col items-center ${getAlignmentClass(config.alignment)} p-4`}
|
className={`min-h-screen w-full flex flex-col items-center ${getAlignmentClass(config.alignment)} p-4`}
|
||||||
>
|
>
|
||||||
<div
|
<Wallpaper
|
||||||
className="fixed inset-0 w-full h-full bg-cover bg-center bg-fixed -z-10"
|
wallpaperNames={config.currentWallpapers}
|
||||||
style={{
|
blur={config.wallpaperBlur}
|
||||||
backgroundImage: `url('${currentWallpaper}')`,
|
brightness={config.wallpaperBrightness}
|
||||||
filter: `blur(${config.wallpaperBlur}px) brightness(${config.wallpaperBrightness}%)`,
|
opacity={config.wallpaperOpacity}
|
||||||
opacity: `${config.wallpaperOpacity}%`,
|
wallpaperFrequency={config.wallpaperFrequency}
|
||||||
}}
|
/>
|
||||||
></div>
|
|
||||||
<EditButton isEditing={isEditing} onClick={() => setIsEditing(!isEditing)} />
|
<EditButton isEditing={isEditing} onClick={() => setIsEditing(!isEditing)} />
|
||||||
<ConfigurationButton onClick={() => setIsConfigModalOpen(true)} />
|
<ConfigurationButton onClick={() => setIsConfigModalOpen(true)} />
|
||||||
|
|
||||||
@@ -288,13 +240,13 @@ const App: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<div className={`flex justify-center transition-all duration-300 ease-in-out transform ${isEditing ? 'scale-100 opacity-100' : 'scale-0 opacity-0'}`}>
|
<div className={`flex justify-center transition-all duration-200 ease-ios transform ${isEditing ? 'scale-100 opacity-100' : 'scale-0 opacity-0'}`}>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditingCategory(null);
|
setEditingCategory(null);
|
||||||
setIsCategoryModalOpen(true);
|
setIsCategoryModalOpen(true);
|
||||||
}}
|
}}
|
||||||
className="text-white/50 hover:text-white transition-colors"
|
className="text-white/50 hover:text-white active:scale-90 transition-all duration-150 ease-ios"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" className="bi bi-plus-circle" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" className="bi bi-plus-circle" viewBox="0 0 16 16">
|
||||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
||||||
@@ -305,11 +257,7 @@ const App: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{config.serverWidget.enabled && (
|
{config.serverWidget.enabled && <ServerWidget config={config} />}
|
||||||
<div className="absolute bottom-4 right-4">
|
|
||||||
<ServerWidget config={config} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(editingWebsite || addingWebsite) && (
|
{(editingWebsite || addingWebsite) && (
|
||||||
<WebsiteEditModal
|
<WebsiteEditModal
|
||||||
|
|||||||
34
README.md
Executable file → Normal file
34
README.md
Executable file → Normal file
@@ -7,16 +7,16 @@
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
## Backgrounds
|
## Installing
|
||||||
|
|
||||||
It comes with a selection of some nice pre-defined backgrounds. You can also upload up to one image to it.
|
Vision Start is not yet available on Chrome Web Store, but it can be installed manually:
|
||||||
|
1. Go to https://git.ivanch.me/ivanch/vision-start/releases/latest
|
||||||
* **Abstract**
|
2. Download the latest `vision-start-[version].zip` file
|
||||||
* **Abstract Red**
|
3. Extract the zip file, you will have a `vision-start` folder
|
||||||
* **Beach**
|
4. Go to chrome://extensions/
|
||||||
* **Dark**
|
5. Enable "Developer mode" in the top right corner
|
||||||
* **Mountain**
|
6. Click on "Load unpacked" and select the `vision-start` folder you extracted in step 3
|
||||||
* **Waves**
|
7. The extension should now be installed! Just open a new tab to see it in action.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -27,6 +27,12 @@ It comes with a selection of some nice pre-defined backgrounds. You can also upl
|
|||||||
* **Icon Library:** It uses the [Dashboard Icon library](https://dashboardicons.com/) for a better look and feel. It also supports auto-fetch for some websites.
|
* **Icon Library:** It uses the [Dashboard Icon library](https://dashboardicons.com/) for a better look and feel. It also supports auto-fetch for some websites.
|
||||||
* **Future**: a long to do list :(
|
* **Future**: a long to do list :(
|
||||||
|
|
||||||
|
## Backgrounds
|
||||||
|
|
||||||
|
It comes with a selection of some nice pre-defined backgrounds: **Abstract**, **Abstract Red**, **Beach**, **Dark**, **Mountain**, **Waves**.
|
||||||
|
|
||||||
|
You can also upload your own images on it (or fetch it from the web).
|
||||||
|
|
||||||
## Running Locally
|
## Running Locally
|
||||||
|
|
||||||
**Prerequisites:** Node.js
|
**Prerequisites:** Node.js
|
||||||
@@ -49,10 +55,10 @@ npm run dev
|
|||||||
|
|
||||||
* [x] Multiple Wallpapers
|
* [x] Multiple Wallpapers
|
||||||
* [x] Remake icons
|
* [x] Remake icons
|
||||||
* [] Increase offline compatibility (might not be possible)
|
* [/] Increase offline compatibility (might not be possible)
|
||||||
* Use chrome.storage.local for user wallpapers -- this one is
|
- [x] Use chrome.storage.local for user wallpapers -- this one is
|
||||||
* Use chrome.storage.local for some logos -- a bit hard
|
- [ ] Use chrome.storage.local for some logos -- a bit hard
|
||||||
* Some logos have CORS enabled, we can add `"<all_urls>"` to the manifest.json file and cache them on storage local
|
- Some logos have CORS enabled, we can add `"<all_urls>"` to the manifest.json file and cache them on storage local
|
||||||
* Dynamic Weather Widget
|
* Dynamic Weather Widget
|
||||||
* A box with information about the current weather, with manual entry on the location
|
* A box with information about the current weather, with manual entry on the location
|
||||||
* Display current temperature, weather condition (e.g., "Sunny," "Cloudy"), and a corresponding icon
|
* Display current temperature, weather condition (e.g., "Sunny," "Cloudy"), and a corresponding icon
|
||||||
@@ -76,7 +82,7 @@ npm run dev
|
|||||||
* Dynamic Wallpaper-Based Theming
|
* Dynamic Wallpaper-Based Theming
|
||||||
* Automatically adapt the UI's accent color to match the current wallpaper
|
* Automatically adapt the UI's accent color to match the current wallpaper
|
||||||
* Minimal feel toggle
|
* Minimal feel toggle
|
||||||
* Disable title & subtitle and search widget
|
* Disable title and search widget
|
||||||
* Tiles become small stylish lines
|
* Tiles become small stylish lines
|
||||||
|
|
||||||
From a technical side:
|
From a technical side:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Category } from '../types';
|
import { Category } from '../types';
|
||||||
|
|
||||||
interface CategoryEditModalProps {
|
interface CategoryEditModalProps {
|
||||||
@@ -12,10 +12,6 @@ interface CategoryEditModalProps {
|
|||||||
const CategoryEditModal: React.FC<CategoryEditModalProps> = ({ category, edit, onClose, onSave, onDelete }) => {
|
const CategoryEditModal: React.FC<CategoryEditModalProps> = ({ category, edit, onClose, onSave, onDelete }) => {
|
||||||
const [name, setName] = useState(category ? category.name : '');
|
const [name, setName] = useState(category ? category.name : '');
|
||||||
|
|
||||||
const handleSave = () => {
|
|
||||||
onSave(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
onClose();
|
onClose();
|
||||||
@@ -44,7 +40,7 @@ const CategoryEditModal: React.FC<CategoryEditModalProps> = ({ category, edit, o
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-4">
|
<div className="flex justify-end gap-4">
|
||||||
<button onClick={handleSave} className="bg-green-500 hover:bg-green-400 text-white font-bold py-2 px-6 rounded-lg">
|
<button onClick={() => onSave(name)} className="bg-green-500 hover:bg-green-400 text-white font-bold py-2 px-6 rounded-lg">
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
<button onClick={onClose} className="bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-6 rounded-lg">
|
<button onClick={onClose} className="bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-6 rounded-lg">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Server, Wallpaper } from '../types';
|
|||||||
|
|
||||||
import Dropdown from './Dropdown';
|
import Dropdown from './Dropdown';
|
||||||
import { baseWallpapers } from './utils/baseWallpapers';
|
import { baseWallpapers } from './utils/baseWallpapers';
|
||||||
|
import { addWallpaperToChromeStorageLocal, removeWallpaperFromChromeStorageLocal, checkChromeStorageLocalAvailable } from './utils/StorageLocalManager';
|
||||||
|
|
||||||
interface ConfigurationModalProps {
|
interface ConfigurationModalProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -17,7 +18,6 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
const [config, setConfig] = useState({
|
const [config, setConfig] = useState({
|
||||||
...currentConfig,
|
...currentConfig,
|
||||||
titleSize: currentConfig.titleSize || 'medium',
|
titleSize: currentConfig.titleSize || 'medium',
|
||||||
subtitleSize: currentConfig.subtitleSize || 'medium',
|
|
||||||
alignment: currentConfig.alignment || 'middle',
|
alignment: currentConfig.alignment || 'middle',
|
||||||
tileSize: currentConfig.tileSize || 'medium',
|
tileSize: currentConfig.tileSize || 'medium',
|
||||||
horizontalAlignment: currentConfig.horizontalAlignment || 'middle',
|
horizontalAlignment: currentConfig.horizontalAlignment || 'middle',
|
||||||
@@ -37,7 +37,9 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
format: 'h:mm A',
|
format: 'h:mm A',
|
||||||
...currentConfig.clock,
|
...currentConfig.clock,
|
||||||
},
|
},
|
||||||
backgroundUrls: currentConfig.backgroundUrls || [],
|
currentWallpapers: Array.isArray(currentConfig.currentWallpapers)
|
||||||
|
? currentConfig.currentWallpapers.filter((name: string) => typeof name === 'string')
|
||||||
|
: [],
|
||||||
wallpaperFrequency: currentConfig.wallpaperFrequency || '1d',
|
wallpaperFrequency: currentConfig.wallpaperFrequency || '1d',
|
||||||
});
|
});
|
||||||
const [activeTab, setActiveTab] = useState('general');
|
const [activeTab, setActiveTab] = useState('general');
|
||||||
@@ -46,12 +48,14 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
const [newWallpaperName, setNewWallpaperName] = useState('');
|
const [newWallpaperName, setNewWallpaperName] = useState('');
|
||||||
const [newWallpaperUrl, setNewWallpaperUrl] = useState('');
|
const [newWallpaperUrl, setNewWallpaperUrl] = useState('');
|
||||||
const [userWallpapers, setUserWallpapers] = useState<Wallpaper[]>([]);
|
const [userWallpapers, setUserWallpapers] = useState<Wallpaper[]>([]);
|
||||||
|
const [chromeStorageAvailable, setChromeStorageAvailable] = useState(false);
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const isSaving = useRef(false);
|
const isSaving = useRef(false);
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setChromeStorageAvailable(checkChromeStorageLocalAvailable());
|
||||||
const storedUserWallpapers = localStorage.getItem('userWallpapers');
|
const storedUserWallpapers = localStorage.getItem('userWallpapers');
|
||||||
if (storedUserWallpapers) {
|
if (storedUserWallpapers) {
|
||||||
setUserWallpapers(JSON.parse(storedUserWallpapers));
|
setUserWallpapers(JSON.parse(storedUserWallpapers));
|
||||||
@@ -59,7 +63,6 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// A small timeout to allow the component to mount before starting the transition
|
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setIsVisible(true);
|
setIsVisible(true);
|
||||||
}, 10);
|
}, 10);
|
||||||
@@ -69,7 +72,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (!isSaving.current) {
|
if (!isSaving.current) {
|
||||||
onWallpaperChange({ backgroundUrls: currentConfig.backgroundUrls });
|
onWallpaperChange({ currentWallpapers: currentConfig.currentWallpapers });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@@ -78,12 +81,15 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
setIsVisible(false);
|
setIsVisible(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onClose();
|
onClose();
|
||||||
}, 300); // This duration should match the transition duration
|
}, 250);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement> | { target: { name: string; value: string | string[] } }) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement> | { target: { name: string; value: string | string[] } }) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
if (name.startsWith('serverWidget.')) {
|
if (name === 'currentWallpapers') {
|
||||||
|
const wallpaperNames = Array.isArray(value) ? value : [value];
|
||||||
|
setConfig({ ...config, currentWallpapers: wallpaperNames });
|
||||||
|
} else if (name.startsWith('serverWidget.')) {
|
||||||
const field = name.split('.')[1];
|
const field = name.split('.')[1];
|
||||||
setConfig({
|
setConfig({
|
||||||
...config,
|
...config,
|
||||||
@@ -101,8 +107,13 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onWallpaperChange({ backgroundUrls: config.backgroundUrls });
|
onWallpaperChange({ currentWallpapers: config.currentWallpapers });
|
||||||
}, [config.backgroundUrls]);
|
// Set wallpaperState in localStorage with lastWallpaperChange datetime
|
||||||
|
localStorage.setItem('wallpaperState', JSON.stringify({
|
||||||
|
lastWallpaperChange: new Date().toISOString(),
|
||||||
|
currentIndex: 0,
|
||||||
|
}));
|
||||||
|
}, [config.currentWallpapers]);
|
||||||
|
|
||||||
const handleClockToggleChange = (checked: boolean) => {
|
const handleClockToggleChange = (checked: boolean) => {
|
||||||
setConfig({ ...config, clock: { ...config.clock, enabled: checked } });
|
setConfig({ ...config, clock: { ...config.clock, enabled: checked } });
|
||||||
@@ -162,21 +173,21 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddWallpaper = () => {
|
const handleAddWallpaper = async () => {
|
||||||
if (newWallpaperName.trim() === '' || newWallpaperUrl.trim() === '') return;
|
if (newWallpaperUrl.trim() === '') return;
|
||||||
|
try {
|
||||||
const newWallpaper: Wallpaper = {
|
const finalName = await addWallpaperToChromeStorageLocal(newWallpaperName, newWallpaperUrl);
|
||||||
name: newWallpaperName,
|
const newWallpaper: Wallpaper = { name: finalName };
|
||||||
url: newWallpaperUrl,
|
const updatedUserWallpapers = [...userWallpapers, newWallpaper];
|
||||||
};
|
setUserWallpapers(updatedUserWallpapers);
|
||||||
|
localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
|
||||||
const updatedUserWallpapers = [...userWallpapers, newWallpaper];
|
setConfig({ ...config, currentWallpapers: [...config.currentWallpapers, newWallpaper.name] });
|
||||||
setUserWallpapers(updatedUserWallpapers);
|
setNewWallpaperName('');
|
||||||
localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
|
setNewWallpaperUrl('');
|
||||||
setConfig({ ...config, backgroundUrls: [...config.backgroundUrls, newWallpaperUrl] });
|
} catch (error) {
|
||||||
|
alert('Error adding wallpaper. Please check the URL and try again.');
|
||||||
setNewWallpaperName('');
|
console.error(error);
|
||||||
setNewWallpaperUrl('');
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -186,39 +197,43 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
alert('File size exceeds 4MB. Please choose a smaller file.');
|
alert('File size exceeds 4MB. Please choose a smaller file.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = async () => {
|
||||||
const base64 = reader.result as string;
|
const base64 = reader.result as string;
|
||||||
if (base64.length > 4.5 * 1024 * 1024) {
|
if (base64.length > 4.5 * 1024 * 1024) {
|
||||||
alert('The uploaded image is too large. Please choose a smaller file.');
|
alert('The uploaded image is too large. Please choose a smaller file.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
const updatedUserWallpapers = userWallpapers.filter(w => !w.base64);
|
const finalName = await addWallpaperToChromeStorageLocal(file.name, base64);
|
||||||
const newWallpaper: Wallpaper = {
|
const newWallpaper: Wallpaper = { name: finalName };
|
||||||
name: file.name,
|
const updatedUserWallpapers = [...userWallpapers, newWallpaper];
|
||||||
base64,
|
setUserWallpapers(updatedUserWallpapers);
|
||||||
};
|
localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
|
||||||
setUserWallpapers([...updatedUserWallpapers, newWallpaper]);
|
setConfig({ ...config, currentWallpapers: [...config.currentWallpapers, newWallpaper.name] });
|
||||||
localStorage.setItem('userWallpapers', JSON.stringify([...updatedUserWallpapers, newWallpaper]));
|
} catch (error) {
|
||||||
setConfig({ ...config, backgroundUrls: [...config.backgroundUrls, base64] });
|
alert('Error adding wallpaper. Please try again.');
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteWallpaper = (wallpaper: Wallpaper) => {
|
const handleDeleteUserWallpaper = async (wallpaper: Wallpaper) => {
|
||||||
const wallpaperIdentifier = wallpaper.url || wallpaper.base64;
|
try {
|
||||||
const updatedUserWallpapers = userWallpapers.filter(w => (w.url || w.base64) !== wallpaperIdentifier);
|
await removeWallpaperFromChromeStorageLocal(wallpaper.name);
|
||||||
setUserWallpapers(updatedUserWallpapers);
|
const updatedUserWallpapers = userWallpapers.filter(w => w.name !== wallpaper.name);
|
||||||
localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
|
setUserWallpapers(updatedUserWallpapers);
|
||||||
|
localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
|
||||||
const newBackgroundUrls = config.backgroundUrls.filter((url: string) => url !== wallpaperIdentifier);
|
const newcurrentWallpapers = config.currentWallpapers.filter((name: string) => name !== wallpaper.name);
|
||||||
|
const newConfig = { ...config, currentWallpapers: newcurrentWallpapers };
|
||||||
const newConfig = { ...config, backgroundUrls: newBackgroundUrls };
|
setConfig(newConfig);
|
||||||
setConfig(newConfig);
|
onWallpaperChange({ currentWallpapers: newcurrentWallpapers });
|
||||||
onWallpaperChange({ backgroundUrls: newBackgroundUrls });
|
} catch (error) {
|
||||||
|
alert('Error deleting wallpaper. Please try again.');
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const allWallpapers = [...baseWallpapers, ...userWallpapers];
|
const allWallpapers = [...baseWallpapers, ...userWallpapers];
|
||||||
@@ -226,7 +241,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50" role="dialog" aria-modal="true">
|
<div className="fixed inset-0 z-50" role="dialog" aria-modal="true">
|
||||||
<div
|
<div
|
||||||
className={`fixed inset-0 bg-black/60 backdrop-blur-sm transition-opacity duration-300 ease-in-out ${
|
className={`fixed inset-0 bg-black/60 backdrop-blur-sm transition-opacity duration-250 ease-ios ${
|
||||||
isVisible ? 'opacity-100' : 'opacity-0'
|
isVisible ? 'opacity-100' : 'opacity-0'
|
||||||
}`}
|
}`}
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
@@ -234,7 +249,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
className={`fixed top-0 right-0 h-full w-full max-w-lg bg-black/50 backdrop-blur-xl border-l border-white/10 text-white flex flex-col transition-transform duration-300 ease-in-out transform ${
|
className={`fixed top-0 right-0 h-full w-full max-w-lg bg-black/50 backdrop-blur-xl border-l border-white/10 text-white flex flex-col transition-transform duration-300 ease-spring transform ${
|
||||||
isVisible ? 'translate-x-0' : 'translate-x-full'
|
isVisible ? 'translate-x-0' : 'translate-x-full'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -294,30 +309,6 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label className="text-slate-300 text-sm font-semibold mb-2 block">Subtitle</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="subtitle"
|
|
||||||
value={config.subtitle}
|
|
||||||
onChange={handleChange}
|
|
||||||
className="bg-white/10 p-3 rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-cyan-400"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<label className="text-slate-300 text-sm font-semibold">Subtitle Size</label>
|
|
||||||
<Dropdown
|
|
||||||
name="subtitleSize"
|
|
||||||
value={config.subtitleSize}
|
|
||||||
onChange={handleChange}
|
|
||||||
options={[
|
|
||||||
{ value: 'tiny', label: 'Tiny' },
|
|
||||||
{ value: 'small', label: 'Small' },
|
|
||||||
{ value: 'medium', label: 'Medium' },
|
|
||||||
{ value: 'large', label: 'Large' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-slate-300 text-sm font-semibold">Vertical Alignment</label>
|
<label className="text-slate-300 text-sm font-semibold">Vertical Alignment</label>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -365,35 +356,17 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-slate-300 text-sm font-semibold">Background</label>
|
<label className="text-slate-300 text-sm font-semibold">Background</label>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
name="backgroundUrls"
|
name="currentWallpapers"
|
||||||
value={config.backgroundUrls}
|
value={config.currentWallpapers}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
multiple
|
multiple
|
||||||
options={allWallpapers.map(w => ({
|
options={allWallpapers.map(w => ({
|
||||||
value: w.url || w.base64 || '',
|
value: w.name,
|
||||||
label: (
|
label: w.name
|
||||||
<div className="flex items-center justify-between w-full">
|
|
||||||
<span>{w.name}</span>
|
|
||||||
{!baseWallpapers.find(bw => (bw.url || bw.base64) === (w.url || w.base64)) && (
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleDeleteWallpaper(w);
|
|
||||||
}}
|
|
||||||
className="text-red-500 hover:text-red-400 ml-4 p-1 rounded-full flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" className="bi bi-trash" viewBox="0 0 16 16">
|
|
||||||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
|
||||||
<path fillRule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{Array.isArray(config.backgroundUrls) && config.backgroundUrls.length > 1 && (
|
{Array.isArray(config.currentWallpapers) && config.currentWallpapers.length > 1 && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-slate-300 text-sm font-semibold">Change Frequency</label>
|
<label className="text-slate-300 text-sm font-semibold">Change Frequency</label>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -456,48 +429,71 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
<span>{config.wallpaperOpacity}%</span>
|
<span>{config.wallpaperOpacity}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{chromeStorageAvailable && (
|
||||||
<h3 className="text-slate-300 text-sm font-semibold mb-2">Add New Wallpaper</h3>
|
<>
|
||||||
<div className="flex flex-col gap-2">
|
<div>
|
||||||
<input
|
<h3 className="text-slate-300 text-sm font-semibold mb-2">User Wallpapers</h3>
|
||||||
type="text"
|
<div className="flex flex-col gap-2">
|
||||||
placeholder="Wallpaper Name"
|
{userWallpapers.map((wallpaper) => (
|
||||||
value={newWallpaperName}
|
<div key={wallpaper.name} className="flex items-center justify-between bg-white/10 p-2 rounded-lg">
|
||||||
onChange={(e) => setNewWallpaperName(e.target.value)}
|
<span className="truncate">{wallpaper.name}</span>
|
||||||
className="bg-white/10 p-2 rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-cyan-400"
|
<button
|
||||||
/>
|
onClick={() => handleDeleteUserWallpaper(wallpaper)}
|
||||||
<div className="flex gap-2">
|
className="text-red-500 hover:text-red-400"
|
||||||
<input
|
>
|
||||||
type="text"
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-trash" viewBox="0 0 16 16">
|
||||||
placeholder="Image URL"
|
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
||||||
value={newWallpaperUrl}
|
<path fillRule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
||||||
onChange={(e) => setNewWallpaperUrl(e.target.value)}
|
</svg>
|
||||||
className="bg-white/10 p-2 rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-cyan-400"
|
</button>
|
||||||
/>
|
</div>
|
||||||
<button
|
))}
|
||||||
onClick={handleAddWallpaper}
|
</div>
|
||||||
className="bg-cyan-500 hover:bg-cyan-400 text-white font-bold py-2 px-4 rounded-lg"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center w-full">
|
<div>
|
||||||
<label
|
<h3 className="text-slate-300 text-sm font-semibold mb-2">Add New Wallpaper</h3>
|
||||||
htmlFor="file-upload"
|
<div className="flex flex-col gap-2">
|
||||||
className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed rounded-lg cursor-pointer bg-white/5 border-white/20 hover:bg-white/10"
|
<input
|
||||||
>
|
type="text"
|
||||||
<div className="flex flex-col items-center justify-center pt-5 pb-6">
|
placeholder="Wallpaper Name (optional for URLs)"
|
||||||
<svg className="w-8 h-8 mb-4 text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
|
value={newWallpaperName}
|
||||||
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
|
onChange={(e) => setNewWallpaperName(e.target.value)}
|
||||||
</svg>
|
className="bg-white/10 p-2 rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-cyan-400"
|
||||||
<p className="mb-2 text-sm text-gray-400"><span className="font-semibold">Click to upload</span> or drag and drop</p>
|
/>
|
||||||
<p className="text-xs text-gray-400">PNG, JPG, WEBP, etc.</p>
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Image URL"
|
||||||
|
value={newWallpaperUrl}
|
||||||
|
onChange={(e) => setNewWallpaperUrl(e.target.value)}
|
||||||
|
className="bg-white/10 p-2 rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-cyan-400"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleAddWallpaper}
|
||||||
|
className="bg-cyan-500 hover:bg-cyan-400 text-white font-bold py-2 px-4 rounded-lg"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<input id="file-upload" type="file" className="hidden" onChange={handleFileUpload} ref={fileInputRef} />
|
<div className="flex items-center justify-center w-full">
|
||||||
</label>
|
<label
|
||||||
|
htmlFor="file-upload"
|
||||||
|
className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed rounded-lg cursor-pointer bg-white/5 border-white/20 hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center justify-center pt-5 pb-6">
|
||||||
|
<svg className="w-8 h-8 mb-4 text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
|
||||||
|
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
|
||||||
|
</svg>
|
||||||
|
<p className="mb-2 text-sm text-gray-400"><span className="font-semibold">Click to upload</span> or drag and drop</p>
|
||||||
|
<p className="text-xs text-gray-400">PNG, JPG, WEBP, etc.</p>
|
||||||
|
</div>
|
||||||
|
<input id="file-upload" type="file" className="hidden" onChange={handleFileUpload} ref={fileInputRef} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -646,10 +642,10 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-8 border-t border-white/10">
|
<div className="p-8 border-t border-white/10">
|
||||||
<div className="flex justify-end gap-4">
|
<div className="flex justify-end gap-4">
|
||||||
<button onClick={() => { isSaving.current = true; onSave(config); }} className="bg-green-500 hover:bg-green-400 text-white font-bold py-2 px-6 rounded-lg">
|
<button onClick={() => { isSaving.current = true; onSave(config); }} className="bg-green-500 hover:bg-green-400 active:scale-95 text-white font-bold py-2 px-6 rounded-lg transition-all duration-150 ease-ios">
|
||||||
Save & Close
|
Save & Close
|
||||||
</button>
|
</button>
|
||||||
<button onClick={handleClose} className="bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-6 rounded-lg">
|
<button onClick={handleClose} className="bg-gray-600 hover:bg-gray-500 active:scale-95 text-white font-bold py-2 px-6 rounded-lg transition-all duration-150 ease-ios">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const Dropdown: React.FC<DropdownProps> = ({ options, value, onChange, name, mul
|
|||||||
>
|
>
|
||||||
<span className="truncate">{selectedOptionLabel}</span>
|
<span className="truncate">{selectedOptionLabel}</span>
|
||||||
<svg
|
<svg
|
||||||
className={`w-5 h-5 transition-transform duration-300 ease-in-out ${isOpen ? 'rotate-180' : 'rotate-0'}`}
|
className={`w-5 h-5 transition-transform duration-200 ease-ios ${isOpen ? 'rotate-180' : 'rotate-0'}`}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@@ -95,14 +95,14 @@ const Dropdown: React.FC<DropdownProps> = ({ options, value, onChange, name, mul
|
|||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<ul
|
<ul
|
||||||
className="absolute z-10 mt-1 w-full bg-black/70 backdrop-blur-xl border border-white/20 rounded-lg shadow-2xl overflow-hidden animate-in slide-in-from-top-2 fade-in duration-200"
|
className="absolute z-10 mt-1 w-full bg-black/70 backdrop-blur-xl border border-white/20 rounded-lg shadow-2xl overflow-hidden animate-in slide-in-from-top-2 fade-in duration-150"
|
||||||
role="listbox"
|
role="listbox"
|
||||||
>
|
>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<li
|
<li
|
||||||
key={option.value}
|
key={option.value}
|
||||||
onClick={() => handleOptionClick(option.value)}
|
onClick={() => handleOptionClick(option.value)}
|
||||||
className={`h-10 px-3 text-white cursor-pointer transition-all duration-150 ease-in-out flex items-center
|
className={`h-10 px-3 text-white cursor-pointer transition-all duration-150 ease-ios flex items-center
|
||||||
${
|
${
|
||||||
isSelected(option.value)
|
isSelected(option.value)
|
||||||
? 'bg-cyan-500/20 text-cyan-300'
|
? 'bg-cyan-500/20 text-cyan-300'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Server } from '../types';
|
import { Server } from '../types';
|
||||||
import ping from './utils/jsping.js';
|
import ping from './utils/jsping.js';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface ToggleSwitchProps {
|
interface ToggleSwitchProps {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
@@ -12,11 +12,11 @@ const ToggleSwitch: React.FC<ToggleSwitchProps> = ({ checked, onChange }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-14 h-8 flex items-center rounded-full p-1 cursor-pointer transition-colors duration-300 ${checked ? 'bg-cyan-500' : 'bg-gray-600'}`}
|
className={`w-14 h-8 flex items-center rounded-full p-1 cursor-pointer transition-colors duration-200 ease-ios ${checked ? 'bg-cyan-500' : 'bg-gray-600'}`}
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`bg-white w-6 h-6 rounded-full shadow-md transform transition-transform duration-300 ${checked ? 'translate-x-6' : 'translate-x-0'}`}
|
className={`bg-white w-6 h-6 rounded-full shadow-md transform transition-transform duration-200 ease-spring ${checked ? 'translate-x-6' : 'translate-x-0'}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
106
components/Wallpaper.tsx
Normal file
106
components/Wallpaper.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { baseWallpapers } from './utils/baseWallpapers';
|
||||||
|
import { Wallpaper as WallpaperType } from '../types';
|
||||||
|
import { getWallpaperFromChromeStorageLocal } from './utils/StorageLocalManager';
|
||||||
|
|
||||||
|
interface WallpaperProps {
|
||||||
|
wallpaperNames: string[];
|
||||||
|
blur: number;
|
||||||
|
brightness: number;
|
||||||
|
opacity: number;
|
||||||
|
wallpaperFrequency: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWallpaperUrlByName = async (name: string): Promise<string | undefined> => {
|
||||||
|
const foundInBase = baseWallpapers.find((w: WallpaperType) => w.name === name);
|
||||||
|
if (foundInBase) {
|
||||||
|
return foundInBase.url || foundInBase.base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userWallpapers: WallpaperType[] = JSON.parse(localStorage.getItem('userWallpapers') || '[]');
|
||||||
|
const foundInUser = userWallpapers.find((w: WallpaperType) => w.name === name);
|
||||||
|
if (foundInUser) {
|
||||||
|
try {
|
||||||
|
const wallpaperData = await getWallpaperFromChromeStorageLocal(name);
|
||||||
|
if (wallpaperData && wallpaperData.startsWith('http')) {
|
||||||
|
return wallpaperData;
|
||||||
|
}
|
||||||
|
return wallpaperData || undefined;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting wallpaper from chrome storage', error);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Wallpaper: React.FC<WallpaperProps> = ({ wallpaperNames, blur, brightness, opacity, wallpaperFrequency }) => {
|
||||||
|
const [imageUrl, setImageUrl] = useState<string | undefined>(undefined);
|
||||||
|
const [currentWallpaperIndex, setCurrentWallpaperIndex] = useState<number>(0);
|
||||||
|
|
||||||
|
// Helper to parse wallpaperFrequency string to ms
|
||||||
|
const parseFrequencyToMs = (freq: string): number => {
|
||||||
|
if (!freq) return 24 * 60 * 60 * 1000; // default 1 day
|
||||||
|
const match = freq.match(/(\d+)(h|d)/);
|
||||||
|
if (!match) return 24 * 60 * 60 * 1000;
|
||||||
|
const value = parseInt(match[1], 10);
|
||||||
|
const unit = match[2];
|
||||||
|
if (unit === 'h') return value * 60 * 60 * 1000;
|
||||||
|
if (unit === 'd') return value * 24 * 60 * 60 * 1000;
|
||||||
|
return 24 * 60 * 60 * 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateWallpaper = async () => {
|
||||||
|
if (wallpaperNames.length === 0) return;
|
||||||
|
// Read wallpaperState from localStorage
|
||||||
|
const wallpaperState = JSON.parse(localStorage.getItem('wallpaperState') || '{}');
|
||||||
|
const lastChange = wallpaperState.lastWallpaperChange ? new Date(wallpaperState.lastWallpaperChange).getTime() : 0;
|
||||||
|
const now = Date.now();
|
||||||
|
const freqMs = parseFrequencyToMs(wallpaperFrequency);
|
||||||
|
let currentIndex = typeof wallpaperState.currentIndex === 'number' ? wallpaperState.currentIndex : 0;
|
||||||
|
|
||||||
|
// If enough time has passed, pick a new wallpaper
|
||||||
|
if (now - lastChange >= freqMs) {
|
||||||
|
currentIndex = (currentIndex + 1) % wallpaperNames.length;
|
||||||
|
localStorage.setItem('wallpaperState', JSON.stringify({
|
||||||
|
lastWallpaperChange: new Date().toISOString(),
|
||||||
|
currentIndex
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
// Keep currentIndex in sync with localStorage if not updating
|
||||||
|
localStorage.setItem('wallpaperState', JSON.stringify({
|
||||||
|
lastWallpaperChange: wallpaperState.lastWallpaperChange || new Date().toISOString(),
|
||||||
|
currentIndex
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
setCurrentWallpaperIndex(currentIndex);
|
||||||
|
const wallpaperName = wallpaperNames[currentIndex];
|
||||||
|
const url = await getWallpaperUrlByName(wallpaperName);
|
||||||
|
setImageUrl(url);
|
||||||
|
};
|
||||||
|
updateWallpaper();
|
||||||
|
// No timer, just run on render/dependency change
|
||||||
|
}, [wallpaperNames, wallpaperFrequency]);
|
||||||
|
|
||||||
|
if (!imageUrl) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 -z-10 w-full h-full"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${imageUrl})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
filter: `blur(${blur}px) brightness(${brightness / 100})`,
|
||||||
|
opacity: opacity / 100,
|
||||||
|
transition: 'filter 0.3s, opacity 0.3s',
|
||||||
|
}}
|
||||||
|
aria-label="Wallpaper background"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Wallpaper;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Website } from '../types';
|
import { Website } from '../types';
|
||||||
import { getWebsiteIcon } from './utils/iconService';
|
import { getWebsiteIcon } from './utils/iconService';
|
||||||
|
|
||||||
@@ -76,6 +76,7 @@ const WebsiteEditModal: React.FC<WebsiteEditModalProps> = ({ website, edit, onCl
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
|
console.log({ id: website?.id, name, url, icon });
|
||||||
onSave({ id: website?.id, name, url, icon });
|
onSave({ id: website?.id, name, url, icon });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
17
components/WebsiteTile.tsx
Executable file → Normal file
17
components/WebsiteTile.tsx
Executable file → Normal file
@@ -1,7 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Website } from '../types';
|
import { Website } from '../types';
|
||||||
|
|
||||||
|
|
||||||
interface WebsiteTileProps {
|
interface WebsiteTileProps {
|
||||||
website: Website;
|
website: Website;
|
||||||
isEditing: boolean;
|
isEditing: boolean;
|
||||||
@@ -28,9 +27,9 @@ const getTileSizeClass = (size: string | undefined) => {
|
|||||||
const getIconPixelSize = (size: string | undefined): number => {
|
const getIconPixelSize = (size: string | undefined): number => {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 'small':
|
case 'small':
|
||||||
return 32;
|
return 34;
|
||||||
case 'medium':
|
case 'medium':
|
||||||
return 40;
|
return 42;
|
||||||
case 'large':
|
case 'large':
|
||||||
return 48;
|
return 48;
|
||||||
default:
|
default:
|
||||||
@@ -74,13 +73,13 @@ const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, o
|
|||||||
const iconSizeLoadingClass = `w-[${getIconLoadingPixelSize(tileSize)}px] h-[${getIconLoadingPixelSize(tileSize)}px]`;
|
const iconSizeLoadingClass = `w-[${getIconLoadingPixelSize(tileSize)}px] h-[${getIconLoadingPixelSize(tileSize)}px]`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`relative ${getTileSizeClass(tileSize)} transition-all duration-300 ease-in-out`}>
|
<div className={`relative ${getTileSizeClass(tileSize)} transition-all duration-200 ease-ios`}>
|
||||||
<a
|
<a
|
||||||
href={isEditing ? undefined : website.url}
|
href={isEditing ? undefined : website.url}
|
||||||
target="_self"
|
target="_self"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className="group flex flex-col items-center justify-center p-4 bg-black/25 backdrop-blur-md border border-white/10 rounded-2xl w-full h-full transform transition-all duration-300 ease-in-out hover:scale-105 hover:bg-white/25 shadow-lg focus:outline-none focus:ring-2 focus:ring-cyan-400 focus:ring-opacity-75"
|
className="group flex flex-col items-center justify-center p-4 bg-black/25 backdrop-blur-md border border-white/10 rounded-2xl w-full h-full transform transition-all duration-200 ease-ios hover:scale-[1.04] active:scale-[0.96] hover:bg-white/25 shadow-lg focus:outline-none focus:ring-2 focus:ring-cyan-400 focus:ring-opacity-75"
|
||||||
>
|
>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center mb-6">
|
<div className="absolute inset-0 flex items-center justify-center mb-6">
|
||||||
@@ -90,11 +89,11 @@ const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, o
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={`flex items-center transition-all duration-300 ease-in ${isLoading ? 'mt-18' : 'flex-col'} ${isLoading ? 'gap-2' : ''}`}>
|
<div className={`flex items-center transition-all duration-200 ease-ios ${isLoading ? 'mt-18' : 'flex-col'} ${isLoading ? 'gap-2' : ''}`}>
|
||||||
<div className={`transition-all duration-300 ease-in ${isLoading ? iconSizeLoadingClass : iconSizeClass}`}>
|
<div className={`transition-all duration-200 ease-ios ${isLoading ? iconSizeLoadingClass : iconSizeClass}`}>
|
||||||
<img src={website.icon} alt={`${website.name} icon`} className="object-contain" />
|
<img src={website.icon} alt={`${website.name} icon`} className={`object-contain w-full h-full`} />
|
||||||
</div>
|
</div>
|
||||||
<span className={`text-slate-100 font-medium text-base tracking-wide text-center transition-all duration-300 ease-in ${isLoading ? 'text-sm' : ''}`}>
|
<span className={`text-slate-100 font-medium text-base tracking-wide text-center transition-all duration-200 ease-ios ${isLoading ? 'text-sm' : ''}`}>
|
||||||
{website.name}
|
{website.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
import WebsiteTile from '../WebsiteTile';
|
import WebsiteTile from '../WebsiteTile';
|
||||||
import { Category, Website } from '../../types';
|
import { Category, Website } from '../../types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface ConfigurationButtonProps {
|
interface ConfigurationButtonProps {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@@ -9,7 +9,7 @@ const ConfigurationButton: React.FC<ConfigurationButtonProps> = ({ onClick }) =>
|
|||||||
<div className="absolute top-4 right-4">
|
<div className="absolute top-4 right-4">
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="bg-black/25 backdrop-blur-md border border-white/10 rounded-xl p-3 text-white flex items-center gap-2 hover:bg-white/25 transition-colors"
|
className="bg-black/25 backdrop-blur-md border border-white/10 rounded-xl p-3 text-white flex items-center gap-2 hover:bg-white/25 active:scale-90 transition-all duration-200 ease-ios"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<circle cx="12" cy="12" r="3" stroke="currentColor" strokeWidth="2" fill="none"/>
|
<circle cx="12" cy="12" r="3" stroke="currentColor" strokeWidth="2" fill="none"/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface EditButtonProps {
|
interface EditButtonProps {
|
||||||
isEditing: boolean;
|
isEditing: boolean;
|
||||||
@@ -10,7 +10,7 @@ const EditButton: React.FC<EditButtonProps> = ({ isEditing, onClick }) => {
|
|||||||
<div className="absolute top-4 left-4">
|
<div className="absolute top-4 left-4">
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="bg-black/25 backdrop-blur-md border border-white/10 rounded-xl p-3 text-white flex items-center gap-2 hover:bg-white/25 transition-colors"
|
className="bg-black/25 backdrop-blur-md border border-white/10 rounded-xl p-3 text-white flex items-center gap-2 hover:bg-white/25 active:scale-90 transition-all duration-200 ease-ios"
|
||||||
style={{ fontSize: '12px' }}
|
style={{ fontSize: '12px' }}
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-pencil" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-pencil" viewBox="0 0 16 16">
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
import Clock from '../Clock';
|
import Clock from '../Clock';
|
||||||
import { Config } from '../../types';
|
import { Config } from '../../types';
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ const Header: React.FC<HeaderProps> = ({ config }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={`flex flex-col ${config.alignment === 'bottom' ? 'mt-auto' : ''} items-center`}>
|
<div className={`flex flex-col ${config.alignment === 'bottom' ? 'mt-auto' : ''} items-center`}>
|
||||||
{(config.title || config.subtitle) && (
|
{config.title && (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h1
|
<h1
|
||||||
className={`${getTitleSizeClass(config.titleSize)} font-extrabold text-white tracking-tighter mb-3 mt-4`}
|
className={`${getTitleSizeClass(config.titleSize)} font-extrabold text-white tracking-tighter mb-3 mt-4`}
|
||||||
@@ -68,12 +67,6 @@ const Header: React.FC<HeaderProps> = ({ config }) => {
|
|||||||
>
|
>
|
||||||
{config.title}
|
{config.title}
|
||||||
</h1>
|
</h1>
|
||||||
<p
|
|
||||||
className={`${getSubtitleSizeClass(config.subtitleSize)} text-slate-300`}
|
|
||||||
style={{ textShadow: '0 1px 3px rgba(0,0,0,0.5)' }}
|
|
||||||
>
|
|
||||||
{config.subtitle}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
164
components/utils/StorageLocalManager.ts
Normal file
164
components/utils/StorageLocalManager.ts
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
// TypeScript interface for window.chrome
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
chrome?: {
|
||||||
|
storage?: {
|
||||||
|
local?: {
|
||||||
|
set: (items: object, callback?: () => void) => void;
|
||||||
|
get: (keys: string[] | string, callback: (items: { [key: string]: string }) => void) => void;
|
||||||
|
remove: (keys: string | string[], callback?: () => void) => void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
runtime?: {
|
||||||
|
lastError?: { message: string };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let isChromeStorageLocalAvailable: boolean | null = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if chrome.storage.local is available and caches the result.
|
||||||
|
*/
|
||||||
|
export function checkChromeStorageLocalAvailable(): boolean {
|
||||||
|
if (isChromeStorageLocalAvailable !== null) return isChromeStorageLocalAvailable;
|
||||||
|
isChromeStorageLocalAvailable =
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
typeof window.chrome !== 'undefined' &&
|
||||||
|
typeof window.chrome.storage !== 'undefined' &&
|
||||||
|
typeof window.chrome.storage.local !== 'undefined';
|
||||||
|
return isChromeStorageLocalAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new wallpaper to chrome.storage.local.
|
||||||
|
* If the URL is fetchable, it will be stored as base64 and the name will be derived from the URL.
|
||||||
|
* If the URL is not fetchable (e.g., CORS), it will be stored as a URL and the provided name will be used.
|
||||||
|
* @param name Wallpaper name (string), used as a fallback.
|
||||||
|
* @param url Wallpaper image URL (string) or base64 data URL.
|
||||||
|
* @returns Promise<string> The name under which the wallpaper was stored.
|
||||||
|
* @throws Error if chrome.storage.local is unavailable or if a name is not provided for a non-fetchable URL.
|
||||||
|
*/
|
||||||
|
export async function addWallpaperToChromeStorageLocal(name: string, url: string): Promise<string> {
|
||||||
|
if (!checkChromeStorageLocalAvailable()) {
|
||||||
|
throw new Error('chrome.storage.local is not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.startsWith('data:')) {
|
||||||
|
// This is a base64 encoded image from a file upload.
|
||||||
|
// The name is the file name.
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
if (window.chrome?.storage?.local) {
|
||||||
|
window.chrome.storage.local.set({ [name]: url }, function () {
|
||||||
|
if (window.chrome?.runtime?.lastError) {
|
||||||
|
reject(new Error(window.chrome.runtime.lastError.message));
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(new Error('chrome.storage.local is not available'));
|
||||||
|
}
|
||||||
|
}).then(() => name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a URL. Let's try to fetch it.
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) throw new Error('Failed to fetch image');
|
||||||
|
const imageBlob = await response.blob();
|
||||||
|
const reader = new FileReader();
|
||||||
|
const base64 = await new Promise<string>((resolve, reject) => {
|
||||||
|
reader.onloadend = () => resolve(reader.result as string);
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsDataURL(imageBlob);
|
||||||
|
});
|
||||||
|
|
||||||
|
// If successful, use the filename from URL as the name.
|
||||||
|
const finalName = url.substring(url.lastIndexOf('/') + 1).replace(/[?#].*$/, '') || name;
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
if (window.chrome?.storage?.local) {
|
||||||
|
window.chrome.storage.local.set({ [finalName]: base64 }, function () {
|
||||||
|
if (window.chrome?.runtime?.lastError) {
|
||||||
|
reject(new Error(window.chrome.runtime.lastError.message));
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(new Error('chrome.storage.local is not available'));
|
||||||
|
}
|
||||||
|
}).then(() => finalName);
|
||||||
|
} catch (error) {
|
||||||
|
// If fetch fails (e.g., CORS), store the URL directly with the user-provided name.
|
||||||
|
console.warn('Could not fetch wallpaper, storing URL instead. Error:', error);
|
||||||
|
if (!name) {
|
||||||
|
throw new Error("A name for the wallpaper is required when the URL can't be accessed.");
|
||||||
|
}
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
if (window.chrome?.storage?.local) {
|
||||||
|
window.chrome.storage.local.set({ [name]: url }, function () {
|
||||||
|
if (window.chrome?.runtime?.lastError) {
|
||||||
|
reject(new Error(window.chrome.runtime.lastError.message));
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(new Error('chrome.storage.local is not available'));
|
||||||
|
}
|
||||||
|
}).then(() => name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a specific wallpaper from chrome.storage.local by name.
|
||||||
|
* @param name Wallpaper name (string)
|
||||||
|
* @returns Promise<string | null> (base64 string or null)
|
||||||
|
* @throws Error if chrome.storage.local is unavailable
|
||||||
|
*/
|
||||||
|
export async function getWallpaperFromChromeStorageLocal(name: string): Promise<string | null> {
|
||||||
|
if (!checkChromeStorageLocalAvailable()) {
|
||||||
|
throw new Error('chrome.storage.local is not available');
|
||||||
|
}
|
||||||
|
return new Promise<string | null>((resolve, reject) => {
|
||||||
|
if (window.chrome?.storage?.local) {
|
||||||
|
window.chrome.storage.local.get([name], function (result: { [key: string]: string }) {
|
||||||
|
if (window.chrome?.runtime?.lastError) {
|
||||||
|
reject(new Error(window.chrome.runtime.lastError.message));
|
||||||
|
} else {
|
||||||
|
resolve(result[name] || null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(new Error('chrome.storage.local is not available'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a wallpaper from chrome.storage.local by name.
|
||||||
|
* @param name Wallpaper name (string)
|
||||||
|
* @returns Promise<void>
|
||||||
|
* @throws Error if chrome.storage.local is unavailable
|
||||||
|
*/
|
||||||
|
export async function removeWallpaperFromChromeStorageLocal(name: string): Promise<void> {
|
||||||
|
if (!checkChromeStorageLocalAvailable()) {
|
||||||
|
throw new Error('chrome.storage.local is not available');
|
||||||
|
}
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
if (window.chrome?.storage?.local) {
|
||||||
|
window.chrome.storage.local.remove(name, function () {
|
||||||
|
if (window.chrome?.runtime?.lastError) {
|
||||||
|
reject(new Error(window.chrome.runtime.lastError.message));
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(new Error('chrome.storage.local is not available'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
0
constants.tsx
Executable file → Normal file
0
constants.tsx
Executable file → Normal file
0
icon.png
Executable file → Normal file
0
icon.png
Executable file → Normal file
|
Before Width: | Height: | Size: 763 KiB After Width: | Height: | Size: 763 KiB |
@@ -1 +1,6 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--ease-ios: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
}
|
||||||
0
index.html
Executable file → Normal file
0
index.html
Executable file → Normal file
57
notes-widget-plan.md
Normal file
57
notes-widget-plan.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Notes Widget Implementation Plan
|
||||||
|
## Overview
|
||||||
|
This document outlines the implementation plan for a Notes / Scratchpad Widget feature that:
|
||||||
|
1. Provides a text area that saves content to local storage automatically
|
||||||
|
2. Includes bold and italic formatting buttons
|
||||||
|
3. Has a toggle icon to show/hide the widget
|
||||||
|
4. Has a glassy/frosty design with strong blur
|
||||||
|
## Implementation Steps
|
||||||
|
### 1. Update Config Interface
|
||||||
|
In `types.ts`, add a new `notesWidget` configuration object:
|
||||||
|
```typescript
|
||||||
|
notesWidget: {
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 2. Update ConfigurationModal Component
|
||||||
|
In `components/ConfigurationModal.tsx`:
|
||||||
|
- Add `notesWidget` to the config state initialization (lines 18-44)
|
||||||
|
- Add a new "Notes" tab to the tab navigation (lines 259-284)
|
||||||
|
- Add a toggle switch for notesWidget.enabled in the Notes tab (lines 552-560)
|
||||||
|
- Add the Notes tab content with the toggle switch
|
||||||
|
### 3. Create NotesWidget Component
|
||||||
|
Create a new file `components/NotesWidget.tsx`:
|
||||||
|
- Text area for notes input
|
||||||
|
- Bold and italic formatting buttons
|
||||||
|
- Toggle icon for showing/hiding the widget
|
||||||
|
- Glassy/frosty design with strong blur
|
||||||
|
- Auto-save to localStorage using `userNotes` key
|
||||||
|
### 4. Update App Component
|
||||||
|
In `App.tsx`:
|
||||||
|
- Add notesWidget to the defaultConfig (lines 14-35)
|
||||||
|
- Add the NotesWidget component to the render output when enabled in config (lines 250-251)
|
||||||
|
- Add logic to save/load notes from localStorage
|
||||||
|
## Technical Details
|
||||||
|
### Configuration Structure
|
||||||
|
The configuration will be stored in the same way as other widgets:
|
||||||
|
```typescript
|
||||||
|
config: {
|
||||||
|
notesWidget: {
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### Local Storage Key
|
||||||
|
All notes text will be saved to localStorage with the key `userNotes`.
|
||||||
|
### UI Design
|
||||||
|
- Widget will be positioned on the left side of the screen
|
||||||
|
- Vertically centered using flexbox
|
||||||
|
- Glassy/frosty design using backdrop-blur and background transparency
|
||||||
|
- Strong blur effect (backdrop-blur-2xl or similar)
|
||||||
|
- Toggle icon will be positioned on the left edge of the widget
|
||||||
|
- When hidden, icon shows a "show" indicator
|
||||||
|
- When open, icon shows a "hide" indicator
|
||||||
|
### Formatting Buttons
|
||||||
|
- Bold button (B)
|
||||||
|
- Italic button (I)
|
||||||
|
- These will use HTML formatting (bold/italic tags) or rich text approach
|
||||||
616
package-lock.json
generated
616
package-lock.json
generated
@@ -10,14 +10,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"react": "^19.1.0",
|
"preact": "^10.26.4"
|
||||||
"react-dom": "^19.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@preact/preset-vite": "^2.10.1",
|
||||||
"@tailwindcss/postcss": "^4.1.11",
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@vitejs/plugin-react": "^4.7.0",
|
"@types/react-dom": "^19.1.5",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
@@ -52,13 +52,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.27.1",
|
"version": "7.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.27.1",
|
"@babel/helper-validator-identifier": "^7.28.5",
|
||||||
"js-tokens": "^4.0.0",
|
"js-tokens": "^4.0.0",
|
||||||
"picocolors": "^1.1.1"
|
"picocolors": "^1.1.1"
|
||||||
},
|
},
|
||||||
@@ -108,14 +108,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/generator": {
|
"node_modules/@babel/generator": {
|
||||||
"version": "7.28.0",
|
"version": "7.29.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||||
"integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
|
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.28.0",
|
"@babel/parser": "^7.29.0",
|
||||||
"@babel/types": "^7.28.0",
|
"@babel/types": "^7.29.0",
|
||||||
"@jridgewell/gen-mapping": "^0.3.12",
|
"@jridgewell/gen-mapping": "^0.3.12",
|
||||||
"@jridgewell/trace-mapping": "^0.3.28",
|
"@jridgewell/trace-mapping": "^0.3.28",
|
||||||
"jsesc": "^3.0.2"
|
"jsesc": "^3.0.2"
|
||||||
@@ -124,6 +124,19 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-annotate-as-pure": {
|
||||||
|
"version": "7.27.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
|
||||||
|
"integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.27.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-compilation-targets": {
|
"node_modules/@babel/helper-compilation-targets": {
|
||||||
"version": "7.27.2",
|
"version": "7.27.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
|
||||||
@@ -152,14 +165,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-module-imports": {
|
"node_modules/@babel/helper-module-imports": {
|
||||||
"version": "7.27.1",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||||
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/traverse": "^7.27.1",
|
"@babel/traverse": "^7.28.6",
|
||||||
"@babel/types": "^7.27.1"
|
"@babel/types": "^7.28.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -184,9 +197,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-plugin-utils": {
|
"node_modules/@babel/helper-plugin-utils": {
|
||||||
"version": "7.27.1",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
||||||
"integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
|
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -204,9 +217,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-validator-identifier": {
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
"version": "7.27.1",
|
"version": "7.28.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -238,13 +251,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.28.0",
|
"version": "7.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
|
||||||
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
|
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.28.0"
|
"@babel/types": "^7.29.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
@@ -253,14 +266,14 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/plugin-transform-react-jsx-self": {
|
"node_modules/@babel/plugin-syntax-jsx": {
|
||||||
"version": "7.27.1",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
|
||||||
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
|
"integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.27.1"
|
"@babel/helper-plugin-utils": "^7.28.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -269,14 +282,34 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/plugin-transform-react-jsx-source": {
|
"node_modules/@babel/plugin-transform-react-jsx": {
|
||||||
"version": "7.27.1",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz",
|
||||||
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
|
"integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.27.1"
|
"@babel/helper-annotate-as-pure": "^7.27.3",
|
||||||
|
"@babel/helper-module-imports": "^7.28.6",
|
||||||
|
"@babel/helper-plugin-utils": "^7.28.6",
|
||||||
|
"@babel/plugin-syntax-jsx": "^7.28.6",
|
||||||
|
"@babel/types": "^7.28.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/plugin-transform-react-jsx-development": {
|
||||||
|
"version": "7.27.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz",
|
||||||
|
"integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/plugin-transform-react-jsx": "^7.27.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -295,33 +328,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.27.2",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||||
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.28.6",
|
||||||
"@babel/parser": "^7.27.2",
|
"@babel/parser": "^7.28.6",
|
||||||
"@babel/types": "^7.27.1"
|
"@babel/types": "^7.28.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/traverse": {
|
"node_modules/@babel/traverse": {
|
||||||
"version": "7.28.0",
|
"version": "7.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||||
"integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
|
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.28.0",
|
"@babel/generator": "^7.29.0",
|
||||||
"@babel/helper-globals": "^7.28.0",
|
"@babel/helper-globals": "^7.28.0",
|
||||||
"@babel/parser": "^7.28.0",
|
"@babel/parser": "^7.29.0",
|
||||||
"@babel/template": "^7.27.2",
|
"@babel/template": "^7.28.6",
|
||||||
"@babel/types": "^7.28.0",
|
"@babel/types": "^7.29.0",
|
||||||
"debug": "^4.3.1"
|
"debug": "^4.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -329,14 +362,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/types": {
|
"node_modules/@babel/types": {
|
||||||
"version": "7.28.1",
|
"version": "7.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||||
"integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
|
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.27.1",
|
"@babel/helper-string-parser": "^7.27.1",
|
||||||
"@babel/helper-validator-identifier": "^7.27.1"
|
"@babel/helper-validator-identifier": "^7.28.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -807,9 +840,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
@@ -822,13 +855,121 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@preact/preset-vite": {
|
||||||
"version": "1.0.0-beta.27",
|
"version": "2.10.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.10.5.tgz",
|
||||||
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
"integrity": "sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/plugin-transform-react-jsx": "^7.27.1",
|
||||||
|
"@babel/plugin-transform-react-jsx-development": "^7.27.1",
|
||||||
|
"@prefresh/vite": "^2.4.11",
|
||||||
|
"@rollup/pluginutils": "^5.0.0",
|
||||||
|
"babel-plugin-transform-hook-names": "^1.0.2",
|
||||||
|
"debug": "^4.4.3",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"vite-prerender-plugin": "^0.5.8",
|
||||||
|
"zimmerframe": "^1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "7.x",
|
||||||
|
"vite": "2.x || 3.x || 4.x || 5.x || 6.x || 7.x || 8.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prefresh/babel-plugin": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prefresh/babel-plugin/-/babel-plugin-0.5.3.tgz",
|
||||||
|
"integrity": "sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@prefresh/core": {
|
||||||
|
"version": "1.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.9.tgz",
|
||||||
|
"integrity": "sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"preact": "^10.0.0 || ^11.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prefresh/utils": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prefresh/utils/-/utils-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@prefresh/vite": {
|
||||||
|
"version": "2.4.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prefresh/vite/-/vite-2.4.12.tgz",
|
||||||
|
"integrity": "sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.22.1",
|
||||||
|
"@prefresh/babel-plugin": "^0.5.2",
|
||||||
|
"@prefresh/core": "^1.5.0",
|
||||||
|
"@prefresh/utils": "^1.2.0",
|
||||||
|
"@rollup/pluginutils": "^4.2.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"preact": "^10.4.0 || ^11.0.0-0",
|
||||||
|
"vite": ">=2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prefresh/vite/node_modules/@rollup/pluginutils": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"estree-walker": "^2.0.1",
|
||||||
|
"picomatch": "^2.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prefresh/vite/node_modules/picomatch": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/pluginutils": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.0",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"picomatch": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.45.0",
|
"version": "4.45.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.0.tgz",
|
||||||
@@ -1365,51 +1506,6 @@
|
|||||||
"vite": "^5.2.0 || ^6 || ^7"
|
"vite": "^5.2.0 || ^6 || ^7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/babel__core": {
|
|
||||||
"version": "7.20.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
|
||||||
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/parser": "^7.20.7",
|
|
||||||
"@babel/types": "^7.20.7",
|
|
||||||
"@types/babel__generator": "*",
|
|
||||||
"@types/babel__template": "*",
|
|
||||||
"@types/babel__traverse": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/babel__generator": {
|
|
||||||
"version": "7.27.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
|
|
||||||
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/types": "^7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/babel__template": {
|
|
||||||
"version": "7.4.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
|
|
||||||
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/parser": "^7.1.0",
|
|
||||||
"@babel/types": "^7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/babel__traverse": {
|
|
||||||
"version": "7.20.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
|
|
||||||
"integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/types": "^7.20.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
@@ -1427,13 +1523,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.8",
|
"version": "19.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||||
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-dom": {
|
||||||
|
"version": "19.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^19.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/use-sync-external-store": {
|
"node_modules/@types/use-sync-external-store": {
|
||||||
@@ -1442,27 +1548,6 @@
|
|||||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
|
||||||
"version": "4.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
|
||||||
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/core": "^7.28.0",
|
|
||||||
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
|
|
||||||
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
|
|
||||||
"@rolldown/pluginutils": "1.0.0-beta.27",
|
|
||||||
"@types/babel__core": "^7.20.5",
|
|
||||||
"react-refresh": "^0.17.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^14.18.0 || >=16.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.21",
|
"version": "10.4.21",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
||||||
@@ -1501,6 +1586,23 @@
|
|||||||
"postcss": "^8.1.0"
|
"postcss": "^8.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/babel-plugin-transform-hook-names": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.12.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.25.1",
|
"version": "4.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
|
||||||
@@ -1580,17 +1682,47 @@
|
|||||||
"tiny-invariant": "^1.0.6"
|
"tiny-invariant": "^1.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1614,6 +1746,65 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.187",
|
"version": "1.5.187",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz",
|
||||||
@@ -1634,6 +1825,19 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.6",
|
"version": "0.25.6",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz",
|
||||||
@@ -1685,6 +1889,13 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/estree-walker": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.4.6",
|
"version": "6.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
||||||
@@ -1743,6 +1954,16 @@
|
|||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/he": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"he": "bin/he"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jiti": {
|
"node_modules/jiti": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
||||||
@@ -1785,6 +2006,13 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/kolorist": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lightningcss": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
||||||
@@ -2031,12 +2259,12 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.17",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
|
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minipass": {
|
"node_modules/minipass": {
|
||||||
@@ -2100,6 +2328,17 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-html-parser": {
|
||||||
|
"version": "6.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz",
|
||||||
|
"integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-select": "^5.1.0",
|
||||||
|
"he": "1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.19",
|
"version": "2.0.19",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||||
@@ -2117,6 +2356,19 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -2170,6 +2422,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/preact": {
|
||||||
|
"version": "10.29.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.29.0.tgz",
|
||||||
|
"integrity": "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/raf-schd": {
|
"node_modules/raf-schd": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
|
||||||
@@ -2181,6 +2443,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -2190,6 +2453,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
@@ -2220,16 +2484,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-refresh": {
|
|
||||||
"version": "0.17.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
|
||||||
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/redux": {
|
"node_modules/redux": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
@@ -2279,7 +2533,8 @@
|
|||||||
"version": "0.26.0",
|
"version": "0.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "6.3.1",
|
"version": "6.3.1",
|
||||||
@@ -2291,6 +2546,26 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-code-frame": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-code-frame/-/simple-code-frame-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"kolorist": "^1.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||||
|
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -2300,6 +2575,16 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stack-trace": {
|
||||||
|
"version": "1.0.0-pre2",
|
||||||
|
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz",
|
||||||
|
"integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
|
||||||
@@ -2489,6 +2774,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vite-prerender-plugin": {
|
||||||
|
"version": "0.5.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite-prerender-plugin/-/vite-prerender-plugin-0.5.13.tgz",
|
||||||
|
"integrity": "sha512-IKSpYkzDBsKAxa05naRbj7GvNVMSdww/Z/E89oO3xndz+gWnOBOKOAbEXv7qDhktY/j3vHgJmoV1pPzqU2tx9g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"kolorist": "^1.8.0",
|
||||||
|
"magic-string": "0.x >= 0.26.0",
|
||||||
|
"node-html-parser": "^6.1.12",
|
||||||
|
"simple-code-frame": "^1.3.0",
|
||||||
|
"source-map": "^0.7.4",
|
||||||
|
"stack-trace": "^1.0.0-pre2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": "5.x || 6.x || 7.x || 8.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||||
@@ -2497,6 +2800,13 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zimmerframe": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
package.json
Executable file → Normal file
6
package.json
Executable file → Normal file
@@ -11,14 +11,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"react": "^19.1.0",
|
"preact": "^10.26.4"
|
||||||
"react-dom": "^19.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@preact/preset-vite": "^2.10.1",
|
||||||
"@tailwindcss/postcss": "^4.1.11",
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@vitejs/plugin-react": "^4.7.0",
|
"@types/react-dom": "^19.1.5",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
|
|||||||
0
public/favicon.ico
Executable file → Normal file
0
public/favicon.ico
Executable file → Normal file
|
Before Width: | Height: | Size: 763 KiB After Width: | Height: | Size: 763 KiB |
113
scripts/check_virustotal.sh
Normal file
113
scripts/check_virustotal.sh
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to check a file against VirusTotal API
|
||||||
|
# Requires: curl, jq
|
||||||
|
# Environment variable: virustotal_apikey
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
FILE_PATH="${VIRUS_TOTAL_FILE:-vision-start.zip}"
|
||||||
|
API_KEY="${virustotal_apikey}"
|
||||||
|
BASE_URL="https://www.virustotal.com/vtapi/v2"
|
||||||
|
|
||||||
|
# Check if API key is set
|
||||||
|
if [ -z "$API_KEY" ]; then
|
||||||
|
echo "Error: virustotal_apikey environment variable is not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if file exists
|
||||||
|
if [ ! -f "$FILE_PATH" ]; then
|
||||||
|
echo "Error: File $FILE_PATH not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if required tools are available
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
echo "Error: curl is required but not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
echo "Error: jq is required but not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Uploading $FILE_PATH to VirusTotal for analysis..."
|
||||||
|
|
||||||
|
# Upload file to VirusTotal
|
||||||
|
UPLOAD_RESPONSE=$(curl -s -X POST \
|
||||||
|
-F "apikey=$API_KEY" \
|
||||||
|
-F "file=@$FILE_PATH" \
|
||||||
|
"$BASE_URL/file/scan")
|
||||||
|
|
||||||
|
# Extract scan_id from response
|
||||||
|
SCAN_ID=$(echo "$UPLOAD_RESPONSE" | jq -r '.scan_id')
|
||||||
|
|
||||||
|
if [ "$SCAN_ID" == "null" ] || [ -z "$SCAN_ID" ]; then
|
||||||
|
echo "Error: Failed to upload file or get scan ID"
|
||||||
|
echo "Response: $UPLOAD_RESPONSE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "File uploaded successfully. Scan ID: $SCAN_ID"
|
||||||
|
echo "Waiting for analysis to complete..."
|
||||||
|
|
||||||
|
# Wait for analysis to complete and get results
|
||||||
|
MAX_ATTEMPTS=30
|
||||||
|
ATTEMPT=0
|
||||||
|
SLEEP_INTERVAL=10
|
||||||
|
|
||||||
|
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
||||||
|
echo "Checking analysis status (attempt $((ATTEMPT + 1))/$MAX_ATTEMPTS)..."
|
||||||
|
|
||||||
|
# Get scan report
|
||||||
|
REPORT_RESPONSE=$(curl -s -X POST \
|
||||||
|
-d "apikey=$API_KEY" \
|
||||||
|
-d "resource=$SCAN_ID" \
|
||||||
|
"$BASE_URL/file/report")
|
||||||
|
|
||||||
|
# Check if analysis is complete
|
||||||
|
RESPONSE_CODE=$(echo "$REPORT_RESPONSE" | jq -r '.response_code')
|
||||||
|
|
||||||
|
if [ "$RESPONSE_CODE" == "1" ]; then
|
||||||
|
# Analysis complete
|
||||||
|
echo "Analysis completed!"
|
||||||
|
|
||||||
|
# Extract results
|
||||||
|
POSITIVES=$(echo "$REPORT_RESPONSE" | jq -r '.positives')
|
||||||
|
TOTAL=$(echo "$REPORT_RESPONSE" | jq -r '.total')
|
||||||
|
PERMALINK=$(echo "$REPORT_RESPONSE" | jq -r '.permalink')
|
||||||
|
|
||||||
|
echo "Analysis URL: $PERMALINK"
|
||||||
|
echo "Detection ratio: $POSITIVES/$TOTAL"
|
||||||
|
|
||||||
|
# Check if file is safe
|
||||||
|
if [ "$POSITIVES" -eq 0 ]; then
|
||||||
|
echo "✅ File is clean (no threats detected)"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "❌ File contains threats ($POSITIVES detections out of $TOTAL scanners)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [ "$RESPONSE_CODE" == "0" ]; then
|
||||||
|
# File not found or analysis not complete yet
|
||||||
|
echo "Analysis still in progress..."
|
||||||
|
elif [ "$RESPONSE_CODE" == "-2" ]; then
|
||||||
|
# Still queued for analysis
|
||||||
|
echo "File still queued for analysis..."
|
||||||
|
else
|
||||||
|
echo "Unexpected response code: $RESPONSE_CODE"
|
||||||
|
echo "Response: $REPORT_RESPONSE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ATTEMPT=$((ATTEMPT + 1))
|
||||||
|
if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
|
||||||
|
sleep $SLEEP_INTERVAL
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Timeout: Analysis did not complete within expected time"
|
||||||
|
exit 1
|
||||||
@@ -8,4 +8,15 @@ export default {
|
|||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
safelist: [
|
||||||
|
'w-[24px]', 'h-[24px]',
|
||||||
|
'w-[28px]', 'h-[28px]',
|
||||||
|
'w-[32px]', 'h-[32px]',
|
||||||
|
'w-[34px]', 'h-[34px]',
|
||||||
|
'w-[36px]', 'h-[36px]',
|
||||||
|
'w-[40px]', 'h-[40px]',
|
||||||
|
'w-[42px]', 'h-[42px]',
|
||||||
|
'w-[48px]', 'h-[48px]',
|
||||||
|
// add any other sizes you use
|
||||||
|
],
|
||||||
}
|
}
|
||||||
0
tsconfig.json
Executable file → Normal file
0
tsconfig.json
Executable file → Normal file
4
types.ts
Executable file → Normal file
4
types.ts
Executable file → Normal file
@@ -26,14 +26,12 @@ export interface Wallpaper {
|
|||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
title: string;
|
title: string;
|
||||||
subtitle: string;
|
currentWallpapers: string[];
|
||||||
backgroundUrls: string[];
|
|
||||||
wallpaperFrequency: string;
|
wallpaperFrequency: string;
|
||||||
wallpaperBlur: number;
|
wallpaperBlur: number;
|
||||||
wallpaperBrightness: number;
|
wallpaperBrightness: number;
|
||||||
wallpaperOpacity: number;
|
wallpaperOpacity: number;
|
||||||
titleSize: string;
|
titleSize: string;
|
||||||
subtitleSize: string;
|
|
||||||
alignment: string;
|
alignment: string;
|
||||||
horizontalAlignment: string;
|
horizontalAlignment: string;
|
||||||
clock: {
|
clock: {
|
||||||
|
|||||||
4
vite.config.ts
Executable file → Normal file
4
vite.config.ts
Executable file → Normal file
@@ -1,12 +1,12 @@
|
|||||||
|
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import preact from '@preact/preset-vite'
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [preact(), tailwindcss()],
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
|
|||||||
Reference in New Issue
Block a user