Fix private calls
This commit is contained in:
@@ -278,6 +278,19 @@ export class ChatMessagesComponent {
|
||||
const electronApi = this.electronBridge.getApi();
|
||||
|
||||
if (electronApi) {
|
||||
const diskPath = this.getAttachmentDiskPath(attachment);
|
||||
|
||||
if (diskPath && electronApi.saveExistingFileAs) {
|
||||
try {
|
||||
const result = await electronApi.saveExistingFileAs(diskPath, attachment.filename);
|
||||
|
||||
if (result.saved || result.cancelled)
|
||||
return;
|
||||
} catch {
|
||||
/* fall back to blob/browser download */
|
||||
}
|
||||
}
|
||||
|
||||
const blob = await this.getAttachmentBlob(attachment);
|
||||
|
||||
if (blob) {
|
||||
@@ -326,6 +339,9 @@ export class ChatMessagesComponent {
|
||||
if (!attachment.objectUrl)
|
||||
return null;
|
||||
|
||||
if (attachment.objectUrl.startsWith('file:'))
|
||||
return null;
|
||||
|
||||
try {
|
||||
const response = await fetch(attachment.objectUrl);
|
||||
|
||||
@@ -335,6 +351,10 @@ export class ChatMessagesComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private getAttachmentDiskPath(attachment: Attachment): string | null {
|
||||
return attachment.savedPath || attachment.filePath || null;
|
||||
}
|
||||
|
||||
private blobToBase64(blob: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
@@ -112,7 +112,8 @@
|
||||
type="button"
|
||||
class="font-semibold text-primary underline-offset-4 hover:underline"
|
||||
(click)="openMissingPluginStore(missingEmbed)"
|
||||
>store</button
|
||||
>
|
||||
store</button
|
||||
>.
|
||||
</article>
|
||||
}
|
||||
@@ -359,6 +360,30 @@
|
||||
</button>
|
||||
}
|
||||
} @else {
|
||||
@if (att.canOpenExternally) {
|
||||
<button
|
||||
class="inline-flex items-center gap-1.5 rounded bg-secondary px-2 py-1 text-xs text-foreground transition-colors hover:bg-secondary/80"
|
||||
(click)="openAttachmentExternally(att)"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideExternalLink"
|
||||
class="h-3.5 w-3.5"
|
||||
/>
|
||||
Open
|
||||
</button>
|
||||
}
|
||||
@if (att.canUseExperimentalPlayer) {
|
||||
<button
|
||||
class="inline-flex items-center gap-1.5 rounded bg-secondary px-2 py-1 text-xs text-foreground transition-colors hover:bg-secondary/80"
|
||||
(click)="openExperimentalPlayer(att)"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucidePlay"
|
||||
class="h-3.5 w-3.5"
|
||||
/>
|
||||
Play
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
class="rounded bg-primary px-2 py-1 text-xs text-primary-foreground"
|
||||
(click)="downloadAttachment(att)"
|
||||
@@ -368,6 +393,30 @@
|
||||
}
|
||||
} @else {
|
||||
<div class="text-xs text-muted-foreground">Shared from your device</div>
|
||||
@if (att.canOpenExternally) {
|
||||
<button
|
||||
class="inline-flex items-center gap-1.5 rounded bg-secondary px-2 py-1 text-xs text-foreground transition-colors hover:bg-secondary/80"
|
||||
(click)="openAttachmentExternally(att)"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideExternalLink"
|
||||
class="h-3.5 w-3.5"
|
||||
/>
|
||||
Open
|
||||
</button>
|
||||
}
|
||||
@if (att.canUseExperimentalPlayer) {
|
||||
<button
|
||||
class="inline-flex items-center gap-1.5 rounded bg-secondary px-2 py-1 text-xs text-foreground transition-colors hover:bg-secondary/80"
|
||||
(click)="openExperimentalPlayer(att)"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucidePlay"
|
||||
class="h-3.5 w-3.5"
|
||||
/>
|
||||
Play
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -379,6 +428,22 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (att.experimentalPlayerActive && att.objectUrl) {
|
||||
@defer {
|
||||
<app-experimental-vlc-player
|
||||
[src]="att.objectUrl"
|
||||
[filename]="att.filename"
|
||||
[mime]="att.mime"
|
||||
[sizeLabel]="formatBytes(att.size)"
|
||||
(closed)="closeExperimentalPlayer()"
|
||||
(downloadRequested)="downloadAttachment(att)"
|
||||
/>
|
||||
} @loading {
|
||||
<div class="mt-2 max-w-xl rounded-md border border-border bg-secondary/20 p-3 text-xs text-muted-foreground">
|
||||
Loading experimental player...
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,9 @@ import {
|
||||
lucideDownload,
|
||||
lucideEdit,
|
||||
lucideExpand,
|
||||
lucideExternalLink,
|
||||
lucideImage,
|
||||
lucidePlay,
|
||||
lucideReply,
|
||||
lucideSmile,
|
||||
lucideTrash2,
|
||||
@@ -29,8 +31,15 @@ import {
|
||||
import {
|
||||
Attachment,
|
||||
AttachmentFacade,
|
||||
MAX_BROWSER_INLINE_MEDIA_SIZE_BYTES,
|
||||
MAX_AUTO_SAVE_SIZE_BYTES
|
||||
} from '../../../../../attachment';
|
||||
import { PlatformService } from '../../../../../../core/platform';
|
||||
import { ElectronBridgeService } from '../../../../../../core/platform/electron/electron-bridge.service';
|
||||
import {
|
||||
ExperimentalMediaSettingsService
|
||||
} from '../../../../../experimental-media';
|
||||
import { ExperimentalVlcPlayerComponent } from '../../../../../experimental-media/feature/experimental-vlc-player/experimental-vlc-player.component';
|
||||
import { KlipyService } from '../../../../application/services/klipy.service';
|
||||
import { hasDedicatedChatEmbed } from '../../../../domain/rules/link-embed.rules';
|
||||
import {
|
||||
@@ -81,6 +90,9 @@ const RICH_MARKDOWN_PATTERNS = [
|
||||
];
|
||||
|
||||
interface ChatMessageAttachmentViewModel extends Attachment {
|
||||
canOpenExternally: boolean;
|
||||
canUseExperimentalPlayer: boolean;
|
||||
experimentalPlayerActive: boolean;
|
||||
isAudio: boolean;
|
||||
isUploader: boolean;
|
||||
isVideo: boolean;
|
||||
@@ -112,6 +124,7 @@ interface MissingPluginEmbedFallback {
|
||||
ChatLinkEmbedComponent,
|
||||
UserAvatarComponent,
|
||||
PluginRenderHostComponent,
|
||||
ExperimentalVlcPlayerComponent,
|
||||
ThemeNodeDirective
|
||||
],
|
||||
viewProviders: [
|
||||
@@ -120,7 +133,9 @@ interface MissingPluginEmbedFallback {
|
||||
lucideDownload,
|
||||
lucideEdit,
|
||||
lucideExpand,
|
||||
lucideExternalLink,
|
||||
lucideImage,
|
||||
lucidePlay,
|
||||
lucideReply,
|
||||
lucideSmile,
|
||||
lucideTrash2,
|
||||
@@ -140,9 +155,14 @@ export class ChatMessageItemComponent {
|
||||
private readonly klipy = inject(KlipyService);
|
||||
private readonly pluginRequirements = inject(PluginRequirementStateService);
|
||||
private readonly pluginUi = inject(PluginUiRegistryService);
|
||||
private readonly electronBridge = inject(ElectronBridgeService);
|
||||
private readonly platform = inject(PlatformService);
|
||||
private readonly experimentalMedia = inject(ExperimentalMediaSettingsService);
|
||||
private readonly profileCard = inject(ProfileCardService);
|
||||
private readonly router = inject(Router);
|
||||
private readonly attachmentVersion = signal(this.attachmentsSvc.updated());
|
||||
private readonly experimentalPlayerAttachmentId = signal<string | null>(null);
|
||||
private readonly mediaSupportCache = new Map<string, boolean>();
|
||||
|
||||
readonly message = input.required<Message>();
|
||||
readonly repliedMessage = input<Message | undefined>();
|
||||
@@ -539,13 +559,51 @@ export class ChatMessageItemComponent {
|
||||
this.downloadRequested.emit(attachment);
|
||||
}
|
||||
|
||||
openExperimentalPlayer(attachment: Attachment): void {
|
||||
if (!attachment.available || !attachment.objectUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.experimentalPlayerAttachmentId.set(attachment.id);
|
||||
}
|
||||
|
||||
async openAttachmentExternally(attachment: Attachment): Promise<void> {
|
||||
const diskPath = this.getAttachmentDiskPath(attachment);
|
||||
const electronApi = this.electronBridge.getApi();
|
||||
|
||||
if (!diskPath || !electronApi?.openFilePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
await electronApi.openFilePath(diskPath);
|
||||
}
|
||||
|
||||
closeExperimentalPlayer(): void {
|
||||
this.experimentalPlayerAttachmentId.set(null);
|
||||
}
|
||||
|
||||
private buildAttachmentViewModel(attachment: Attachment): ChatMessageAttachmentViewModel {
|
||||
const isVideo = this.isVideoAttachment(attachment);
|
||||
const isAudio = this.isAudioAttachment(attachment);
|
||||
const isRawVideo = this.isVideoAttachment(attachment);
|
||||
const isRawAudio = this.isAudioAttachment(attachment);
|
||||
const isRawPlayableMedia = isRawVideo || isRawAudio;
|
||||
const isNativePlayableMedia = this.canPlayMediaType(attachment.mime);
|
||||
const shouldUseDefaultFileInterface = isRawPlayableMedia &&
|
||||
(!isNativePlayableMedia ||
|
||||
(this.platform.isBrowser && attachment.size > MAX_BROWSER_INLINE_MEDIA_SIZE_BYTES));
|
||||
const isVideo = isRawVideo && !shouldUseDefaultFileInterface;
|
||||
const isAudio = isRawAudio && !shouldUseDefaultFileInterface;
|
||||
const requiresMediaDownloadAcceptance = (isVideo || isAudio) && attachment.size > MAX_AUTO_SAVE_SIZE_BYTES;
|
||||
const canUseExperimentalPlayer = this.experimentalMedia.vlcJsPlaybackEnabled() &&
|
||||
shouldUseDefaultFileInterface &&
|
||||
isRawPlayableMedia &&
|
||||
attachment.available &&
|
||||
!!attachment.objectUrl;
|
||||
|
||||
return {
|
||||
...attachment,
|
||||
canOpenExternally: this.platform.isElectron && attachment.available && !!this.getAttachmentDiskPath(attachment),
|
||||
canUseExperimentalPlayer,
|
||||
experimentalPlayerActive: canUseExperimentalPlayer && this.experimentalPlayerAttachmentId() === attachment.id,
|
||||
isAudio,
|
||||
isUploader: this.isUploader(attachment),
|
||||
isVideo,
|
||||
@@ -572,6 +630,30 @@ export class ChatMessageItemComponent {
|
||||
private getLiveAttachment(attachmentId: string): Attachment | undefined {
|
||||
return this.attachmentsSvc.getForMessage(this.message().id).find((attachment) => attachment.id === attachmentId);
|
||||
}
|
||||
|
||||
private getAttachmentDiskPath(attachment: Attachment): string | null {
|
||||
return attachment.savedPath || attachment.filePath || null;
|
||||
}
|
||||
|
||||
private canPlayMediaType(mime: string): boolean {
|
||||
if (!mime.startsWith('video/') && !mime.startsWith('audio/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cached = this.mediaSupportCache.get(mime);
|
||||
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const element = document.createElement(mime.startsWith('video/') ? 'video' : 'audio');
|
||||
|
||||
const canPlay = element.canPlayType(mime) !== '';
|
||||
|
||||
this.mediaSupportCache.set(mime, canPlay);
|
||||
|
||||
return canPlay;
|
||||
}
|
||||
}
|
||||
|
||||
function parsePluginEmbedToken(content: string): PluginEmbedToken | null {
|
||||
|
||||
Reference in New Issue
Block a user