mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 23:59:39 +02:00
✨ feat(matrix): add widget support to Manalink client
Add the ability to view and interact with Matrix widgets in the room settings panel. Widgets are displayed in a new "Widgets" tab with collapsible iframes. - Add RoomWidget type to types.ts - Add getRoomWidgets() and buildWidgetUrl() methods to store - Add Widgets tab to RoomSettingsPanel with iframe display - Handle Matrix variable substitution ($matrix_user_id, $matrix_room_id) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
78c7383d54
commit
376ba8279d
3 changed files with 132 additions and 1 deletions
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { matrixStore } from '$lib/matrix';
|
||||
import type { RoomWidget } from '$lib/matrix/types';
|
||||
import {
|
||||
X,
|
||||
Users,
|
||||
|
|
@ -11,6 +12,7 @@
|
|||
Bell,
|
||||
BellSlash,
|
||||
CircleNotch,
|
||||
SquaresFour,
|
||||
} from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -20,15 +22,25 @@
|
|||
|
||||
let { open, onClose }: Props = $props();
|
||||
|
||||
let activeTab = $state<'members' | 'settings'>('members');
|
||||
let activeTab = $state<'members' | 'widgets' | 'settings'>('members');
|
||||
let inviteQuery = $state('');
|
||||
let searchResults = $state<{ userId: string; displayName?: string; avatarUrl?: string }[]>([]);
|
||||
let searching = $state(false);
|
||||
let inviting = $state(false);
|
||||
let searchTimeout: ReturnType<typeof setTimeout>;
|
||||
let expandedWidget = $state<string | null>(null);
|
||||
|
||||
let room = $derived(matrixStore.currentSimpleRoom);
|
||||
let members = $derived(matrixStore.getRoomMembers());
|
||||
let widgets = $derived(matrixStore.getRoomWidgets());
|
||||
|
||||
function getWidgetUrl(widget: RoomWidget): string {
|
||||
return matrixStore.buildWidgetUrl(widget);
|
||||
}
|
||||
|
||||
function toggleWidget(widgetId: string) {
|
||||
expandedWidget = expandedWidget === widgetId ? null : widgetId;
|
||||
}
|
||||
|
||||
function handleSearchInput() {
|
||||
clearTimeout(searchTimeout);
|
||||
|
|
@ -129,6 +141,17 @@
|
|||
<Users class="mr-1 h-4 w-4" />
|
||||
Mitglieder
|
||||
</button>
|
||||
<button
|
||||
class="tab flex-1"
|
||||
class:tab-active={activeTab === 'widgets'}
|
||||
onclick={() => (activeTab = 'widgets')}
|
||||
>
|
||||
<SquaresFour class="mr-1 h-4 w-4" />
|
||||
Widgets
|
||||
{#if widgets.length > 0}
|
||||
<span class="badge badge-sm badge-primary ml-1">{widgets.length}</span>
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
class="tab flex-1"
|
||||
class:tab-active={activeTab === 'settings'}
|
||||
|
|
@ -211,6 +234,43 @@
|
|||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else if activeTab === 'widgets'}
|
||||
<!-- Widgets -->
|
||||
<div class="p-3">
|
||||
{#if widgets.length === 0}
|
||||
<div class="text-center py-8 text-base-content/50">
|
||||
<SquaresFour class="h-12 w-12 mx-auto mb-2 opacity-50" />
|
||||
<p>Keine Widgets in diesem Raum</p>
|
||||
<p class="text-xs mt-1">Bots können Widgets hinzufügen</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-3">
|
||||
{#each widgets as widget}
|
||||
<div class="card bg-base-200 shadow-sm">
|
||||
<div class="card-body p-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="font-medium text-sm">{widget.name}</h3>
|
||||
<button class="btn btn-ghost btn-xs" onclick={() => toggleWidget(widget.id)}>
|
||||
{expandedWidget === widget.id ? 'Schließen' : 'Öffnen'}
|
||||
</button>
|
||||
</div>
|
||||
{#if expandedWidget === widget.id}
|
||||
<div class="mt-2 -mx-3 -mb-3">
|
||||
<iframe
|
||||
src={getWidgetUrl(widget)}
|
||||
title={widget.name}
|
||||
class="w-full border-0 rounded-b-2xl bg-base-300"
|
||||
style="height: 300px;"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
||||
></iframe>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Settings -->
|
||||
<div class="space-y-2 p-3">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import type {
|
|||
CallState as CallStateType,
|
||||
CallType,
|
||||
CallDirection,
|
||||
RoomWidget,
|
||||
} from './types';
|
||||
|
||||
const STORAGE_KEY = 'matrix_credentials';
|
||||
|
|
@ -968,6 +969,60 @@ class MatrixStore {
|
|||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get widgets in a room
|
||||
*/
|
||||
getRoomWidgets(roomId?: string): RoomWidget[] {
|
||||
const id = roomId || this._currentRoomId;
|
||||
if (!this._client || !id) return [];
|
||||
|
||||
const room = this._client.getRoom(id);
|
||||
if (!room) return [];
|
||||
|
||||
const widgets: RoomWidget[] = [];
|
||||
|
||||
// Get all widget state events (im.vector.modular.widgets is the standard type)
|
||||
const widgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
||||
|
||||
for (const event of widgetEvents) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const content = (event as any).getContent?.();
|
||||
if (!content || !content.url) continue; // Skip removed widgets
|
||||
|
||||
widgets.push({
|
||||
id: content.id || (event as any).getStateKey?.() || '',
|
||||
type: content.type || 'm.custom',
|
||||
name: content.name || 'Widget',
|
||||
url: content.url,
|
||||
creatorUserId: content.creatorUserId || (event as any).getSender?.() || '',
|
||||
data: content.data,
|
||||
});
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build widget URL with Matrix variable substitution
|
||||
*/
|
||||
buildWidgetUrl(widget: RoomWidget, roomId?: string): string {
|
||||
const id = roomId || this._currentRoomId;
|
||||
const userId = this._client?.getUserId() || '';
|
||||
|
||||
let url = widget.url;
|
||||
|
||||
// Substitute Matrix variables
|
||||
url = url.replace(/\$matrix_user_id/g, encodeURIComponent(userId));
|
||||
url = url.replace(/\$matrix_room_id/g, encodeURIComponent(id || ''));
|
||||
url = url.replace(
|
||||
/\$matrix_display_name/g,
|
||||
encodeURIComponent(userId.split(':')[0].substring(1))
|
||||
);
|
||||
url = url.replace(/\$matrix_avatar_url/g, '');
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// Crypto Actions
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -306,3 +306,19 @@ export interface CallCallbacks {
|
|||
onCallStateChange?: (call: SimpleCall) => void;
|
||||
onCallEnded?: (call: SimpleCall, reason?: string) => void;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// Widget Types
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Matrix widget info
|
||||
*/
|
||||
export interface RoomWidget {
|
||||
id: string;
|
||||
type: string;
|
||||
name: string;
|
||||
url: string;
|
||||
creatorUserId: string;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue