File size: 4,824 Bytes
cfb0fa4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
<script lang="ts">
	import { getContext } from 'svelte';
	const i18n = getContext('i18n');

	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';

	import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
	import Clipboard from '$lib/components/icons/Clipboard.svelte';
	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
	import Tooltip from '$lib/components/common/Tooltip.svelte';

	import { toast } from 'svelte-sonner';
	import dayjs from 'dayjs';

	export let webhook;
	export let expanded = false;

	export let onClick = () => {};
	export let onDelete = () => {};
	export let onUpdate = (changes: { name: string; profile_image_url: string }) => {};

	let name = webhook.name;
	let image = webhook.profile_image_url || '';

	// Notify parent when changes occur
	$: if (name !== webhook.name || image !== (webhook.profile_image_url || '')) {
		onUpdate({ name: name.trim() || webhook.name, profile_image_url: image });
	}

	let filesInputElement;
	let inputFiles;

	const handleImageUpload = () => {
		if (!inputFiles?.length) return;

		const reader = new FileReader();
		reader.onload = (event) => {
			const dataUrl = `${event.target?.result}`;
			const fileType = inputFiles[0]?.type;

			if (['image/gif', 'image/webp'].includes(fileType)) {
				image = dataUrl;
			} else {
				const tempImage = new Image();
				tempImage.src = dataUrl;
				tempImage.onload = () => {
					const canvas = document.createElement('canvas');
					const canvasSize = 100;
					canvas.width = canvasSize;
					canvas.height = canvasSize;

					const context = canvas.getContext('2d');
					const aspectRatio = tempImage.width / tempImage.height;
					const scaledWidth = aspectRatio > 1 ? canvasSize * aspectRatio : canvasSize;
					const scaledHeight = aspectRatio > 1 ? canvasSize : canvasSize / aspectRatio;
					const offsetX = (canvasSize - scaledWidth) / 2;
					const offsetY = (canvasSize - scaledHeight) / 2;

					context.drawImage(tempImage, offsetX, offsetY, scaledWidth, scaledHeight);
					image = canvas.toDataURL('image/webp', 0.8);
				};
			}
			inputFiles = null;
		};
		reader.readAsDataURL(inputFiles[0]);
	};

	const copyUrl = () => {
		navigator.clipboard.writeText(
			`${WEBUI_API_BASE_URL}/channels/webhooks/${webhook.id}/${webhook.token}`
		);
		toast.success($i18n.t('Copied'));
	};
</script>

<input
	bind:this={filesInputElement}
	bind:files={inputFiles}
	type="file"
	hidden
	accept="image/*"
	on:change={handleImageUpload}
/>

<div class="text-xs -mx-1">
	<!-- Row -->
	<button
		type="button"
		class="w-full flex items-center gap-3 px-3.5 py-3 hover:bg-gray-50 dark:hover:bg-gray-900 rounded-xl transition"
		on:click={onClick}
	>
		<img
			src={image || `${WEBUI_BASE_URL}/static/favicon.png`}
			class="rounded-full size-8 object-cover flex-shrink-0"
			alt=""
		/>
		<div class="flex-1 text-left min-w-0">
			<div class="font-medium text-gray-900 dark:text-white truncate">
				{name}
			</div>
			<div class="text-gray-500 text-xs">
				{$i18n.t('Created on {{date}}', {
					date: dayjs(webhook.created_at / 1000000).format('MMM D, YYYY')
				})}
				{#if webhook.user?.name}
					{$i18n.t('by {{name}}', { name: webhook.user.name })}
				{/if}
			</div>
		</div>
		<ChevronDown
			className="size-3.5 text-gray-400 transition-transform duration-200 {expanded
				? 'rotate-180'
				: ''}"
		/>
	</button>

	<!-- Expanded -->
	{#if expanded}
		<div class="mt-1 mb-3 px-3.5 py-3 border border-gray-100 dark:border-gray-850 rounded-2xl">
			<div class="flex items-center gap-3">
				<button
					type="button"
					class="shrink-0 rounded-xl overflow-hidden hover:opacity-80 transition"
					on:click={() => filesInputElement.click()}
				>
					<img
						src={image || `${WEBUI_BASE_URL}/static/favicon.png`}
						class="size-8 object-cover"
						alt=""
					/>
				</button>
				<div class="flex-1">
					<div class=" text-gray-500 text-xs">{$i18n.t('Name')}</div>
					<input
						type="text"
						class="w-full text-sm bg-transparent outline-none placeholder:text-gray-300 dark:placeholder:text-gray-700"
						bind:value={name}
						placeholder={$i18n.t('Webhook Name')}
					/>
				</div>
				<div class="flex items-center gap-1">
					<Tooltip content={$i18n.t('Copy URL')}>
						<button
							type="button"
							class="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition"
							on:click={copyUrl}
						>
							<Clipboard className="size-4 text-gray-500" />
						</button>
					</Tooltip>
					<Tooltip content={$i18n.t('Delete')}>
						<button
							type="button"
							class="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition"
							on:click={onDelete}
						>
							<GarbageBin className="size-4 text-gray-500" />
						</button>
					</Tooltip>
				</div>
			</div>
		</div>
	{/if}
</div>