feat: Basic general context menu
All checks were successful
Queue Release Build / prepare (push) Successful in 14s
Deploy Web Apps / deploy (push) Successful in 14m39s
Queue Release Build / build-linux (push) Successful in 40m59s
Queue Release Build / build-windows (push) Successful in 28m59s
Queue Release Build / finalize (push) Successful in 1m58s
All checks were successful
Queue Release Build / prepare (push) Successful in 14s
Deploy Web Apps / deploy (push) Successful in 14m39s
Queue Release Build / build-linux (push) Successful in 40m59s
Queue Release Build / build-windows (push) Successful in 28m59s
Queue Release Build / finalize (push) Successful in 1m58s
This commit is contained in:
@@ -13,21 +13,19 @@
|
||||
</button>
|
||||
|
||||
<!-- Saved servers icons -->
|
||||
<div class="no-scrollbar mt-2 flex w-full flex-1 flex-col items-center gap-2 overflow-y-auto">
|
||||
<div class="no-scrollbar mt-2 flex w-full flex-1 flex-col items-center gap-2 overflow-y-auto pt-0.5">
|
||||
@for (room of visibleSavedRooms(); track room.id) {
|
||||
<div class="relative flex w-full justify-center">
|
||||
@if (isSelectedRoom(room)) {
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute left-0 top-1/2 h-10 w-1 -translate-y-1/2 rounded-l-full bg-primary"
|
||||
></span>
|
||||
}
|
||||
<div class="group/server relative flex w-full justify-center">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute left-0 top-1/2 w-[3px] -translate-y-1/2 rounded-r-full bg-primary transition-[height,opacity] duration-100"
|
||||
[ngClass]="isSelectedRoom(room) ? 'h-5 opacity-100' : 'h-0 opacity-0 group-hover/server:h-2.5 group-hover/server:opacity-100'"
|
||||
></span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="relative z-10 flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md border border-transparent bg-card transition-colors hover:border-border hover:bg-card"
|
||||
[class.border-primary/30]="isSelectedRoom(room)"
|
||||
[class.bg-primary/10]="isSelectedRoom(room)"
|
||||
class="relative z-10 flex h-10 w-10 cursor-pointer flex-shrink-0 items-center justify-center border border-transparent transition-[border-radius,box-shadow,background-color] duration-100 hover:rounded-lg hover:bg-card"
|
||||
[ngClass]="isSelectedRoom(room) ? 'rounded-lg ring-2 ring-primary/40 bg-primary/10' : 'rounded-xl bg-card'"
|
||||
[title]="room.name"
|
||||
[attr.aria-current]="isSelectedRoom(room) ? 'page' : null"
|
||||
(click)="joinSavedRoom(room)"
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
@if (params()) {
|
||||
<app-context-menu
|
||||
[x]="params()!.posX"
|
||||
[y]="params()!.posY"
|
||||
[width]="'w-44'"
|
||||
(closed)="close()"
|
||||
>
|
||||
@if (params()!.isEditable) {
|
||||
<button
|
||||
type="button"
|
||||
class="context-menu-item"
|
||||
[disabled]="!params()!.editFlags.canCut"
|
||||
[class.opacity-40]="!params()!.editFlags.canCut"
|
||||
(click)="execCommand('cut')"
|
||||
>
|
||||
Cut
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="context-menu-item"
|
||||
[disabled]="!params()!.editFlags.canCopy"
|
||||
[class.opacity-40]="!params()!.editFlags.canCopy"
|
||||
(click)="execCommand('copy')"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="context-menu-item"
|
||||
[disabled]="!params()!.editFlags.canPaste"
|
||||
[class.opacity-40]="!params()!.editFlags.canPaste"
|
||||
(click)="execCommand('paste')"
|
||||
>
|
||||
Paste
|
||||
</button>
|
||||
<div class="context-menu-divider"></div>
|
||||
<button
|
||||
type="button"
|
||||
class="context-menu-item"
|
||||
[disabled]="!params()!.editFlags.canSelectAll"
|
||||
[class.opacity-40]="!params()!.editFlags.canSelectAll"
|
||||
(click)="execCommand('selectAll')"
|
||||
>
|
||||
Select All
|
||||
</button>
|
||||
} @else if (params()!.selectionText) {
|
||||
<button
|
||||
type="button"
|
||||
class="context-menu-item"
|
||||
(click)="execCommand('copy')"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (params()!.linkURL) {
|
||||
@if (params()!.isEditable || params()!.selectionText) {
|
||||
<div class="context-menu-divider"></div>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="context-menu-item"
|
||||
(click)="copyLink()"
|
||||
>
|
||||
Copy Link
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (params()!.mediaType === 'image' && params()!.srcURL) {
|
||||
@if (params()!.isEditable || params()!.selectionText || params()!.linkURL) {
|
||||
<div class="context-menu-divider"></div>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="context-menu-item"
|
||||
(click)="copyImage()"
|
||||
>
|
||||
Copy Image
|
||||
</button>
|
||||
}
|
||||
</app-context-menu>
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
inject,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import { ElectronBridgeService } from '../../core/platform/electron/electron-bridge.service';
|
||||
import { ContextMenuComponent } from '../../shared';
|
||||
import type { ContextMenuParams } from '../../core/platform/electron/electron-api.models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-native-context-menu',
|
||||
standalone: true,
|
||||
imports: [ContextMenuComponent],
|
||||
templateUrl: './native-context-menu.component.html'
|
||||
})
|
||||
export class NativeContextMenuComponent implements OnInit, OnDestroy {
|
||||
params = signal<ContextMenuParams | null>(null);
|
||||
|
||||
private readonly electronBridge = inject(ElectronBridgeService);
|
||||
private cleanup: (() => void) | null = null;
|
||||
|
||||
ngOnInit(): void {
|
||||
const api = this.electronBridge.getApi();
|
||||
|
||||
if (!api?.onContextMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cleanup = api.onContextMenu((incoming) => {
|
||||
const hasContent = incoming.isEditable
|
||||
|| !!incoming.selectionText
|
||||
|| !!incoming.linkURL
|
||||
|| (incoming.mediaType === 'image' && !!incoming.srcURL);
|
||||
|
||||
this.params.set(hasContent ? incoming : null);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.cleanup?.();
|
||||
this.cleanup = null;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.params.set(null);
|
||||
}
|
||||
|
||||
execCommand(command: string): void {
|
||||
document.execCommand(command);
|
||||
this.close();
|
||||
}
|
||||
|
||||
copyLink(): void {
|
||||
const url = this.params()?.linkURL;
|
||||
|
||||
if (url) {
|
||||
navigator.clipboard.writeText(url).catch(() => {});
|
||||
}
|
||||
|
||||
this.close();
|
||||
}
|
||||
|
||||
copyImage(): void {
|
||||
const srcURL = this.params()?.srcURL;
|
||||
const api = this.electronBridge.getApi();
|
||||
|
||||
if (srcURL && api?.copyImageToClipboard) {
|
||||
api.copyImageToClipboard(srcURL).catch(() => {});
|
||||
}
|
||||
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user