Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,95 @@ th.mat-header-cell, td.mat-cell {
text-align: left;
}

/* Sortable header styles */
.sortable-header__content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
gap: 4px;
}

.sortable-header__text {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.sortable-header__button {
opacity: 0;
width: 24px;
height: 24px;
line-height: 24px;
flex-shrink: 0;
transition: opacity 0.2s ease;
}

.sortable-header:hover .sortable-header__button,
.sortable-header_active .sortable-header__button {
opacity: 1;
}

.sortable-header__icon {
font-size: 18px;
width: 18px;
height: 18px;
color: rgba(0, 0, 0, 0.54);
}

.sortable-header_active .sortable-header__icon {
color: var(--color-primaryPalette-500);
}

@media (prefers-color-scheme: dark) {
.sortable-header__icon {
color: rgba(255, 255, 255, 0.54);
}

.sortable-header_active .sortable-header__icon {
color: var(--color-primaryPalette-300);
}
}

/* Sort menu styles */
.sort-menu__item {
display: flex;
align-items: center;
gap: 8px;
}

.sort-menu__item mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}

.sort-menu__item-content {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 10px;
border-radius: 4px;
margin: -4px -10px;
}

.sort-menu__item-content_active {
background-color: rgba(0, 0, 0, 0.04);
}

@media (prefers-color-scheme: dark) {
.sort-menu__item-content_active {
background-color: rgba(255, 255, 255, 0.06);
}
}

.sort-menu__lock-icon {
font-size: 16px;
width: 16px;
height: 16px;
}

