Compare commits
9 Commits
v0.1.3
...
feat/preac
| Author | SHA1 | Date | |
|---|---|---|---|
| ff6670c98c | |||
| d805531369 | |||
| 8cc17a5a17 | |||
| d4c1884471 | |||
| 377be6e8f6 | |||
| c60cb24dd4 | |||
| 2f3949c2e3 | |||
| 9b818b05f9 | |||
| 008d2321e5 |
@@ -6,13 +6,15 @@ on:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
release:
|
||||
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 zip
|
||||
run: sudo apt-get install zip -y
|
||||
- name: Setup required tools
|
||||
run: sudo apt-get install zip jq curl -y
|
||||
- name: Install JS dependencies
|
||||
run: npm install
|
||||
- name: Run build
|
||||
@@ -24,9 +26,65 @@ jobs:
|
||||
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 ServerWidget from './components/ServerWidget';
|
||||
import { DEFAULT_CATEGORIES } from './constants';
|
||||
import { Category, Website, Wallpaper, Config } from './types';
|
||||
import { Category, Website, Config } from './types';
|
||||
import WebsiteEditModal from './components/WebsiteEditModal';
|
||||
import CategoryEditModal from './components/CategoryEditModal';
|
||||
import Header from './components/layout/Header';
|
||||
import EditButton from './components/layout/EditButton';
|
||||
import ConfigurationButton from './components/layout/ConfigurationButton';
|
||||
import CategoryGroup from './components/layout/CategoryGroup';
|
||||
|
||||
import { baseWallpapers } from './components/utils/baseWallpapers';
|
||||
import Wallpaper from './components/Wallpaper';
|
||||
|
||||
const defaultConfig: Config = {
|
||||
title: 'Vision Start',
|
||||
subtitle: 'Your personal portal to the web.',
|
||||
backgroundUrls: ['https://i.imgur.com/C6ynAtX.jpeg'],
|
||||
currentWallpapers: ['Abstract'],
|
||||
wallpaperFrequency: '1d',
|
||||
wallpaperBlur: 0,
|
||||
wallpaperBrightness: 100,
|
||||
wallpaperOpacity: 100,
|
||||
titleSize: 'medium',
|
||||
subtitleSize: 'medium',
|
||||
alignment: 'middle',
|
||||
horizontalAlignment: 'middle',
|
||||
clock: {
|
||||
@@ -60,9 +57,6 @@ const App: React.FC = () => {
|
||||
const storedConfig = localStorage.getItem('config');
|
||||
if (storedConfig) {
|
||||
const parsedConfig = JSON.parse(storedConfig);
|
||||
if (!parsedConfig.backgroundUrls) {
|
||||
parsedConfig.backgroundUrls = [parsedConfig.backgroundUrl].filter(Boolean);
|
||||
}
|
||||
return { ...defaultConfig, ...parsedConfig };
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -70,62 +64,20 @@ const App: React.FC = () => {
|
||||
}
|
||||
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(() => {
|
||||
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));
|
||||
}, [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);
|
||||
setIsConfigModalOpen(false);
|
||||
};
|
||||
@@ -136,10 +88,11 @@ const App: React.FC = () => {
|
||||
|
||||
const handleSaveWebsite = (website: Partial<Website>) => {
|
||||
if (editingWebsite) {
|
||||
const idToUpdate = website.id ?? editingWebsite.id;
|
||||
const newCategories = categories.map(category => ({
|
||||
...category,
|
||||
websites: category.websites.map(w =>
|
||||
w.id === website.id ? { ...w, ...website } : w
|
||||
w.id === idToUpdate ? { ...w, ...website, id: idToUpdate } : w
|
||||
),
|
||||
}));
|
||||
setCategories(newCategories);
|
||||
@@ -201,7 +154,7 @@ const App: React.FC = () => {
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
const category = categories[categoryIndex];
|
||||
@@ -259,14 +212,13 @@ const App: React.FC = () => {
|
||||
<main
|
||||
className={`min-h-screen w-full flex flex-col items-center ${getAlignmentClass(config.alignment)} p-4`}
|
||||
>
|
||||
<div
|
||||
className="fixed inset-0 w-full h-full bg-cover bg-center bg-fixed -z-10"
|
||||
style={{
|
||||
backgroundImage: `url('${currentWallpaper}')`,
|
||||
filter: `blur(${config.wallpaperBlur}px) brightness(${config.wallpaperBrightness}%)`,
|
||||
opacity: `${config.wallpaperOpacity}%`,
|
||||
}}
|
||||
></div>
|
||||
<Wallpaper
|
||||
wallpaperNames={config.currentWallpapers}
|
||||
blur={config.wallpaperBlur}
|
||||
brightness={config.wallpaperBrightness}
|
||||
opacity={config.wallpaperOpacity}
|
||||
wallpaperFrequency={config.wallpaperFrequency}
|
||||
/>
|
||||
<EditButton isEditing={isEditing} onClick={() => setIsEditing(!isEditing)} />
|
||||
<ConfigurationButton onClick={() => setIsConfigModalOpen(true)} />
|
||||
|
||||
@@ -288,13 +240,13 @@ const App: React.FC = () => {
|
||||
/>
|
||||
))}
|
||||
{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
|
||||
onClick={() => {
|
||||
setEditingCategory(null);
|
||||
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">
|
||||
<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>
|
||||
|
||||
{config.serverWidget.enabled && (
|
||||
<div className="absolute bottom-4 right-4">
|
||||
<ServerWidget config={config} />
|
||||
</div>
|
||||
)}
|
||||
{config.serverWidget.enabled && <ServerWidget config={config} />}
|
||||
|
||||
{(editingWebsite || addingWebsite) && (
|
||||
<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.
|
||||
|
||||
* **Abstract**
|
||||
* **Abstract Red**
|
||||
* **Beach**
|
||||
* **Dark**
|
||||
* **Mountain**
|
||||
* **Waves**
|
||||
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
|
||||
2. Download the latest `vision-start-[version].zip` file
|
||||
3. Extract the zip file, you will have a `vision-start` folder
|
||||
4. Go to chrome://extensions/
|
||||
5. Enable "Developer mode" in the top right corner
|
||||
6. Click on "Load unpacked" and select the `vision-start` folder you extracted in step 3
|
||||
7. The extension should now be installed! Just open a new tab to see it in action.
|
||||
|
||||
## 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.
|
||||
* **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
|
||||
|
||||
**Prerequisites:** Node.js
|
||||
@@ -49,10 +55,10 @@ npm run dev
|
||||
|
||||
* [x] Multiple Wallpapers
|
||||
* [x] Remake icons
|
||||
* [] Increase offline compatibility (might not be possible)
|
||||
* Use chrome.storage.local for user wallpapers -- this one is
|
||||
* 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
|
||||
* [/] Increase offline compatibility (might not be possible)
|
||||
- [x] Use chrome.storage.local for user wallpapers -- this one is
|
||||
- [ ] 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
|
||||
* Dynamic Weather Widget
|
||||
* 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
|
||||
@@ -76,7 +82,7 @@ npm run dev
|
||||
* Dynamic Wallpaper-Based Theming
|
||||
* Automatically adapt the UI's accent color to match the current wallpaper
|
||||
* Minimal feel toggle
|
||||
* Disable title & subtitle and search widget
|
||||
* Disable title and search widget
|
||||
* Tiles become small stylish lines
|
||||
|
||||
From a technical side:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Category } from '../types';
|
||||
|
||||
interface CategoryEditModalProps {
|
||||
@@ -12,10 +12,6 @@ interface CategoryEditModalProps {
|
||||
const CategoryEditModal: React.FC<CategoryEditModalProps> = ({ category, edit, onClose, onSave, onDelete }) => {
|
||||
const [name, setName] = useState(category ? category.name : '');
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(name);
|
||||
};
|
||||
|
||||
const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
@@ -44,7 +40,7 @@ const CategoryEditModal: React.FC<CategoryEditModalProps> = ({ category, edit, o
|
||||
)}
|
||||
</div>
|
||||
<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
|
||||
</button>
|
||||
<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 { baseWallpapers } from './utils/baseWallpapers';
|
||||
import { addWallpaperToChromeStorageLocal, removeWallpaperFromChromeStorageLocal, checkChromeStorageLocalAvailable } from './utils/StorageLocalManager';
|
||||
|
||||
interface ConfigurationModalProps {
|
||||
onClose: () => void;
|
||||
@@ -17,7 +18,6 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
const [config, setConfig] = useState({
|
||||
...currentConfig,
|
||||
titleSize: currentConfig.titleSize || 'medium',
|
||||
subtitleSize: currentConfig.subtitleSize || 'medium',
|
||||
alignment: currentConfig.alignment || 'middle',
|
||||
tileSize: currentConfig.tileSize || 'medium',
|
||||
horizontalAlignment: currentConfig.horizontalAlignment || 'middle',
|
||||
@@ -37,7 +37,9 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
format: 'h:mm A',
|
||||
...currentConfig.clock,
|
||||
},
|
||||
backgroundUrls: currentConfig.backgroundUrls || [],
|
||||
currentWallpapers: Array.isArray(currentConfig.currentWallpapers)
|
||||
? currentConfig.currentWallpapers.filter((name: string) => typeof name === 'string')
|
||||
: [],
|
||||
wallpaperFrequency: currentConfig.wallpaperFrequency || '1d',
|
||||
});
|
||||
const [activeTab, setActiveTab] = useState('general');
|
||||
@@ -46,12 +48,14 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
const [newWallpaperName, setNewWallpaperName] = useState('');
|
||||
const [newWallpaperUrl, setNewWallpaperUrl] = useState('');
|
||||
const [userWallpapers, setUserWallpapers] = useState<Wallpaper[]>([]);
|
||||
const [chromeStorageAvailable, setChromeStorageAvailable] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const isSaving = useRef(false);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setChromeStorageAvailable(checkChromeStorageLocalAvailable());
|
||||
const storedUserWallpapers = localStorage.getItem('userWallpapers');
|
||||
if (storedUserWallpapers) {
|
||||
setUserWallpapers(JSON.parse(storedUserWallpapers));
|
||||
@@ -59,7 +63,6 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// A small timeout to allow the component to mount before starting the transition
|
||||
const timer = setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 10);
|
||||
@@ -69,7 +72,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (!isSaving.current) {
|
||||
onWallpaperChange({ backgroundUrls: currentConfig.backgroundUrls });
|
||||
onWallpaperChange({ currentWallpapers: currentConfig.currentWallpapers });
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
@@ -78,12 +81,15 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
setIsVisible(false);
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
}, 300); // This duration should match the transition duration
|
||||
}, 250);
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement> | { target: { name: string; value: string | string[] } }) => {
|
||||
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];
|
||||
setConfig({
|
||||
...config,
|
||||
@@ -101,8 +107,13 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onWallpaperChange({ backgroundUrls: config.backgroundUrls });
|
||||
}, [config.backgroundUrls]);
|
||||
onWallpaperChange({ currentWallpapers: config.currentWallpapers });
|
||||
// Set wallpaperState in localStorage with lastWallpaperChange datetime
|
||||
localStorage.setItem('wallpaperState', JSON.stringify({
|
||||
lastWallpaperChange: new Date().toISOString(),
|
||||
currentIndex: 0,
|
||||
}));
|
||||
}, [config.currentWallpapers]);
|
||||
|
||||
const handleClockToggleChange = (checked: boolean) => {
|
||||
setConfig({ ...config, clock: { ...config.clock, enabled: checked } });
|
||||
@@ -162,21 +173,21 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddWallpaper = () => {
|
||||
if (newWallpaperName.trim() === '' || newWallpaperUrl.trim() === '') return;
|
||||
|
||||
const newWallpaper: Wallpaper = {
|
||||
name: newWallpaperName,
|
||||
url: newWallpaperUrl,
|
||||
};
|
||||
|
||||
const updatedUserWallpapers = [...userWallpapers, newWallpaper];
|
||||
setUserWallpapers(updatedUserWallpapers);
|
||||
localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
|
||||
setConfig({ ...config, backgroundUrls: [...config.backgroundUrls, newWallpaperUrl] });
|
||||
|
||||
setNewWallpaperName('');
|
||||
setNewWallpaperUrl('');
|
||||
const handleAddWallpaper = async () => {
|
||||
if (newWallpaperUrl.trim() === '') return;
|
||||
try {
|
||||
const finalName = await addWallpaperToChromeStorageLocal(newWallpaperName, newWallpaperUrl);
|
||||
const newWallpaper: Wallpaper = { name: finalName };
|
||||
const updatedUserWallpapers = [...userWallpapers, newWallpaper];
|
||||
setUserWallpapers(updatedUserWallpapers);
|
||||
localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
|
||||
setConfig({ ...config, currentWallpapers: [...config.currentWallpapers, newWallpaper.name] });
|
||||
setNewWallpaperName('');
|
||||
setNewWallpaperUrl('');
|
||||
} catch (error) {
|
||||
alert('Error adding wallpaper. Please check the URL and try again.');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
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.');
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
reader.onload = async () => {
|
||||
const base64 = reader.result as string;
|
||||
if (base64.length > 4.5 * 1024 * 1024) {
|
||||
alert('The uploaded image is too large. Please choose a smaller file.');
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedUserWallpapers = userWallpapers.filter(w => !w.base64);
|
||||
const newWallpaper: Wallpaper = {
|
||||
name: file.name,
|
||||
base64,
|
||||
};
|
||||
setUserWallpapers([...updatedUserWallpapers, newWallpaper]);
|
||||
localStorage.setItem('userWallpapers', JSON.stringify([...updatedUserWallpapers, newWallpaper]));
|
||||
setConfig({ ...config, backgroundUrls: [...config.backgroundUrls, base64] });
|
||||
try {
|
||||
const finalName = await addWallpaperToChromeStorageLocal(file.name, base64);
|
||||
const newWallpaper: Wallpaper = { name: finalName };
|
||||
const updatedUserWallpapers = [...userWallpapers, newWallpaper];
|
||||
setUserWallpapers(updatedUserWallpapers);
|
||||
localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
|
||||
setConfig({ ...config, currentWallpapers: [...config.currentWallpapers, newWallpaper.name] });
|
||||
} catch (error) {
|
||||
alert('Error adding wallpaper. Please try again.');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteWallpaper = (wallpaper: Wallpaper) => {
|
||||
const wallpaperIdentifier = wallpaper.url || wallpaper.base64;
|
||||
const updatedUserWallpapers = userWallpapers.filter(w => (w.url || w.base64) !== wallpaperIdentifier);
|
||||
setUserWallpapers(updatedUserWallpapers);
|
||||
localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
|
||||
|
||||
const newBackgroundUrls = config.backgroundUrls.filter((url: string) => url !== wallpaperIdentifier);
|
||||
|
||||
const newConfig = { ...config, backgroundUrls: newBackgroundUrls };
|
||||
setConfig(newConfig);
|
||||
onWallpaperChange({ backgroundUrls: newBackgroundUrls });
|
||||
const handleDeleteUserWallpaper = async (wallpaper: Wallpaper) => {
|
||||
try {
|
||||
await removeWallpaperFromChromeStorageLocal(wallpaper.name);
|
||||
const updatedUserWallpapers = userWallpapers.filter(w => w.name !== wallpaper.name);
|
||||
setUserWallpapers(updatedUserWallpapers);
|
||||
localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
|
||||
const newcurrentWallpapers = config.currentWallpapers.filter((name: string) => name !== wallpaper.name);
|
||||
const newConfig = { ...config, currentWallpapers: newcurrentWallpapers };
|
||||
setConfig(newConfig);
|
||||
onWallpaperChange({ currentWallpapers: newcurrentWallpapers });
|
||||
} catch (error) {
|
||||
alert('Error deleting wallpaper. Please try again.');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const allWallpapers = [...baseWallpapers, ...userWallpapers];
|
||||
@@ -226,7 +241,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
return (
|
||||
<div className="fixed inset-0 z-50" role="dialog" aria-modal="true">
|
||||
<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'
|
||||
}`}
|
||||
onClick={handleClose}
|
||||
@@ -234,7 +249,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
|
||||
<div
|
||||
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'
|
||||
}`}
|
||||
>
|
||||
@@ -294,30 +309,6 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
]}
|
||||
/>
|
||||
</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">
|
||||
<label className="text-slate-300 text-sm font-semibold">Vertical Alignment</label>
|
||||
<Dropdown
|
||||
@@ -365,35 +356,17 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-slate-300 text-sm font-semibold">Background</label>
|
||||
<Dropdown
|
||||
name="backgroundUrls"
|
||||
value={config.backgroundUrls}
|
||||
name="currentWallpapers"
|
||||
value={config.currentWallpapers}
|
||||
onChange={handleChange}
|
||||
multiple
|
||||
options={allWallpapers.map(w => ({
|
||||
value: w.url || w.base64 || '',
|
||||
label: (
|
||||
<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>
|
||||
)
|
||||
value: w.name,
|
||||
label: w.name
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
{Array.isArray(config.backgroundUrls) && config.backgroundUrls.length > 1 && (
|
||||
{Array.isArray(config.currentWallpapers) && config.currentWallpapers.length > 1 && (
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-slate-300 text-sm font-semibold">Change Frequency</label>
|
||||
<Dropdown
|
||||
@@ -456,48 +429,71 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
<span>{config.wallpaperOpacity}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-slate-300 text-sm font-semibold mb-2">Add New Wallpaper</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Wallpaper Name"
|
||||
value={newWallpaperName}
|
||||
onChange={(e) => setNewWallpaperName(e.target.value)}
|
||||
className="bg-white/10 p-2 rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-cyan-400"
|
||||
/>
|
||||
<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>
|
||||
{chromeStorageAvailable && (
|
||||
<>
|
||||
<div>
|
||||
<h3 className="text-slate-300 text-sm font-semibold mb-2">User Wallpapers</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{userWallpapers.map((wallpaper) => (
|
||||
<div key={wallpaper.name} className="flex items-center justify-between bg-white/10 p-2 rounded-lg">
|
||||
<span className="truncate">{wallpaper.name}</span>
|
||||
<button
|
||||
onClick={() => handleDeleteUserWallpaper(wallpaper)}
|
||||
className="text-red-500 hover:text-red-400"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" 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>
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<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>
|
||||
<h3 className="text-slate-300 text-sm font-semibold mb-2">Add New Wallpaper</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Wallpaper Name (optional for URLs)"
|
||||
value={newWallpaperName}
|
||||
onChange={(e) => setNewWallpaperName(e.target.value)}
|
||||
className="bg-white/10 p-2 rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-cyan-400"
|
||||
/>
|
||||
<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>
|
||||
<input id="file-upload" type="file" className="hidden" onChange={handleFileUpload} ref={fileInputRef} />
|
||||
</label>
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<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>
|
||||
)}
|
||||
|
||||
@@ -646,10 +642,10 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
|
||||
</div>
|
||||
<div className="p-8 border-t border-white/10">
|
||||
<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
|
||||
</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
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -79,7 +79,7 @@ const Dropdown: React.FC<DropdownProps> = ({ options, value, onChange, name, mul
|
||||
>
|
||||
<span className="truncate">{selectedOptionLabel}</span>
|
||||
<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"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
@@ -95,14 +95,14 @@ const Dropdown: React.FC<DropdownProps> = ({ options, value, onChange, name, mul
|
||||
|
||||
{isOpen && (
|
||||
<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"
|
||||
>
|
||||
{options.map((option) => (
|
||||
<li
|
||||
key={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)
|
||||
? '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 ping from './utils/jsping.js';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
|
||||
|
||||
interface ToggleSwitchProps {
|
||||
checked: boolean;
|
||||
@@ -12,11 +12,11 @@ const ToggleSwitch: React.FC<ToggleSwitchProps> = ({ checked, onChange }) => {
|
||||
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
|
||||
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 { getWebsiteIcon } from './utils/iconService';
|
||||
|
||||
@@ -76,6 +76,7 @@ const WebsiteEditModal: React.FC<WebsiteEditModalProps> = ({ website, edit, onCl
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
console.log({ 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 { Website } from '../types';
|
||||
|
||||
|
||||
interface WebsiteTileProps {
|
||||
website: Website;
|
||||
isEditing: boolean;
|
||||
@@ -28,9 +27,9 @@ const getTileSizeClass = (size: string | undefined) => {
|
||||
const getIconPixelSize = (size: string | undefined): number => {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return 32;
|
||||
return 34;
|
||||
case 'medium':
|
||||
return 40;
|
||||
return 42;
|
||||
case 'large':
|
||||
return 48;
|
||||
default:
|
||||
@@ -74,13 +73,13 @@ const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, o
|
||||
const iconSizeLoadingClass = `w-[${getIconLoadingPixelSize(tileSize)}px] h-[${getIconLoadingPixelSize(tileSize)}px]`;
|
||||
|
||||
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
|
||||
href={isEditing ? undefined : website.url}
|
||||
target="_self"
|
||||
rel="noopener noreferrer"
|
||||
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 && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
<div className={`flex items-center transition-all duration-300 ease-in ${isLoading ? 'mt-18' : 'flex-col'} ${isLoading ? 'gap-2' : ''}`}>
|
||||
<div className={`transition-all duration-300 ease-in ${isLoading ? iconSizeLoadingClass : iconSizeClass}`}>
|
||||
<img src={website.icon} alt={`${website.name} icon`} className="object-contain" />
|
||||
<div className={`flex items-center transition-all duration-200 ease-ios ${isLoading ? 'mt-18' : 'flex-col'} ${isLoading ? 'gap-2' : ''}`}>
|
||||
<div className={`transition-all duration-200 ease-ios ${isLoading ? iconSizeLoadingClass : iconSizeClass}`}>
|
||||
<img src={website.icon} alt={`${website.name} icon`} className={`object-contain w-full h-full`} />
|
||||
</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}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import WebsiteTile from '../WebsiteTile';
|
||||
import { Category, Website } from '../../types';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
|
||||
|
||||
interface ConfigurationButtonProps {
|
||||
onClick: () => void;
|
||||
@@ -9,7 +9,7 @@ const ConfigurationButton: React.FC<ConfigurationButtonProps> = ({ onClick }) =>
|
||||
<div className="absolute top-4 right-4">
|
||||
<button
|
||||
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">
|
||||
<circle cx="12" cy="12" r="3" stroke="currentColor" strokeWidth="2" fill="none"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
|
||||
|
||||
interface EditButtonProps {
|
||||
isEditing: boolean;
|
||||
@@ -10,7 +10,7 @@ const EditButton: React.FC<EditButtonProps> = ({ isEditing, onClick }) => {
|
||||
<div className="absolute top-4 left-4">
|
||||
<button
|
||||
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' }}
|
||||
>
|
||||
<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 { Config } from '../../types';
|
||||
|
||||
@@ -60,7 +59,7 @@ const Header: React.FC<HeaderProps> = ({ config }) => {
|
||||
</div>
|
||||
)}
|
||||
<div className={`flex flex-col ${config.alignment === 'bottom' ? 'mt-auto' : ''} items-center`}>
|
||||
{(config.title || config.subtitle) && (
|
||||
{config.title && (
|
||||
<div className="text-center">
|
||||
<h1
|
||||
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}
|
||||
</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>
|
||||
|
||||
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";
|
||||
|
||||
@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": {
|
||||
"@hello-pangea/dnd": "^18.0.1",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
"preact": "^10.26.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.10.1",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^4.1.11",
|
||||
@@ -52,13 +52,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
@@ -108,14 +108,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
|
||||
"integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@babel/types": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
@@ -124,6 +124,19 @@
|
||||
"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": {
|
||||
"version": "7.27.2",
|
||||
"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": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
||||
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.27.1",
|
||||
"@babel/types": "^7.27.1"
|
||||
"@babel/traverse": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -184,9 +197,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
|
||||
"integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
||||
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -204,9 +217,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -238,13 +251,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
|
||||
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
|
||||
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.0"
|
||||
"@babel/types": "^7.29.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -253,14 +266,14 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-jsx-self": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
|
||||
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
|
||||
"node_modules/@babel/plugin-syntax-jsx": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
|
||||
"integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
"@babel/helper-plugin-utils": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -269,14 +282,34 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-jsx-source": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
|
||||
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
|
||||
"node_modules/@babel/plugin-transform-react-jsx": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz",
|
||||
"integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"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": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -295,33 +328,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/parser": "^7.27.2",
|
||||
"@babel/types": "^7.27.1"
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/parser": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
|
||||
"integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.0",
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -329,14 +362,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
|
||||
"integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1"
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -807,9 +840,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
|
||||
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
@@ -822,13 +855,121 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
||||
"node_modules/@preact/preset-vite": {
|
||||
"version": "2.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.10.5.tgz",
|
||||
"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,
|
||||
"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": {
|
||||
"version": "4.45.0",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -1427,13 +1523,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
|
||||
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
||||
"version": "19.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"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": {
|
||||
@@ -1442,27 +1548,6 @@
|
||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||
"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": {
|
||||
"version": "10.4.21",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
||||
@@ -1501,6 +1586,23 @@
|
||||
"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": {
|
||||
"version": "4.25.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
|
||||
@@ -1580,17 +1682,47 @@
|
||||
"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": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1614,6 +1746,65 @@
|
||||
"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": {
|
||||
"version": "1.5.187",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz",
|
||||
@@ -1634,6 +1825,19 @@
|
||||
"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": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz",
|
||||
@@ -1685,6 +1889,13 @@
|
||||
"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": {
|
||||
"version": "6.4.6",
|
||||
"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==",
|
||||
"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": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
||||
@@ -1785,6 +2006,13 @@
|
||||
"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": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
||||
@@ -2031,12 +2259,12 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.17",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
@@ -2100,6 +2328,17 @@
|
||||
"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": {
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||
@@ -2117,6 +2356,19 @@
|
||||
"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": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -2170,6 +2422,16 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "4.0.3",
|
||||
"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",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -2190,6 +2453,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"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": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
@@ -2279,7 +2533,8 @@
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
@@ -2291,6 +2546,26 @@
|
||||
"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": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -2300,6 +2575,16 @@
|
||||
"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": {
|
||||
"version": "4.1.11",
|
||||
"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": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||
@@ -2497,6 +2800,13 @@
|
||||
"engines": {
|
||||
"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": {
|
||||
"@hello-pangea/dnd": "^18.0.1",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
"preact": "^10.26.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.10.1",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.6",
|
||||
"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: {},
|
||||
},
|
||||
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 {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
backgroundUrls: string[];
|
||||
currentWallpapers: string[];
|
||||
wallpaperFrequency: string;
|
||||
wallpaperBlur: number;
|
||||
wallpaperBrightness: number;
|
||||
wallpaperOpacity: number;
|
||||
titleSize: string;
|
||||
subtitleSize: string;
|
||||
alignment: string;
|
||||
horizontalAlignment: string;
|
||||
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 react from '@vitejs/plugin-react'
|
||||
import preact from '@preact/preset-vite'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { resolve } from 'path'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
plugins: [preact(), tailwindcss()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
|
||||
Reference in New Issue
Block a user