create-shared-link-modal.svelte 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <script lang="ts">
  2. import { createEventDispatcher, onMount } from 'svelte';
  3. import BaseModal from '../base-modal.svelte';
  4. import Link from 'svelte-material-icons/Link.svelte';
  5. import {
  6. AlbumResponseDto,
  7. api,
  8. AssetResponseDto,
  9. SharedLinkResponseDto,
  10. SharedLinkType
  11. } from '@api';
  12. import { notificationController, NotificationType } from '../notification/notification';
  13. import { ImmichDropDownOption } from '../dropdown-button.svelte';
  14. import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.svelte';
  15. import DropdownButton from '../dropdown-button.svelte';
  16. import SettingInputField, {
  17. SettingInputFieldType
  18. } from '$lib/components/admin-page/settings/setting-input-field.svelte';
  19. import { handleError } from '$lib/utils/handle-error';
  20. export let shareType: SharedLinkType;
  21. export let sharedAssets: AssetResponseDto[] = [];
  22. export let album: AlbumResponseDto | undefined = undefined;
  23. export let editingLink: SharedLinkResponseDto | undefined = undefined;
  24. let isShowSharedLink = false;
  25. let expirationTime = '';
  26. let isAllowUpload = false;
  27. let sharedLink = '';
  28. let description = '';
  29. let shouldChangeExpirationTime = false;
  30. let isAllowDownload = true;
  31. let shouldShowExif = true;
  32. const dispatch = createEventDispatcher();
  33. const expiredDateOption: ImmichDropDownOption = {
  34. default: 'Never',
  35. options: ['Never', '30 minutes', '1 hour', '6 hours', '1 day', '7 days', '30 days']
  36. };
  37. onMount(() => {
  38. if (editingLink) {
  39. if (editingLink.description) {
  40. description = editingLink.description;
  41. }
  42. isAllowUpload = editingLink.allowUpload;
  43. isAllowDownload = editingLink.allowDownload;
  44. shouldShowExif = editingLink.showExif;
  45. }
  46. });
  47. const handleCreateSharedLink = async () => {
  48. const expirationTime = getExpirationTimeInMillisecond();
  49. const currentTime = new Date().getTime();
  50. const expirationDate = expirationTime
  51. ? new Date(currentTime + expirationTime).toISOString()
  52. : undefined;
  53. try {
  54. if (shareType === SharedLinkType.Album && album) {
  55. const { data } = await api.albumApi.createAlbumSharedLink({
  56. albumId: album.id,
  57. expiredAt: expirationDate,
  58. allowUpload: isAllowUpload,
  59. description: description,
  60. allowDownload: isAllowDownload,
  61. showExif: shouldShowExif
  62. });
  63. buildSharedLink(data);
  64. } else {
  65. const { data } = await api.assetApi.createAssetsSharedLink({
  66. assetIds: sharedAssets.map((a) => a.id),
  67. expiredAt: expirationDate,
  68. allowUpload: isAllowUpload,
  69. description: description,
  70. allowDownload: isAllowDownload,
  71. showExif: shouldShowExif
  72. });
  73. buildSharedLink(data);
  74. }
  75. } catch (e) {
  76. handleError(e, 'Failed to create shared link');
  77. }
  78. isShowSharedLink = true;
  79. };
  80. const buildSharedLink = (createdLink: SharedLinkResponseDto) => {
  81. sharedLink = `${window.location.origin}/share/${createdLink.key}`;
  82. };
  83. const handleCopy = async () => {
  84. try {
  85. await navigator.clipboard.writeText(sharedLink);
  86. notificationController.show({
  87. message: 'Copied to clipboard!',
  88. type: NotificationType.Info
  89. });
  90. } catch (e) {
  91. handleError(
  92. e,
  93. 'Cannot copy to clipboard, make sure you are accessing the page through https'
  94. );
  95. }
  96. };
  97. const getExpirationTimeInMillisecond = () => {
  98. switch (expirationTime) {
  99. case '30 minutes':
  100. return 30 * 60 * 1000;
  101. case '1 hour':
  102. return 60 * 60 * 1000;
  103. case '6 hours':
  104. return 6 * 60 * 60 * 1000;
  105. case '1 day':
  106. return 24 * 60 * 60 * 1000;
  107. case '7 days':
  108. return 7 * 24 * 60 * 60 * 1000;
  109. case '30 days':
  110. return 30 * 24 * 60 * 60 * 1000;
  111. default:
  112. return 0;
  113. }
  114. };
  115. const handleEditLink = async () => {
  116. if (editingLink) {
  117. try {
  118. const expirationTime = getExpirationTimeInMillisecond();
  119. const currentTime = new Date().getTime();
  120. let expirationDate = expirationTime
  121. ? new Date(currentTime + expirationTime).toISOString()
  122. : undefined;
  123. if (expirationTime === 0) {
  124. expirationDate = undefined;
  125. }
  126. await api.shareApi.editSharedLink(editingLink.id, {
  127. description: description,
  128. expiredAt: expirationDate,
  129. allowUpload: isAllowUpload,
  130. isEditExpireTime: shouldChangeExpirationTime,
  131. allowDownload: isAllowDownload,
  132. showExif: shouldShowExif
  133. });
  134. notificationController.show({
  135. type: NotificationType.Info,
  136. message: 'Edited'
  137. });
  138. dispatch('close');
  139. } catch (e) {
  140. handleError(e, 'Failed to edit shared link');
  141. }
  142. }
  143. };
  144. </script>
  145. <BaseModal on:close={() => dispatch('close')}>
  146. <svelte:fragment slot="title">
  147. <span class="flex gap-2 place-items-center">
  148. <Link size={24} />
  149. {#if editingLink}
  150. <p class="font-medium text-immich-fg dark:text-immich-dark-fg">Edit link</p>
  151. {:else}
  152. <p class="font-medium text-immich-fg dark:text-immich-dark-fg">Create link to share</p>
  153. {/if}
  154. </span>
  155. </svelte:fragment>
  156. <section class="mx-6 mb-6">
  157. {#if shareType == SharedLinkType.Album}
  158. {#if !editingLink}
  159. <div>Let anyone with the link see photos and people in this album.</div>
  160. {:else}
  161. <div class="text-sm">
  162. Public album | <span class="text-immich-primary dark:text-immich-dark-primary"
  163. >{editingLink.album?.albumName}</span
  164. >
  165. </div>
  166. {/if}
  167. {/if}
  168. {#if shareType == SharedLinkType.Individual}
  169. {#if !editingLink}
  170. <div>Let anyone with the link see the selected photo(s)</div>
  171. {:else}
  172. <div class="text-sm">
  173. Individual shared | <span class="text-immich-primary dark:text-immich-dark-primary"
  174. >{editingLink.description}</span
  175. >
  176. </div>
  177. {/if}
  178. {/if}
  179. <div class="mt-4 mb-2">
  180. <p class="text-xs">LINK OPTIONS</p>
  181. </div>
  182. <div class="p-4 bg-gray-100 dark:bg-black/40 rounded-lg">
  183. <div class="flex flex-col">
  184. <div class="mb-2">
  185. <SettingInputField
  186. inputType={SettingInputFieldType.TEXT}
  187. label="Description"
  188. bind:value={description}
  189. />
  190. </div>
  191. <div class="my-3">
  192. <SettingSwitch bind:checked={shouldShowExif} title={'Show metadata'} />
  193. </div>
  194. <div class="my-3">
  195. <SettingSwitch bind:checked={isAllowDownload} title={'Allow public user to download'} />
  196. </div>
  197. <div class="my-3">
  198. <SettingSwitch bind:checked={isAllowUpload} title={'Allow public user to upload'} />
  199. </div>
  200. <div class="text-sm">
  201. {#if editingLink}
  202. <p class="my-2 immich-form-label">
  203. <SettingSwitch
  204. bind:checked={shouldChangeExpirationTime}
  205. title={'Change expiration time'}
  206. />
  207. </p>
  208. {:else}
  209. <p class="my-2 immich-form-label">Expire after</p>
  210. {/if}
  211. <DropdownButton
  212. options={expiredDateOption}
  213. bind:selected={expirationTime}
  214. disabled={editingLink && !shouldChangeExpirationTime}
  215. />
  216. </div>
  217. </div>
  218. </div>
  219. </section>
  220. <hr />
  221. <section class="m-6">
  222. {#if !isShowSharedLink}
  223. {#if editingLink}
  224. <div class="flex justify-end">
  225. <button
  226. on:click={handleEditLink}
  227. class="text-white dark:text-black bg-immich-primary px-4 py-2 rounded-lg text-sm transition-colors hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:hover:bg-immich-dark-primary/75"
  228. >
  229. Confirm
  230. </button>
  231. </div>
  232. {:else}
  233. <div class="flex justify-end">
  234. <button
  235. on:click={handleCreateSharedLink}
  236. class="text-white dark:text-black bg-immich-primary px-4 py-2 rounded-lg text-sm transition-colors hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:hover:bg-immich-dark-primary/75"
  237. >
  238. Create Link
  239. </button>
  240. </div>
  241. {/if}
  242. {/if}
  243. {#if isShowSharedLink}
  244. <div class="flex w-full gap-4">
  245. <input class="immich-form-input w-full" bind:value={sharedLink} disabled />
  246. <button
  247. on:click={() => handleCopy()}
  248. class="flex-1 transition-colors bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 dark:hover:bg-immich-dark-primary/80 dark:text-immich-dark-gray px-6 py-2 text-white rounded-full shadow-md w-full font-medium"
  249. >Copy</button
  250. >
  251. </div>
  252. {/if}
  253. </section>
  254. </BaseModal>