mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
feat(command-palette): add command icons and enhance search panel styling
style(shell): update rail shortcut styles and improve hover effects
This commit is contained in:
1 parent
963049a7cc
commit
b0a1d8da77
7 files changed
+169
-91
No files matched your search
@@ -269,7 +269,7 @@
|
||||
gap: var(--widget-grid-gap);
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
//padding: 12px;
|
||||
min-width: 0;
|
||||
overflow-x: clip;
|
||||
}
|
||||
@@ -318,7 +318,7 @@
|
||||
}
|
||||
|
||||
.widget-slot:hover {
|
||||
transform: translateY(-1px);
|
||||
//transform: translateY(-1px);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--dashboard-frame-border) 52%, transparent),
|
||||
0 18px 28px -20px var(--dashboard-frame-glow);
|
||||
|
||||
+38
-24
@@ -4,24 +4,26 @@
|
||||
<p>{{ 'WORKSPACE.COMMAND_PALETTE.SUBTITLE' | translate }}</p>
|
||||
</header>
|
||||
|
||||
<label class="palette-search">
|
||||
<mat-icon>search</mat-icon>
|
||||
<input
|
||||
#queryInput
|
||||
type="search"
|
||||
spellcheck="false"
|
||||
[value]="query()"
|
||||
(input)="onQueryInput($event)"
|
||||
(keydown)="onInputKeydown($event)"
|
||||
[placeholder]="
|
||||
'WORKSPACE.COMMAND_PALETTE.SEARCH_PLACEHOLDER' | translate
|
||||
"
|
||||
[attr.aria-label]="
|
||||
'WORKSPACE.COMMAND_PALETTE.SEARCH_LABEL' | translate
|
||||
"
|
||||
/>
|
||||
<span class="palette-search__hint">Ctrl/Cmd+K</span>
|
||||
</label>
|
||||
<div class="palette-search-panel">
|
||||
<label class="palette-search">
|
||||
<mat-icon>search</mat-icon>
|
||||
<input
|
||||
#queryInput
|
||||
type="search"
|
||||
spellcheck="false"
|
||||
[value]="query()"
|
||||
(input)="onQueryInput($event)"
|
||||
(keydown)="onInputKeydown($event)"
|
||||
[placeholder]="
|
||||
'WORKSPACE.COMMAND_PALETTE.SEARCH_PLACEHOLDER' | translate
|
||||
"
|
||||
[attr.aria-label]="
|
||||
'WORKSPACE.COMMAND_PALETTE.SEARCH_LABEL' | translate
|
||||
"
|
||||
/>
|
||||
<span class="palette-search__hint">Ctrl/Cmd+K</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="palette-results">
|
||||
@if (commandGroups().length === 0) {
|
||||
@@ -37,17 +39,29 @@
|
||||
<button
|
||||
type="button"
|
||||
class="palette-command"
|
||||
[class.palette-command--with-icon]="
|
||||
!!getCommandIcon(command)
|
||||
"
|
||||
[class.is-selected]="isCommandSelected(command)"
|
||||
[disabled]="!command.enabled"
|
||||
(mouseenter)="onCommandHover(command)"
|
||||
(click)="onCommandClick(command)"
|
||||
>
|
||||
<span class="palette-command__label">{{
|
||||
command.label
|
||||
}}</span>
|
||||
<span class="palette-command__description">{{
|
||||
command.description
|
||||
}}</span>
|
||||
@if (getCommandIcon(command); as icon) {
|
||||
<span class="palette-command__icon">
|
||||
<mat-icon>{{ icon }}</mat-icon>
|
||||
</span>
|
||||
}
|
||||
<span class="palette-command__content">
|
||||
<span class="palette-command__label">{{
|
||||
command.label
|
||||
}}</span>
|
||||
<span
|
||||
class="palette-command__description"
|
||||
>
|
||||
{{ command.description }}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
+65
-7
@@ -35,18 +35,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
.palette-search-panel {
|
||||
margin: 0 -2px;
|
||||
padding: 10px 12px 12px;
|
||||
border: 1px solid var(--app-separator, var(--mat-sys-outline-variant));
|
||||
border-radius: 12px;
|
||||
background: var(
|
||||
--app-widget-header-bg,
|
||||
var(--mat-sys-surface-container-high)
|
||||
);
|
||||
}
|
||||
|
||||
.palette-search {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--mat-sys-outline-variant);
|
||||
background: var(--mat-sys-surface-container);
|
||||
min-height: 38px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--app-search-border, var(--mat-sys-outline-variant));
|
||||
background: var(--app-search-bg, var(--mat-sys-surface-container));
|
||||
min-height: 40px;
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
background-color 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--mat-sys-primary);
|
||||
box-shadow: 0 0 0 3px
|
||||
color-mix(in srgb, var(--mat-sys-primary) 12%, transparent);
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
@@ -58,12 +77,13 @@
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
color: var(--mat-sys-on-surface);
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.875rem;
|
||||
width: 100%;
|
||||
|
||||
&::placeholder {
|
||||
@@ -76,11 +96,15 @@
|
||||
.palette-search__hint {
|
||||
font-size: 0.72rem;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
border: 1px solid var(--mat-sys-outline-variant);
|
||||
border: 1px solid var(--app-separator, var(--mat-sys-outline-variant));
|
||||
border-radius: 6px;
|
||||
padding: 2px 6px;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
background: var(
|
||||
--app-widget-header-bg,
|
||||
var(--mat-sys-surface-container-high)
|
||||
);
|
||||
}
|
||||
|
||||
.palette-results {
|
||||
@@ -139,6 +163,40 @@
|
||||
}
|
||||
}
|
||||
|
||||
.palette-command--with-icon {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.palette-command__icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--app-selection-surface-strong) 72%,
|
||||
transparent
|
||||
);
|
||||
color: var(--app-selection-color);
|
||||
flex-shrink: 0;
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.palette-command__content {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.palette-command__label {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 560;
|
||||
|
||||
+19
@@ -228,4 +228,23 @@ export class WorkspaceCommandPaletteComponent implements AfterViewInit {
|
||||
}
|
||||
return 'WORKSPACE.COMMAND_PALETTE.GROUP_SECTION';
|
||||
}
|
||||
|
||||
getCommandIcon(command: WorkspaceCommandItem): string | null {
|
||||
if (command.scope !== 'global') {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (command.id) {
|
||||
case 'global-search':
|
||||
return 'search';
|
||||
case 'open-global-favorites':
|
||||
return 'star';
|
||||
case 'open-downloads':
|
||||
return 'download';
|
||||
case 'open-global-recent':
|
||||
return 'history';
|
||||
default:
|
||||
return 'bolt';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
<div class="rail-footer">
|
||||
<a
|
||||
class="rail-shortcut"
|
||||
class="rail-shortcut nav-item"
|
||||
routerLink="/workspace/settings"
|
||||
[class.is-active]="isSettingsRoute()"
|
||||
[matTooltip]="'WORKSPACE.SHELL.RAIL_SETTINGS' | translate"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@use '../../nav-list.scss';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -146,6 +148,44 @@
|
||||
|
||||
.rail-footer {
|
||||
margin-top: auto;
|
||||
|
||||
.rail-shortcut {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
border-radius: 14px;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
app-region: no-drag;
|
||||
|
||||
mat-icon {
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
&:hover:not(.is-active) {
|
||||
background: var(--app-selection-surface);
|
||||
color: var(--mat-sys-on-surface);
|
||||
}
|
||||
|
||||
&.is-active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
border-radius: 999px;
|
||||
background: var(--app-selection-color);
|
||||
box-shadow: 0 0 10px var(--app-selection-glow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Workspace Header ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -52,7 +52,6 @@ import {
|
||||
import { PortalRailLinksComponent } from '@iptvnator/portal/shared/ui';
|
||||
import { XtreamStore } from '@iptvnator/portal/xtream/data-access';
|
||||
import {
|
||||
DownloadsService,
|
||||
PlaylistsService,
|
||||
SettingsStore,
|
||||
} from 'services';
|
||||
@@ -121,7 +120,6 @@ export class WorkspaceShellComponent {
|
||||
private readonly store = inject(Store);
|
||||
private readonly xtreamStore = inject(XtreamStore);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly downloadsService = inject(DownloadsService);
|
||||
readonly externalPlayback = inject(PORTAL_EXTERNAL_PLAYBACK);
|
||||
private readonly settingsStore = inject(SettingsStore);
|
||||
private readonly playlistsService = inject(PlaylistsService);
|
||||
@@ -192,13 +190,14 @@ export class WorkspaceShellComponent {
|
||||
return routeContext;
|
||||
}
|
||||
|
||||
// On dashboard, sources, settings, and global favorites we still want provider-specific rail links
|
||||
// for the currently active playlist selected in the switcher.
|
||||
// On dashboard, sources, settings, global favorites, and global downloads we still want
|
||||
// provider-specific rail links for the currently active playlist selected in the switcher.
|
||||
if (
|
||||
!this.isDashboardRoute() &&
|
||||
!this.isSourcesRoute() &&
|
||||
!this.isSettingsRoute() &&
|
||||
!this.isGlobalFavoritesRoute()
|
||||
!this.isGlobalFavoritesRoute() &&
|
||||
!this.isGlobalDownloadsRoute()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -410,44 +409,6 @@ export class WorkspaceShellComponent {
|
||||
|
||||
const context = this.currentContext();
|
||||
const section = this.currentSection();
|
||||
const isGlobalDownloads = this.isGlobalDownloadsRoute();
|
||||
|
||||
if (
|
||||
isGlobalDownloads ||
|
||||
(context &&
|
||||
(context.provider === 'xtreams' ||
|
||||
context.provider === 'stalker') &&
|
||||
section === 'downloads')
|
||||
) {
|
||||
const playlistId = context?.playlistId;
|
||||
const hasClearable = this.downloadsService
|
||||
.downloads()
|
||||
.some(
|
||||
(item) =>
|
||||
(!playlistId || item.playlistId === playlistId) &&
|
||||
(item.status === 'completed' ||
|
||||
item.status === 'failed' ||
|
||||
item.status === 'canceled')
|
||||
);
|
||||
return {
|
||||
icon: 'delete_sweep',
|
||||
tooltip: isGlobalDownloads
|
||||
? this.translateText(
|
||||
'WORKSPACE.SHELL.CLEAR_COMPLETED_DOWNLOADS_ALL'
|
||||
)
|
||||
: this.translateText(
|
||||
'WORKSPACE.SHELL.CLEAR_COMPLETED_DOWNLOADS_THIS_PLAYLIST'
|
||||
),
|
||||
ariaLabel: isGlobalDownloads
|
||||
? this.translateText(
|
||||
'WORKSPACE.SHELL.CLEAR_COMPLETED_DOWNLOADS_ALL_ARIA'
|
||||
)
|
||||
: this.translateText(
|
||||
'WORKSPACE.SHELL.CLEAR_COMPLETED_DOWNLOADS_THIS_PLAYLIST_ARIA'
|
||||
),
|
||||
disabled: !hasClearable,
|
||||
};
|
||||
}
|
||||
|
||||
if (!context || !section) {
|
||||
return null;
|
||||
@@ -695,20 +656,6 @@ export class WorkspaceShellComponent {
|
||||
async runHeaderBulkAction(): Promise<void> {
|
||||
const context = this.currentContext();
|
||||
const section = this.currentSection();
|
||||
const isGlobalDownloads = this.isGlobalDownloadsRoute();
|
||||
|
||||
if (
|
||||
isGlobalDownloads ||
|
||||
(context &&
|
||||
(context.provider === 'xtreams' ||
|
||||
context.provider === 'stalker') &&
|
||||
section === 'downloads')
|
||||
) {
|
||||
const playlistId = context?.playlistId;
|
||||
await this.downloadsService.clearCompleted(playlistId);
|
||||
await this.downloadsService.loadDownloads(playlistId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context || !section) {
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user