-
-
Notifications
You must be signed in to change notification settings - Fork 18
feat: improve connection list cards UI for dark theme #1529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
26162ad
2ffcefc
84e8808
5c11684
20ccfe1
cb62bc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,9 @@ | ||||||||||||
| :host { | ||||||||||||
| display: flex; | ||||||||||||
| flex-direction: column; | ||||||||||||
| align-items: center; | ||||||||||||
| width: 100%; | ||||||||||||
| flex-grow: 1; | ||||||||||||
| display: flex; | ||||||||||||
| flex-direction: column; | ||||||||||||
| align-items: center; | ||||||||||||
| width: 100%; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| .empty-state { | ||||||||||||
|
|
@@ -53,24 +54,23 @@ | |||||||||||
| } | ||||||||||||
|
|
||||||||||||
| .addConnectionLink { | ||||||||||||
| display: flex; | ||||||||||||
| flex-direction: column; | ||||||||||||
| align-items: center; | ||||||||||||
| gap: 6px; | ||||||||||||
| border-radius: 4px; | ||||||||||||
| box-shadow: | ||||||||||||
| 0px 1px 3px 0px rgba(0, 0, 0, 0.2), | ||||||||||||
| 0px 2px 2px 0px rgba(0, 0, 0, 0.12), | ||||||||||||
| 0px 0px 2px 0px rgba(0, 0, 0, 0.14); | ||||||||||||
| color: inherit; | ||||||||||||
| font-weight: 600; | ||||||||||||
| padding: 12px; | ||||||||||||
| text-decoration: none; | ||||||||||||
| height: 100%; | ||||||||||||
| transition: | ||||||||||||
| box-shadow 200ms, | ||||||||||||
| background 200ms, | ||||||||||||
| border 200ms; | ||||||||||||
| display: flex; | ||||||||||||
| flex-direction: column; | ||||||||||||
| align-items: center; | ||||||||||||
| justify-content: center; | ||||||||||||
| gap: 4px; | ||||||||||||
| border-radius: 4px; | ||||||||||||
| box-shadow: | ||||||||||||
| 0px 1px 3px 0px rgba(0, 0, 0, 0.2), | ||||||||||||
| 0px 2px 2px 0px rgba(0, 0, 0, 0.12), | ||||||||||||
| 0px 0px 2px 0px rgba(0, 0, 0, 0.14); | ||||||||||||
| color: inherit; | ||||||||||||
| font-weight: 600; | ||||||||||||
| padding: 8px; | ||||||||||||
| text-decoration: none; | ||||||||||||
| height: 100%; | ||||||||||||
| min-height: 90px; | ||||||||||||
| transition: box-shadow 200ms, background 200ms, border 200ms; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| .addConnectionLink:hover { | ||||||||||||
|
|
@@ -81,10 +81,10 @@ | |||||||||||
| } | ||||||||||||
|
|
||||||||||||
| @media (prefers-color-scheme: dark) { | ||||||||||||
| .addConnectionLink { | ||||||||||||
| background: #404040; | ||||||||||||
| border: 1px solid #404040; | ||||||||||||
| } | ||||||||||||
| .addConnectionLink { | ||||||||||||
| background: #2a2a2a; | ||||||||||||
| border: 1px solid #2a2a2a; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| .addConnectionLink:hover { | ||||||||||||
| background: #212121; | ||||||||||||
|
|
@@ -97,26 +97,41 @@ | |||||||||||
| } | ||||||||||||
|
|
||||||||||||
| .addConnectionLink__iconBox { | ||||||||||||
| flex-shrink: 0; | ||||||||||||
| display: flex; | ||||||||||||
| align-items: center; | ||||||||||||
| justify-content: center; | ||||||||||||
| background-color: #fff; | ||||||||||||
| border-radius: 4px; | ||||||||||||
| height: 40px; | ||||||||||||
| width: 40px; | ||||||||||||
| flex-shrink: 0; | ||||||||||||
| display: flex; | ||||||||||||
| align-items: center; | ||||||||||||
| justify-content: center; | ||||||||||||
| height: 32px; | ||||||||||||
| width: 32px; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| .addConnectionLink__icon { | ||||||||||||
| flex-shrink: 0; | ||||||||||||
| height: 30px; | ||||||||||||
| width: 30px; | ||||||||||||
| flex-shrink: 0; | ||||||||||||
| height: 28px; | ||||||||||||
| width: 28px; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| @media (prefers-color-scheme: dark) { | ||||||||||||
| .addConnectionLink__icon[data-mat-icon-name="mongodb"], | ||||||||||||
| .addConnectionLink__icon[data-mat-icon-name="mysql"], | ||||||||||||
| .addConnectionLink__icon[data-mat-icon-name="dynamodb"], | ||||||||||||
| .addConnectionLink__icon[data-mat-icon-name="mssql"] { | ||||||||||||
|
Comment on lines
+115
to
+118
|
||||||||||||
| .addConnectionLink__icon[data-mat-icon-name="mongodb"], | |
| .addConnectionLink__icon[data-mat-icon-name="mysql"], | |
| .addConnectionLink__icon[data-mat-icon-name="dynamodb"], | |
| .addConnectionLink__icon[data-mat-icon-name="mssql"] { | |
| .addConnectionLink__icon { |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,8 +1,8 @@ | ||||||
| <h1 *ngIf="isDemo" class="mat-display-1">Create you own test connection</h1> | ||||||
| <h1 *ngIf="isDemo" class="mat-display-1">Create your own test connection</h1> | ||||||
|
|
||||||
| <div *ngIf="!connections?.length && connections !== null" class="empty-state"> | ||||||
| <h1 *ngIf="(currentUser?.role === 'ADMIN' || currentUser?.role === 'DB_ADMIN') && !isDemo" class="mat-display-1"> | ||||||
| Create your first connection | ||||||
| Create first connection | ||||||
|
||||||
| Create first connection | |
| Create your first connection |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||||||||||||||||||||||||||||
| import { CommonModule } from '@angular/common'; | ||||||||||||||||||||||||||||||||
| import { Component, Input } from '@angular/core'; | ||||||||||||||||||||||||||||||||
| import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; | ||||||||||||||||||||||||||||||||
| import { MatButtonModule } from '@angular/material/button'; | ||||||||||||||||||||||||||||||||
| import { MatIconModule } from '@angular/material/icon'; | ||||||||||||||||||||||||||||||||
| import { RouterModule } from '@angular/router'; | ||||||||||||||||||||||||||||||||
|
|
@@ -8,6 +8,7 @@ import posthog from 'posthog-js'; | |||||||||||||||||||||||||||||||
| import { supportedDatabasesTitles, supportedOrderedDatabases } from 'src/app/consts/databases'; | ||||||||||||||||||||||||||||||||
| import { ConnectionItem } from 'src/app/models/connection'; | ||||||||||||||||||||||||||||||||
| import { UiSettings } from 'src/app/models/ui-settings'; | ||||||||||||||||||||||||||||||||
| import { CompanyService } from 'src/app/services/company.service'; | ||||||||||||||||||||||||||||||||
| import { UiSettingsService } from 'src/app/services/ui-settings.service'; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @Component({ | ||||||||||||||||||||||||||||||||
|
|
@@ -16,18 +17,24 @@ import { UiSettingsService } from 'src/app/services/ui-settings.service'; | |||||||||||||||||||||||||||||||
| templateUrl: './own-connections.component.html', | ||||||||||||||||||||||||||||||||
| styleUrl: './own-connections.component.css', | ||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||
| export class OwnConnectionsComponent { | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| export class OwnConnectionsComponent implements OnInit, OnChanges { | ||||||||||||||||||||||||||||||||
| protected posthog = posthog; | ||||||||||||||||||||||||||||||||
| @Input() currentUser: User; | ||||||||||||||||||||||||||||||||
| @Input() connections: ConnectionItem[] = null; | ||||||||||||||||||||||||||||||||
| @Input() isDemo: boolean = false; | ||||||||||||||||||||||||||||||||
| @Input() companyId: string; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| public displayedCardCount: number = 3; | ||||||||||||||||||||||||||||||||
| public connectionsListCollapsed: boolean; | ||||||||||||||||||||||||||||||||
| public supportedDatabasesTitles = supportedDatabasesTitles; | ||||||||||||||||||||||||||||||||
| public supportedOrderedDatabases = supportedOrderedDatabases; | ||||||||||||||||||||||||||||||||
| public hasMultipleMembers: boolean = false; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| constructor(private _uiSettings: UiSettingsService) {} | ||||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||||
| private _uiSettings: UiSettingsService, | ||||||||||||||||||||||||||||||||
| private _companyService: CompanyService | ||||||||||||||||||||||||||||||||
| ) {} | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ngOnInit() { | ||||||||||||||||||||||||||||||||
| this._uiSettings.getUiSettings().subscribe((settings: UiSettings) => { | ||||||||||||||||||||||||||||||||
|
|
@@ -36,6 +43,14 @@ export class OwnConnectionsComponent { | |||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ngOnChanges(changes: SimpleChanges) { | ||||||||||||||||||||||||||||||||
| if (changes['companyId'] && this.companyId) { | ||||||||||||||||||||||||||||||||
| this._companyService.fetchCompanyMembers(this.companyId).subscribe((members: any[]) => { | ||||||||||||||||||||||||||||||||
| this.hasMultipleMembers = members && members.length > 1; | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+47
to
+50
|
||||||||||||||||||||||||||||||||
| if (changes['companyId'] && this.companyId) { | |
| this._companyService.fetchCompanyMembers(this.companyId).subscribe((members: any[]) => { | |
| this.hasMultipleMembers = members && members.length > 1; | |
| }); | |
| const companyIdChange = changes['companyId']; | |
| if (companyIdChange && companyIdChange.currentValue && companyIdChange.currentValue !== companyIdChange.previousValue) { | |
| const companyId = companyIdChange.currentValue; | |
| this._companyService.fetchCompanyMembers(companyId).subscribe((members: any[] = []) => { | |
| const safeMembers = Array.isArray(members) ? members : []; | |
| this.hasMultipleMembers = safeMembers.length > 1; | |
| }); | |
| } else if (companyIdChange && !companyIdChange.currentValue) { | |
| // Reset flag if companyId becomes undefined or falsy | |
| this.hasMultipleMembers = false; |
Copilot
AI
Feb 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The subscription to fetchCompanyMembers in ngOnChanges is not being cleaned up, which could lead to memory leaks. The subscription should be stored and unsubscribed in ngOnDestroy, or you should use the async pipe in the template, or add .pipe(take(1)) to automatically unsubscribe after the first emission.
Copilot
AI
Feb 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ngOnChanges hook will be called on every change detection cycle when any input changes, not just when companyId changes. The condition checks if companyId exists, but ngOnChanges will still trigger whenever any of the other inputs (currentUser, connections, isDemo) change. Consider adding a check to verify that companyId has actually changed from its previous value: if (changes['companyId'] && this.companyId && !changes['companyId'].firstChange && changes['companyId'].currentValue !== changes['companyId'].previousValue) or simply check changes['companyId'].firstChange to only fetch on initial load.
Copilot
AI
Feb 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex pattern /^([^(]+)/ will match from the beginning up to (but not including) the first opening parenthesis. However, for databases without parentheses (like "PostgreSQL", "MongoDB", "Redis", etc.), this regex will match the entire string. While this works, it's more efficient to simply use split or check for the presence of a parenthesis first. Consider simplifying to: const index = title.indexOf('('); return index > 0 ? title.substring(0, index).trim() : title;
| const match = title.match(/^([^(]+)/); | |
| return match ? match[1].trim() : title; | |
| const index = title.indexOf('('); | |
| return index > 0 ? title.substring(0, index).trim() : title; |
Copilot
AI
Feb 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new methods getMainTitle and getSubTitle lack test coverage. Since this component has a test file (own-connections.component.spec.ts) with basic setup, consider adding unit tests for these methods to ensure they correctly parse database titles with and without parentheses, such as "MySQL (MariaDB)" → main: "MySQL", sub: "(MariaDB)" and "PostgreSQL" → main: "PostgreSQL", sub: "".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The text was shortened from "Explore demo admin panels" to "Explore demo panels". While this is more concise, consider whether "admin" is an important qualifier that helps users understand what these demo panels are for.