Compare commits
2 Commits
49e024343e
...
b6df2f1a34
Author | SHA1 | Date | |
---|---|---|---|
b6df2f1a34 | |||
c2ff03521c |
@ -15,6 +15,7 @@
|
|||||||
iconSizeValue: 2, // Slider value: 1=small, 2=medium, 3=large
|
iconSizeValue: 2, // Slider value: 1=small, 2=medium, 3=large
|
||||||
viewMode: 'grid',
|
viewMode: 'grid',
|
||||||
displayMode: 'both',
|
displayMode: 'both',
|
||||||
|
groupByCategory: false, // Whether to group services by category (default: ungrouped)
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Load preferences from localStorage
|
// Load preferences from localStorage
|
||||||
@ -23,6 +24,9 @@
|
|||||||
// Set initial iconSizeValue based on iconSize
|
// Set initial iconSizeValue based on iconSize
|
||||||
this.updateSliderFromIconSize();
|
this.updateSliderFromIconSize();
|
||||||
|
|
||||||
|
// Apply initial grouping mode
|
||||||
|
this.applyGroupingModeDirectly(this.groupByCategory);
|
||||||
|
|
||||||
// Listen for events from searchServices component
|
// Listen for events from searchServices component
|
||||||
window.addEventListener('styleControls:updateIconSize', (e) => {
|
window.addEventListener('styleControls:updateIconSize', (e) => {
|
||||||
if (e.detail && e.detail.size) {
|
if (e.detail && e.detail.size) {
|
||||||
@ -65,6 +69,12 @@
|
|||||||
this.displayMode = savedDisplayMode;
|
this.displayMode = savedDisplayMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load grouping preference
|
||||||
|
const savedGrouping = localStorage.getItem('services-group-by-category');
|
||||||
|
if (savedGrouping !== null) {
|
||||||
|
this.groupByCategory = savedGrouping === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
// Update slider value based on loaded icon size
|
// Update slider value based on loaded icon size
|
||||||
this.updateSliderFromIconSize();
|
this.updateSliderFromIconSize();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -79,6 +89,7 @@
|
|||||||
localStorage.setItem('services-icon-size', this.iconSize);
|
localStorage.setItem('services-icon-size', this.iconSize);
|
||||||
localStorage.setItem('services-view-mode', this.viewMode);
|
localStorage.setItem('services-view-mode', this.viewMode);
|
||||||
localStorage.setItem('services-display-mode', this.displayMode);
|
localStorage.setItem('services-display-mode', this.displayMode);
|
||||||
|
localStorage.setItem('services-group-by-category', this.groupByCategory);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error saving preferences:', e);
|
console.error('Error saving preferences:', e);
|
||||||
}
|
}
|
||||||
@ -237,6 +248,110 @@
|
|||||||
appList.classList.add('display-both');
|
appList.classList.add('display-both');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Toggle grouping of services by category
|
||||||
|
toggleGrouping() {
|
||||||
|
this.groupByCategory = !this.groupByCategory;
|
||||||
|
this.savePreferences();
|
||||||
|
|
||||||
|
// Apply grouping mode directly with DOM restructuring
|
||||||
|
this.applyGroupingModeDirectly(this.groupByCategory);
|
||||||
|
|
||||||
|
// Dispatch event to notify searchServices component
|
||||||
|
window.dispatchEvent(new CustomEvent('searchServices:setGrouping', {
|
||||||
|
detail: { groupByCategory: this.groupByCategory }
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Apply grouping mode directly to DOM elements with full restructuring
|
||||||
|
applyGroupingModeDirectly(groupByCategory) {
|
||||||
|
console.log('Applying grouping mode directly:', groupByCategory);
|
||||||
|
const appList = document.getElementById('app-list');
|
||||||
|
if (appList) {
|
||||||
|
// Toggle class on app-list for any global styling
|
||||||
|
appList.classList.toggle('no-category-grouping', !groupByCategory);
|
||||||
|
|
||||||
|
if (groupByCategory) {
|
||||||
|
// GROUPED MODE: Restore original category structure
|
||||||
|
|
||||||
|
// Get unified grid if it exists
|
||||||
|
const unifiedGrid = document.getElementById('unified-grid-container');
|
||||||
|
if (unifiedGrid) {
|
||||||
|
// Get all card wrappers in unified grid (these are the ScrollReveal containers)
|
||||||
|
const cardWrappers = Array.from(unifiedGrid.querySelectorAll('.scroll-reveal'));
|
||||||
|
|
||||||
|
// Show all category sections
|
||||||
|
document.querySelectorAll('.category-section').forEach(section => {
|
||||||
|
section.classList.remove('no-grouping');
|
||||||
|
section.style.display = '';
|
||||||
|
const categoryTitle = section.querySelector('.category-toggle');
|
||||||
|
if (categoryTitle) categoryTitle.style.display = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Move each card wrapper back to its original category
|
||||||
|
cardWrappers.forEach(wrapper => {
|
||||||
|
// Find the app card inside the wrapper
|
||||||
|
const card = wrapper.querySelector('.app-card');
|
||||||
|
if (card) {
|
||||||
|
const categoryName = card.getAttribute('data-app-category');
|
||||||
|
if (categoryName) {
|
||||||
|
const originalSection = document.querySelector(`.category-section[data-category="${categoryName}"]`);
|
||||||
|
if (originalSection) {
|
||||||
|
const gridContainer = originalSection.querySelector('.grid');
|
||||||
|
if (gridContainer) {
|
||||||
|
// Move the wrapper back to its original grid
|
||||||
|
gridContainer.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove unified grid
|
||||||
|
unifiedGrid.remove();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// UNGROUPED MODE: Create single unified grid
|
||||||
|
|
||||||
|
// Create unified grid if it doesn't exist
|
||||||
|
let unifiedGrid = document.getElementById('unified-grid-container');
|
||||||
|
if (!unifiedGrid) {
|
||||||
|
unifiedGrid = document.createElement('div');
|
||||||
|
unifiedGrid.id = 'unified-grid-container';
|
||||||
|
unifiedGrid.className = 'grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-6';
|
||||||
|
// Add any necessary styles directly to ensure consistent spacing
|
||||||
|
unifiedGrid.style.gap = '1.5rem';
|
||||||
|
unifiedGrid.style.padding = '0.5rem';
|
||||||
|
appList.appendChild(unifiedGrid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each category section
|
||||||
|
document.querySelectorAll('.category-section').forEach(section => {
|
||||||
|
section.classList.add('no-grouping');
|
||||||
|
const categoryTitle = section.querySelector('.category-toggle');
|
||||||
|
if (categoryTitle) categoryTitle.style.display = 'none';
|
||||||
|
|
||||||
|
// Ensure all categories are expanded
|
||||||
|
if (section.__x) {
|
||||||
|
section.__x.$data.open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all card wrappers in this category (these are the ScrollReveal containers)
|
||||||
|
const cardWrappers = Array.from(section.querySelectorAll('.scroll-reveal'));
|
||||||
|
|
||||||
|
// Move all card wrappers to unified grid
|
||||||
|
cardWrappers.forEach(wrapper => {
|
||||||
|
unifiedGrid.appendChild(wrapper);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hide the now-empty category section
|
||||||
|
if (cardWrappers.length > 0) {
|
||||||
|
section.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -321,6 +436,16 @@
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Listen for grouping changes
|
||||||
|
window.addEventListener('searchServices:setGrouping', (e) => {
|
||||||
|
if (e.detail && e.detail.groupByCategory !== undefined) {
|
||||||
|
console.log('searchServices received grouping event:', e.detail.groupByCategory);
|
||||||
|
|
||||||
|
// Update any searchServices related functionality if needed
|
||||||
|
// This is primarily handled by styleControls but can be extended here
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setupWatchers() {
|
setupWatchers() {
|
||||||
@ -467,7 +592,20 @@
|
|||||||
// Add the new view mode class
|
// Add the new view mode class
|
||||||
appList.classList.add(`view-mode-${this.viewMode}`);
|
appList.classList.add(`view-mode-${this.viewMode}`);
|
||||||
|
|
||||||
// Update all category sections
|
// Check for unified grid (in ungrouped mode)
|
||||||
|
const unifiedGrid = document.getElementById('unified-grid-container');
|
||||||
|
if (unifiedGrid) {
|
||||||
|
// Update unified grid classes based on view mode
|
||||||
|
if (this.viewMode === 'grid') {
|
||||||
|
unifiedGrid.classList.remove('grid-cols-1');
|
||||||
|
unifiedGrid.classList.add('grid-cols-2', 'sm:grid-cols-3', 'lg:grid-cols-4');
|
||||||
|
} else {
|
||||||
|
unifiedGrid.classList.remove('grid-cols-2', 'sm:grid-cols-3', 'lg:grid-cols-4');
|
||||||
|
unifiedGrid.classList.add('grid-cols-1');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all category sections (for grouped mode)
|
||||||
document.querySelectorAll('.category-section').forEach(section => {
|
document.querySelectorAll('.category-section').forEach(section => {
|
||||||
const gridContainer = section.querySelector('.grid');
|
const gridContainer = section.querySelector('.grid');
|
||||||
if (gridContainer) {
|
if (gridContainer) {
|
||||||
|
@ -50,6 +50,11 @@
|
|||||||
} else if (e.altKey && e.key === 'n' && styleComponent.setDisplayMode) {
|
} else if (e.altKey && e.key === 'n' && styleComponent.setDisplayMode) {
|
||||||
styleComponent.setDisplayMode('name');
|
styleComponent.setDisplayMode('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alt+C for category grouping toggle
|
||||||
|
if (e.altKey && e.key === 'c' && styleComponent.toggleGrouping) {
|
||||||
|
styleComponent.toggleGrouping();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -31,6 +31,7 @@ const id = `scroll-reveal-${crypto.randomUUID().slice(0, 8)}`;
|
|||||||
data-threshold={threshold}
|
data-threshold={threshold}
|
||||||
data-root-margin={rootMargin}
|
data-root-margin={rootMargin}
|
||||||
data-once={once}
|
data-once={once}
|
||||||
|
style="display: contents;"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,15 +94,18 @@ const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`;
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
/* Transitions */
|
/* Enhanced transitions for smoother mode changes */
|
||||||
transition:
|
transition:
|
||||||
transform var(--card-transition-duration) var(--card-transition-timing),
|
transform var(--card-transition-duration) var(--card-transition-timing),
|
||||||
box-shadow var(--card-transition-duration) var(--card-transition-timing),
|
box-shadow var(--card-transition-duration) var(--card-transition-timing),
|
||||||
border-color var(--card-transition-duration) var(--card-transition-timing),
|
border-color var(--card-transition-duration) var(--card-transition-timing),
|
||||||
background-color var(--card-transition-duration) var(--card-transition-timing);
|
border var(--card-transition-duration) var(--card-transition-timing),
|
||||||
|
background-color var(--card-transition-duration) var(--card-transition-timing),
|
||||||
|
opacity var(--card-transition-duration) var(--card-transition-timing),
|
||||||
|
padding var(--card-transition-duration) var(--card-transition-timing);
|
||||||
|
|
||||||
/* Performance optimizations */
|
/* Performance optimizations */
|
||||||
will-change: transform, box-shadow, border-color;
|
will-change: transform, box-shadow, border-color, background-color, opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gradient background effect */
|
/* Gradient background effect */
|
||||||
@ -139,6 +142,18 @@ const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`;
|
|||||||
transform var(--card-transition-duration) var(--card-transition-timing);
|
transform var(--card-transition-duration) var(--card-transition-timing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Enhanced background glow in image-only mode */
|
||||||
|
:global(.display-image-only) .service-icon-background {
|
||||||
|
transition:
|
||||||
|
opacity calc(var(--card-transition-duration) * 1.5) var(--card-transition-timing),
|
||||||
|
transform calc(var(--card-transition-duration) * 1.5) var(--card-transition-timing);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.display-image-only) .service-card:hover .service-icon-background {
|
||||||
|
opacity: 0.25;
|
||||||
|
transform: scale(1.8);
|
||||||
|
}
|
||||||
|
|
||||||
/* Icon image */
|
/* Icon image */
|
||||||
.service-icon {
|
.service-icon {
|
||||||
width: var(--icon-size);
|
width: var(--icon-size);
|
||||||
@ -150,6 +165,18 @@ const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`;
|
|||||||
filter var(--card-transition-duration) var(--card-transition-timing);
|
filter var(--card-transition-duration) var(--card-transition-timing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Special icon animation for image-only mode */
|
||||||
|
:global(.display-image-only) .service-icon {
|
||||||
|
transition:
|
||||||
|
transform calc(var(--card-transition-duration) * 1.2) var(--card-transition-timing),
|
||||||
|
filter var(--card-transition-duration) var(--card-transition-timing);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.display-image-only) .service-card:hover .service-icon {
|
||||||
|
transform: scale(1.15) rotate(3deg);
|
||||||
|
filter: drop-shadow(0 6px 8px rgba(0, 0, 0, 0.15));
|
||||||
|
}
|
||||||
|
|
||||||
/* Service name */
|
/* Service name */
|
||||||
.service-name {
|
.service-name {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
@ -229,6 +256,39 @@ const cardId = `service-card-${crypto.randomUUID().slice(0, 8)}`;
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
transform: translateY(0);
|
||||||
|
transition:
|
||||||
|
transform var(--card-transition-duration) var(--card-transition-timing),
|
||||||
|
filter var(--card-transition-duration) var(--card-transition-timing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced icon effects in image-only mode */
|
||||||
|
:global(.display-image-only) .service-card:hover .service-icon-container {
|
||||||
|
transform: translateY(-2px) scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Completely hide card background in image-only mode */
|
||||||
|
:global(.display-image-only) .service-card {
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
border-width: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
transform: scale(1.01);
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust hover/active states in image-only mode */
|
||||||
|
:global(.display-image-only) .service-card:hover {
|
||||||
|
transform: translateY(-4px) scale(1.03);
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.display-image-only) .service-card:hover::before,
|
||||||
|
:global(.display-image-only) .service-card::before {
|
||||||
|
opacity: 0;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Name only mode */
|
/* Name only mode */
|
||||||
|
@ -107,6 +107,30 @@ const {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Grouping toggle */}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm zag-text-muted hidden sm:inline">Group:</span>
|
||||||
|
<div class="grouping-selector border-2 border-solid zag-border-b rounded-lg p-1 zag-bg zag-transition">
|
||||||
|
<button
|
||||||
|
@click="toggleGrouping"
|
||||||
|
:class="groupByCategory ? 'active-view' : 'inactive-view'"
|
||||||
|
class="p-1.5 transition-all rounded-md focus:outline-none focus:ring-2 focus:ring-current"
|
||||||
|
aria-label="Toggle category grouping"
|
||||||
|
:title="groupByCategory ? 'Show all apps together' : 'Group by category'"
|
||||||
|
>
|
||||||
|
<!-- Icon for grouped view (shows when grouping is active) -->
|
||||||
|
<svg x-show="groupByCategory" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="3" y="3" width="18" height="7" rx="1"></rect>
|
||||||
|
<rect x="3" y="14" width="18" height="7" rx="1"></rect>
|
||||||
|
</svg>
|
||||||
|
<!-- Icon for ungrouped view (shows when grouping is inactive) -->
|
||||||
|
<svg x-show="!groupByCategory" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="1"></rect>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -79,7 +79,7 @@ const categoryLower = category.toLowerCase();
|
|||||||
x-transition:leave-end="opacity-0 transform -translate-y-4"
|
x-transition:leave-end="opacity-0 transform -translate-y-4"
|
||||||
id={categoryId}
|
id={categoryId}
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4 transition-all duration-300">
|
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-6 transition-all duration-300">
|
||||||
{apps.length > 0 ? (
|
{apps.length > 0 ? (
|
||||||
apps.map(app => {
|
apps.map(app => {
|
||||||
const appName = app.name.toLowerCase();
|
const appName = app.name.toLowerCase();
|
||||||
|
@ -40,8 +40,57 @@ const webpageData = {
|
|||||||
/>
|
/>
|
||||||
<StructuredData slot="head" type="WebPage" data={webpageData} />
|
<StructuredData slot="head" type="WebPage" data={webpageData} />
|
||||||
|
|
||||||
<!-- Print-specific styles -->
|
<!-- Print-specific styles and no-grouping styles -->
|
||||||
<style is:global slot="head">
|
<style is:global slot="head">
|
||||||
|
/* No grouping styles - completely overhauled grid layout */
|
||||||
|
:global(.no-category-grouping) .category-section {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.no-category-grouping) .category-section:not(:first-child) {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When a category section has the no-grouping class */
|
||||||
|
.category-section.no-grouping {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a unified grid container for all items in ungrouped mode */
|
||||||
|
:global(.no-category-grouping) #app-list {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)) !important;
|
||||||
|
gap: 1.5rem !important; /* Force equal spacing in both directions */
|
||||||
|
row-gap: 1.5rem !important; /* Explicitly set row gap */
|
||||||
|
column-gap: 1.5rem !important; /* Explicitly set column gap */
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 0.75rem !important; /* Add some padding around the entire grid */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Completely flatten grid to be a single container in ungrouped mode */
|
||||||
|
:global(.no-category-grouping) .grid {
|
||||||
|
display: contents !important; /* Make grid container act as its children */
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
gap: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix each app card to ensure proper layout in the grid */
|
||||||
|
:global(.no-category-grouping) .app-card {
|
||||||
|
height: 100% !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix ScrollReveal containers in no-grouping mode */
|
||||||
|
:global(.no-category-grouping) .scroll-reveal {
|
||||||
|
height: 100% !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print styles */
|
||||||
@media print {
|
@media print {
|
||||||
/* Hide non-essential UI elements */
|
/* Hide non-essential UI elements */
|
||||||
header, footer, .search-container, .style-controls, button[aria-controls] {
|
header, footer, .search-container, .style-controls, button[aria-controls] {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user