Compare commits

..

1 Commits
dev ... v7.6.1

Author SHA1 Message Date
Alex Sparkes
e5d8bfec0e Beta (#1138)
* feat: add professional three-branch release workflow automation (#1129) (#1130)

- Add version-bump workflow for semantic versioning across all files
- Add beta-release workflow for automated pre-release testing
- Add production-release workflow with manual approval gates
- Add hotfix-release workflow for emergency patches
- Create comprehensive CONTRIBUTING.md with workflow guide
- Create detailed RELEASE_PROCESS.md for maintainers
- Add PR template with release checklists
- Update CODEOWNERS to protect workflow files
- Update README with contribution links
- Remove /docs from .gitignore to allow documentation

This implements a dev  beta  main branching strategy with:
- Automated version management across 6 files
- Changelog generation from conventional commits
- GitHub Releases with build artifacts
- Environment-based approvals for production
- Back-merge support for hotfixes

* feat: new default quotes experience, improve added page

* Sync/workflow fixes to beta (#1132)

* feat: add professional three-branch release workflow automation (#1129)

- Add version-bump workflow for semantic versioning across all files
- Add beta-release workflow for automated pre-release testing
- Add production-release workflow with manual approval gates
- Add hotfix-release workflow for emergency patches
- Create comprehensive CONTRIBUTING.md with workflow guide
- Create detailed RELEASE_PROCESS.md for maintainers
- Add PR template with release checklists
- Update CODEOWNERS to protect workflow files
- Update README with contribution links
- Remove /docs from .gitignore to allow documentation

This implements a dev  beta  main branching strategy with:
- Automated version management across 6 files
- Changelog generation from conventional commits
- GitHub Releases with build artifacts
- Environment-based approvals for production
- Back-merge support for hotfixes

* fix(workflows): prevent beta release for non-beta versions

* Fix/beta workflow version check (#1131)

* fix(workflows): prevent beta release for non-beta versions

* fix(workflows): address copilot PR review feedback

- Support iterative beta versions (7.6.0-beta.1 -> 7.6.0-beta.2)
- Remove tag trigger from beta workflow to prevent premature releases
- Fix tag format in docs/summaries to include 'v' prefix
- Clarify deployment approval wording

---------

Signed-off-by: Alex Sparkes <alexsparkes@gmail.com>

* feat: replace mui with new style

* feat: improve time formatting in Clock component with padded digits

* fix: change Checkbox component from label to div for better semantics

* fix: change Switch component from label to div for better semantics

* feat: add smooth animation to reset functionality in Slider component

* feat: enhance accessibility and styling for form components including Checkbox, Dropdown, Radio, Slider, and Text

* feat: enhance WeatherOptions component with improved layout and auto location reset functionality

* feat: update Slider and Dropdown components with improved layout and z-index adjustments

* feat: add reset functionality to Dropdown component with toast notification

* feat: update Dropdown component styles for improved layout and structure

* feat: update languageSettings component with increased padding for better spacing

* feat: bump version to 7.6.0 across all manifests and documentation

* Dev (#1134)

* feat: add professional three-branch release workflow automation (#1129)

- Add version-bump workflow for semantic versioning across all files
- Add beta-release workflow for automated pre-release testing
- Add production-release workflow with manual approval gates
- Add hotfix-release workflow for emergency patches
- Create comprehensive CONTRIBUTING.md with workflow guide
- Create detailed RELEASE_PROCESS.md for maintainers
- Add PR template with release checklists
- Update CODEOWNERS to protect workflow files
- Update README with contribution links
- Remove /docs from .gitignore to allow documentation

This implements a dev  beta  main branching strategy with:
- Automated version management across 6 files
- Changelog generation from conventional commits
- GitHub Releases with build artifacts
- Environment-based approvals for production
- Back-merge support for hotfixes

* feat: new default quotes experience, improve added page

* Fix/beta workflow version check (#1131)

* fix(workflows): prevent beta release for non-beta versions

* fix(workflows): address copilot PR review feedback

- Support iterative beta versions (7.6.0-beta.1 -> 7.6.0-beta.2)
- Remove tag trigger from beta workflow to prevent premature releases
- Fix tag format in docs/summaries to include 'v' prefix
- Clarify deployment approval wording

* feat: replace mui with new style

* feat: improve time formatting in Clock component with padded digits

* fix: change Checkbox component from label to div for better semantics

* fix: change Switch component from label to div for better semantics

* feat: add smooth animation to reset functionality in Slider component

* feat: enhance accessibility and styling for form components including Checkbox, Dropdown, Radio, Slider, and Text

* feat: enhance WeatherOptions component with improved layout and auto location reset functionality

* feat: update Slider and Dropdown components with improved layout and z-index adjustments

* feat: add reset functionality to Dropdown component with toast notification

* feat: update Dropdown component styles for improved layout and structure

* feat: update languageSettings component with increased padding for better spacing

* feat: bump version to 7.6.0 across all manifests and documentation

---------

Signed-off-by: Alex Sparkes <alexsparkes@gmail.com>
Co-authored-by: David Ralph <me@davidcralph.co.uk>

* font: replace montserrat with inter

* cleanup: remove unused code from addons and marketplace

* fix(greeting/events): event text box styling

* fix(quote/buttons): improve state management and event handling

* feat(background): implement custom background loading and improve state management

* feat: enhance image management features

- Added new localization strings for image management, including upload and storage information.
- Refactored custom background database functions to support metadata and backward compatibility.
- Introduced a new FolderTaggingModal component for organizing images into folders.
- Created utility functions for image metadata extraction, including dimensions, blur hash generation, and file size calculation.
- Implemented functions to delete multiple backgrounds and update background metadata.

* Add new localization strings and improve image metadata utility functions

- Updated localization files for multiple languages (Hungarian, Indonesian, Japanese, Lithuanian, Latvian, Dutch, Norwegian, Persian, Portuguese, Brazilian Portuguese, Russian, Slovenian, Swedish, Tamil, Turkish, Ukrainian, Vietnamese, Simplified Chinese, Traditional Chinese) to include new strings for image management features such as "Delete Selected", "Uploading", "Tag Images", and storage information.
- Enhanced the `getDataUrlSize` and `formatBytes` functions in `imageMetadata.js` for better readability and maintainability by adding braces for conditional statements.

* fix(background/custom): prevent flashing during uploads

* feat(storage): implement dynamic storage quota estimation and request persistence

* feat(modal): enhance close button styling and theming support

* fix(Custom): remove unnecessary characters from loading state

* feat(Dropdown): implement dropdown closing animation and portal rendering

* fix(QuoteOptions): ensure authorDetails is set to true for all users during migration

* refactor(Items): remove unused imports and hex color conversion logic

* fix: add blurhash dependency for image metadata encoding

---------

Signed-off-by: Alex Sparkes <alexsparkes@gmail.com>
Co-authored-by: David Ralph <me@davidcralph.co.uk>
2026-01-27 12:35:18 +00:00
339 changed files with 7560 additions and 28913 deletions

View File

@@ -1,5 +0,0 @@
{
"enabledPlugins": {
"typescript-lsp@claude-plugins-official": true
}
}

View File

@@ -1,8 +0,0 @@
{
"permissions": {
"allow": [
"Bash(perl -i -pe:*)",
"Bash(bun run:*)"
]
}
}

View File

@@ -1,153 +0,0 @@
# Mue Project Guidelines for GitHub Copilot
## Project Overview
Mue is a fast, open-source browser extension that provides a customizable new tab page for Chrome, Firefox, Edge, and Safari. Built with React and Vite, it emphasizes privacy, performance, and extensibility.
## Technical Stack
- **Framework**: React 19 with hooks and functional components
- **Build Tool**: Vite with SWC
- **Package Manager**: Bun >= 1.3.0 (ALWAYS use Bun, never npm or yarn)
- **Styling**: SCSS with component-scoped styles
- **Target**: Browser extensions (Manifest V3)
- **Testing**: Manual testing across Chrome, Firefox, and Safari
## Code Style & Quality
- Follow ESLint configuration in `eslint.config.js`
- Run `bun run lint:fix` before committing
- Run `bun run pretty` to format code with Prettier
- Use conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, etc.
- All commits must pass commitlint validation
## File Organization
```
src/
├── components/ # Reusable UI components (Elements, Form, Layout)
├── features/ # Feature-specific code (background, weather, etc.)
├── utils/ # Utility functions and helpers
├── contexts/ # React Context providers
├── hooks/ # Custom React hooks
├── i18n/ # Internationalization files
└── scss/ # Global styles, variables, mixins
```
## Critical Rules
### 1. Translation Files (IMPORTANT!)
- **`en_GB.json` is the base translation file**
- Never edit other locale files directly
- Workflow: Edit `en_GB.json` → Run `bun run translations`
- This ensures consistent formatting across all 30+ locales
- Translation files are in `src/i18n/locales/`
### 2. Branch Strategy
```
dev (active development)
beta (release candidates)
main (production/stable)
```
- All PRs target the `dev` branch
- Never commit directly to `beta` or `main`
- See `CONTRIBUTING.md` for full workflow
### 3. Package Manager
- **ALWAYS use Bun**, never npm or yarn
- Commands: `bun install`, `bun run <script>`
- Project requires Bun >= 1.3.0
### 4. Multi-Browser Support
- Test changes in Chrome, Firefox, and Safari when possible
- Browser-specific builds are in `build/chrome/`, `build/firefox/`, etc.
- Manifest files: `manifest/chrome.json`, `manifest/firefox.json`
### 5. State Management
- Use `useLocalStorageState` hook for persistent settings
- Use React Context for shared state (see `src/contexts/`)
- Store user data in localStorage or IndexedDB (via `customBackgroundDB.js`)
### 6. Styling
- SCSS files in `src/scss/`
- Use existing variables from `_variables.scss`
- Use mixins from `_mixins.scss` (especially for responsive design)
- Mobile styles in `_mobile.scss`
## Development Commands
```bash
bun run dev # Start dev server
bun run dev:host # Dev server accessible on network
bun run build # Build for production (all browsers)
bun run lint # Run linters
bun run lint:fix # Auto-fix linting issues
bun run pretty # Format code
bun run translations # Update translation files
```
## Common Patterns
### Component Structure
```jsx
import { useState } from 'react';
import './ComponentName.scss';
export default function ComponentName({ prop1, prop2 }) {
const [state, setState] = useState();
return <div className="component-name">{/* JSX content */}</div>;
}
```
### Using localStorage
```jsx
import { useLocalStorageState } from 'utils/useLocalStorageState';
const [value, setValue] = useLocalStorageState('settingKey', defaultValue);
```
### Using Translations
```jsx
import { useContext } from 'react';
import { TranslationContext } from 'contexts';
const { t } = useContext(TranslationContext);
const text = t('translation.key');
```
## Things to Avoid
- ❌ Don't use npm or yarn (use Bun)
- ❌ Don't edit locale files other than `en_GB.json`
- ❌ Don't commit directly to `beta` or `main` branches
- ❌ Don't introduce new dependencies without discussion
- ❌ Don't skip linting/formatting before commits
- ❌ Don't use class components (use functional components only)
## When Making Changes
1. Check which files need updates (component, styles, utils)
2. Follow existing patterns in similar components
3. Update translations in `en_GB.json` if adding new text
4. Run `bun run lint:fix` and `bun run pretty`
5. Test in at least Chrome and Firefox
6. Write conventional commit message
## Additional Resources
- See `CONTRIBUTING.md` for full contribution guidelines
- See `docs/RELEASE_PROCESS.md` for release procedures
- Check existing components in `src/components/` for patterns

View File

@@ -1,12 +1 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
target-branch: "dev"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"

View File

@@ -8,11 +8,12 @@ jobs:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.1'
bun-version: latest
- name: Install dependencies
run: bun install
- name: Lint
run: bun run lint
continue-on-error: true
- name: Build
run: bun run build
automerge:

View File

@@ -5,7 +5,7 @@ on:
branches:
- beta
tags:
- "v*-beta.*"
- 'v*-beta.*'
permissions:
contents: write
@@ -23,7 +23,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.1"
bun-version: '1.3.1'
- name: Install dependencies
run: bun install
@@ -45,7 +45,7 @@ jobs:
run: |
# Get the latest beta or production tag
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then
echo "No previous tag found, using all commits"
COMMITS=$(git log --pretty=format:"- %s (%h)" HEAD)
@@ -53,13 +53,13 @@ jobs:
echo "Generating changelog from $PREVIOUS_TAG to HEAD"
COMMITS=$(git log --pretty=format:"- %s (%h)" ${PREVIOUS_TAG}..HEAD)
fi
# Create changelog with categorization
FEATURES=$(echo "$COMMITS" | grep -i "^- feat" || echo "")
FIXES=$(echo "$COMMITS" | grep -i "^- fix" || echo "")
CHORES=$(echo "$COMMITS" | grep -i "^- chore\|^- docs\|^- style\|^- refactor" || echo "")
OTHER=$(echo "$COMMITS" | grep -v -i "^- feat\|^- fix\|^- chore\|^- docs\|^- style\|^- refactor" || echo "")
{
echo "changelog<<EOF"
if [ -n "$FEATURES" ]; then
@@ -98,35 +98,35 @@ jobs:
- name: Create or Update GitHub Pre-Release
run: |
RELEASE_NOTES=$(cat <<EOF
## 🧪 Mue v${{ steps.version.outputs.version }}
## 🧪 Mue Beta v${{ steps.version.outputs.version }}
**⚠️ This is a beta release for testing purposes only.**
### Testing Instructions
1. Download the appropriate ZIP file below
2. For Chrome: Load as unpacked extension or install from [unlisted link](https://chromewebstore.google.com/detail/mue/bngmbednanpcfochchhgbkookpiaiaid) (dev team only)
3. For Firefox: Install via about:debugging → Load Temporary Add-on
4. Report issues at https://github.com/mue/mue/issues
${{ steps.changelog.outputs.changelog }}
### Installation Files
- **Chrome/Edge**: \`chrome-${{ steps.version.outputs.version }}.zip\`
- **Firefox**: \`firefox-${{ steps.version.outputs.version }}.zip\`
---
**🔗 Demo**: [demo.muetab.com](https://demo.muetab.com)
**📱 Beta Branch Demo**: [mue-git-beta-mue.vercel.app](https://mue-git-beta-mue.vercel.app)
EOF
)
if [ "${{ steps.check_release.outputs.exists }}" = "true" ]; then
echo "Updating existing release..."
gh release edit "v${{ steps.version.outputs.version }}" \
--notes "$RELEASE_NOTES" \
--prerelease
# Upload new files (will replace if they exist)
gh release upload "v${{ steps.version.outputs.version }}" \
"build/chrome-${{ steps.version.outputs.version }}.zip" \
@@ -137,7 +137,7 @@ jobs:
gh release create "v${{ steps.version.outputs.version }}" \
"build/chrome-${{ steps.version.outputs.version }}.zip" \
"build/firefox-${{ steps.version.outputs.version }}.zip" \
--title "v${{ steps.version.outputs.version }}" \
--title "Beta v${{ steps.version.outputs.version }}" \
--notes "$RELEASE_NOTES" \
--prerelease
fi

View File

@@ -42,21 +42,6 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Generate changelog
id: changelog
run: |
COMMITS=$(git log --pretty=format:"- %s (%h)" origin/main..${{ github.event.inputs.branch_name }})
{
echo "changelog<<EOF"
echo "### 🚨 Hotfix"
echo "${{ github.event.inputs.description }}"
echo ""
echo "### Changes"
echo "$COMMITS"
echo "EOF"
} >> $GITHUB_OUTPUT
- name: Calculate hotfix version (auto-patch bump)
id: version
run: |
@@ -116,6 +101,23 @@ jobs:
git push origin main
git push origin "v${{ steps.version.outputs.new_version }}"
- name: Generate changelog
id: changelog
run: |
# Get commits from hotfix branch
git checkout ${{ github.event.inputs.branch_name }}
COMMITS=$(git log --pretty=format:"- %s (%h)" origin/main..${{ github.event.inputs.branch_name }})
{
echo "changelog<<EOF"
echo "### 🚨 Hotfix"
echo "${{ github.event.inputs.description }}"
echo ""
echo "### Changes"
echo "$COMMITS"
echo "EOF"
} >> $GITHUB_OUTPUT
- name: Create GitHub Release
run: |
git checkout main

View File

@@ -162,25 +162,6 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Back-merge main to beta
run: |
git fetch origin beta
git checkout beta
git merge --no-ff main -m "chore: back-merge production release v${{ steps.version.outputs.version }} from main"
git push origin beta
- name: Back-merge main to dev
run: |
git fetch origin dev
git checkout dev
git merge --no-ff main -m "chore: back-merge production release v${{ steps.version.outputs.version }} from main"
git push origin dev
- name: Output success summary
run: |
echo "## 🚀 Production Release Published!" >> $GITHUB_STEP_SUMMARY
@@ -203,4 +184,4 @@ jobs:
echo "- [ ] Update [muetab.com/blog/changelog](https://muetab.com/blog/changelog)" >> $GITHUB_STEP_SUMMARY
echo "- [ ] Announce release on Discord/social media" >> $GITHUB_STEP_SUMMARY
echo "- [ ] Monitor issue tracker for bug reports" >> $GITHUB_STEP_SUMMARY
echo "- [x] Back-merged \`main\` to \`beta\` and \`dev\` (automated)" >> $GITHUB_STEP_SUMMARY
echo "- [ ] Merge \`main\` back to \`beta\` and \`dev\` to sync version" >> $GITHUB_STEP_SUMMARY

View File

@@ -1,57 +0,0 @@
name: Submit to Browser Stores (Beta)
on:
release:
types: [prereleased]
workflow_dispatch:
inputs:
tag:
description: "Pre-release tag to re-submit (e.g. v7.6.1-beta.3)"
required: true
permissions:
contents: read
jobs:
submit-beta:
runs-on: ubuntu-latest
environment: beta
steps:
- name: Resolve tag and version
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ github.event.inputs.tag }}"
else
TAG="${{ github.event.release.tag_name }}"
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
- name: Download release ZIPs
run: |
mkdir -p dist
gh release download "${{ steps.tag.outputs.tag }}" \
--pattern "chrome-*.zip" \
--dir ./dist
env:
GH_TOKEN: ${{ github.token }}
- name: Verify ZIP
run: |
ls -la dist/
test -n "$(ls dist/chrome-*.zip 2>/dev/null)" || \
{ echo "chrome ZIP not found in dist/"; exit 1; }
- name: Submit to Chrome Web Store (Trusted Testers)
uses: PlasmoHQ/bpp@v3
with:
keys: ${{ secrets.SUBMIT_KEYS_BETA }}
chrome-zip: dist/chrome-${{ steps.tag.outputs.version }}.zip
- name: Summary
if: always()
run: |
echo "## Beta Store Submission: ${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "- Chrome Web Store (trustedTesters)" >> $GITHUB_STEP_SUMMARY

View File

@@ -1,57 +1,27 @@
name: Submit to Browser Stores
name: Submit
on:
release:
types: [released]
workflow_dispatch:
inputs:
tag:
description: "Release tag to re-submit (e.g. v7.6.1)"
description: "Release tag to submit, i.e 6.0.5"
required: true
permissions:
contents: read
jobs:
submit:
runs-on: ubuntu-latest
environment: production
steps:
- name: Resolve tag and version
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ github.event.inputs.tag }}"
else
TAG="${{ github.event.release.tag_name }}"
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
- name: Download release ZIPs
run: |
mkdir -p dist
gh release download "${{ steps.tag.outputs.tag }}" \
--pattern "chrome-*.zip" \
--dir ./dist
- name: Setup Chrome
uses: browser-actions/setup-chrome@latest
with:
chrome-version: latest
- name: Download Github Release Assets
uses: PlasmoHQ/download-release-asset@v1.0.0
with:
tag: ${{ github.event.inputs.tag }}
- name: Browser Plugin Publish
uses: PlasmoHQ/bpp@v2
env:
GH_TOKEN: ${{ github.token }}
- name: Verify ZIP
run: |
ls -la dist/
test -f "dist/chrome-${{ steps.tag.outputs.version }}.zip" || \
{ echo "chrome ZIP not found"; exit 1; }
- name: Submit to Chrome Web Store
uses: PlasmoHQ/bpp@v3
PUPPETEER_EXECUTABLE_PATH: /opt/hostedtoolcache/chromium/latest/x64/chrome
with:
keys: ${{ secrets.SUBMIT_KEYS }}
chrome-zip: dist/chrome-${{ steps.tag.outputs.version }}.zip
- name: Summary
if: always()
run: |
echo "## Store Submission: ${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "- Chrome Web Store (listed)" >> $GITHUB_STEP_SUMMARY

View File

@@ -1,18 +1,31 @@
name: Version Bump
on:
pull_request:
types:
- closed
labels:
- 'release'
workflow_dispatch:
inputs:
bump_type:
description: 'Version bump type'
required: true
type: choice
options:
- patch # 7.5.0 -> 7.5.1 (bug fixes)
- minor # 7.5.0 -> 7.6.0 (new features)
- major # 7.5.0 -> 8.0.0 (breaking changes)
pre_release:
description: 'Pre-release label (leave empty for stable release)'
required: false
type: choice
options:
- ''
- beta
- rc
- alpha
permissions:
contents: write
jobs:
bump-version:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -31,99 +44,99 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Extract bump info from PR
id: bump_info
run: |
BUMP_TYPE=$(echo "${{ github.event.pull_request.body }}" | grep -oP "bump: \K(major|minor|patch)" || echo "patch")
IS_RELEASE=$(echo "${{ github.event.pull_request.body }}" | grep -q "is_release: true" && echo "true" || echo "false")
echo "bump_type=$BUMP_TYPE" >> $GITHUB_OUTPUT
echo "is_release=$IS_RELEASE" >> $GITHUB_OUTPUT
- name: Calculate new version
id: version
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "Current version: $CURRENT_VERSION"
# Remove any pre-release suffix for base version
BASE_VERSION=$(echo $CURRENT_VERSION | sed 's/-.*$//')
IFS='.' read -r -a VERSION_PARTS <<< "$BASE_VERSION"
MAJOR="${VERSION_PARTS[0]}"
MINOR="${VERSION_PARTS[1]}"
PATCH="${VERSION_PARTS[2]}"
case "${{ steps.bump_info.outputs.bump_type }}" in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
# Add pre-release suffix if NOT a production release AND NOT on main branch
if [ "${{ steps.bump_info.outputs.is_release }}" != "true" ] && [ "${{ github.ref_name }}" != "main" ]; then
BETA_COUNT=$(git tag -l "v${NEW_VERSION}-beta.*" | wc -l)
BETA_NUM=$((BETA_COUNT + 1))
NEW_VERSION="${NEW_VERSION}-beta.${BETA_NUM}"
# If requesting a pre-release and current is already a pre-release, keep base version
# Otherwise, bump the version based on type
if [ -n "${{ github.event.inputs.pre_release }}" ] && [ "$IS_PRERELEASE" = "true" ]; then
# Keep existing base version for iterative betas (7.6.0-beta.1 -> 7.6.0-beta.2)
NEW_VERSION="$BASE_VERSION"
else
# Bump version based on type
case "${{ github.event.inputs.bump_type }}" in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
fi
# Browser extension manifests require clean semver — strip pre-release suffix
STABLE_VERSION=$(echo "$NEW_VERSION" | sed 's/-.*$//')
# Add pre-release label if specified
if [ -n "${{ github.event.inputs.pre_release }}" ]; then
# Get beta number by counting existing beta tags for this version
BETA_COUNT=$(git tag -l "v${NEW_VERSION}-${{ github.event.inputs.pre_release }}.*" | wc -l)
BETA_NUM=$((BETA_COUNT + 1))
NEW_VERSION="${NEW_VERSION}-${{ github.event.inputs.pre_release }}.${BETA_NUM}"
fi
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "stable_version=$STABLE_VERSION" >> $GITHUB_OUTPUT
echo "New version will be: $NEW_VERSION"
- name: Update package.json
run: bun x json -I -f package.json -e "this.version='${{ steps.version.outputs.new_version }}'"
run: |
bun x json -I -f package.json -e "this.version='${{ steps.version.outputs.new_version }}'"
- name: Update Chrome manifest
run: bun x json -I -f manifest/chrome.json -e "this.version='${{ steps.version.outputs.stable_version }}'"
run: |
VERSION_WITHOUT_PRERELEASE=$(echo "${{ steps.version.outputs.new_version }}" | sed 's/-.*$//')
bun x json -I -f manifest/chrome.json -e "this.version='${VERSION_WITHOUT_PRERELEASE}'"
- name: Update Firefox manifest
run: bun x json -I -f manifest/firefox.json -e "this.version='${{ steps.version.outputs.stable_version }}'"
run: |
VERSION_WITHOUT_PRERELEASE=$(echo "${{ steps.version.outputs.new_version }}" | sed 's/-.*$//')
bun x json -I -f manifest/firefox.json -e "this.version='${VERSION_WITHOUT_PRERELEASE}'"
- name: Update Safari manifest
run: bun x json -I -f safari/Mue\ Extension/Resources/manifest.json -e "this.version='${{ steps.version.outputs.stable_version }}'"
run: |
VERSION_WITHOUT_PRERELEASE=$(echo "${{ steps.version.outputs.new_version }}" | sed 's/-.*$//')
bun x json -I -f safari/Mue\ Extension/Resources/manifest.json -e "this.version='${VERSION_WITHOUT_PRERELEASE}'"
- name: Update Safari Xcode project
run: sed -i "s/MARKETING_VERSION = [^;]*/MARKETING_VERSION = ${{ steps.version.outputs.stable_version }}/g" safari/Mue.xcodeproj/project.pbxproj
run: |
VERSION_WITHOUT_PRERELEASE=$(echo "${{ steps.version.outputs.new_version }}" | sed 's/-.*$//')
sed -i "s/MARKETING_VERSION = [^;]*/MARKETING_VERSION = ${VERSION_WITHOUT_PRERELEASE}/g" safari/Mue.xcodeproj/project.pbxproj
- name: Update constants.js
run: sed -i "s/export const VERSION = '[^']*'/export const VERSION = '${{ steps.version.outputs.new_version }}'/" src/config/constants.js
run: |
sed -i "s/export const VERSION = '[^']*'/export const VERSION = '${{ steps.version.outputs.new_version }}'/" src/config/constants.js
- name: Commit and tag
- name: Commit version bump
run: |
git add package.json manifest/chrome.json manifest/firefox.json safari/Mue\ Extension/Resources/manifest.json safari/Mue.xcodeproj/project.pbxproj src/config/constants.js
git commit -m "chore: bump version to ${{ steps.version.outputs.new_version }}"
git tag -a "v${{ steps.version.outputs.new_version }}" -m "Release v${{ steps.version.outputs.new_version }}"
- name: Push changes
run: |
git push origin ${{ github.ref_name }}
git push origin "v${{ steps.version.outputs.new_version }}"
- name: Auto back-merge (main only)
if: github.ref_name == 'main'
run: |
git fetch origin
git checkout beta
git merge --no-ff origin/main -m "chore: back-merge main into beta"
git push origin beta
git checkout dev
git merge --no-ff origin/beta -m "chore: back-merge beta into dev"
git push origin dev
- name: Summary
run: |
echo "✅ Version bumped to ${{ steps.version.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY
echo "📦 Tag: v${{ steps.version.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY
echo "🔀 Branch: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "📦 Tag created: v${{ steps.version.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Files updated:" >> $GITHUB_STEP_SUMMARY
echo "- package.json" >> $GITHUB_STEP_SUMMARY
@@ -132,9 +145,3 @@ jobs:
echo "- safari/Mue Extension/Resources/manifest.json" >> $GITHUB_STEP_SUMMARY
echo "- safari/Mue.xcodeproj/project.pbxproj" >> $GITHUB_STEP_SUMMARY
echo "- src/config/constants.js" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ github.ref_name }}" = "main" ]; then
echo "### 🔄 Auto back-merge:" >> $GITHUB_STEP_SUMMARY
echo "- main → beta ✅" >> $GITHUB_STEP_SUMMARY
echo "- beta → dev ✅" >> $GITHUB_STEP_SUMMARY
fi

2
.gitignore vendored
View File

@@ -5,7 +5,6 @@ node_modules/
build/
.idea/
dist/
.claude/worktrees/
# Safari Extension Build Files
safari/Mue Extension/Resources/*.html
@@ -30,7 +29,6 @@ safari/DerivedData/
safari/build/
# Files
unused-translations.txt
package-lock.json
.stylelintcache
yarn-error.log

View File

@@ -1,2 +1 @@
#!/bin/sh
bunx --bun commitlint --edit "$1"
bunx --bun commitlint --edit $1

View File

@@ -1,7 +0,0 @@
{
"setup": [],
"teardown": [],
"run": [
"bun run dev"
]
}

121
CLAUDE.md
View File

@@ -1,121 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What is Mue?
Mue is a browser extension (Chrome, Firefox, Safari) that replaces the new tab page with a customizable dashboard featuring widgets like clock, weather, quotes, greetings, quick links, and backgrounds. Built with React 19, Vite 7, and Manifest V3.
## Commands
```bash
bun install # Install dependencies (always use bun, never npm/yarn)
bun run dev # Dev server with HMR (opens browser automatically)
bun run dev:host # Dev server exposed on network
bun run build # Production build for all browsers (Chrome, Firefox, Safari)
bun run lint # Run ESLint + Stylelint
bun run lint:fix # Auto-fix lint issues
bun run pretty # Format with Prettier
bun run translations # Sync all locale files from en_GB.json base
bun run translations:percentages # Update translation completion stats
bun run translations:unused # Find unused translation keys
```
Build outputs: `build/chrome/`, `build/firefox/`, `safari/Mue Extension/Resources/`. Vite's `prepareBuilds` plugin (in `vite.config.mjs`) copies dist + manifests + icons into each browser folder and creates versioned zips.
E2E tests use Cypress:
```bash
bun run test:e2e # Open Cypress UI
bun run test:e2e:headless # Run headlessly (CI)
```
Tests live in `cypress/e2e/`.
## Architecture
### Bootstrap Flow
1. `src/index.jsx` - Initializes i18n from localStorage language, sets up Sentry, runs data migrations, exposes global `window.t()`, renders `<RouterProvider router={router} />`
2. `src/router/` - Hash-based router (`createHashRouter`) required for extension compatibility (no physical file routing). `ModalRouter.jsx` and `RouterBridge.jsx` handle modal deep-linking.
3. `src/App.jsx` - `useAppSetup()` checks first-run state, calls `loadSettings()` (which applies theme, fonts, custom CSS to the DOM), listens for EventBus `'refresh'` events. Renders: `<TranslationProvider>``<Background>` + `<CustomWidgets>` + `<Widgets>` + `<Modals>`
### State Management (no Redux/Zustand)
All persistent state lives in **localStorage**. Components read from localStorage on mount and re-read when they receive EventBus refresh events. The only React Context is `TranslationContext` for i18n.
- `localStorage.getItem('key') === 'true'` is the standard boolean check pattern
- Settings changes write to localStorage, then emit `EventBus.emit('refresh', 'category')` to notify widgets
- `src/utils/settings/load.js` applies localStorage settings to the DOM (theme classes, injected style elements for custom fonts/CSS)
### EventBus (`src/utils/eventbus.js`)
Static class wrapping DOM CustomEvents. Primary communication mechanism between settings UI and widgets.
Key events:
- `'refresh'` with payload: `'quote'`, `'greeting'`, `'background'`, `'widgets'`, `'clock'`, `'other'` - triggers widget reload
- `'languageChange'` with `{language: 'en_GB'}` - switches locale
- `'modal'` with `'openMainModal'` - opens settings modal
Pattern: register in `useEffect`, clean up with `EventBus.off()` on unmount.
### Feature Organization (`src/features/`)
Each feature (background, time, quote, greeting, weather, search, quicklinks, message, navbar, stats, marketplace, welcome, helpers, modals) follows this structure:
```
feature/
├── index.jsx # Main component
├── options/index.jsx # Settings panel UI
├── hooks/ # Feature-specific hooks (useQuoteState, useQuoteLoader, etc.)
├── components/ # Subcomponents
├── api/ # Data fetching/processing
└── scss/ # Styles
```
The `misc` feature is special - it contains the modal system (`modals/Modals.jsx`), widget layout (`CustomWidgets.jsx`, `views/Widgets.jsx`), and the settings view (`views/Settings.jsx`).
### Modal & Settings System
`Modals.jsx` orchestrates four modals (main, welcome, update, apps). The main modal has three tabs: Settings, Discover (marketplace), Library. Deep-linking via URL hash: `#settings/appearance/fonts`, `#discover/quote_packs`.
### Global Variables (`src/config/variables.js`)
Singleton object holding `language` (i18n instance), `languagecode`, `stats`, and `constants`. Mutated during initialization. `variables.getMessage()` is dynamically set by TranslationContext.
### Hooks
- `useFrequencyInterval()` - configurable update intervals with visibility-aware pause/resume
- `useCachedFetch()` - fetch with localStorage caching and TTL
- `useT()` / `useTranslation()` - access translation function from context
### Path Aliases (configured in `vite.config.mjs`)
Use these instead of relative imports: `@/`, `components/`, `contexts/`, `hooks/`, `assets/`, `config/`, `features/`, `lib/`, `scss/`, `translations/`, `utils/`, `i18n/`
## Code Rules
### Do not add comments
Keep code self-explanatory. Use descriptive names instead of comments.
### Do not use emojis in code strings
No emojis in console logs, placeholders, or code strings. Exception: user-facing toast notifications may include emojis.
### All user-visible strings must use i18n
Never hardcode user-facing text. Use `const t = useT(); t('widgets.greeting.morning')` or `getMessage('key')`. Console logs don't need i18n.
### Translation workflow
Edit `src/i18n/locales/en_GB.json` first (it's the base file), then run `bun run translations` to sync all other locales.
### Naming conventions
Concise names without redundant prefixes. `const [open, setOpen] = useState(false)` not `const [isOpen, setIsOpen]`. Exception: `is` prefix is fine for boolean-returning functions (`isValid()`), `has` prefix for boolean properties.
### Branch strategy
Three branches: `dev` (all PRs target here) → `beta``main`. Hotfix branches (`hotfix/*`) branch from `main`.
### Conventional commits
Enforced by commitlint: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `test:`, `style:`, `perf:`. Scopes encouraged: `feat(weather): add hourly forecast`.

View File

@@ -1,4 +1,4 @@
FROM oven/bun:1.1.42
FROM oven/bun:latest
WORKDIR /app
@@ -8,4 +8,4 @@ RUN bun install
EXPOSE 5173
CMD ["bun", "run", "dev:host"]
CMD ["bun", "run", "dev:host"]

View File

@@ -1,45 +0,0 @@
# Mue Engineering Standards (GEMINI.md)
This file contains project-specific instructions and guidelines for Mue.
## Stack & Commands
- **Package Manager:** Always use `bun`. Never use `npm` or `yarn`.
- **Framework:** React 19, Vite 7, Manifest V3.
- **Core Commands:**
- `bun install`: Install dependencies.
- `bun run dev`: Start dev server.
- `bun run build`: Production build for Chrome, Firefox, Safari.
- `bun run lint`: Run ESLint and Stylelint.
- `bun run lint:fix`: Auto-fix lint issues.
- `bun run pretty`: Format with Prettier.
- `bun run translations`: Sync locale files from `en_GB.json`.
- `bun run translations:percentages`: Update translation stats.
- `bun run translations:unused`: Find unused translation keys.
- `bun run test:e2e:headless`: Run Cypress tests headlessly.
## Architecture & Patterns
- **State Management:** Use **localStorage** for persistent state.
- **Communication:** Use `src/utils/eventbus.js` for inter-widget/UI communication.
- Register in `useEffect`, cleanup with `EventBus.off()`.
- **I18n:** All user-visible strings MUST use `src/i18n/locales/en_GB.json` as the base.
- Use `useT()` hook or `getMessage()` helper.
- **Routing:** Hash-based router (`createHashRouter`) in `src/router/`.
- **Path Aliases:** Use `@/`, `components/`, `features/`, etc. (see `vite.config.mjs`).
## Coding Rules
- **No Comments:** Keep code self-explanatory through descriptive naming.
- **No Emojis:** Do not use emojis in code strings or logs (except user-facing toasts).
- **Naming:** Concise names. Use `const [open, setOpen] = useState(false)`, not `isOpen`.
- **Commits:** Conventional commits (`feat:`, `fix:`, etc.) are enforced.
- **Translations:** Edit `en_GB.json` first, then run `bun run translations`.
## Directory Structure
- `src/features/`: Feature-based organization (background, time, quote, etc.).
- `src/components/`: Shared UI elements and layouts.
- `src/utils/`: Shared utilities and helpers.
- `manifest/`: Extension manifest templates.
- `safari/`: Safari-specific project files.

362
bun.lock
View File

@@ -1,6 +1,5 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "mue",
@@ -17,14 +16,12 @@
"blurhash": "^2.0.5",
"fast-blurhash": "^1.1.4",
"image-conversion": "^2.1.1",
"maplibre-gl": "^5.24.0",
"react": "^19.2.3",
"react-best-gradient-color-picker": "^3.0.14",
"react-clock": "6.0.0",
"react-dom": "^19.2.3",
"react-icons": "^5.5.0",
"react-modal": "3.16.3",
"react-router": "^7.13.0",
"react-toastify": "11.0.5",
"use-debounce": "^10.1.0",
},
@@ -35,7 +32,6 @@
"@eslint/js": "^9.39.2",
"@vitejs/plugin-react-swc": "^4.2.2",
"adm-zip": "0.5.16",
"cypress": "^15.10.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsx-a11y": "^6.10.2",
@@ -135,10 +131,6 @@
"@csstools/selector-specificity": ["@csstools/selector-specificity@6.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.1.1" } }, "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA=="],
"@cypress/request": ["@cypress/request@3.0.10", "", { "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", "caseless": "~0.12.0", "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~4.0.4", "http-signature": "~1.4.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", "qs": "~6.14.1", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" } }, "sha512-hauBrOdvu08vOsagkZ/Aju5XuiZx6ldsLfByg1htFeldhex+PeMrYauANzFsMJeAA0+dyPLbDoX2OYuvVoLDkQ=="],
"@cypress/xvfb": ["@cypress/xvfb@1.2.4", "", { "dependencies": { "debug": "^3.1.0", "lodash.once": "^4.1.1" } }, "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q=="],
"@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="],
"@dnd-kit/core": ["@dnd-kit/core@6.3.1", "", { "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ=="],
@@ -257,26 +249,6 @@
"@keyv/serialize": ["@keyv/serialize@1.1.1", "", {}, "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA=="],
"@mapbox/jsonlint-lines-primitives": ["@mapbox/jsonlint-lines-primitives@2.0.2", "", {}, "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ=="],
"@mapbox/point-geometry": ["@mapbox/point-geometry@1.1.0", "", {}, "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ=="],
"@mapbox/tiny-sdf": ["@mapbox/tiny-sdf@2.1.0", "", {}, "sha512-uFJhNh36BR4OCuWIEiWaEix9CA2WzT6CAIcqVjWYpnx8+QDtS+oC4QehRrx5cX4mgWs37MmKnwUejeHxVymzNg=="],
"@mapbox/unitbezier": ["@mapbox/unitbezier@0.0.1", "", {}, "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw=="],
"@mapbox/vector-tile": ["@mapbox/vector-tile@2.0.4", "", { "dependencies": { "@mapbox/point-geometry": "~1.1.0", "@types/geojson": "^7946.0.16", "pbf": "^4.0.1" } }, "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg=="],
"@mapbox/whoots-js": ["@mapbox/whoots-js@3.1.0", "", {}, "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q=="],
"@maplibre/geojson-vt": ["@maplibre/geojson-vt@6.1.0", "", { "dependencies": { "kdbush": "^4.0.2" } }, "sha512-2eIY4gZxeKIVOZVNkAMb+5NgXhgsMQpOveTQAvnp53LYqHGJZDidk7Ew0Tged9PThidpbS+NFTh0g4zivhPDzQ=="],
"@maplibre/maplibre-gl-style-spec": ["@maplibre/maplibre-gl-style-spec@24.8.3", "", { "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", "json-stringify-pretty-compact": "^4.0.0", "minimist": "^1.2.8", "quickselect": "^3.0.0", "rw": "^1.3.3", "tinyqueue": "^3.0.0" }, "bin": { "gl-style-migrate": "dist/gl-style-migrate.mjs", "gl-style-validate": "dist/gl-style-validate.mjs", "gl-style-format": "dist/gl-style-format.mjs" } }, "sha512-e5u6GZXA0t6HnBh63YLefSLy62W1QloF4qVLAo1ae88oH/8+PNK98i7/aVf/XWc4wD9D2lTgW2LaApYLPtM1sA=="],
"@maplibre/mlt": ["@maplibre/mlt@1.1.9", "", { "dependencies": { "@mapbox/point-geometry": "^1.1.0" } }, "sha512-g/tD8EYJB97udq33ipuJ9a4Q7fcbZnTEnUrgnEc/tLMmEL+zaCbR+X5fkDBO2dgpaAMsLH179qE3UXg2N0Nc/g=="],
"@maplibre/vt-pbf": ["@maplibre/vt-pbf@4.3.0", "", { "dependencies": { "@mapbox/point-geometry": "^1.1.0", "@mapbox/vector-tile": "^2.0.4", "@maplibre/geojson-vt": "^5.0.4", "@types/geojson": "^7946.0.16", "@types/supercluster": "^7.1.3", "pbf": "^4.0.1", "supercluster": "^8.0.1" } }, "sha512-jIvp8F5hQCcreqOOpEt42TJMUlsrEcpf/kI1T2v85YrQRV6PPXUcEXUg5karKtH6oh47XJZ4kHu56pUkOuqA7w=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
@@ -409,22 +381,10 @@
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@10.17.60", "", {}, "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw=="],
"@types/sinonjs__fake-timers": ["@types/sinonjs__fake-timers@8.1.1", "", {}, "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g=="],
"@types/sizzle": ["@types/sizzle@2.3.10", "", {}, "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww=="],
"@types/supercluster": ["@types/supercluster@7.1.3", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA=="],
"@types/tmp": ["@types/tmp@0.2.6", "", {}, "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA=="],
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
"@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@4.2.2", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.47", "@swc/core": "^1.13.5" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7" } }, "sha512-x+rE6tsxq/gxrEJN3Nv3dIV60lFflPj94c90b+NNo6n1QV1QQUTLoL0MpaOVasUZ0zqVBn7ead1B5ecx1JAGfA=="],
"@wojtekmaj/date-utils": ["@wojtekmaj/date-utils@2.0.2", "", {}, "sha512-Do66mSlSNifFFuo3l9gNKfRMSFi26CRuQMsDJuuKO/ekrDWuTTtE4ZQxoFCUOG+NgxnpSeBq/k5TY8ZseEzLpA=="],
@@ -437,20 +397,12 @@
"adm-zip": ["adm-zip@0.5.16", "", {}, "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ=="],
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"arch": ["arch@2.2.0", "", {}, "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
@@ -471,26 +423,14 @@
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
"asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
"assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="],
"ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="],
"astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"aws-sign2": ["aws-sign2@0.7.0", "", {}, "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="],
"aws4": ["aws4@1.13.2", "", {}, "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="],
"axe-core": ["axe-core@4.11.1", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
@@ -499,16 +439,8 @@
"base64-arraybuffer": ["base64-arraybuffer@1.0.2", "", {}, "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.18", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA=="],
"bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
"blob-util": ["blob-util@2.0.2", "", {}, "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ=="],
"bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
"blurhash": ["blurhash@2.0.5", "", {}, "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w=="],
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
@@ -517,14 +449,8 @@
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"cacheable": ["cacheable@2.3.2", "", { "dependencies": { "@cacheable/memory": "^2.0.7", "@cacheable/utils": "^2.3.3", "hookified": "^1.15.0", "keyv": "^5.5.5", "qified": "^0.6.0" } }, "sha512-w+ZuRNmex9c1TR9RcsxbfTKCjSL0rh1WA5SABbrWprIHeNBdmyQLSYonlDy9gpD+63XT8DgZ/wNh1Smvc9WnJA=="],
"cachedir": ["cachedir@2.4.0", "", {}, "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
@@ -535,22 +461,10 @@
"caniuse-lite": ["caniuse-lite@1.0.30001766", "", {}, "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA=="],
"caseless": ["caseless@0.12.0", "", {}, "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="],
"clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
"cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
"cli-table3": ["cli-table3@0.6.1", "", { "dependencies": { "string-width": "^4.2.0" }, "optionalDependencies": { "colors": "1.4.0" } }, "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA=="],
"cli-truncate": ["cli-truncate@2.1.0", "", { "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" } }, "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg=="],
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
@@ -561,16 +475,6 @@
"colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="],
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
"colors": ["colors@1.4.0", "", {}, "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="],
"common-tags": ["common-tags@1.8.2", "", {}, "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="],
"compare-func": ["compare-func@2.0.0", "", { "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" } }, "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
@@ -583,10 +487,6 @@
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
"core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="],
"cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="],
"cosmiconfig-typescript-loader": ["cosmiconfig-typescript-loader@6.2.0", "", { "dependencies": { "jiti": "^2.6.1" }, "peerDependencies": { "@types/node": "*", "cosmiconfig": ">=9", "typescript": ">=5" } }, "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ=="],
@@ -601,22 +501,16 @@
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"cypress": ["cypress@15.10.0", "", { "dependencies": { "@cypress/request": "^3.0.10", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "@types/tmp": "^0.2.3", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "ci-info": "^4.1.0", "cli-cursor": "^3.1.0", "cli-table3": "0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", "figures": "^3.2.0", "fs-extra": "^9.1.0", "hasha": "5.2.2", "is-installed-globally": "~0.4.0", "listr2": "^3.8.3", "lodash": "^4.17.23", "log-symbols": "^4.0.0", "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "supports-color": "^8.1.1", "systeminformation": "^5.27.14", "tmp": "~0.2.4", "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, "bin": { "cypress": "bin/cypress" } }, "sha512-OtUh7OMrfEjKoXydlAD1CfG2BvKxIqgWGY4/RMjrqQ3BKGBo5JFKoYNH+Tpcj4xKxWH4XK0Xri+9y8WkxhYbqQ=="],
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
"dargs": ["dargs@8.1.0", "", {}, "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw=="],
"dashdash": ["dashdash@1.14.1", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g=="],
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
"dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
@@ -625,8 +519,6 @@
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
@@ -635,18 +527,10 @@
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"earcut": ["earcut@3.0.2", "", {}, "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ=="],
"ecc-jsbn": ["ecc-jsbn@0.1.2", "", { "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw=="],
"electron-to-chromium": ["electron-to-chromium@1.5.279", "", {}, "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg=="],
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
@@ -697,20 +581,8 @@
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"eventemitter2": ["eventemitter2@6.4.7", "", {}, "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg=="],
"execa": ["execa@4.1.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", "human-signals": "^1.1.1", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } }, "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA=="],
"executable": ["executable@4.1.1", "", { "dependencies": { "pify": "^2.2.0" } }, "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg=="],
"exenv": ["exenv@1.2.2", "", {}, "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="],
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
"extsprintf": ["extsprintf@1.3.0", "", {}, "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="],
"fast-blurhash": ["fast-blurhash@1.1.4", "", {}, "sha512-xeH121M027hgWHHhHWYYjUmMKl8vCH3PPkXk439ixsP8Bvb/r3UFqg12oMSToD/aSAw8EE6XiTdfZ6M5jaLfzg=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
@@ -727,12 +599,8 @@
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
@@ -745,12 +613,6 @@
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"forever-agent": ["forever-agent@0.6.1", "", {}, "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -771,24 +633,16 @@
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
"get-user-locale": ["get-user-locale@3.0.0", "", { "dependencies": { "memoize": "^10.0.0" } }, "sha512-iJfHSmdYV39UUBw7Jq6GJzeJxUr4U+S03qdhVuDsR9gCEnfbqLy9gYDJFBJQL1riqolFUKQvx36mEkp2iGgJ3g=="],
"getpass": ["getpass@0.1.7", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng=="],
"git-raw-commits": ["git-raw-commits@4.0.0", "", { "dependencies": { "dargs": "^8.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "git-raw-commits": "cli.mjs" } }, "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ=="],
"gl-matrix": ["gl-matrix@3.4.4", "", {}, "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"global-directory": ["global-directory@4.0.1", "", { "dependencies": { "ini": "4.1.1" } }, "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q=="],
"global-dirs": ["global-dirs@3.0.1", "", { "dependencies": { "ini": "2.0.0" } }, "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA=="],
"global-modules": ["global-modules@2.0.0", "", { "dependencies": { "global-prefix": "^3.0.0" } }, "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A=="],
"global-prefix": ["global-prefix@3.0.0", "", { "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", "which": "^1.3.1" } }, "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg=="],
@@ -803,11 +657,9 @@
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"has-flag": ["has-flag@5.0.1", "", {}, "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA=="],
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
@@ -817,8 +669,6 @@
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hasha": ["hasha@5.2.2", "", { "dependencies": { "is-stream": "^2.0.0", "type-fest": "^0.8.0" } }, "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ=="],
"hashery": ["hashery@1.4.0", "", { "dependencies": { "hookified": "^1.14.0" } }, "sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
@@ -833,14 +683,8 @@
"html2canvas": ["html2canvas@1.4.1", "", { "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" } }, "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA=="],
"http-signature": ["http-signature@1.4.0", "", { "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", "sshpk": "^1.18.0" } }, "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg=="],
"human-signals": ["human-signals@1.1.1", "", {}, "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="],
"husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"image-conversion": ["image-conversion@2.1.1", "", {}, "sha512-hnMOmP7q2jxA+52FZ+wHNhg3fdFRlgfngsQH2JQHEQkafY7tj/8F15e6Rv/RxDegc872jvyaRHwMbkTZK1Cjbg=="],
@@ -853,9 +697,7 @@
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
"ini": ["ini@2.0.0", "", {}, "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA=="],
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
@@ -887,8 +729,6 @@
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-installed-globally": ["is-installed-globally@0.4.0", "", { "dependencies": { "global-dirs": "^3.0.0", "is-path-inside": "^3.0.2" } }, "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ=="],
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
@@ -899,7 +739,7 @@
"is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="],
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
"is-path-inside": ["is-path-inside@4.0.0", "", {}, "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA=="],
"is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="],
@@ -909,8 +749,6 @@
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
"is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
@@ -919,10 +757,6 @@
"is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
"is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="],
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
"is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
@@ -933,8 +767,6 @@
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"isstream": ["isstream@0.1.2", "", {}, "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="],
"iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@@ -943,36 +775,22 @@
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"jsbn": ["jsbn@0.1.1", "", {}, "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"json-stringify-pretty-compact": ["json-stringify-pretty-compact@4.0.0", "", {}, "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q=="],
"json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
"jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="],
"jsprim": ["jsprim@2.0.2", "", { "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" } }, "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ=="],
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
"kdbush": ["kdbush@4.0.2", "", {}, "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
@@ -987,12 +805,8 @@
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"listr2": ["listr2@3.14.0", "", { "dependencies": { "cli-truncate": "^2.1.0", "colorette": "^2.0.16", "log-update": "^4.0.0", "p-map": "^4.0.0", "rfdc": "^1.3.0", "rxjs": "^7.5.1", "through": "^2.3.8", "wrap-ansi": "^7.0.0" }, "peerDependencies": { "enquirer": ">= 2.3.0 < 3" }, "optionalPeers": ["enquirer"] }, "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
"lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
"lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
@@ -1003,8 +817,6 @@
"lodash.mergewith": ["lodash.mergewith@4.6.2", "", {}, "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="],
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
"lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="],
"lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="],
@@ -1017,16 +829,10 @@
"lodash.upperfirst": ["lodash.upperfirst@4.3.1", "", {}, "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg=="],
"log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
"log-update": ["log-update@4.0.0", "", { "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", "slice-ansi": "^4.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"maplibre-gl": ["maplibre-gl@5.24.0", "", { "dependencies": { "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/point-geometry": "^1.1.0", "@mapbox/tiny-sdf": "^2.1.0", "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^2.0.4", "@mapbox/whoots-js": "^3.1.0", "@maplibre/geojson-vt": "^6.1.0", "@maplibre/maplibre-gl-style-spec": "^24.8.1", "@maplibre/mlt": "^1.1.8", "@maplibre/vt-pbf": "^4.3.0", "@types/geojson": "^7946.0.16", "earcut": "^3.0.2", "gl-matrix": "^3.4.4", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", "pbf": "^4.0.1", "potpack": "^2.1.0", "quickselect": "^3.0.0", "tinyqueue": "^3.0.0" } }, "sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"mathml-tag-names": ["mathml-tag-names@4.0.0", "", {}, "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ=="],
@@ -1037,18 +843,10 @@
"meow": ["meow@14.0.0", "", {}, "sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA=="],
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
@@ -1057,8 +855,6 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"murmurhash-js": ["murmurhash-js@1.0.0", "", {}, "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
@@ -1069,8 +865,6 @@
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -1085,22 +879,14 @@
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"ospath": ["ospath@1.2.2", "", {}, "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA=="],
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
@@ -1111,18 +897,10 @@
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"pbf": ["pbf@4.0.1", "", { "dependencies": { "resolve-protobuf-schema": "^2.1.0" }, "bin": { "pbf": "bin/pbf" } }, "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA=="],
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"performance-now": ["performance-now@2.1.0", "", {}, "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
@@ -1139,36 +917,20 @@
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"potpack": ["potpack@2.1.0", "", {}, "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
"pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="],
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"protocol-buffers-schema": ["protocol-buffers-schema@3.6.1", "", {}, "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ=="],
"proxy-from-env": ["proxy-from-env@1.0.0", "", {}, "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A=="],
"pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"qified": ["qified@0.6.0", "", { "dependencies": { "hookified": "^1.14.0" } }, "sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA=="],
"qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"quickselect": ["quickselect@3.0.0", "", {}, "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="],
"rd": ["rd@2.0.1", "", { "dependencies": { "@types/node": "^10.3.6" } }, "sha512-/XdKU4UazUZTXFmI0dpABt8jSXPWcEyaGdk340KdHnsEOdkTctlX23aAK7ChQDn39YGNlAJr1M5uvaKt4QnpNw=="],
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
@@ -1187,8 +949,6 @@
"react-modal": ["react-modal@3.16.3", "", { "dependencies": { "exenv": "^1.2.0", "prop-types": "^15.7.2", "react-lifecycles-compat": "^3.0.0", "warning": "^4.0.3" }, "peerDependencies": { "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19", "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19" } }, "sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw=="],
"react-router": ["react-router@7.13.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw=="],
"react-toastify": ["react-toastify@11.0.5", "", { "dependencies": { "clsx": "^2.1.1" }, "peerDependencies": { "react": "^18 || ^19", "react-dom": "^18 || ^19" } }, "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
@@ -1197,8 +957,6 @@
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
"request-progress": ["request-progress@3.0.0", "", { "dependencies": { "throttleit": "^1.0.0" } }, "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
@@ -1207,40 +965,24 @@
"resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
"resolve-protobuf-schema": ["resolve-protobuf-schema@2.1.0", "", { "dependencies": { "protocol-buffers-schema": "^3.3.1" } }, "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ=="],
"restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
"rollup": ["rollup@4.57.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.0", "@rollup/rollup-android-arm64": "4.57.0", "@rollup/rollup-darwin-arm64": "4.57.0", "@rollup/rollup-darwin-x64": "4.57.0", "@rollup/rollup-freebsd-arm64": "4.57.0", "@rollup/rollup-freebsd-x64": "4.57.0", "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", "@rollup/rollup-linux-arm-musleabihf": "4.57.0", "@rollup/rollup-linux-arm64-gnu": "4.57.0", "@rollup/rollup-linux-arm64-musl": "4.57.0", "@rollup/rollup-linux-loong64-gnu": "4.57.0", "@rollup/rollup-linux-loong64-musl": "4.57.0", "@rollup/rollup-linux-ppc64-gnu": "4.57.0", "@rollup/rollup-linux-ppc64-musl": "4.57.0", "@rollup/rollup-linux-riscv64-gnu": "4.57.0", "@rollup/rollup-linux-riscv64-musl": "4.57.0", "@rollup/rollup-linux-s390x-gnu": "4.57.0", "@rollup/rollup-linux-x64-gnu": "4.57.0", "@rollup/rollup-linux-x64-musl": "4.57.0", "@rollup/rollup-openbsd-x64": "4.57.0", "@rollup/rollup-openharmony-arm64": "4.57.0", "@rollup/rollup-win32-arm64-msvc": "4.57.0", "@rollup/rollup-win32-ia32-msvc": "4.57.0", "@rollup/rollup-win32-x64-gnu": "4.57.0", "@rollup/rollup-win32-x64-msvc": "4.57.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"sass": ["sass@1.97.3", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
@@ -1259,7 +1001,7 @@
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
@@ -1269,8 +1011,6 @@
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"sshpk": ["sshpk@1.18.0", "", { "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, "bin": { "sshpk-conv": "bin/sshpk-conv", "sshpk-sign": "bin/sshpk-sign", "sshpk-verify": "bin/sshpk-verify" } }, "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ=="],
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
"string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="],
@@ -1287,9 +1027,7 @@
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
@@ -1305,9 +1043,7 @@
"stylelint-scss": ["stylelint-scss@7.0.0", "", { "dependencies": { "css-tree": "^3.0.1", "is-plain-object": "^5.0.0", "known-css-properties": "^0.37.0", "mdn-data": "^2.25.0", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.6", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "stylelint": "^16.8.2 || ^17.0.0" } }, "sha512-H88kCC+6Vtzj76NsC8rv6x/LW8slBzIbyeSjsKVlS+4qaEJoDrcJR4L+8JdrR2ORdTscrBzYWiiT2jq6leYR1Q=="],
"supercluster": ["supercluster@8.0.1", "", { "dependencies": { "kdbush": "^4.0.2" } }, "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ=="],
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"supports-hyperlinks": ["supports-hyperlinks@4.4.0", "", { "dependencies": { "has-flag": "^5.0.1", "supports-color": "^10.2.2" } }, "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg=="],
@@ -1315,16 +1051,12 @@
"svg-tags": ["svg-tags@1.0.0", "", {}, "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA=="],
"systeminformation": ["systeminformation@5.30.7", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-33B/cftpaWdpvH+Ho9U1b08ss8GQuLxrWHelbJT1yw4M48Taj8W3ezcPuaLoIHZz5V6tVHuQPr5BprEfnBLBMw=="],
"table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="],
"text-extensions": ["text-extensions@2.4.0", "", {}, "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g=="],
"text-segmentation": ["text-segmentation@1.0.3", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw=="],
"throttleit": ["throttleit@1.0.1", "", {}, "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ=="],
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
"tinycolor2": ["tinycolor2@1.4.2", "", {}, "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA=="],
@@ -1333,30 +1065,12 @@
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"tinyqueue": ["tinyqueue@3.0.0", "", {}, "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="],
"tldts": ["tldts@6.1.86", "", { "dependencies": { "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ=="],
"tldts-core": ["tldts-core@6.1.86", "", {}, "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA=="],
"tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"tough-cookie": ["tough-cookie@5.1.2", "", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="],
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"type-fest": ["type-fest@0.8.1", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="],
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
"typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
@@ -1373,10 +1087,6 @@
"unicorn-magic": ["unicorn-magic@0.4.0", "", {}, "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"untildify": ["untildify@4.0.0", "", {}, "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw=="],
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
@@ -1387,10 +1097,6 @@
"utrie": ["utrie@1.0.2", "", { "dependencies": { "base64-arraybuffer": "^1.0.2" } }, "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw=="],
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"verror": ["verror@1.10.0", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw=="],
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"vite-plugin-progress": ["vite-plugin-progress@0.0.7", "", { "dependencies": { "picocolors": "^1.0.0", "progress": "^2.0.3", "rd": "^2.0.1" }, "peerDependencies": { "vite": ">2.0.0-0" } }, "sha512-zyvKdcc/X+6hnw3J1HVV1TKrlFKC4Rh8GnDnWG/2qhRXjqytTcM++xZ+SAPnoDsSyWl8O93ymK0wZRgHAoglEQ=="],
@@ -1411,8 +1117,6 @@
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"write-file-atomic": ["write-file-atomic@7.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
@@ -1423,8 +1127,6 @@
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
@@ -1447,67 +1149,43 @@
"@commitlint/types/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"@cypress/xvfb/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@keyv/bigmap/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
"@maplibre/vt-pbf/@maplibre/geojson-vt": ["@maplibre/geojson-vt@5.0.4", "", {}, "sha512-KGg9sma45S+stfH9vPCJk1J0lSDLWZgCT9Y8u8qWZJyjFlP8MNP1WGTxIMYJZjDvVT3PDn05kN1C95Sut1HpgQ=="],
"@types/conventional-commits-parser/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
"@types/yauzl/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
"brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"cacheable/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"cli-table3/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"cli-truncate/slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="],
"cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"conventional-commits-parser/meow": ["meow@12.1.1", "", {}, "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw=="],
"css-tree/mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"figures/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
"git-raw-commits/meow": ["meow@12.1.1", "", {}, "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw=="],
"global-directory/ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="],
"global-prefix/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"global-prefix/which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="],
"globby/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"globby/is-path-inside": ["is-path-inside@4.0.0", "", {}, "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA=="],
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"log-update/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"stylelint/file-entry-cache": ["file-entry-cache@11.1.2", "", { "dependencies": { "flat-cache": "^6.1.20" } }, "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log=="],
"stylelint/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"supports-hyperlinks/has-flag": ["has-flag@5.0.1", "", {}, "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA=="],
"supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"supports-hyperlinks/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
@@ -1515,9 +1193,11 @@
"table/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"table/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"write-file-atomic/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -1529,15 +1209,9 @@
"@commitlint/top-level/find-up/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="],
"cli-table3/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"cli-truncate/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"log-update/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"stylelint/file-entry-cache/flat-cache": ["flat-cache@6.1.20", "", { "dependencies": { "cacheable": "^2.3.2", "flatted": "^3.3.3", "hookified": "^1.15.0" } }, "sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ=="],
@@ -1545,13 +1219,19 @@
"table/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"table/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@commitlint/top-level/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="],
"log-update/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@commitlint/top-level/find-up/locate-path/p-locate/p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="],

View File

@@ -1,12 +0,0 @@
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
baseUrl: "http://localhost:5173",
viewportWidth: 1920,
viewportHeight: 1080,
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});

View File

@@ -1,131 +0,0 @@
describe('Basic Features', () => {
beforeEach(() => {
// Bypass onboarding and enable features
cy.visit('/', {
onBeforeLoad: (win) => {
win.localStorage.clear();
win.localStorage.setItem('firstRun', 'true');
win.localStorage.setItem('stats', 'true');
win.localStorage.setItem('navbarHover', 'false');
win.localStorage.setItem('notesEnabled', 'true');
win.localStorage.setItem('searchBar', 'true');
win.localStorage.setItem('voiceSearch', 'false');
win.localStorage.setItem('language', 'en_GB');
win.localStorage.setItem('theme', 'dark');
win.localStorage.setItem('widgetStyle', 'new');
win.localStorage.setItem('showWelcome', 'false');
win.localStorage.setItem('view', 'true'); // Enable Maximise button
win.localStorage.setItem('background', 'true'); // Enable Background for Maximise button
win.localStorage.setItem('photoInformation', 'true'); // Enable Photo Information
win.localStorage.setItem('backgroundType', 'api'); // Required for backgroundLoader
win.localStorage.setItem('quoteType', 'custom');
win.localStorage.setItem('customQuote', 'Test Quote');
win.localStorage.setItem('customQuoteAuthor', 'Test Author');
// Seed other widgets
win.localStorage.setItem('greeting', 'true');
win.localStorage.setItem('time', 'true');
win.localStorage.setItem('date', 'true');
// Seed Quick Links with data so it renders with height
win.localStorage.setItem('quicklinks', JSON.stringify([
{ name: "Test Link", url: "https://example.com", key: "1" }
]));
win.localStorage.setItem('quicklinksenabled', 'true');
win.localStorage.setItem('message', 'true');
win.localStorage.setItem('messages', '["Test Message"]');
win.localStorage.setItem('order', JSON.stringify(["greeting", "time", "quicklinks", "quote", "date", "message"]));
}
});
// Mock Background API
cy.intercept('GET', '**/images/random*', { fixture: 'background.json' }).as('getBackground');
});
it('should perform search', () => {
const searchText = 'Cypress Test Search';
cy.get('#searchtext').should('exist');
cy.get('#searchtext').type(searchText);
cy.get('#searchtext').should('have.value', searchText);
});
it('should use Notes feature', () => {
cy.get('.notes .navbarButton').click();
const noteText = 'This is a test note';
cy.get('.notesContainer textarea').should('be.visible');
cy.get('.notesContainer textarea').clear().type(noteText);
cy.reload();
cy.get('.notes .navbarButton').click();
cy.get('.notesContainer textarea').should('have.value', noteText);
});
it('should maximise and unmaximise widgets', () => {
cy.get('#widgets').should('not.have.css', 'display', 'none');
cy.get('button[aria-label="Maximise"]').click();
cy.get('#widgets').should('have.css', 'display', 'none');
cy.get('button[aria-label="Maximise"]').click();
cy.get('#widgets').should('not.have.css', 'display', 'none');
});
it('should display photo information and background actions', () => {
cy.wait('@getBackground');
cy.get('.photoInformation', { timeout: 10000 }).should('exist').and('be.visible');
cy.get('.photoInformation').trigger('mouseover');
// Check primary content
cy.get('.photoInformation .primary-content').should('exist');
// Check Action Buttons
cy.get('.photoInformation .buttons').should('exist');
cy.get('.photoInformation .buttons svg').should('have.length.at.least', 3);
// Test Favourite Interaction
cy.get('.photoInformation .buttons > .tooltip').eq(1).click({ force: true });
});
it('should display quote link', () => {
// Reload with offlineMode to ensure we get a guaranteed offline quote
cy.visit('/', {
onBeforeLoad: (win) => {
win.localStorage.clear();
win.localStorage.setItem('firstRun', 'true');
win.localStorage.setItem('showWelcome', 'false');
win.localStorage.setItem('widgetStyle', 'new');
win.localStorage.setItem('offlineMode', 'true');
win.localStorage.setItem('order', JSON.stringify(["quote"]));
}
});
cy.get('.quotediv').should('exist');
cy.get('.quotediv .quote').should('not.be.empty');
cy.get('.quotediv a').should('exist').and('have.attr', 'href').and('include', 'wikipedia');
});
it('should have a refresh button', () => {
cy.get('button[aria-label="Refresh"]').should('exist').and('be.visible');
});
it('should display other dashboard widgets', () => {
// Verify Greeting
cy.get('.greeting').should('exist').and('be.visible');
// Verify Time (Clock)
// Checks for either generic clock class or clock container
cy.get('.clock-container').should('exist').and('be.visible');
// Verify Date
cy.get('.date').should('exist').and('be.visible');
// Verify Quick Links
// Checks for the container
cy.get('.quicklinkscontainer').should('exist').and('be.visible');
// Ensure it has content (the link we seeded)
cy.get('.quicklinkscontainer a').should('have.length.at.least', 1);
// Verify Message
cy.get('.message').should('exist').and('be.visible').and('contain', 'Test Message');
});
});

View File

@@ -1,48 +0,0 @@
describe('Welcome Modal Flow', () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/');
});
it('should complete the full onboarding flow', () => {
// Step 1: Intro
cy.contains('Welcome to Mue Tab', { timeout: 10000 }).should('be.visible');
cy.contains('Next').click();
// Step 2: Language
cy.contains('Choose your language').should('be.visible');
cy.contains('Next').click();
// Step 3: Import Settings
cy.get('.upload').should('be.visible');
cy.contains('Next').click();
// Step 4: Theme
cy.contains('Select a theme').should('be.visible');
cy.contains('Dark').click();
cy.get('body').should('have.class', 'dark');
cy.contains('Light').click();
cy.get('body').should('not.have.class', 'dark');
cy.contains('Auto').click();
cy.contains('Dark').click();
cy.contains('Next').click();
// Step 5: Style
cy.contains('Choose a style').should('be.visible');
cy.contains('Modern').click();
cy.contains('Next').click();
// Step 6: Privacy
cy.contains('Privacy Options').should('be.visible');
cy.contains('Next').click();
// Step 7: Final
cy.contains('Final step').should('be.visible');
cy.contains('Finish').click();
// Verify Dashboard
cy.get('.greeting').should('be.visible');
});
});

View File

@@ -1,18 +0,0 @@
{
"file": "https://images.unsplash.com/photo-1472214103451-9374bd1c798e",
"location": {
"name": "Loch Lomond, United Kingdom",
"latitude": 56.2466465,
"longitude": -4.7208886
},
"photographer": "Adam Jang",
"photographer_page": "https://unsplash.com/@adamjang",
"camera": "Canon, EOS 70D",
"views": 1500000,
"downloads": 10000,
"likes": 500,
"category": "nature",
"colour": "#262626",
"blur_hash": "L25#t70000?b00?b?bof00D%?bof",
"pun": "test_unique_id"
}

View File

@@ -1,9 +0,0 @@
[
{
"quote": "The only way to do great work is to love what you do.",
"author": "Steve Jobs",
"category": "inspirational",
"language": "en",
"authorlink": "https://en.wikipedia.org/wiki/Steve_Jobs"
}
]

View File

@@ -1,24 +0,0 @@
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

View File

@@ -1,19 +0,0 @@
// This example e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@@ -5,6 +5,7 @@ import jsxA11y from 'eslint-plugin-jsx-a11y';
import prettier from 'eslint-config-prettier';
export default [
// Ignore patterns
{
ignores: [
'**/node_modules/**',
@@ -17,6 +18,7 @@ export default [
],
},
// Base config for all JS/JSX files
{
files: ['**/*.{js,jsx,mjs}'],
languageOptions: {
@@ -24,6 +26,7 @@ export default [
sourceType: 'module',
parserOptions: { ecmaFeatures: { jsx: true } },
globals: {
// Browser globals
window: 'readonly',
document: 'readonly',
navigator: 'readonly',
@@ -45,6 +48,7 @@ export default [
AbortController: 'readonly',
btoa: 'readonly',
atob: 'readonly',
// Node globals for scripts
process: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
@@ -60,17 +64,21 @@ export default [
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'react/jsx-uses-react': 'off',
// React specific rules
'react/prop-types': 'off', // Using PropTypes is optional
'react/react-in-jsx-scope': 'off', // Not needed with React 17+
'react/jsx-uses-react': 'off', // Not needed with React 17+
// General rules
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'no-console': ['warn', { allow: ['warn', 'error'] }],
// Modern JS
'prefer-const': 'warn',
'no-var': 'error',
},
},
// Prettier config (must be last to override other formatting rules)
prettier,
];

View File

@@ -12,7 +12,6 @@
"@/*": ["./*"],
"i18n/*": ["./i18n/*"],
"components/*": ["./components/*"],
"hooks/*": ["./hooks/*"],
"assets/*": ["./assets/*"],
"config/*": ["./config/*"],
"features/*": ["./features/*"],

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -1,8 +0,0 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -4,9 +4,9 @@
"default_locale": "en",
"name": "__MSG_name__",
"description": "__MSG_description__",
"version": "7.6.1",
"version": "7.6.0",
"homepage_url": "https://muetab.com",
"permissions": ["search", "bookmarks"],
"permissions": ["search"],
"action": {
"default_icon": "icons/128x128.png"
},

View File

@@ -2,9 +2,8 @@
"manifest_version": 3,
"name": "Mue",
"description": "Fast, open and free-to-use new tab page for modern browsers.",
"version": "7.6.1",
"version": "7.6.0",
"homepage_url": "https://muetab.com",
"permissions": ["bookmarks"],
"action": {
"default_icon": "icons/128x128.png"
},

View File

@@ -9,7 +9,7 @@
"homepage": "https://muetab.com",
"bugs": "https://github.com/mue/mue/issues/new?assignees=&labels=bug&template=bug-report.md&title=%5BBUG%5D",
"license": "BSD-3-Clause",
"version": "7.6.1",
"version": "7.6.0",
"type": "module",
"packageManager": "bun@1.3.1",
"engines": {
@@ -29,14 +29,12 @@
"blurhash": "^2.0.5",
"fast-blurhash": "^1.1.4",
"image-conversion": "^2.1.1",
"maplibre-gl": "^5.24.0",
"react": "^19.2.3",
"react-best-gradient-color-picker": "^3.0.14",
"react-clock": "6.0.0",
"react-dom": "^19.2.3",
"react-icons": "^5.5.0",
"react-modal": "3.16.3",
"react-router": "^7.13.0",
"react-toastify": "11.0.5",
"use-debounce": "^10.1.0"
},
@@ -47,7 +45,6 @@
"@eslint/js": "^9.39.2",
"@vitejs/plugin-react-swc": "^4.2.2",
"adm-zip": "0.5.16",
"cypress": "^15.10.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsx-a11y": "^6.10.2",
@@ -65,11 +62,8 @@
"scripts": {
"dev": "vite",
"dev:host": "vite --host",
"test:e2e": "cypress open",
"test:e2e:headless": "cypress run",
"translations": "cd scripts && node updatetranslations.cjs",
"translations:percentages": "node scripts/updateTranslationPercentages.cjs",
"translations:unused": "node scripts/findUnusedTranslations.cjs",
"build": "vite build",
"pretty": "prettier --write \"./**/*.{js,jsx,json,scss,css}\"",
"lint": "eslint \"./src/**/*.{js,jsx}\" && stylelint \"./src/**/*.{scss,css}\"",
@@ -77,4 +71,4 @@
"postinstall": "husky",
"prepare": "husky"
}
}
}

View File

@@ -4,7 +4,7 @@
"default_locale": "en",
"name": "__MSG_name__",
"description": "__MSG_description__",
"version": "7.6.1",
"version": "7.6.0",
"homepage_url": "https://muetab.com",
"permissions": ["search"],
"chrome_url_overrides": {

View File

@@ -27,12 +27,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
message = request?.userInfo?["message"]
}
os_log(
.default,
"Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)",
String(describing: message),
profile?.uuidString ?? "none"
)
os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", String(describing: message), profile?.uuidString ?? "none")
let response = NSExtensionItem()
if #available(iOS 15.0, macOS 11.0, *) {

View File

@@ -255,7 +255,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 7.6.1;
MARKETING_VERSION = 7.6.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -289,7 +289,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 7.6.1;
MARKETING_VERSION = 7.6.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -445,7 +445,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 7.6.1;
MARKETING_VERSION = 7.6.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -486,7 +486,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 7.6.1;
MARKETING_VERSION = 7.6.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,

View File

@@ -1,11 +1,11 @@
{
"colors": [
"colors" : [
{
"idiom": "universal"
"idiom" : "universal"
}
],
"info": {
"author": "xcode",
"version": 1
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,68 +1,68 @@
{
"images": [
"images" : [
{
"filename": "icon_16x16.png",
"idiom": "mac",
"scale": "1x",
"size": "16x16"
"filename" : "icon_16x16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename": "icon_16x16@2x.png",
"idiom": "mac",
"scale": "2x",
"size": "16x16"
"filename" : "icon_16x16@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename": "icon_32x32.png",
"idiom": "mac",
"scale": "1x",
"size": "32x32"
"filename" : "icon_32x32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename": "icon_32x32@2x.png",
"idiom": "mac",
"scale": "2x",
"size": "32x32"
"filename" : "icon_32x32@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename": "icon_128x128.png",
"idiom": "mac",
"scale": "1x",
"size": "128x128"
"filename" : "icon_128x128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename": "icon_128x128@2x.png",
"idiom": "mac",
"scale": "2x",
"size": "128x128"
"filename" : "icon_128x128@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename": "icon_256x256.png",
"idiom": "mac",
"scale": "1x",
"size": "256x256"
"filename" : "icon_256x256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename": "icon_256x256@2x.png",
"idiom": "mac",
"scale": "2x",
"size": "256x256"
"filename" : "icon_256x256@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename": "icon_512x512.png",
"idiom": "mac",
"scale": "1x",
"size": "512x512"
"filename" : "icon_512x512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename": "icon_512x512@2x.png",
"idiom": "mac",
"scale": "2x",
"size": "512x512"
"filename" : "icon_512x512@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info": {
"author": "xcode",
"version": 1
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,6 +1,6 @@
{
"info": {
"author": "xcode",
"version": 1
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,20 +1,20 @@
{
"images": [
"images" : [
{
"idiom": "universal",
"scale": "1x"
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom": "universal",
"scale": "2x"
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom": "universal",
"scale": "3x"
"idiom" : "universal",
"scale" : "3x"
}
],
"info": {
"author": "xcode",
"version": 1
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,25 +1,22 @@
function show(enabled, useSettingsInsteadOfPreferences) {
if (useSettingsInsteadOfPreferences) {
document.getElementsByClassName('state-on')[0].innerText =
'Extension is enabled and ready to use!';
document.getElementsByClassName('state-off')[0].innerText =
'Extension is disabled. Enable it in Safari Settings.';
document.getElementsByClassName('state-unknown')[0].innerText =
'Enable Mue in Safari Settings to get started.';
document.getElementsByClassName('open-preferences')[0].innerText = 'Open Safari Settings';
}
if (useSettingsInsteadOfPreferences) {
document.getElementsByClassName('state-on')[0].innerText = "Extension is enabled and ready to use!";
document.getElementsByClassName('state-off')[0].innerText = "Extension is disabled. Enable it in Safari Settings.";
document.getElementsByClassName('state-unknown')[0].innerText = "Enable Mue in Safari Settings to get started.";
document.getElementsByClassName('open-preferences')[0].innerText = "Open Safari Settings";
}
if (typeof enabled === 'boolean') {
document.body.classList.toggle(`state-on`, enabled);
document.body.classList.toggle(`state-off`, !enabled);
} else {
document.body.classList.remove(`state-on`);
document.body.classList.remove(`state-off`);
}
if (typeof enabled === "boolean") {
document.body.classList.toggle(`state-on`, enabled);
document.body.classList.toggle(`state-off`, !enabled);
} else {
document.body.classList.remove(`state-on`);
document.body.classList.remove(`state-off`);
}
}
function openPreferences() {
webkit.messageHandlers.controller.postMessage('open-preferences');
webkit.messageHandlers.controller.postMessage("open-preferences");
}
document.querySelector('button.open-preferences').addEventListener('click', openPreferences);
document.querySelector("button.open-preferences").addEventListener("click", openPreferences);

View File

@@ -1,133 +1,124 @@
* {
-webkit-user-select: none;
-webkit-user-drag: none;
cursor: default;
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-user-select: none;
-webkit-user-drag: none;
cursor: default;
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--spacing: 20px;
--text-color: #fff;
--shadow: 0 4px 20px rgb(0 0 0 / 30%);
--background-color: #0a0a0a;
--spacing: 20px;
--text-color: #ffffff;
--shadow: 0 4px 20px rgb(0 0 0 / 30%);
--background-color: #0A0A0A;
}
html {
height: 100%;
height: 100%;
}
body {
height: 100%;
margin: 0;
overflow: hidden;
font-family:
'Lexend Deca',
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
sans-serif;
height: 100%;
margin: 0;
overflow: hidden;
font-family: 'Lexend Deca', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}
.gradient-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--background-color);
z-index: -1;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--background-color);
z-index: -1;
}
.content {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: calc(var(--spacing) * 0.5);
height: 100%;
text-align: center;
color: var(--text-color);
padding: var(--spacing);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: calc(var(--spacing) * 0.5);
height: 100%;
text-align: center;
color: var(--text-color);
padding: var(--spacing);
}
img {
filter: drop-shadow(var(--shadow));
margin-bottom: 5px;
filter: drop-shadow(var(--shadow));
margin-bottom: 5px;
}
.title {
font-size: 3em;
font-weight: 700;
margin: 0;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
letter-spacing: -0.5px;
font-size: 3em;
font-weight: 700;
margin: 0;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
letter-spacing: -0.5px;
}
.subtitle {
font-size: 1.2em;
font-weight: 400;
margin: 0;
opacity: 0.95;
text-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
font-size: 1.2em;
font-weight: 400;
margin: 0;
opacity: 0.95;
text-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
}
.status-container {
margin: 10px 0;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
margin: 10px 0;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.status-container p {
font-size: 1.1em;
margin: 0;
padding: 12px 24px;
background: #fff;
color: #000;
border-radius: 12px;
box-shadow: var(--shadow);
font-size: 1.1em;
margin: 0;
padding: 12px 24px;
background: #fff;
color: #000;
border-radius: 12px;
box-shadow: var(--shadow);
}
body:not(.state-on, .state-off) :is(.state-on, .state-off) {
display: none;
display: none;
}
body.state-on :is(.state-off, .state-unknown) {
display: none;
display: none;
}
body.state-off :is(.state-on, .state-unknown) {
display: none;
display: none;
}
button {
font-size: 1.1em;
font-weight: 600;
padding: 12px 28px;
background: #fff;
color: #000;
border: none;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: var(--shadow);
margin-top: 5px;
font-size: 1.1em;
font-weight: 600;
padding: 12px 28px;
background: #fff;
color: #000;
border: none;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: var(--shadow);
margin-top: 5px;
}
button:hover {
background: #f0f0f0;
transform: translateY(-2px);
box-shadow: 0 6px 25px rgb(0 0 0 / 40%);
background: #f0f0f0;
transform: translateY(-2px);
box-shadow: 0 6px 25px rgb(0 0 0 / 40%);
}
button:active {
transform: translateY(0);
box-shadow: var(--shadow);
transform: translateY(0);
box-shadow: var(--shadow);
}

View File

@@ -22,15 +22,11 @@ class ViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHan
self.webView.configuration.userContentController.add(self, name: "controller")
let mainUrl = Bundle.main.url(forResource: "Main", withExtension: "html")!
let resourceUrl = Bundle.main.resourceURL!
self.webView.loadFileURL(mainUrl, allowingReadAccessTo: resourceUrl)
self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
SFSafariExtensionManager.getStateOfSafariExtension(
withIdentifier: extensionBundleIdentifier
) { (state, error) in
SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in
guard let state = state, error == nil else {
// Insert code to inform the user that something went wrong.
return
@@ -47,11 +43,11 @@ class ViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHan
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let messageBody = message.body as? String, messageBody == "open-preferences" else {
return
if (message.body as! String != "open-preferences") {
return;
}
SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { _ in
SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in
DispatchQueue.main.async {
NSApplication.shared.terminate(nil)
}

View File

@@ -1,224 +0,0 @@
/* eslint-disable no-console */
const fs = require('fs');
const path = require('path');
const LOCALE_FILE = path.join(__dirname, '../src/i18n/locales/en_GB.json');
const ACHIEVEMENTS_FILE = path.join(__dirname, '../src/i18n/locales/achievements/en_GB.json');
const SEARCH_DIR = path.join(__dirname, '../src');
const FILE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.json'];
/**
* Flatten nested JSON object into dot-notation keys
* @param {Object} obj - The object to flatten
* @param {String} prefix - The prefix for nested keys
* @returns {Array} Array of flattened keys
*/
function flattenKeys(obj, prefix = '') {
const keys = [];
for (const key in obj) {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
keys.push(...flattenKeys(obj[key], fullKey));
} else {
keys.push(fullKey);
}
}
return keys;
}
function getAllFiles(dir, fileList = []) {
const files = fs.readdirSync(dir);
files.forEach(file => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
if (!file.startsWith('.') && file !== 'node_modules' && file !== 'dist' && file !== 'build') {
getAllFiles(filePath, fileList);
}
} else {
const ext = path.extname(file);
if (FILE_EXTENSIONS.includes(ext)) {
fileList.push(filePath);
}
}
});
return fileList;
}
/**
* Search files for usage of a translation key
* Handles both direct usage and dynamic template literal construction
* @param {String} key - The translation key to search for
* @param {Array} files - Array of file paths to search
* @param {Map} fileContentsCache - Cache of file contents
* @returns {Boolean} True if the key is found in any file
*/
function isKeyUsed(key, files, fileContentsCache) {
const keySegments = key.split('.');
const patterns = [];
const escapedFullKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
patterns.push(new RegExp(`['"\`]${escapedFullKey}['"\`]`, 'g'));
if (keySegments.length >= 2) {
const lastTwo = keySegments.slice(-2).map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\.');
patterns.push(new RegExp(`['"\`]${lastTwo}['"\`]`, 'g'));
}
if (keySegments.length >= 3) {
const lastThree = keySegments.slice(-3).map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\.');
patterns.push(new RegExp(`['"\`]${lastThree}['"\`]`, 'g'));
}
if (keySegments.length >= 3) {
const finalSegment = keySegments[keySegments.length - 1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
patterns.push(new RegExp(`\\.${finalSegment}['"\`]`, 'g'));
}
for (const file of files) {
let content = fileContentsCache.get(file);
if (content === undefined) {
try {
content = fs.readFileSync(file, 'utf-8');
fileContentsCache.set(file, content);
} catch {
fileContentsCache.set(file, '');
continue;
}
}
for (const pattern of patterns) {
if (pattern.test(content)) {
return true;
}
}
}
return false;
}
function main() {
console.log('Finding unused translation keys...\n');
let translationKeys = [];
try {
const localeContent = fs.readFileSync(LOCALE_FILE, 'utf-8');
const localeData = JSON.parse(localeContent);
translationKeys = flattenKeys(localeData);
console.log(`Found ${translationKeys.length} translation keys in en_GB.json`);
} catch (error) {
console.error(`Error reading locale file: ${error.message}`);
process.exit(1);
}
try {
if (fs.existsSync(ACHIEVEMENTS_FILE)) {
const achievementsContent = fs.readFileSync(ACHIEVEMENTS_FILE, 'utf-8');
const achievementsData = JSON.parse(achievementsContent);
const achievementKeys = flattenKeys(achievementsData, 'achievements');
translationKeys.push(...achievementKeys);
console.log(`Found ${achievementKeys.length} achievement keys in achievements/en_GB.json`);
}
} catch (error) {
console.warn(`Warning: Could not read achievements file: ${error.message}`);
}
console.log(`\nTotal keys to check: ${translationKeys.length}`);
console.log('Scanning source files...');
const files = getAllFiles(SEARCH_DIR);
console.log(`Found ${files.length} files to search\n`);
const fileContentsCache = new Map();
const unusedKeys = [];
const usedKeys = [];
console.log('Searching for key usage (including template literals)...');
let processed = 0;
const totalKeys = translationKeys.length;
const startTime = Date.now();
for (const key of translationKeys) {
processed++;
if (processed % 10 === 0 || processed === totalKeys) {
const percent = Math.round(processed / totalKeys * 100);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
process.stdout.write(`\r Progress: ${processed}/${totalKeys} (${percent}%) - ${elapsed}s elapsed`);
}
if (isKeyUsed(key, files, fileContentsCache)) {
usedKeys.push(key);
} else {
unusedKeys.push(key);
}
}
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
console.log(`\r Progress: ${totalKeys}/${totalKeys} (100%) - ${totalTime}s total \n`);
console.log('\n' + '='.repeat(70));
console.log('RESULTS');
console.log('='.repeat(70) + '\n');
console.log(`Used keys: ${usedKeys.length}`);
console.log(`Unused keys: ${unusedKeys.length}`);
console.log(`Usage rate: ${((usedKeys.length / totalKeys) * 100).toFixed(2)}%\n`);
if (unusedKeys.length > 0) {
console.log('Unused translation keys:\n');
const grouped = {};
unusedKeys.forEach(key => {
const topLevel = key.split('.')[0];
if (!grouped[topLevel]) {
grouped[topLevel] = [];
}
grouped[topLevel].push(key);
});
Object.keys(grouped).sort().forEach(category => {
console.log(`\n ${category}:`);
grouped[category].sort().forEach(key => {
console.log(` - ${key}`);
});
});
const outputFile = path.join(__dirname, '../unused-translations.txt');
const outputContent = [
'# Unused Translation Keys',
`# Generated: ${new Date().toISOString()}`,
`# Total unused: ${unusedKeys.length}`,
`# Note: This script checks for full keys and partial keys (last 2-3 segments)`,
`# to catch dynamic template literal usage like \`\${PREFIX}.key\``,
'',
...unusedKeys.sort()
].join('\n');
fs.writeFileSync(outputFile, outputContent, 'utf-8');
console.log(`\nFull list saved to: ${path.relative(process.cwd(), outputFile)}`);
} else {
console.log('No unused translation keys found!');
}
console.log('\n' + '='.repeat(70) + '\n');
if (unusedKeys.length > 0) {
console.log('Tip: You can safely remove these keys from your translation files to reduce bundle size.');
console.log('Note: Some keys might be used dynamically - review before removing!');
}
}
main();

View File

@@ -2,6 +2,7 @@ const fs = require('fs');
const path = require('path');
const https = require('https');
// Language code mappings between Weblate and Mue
const CODE_MAPPINGS = {
de: 'de_DE',
id: 'id_ID',
@@ -68,7 +69,7 @@ async function updateTranslationPercentages() {
fs.writeFileSync(outputPath, JSON.stringify(percentages, null, 2));
fs.appendFileSync(outputPath, '\n');
console.log(`Translation percentages updated successfully!`);
console.log(`Translation percentages updated successfully!`);
console.log(` Total languages: ${Object.keys(percentages).length}`);
console.log(` Output: ${outputPath}`);

View File

@@ -16,12 +16,13 @@ const compareAndRemoveKeys = (json1, json2) => {
const localesDir = path.join(__dirname, '../src/i18n/locales');
const achievementsDir = path.join(localesDir, 'achievements');
const manifestLocalesDir = path.join(__dirname, '../manifest/_locales');
// Check if the locales directory exists, if not, create it
if (!fs.existsSync(localesDir)) {
fs.mkdirSync(localesDir, { recursive: true });
}
// Check if the achievements directory exists, if not, create it
if (!fs.existsSync(achievementsDir)) {
fs.mkdirSync(achievementsDir, { recursive: true });
}
@@ -72,6 +73,7 @@ fs.readdirSync(achievementsDir).forEach((file) => {
const locales = fs.readdirSync(localesDir);
locales.forEach((locale) => {
if (!fs.existsSync(path.join(achievementsDir, locale))) {
// ignore directories
if (fs.lstatSync(path.join(localesDir, locale)).isDirectory()) {
return;
}
@@ -81,30 +83,3 @@ fs.readdirSync(achievementsDir).forEach((file) => {
}
});
});
console.log('Syncing manifest/_locales with src/i18n/locales...');
const enManifestPath = path.join(manifestLocalesDir, 'en', 'messages.json');
if (!fs.existsSync(enManifestPath)) {
console.error(`English manifest file does not exist at '${enManifestPath}'`);
} else {
const enManifest = fs.readFileSync(enManifestPath, 'utf8');
const localeFiles = fs.readdirSync(localesDir).filter((file) => {
return !fs.lstatSync(path.join(localesDir, file)).isDirectory() && file.endsWith('.json');
});
localeFiles.forEach((localeFile) => {
const localeCode = localeFile.replace('.json', '');
const manifestLocalePath = path.join(manifestLocalesDir, localeCode);
const manifestMessagesPath = path.join(manifestLocalePath, 'messages.json');
if (!fs.existsSync(manifestLocalePath)) {
console.log(`Creating missing locale: ${localeCode}`);
fs.mkdirSync(manifestLocalePath, { recursive: true });
fs.writeFileSync(manifestMessagesPath, enManifest);
}
});
console.log('Manifest locales sync complete!');
}

View File

@@ -1,21 +1,15 @@
import { useEffect, useState } from 'react';
import { Outlet } from 'react-router';
import { ToastContainer } from 'react-toastify';
import Background from 'features/background/Background';
import Widgets from 'features/misc/views/Widgets';
import Modals from 'features/misc/modals/Modals';
import CustomWidgets from 'features/misc/CustomWidgets';
import { loadSettings, moveSettings } from 'utils/settings';
import EventBus from 'utils/eventbus';
import variables from 'config/variables';
import { TranslationProvider } from 'contexts/TranslationContext';
import { registerAllHandlers } from 'utils/marketplace/registerHandlers';
import { installDefaultPacks } from 'utils/marketplace/installDefaultPacks';
const useAppSetup = () => {
useEffect(() => {
registerAllHandlers();
const firstRun = localStorage.getItem('firstRun');
const stats = localStorage.getItem('stats');
@@ -26,10 +20,8 @@ const useAppSetup = () => {
loadSettings();
installDefaultPacks();
const refreshHandler = (data) => {
if (data === 'other' || data === 'greeting' || data === 'clock' || data === 'quote') {
if (data === 'other') {
loadSettings(true);
}
};
@@ -63,15 +55,11 @@ const App = () => {
useAppSetup();
const languagecode = variables.languagecode || localStorage.getItem('language') || 'en_GB';
const languagecode = localStorage.getItem('language') || 'en_GB';
return (
<TranslationProvider
initialLanguage={languagecode}
initialTranslations={variables.language?.messages || {}}
>
<TranslationProvider initialLanguage={languagecode}>
{showBackground && <Background />}
<CustomWidgets />
<ToastContainer
position="top-center"
autoClose={toastDisplayTime}
@@ -83,7 +71,6 @@ const App = () => {
<Widgets />
<Modals />
</div>
<Outlet />
</TranslationProvider>
);
};

View File

@@ -1,7 +1,6 @@
import React, { PureComponent } from 'react';
import { captureException } from '@sentry/react';
import variables from 'config/variables';
class ErrorBoundary extends PureComponent {
constructor(props) {
@@ -27,35 +26,21 @@ class ErrorBoundary extends PureComponent {
render() {
if (this.state.error) {
const title = variables.getMessage
? variables.getMessage('error_boundary.title')
: 'An error occurred';
const message = variables.getMessage
? variables.getMessage('error_boundary.message')
: 'Something went wrong. Please try refreshing the page.';
const reportButton = variables.getMessage
? variables.getMessage('error_boundary.report_button')
: 'Report Error';
const sentSuccessfully = variables.getMessage
? variables.getMessage('error_boundary.sent_successfully')
: 'Report sent successfully';
const supportDiscord = variables.getMessage
? variables.getMessage('error_boundary.support_discord')
: 'Get Support on Discord';
return (
<div className="criticalError">
<div className="criticalError-message">
<h1>{title}</h1>
<p>{message}</p>
<h1>A critical error has occurred</h1>
<p>
The new tab page could not be loaded. Please uninstall the extension and try again.
</p>
<div className="criticalError-actions">
{this.state.showReport ? (
<button onClick={() => this.reportError()}>{reportButton}</button>
<button onClick={() => this.reportError()}>Report Issue</button>
) : (
<p>{sentSuccessfully}</p>
<p>Sent Successfully</p>
)}
<a href="https://discord.gg/zv8C9F8" target="_blank" rel="noreferrer">
{supportDiscord}
Support Discord
</a>
</div>
</div>

View File

@@ -1,229 +1,72 @@
import { useT } from 'contexts';
import variables from 'config/variables';
import { useState, memo, useEffect } from 'react';
import { useState, memo } from 'react';
import { MdAddLink, MdClose } from 'react-icons/md';
import { Tooltip } from 'components/Elements';
import { Button } from 'components/Elements';
import { Dropdown, Text } from 'components/Form/Settings';
import { IconService } from 'utils/quicklinks';
import './AddModal.scss';
function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, editLink }) {
const t = useT();
const [name, setName] = useState(edit ? editData.name : '');
const [url, setUrl] = useState(edit ? editData.url : '');
const [icon, setIcon] = useState(edit ? editData.icon : '');
const [iconType, setIconType] = useState(edit && editData.iconType ? editData.iconType : 'auto');
const [iconData, setIconData] = useState(edit && editData.iconData ? editData.iconData : null);
const [iconPreview, setIconPreview] = useState(null);
const [uploadError, setUploadError] = useState('');
const [suggestedName, setSuggestedName] = useState('');
const [resetKey, setResetKey] = useState(Date.now());
useEffect(() => {
if (!edit) {
localStorage.removeItem('quicklink_modal_name');
localStorage.removeItem('quicklink_modal_url');
localStorage.removeItem('quicklink_modal_iconType');
localStorage.removeItem('quicklink_modal_icon_url');
localStorage.removeItem('quicklink_modal_emoji');
setName('');
setUrl('');
setIcon('');
setIconType('auto');
setIconData(null);
setIconPreview(null);
setUploadError('');
setSuggestedName('');
setResetKey(Date.now());
}
}, [edit]);
useEffect(() => {
if (name || !url) {
setSuggestedName('');
return;
}
try {
let urlToTest = url;
if (!url.startsWith('http://') && !url.startsWith('https://')) {
urlToTest = 'https://' + url;
}
const domain = new URL(urlToTest).hostname;
if (domain) {
const parts = domain.split('.');
let name = parts[0];
if (parts.length > 2 && parts[parts.length - 2] === 'co') {
name = parts[parts.length - 3];
}
setSuggestedName(name);
}
} catch (e) {
setSuggestedName('');
}
}, [url, name]);
const handleIconUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const dataUrl = await IconService.uploadCustomIcon(file);
setIconData(dataUrl);
setIconPreview(dataUrl);
setUploadError('');
} catch (e) {
setUploadError(e.message);
}
};
const handleSubmit = () => {
const finalName = name || suggestedName || '';
if (edit) {
editLink(editData, finalName, url, icon, iconType, iconData);
} else {
addLink(finalName, url, icon, iconType, iconData);
}
};
const handleKeyDown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit();
}
};
return (
<div className="addLinkModal" onKeyDown={handleKeyDown}>
<div className="addLinkModal">
<div className="shareHeader">
<span className="title">
{edit ? t('widgets.quicklinks.edit') : t('widgets.quicklinks.new')}
{edit
? variables.getMessage('widgets.quicklinks.edit')
: variables.getMessage('widgets.quicklinks.new')}
</span>
<Tooltip title={t('modals.welcome.buttons.close')}>
<Tooltip title={variables.getMessage('modals.welcome.buttons.close')}>
<div className="close" onClick={() => closeModal()}>
<MdClose />
</div>
</Tooltip>
</div>
<div className="quicklinkModalTextbox">
<div className="addLinkModal-row">
<div className="addLinkModal-field">
<div className="addLinkModal-labelRow">
<label className="addLinkModal-label">{t('widgets.quicklinks.name')}</label>
{suggestedName && !name && (
<span className="addLinkModal-suggestedText">
{t('widgets.quicklinks.suggested', { name: suggestedName })}
</span>
)}
</div>
<Text
key={`name-${resetKey}`}
name="quicklink_modal_name"
noSetting={true}
onChange={(value) => setName(value)}
placeholder={suggestedName || t('widgets.quicklinks.name_placeholder')}
/>
</div>
<div className="addLinkModal-field">
<label className="addLinkModal-label">
{t('widgets.quicklinks.url')}
<span className="addLinkModal-required">*</span>
</label>
<Text
key={`url-${resetKey}`}
name="quicklink_modal_url"
noSetting={true}
onChange={(value) => {
let finalValue = value;
if (value && !value.startsWith('http://') && !value.startsWith('https://')) {
finalValue = 'https://' + value;
}
setUrl(finalValue);
}}
placeholder={t('widgets.quicklinks.url_placeholder')}
/>
</div>
</div>
<div className="addLinkModal-dropdownWrapper">
<Dropdown
label={t('widgets.quicklinks.icon_type_label')}
name="quicklink_modal_iconType"
noSetting={true}
onChange={(value) => setIconType(value)}
items={[
{ value: 'auto', text: t('widgets.quicklinks.icon_type_auto') },
{ value: 'custom_url', text: t('widgets.quicklinks.icon_type_custom_url') },
{ value: 'custom_upload', text: t('widgets.quicklinks.icon_type_upload') },
{ value: 'emoji', text: t('widgets.quicklinks.icon_type_emoji') },
{ value: 'letter', text: t('widgets.quicklinks.icon_type_letter') },
]}
/>
</div>
{iconType === 'custom_url' && (
<div className="text-field" style={{ gridColumn: 'span 2' }}>
<Text
key={`icon-url-${resetKey}`}
name="quicklink_modal_icon_url"
noSetting={true}
onChange={(value) => setIcon(value)}
placeholder={t('widgets.quicklinks.icon_url_placeholder')}
/>
</div>
)}
{iconType === 'custom_upload' && (
<div className="text-field" style={{ gridColumn: 'span 2' }}>
<input
type="file"
accept="image/*"
onChange={handleIconUpload}
style={{ padding: '10px 0' }}
/>
{iconPreview && (
<img
src={iconPreview}
alt={t('common.alt_text.preview')}
style={{ width: '40px', height: '40px', marginTop: '8px', borderRadius: '4px' }}
/>
)}
{uploadError && (
<span className="dropdown-error" style={{ display: 'block', marginTop: '4px' }}>
{uploadError}
</span>
)}
</div>
)}
{iconType === 'emoji' && (
<div className="text-field" style={{ gridColumn: 'span 2' }}>
<Text
key={`emoji-${resetKey}`}
name="quicklink_modal_emoji"
noSetting={true}
onChange={(value) => setIcon(value)}
placeholder=""
/>
</div>
)}
<input
type="text"
className="text-field-input"
placeholder={variables.getMessage('widgets.quicklinks.name')}
value={name}
onChange={(e) => setName(e.target.value.replace(/(\r\n|\n|\r)/gm, ''))}
style={{ gridColumn: 'span 2' }}
/>
<input
type="text"
className="text-field-input"
placeholder={variables.getMessage('widgets.quicklinks.url')}
value={url}
onChange={(e) => setUrl(e.target.value.replace(/(\r\n|\n|\r)/gm, ''))}
/>
<input
type="text"
className="text-field-input"
placeholder={variables.getMessage('widgets.quicklinks.icon')}
value={icon}
onChange={(e) => setIcon(e.target.value.replace(/(\r\n|\n|\r)/gm, ''))}
/>
</div>
<div className="addFooter">
<span className="dropdown-error">
{iconError} {urlError}
</span>
<Button
type="settings"
onClick={handleSubmit}
icon={<MdAddLink />}
label={
edit ? t('modals.main.settings.sections.quicklinks.edit') : t('widgets.quicklinks.add')
}
/>
{edit ? (
<Button
type="settings"
onClick={() => editLink(editData, name, url, icon)}
icon={<MdAddLink />}
label={variables.getMessage('modals.main.settings.sections.quicklinks.edit')}
/>
) : (
<Button
type="settings"
onClick={() => addLink(name, url, icon)}
icon={<MdAddLink />}
label={variables.getMessage('widgets.quicklinks.add')}
/>
)}
</div>
</div>
);

View File

@@ -1,81 +0,0 @@
@use 'scss/variables' as *;
@use 'scss/mixins' as *;
.addLinkModal {
width: 600px;
max-width: 90vw;
.addLinkModal-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
grid-column: span 2;
}
.addLinkModal-field {
display: flex;
flex-direction: column;
gap: 8px;
margin-right: 35px;
.text-field-container {
width: 100%;
.text-field {
width: 100%;
.text-field-input {
width: 100%;
}
}
}
}
.addLinkModal-labelRow {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.addLinkModal-label {
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
display: flex;
align-items: center;
gap: 4px;
@include themed {
color: t($subColor);
}
}
.addLinkModal-suggestedText {
font-size: 11px;
font-weight: 400;
text-transform: none;
letter-spacing: 0;
white-space: nowrap;
@include themed {
color: t($link);
opacity: 0.8;
}
}
.addLinkModal-required {
@include themed {
color: t($link);
}
}
.addLinkModal-dropdownWrapper {
grid-column: span 2;
.dropdown {
width: 100%;
}
}
}

View File

@@ -3,20 +3,7 @@ import Tooltip from 'components/Elements/Tooltip/Tooltip';
const Button = forwardRef(
(
{
icon,
label,
type,
iconPlacement,
onClick,
active,
disabled,
tooltipTitle,
tooltipKey,
href,
style,
badge,
},
{ icon, label, type, iconPlacement, onClick, active, disabled, tooltipTitle, tooltipKey, href, style },
ref,
) => {
let className;
@@ -59,24 +46,16 @@ const Button = forwardRef(
<button className={className} onClick={onClick} ref={ref} disabled={disabled} style={style}>
{icon}
{label}
{badge !== undefined && badge !== null && <span className="btn-badge">{badge}</span>}
</button>
);
const linkButton = (
<a
className={className}
onClick={onClick}
ref={ref}
disabled={disabled}
href={href}
style={style}
target="_blank"
rel="noopener noreferrer"
<a className={className} onClick={onClick} ref={ref} disabled={disabled} href={href} style={style}
target="_blank"
rel="noopener noreferrer"
>
{icon}
{label}
{badge && <span className="btn-badge">{badge}</span>}
</a>
);
@@ -94,7 +73,6 @@ const Button = forwardRef(
>
{icon}
{label}
{badge && <span className="btn-badge">{badge}</span>}
</a>
</Tooltip>
);

View File

@@ -1,18 +1,17 @@
import { Suspense, lazy, useState, memo, useEffect, useRef, useMemo } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router';
import { useT } from 'contexts';
import { Suspense, lazy, useState, memo, useEffect } from 'react';
import variables from 'config/variables';
import './scss/index.scss';
import ModalLoader from './components/ModalLoader';
import ModalTopBar from './components/ModalTopBar';
import { TAB_TYPES } from './constants/tabConfig';
import { updateHash, parseDeepLink } from 'utils/deepLinking';
import { useRouterBridge } from '../../../router/RouterBridge';
const Settings = lazy(() => import('../../../features/misc/views/Settings'));
const Library = lazy(() => import('../../../features/misc/views/Library'));
const Discover = lazy(() => import('../../../features/misc/views/Discover'));
// Map tab types to their corresponding components
const TAB_COMPONENTS = {
[TAB_TYPES.SETTINGS]: Settings,
[TAB_TYPES.LIBRARY]: Library,
@@ -20,224 +19,185 @@ const TAB_COMPONENTS = {
};
function MainModal({ modalClose, deepLinkData }) {
const t = useT();
const location = useLocation();
const navigate = useNavigate();
const params = useParams();
const { deepLinkData: routerDeepLinkData } = useRouterBridge();
// Use router-based deepLinkData if available, fallback to prop
// Memoize to prevent infinite loops in useEffect
const effectiveDeepLinkData = useMemo(
() => routerDeepLinkData || deepLinkData,
[location.pathname, deepLinkData],
);
// Derive currentTab from router location instead of state
const currentTab = effectiveDeepLinkData?.tab || TAB_TYPES.SETTINGS;
// Initialize with deep link tab if provided, otherwise default to settings
const initialTab = deepLinkData?.tab || TAB_TYPES.SETTINGS;
const [currentTab, setCurrentTab] = useState(initialTab);
const [currentSection, setCurrentSection] = useState('');
const [currentSectionName, setCurrentSectionName] = useState('');
const [currentSubSection, setCurrentSubSection] = useState(
effectiveDeepLinkData?.subSection || null,
);
const [currentSubSection, setCurrentSubSection] = useState(deepLinkData?.subSection || null);
const [productView, setProductView] = useState(null);
const [resetDiscoverToAll, setResetDiscoverToAll] = useState(false);
const [navigationTrigger, setNavigationTrigger] = useState(null);
const [iframeBreadcrumbs, setIframeBreadcrumbs] = useState([]);
const historyRef = useRef([]);
const historyIndexRef = useRef(-1);
const [canGoBack, setCanGoBack] = useState(false);
const [canGoForward, setCanGoForward] = useState(false);
const updateNavButtons = () => {
setCanGoBack(historyIndexRef.current > 0);
setCanGoForward(historyIndexRef.current < historyRef.current.length - 1);
};
// Clear product view when changing tabs
useEffect(() => {
setProductView(null);
}, [currentTab]);
// Handle deep link updates (when modal opens via EventBus with new deep link)
useEffect(() => {
if (effectiveDeepLinkData) {
if (effectiveDeepLinkData.tab === TAB_TYPES.SETTINGS && effectiveDeepLinkData.section) {
if (deepLinkData) {
// Update tab if different
if (deepLinkData.tab && deepLinkData.tab !== currentTab) {
setCurrentTab(deepLinkData.tab);
}
// Handle settings section navigation with subsection
if (deepLinkData.tab === TAB_TYPES.SETTINGS && deepLinkData.section) {
setNavigationTrigger({
type: 'settings-section',
data: effectiveDeepLinkData.section,
data: deepLinkData.section,
timestamp: Date.now(),
});
if (effectiveDeepLinkData.subSection) {
setCurrentSubSection(effectiveDeepLinkData.subSection);
if (historyIndexRef.current >= 0) {
historyRef.current[historyIndexRef.current] = {
...historyRef.current[historyIndexRef.current],
subSection: effectiveDeepLinkData.subSection,
};
}
// Set sub-section if present
if (deepLinkData.subSection) {
setCurrentSubSection(deepLinkData.subSection);
}
}
}
}, [effectiveDeepLinkData]);
}, [deepLinkData]);
// Clear hash when modal closes
useEffect(() => {
return () => {
// When modal unmounts, clear the hash
if (window.location.hash) {
window.history.replaceState(null, null, window.location.pathname);
}
};
}, []);
// React to router location changes
useEffect(() => {
if (effectiveDeepLinkData) {
if (effectiveDeepLinkData.tab === TAB_TYPES.SETTINGS && effectiveDeepLinkData.section) {
setNavigationTrigger({
type: 'settings-section',
data: effectiveDeepLinkData.section,
timestamp: Date.now(),
});
setCurrentSubSection(effectiveDeepLinkData.subSection || null);
return;
}
// Listen for browser back/forward navigation via popstate
const handlePopState = () => {
const linkData = window.location.hash ? parseDeepLink(window.location.hash) : null;
if (
effectiveDeepLinkData.itemId &&
effectiveDeepLinkData.collection &&
effectiveDeepLinkData.fromCollection
) {
setNavigationTrigger({
type: 'collection',
data: effectiveDeepLinkData.collection,
timestamp: Date.now(),
});
setTimeout(() => {
if (linkData) {
// Update tab if different
if (linkData.tab && linkData.tab !== currentTab) {
setCurrentTab(linkData.tab);
}
// Handle settings section navigation
if (linkData.tab === TAB_TYPES.SETTINGS && linkData.section) {
setNavigationTrigger({
type: 'settings-section',
data: linkData.section,
timestamp: Date.now(),
});
// Set sub-section if present in hash
setCurrentSubSection(linkData.subSection || null);
return;
}
// Handle product and collection navigation
if (linkData.itemId && linkData.collection && linkData.fromCollection) {
// Product viewed from within a collection
// First set collection state, then navigate to product
setNavigationTrigger({
type: 'collection',
data: linkData.collection,
timestamp: Date.now(),
});
// Small delay to ensure collection state is set before navigating to product
setTimeout(() => {
setNavigationTrigger({
type: 'product',
data: {
id: linkData.itemId,
type: linkData.category,
},
timestamp: Date.now(),
});
}, 100);
} else if (linkData.itemId) {
// Product navigation (standalone)
setNavigationTrigger({
type: 'product',
data: {
id: effectiveDeepLinkData.itemId,
type: effectiveDeepLinkData.category,
id: linkData.itemId,
type: linkData.category,
},
timestamp: Date.now(),
});
}, 100);
} else if (effectiveDeepLinkData.itemId) {
setNavigationTrigger({
type: 'product',
data: {
id: effectiveDeepLinkData.itemId,
type: effectiveDeepLinkData.category,
},
timestamp: Date.now(),
});
} else if (effectiveDeepLinkData.collection) {
setNavigationTrigger({
type: 'collection',
data: effectiveDeepLinkData.collection,
timestamp: Date.now(),
});
} else {
setProductView(null);
setNavigationTrigger({
type: 'main',
data: { clearCollection: true },
timestamp: Date.now(),
});
} else if (linkData.collection) {
// Collection page navigation
setNavigationTrigger({
type: 'collection',
data: linkData.collection,
timestamp: Date.now(),
});
} else {
// Back to main view (clear collection state)
setProductView(null);
setNavigationTrigger({
type: 'main',
data: { clearCollection: true },
timestamp: Date.now(),
});
}
}
}
}, [effectiveDeepLinkData, currentTab]);
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, [currentTab]);
const handleChangeTab = (newTab) => {
historyRef.current = [];
historyIndexRef.current = -1;
updateNavButtons();
setCurrentTab(newTab);
// Update URL hash when tab changes
if (newTab === TAB_TYPES.DISCOVER) {
const section = effectiveDeepLinkData?.category || effectiveDeepLinkData?.section || 'all';
const itemId = effectiveDeepLinkData?.itemId ? `/${effectiveDeepLinkData.itemId}` : '';
navigate(`/${newTab}/${section}${itemId}`);
updateHash(`#${newTab}/all`);
} else if (newTab === TAB_TYPES.LIBRARY) {
navigate(`/${newTab}/added`);
updateHash(`#${newTab}/added`);
} else {
navigate(`/${newTab}`);
updateHash(`#${newTab}`);
}
};
const handleSectionChange = (section, sectionName) => {
setCurrentSection(section);
setCurrentSectionName(sectionName);
// Only reset subsection if we're actually changing to a different section
// Don't reset on initial section set (when currentSectionName is empty)
if (currentSectionName !== '' && currentSectionName !== sectionName) {
setCurrentSubSection(null);
}
const entry = {
section,
sectionName,
subSection:
currentSectionName === '' || currentSectionName === sectionName ? currentSubSection : null,
};
const current = historyRef.current[historyIndexRef.current];
if (
!current ||
current.sectionName !== sectionName ||
current.subSection !== entry.subSection
) {
historyRef.current = historyRef.current.slice(0, historyIndexRef.current + 1).concat(entry);
historyIndexRef.current = historyRef.current.length - 1;
updateNavButtons();
}
// Clear sub-section when changing sections
setCurrentSubSection(null);
// Update URL hash when section changes
if (currentTab === TAB_TYPES.DISCOVER) {
// Don't navigate away if we're viewing a specific item
if (effectiveDeepLinkData?.itemId) {
return;
}
// For Discover tab, update with the section type
const sectionMap = {
[t('modals.main.marketplace.all')]: 'all',
[t('modals.main.marketplace.photo_packs')]: 'photo_packs',
[t('modals.main.marketplace.quote_packs')]: 'quote_packs',
[t('modals.main.marketplace.preset_settings')]: 'preset_settings',
[t('modals.main.marketplace.collections')]: 'collections',
[variables.getMessage('modals.main.marketplace.all')]: 'all',
[variables.getMessage('modals.main.marketplace.photo_packs')]: 'photo_packs',
[variables.getMessage('modals.main.marketplace.quote_packs')]: 'quote_packs',
[variables.getMessage('modals.main.marketplace.preset_settings')]: 'preset_settings',
[variables.getMessage('modals.main.marketplace.collections')]: 'collections',
};
const sectionKey = sectionMap[section];
if (sectionKey) {
navigate(`/${currentTab}/${sectionKey}`);
updateHash(`#${currentTab}/${sectionKey}`);
}
} else if (currentTab === TAB_TYPES.SETTINGS && sectionName) {
// Include subsection in hash if it exists and we're not changing sections
const path =
currentSubSection && (currentSectionName === '' || currentSectionName === sectionName)
? `/${currentTab}/${sectionName}/${currentSubSection}`
: `/${currentTab}/${sectionName}`;
navigate(path, { replace: true });
// For Settings tab, update with the section name
updateHash(`#${currentTab}/${sectionName}`, false);
}
};
const handleSubSectionChange = (subSection, sectionName) => {
setCurrentSubSection(subSection);
const effectiveSectionName = sectionName || currentSectionName;
const entry = { section: currentSection, sectionName: effectiveSectionName, subSection };
const current = historyRef.current[historyIndexRef.current];
if (
!current ||
current.sectionName !== effectiveSectionName ||
current.subSection !== subSection
) {
historyRef.current = historyRef.current.slice(0, historyIndexRef.current + 1).concat(entry);
historyIndexRef.current = historyRef.current.length - 1;
updateNavButtons();
}
// Update URL hash when sub-section changes
if (currentTab === TAB_TYPES.SETTINGS && sectionName) {
if (subSection) {
navigate(`/${currentTab}/${sectionName}/${subSection}`);
updateHash(`#${currentTab}/${sectionName}/${subSection}`);
} else {
navigate(`/${currentTab}/${sectionName}`);
// Going back to section, remove sub-section from hash
updateHash(`#${currentTab}/${sectionName}`);
}
}
};
const handleProductView = (product) => {
setProductView(product);
// URL hash is already updated by child components (Browse.jsx)
// Browser history automatically tracks hash changes
};
const handleResetDiscoverToAll = () => {
@@ -245,39 +205,21 @@ function MainModal({ modalClose, deepLinkData }) {
setTimeout(() => setResetDiscoverToAll(false), 100);
};
const restoreHistoryEntry = (entry) => {
setCurrentSubSection(entry.subSection);
if (entry.sectionName !== currentSectionName) {
setCurrentSection(entry.section);
setCurrentSectionName(entry.sectionName);
setNavigationTrigger({
type: 'settings-section',
data: entry.sectionName,
timestamp: Date.now(),
});
}
if (currentTab === TAB_TYPES.SETTINGS) {
const hash = entry.subSection
? `#${currentTab}/${entry.sectionName}/${entry.subSection}`
: `#${currentTab}/${entry.sectionName}`;
window.history.replaceState(null, null, hash);
}
};
const handleBack = () => {
if (historyIndexRef.current <= 0) return;
historyIndexRef.current -= 1;
updateNavButtons();
restoreHistoryEntry(historyRef.current[historyIndexRef.current]);
// Clear iframe breadcrumbs when navigating back
setIframeBreadcrumbs([]);
window.history.back();
};
const handleForward = () => {
if (historyIndexRef.current >= historyRef.current.length - 1) return;
historyIndexRef.current += 1;
updateNavButtons();
restoreHistoryEntry(historyRef.current[historyIndexRef.current]);
window.history.forward();
};
// Browser manages history state, so we always show buttons enabled
// Browser will handle whether there's actually history to go back/forward
const canGoBack = true;
const canGoForward = true;
const TabComponent = TAB_COMPONENTS[currentTab] || Settings;
return (
@@ -302,7 +244,7 @@ function MainModal({ modalClose, deepLinkData }) {
<TabComponent
key={currentTab}
changeTab={handleChangeTab}
deepLinkData={effectiveDeepLinkData}
deepLinkData={deepLinkData}
currentTab={currentTab}
onSectionChange={handleSectionChange}
onSubSectionChange={handleSubSectionChange}

View File

@@ -1,20 +1,27 @@
import { memo } from 'react';
import { memo, useState, useEffect } from 'react';
import { useT } from 'contexts/TranslationContext';
import { Tooltip } from 'components/Elements';
import { getIconComponent, DIVIDER_LABELS } from '../constants/tabConfig';
function Tab({ label, currentTab, onClick, navbarTab, isCollapsed }) {
function Tab({ label, currentTab, onClick, navbarTab }) {
const t = useT();
const isExperimental = localStorage.getItem('experimental') !== 'false';
const [isExperimental, setIsExperimental] = useState(true);
useEffect(() => {
setIsExperimental(localStorage.getItem('experimental') !== 'false');
}, []);
// Get the icon component for this label (label is already translated)
const IconComponent = getIconComponent(label, { getMessage: t });
// Determine if this label should have a divider after it
const hasDivider = DIVIDER_LABELS.some((key) => t(key) === label);
// Build className
const baseClass = navbarTab ? 'navbar-item' : 'tab-list-item';
const activeClass = navbarTab ? 'navbar-item-active' : 'tab-list-active';
const className = `${baseClass}${currentTab === label ? ` ${activeClass}` : ''}`;
// Hide experimental tab if experimental mode is disabled
const isExperimentalTab = label === t('modals.main.settings.sections.experimental.title');
if (isExperimentalTab && !isExperimental) {
return <hr />;
@@ -22,18 +29,10 @@ function Tab({ label, currentTab, onClick, navbarTab, isCollapsed }) {
return (
<>
{isCollapsed ? (
<Tooltip title={label} placement="right">
<button className={className} onClick={() => onClick(label)}>
{IconComponent && <IconComponent />}
</button>
</Tooltip>
) : (
<button className={className} onClick={() => onClick(label)}>
{IconComponent && <IconComponent />} <span>{label}</span>
</button>
)}
{!isCollapsed && hasDivider && <hr />}
<button className={className} onClick={() => onClick(label)}>
{IconComponent && <IconComponent />} <span>{label}</span>
</button>
{hasDivider && <hr />}
</>
);
}

View File

@@ -1,13 +1,10 @@
import { useState, useEffect, useLayoutEffect, useRef } from 'react';
import { useState, useEffect, useRef } from 'react';
import { useT } from 'contexts/TranslationContext';
import variables from 'config/variables';
import Tab from './Tab';
import ReminderInfo from '../components/ReminderInfo';
import SidebarToggle from '../components/SidebarToggle';
import ErrorBoundary from '../../../../features/misc/modals/ErrorBoundary';
import { TAB_TYPES } from '../constants/tabConfig';
import { SearchInput } from 'components/Form/Settings';
import EventBus from 'utils/eventbus';
const Tabs = ({
children,
@@ -21,6 +18,7 @@ const Tabs = ({
}) => {
const t = useT();
// Find initial section from deep link if available
const getInitialSection = () => {
if (deepLinkData?.section && sections) {
const section = sections.find((s) => s.name === deepLinkData.section);
@@ -31,15 +29,6 @@ const Tabs = ({
};
}
}
if (deepLinkData?.category && sections) {
const section = sections.find((s) => s.name === deepLinkData.category);
if (section) {
return {
label: t(section.label),
name: section.name,
};
}
}
return {
label: children[0]?.props.label,
name: children[0]?.props.name,
@@ -47,80 +36,70 @@ const Tabs = ({
};
const initial = getInitialSection();
const [currentTab, setCurrentTab] = useState(initial.label);
const [currentName, setCurrentName] = useState(initial.name);
const [showReminder, setShowReminder] = useState(localStorage.getItem('showReminder') === 'true');
const [sidebarCollapsed, setSidebarCollapsed] = useState(
localStorage.getItem('sidebarCollapsed') === 'true',
);
const [searchQuery, setSearchQuery] = useState('');
const contentRef = useRef(null);
const currentTab = (() => {
if (sections && currentName) {
const section = sections.find((s) => s.name === currentName);
if (section) {
return t(section.label);
}
}
const child = children.find((c) => c.props.name === currentName);
return child?.props.label || children[0]?.props.label;
})();
const handleTabClick = (tab, name) => {
if (name !== currentName) {
variables.stats.postEvent('tab', `Opened ${name}`);
}
setCurrentTab(tab);
setCurrentName(name);
// Scroll content to top when changing tabs
if (contentRef.current) {
contentRef.current.scrollTop = 0;
}
// Notify parent of section change with both label and name
if (onSectionChange) {
onSectionChange(tab, name);
}
};
// Notify parent of initial section on mount
useEffect(() => {
if (onSectionChange && currentTab) {
onSectionChange(currentTab, currentName);
}
}, []);
// React to deep link changes (e.g., when navigating to a suggested pack from settings)
// Update labels when language changes
useEffect(() => {
if (deepLinkData && sections) {
const targetSection = deepLinkData.section || deepLinkData.category;
if (targetSection) {
const section = sections.find((s) => s.name === targetSection);
if (section && section.name !== currentName) {
setCurrentName(section.name);
if (contentRef.current) {
contentRef.current.scrollTop = 0;
}
}
if (sections && currentName) {
const section = sections.find((s) => s.name === currentName);
if (section) {
const newLabel = t(section.label);
setCurrentTab(newLabel);
}
}
}, [deepLinkData, sections, currentName]);
}, [t, sections, currentName]);
// useLayoutEffect is appropriate here for synchronous state updates before paint
useLayoutEffect(() => {
// Handle navigation trigger for settings sections (popstate)
useEffect(() => {
if (navigationTrigger?.type === 'settings-section' && sections) {
const section = sections.find((s) => s.name === navigationTrigger.data);
if (section) {
const label = t(section.label);
setCurrentTab(label);
setCurrentName(section.name);
// Scroll content to top when navigating via browser history
if (contentRef.current) {
contentRef.current.scrollTop = 0;
}
}
}
}, [navigationTrigger, sections]);
}, [navigationTrigger, sections, t]);
// useLayoutEffect is appropriate here for synchronous state updates before paint
useLayoutEffect(() => {
// Reset to first tab when requested
useEffect(() => {
if (resetToFirst) {
setCurrentTab(children[0]?.props.label);
setCurrentName(children[0]?.props.name);
// Scroll content to top when resetting to first tab
if (contentRef.current) {
contentRef.current.scrollTop = 0;
}
@@ -130,91 +109,40 @@ const Tabs = ({
}
}, [resetToFirst]);
useEffect(() => {
const handleShowReminder = () => {
localStorage.setItem('showReminder', 'true');
setShowReminder(true);
};
EventBus.on('showReminder', handleShowReminder);
return () => EventBus.off('showReminder', handleShowReminder);
}, []);
const handleHideReminder = () => {
localStorage.setItem('showReminder', 'false');
setShowReminder(false);
};
const handleToggleSidebar = () => {
const newState = !sidebarCollapsed;
setSidebarCollapsed(newState);
localStorage.setItem('sidebarCollapsed', newState.toString());
};
// Show sidebar for Settings and Discover tabs
const showSidebar = activeTab === TAB_TYPES.SETTINGS || activeTab === TAB_TYPES.DISCOVER;
const filteredChildren = children.filter((tab) => {
if (!searchQuery.trim()) return true;
return tab.props.label.toLowerCase().includes(searchQuery.toLowerCase());
});
useEffect(() => {
const handleKeyPress = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'b') {
e.preventDefault();
if (showSidebar) {
setSidebarCollapsed((prev) => !prev);
}
}
};
window.addEventListener('keydown', handleKeyPress);
return () => window.removeEventListener('keydown', handleKeyPress);
}, [showSidebar]);
return (
<div style={{ display: 'flex', width: '100%', height: '100%', overflow: 'hidden' }}>
{showSidebar ? (
<div className={`modalSidebar ${sidebarCollapsed ? 'collapsed' : 'expanded'}`}>
<div className="sidebarHeader">
<SidebarToggle isCollapsed={sidebarCollapsed} onToggle={handleToggleSidebar} />
{!sidebarCollapsed && activeTab === TAB_TYPES.SETTINGS && (
<SearchInput
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={t('widgets.search')}
fullWidth
/>
)}
</div>
{filteredChildren.map((tab, index) => (
<div className="modalSidebar">
{children.map((tab, index) => (
<Tab
key={index}
currentTab={currentTab}
label={tab.props.label}
onClick={(nextTab) => handleTabClick(nextTab, tab.props.name)}
navbarTab={navbar}
isCollapsed={sidebarCollapsed}
/>
))}
{searchQuery.trim() && filteredChildren.length === 0 && (
<div className="sidebarEmptyState">{t('widgets.weather.not_found')}</div>
)}
<ReminderInfo isVisible={showReminder} onHide={handleHideReminder} />
</div>
) : null}
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 }}>
<ReminderInfo isVisible={showReminder} onHide={handleHideReminder} />
<div className="modalTabContent" ref={contentRef}>
{children.map((tab, index) => {
if (tab.props.label !== currentTab) {
return null;
}
<div className="modalTabContent" ref={contentRef}>
{children.map((tab, index) => {
if (tab.props.label !== currentTab) {
return null;
}
return (
<ErrorBoundary key={`error-boundary-${index}`}>{tab.props.children}</ErrorBoundary>
);
})}
</div>
return (
<ErrorBoundary key={`error-boundary-${index}`}>{tab.props.children}</ErrorBoundary>
);
})}
</div>
</div>
);

View File

@@ -1,4 +1,4 @@
import { useT } from 'contexts';
import variables from 'config/variables';
import SidebarSkeleton from './SidebarSkeleton';
const ModalLoader = ({ currentTab }) => (
@@ -11,7 +11,7 @@ const ModalLoader = ({ currentTab }) => (
<div className="emptyMessage">
<div className="loaderHolder">
<div id="loader"></div>
<span className="subtitle">{t('modals.main.loading')}</span>
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
import variables from 'config/variables';
import { Button } from 'components/Elements';
import { NAVBAR_BUTTONS } from '../constants/tabConfig';
const ModalNavbar = ({ currentTab, onChangeTab }) => (
<div className="modalNavbar">
{NAVBAR_BUTTONS.map(({ tab, icon: Icon, messageKey }) => (
<Button
key={tab}
type="navigation"
onClick={() => onChangeTab(tab)}
icon={<Icon />}
label={variables.getMessage(messageKey)}
active={currentTab === tab}
/>
))}
</div>
);
export default ModalNavbar;

View File

@@ -1,33 +1,21 @@
import { useT } from 'contexts/TranslationContext';
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router';
import { MdClose, MdChevronRight, MdArrowBack, MdArrowForward } from 'react-icons/md';
import { Tooltip, Button } from 'components/Elements';
import { NAVBAR_BUTTONS, TAB_TYPES } from '../constants/tabConfig';
import { NAVBAR_BUTTONS } from '../constants/tabConfig';
import mueAboutIcon from 'assets/icons/mue_about.png';
// Map marketplace types to translation keys
const MARKETPLACE_TYPE_TO_KEY = {
photo_packs: 'modals.main.marketplace.photo_packs',
'photo packs': 'modals.main.marketplace.photo_packs',
photos: 'modals.main.marketplace.photo_packs',
quote_packs: 'modals.main.marketplace.quote_packs',
'quote packs': 'modals.main.marketplace.quote_packs',
quotes: 'modals.main.marketplace.quote_packs',
preset_settings: 'modals.main.marketplace.preset_settings',
'preset settings': 'modals.main.marketplace.preset_settings',
settings: 'modals.main.marketplace.preset_settings',
collections: 'modals.main.marketplace.collections',
all: 'modals.main.marketplace.all',
};
const BREADCRUMB_LABEL_TO_CATEGORY = {
'photo packs': 'photo_packs',
'quote packs': 'quote_packs',
'preset settings': 'preset_settings',
collections: 'collections',
marketplace: 'all',
};
function ModalTopBar({
currentTab,
currentSection,
@@ -36,7 +24,6 @@ function ModalTopBar({
productView,
iframeBreadcrumbs,
onTabChange,
onSectionChange,
onSubSectionChange,
onClose,
onBack,
@@ -45,48 +32,21 @@ function ModalTopBar({
canGoForward,
}) {
const t = useT();
const navigate = useNavigate();
const [installedCount, setInstalledCount] = useState(() => {
try {
const installed = JSON.parse(localStorage.getItem('installed')) || [];
return installed.length;
} catch (e) {
return 0;
}
});
useEffect(() => {
const updateCount = () => {
try {
const installed = JSON.parse(localStorage.getItem('installed')) || [];
setInstalledCount(installed.length);
} catch (e) {
setInstalledCount(0);
}
};
window.addEventListener('storage', updateCount);
window.addEventListener('installedAddonsChanged', updateCount);
return () => {
window.removeEventListener('storage', updateCount);
window.removeEventListener('installedAddonsChanged', updateCount);
};
}, []);
// Get the current tab label
const currentTabButton = NAVBAR_BUTTONS.find(({ tab }) => tab === currentTab);
const currentTabLabel = currentTabButton ? t(currentTabButton.messageKey) : '';
// Utility function to get translated sub-section label
const getSubSectionLabel = (subSection, sectionName) => {
if (!subSection || !sectionName) {
return subSection;
}
if (!subSection || !sectionName) return subSection;
// Use the same translation pattern as the section components
const translationKey = `modals.main.settings.sections.${sectionName}.${subSection}.title`;
const translated = t(translationKey);
// If translation key is returned as-is or empty, it means translation doesn't exist
// Fall back to capitalized sub-section name
if (!translated || translated === translationKey) {
return subSection.charAt(0).toUpperCase() + subSection.slice(1);
}
@@ -94,78 +54,53 @@ function ModalTopBar({
return translated;
};
// Determine breadcrumb path with click handlers
const breadcrumbPath = [];
if (currentTabLabel) {
breadcrumbPath.push({
label: currentTabLabel,
onClick:
(iframeBreadcrumbs && iframeBreadcrumbs.length > 0) || productView
? () => {
navigate('/discover/all');
}
: null,
onClick: productView ? productView.onBackToAll : null, // Clickable if viewing a product
});
// Check if we have iframe breadcrumbs (from Discover iframe)
// If so, only use the last item (the item name) and keep our section
if (iframeBreadcrumbs && iframeBreadcrumbs.length > 0) {
const relevantCrumbs = iframeBreadcrumbs.slice(1);
relevantCrumbs.forEach((crumb, index) => {
const isLast = index === relevantCrumbs.length - 1;
const lowerLabel = crumb.label.toLowerCase();
const translationKey = MARKETPLACE_TYPE_TO_KEY[lowerLabel];
const displayLabel = translationKey ? t(translationKey) : crumb.label;
const categoryKey = BREADCRUMB_LABEL_TO_CATEGORY[lowerLabel];
// Get the last breadcrumb item (the item name)
const lastCrumb = iframeBreadcrumbs[iframeBreadcrumbs.length - 1];
// Add current section if available and different from the last crumb
if (currentSection && currentSection !== lastCrumb.label) {
breadcrumbPath.push({
label: displayLabel,
onClick:
crumb.clickable && !isLast && crumb.href
? () => {
const href = crumb.href;
if (href.includes('type=')) {
const urlParams = new URLSearchParams(href.split('?')[1]);
const typeParam = urlParams.get('type');
if (typeParam) {
navigate(`/discover/${typeParam}`);
}
} else if (href.includes('/collections')) {
navigate('/discover/collections');
} else if (href.includes('/collection/')) {
const collectionId = href.split('/collection/')[1]?.split('?')[0];
if (collectionId) {
navigate(`/discover/collection/${collectionId}`);
}
} else if (categoryKey) {
navigate(`/discover/${categoryKey}`);
} else if (href === '/marketplace' || href === '/marketplace/') {
navigate('/discover/all');
} else {
const stepsBack = relevantCrumbs.length - index - 1;
for (let i = 0; i < stepsBack; i++) {
window.history.back();
}
}
}
: null,
label: currentSection,
onClick: () => onBack(), // Clickable to go back
});
}
// Add the item name from iframe
breadcrumbPath.push({
label: lastCrumb.label,
onClick: null, // Current item - not clickable
});
} else if (productView) {
// If viewing a collection page itself (not a product within it)
if (productView.isCollection) {
// Show: Discover > Collection Name
breadcrumbPath.push({
label: productView.collectionTitle || productView.name,
onClick: null,
onClick: null, // Current page - not clickable
});
} else {
// Viewing a product
// Show: Discover > Collection/Category > Product
if (productView.fromCollection && productView.collectionTitle) {
// If from a collection, show collection name
breadcrumbPath.push({
label: productView.collectionTitle,
onClick: productView.onBack || null,
});
} else {
// Otherwise show category
const categoryKey = MARKETPLACE_TYPE_TO_KEY[productView.type];
if (categoryKey) {
breadcrumbPath.push({
@@ -174,23 +109,24 @@ function ModalTopBar({
});
}
}
// Add product name as final breadcrumb
breadcrumbPath.push({
label: productView.name,
onClick: null,
onClick: null, // Current item - not clickable
});
}
} else if (currentSection) {
// Show: Tab > Section or Tab > Section > Sub-Section
breadcrumbPath.push({
label: currentSection,
onClick: currentSubSection
? () => onSubSectionChange(null, currentSectionName)
: null,
onClick: currentSubSection ? () => onSubSectionChange(null) : null, // Clickable if sub-section is active
});
// Add sub-section if present
if (currentSubSection) {
breadcrumbPath.push({
label: getSubSectionLabel(currentSubSection, currentSectionName),
onClick: null,
onClick: null, // Current sub-section - not clickable
});
}
}
@@ -200,30 +136,35 @@ function ModalTopBar({
<div className="modalTopBar">
<div className="topBarLeft">
<div className="navigationButtons">
<Tooltip title={t('common.navigation.back')} key="backTooltip">
<Tooltip title="Back" key="backTooltip">
<button
className="navButton"
onClick={onBack}
disabled={!canGoBack}
aria-label={t('common.navigation.navigate_back')}
aria-label="Navigate back"
>
<MdArrowBack />
</button>
</Tooltip>
<Tooltip title={t('common.navigation.forward')} key="forwardTooltip">
<Tooltip title="Forward" key="forwardTooltip">
<button
className="navButton"
onClick={onForward}
disabled={!canGoForward}
aria-label={t('common.navigation.navigate_forward')}
aria-label="Navigate forward"
>
<MdArrowForward />
</button>
</Tooltip>
</div>
<img src={mueAboutIcon} alt="Mue" className="topBarLogo" draggable={false} />
<img
src={mueAboutIcon}
alt="Mue"
className="topBarLogo"
draggable={false}
/>
{breadcrumbPath.length > 0 && (
<nav className="breadcrumbs" aria-label={t('common.navigation.breadcrumb_navigation')}>
<nav className="breadcrumbs" aria-label="Breadcrumb navigation">
{breadcrumbPath.map((item, index) => {
const isLast = index === breadcrumbPath.length - 1;
const isClickable = item.onClick !== null;
@@ -242,7 +183,7 @@ function ModalTopBar({
}}
role="button"
tabIndex={0}
aria-label={t('common.navigation.navigate_to', { item: item.label })}
aria-label={`Navigate to ${item.label}`}
>
{item.label}
</span>
@@ -254,9 +195,7 @@ function ModalTopBar({
{item.label}
</span>
)}
{!isLast && (
<MdChevronRight className="breadcrumb-separator" aria-hidden="true" />
)}
{!isLast && <MdChevronRight className="breadcrumb-separator" aria-hidden="true" />}
</span>
);
})}
@@ -265,22 +204,16 @@ function ModalTopBar({
</div>
<div className="topBarRight">
<div className="topBarNavigation">
{NAVBAR_BUTTONS.map(({ tab, icon: Icon, messageKey }) => {
const badgeValue =
tab === TAB_TYPES.LIBRARY && installedCount > 0 ? installedCount : undefined;
return (
<Button
key={tab}
type="navigation"
onClick={() => onTabChange(tab)}
active={currentTab === tab}
icon={<Icon />}
label={t(messageKey)}
badge={badgeValue}
/>
);
})}
{NAVBAR_BUTTONS.map(({ tab, icon: Icon, messageKey }) => (
<Button
key={tab}
type="navigation"
onClick={() => onTabChange(tab)}
active={currentTab === tab}
icon={<Icon />}
label={t(messageKey)}
/>
))}
</div>
<Tooltip title={t('modals.welcome.buttons.close')} key="closeTooltip">
<span className="closeModal" onClick={onClose}>

View File

@@ -1,23 +1,26 @@
import { useT } from 'contexts';
import variables from 'config/variables';
import { MdRefresh, MdClose } from 'react-icons/md';
const ReminderInfo = ({ isVisible, onHide }) => {
const t = useT();
if (!isVisible) {
return null;
}
return (
<div className="reminder-info">
<span className="title">{t('modals.main.settings.reminder.title')}</span>
<span className="subtitle">{t('modals.main.settings.reminder.message')}</span>
<div className="shareHeader">
<span className="title">{variables.getMessage('modals.main.settings.reminder.title')}</span>
<span className="closeModal" onClick={onHide}>
<MdClose />
</span>
</div>
<span className="subtitle">
{variables.getMessage('modals.main.settings.reminder.message')}
</span>
<button onClick={() => window.location.reload()}>
<MdRefresh />
{t('modals.main.error_boundary.refresh')}
{variables.getMessage('modals.main.error_boundary.refresh')}
</button>
<span className="closeModal" onClick={onHide}>
<MdClose />
</span>
</div>
);
};

View File

@@ -1,41 +1,34 @@
import { TAB_TYPES } from '../constants/tabConfig';
// Tab-specific configurations with exact divider positions
const TAB_CONFIGS = {
[TAB_TYPES.SETTINGS]: {
itemCount: 16,
dividerPositions: [10, 12],
textWidths: [80, 100, 70, 90, 85, 75, 80, 95, 90, 75, 85, 90, 85, 80, 70, 95],
showSearch: true,
itemCount: 16, // Excluding experimental
dividerPositions: [10, 12], // After Weather, Language
textWidths: [80, 100, 70, 90, 85, 75, 80, 95, 90, 75, 85, 90, 85, 80, 70, 95], // Fixed widths in pixels
},
[TAB_TYPES.DISCOVER]: {
itemCount: 5,
dividerPositions: [0],
textWidths: [60, 95, 95, 110, 90],
showSearch: false,
dividerPositions: [0], // After "All"
textWidths: [60, 95, 95, 110, 90], // Fixed widths
},
[TAB_TYPES.LIBRARY]: {
itemCount: 0,
itemCount: 0, // Library doesn't show sidebar
dividerPositions: [],
textWidths: [],
showSearch: false,
},
};
const SidebarSkeleton = ({ currentTab = TAB_TYPES.SETTINGS }) => {
const config = TAB_CONFIGS[currentTab] || TAB_CONFIGS[TAB_TYPES.SETTINGS];
// Library tab doesn't show sidebar
if (config.itemCount === 0) {
return null;
}
return (
<div className="sidebarSkeleton">
{/* Header with toggle button and optional search */}
<div className="skeletonHeader">
<div className="skeletonToggle pulse" />
{config.showSearch && <div className="skeletonSearch pulse" />}
</div>
{Array.from({ length: config.itemCount }).map((_, index) => {
const hasDivider = config.dividerPositions.includes(index);
const textWidth = config.textWidths[index] || 80;

View File

@@ -1,25 +0,0 @@
import { FiSidebar } from 'react-icons/fi';
import { Tooltip } from 'components/Elements';
import { useT } from 'contexts/TranslationContext';
function SidebarToggle({ isCollapsed, onToggle }) {
const t = useT();
return (
<Tooltip
title={isCollapsed ? t('modals.main.sidebar.expand') : t('modals.main.sidebar.collapse')}
placement="right"
>
<button
className="sidebarToggleButton"
onClick={onToggle}
aria-label={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
aria-expanded={!isCollapsed}
>
<FiSidebar />
</button>
</Tooltip>
);
}
export default SidebarToggle;

View File

@@ -1,2 +1,3 @@
export { default as ModalLoader } from './ModalLoader';
export { default as ModalNavbar } from './ModalNavbar';
export { default as ReminderInfo } from './ReminderInfo';

View File

@@ -1,5 +1,7 @@
import variables from 'config/variables';
import {
MdSettings,
MdWidgets,
MdShoppingBasket,
MdTune,
MdBookmarks,
MdExplore,
@@ -26,12 +28,14 @@ import {
MdCollectionsBookmark,
} from 'react-icons/md';
// Tab type constants
export const TAB_TYPES = {
SETTINGS: 'settings',
LIBRARY: 'library',
DISCOVER: 'discover',
};
// Icon component mapping - using component references instead of elements
export const ICON_COMPONENTS = {
SETTINGS: MdTune,
LIBRARY: MdBookmarks,
@@ -59,8 +63,9 @@ export const ICON_COMPONENTS = {
COLLECTIONS: MdCollectionsBookmark,
};
// Message keys for icon mapping
export const MESSAGE_KEYS = {
OVERVIEW: 'modals.main.settings.sections.order.title',
OVERVIEW: 'modals.main.marketplace.product.overview',
SETTINGS: 'modals.main.navbar.settings',
LIBRARY: 'modals.main.navbar.library',
DISCOVER: 'modals.main.navbar.discover',
@@ -90,6 +95,8 @@ export const MESSAGE_KEYS = {
COLLECTIONS: 'modals.main.marketplace.collections',
};
// Helper to get icon component by translated label
// This function builds a map at runtime using variables.getMessage
export const getIconComponent = (label, variables) => {
const iconMap = {
[variables.getMessage(MESSAGE_KEYS.OVERVIEW)]: ICON_COMPONENTS.OVERVIEW,
@@ -125,6 +132,7 @@ export const getIconComponent = (label, variables) => {
return iconMap[label];
};
// Navbar configuration
export const NAVBAR_BUTTONS = [
{
tab: TAB_TYPES.SETTINGS,
@@ -143,6 +151,7 @@ export const NAVBAR_BUTTONS = [
},
];
// Labels that should have dividers after them
export const DIVIDER_LABELS = [
'modals.main.settings.sections.weather.title',
'modals.main.settings.sections.language.title',

View File

@@ -9,8 +9,7 @@
@use 'modules/scrollbars' as *;
@use 'settings/main' as *;
@use 'marketplace/main' as *;
/* Fixed: Added sass:map module */
// Fixed: Added sass:map module
.Overlay {
position: fixed;
@@ -20,16 +19,6 @@
height: 100vh;
display: grid;
place-items: center;
opacity: 0;
transition: opacity 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
&.ReactModal__Overlay--after-open {
opacity: 1;
}
&.ReactModal__Overlay--before-close {
opacity: 0;
}
}
.Modal {
@@ -38,15 +27,14 @@
}
box-shadow: 0 0 20px rgb(0 0 0 / 30%);
opacity: 0;
opacity: 1;
z-index: -2;
transition-timing-function: ease-in;
border-radius: map.get($modal, 'border-radius');
-webkit-user-select: none;
user-select: none;
overflow: hidden;
transform: scale(0.95);
transition: opacity 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
transform: scale(0);
transition: all 0.3s cubic-bezier(0.47, 1.64, 0.41, 0.8);
&:focus {
outline: 0;
@@ -60,7 +48,7 @@
.closePositioning {
position: absolute;
top: 3rem;
inset-inline-end: 3rem;
right: 3rem;
}
.closeModal {
@@ -102,13 +90,14 @@
.ReactModal__Content--before-close {
opacity: 0;
transform: scale(0.95);
transform: scale(0);
}
#modal {
height: 80vh;
width: clamp(60vw, 1400px, 90vw);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
backdrop-filter: blur(16px) saturate(180%);
overflow: hidden;
border-radius: 12px;
@@ -280,7 +269,7 @@ h5 {
@include themed {
background: t($modal-sidebar);
border-radius: t($borderRadius);
box-shadow: 0 0 0 1px t($modal-border);
box-shadow: 0 0 0 1px t($modal-sidebarActive);
&:hover {
background: t($modal-sidebarActive);
@@ -305,7 +294,6 @@ h5 {
padding: 15px;
border-radius: 100%;
flex-shrink: 0;
}
}
@@ -324,51 +312,27 @@ h5 {
.reminder-info {
display: flex;
flex-flow: row;
align-items: center;
gap: 12px;
padding: 10px 16px;
flex-shrink: 0;
flex-flow: column;
position: absolute;
bottom: 0;
padding: 15px;
gap: 15px;
@include themed {
background-color: t($modal-secondaryColour);
border-bottom: 1px solid t($modal-sidebarActive);
}
.title {
font-weight: 600;
white-space: nowrap;
}
.subtitle {
opacity: 0.7;
flex: 1;
border-radius: t($borderRadius);
border: 1px solid t($modal-sidebarActive);
}
button {
@include basicIconButton(8px 14px, 0.875rem, modal);
@include basicIconButton(5px, 5px, modal);
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
cursor: pointer;
justify-content: center;
gap: 15px;
svg {
margin: 0 !important;
font-size: 1rem;
}
}
.closeModal {
cursor: pointer;
display: flex;
align-items: center;
flex-shrink: 0;
opacity: 0.6;
&:hover {
opacity: 1;
}
}

View File

@@ -1,10 +1,11 @@
// this file is too long
@use 'modules/lightbox' as *;
@use 'scss/variables' as *;
.items {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1.5rem;
grid-template-columns: repeat(auto-fill, minmax(250px, 280px));
grid-gap: 1.5rem;
margin-top: 15px;
margin-bottom: 30px;
@@ -23,7 +24,7 @@
background-size: cover;
padding: 1.5rem;
will-change: transform;
transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0); // Force GPU acceleration
@include themed {
background-color: t($modal-secondaryColour);
@@ -45,10 +46,6 @@
border-radius: 12px;
transition: 0.5s;
@include themed {
background-color: t($modal-sidebarActive);
}
&.item-icon-text {
display: flex;
align-items: center;
@@ -69,7 +66,6 @@
display: flex;
flex-flow: column;
align-items: flex-start;
width: 100%;
.card-title {
font-size: 18px;
@@ -114,17 +110,6 @@
}
}
&.item-disabled {
opacity: 0.5;
.card-title,
.card-subtitle {
@include themed {
color: t($subColor);
}
}
}
.item-uninstall-btn {
display: flex;
align-items: center;
@@ -133,7 +118,7 @@
height: 28px;
border-radius: 50%;
border: none;
background-color: rgb(0 0 0 / 50%);
background-color: rgba(0, 0, 0, 0.5);
cursor: pointer;
transition: background-color 0.2s ease;
@@ -143,7 +128,7 @@
}
&:hover {
background-color: rgb(220 50 50 / 90%);
background-color: rgba(220, 50, 50, 0.9);
}
}
@@ -157,8 +142,8 @@
width: 32px;
height: 32px;
border-radius: 50%;
border: 2px solid rgb(255 255 255 / 40%);
box-shadow: 0 2px 6px rgb(0 0 0 / 30%);
border: 2px solid rgba(255, 255, 255, 0.4);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
z-index: 2;
pointer-events: none;
will-change: transform;
@@ -176,7 +161,7 @@
width: 28px;
height: 28px;
border-radius: 50%;
background-color: rgb(100 100 100 / 90%);
background-color: rgba(100, 100, 100, 0.9);
cursor: help;
svg {
@@ -199,13 +184,6 @@
}
}
.item-card-actions {
display: flex;
gap: 8px;
margin-top: 1.5rem;
width: 100%;
}
.itemPage {
display: flex;
flex-flow: row;
@@ -229,7 +207,7 @@
table {
table-layout: fixed;
width: 100%;
overflow-wrap: break-word !important;
word-wrap: break-word !important;
font-size: 16px;
border-collapse: collapse;
}
@@ -241,7 +219,7 @@
width: 28px;
height: 28px;
border-radius: 50%;
background-color: rgb(100 100 100 / 90%);
background-color: rgba(100, 100, 100, 0.9);
cursor: help;
svg {
@@ -274,7 +252,7 @@
.emptyMessage {
display: grid;
place-items: center;
gap: 5px;
grid-gap: 5px;
padding: 50px;
.title,
@@ -305,7 +283,7 @@
flex-flow: column;
text-align: center;
align-items: center;
user-select: none;
-webkit-user-select: none;
user-select: none;
img {
@@ -348,6 +326,7 @@ p.author {
cursor: pointer;
}
.filter {
display: flex;
flex-flow: row;
@@ -395,7 +374,7 @@ p.author {
margin-bottom: 15px;
.tooltip {
margin-inline-end: 25px;
margin-right: 25px;
}
.mainTitle {
@@ -427,7 +406,7 @@ p.author {
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
user-select: none;
-webkit-user-select: none;
user-select: none;
@include themed {
@@ -437,7 +416,7 @@ p.author {
&:hover {
@include themed {
background-color: rgb(255 255 255 / 15%);
background-color: rgba(255, 255, 255, 0.15);
}
}
@@ -455,105 +434,8 @@ p.author {
}
&:focus-visible {
outline: 2px solid rgb(255 255 255 / 50%);
outline: 2px solid rgba(255, 255, 255, 0.5);
outline-offset: 2px;
}
}
}
.view-toggle-buttons {
display: flex;
gap: 8px;
align-items: center;
.view-toggle-btn {
all: unset;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
user-select: none;
user-select: none;
@include themed {
background-color: t($modal-sidebarActive);
color: t($subColor);
}
svg {
font-size: 20px;
}
&:hover {
@include themed {
background-color: rgb(255 255 255 / 15%);
color: t($color);
}
}
&.active {
@include themed {
background-color: #fff;
color: #000;
}
&:hover {
@include themed {
background-color: #f0f0f0;
}
}
}
&:focus-visible {
outline: 2px solid rgb(255 255 255 / 50%);
outline-offset: 2px;
}
}
}
.items-list {
display: flex !important;
flex-direction: column !important;
grid-template-columns: unset !important;
gap: 12px !important;
.item {
flex-direction: row !important;
align-items: center !important;
padding: 1rem 1.5rem !important;
gap: 20px !important;
&:hover {
transform: translate3d(5px, 0, 0) !important;
}
.item-icon {
flex-shrink: 0;
}
.card-details {
flex: 1;
flex-direction: row !important;
align-items: center !important;
justify-content: space-between;
gap: 15px;
.card-title {
font-size: 16px;
}
.card-subtitle {
font-size: 13px;
}
.card-chips {
margin-top: 0 !important;
margin-inline-start: auto;
}
}
}
}

View File

@@ -1,16 +1,5 @@
@use 'scss/variables' as *;
.btn-default {
@include modal-button(standard);
padding: 0 20px;
font-weight: 500;
&:active {
transform: scale(0.98) !important;
}
}
.updateCheck {
flex-flow: row !important;
}
@@ -22,11 +11,6 @@
margin-top: 0;
float: none !important;
padding: 0 20px;
font-weight: 500;
&:active {
transform: scale(0.98) !important;
}
}
.btn-secondary {
@@ -36,11 +20,6 @@
margin-top: 0;
float: none !important;
padding: 0 20px;
font-weight: 500;
&:active {
transform: scale(0.98) !important;
}
}
.btn-navigation {
@@ -48,9 +27,7 @@
padding: 10px 20px;
border-radius: 12px !important;
transition:
background 0.2s ease,
transform 0.1s ease;
transition: all 0.2s ease;
position: relative;
@include themed {
@@ -71,22 +48,18 @@
&:hover {
@include themed {
background: rgb(0 0 0 / 4%) !important;
background: rgba(0, 0, 0, 0.04) !important;
}
.light & {
background: rgb(0 0 0 / 4%) !important;
background: rgba(0, 0, 0, 0.04) !important;
}
.dark & {
background: rgb(255 255 255 / 6%) !important;
background: rgba(255, 255, 255, 0.06) !important;
}
}
&:active {
transform: scale(0.98);
}
span,
svg {
font-size: 1.1em !important;
@@ -99,51 +72,6 @@
color: t($color);
}
}
.btn-badge {
margin-inline-start: 3px;
padding: 5px 7px;
border-radius: 6px;
font-size: 0.75em !important;
font-weight: 700;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
@include themed {
background-color: t($modal-sidebarActive);
color: t($color);
}
.light & {
background-color: rgb(0 0 0 / 8%);
color: rgb(0 0 0 / 80%);
}
.dark & {
background-color: rgb(255 255 255 / 12%);
color: rgb(255 255 255 / 90%);
}
}
&.btn-navigation-active .btn-badge {
@include themed {
background-color: rgb(0 0 0 / 8%);
color: rgb(0 0 0 / 80%);
}
.light & {
background-color: rgb(0 0 0 / 10%);
color: rgb(0 0 0 / 85%);
}
.dark & {
background-color: rgb(255 255 255 / 15%);
color: rgb(255 255 255 / 100%);
}
}
}
/* safari fix */
@@ -155,17 +83,17 @@
.btn-navigation-active {
@include themed {
background: rgb(0 0 0 / 6%) !important;
background: rgba(0, 0, 0, 0.06) !important;
box-shadow: none !important;
}
.light & {
background: rgb(0 0 0 / 6%) !important;
background: rgba(0, 0, 0, 0.06) !important;
border: none !important;
}
.dark & {
background: rgb(255 255 255 / 10%) !important;
background: rgba(255, 255, 255, 0.1) !important;
border: none !important;
}
}
@@ -184,11 +112,7 @@
flex-flow: row;
justify-content: center;
gap: 20px;
transition:
background 0.2s ease,
transform 0.1s ease,
border 0.2s ease,
box-shadow 0.2s ease;
transition: 0.5s;
cursor: pointer;
&:hover {
@@ -201,15 +125,13 @@
@include themed {
background: t($modal-sidebarActive);
box-shadow: 0 0 0 1px t($color);
transform: scale(0.98);
}
}
&:focus-visible {
&:focus {
@include themed {
background: t($modal-sidebarActive);
box-shadow: 0 0 0 2px t($color);
outline: none;
box-shadow: 0 0 0 1px t($color);
}
}
@@ -217,7 +139,6 @@
@include themed {
background: t($modal-sidebarActive);
cursor: not-allowed;
opacity: 0.5;
}
}
}
@@ -241,11 +162,6 @@ a.btn-collection {
display: grid;
place-content: center;
border-radius: 8px !important;
padding: 0 !important;
@include modal-button(standard);
&:active {
transform: scale(0.95) !important;
}
}

View File

@@ -1,79 +1,74 @@
@use 'scss/variables' as *;
.modalTabContent {
flex: 1;
min-width: 0;
width: 100% !important;
/* button {
@include modal-button(standard);
} */
padding: 1rem 2rem 5rem;
display: flex;
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
margin-bottom: 0;
padding-bottom: 2rem;
@include themed {
background: t($modal-background);
border-radius: t($borderRadius);
}
@extend %tabText;
hr {
width: 100%;
background: rgb(196 196 196 / 74%);
outline: none;
}
.settingsRow {
padding: 1rem 2rem 5rem;
display: flex;
align-items: center;
justify-content: space-between;
transition: 0.4s ease-in-out;
padding-top: 2rem;
padding-bottom: 2rem;
flex-direction: column;
width: 100%;
height: 100%;
overflow-y: auto;
background: t($modal-background);
@include themed {
border-bottom: 1px solid t($modal-border);
margin: 0;
border-radius: t($borderRadius);
@extend %tabText;
hr {
width: 100%;
background: rgb(196 196 196 / 74%);
outline: none;
}
&.settingsNoBorder {
border-bottom: none;
}
&:last-child {
margin-bottom: 2rem;
}
.content {
.settingsRow {
display: flex;
flex-flow: column;
max-width: 50%;
gap: 5px;
}
align-items: center;
justify-content: space-between;
transition: 0.4s ease-in-out;
.action {
display: flex;
flex-flow: column;
align-items: flex-end;
width: 300px;
gap: 10px;
/* border-top: 1px solid #ccc; */
border-bottom: 1px solid #676767;
padding-top: 1rem;
padding-bottom: 1rem;
button {
margin-top: 10px;
&.settingsNoBorder {
border-bottom: none;
}
.link {
margin-top: 10px;
&:last-child {
margin-bottom: 2rem;
}
.content {
display: flex;
flex-flow: row;
gap: 15px;
align-items: center;
flex-flow: column;
max-width: 50%;
}
.action {
display: flex;
flex-flow: column;
align-items: flex-end;
width: 300px;
button {
margin-top: 10px;
}
.link {
margin-top: 10px;
display: flex;
flex-flow: row;
gap: 15px;
align-items: center;
}
}
}
}
@@ -82,7 +77,7 @@
.resetDataButtonsLayout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
grid-gap: 20px;
:nth-child(1) {
grid-column: span 2;
@@ -251,13 +246,7 @@ table {
}
.messageAction {
[dir='ltr'] & {
float: right;
}
[dir='rtl'] & {
float: left;
}
float: right;
}
}
@@ -281,7 +270,7 @@ table {
hr {
height: 100%;
margin-inline-end: 5px;
margin-right: 5px;
@include themed {
border-color: t($modal-secondaryColour);
@@ -296,7 +285,8 @@ table {
transition: 0.5s;
cursor: pointer;
border: 0;
padding-inline: 10px 5px;
padding-left: 10px;
padding-right: 5px;
input[type='tel'],
input[type='number'] {

View File

@@ -4,87 +4,46 @@
@include themed {
position: relative;
margin: 0;
padding: 0.75rem 0.5rem;
// padding: 1rem 1.5rem 4rem 1.5rem;
padding: 0.5rem 0 0 0.5rem;
background: t($modal-sidebar);
border-start-start-radius: 12px;
border-end-start-radius: 12px;
border-start-end-radius: 0;
border-end-end-radius: 0;
overflow: hidden auto;
border-radius: 12px 0 0 12px;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
min-width: 250px;
flex-shrink: 0;
transition: min-width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
.sidebarHeader {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
.search-input-container {
flex: 1;
width: auto;
.search-input-field {
height: 38px;
font-size: 14px;
padding-inline: 42px 14px;
padding-block: 0;
border-radius: 10px;
&::placeholder {
opacity: 0.6;
}
}
.search-input-icon {
inset-inline-start: 16px;
font-size: 18px;
}
}
}
svg {
flex-shrink: 0;
width: 20px;
margin-left: 20px;
margin-right: 20px;
color: t($subColor);
font-size: 20px;
transition: color 0.2s ease;
font-size: 17px;
}
hr {
height: 1px;
background: t($modal-sidebarActive);
margin: 0.5rem 0.75rem;
background: #ccc;
margin: 0 1.75rem;
border: none;
opacity: 0.5;
transition: opacity 0.3s ease;
}
button:not(.sidebarToggleButton) {
button {
color: t($color);
font-size: 16px;
font-weight: 500;
font-size: 18px;
list-style: none;
cursor: pointer;
border-radius: 10px;
border-radius: 12px;
display: flex;
align-items: center;
gap: 12px;
margin: 0.15rem 0.25rem;
padding: 0.65rem 0.75rem;
transition:
background 0.2s ease,
transform 0.1s ease;
margin: 0.2rem;
padding: 0.5rem;
transition: 0.5s;
outline: none;
border: none;
background: none;
width: calc(100% - 0.5rem);
text-align: start;
position: relative;
min-width: calc(100% - 1.2em);
text-align: left;
&:last-child {
margin-bottom: 1rem;
@@ -92,145 +51,21 @@
&:hover {
background: t($modal-sidebarActive);
svg {
color: t($color);
}
}
&:active {
background: t($modal-sidebarActive);
transform: scale(0.98);
box-shadow: 0 0 0 0.5px t($color);
}
&:focus-visible {
&:focus {
background: t($modal-sidebarActive);
box-shadow: 0 0 0 2px t($color);
box-shadow: 0 0 0 0.5px t($color);
}
span {
white-space: nowrap;
max-width: 200px;
overflow: hidden;
transition:
opacity 0.25s ease,
max-width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
}
.sidebarEmptyState {
padding: 2rem 1rem;
text-align: center;
font-size: 14px;
color: t($color);
opacity: 0.6;
}
.tab-list-active {
background: t($modal-sidebarActive);
position: relative;
svg {
color: t($color);
}
&::before {
content: '';
position: absolute;
inset-inline-start: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 60%;
background: t($color);
[dir='ltr'] & {
border-radius: 0 2px 2px 0;
}
[dir='rtl'] & {
border-radius: 2px 0 0 2px;
}
}
}
&.collapsed {
min-width: 64px;
padding: 0.75rem 0.25rem;
.sidebarHeader {
justify-content: center;
}
button:not(.sidebarToggleButton) {
justify-content: center;
padding: 0.65rem;
gap: 0;
span {
opacity: 0;
max-width: 0;
margin: 0;
}
}
.tab-list-active::before {
inset-inline-start: 50%;
transform: translate(-50%, -50%);
width: 60%;
height: 3px;
top: auto;
bottom: 0;
border-radius: 2px 2px 0 0;
}
hr {
opacity: 0;
margin: 0.25rem 0.5rem;
}
}
}
}
.sidebarToggleButton {
@include themed {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
background: transparent;
border: none;
border-radius: 8px;
color: t($subColor);
cursor: pointer;
transition: all 0.2s ease;
outline: none;
flex-shrink: 0;
svg {
font-size: 18px;
transition: color 0.2s ease;
}
&:hover {
background: t($modal-sidebarActive);
color: t($color);
svg {
color: t($color);
}
}
&:active {
background: t($modal-sidebarActive);
transform: scale(0.95);
}
&:focus-visible {
background: t($modal-sidebarActive);
box-shadow: 0 0 0 2px t($color);
}
}
}
@@ -240,39 +75,10 @@
margin-right: 0 !important;
}
// Sidebar skeleton loader
.sidebarSkeleton {
padding: 0.5rem 0.2rem;
.skeletonHeader {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
.skeletonToggle {
width: 32px;
height: 32px;
border-radius: 8px;
flex-shrink: 0;
@include themed {
background: t($modal-sidebarActive);
}
}
.skeletonSearch {
flex: 1;
height: 38px;
border-radius: 10px;
@include themed {
background: t($modal-sidebarActive);
}
}
}
.skeletonItem {
display: flex;
align-items: center;
@@ -284,7 +90,8 @@
width: 17px;
height: 17px;
border-radius: 6px;
margin-inline: 20px;
margin-left: 20px;
margin-right: 20px;
flex-shrink: 0;
@include themed {

View File

@@ -7,11 +7,10 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem;
padding: 1.5rem 1.5rem;
// width: 100%;
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
backdrop-filter: blur(16px) saturate(180%);
@include themed {
@@ -75,7 +74,6 @@
.breadcrumb-segment {
display: flex;
align-items: center;
// gap: 0.5rem;
}
@@ -83,7 +81,6 @@
@include themed {
color: t($subColor);
}
white-space: nowrap;
transition: opacity 0.2s;
}
@@ -101,7 +98,6 @@
@include themed {
color: t($subColor);
}
opacity: 0.5;
font-size: 1.2rem;
margin: 0 0.25rem;
@@ -111,7 +107,6 @@
@include themed {
color: t($color);
}
font-weight: 600;
}
}

View File

@@ -6,6 +6,48 @@
@use 'modules/tabs/stats' as *;
input {
/* colour picker */
&[type='color'] {
border-radius: 100%;
height: 30px;
width: 30px;
border: none;
outline: none;
appearance: none;
vertical-align: middle;
background: none;
@include themed {
border: t($modal-sidebarActive) 1px solid;
}
&::-webkit-color-swatch-wrapper {
padding: 0;
}
&::-webkit-color-swatch {
border: none;
border-radius: 100%;
}
}
/* firefox fixes for colour picker (using "," didn't work) */
&[type='color']::-moz-color-swatch {
border-radius: 100%;
height: 30px;
width: 30px;
border: none;
outline: none;
appearance: none;
vertical-align: middle;
background: none;
&::-moz-color-swatch {
border: none;
border-radius: 100%;
}
}
/* date picker */
&[type='date'] {
width: 260px;
@@ -53,7 +95,7 @@ h4 {
position: sticky;
top: -20px;
z-index: 90;
padding: 25px 0 15px;
padding: 25px 0 15px 0;
display: flex;
flex-flow: row;
justify-content: space-between;
@@ -169,7 +211,7 @@ h4 {
background: none;
border: none;
padding: 0;
margin-inline-start: 5px;
margin-left: 5px;
cursor: pointer;
text-decoration: underline;
font-size: 14px;
@@ -188,7 +230,7 @@ h4 {
background: none;
border: none;
padding: 0;
margin-inline-start: 5px;
margin-left: 5px;
cursor: pointer;
text-decoration: underline;
font-size: 14px;
@@ -231,7 +273,7 @@ h4 {
transition: 0.4s ease-in-out;
}
/* Warning banner (used in Search settings and potentially others) */
// Warning banner (used in Search settings and potentially others)
.itemWarning {
padding: 10px 20px;
display: flex;

View File

@@ -18,7 +18,7 @@ legend {
}
.MuiFormControlLabel-labelPlacementStart {
margin-inline-start: 0 !important;
margin-left: 0 !important;
}
.MuiSwitch-colorPrimary.Mui-checked + .MuiSwitch-track {
@@ -152,10 +152,11 @@ legend,
.settingsRow {
.MuiFormControlLabel-root {
flex-direction: row-reverse;
margin-inline: 0;
margin-right: 0;
display: flex;
justify-content: space-between;
width: 100%;
margin-left: 0;
}
.MuiFormControlLabel-root {

View File

@@ -45,7 +45,6 @@
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);

View File

@@ -48,7 +48,7 @@
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
padding: 20px;
gap: 20px;
grid-gap: 20px;
@include themed {
div {
@@ -95,16 +95,17 @@
}
}
// Enhanced custom images grid
.images-grid {
display: grid;
padding: 1px;
// Show all checkboxes when in selection mode (any image selected)
&.selection-mode {
.image-checkbox {
opacity: 1;
}
}
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
@@ -125,8 +126,7 @@
&:hover {
transform: translateY(-4px);
// box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
.image-nav-buttons {
opacity: 1;
@@ -156,11 +156,11 @@
appearance: none;
border: 2px solid #fff;
border-radius: 4px;
background: rgb(0 0 0 / 60%);
backdrop-filter: blur(4px);
background: rgba(0, 0, 0, 0.6);
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
transition: all 0.2s;
box-shadow: 0 2px 8px rgb(0 0 0 / 30%);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
position: relative;
&:checked {
@@ -187,6 +187,7 @@
}
}
// Keep checkbox visible when checked
&:has(input:checked) {
opacity: 1;
}
@@ -243,8 +244,8 @@
height: 36px;
border-radius: 50%;
border: none;
background: rgb(0 0 0 / 60%);
backdrop-filter: blur(8px);
background: rgba(0, 0, 0, 0.6);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
color: #fff;
display: flex;
@@ -258,7 +259,7 @@
}
&:hover:not(:disabled) {
background: rgb(0 0 0 / 80%);
background: rgba(0, 0, 0, 0.8);
transform: scale(1.1);
}
@@ -313,7 +314,7 @@
height: 32px;
border-radius: 50%;
border: none;
background: rgb(255 71 87 / 90%);
background: rgba(255, 71, 87, 0.9);
color: #fff;
display: flex;
align-items: center;
@@ -322,7 +323,7 @@
opacity: 0;
transition: all 0.2s;
z-index: 11;
box-shadow: 0 2px 8px rgb(0 0 0 / 30%);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
svg {
font-size: 20px;
@@ -334,6 +335,7 @@
}
}
// Show delete button when card is hovered or has checkbox visible
&:hover .delete-button,
.image-checkbox:has(input:checked) ~ * .delete-button {
opacity: 1;
@@ -342,6 +344,7 @@
}
}
// Storage quota display
.storage-quota {
padding: 15px 20px;
margin-top: 10px;
@@ -410,6 +413,7 @@
}
}
// Folder tagging modal styles
.taggingModalContent {
padding: 20px;
@@ -447,7 +451,7 @@
&:focus {
border-color: #ff5c25;
box-shadow: 0 0 0 3px rgb(255 92 37 / 10%);
box-shadow: 0 0 0 3px rgba(255, 92, 37, 0.1);
}
}
}
@@ -501,7 +505,7 @@
.overviewGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
grid-gap: 30px;
}
.tabPreview {

View File

@@ -16,7 +16,7 @@
.statGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
grid-gap: 10px;
div {
display: flex;
@@ -43,7 +43,7 @@
.achievementsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 10px;
grid-gap: 10px;
}
.achievement {
@@ -134,6 +134,7 @@
button {
margin-bottom: 15px;
flex-flow: row !important;
padding-inline: 20px;
padding-left: 20px;
padding-right: 20px;
}
}

View File

@@ -1,12 +1,10 @@
import { memo } from 'react';
import { useT } from 'contexts';
import variables from 'config/variables';
import { MdClose, MdRestartAlt } from 'react-icons/md';
import { setDefaultSettings } from 'utils/settings';
import { Tooltip, Button } from 'components/Elements';
function ResetModal({ modalClose }) {
const t = useT();
const reset = () => {
variables.stats.postEvent('setting', 'Reset');
setDefaultSettings('reset');
@@ -17,32 +15,34 @@ function ResetModal({ modalClose }) {
<div className="smallModal">
<div className="shareHeader">
<span className="title">
{t('modals.main.settings.sections.advanced.reset_modal.title')}
{variables.getMessage('modals.main.settings.sections.advanced.reset_modal.title')}
</span>
<Tooltip title={t('modals.main.settings.sections.advanced.reset_modal.cancel')}>
<Tooltip
title={variables.getMessage('modals.main.settings.sections.advanced.reset_modal.cancel')}
>
<div className="close" onClick={modalClose}>
<MdClose />
</div>
</Tooltip>
</div>
<span className="title">
{t('modals.main.settings.sections.advanced.reset_modal.question')}
{variables.getMessage('modals.main.settings.sections.advanced.reset_modal.question')}
</span>
<span className="subtitle">
{t('modals.main.settings.sections.advanced.reset_modal.information')}
{variables.getMessage('modals.main.settings.sections.advanced.reset_modal.information')}
</span>
<div className="resetFooter">
<Button
type="secondary"
onClick={modalClose}
icon={<MdClose />}
label={t('modals.main.settings.sections.advanced.reset_modal.cancel')}
label={variables.getMessage('modals.main.settings.sections.advanced.reset_modal.cancel')}
/>
<Button
type="settings"
onClick={() => reset()}
icon={<MdRestartAlt />}
label={t('modals.main.settings.buttons.reset')}
label={variables.getMessage('modals.main.settings.buttons.reset')}
/>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { memo } from 'react';
import { useT } from 'contexts';
import variables from 'config/variables';
import { MdClose, MdEmail, MdContentCopy } from 'react-icons/md';
import { FaFacebookF } from 'react-icons/fa';
import { AiFillWechat } from 'react-icons/ai';
@@ -12,34 +12,37 @@ import { Button } from 'components/Elements';
import './sharemodal.scss';
function ShareModal({ modalClose, data }) {
const t = useT();
if (data.startsWith('https://cdn.')) {
data = {
url: data,
name: t('modals.share.item_type.image'),
name: 'this image',
};
} else if (data.startsWith('"')) {
data = {
url: data,
name: t('modals.share.item_type.quote'),
name: 'this quote',
};
} else {
data = {
url: data,
name: t('modals.share.item_type.marketplace_item'),
name: 'this marketplace item',
};
}
const copyLink = () => {
navigator.clipboard.writeText(data.url);
toast(data.startsWith('"') ? t('toasts.quote') : t('toasts.link_copied'));
toast(
data.startsWith('"')
? variables.getMessage('toasts.quote')
: variables.getMessage('toasts.link_copied'),
);
};
return (
<div className="smallModal">
<div className="shareHeader">
<span className="title">{t('widgets.quote.share')}</span>
<Tooltip title={t('modals.welcome.buttons.close')}>
<span className="title">{variables.getMessage('widgets.quote.share')}</span>
<Tooltip title={variables.getMessage('modals.welcome.buttons.close')}>
<div className="close" onClick={modalClose}>
<MdClose />
</div>
@@ -50,13 +53,13 @@ function ShareModal({ modalClose, data }) {
onClick={() =>
window
.open(
`https://x.com/intent/tweet?text=${t('modals.share.twitter_message', { name: data.name })}: ${data.url}`,
`https://x.com/intent/tweet?text=Check out ${data.name} on @getmue: ${data.url}`,
'_blank',
)
.focus()
}
icon={<SiX />}
tooltipTitle={t('modals.share.social.twitter')}
tooltipTitle="X (Twitter)"
type="icon"
/>
<Button
@@ -66,20 +69,23 @@ function ShareModal({ modalClose, data }) {
.focus()
}
icon={<FaFacebookF />}
tooltipTitle={t('modals.share.social.facebook')}
tooltipTitle="Facebook"
type="icon"
/>
<Button
onClick={() =>
window
.open(
`mailto:email@example.com?subject=${encodeURIComponent(t('modals.share.email_subject'))}&body=${encodeURIComponent(t('modals.share.email_body', { name: data.name, url: data.url }))}`,
'mailto:email@example.com?subject=Check%20out%20this%20%on%20%Mue!&body=' +
data.name +
'on Mue: ' +
data.url,
'_blank',
)
.focus()
}
icon={<MdEmail />}
tooltipTitle={t('modals.share.social.email')}
tooltipTitle="Email"
type="icon"
/>
<Button
@@ -92,7 +98,7 @@ function ShareModal({ modalClose, data }) {
.focus()
}
icon={<AiFillWechat />}
tooltipTitle={t('modals.share.social.wechat')}
tooltipTitle="WeChat"
type="icon"
/>
<Button
@@ -102,7 +108,7 @@ function ShareModal({ modalClose, data }) {
.focus()
}
icon={<SiTencentqq />}
tooltipTitle={t('modals.share.social.qq')}
tooltipTitle="Tencent QQ"
type="icon"
/>
</div>
@@ -111,7 +117,7 @@ function ShareModal({ modalClose, data }) {
<Button
onClick={() => copyLink()}
icon={<MdContentCopy />}
tooltipTitle={t('modals.share.copy_link')}
tooltipTitle={variables.getMessage('modals.share.copy_link')}
type="icon"
/>
</div>

View File

@@ -1,83 +0,0 @@
import { useState, useEffect } from 'react';
import { IconService } from 'utils/quicklinks';
import './smarticon.scss';
export const SmartIcon = ({ item, size = 32, fallbackChain, className = '' }) => {
const [currentIconIndex, setCurrentIconIndex] = useState(0);
const [iconUrls, setIconUrls] = useState([]);
const [hasError, setHasError] = useState(false);
const [showPlaceholder, setShowPlaceholder] = useState(false);
useEffect(() => {
const urls = IconService.getIconUrl(item, fallbackChain);
setIconUrls(Array.isArray(urls) ? urls : [urls]);
setCurrentIconIndex(0);
setHasError(false);
setShowPlaceholder(false);
}, [item, fallbackChain]);
const handleImageError = () => {
if (currentIconIndex < iconUrls.length - 1) {
setCurrentIconIndex((prev) => prev + 1);
setHasError(false);
} else {
setHasError(true);
setShowPlaceholder(true);
}
};
if (item.iconType === 'emoji' && item.icon) {
return (
<div className={`smart-icon emoji ${className}`} style={{ fontSize: size }}>
{item.icon}
</div>
);
}
if (item.iconType === 'letter' || showPlaceholder) {
const avatar = IconService.generateLetterAvatar(item.name);
return (
<div
className={`smart-icon letter-avatar ${className}`}
style={{
width: size,
height: size,
backgroundColor: avatar.backgroundColor,
fontSize: size * 0.5,
}}
>
{avatar.letter}
</div>
);
}
const currentUrl = iconUrls[currentIconIndex];
if (!currentUrl) {
const avatar = IconService.generateLetterAvatar(item.name);
return (
<div
className={`smart-icon letter-avatar ${className}`}
style={{
width: size,
height: size,
backgroundColor: avatar.backgroundColor,
fontSize: size * 0.5,
}}
>
{avatar.letter}
</div>
);
}
return (
<img
src={currentUrl}
alt={item.name}
className={`smart-icon ${className}`}
style={{ width: size, height: size }}
onError={handleImageError}
draggable={false}
loading="lazy"
/>
);
};

View File

@@ -1 +0,0 @@
export { SmartIcon } from './SmartIcon';

View File

@@ -1,26 +0,0 @@
.smart-icon {
display: inline-flex;
align-items: center;
justify-content: center;
user-select: none;
flex-shrink: 0;
&.emoji {
line-height: 1;
}
&.letter-avatar {
border-radius: 50%;
color: white;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
display: flex;
align-items: center;
justify-content: center;
}
img {
object-fit: contain;
}
}

View File

@@ -1,12 +1,12 @@
import { useState, memo, useRef, useId } from 'react';
import { useState, memo, useRef } from 'react';
import { useFloating, flip, offset, shift } from '@floating-ui/react-dom';
import './tooltip.scss';
function Tooltip({ children, title, style, placement, subtitle }) {
const [showTooltip, setShowTooltip] = useState(false);
const [closing, setClosing] = useState(false);
const [isClosing, setIsClosing] = useState(false);
const [reference, setReference] = useState(null);
const tooltipId = useId();
const tooltipId = useRef(`tooltip-${Math.random()}`);
const closeTimeout = useRef(null);
const {
@@ -23,23 +23,23 @@ function Tooltip({ children, title, style, placement, subtitle }) {
},
});
const { setFloating } = refs;
const handleMouseEnter = () => {
// Clear any pending close timeout if mouse re-enters during exit
if (closeTimeout.current) {
clearTimeout(closeTimeout.current);
closeTimeout.current = null;
}
setClosing(false);
setIsClosing(false);
setShowTooltip(true);
};
const handleMouseLeave = () => {
setClosing(true);
setIsClosing(true);
// Wait for exit animation to complete before unmounting
closeTimeout.current = setTimeout(() => {
setShowTooltip(false);
setClosing(false);
}, 200);
setIsClosing(false);
}, 200); // Match exit animation duration
};
const handleFocus = () => {
@@ -47,25 +47,22 @@ function Tooltip({ children, title, style, placement, subtitle }) {
clearTimeout(closeTimeout.current);
closeTimeout.current = null;
}
setClosing(false);
setIsClosing(false);
setShowTooltip(true);
};
const handleBlur = () => {
setClosing(true);
setIsClosing(true);
closeTimeout.current = setTimeout(() => {
setShowTooltip(false);
setClosing(false);
setIsClosing(false);
}, 200);
};
// Determine the data-status attribute value
const getStatus = () => {
if (!showTooltip && !closing) {
return 'initial';
}
if (closing) {
return 'close';
}
if (!showTooltip && !isClosing) return 'initial';
if (isClosing) return 'close';
return 'open';
};
@@ -79,13 +76,13 @@ function Tooltip({ children, title, style, placement, subtitle }) {
onFocus={handleFocus}
onBlur={handleBlur}
ref={setReference}
aria-describedby={tooltipId}
aria-describedby={tooltipId.current}
>
{children}
</div>
{(showTooltip || closing) && (
{(showTooltip || isClosing) && (
<span
ref={setFloating}
ref={refs.setFloating}
style={{
position: strategy,
top: y ?? '',

View File

@@ -5,54 +5,6 @@
display: grid;
}
@keyframes floatingFromTop {
0% {
transform: translateY(5px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes floatingFromBottom {
0% {
transform: translateY(-5px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes floatingFromLeft {
0% {
transform: translateX(5px);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
@keyframes floatingFromRight {
0% {
transform: translateX(-5px);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
@keyframes floating {
0% {
transform: translate(0, -5px);
@@ -85,36 +37,21 @@
opacity 0.2s ease-out,
transform 0.2s ease-out;
/* Initial state (not yet shown) */
// Initial state (not yet shown)
&[data-status='initial'] {
opacity: 0;
}
/* Open state - entrance animation */
// Open state - entrance animation
&[data-status='open'] {
opacity: 1;
transform: translate(0, 0);
animation-name: floating;
animation-duration: 0.3s;
animation-timing-function: ease-in;
&[data-placement^='top'] {
animation-name: floatingFromTop;
}
&[data-placement^='bottom'] {
animation-name: floatingFromBottom;
}
&[data-placement^='left'] {
animation-name: floatingFromLeft;
}
&[data-placement^='right'] {
animation-name: floatingFromRight;
}
}
/* Closing state - exit animation */
// Closing state - exit animation
&[data-status='close'] {
opacity: 0;

View File

@@ -18,28 +18,27 @@ const Checkbox = memo((props) => {
props.onChange(value);
}
variables.stats.postEvent('setting', `${props.name} ${value ? 'enabled' : 'disabled'}`);
variables.stats.postEvent(
'setting',
`${props.name} ${value ? 'enabled' : 'disabled'}`,
);
if (props.element) {
if (!document.querySelector(props.element)) {
localStorage.setItem('showReminder', 'true');
EventBus.emit('showReminder');
return;
document.querySelector('.reminder-info').style.display = 'flex';
return localStorage.setItem('showReminder', true);
}
}
EventBus.emit('refresh', props.category);
}, [checked, props]);
const handleKeyDown = useCallback(
(e) => {
if ((e.key === ' ' || e.key === 'Enter') && !props.disabled) {
e.preventDefault();
handleChange();
}
},
[handleChange, props.disabled],
);
const handleKeyDown = useCallback((e) => {
if ((e.key === ' ' || e.key === 'Enter') && !props.disabled) {
e.preventDefault();
handleChange();
}
}, [handleChange, props.disabled]);
return (
<div className={`checkbox-wrapper ${props.disabled ? 'disabled' : ''}`}>
@@ -54,7 +53,9 @@ const Checkbox = memo((props) => {
aria-label={props.text}
onKeyDown={handleKeyDown}
/>
<div className={`checkbox-box ${checked ? 'checked' : ''}`}>{checked && <MdCheck />}</div>
<div className={`checkbox-box ${checked ? 'checked' : ''}`}>
{checked && <MdCheck />}
</div>
</div>
);
});

View File

@@ -41,7 +41,6 @@
flex: 1;
transition: color 0.2s ease;
pointer-events: none;
font-size: 1rem;
@include themed {
color: t($color);
@@ -75,7 +74,7 @@
transform: scale(0.95);
@include themed {
box-shadow: 0 0 0 4px rgb(255 92 37 / 10%);
box-shadow: 0 0 0 4px rgba(255, 92, 37, 0.1);
}
}
@@ -91,7 +90,7 @@
}
svg {
font-size: 1.125rem;
font-size: 18px;
color: white;
}
}

View File

@@ -1,97 +1,28 @@
import { useState, memo, useRef, useEffect, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { MdExpandMore, MdClose, MdCheck } from 'react-icons/md';
import { useState, memo, useRef, useEffect } from 'react';
import { MdExpandMore, MdClose } from 'react-icons/md';
import './ChipSelect.scss';
function ChipSelect({ label, options, onChange, name }) {
const storageKey = name || 'apiCategories';
let start = (localStorage.getItem(storageKey) || '').split(',');
function ChipSelect({ label, options, onChange }) {
let start = (localStorage.getItem('apiCategories') || '').split(',');
if (start[0] === '') {
start = [];
}
const [optionsSelected, setOptionsSelected] = useState(start);
const [open, setOpen] = useState(false);
const [closing, setClosing] = useState(false);
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0, width: 0 });
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef(null);
const controlRef = useRef(null);
const menuRef = useRef(null);
const closeDropdown = useCallback(() => {
setClosing(true);
setTimeout(() => {
setOpen(false);
setClosing(false);
}, 200);
}, []);
useEffect(() => {
const handleClickOutside = (event) => {
// Ignore clicks on color inputs to prevent closing when native color picker opens
if (event.target.type === 'color') {
return;
}
// Also ignore clicks on color input labels and wrappers
const target = event.target;
if (target.tagName === 'LABEL' && target.htmlFor) {
const associatedInput = document.getElementById(target.htmlFor) || document.querySelector(`input[name="${target.htmlFor}"]`);
if (associatedInput && associatedInput.type === 'color') {
return;
}
}
// Check if clicking within a color input wrapper
const colorInputWrapper = target.closest('.colour-picker');
if (colorInputWrapper && colorInputWrapper.querySelector('input[type="color"]')) {
return;
}
if (
containerRef.current &&
!containerRef.current.contains(event.target) &&
menuRef.current &&
!menuRef.current.contains(event.target)
) {
closeDropdown();
if (containerRef.current && !containerRef.current.contains(event.target)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [closeDropdown]);
const calculatePosition = useCallback(() => {
if (controlRef.current) {
const rect = controlRef.current.getBoundingClientRect();
const gap = 4;
const viewportHeight = window.innerHeight;
const estimatedMenuHeight = Math.min(options.length * 44, 250);
const spaceBelow = viewportHeight - rect.bottom - gap;
const spaceAbove = rect.top - gap;
const shouldFlipUp = spaceBelow < estimatedMenuHeight && spaceAbove > spaceBelow;
return {
top: shouldFlipUp ? rect.top - gap : rect.bottom + gap,
left: rect.left,
width: rect.width,
maxHeight: shouldFlipUp ? Math.min(250, spaceAbove) : Math.min(250, spaceBelow),
flipped: shouldFlipUp,
};
}
return { top: 0, left: 0, width: 0, maxHeight: 250, flipped: false };
}, [options]);
const openDropdown = useCallback(() => {
const position = calculatePosition();
setMenuPosition(position);
setOpen(true);
}, [calculatePosition]);
}, []);
const handleToggle = (optionName) => {
let newSelected;
@@ -102,8 +33,7 @@ function ChipSelect({ label, options, onChange, name }) {
}
setOptionsSelected(newSelected);
const storageKey = name || 'apiCategories';
localStorage.setItem(storageKey, newSelected.join(','));
localStorage.setItem('apiCategories', newSelected.join(','));
if (onChange) {
onChange(newSelected);
@@ -118,17 +48,7 @@ function ChipSelect({ label, options, onChange, name }) {
return (
<div className="chipSelect" ref={containerRef}>
{label && <label className="chipSelect-label">{label}</label>}
<div
ref={controlRef}
className="chipSelect-control"
onClick={() => {
if (open) {
closeDropdown();
} else {
openDropdown();
}
}}
>
<div className="chipSelect-control" onClick={() => setIsOpen(!isOpen)}>
<div className="chipSelect-value">
{optionsSelected.length === 0 ? (
<span className="chipSelect-placeholder">Select options...</span>
@@ -149,40 +69,27 @@ function ChipSelect({ label, options, onChange, name }) {
</div>
)}
</div>
<MdExpandMore className={`chipSelect-arrow ${open ? 'open' : ''}`} />
<MdExpandMore className={`chipSelect-arrow ${isOpen ? 'open' : ''}`} />
</div>
{(open || closing) &&
createPortal(
<div
ref={menuRef}
className={`chipSelect-dropdown ${closing ? 'closing' : ''} ${menuPosition.flipped ? 'flipped' : ''}`}
style={{
position: 'fixed',
top: `${menuPosition.top}px`,
left: `${menuPosition.left}px`,
width: `${menuPosition.width}px`,
maxHeight: menuPosition.maxHeight ? `${menuPosition.maxHeight}px` : '250px',
transform: menuPosition.flipped ? 'translateY(-100%)' : 'none',
}}
>
{options.map((option) => (
<div
key={option.name}
className={`chipSelect-option ${optionsSelected.includes(option.name) ? 'selected' : ''}`}
onClick={() => handleToggle(option.name)}
>
<div className="chipSelect-option-checkbox">
{optionsSelected.includes(option.name) && <MdCheck />}
</div>
<span className="chipSelect-option-label">
{option.name.charAt(0).toUpperCase() + option.name.slice(1)}
{option.count && ` (${option.count})`}
</span>
</div>
))}
</div>,
document.body,
)}
{isOpen && (
<div className="chipSelect-dropdown">
{options.map((option) => (
<div
key={option.name}
className={`chipSelect-option ${optionsSelected.includes(option.name) ? 'selected' : ''}`}
onClick={() => handleToggle(option.name)}
>
<span className="chipSelect-option-checkbox">
{optionsSelected.includes(option.name) && '✓'}
</span>
<span className="chipSelect-option-label">
{option.name.charAt(0).toUpperCase() + option.name.slice(1)}
{option.count && ` (${option.count})`}
</span>
</div>
))}
</div>
)}
</div>
);
}

View File

@@ -1,64 +1,14 @@
@use 'scss/variables' as *;
@use 'scss/mixins' as *;
@include keyframes(chipSelectSlideIn) {
0% {
opacity: 0;
transform: translateY(-10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@include keyframes(chipSelectSlideOut) {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-10px);
}
}
@include keyframes(chipSelectSlideInUp) {
0% {
opacity: 0;
transform: translateY(-100%) translateY(10px);
}
100% {
opacity: 1;
transform: translateY(-100%);
}
}
@include keyframes(chipSelectSlideOutUp) {
0% {
opacity: 1;
transform: translateY(-100%);
}
100% {
opacity: 0;
transform: translateY(-100%) translateY(10px);
}
}
.chipSelect {
position: relative;
width: 300px;
margin-top: 10px;
gap: 8px;
display: flex;
flex-flow: column;
.chipSelect-label {
font-size: 0.75rem;
display: block;
margin-bottom: 8px;
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
@@ -73,10 +23,9 @@
align-items: center;
justify-content: space-between;
min-height: 56px;
padding: 0 16px;
padding: 8px 12px;
cursor: pointer;
transition: all 0.2s ease;
outline: none;
transition: 0.2s ease;
@include themed {
background: t($modal-sidebar);
@@ -93,7 +42,6 @@
.chipSelect-value {
flex: 1;
min-width: 0;
padding: 8px 0;
}
.chipSelect-placeholder {
@@ -111,11 +59,10 @@
.chipSelect-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
font-size: 0.8125rem;
gap: 4px;
padding: 4px 8px;
font-size: 13px;
text-transform: capitalize;
transition: all 0.15s ease;
@include themed {
background: t($modal-sidebarActive);
@@ -127,34 +74,32 @@
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
padding: 0;
margin: 0;
padding: 2px;
margin-left: 2px;
border: none;
background: transparent;
cursor: pointer;
border-radius: 50%;
transition: all 0.15s ease;
transition: 0.2s ease;
@include themed {
color: t($subColor);
&:hover {
background: rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.1);
color: t($color);
}
}
svg {
font-size: 0.75rem;
font-size: 14px;
}
}
}
.chipSelect-arrow {
flex-shrink: 0;
font-size: 1.5rem;
font-size: 24px;
transition: transform 0.2s ease;
@include themed {
@@ -165,108 +110,69 @@
transform: rotate(180deg);
}
}
}
.chipSelect-dropdown {
max-height: 250px;
overflow-y: auto;
z-index: 9999;
@include animation(chipSelectSlideIn 0.2s ease-out);
will-change: transform, opacity;
.chipSelect-dropdown {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
max-height: 250px;
overflow-y: auto;
z-index: 100;
@include themed {
background: t($modal-background);
border: 1px solid t($modal-sidebarActive);
border-radius: t($borderRadius);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
&.flipped {
@include animation(chipSelectSlideInUp 0.2s ease-out);
&.closing {
@include animation(chipSelectSlideOutUp 0.2s ease-out forwards);
}
}
&.closing:not(.flipped) {
@include animation(chipSelectSlideOut 0.2s ease-out forwards);
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
@include themed {
background: t($modal-sidebar);
background: t($modal-background);
border: 1px solid t($modal-sidebarActive);
border-radius: t($borderRadius);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
&::-webkit-scrollbar-thumb {
@include themed {
background: t($modal-sidebarActive);
border-radius: 3px;
}
&:hover {
@include themed {
background: t($color);
}
}
}
}
.chipSelect-option {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
cursor: pointer;
transition: all 0.15s ease;
outline: none;
@include themed {
color: t($color);
&:hover {
background: t($modal-sidebarActive);
padding-left: 20px;
}
&.selected {
background: t($modal-sidebar);
}
}
.chipSelect-option-checkbox {
flex-shrink: 0;
.chipSelect-option {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 4px;
gap: 10px;
padding: 12px 16px;
cursor: pointer;
transition: 0.2s ease;
@include themed {
border: 2px solid t($modal-sidebarActive);
color: t($color);
&:hover {
background: t($modal-sidebarActive);
}
&.selected {
background: t($modal-sidebar);
}
}
svg {
font-size: 0.875rem;
}
}
.chipSelect-option-checkbox {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
font-size: 12px;
font-weight: bold;
&.selected .chipSelect-option-checkbox {
@include themed {
background: t($link);
border-color: t($link);
color: white;
@include themed {
border: 2px solid t($modal-sidebarActive);
border-radius: 4px;
color: t($color);
}
}
}
.chipSelect-option-label {
flex: 1;
&.selected .chipSelect-option-checkbox {
@include themed {
background: t($modal-sidebarActive);
border-color: t($color);
}
}
.chipSelect-option-label {
flex: 1;
}
}
}

Some files were not shown because too many files have changed in this diff Show More