.db-table-cell-checkbox {
display: flex;
align-items: center;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,44 @@ <h2 class="mat-h2 table-name">{{ displayName }}</h2>
</ng-container>

<ng-container [matColumnDef]="column" *ngFor="let column of tableData.displayedDataColumns">
<mat-header-cell *matHeaderCellDef mat-sort-header [disabled]="!isSortable(column)"> {{ tableData.dataNormalizedColumns[column] }} </mat-header-cell>
<mat-header-cell *matHeaderCellDef class="sortable-header" [class.sortable-header_active]="sort.active === column">
<div class="sortable-header__content">
<span class="sortable-header__text">{{ tableData.dataNormalizedColumns[column] }}</span>
<button *ngIf="isSortable(column)"
mat-icon-button
class="sortable-header__button"
[matMenuTriggerFor]="sortMenu">
<mat-icon class="sortable-header__icon">
{{ sort.active === column ? (sort.direction === 'asc' ? 'arrow_upward' : 'arrow_downward') : 'unfold_more' }}
</mat-icon>
</button>
</div>
<mat-menu #sortMenu="matMenu" class="sort-menu">
<button mat-menu-item (click)="applySort(column, 'asc'); $event.stopPropagation()"
class="sort-menu__item">
<span class="sort-menu__item-content" [class.sort-menu__item-content_active]="sort.active === column && sort.direction === 'asc'">
<mat-icon>arrow_upward</mat-icon>
<span>Ascending (A-Z)</span>
</span>
</button>
<button mat-menu-item (click)="applySort(column, 'desc'); $event.stopPropagation()"
class="sort-menu__item">
<span class="sort-menu__item-content" [class.sort-menu__item-content_active]="sort.active === column && sort.direction === 'desc'">
<mat-icon>arrow_downward</mat-icon>
<span>Descending (Z-A)</span>
</span>
</button>
<mat-divider></mat-divider>
<button mat-menu-item (click)="toggleDefaultSort(column); $event.stopPropagation()"
class="sort-menu__item"
[disabled]="sort.active !== column && !isDefaultSort(column)"
[matTooltip]="sort.active !== column && !isDefaultSort(column) ? 'Select sort order first' : ''"
matTooltipPosition="left">
<mat-icon class="sort-menu__lock-icon">{{ isDefaultSort(column) ? 'lock' : 'lock_open' }}</mat-icon>
<span>{{ isDefaultSort(column) ? 'Remove default' : 'Set as default' }}</span>
</button>
</mat-menu>
</mat-header-cell>
<mat-cell *matCellDef="let element; let i = index" [attr.data-label]="tableData.dataNormalizedColumns[column]" class="db-table-cell" data-hj-suppress>
<div class="table-cell-content">
<ng-container *ngIf="isForeignKey(column); else contentCell">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatSelectModule } from '@angular/material/select';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatDividerModule } from '@angular/material/divider';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import JsonURL from '@jsonurl/jsonurl';
Expand All @@ -40,6 +41,7 @@ import { ConnectionsService } from 'src/app/services/connections.service';
import { NotificationsService } from 'src/app/services/notifications.service';
import { TableRowService } from 'src/app/services/table-row.service';
import { TableStateService } from 'src/app/services/table-state.service';
import { UiSettingsService } from 'src/app/services/ui-settings.service';
import { tableDisplayTypes, UIwidgets } from '../../../consts/table-display-types';
import { normalizeTableName } from '../../../lib/normalize';
import { PlaceholderTableDataComponent } from '../../skeletons/placeholder-table-data/placeholder-table-data.component';
Expand Down Expand Up @@ -81,6 +83,7 @@ export interface Folder {
MatAutocompleteModule,
MatSelectModule,
MatMenuModule,
MatDividerModule,
MatTooltipModule,
ClipboardModule,
DragDropModule,
Expand Down Expand Up @@ -150,11 +153,14 @@ export class DbTableViewComponent implements OnInit {
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;

public defaultSort: { column: string; direction: 'asc' | 'desc' } | null = null;

constructor(
private _tableState: TableStateService,
private _notifications: NotificationsService,
private _tableRow: TableRowService,
private _connections: ConnectionsService,
private _uiSettings: UiSettingsService,
private route: ActivatedRoute,
public router: Router,
public dialog: MatDialog,
Expand All @@ -164,7 +170,15 @@ export class DbTableViewComponent implements OnInit {
this.tableData.paginator = this.paginator;

this.tableData.sort = this.sort;
// this.sort.sortChange.subscribe(() => { this.paginator.pageIndex = 0 });

// Load default sort from settings
this.loadDefaultSort();

// Initialize sort - default sort takes priority
if (this.defaultSort) {
this.sort.active = this.defaultSort.column;
this.sort.direction = this.defaultSort.direction;
}

merge(this.sort.sortChange, this.paginator.page)
.pipe(
Expand Down Expand Up @@ -246,6 +260,49 @@ export class DbTableViewComponent implements OnInit {
return this.tableData.sortByColumns.includes(column) || !this.tableData.sortByColumns.length;
}

applySort(column: string, direction: 'asc' | 'desc') {
// If clicking on already selected sort - clear it
if (this.sort.active === column && this.sort.direction === direction) {
// If this column was the default, remove the default too
if (this.defaultSort?.column === column) {
this.defaultSort = null;
this._uiSettings.updateTableSetting(this.connectionID, this.name, 'defaultSort', null);
}
// Clear sort
this.sort.active = '';
this.sort.direction = '';
this.sort.sortChange.emit({ active: '', direction: '' });
} else {
this.sort.active = column;
this.sort.direction = direction;
this.sort.sortChange.emit({ active: column, direction: direction });
}
}

loadDefaultSort() {
const tableSettings = this._uiSettings.settings?.connections?.[this.connectionID]?.tables?.[this.name];
if (tableSettings?.defaultSort) {
this.defaultSort = tableSettings.defaultSort;
}
}

toggleDefaultSort(column: string) {
if (this.isDefaultSort(column)) {
// Remove default sort
this.defaultSort = null;
this._uiSettings.updateTableSetting(this.connectionID, this.name, 'defaultSort', null);
} else {
// Set current sort as default
const direction = this.sort.active === column ? this.sort.direction : 'asc';
this.defaultSort = { column, direction: direction as 'asc' | 'desc' };
this._uiSettings.updateTableSetting(this.connectionID, this.name, 'defaultSort', this.defaultSort);
}
}

isDefaultSort(column: string): boolean {
return this.defaultSort?.column === column;
}

isForeignKey(column: string) {
return this.tableData.foreignKeysList.includes(column);
}
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/app/models/ui-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export interface GlobalSettingsUI {

export interface TableSettingsUI {
shownColumns: string[];
defaultSort?: {
column: string;
direction: 'asc' | 'desc';
};
}

export interface ConnectionSettingsUI {
Expand Down
Loading