mirror of
https://github.com/immich-app/immich.git
synced 2025-01-01 08:31:59 +00:00
refactor(web): descriptions (#6517)
* refactor: reusable autogrow * fix: remove useless autogrow * fix: correct size for album description * fix: format * fix: move to own file * refactor: album description * refactor: asset description * simplify * fix: style when no description provided * fix: switching assets * feat: update description with ctrl + enter * fix: variable name * fix: styling --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
95cfe22866
commit
3845fec280
5 changed files with 97 additions and 124 deletions
|
@ -1,49 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import type { AlbumResponseDto } from '@api';
|
|
||||||
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
close: void;
|
|
||||||
save: string;
|
|
||||||
}>();
|
|
||||||
export let album: AlbumResponseDto;
|
|
||||||
|
|
||||||
let description = album.description;
|
|
||||||
|
|
||||||
const handleCancel = () => dispatch('close');
|
|
||||||
const handleSubmit = () => dispatch('save', description);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FullScreenModal on:clickOutside={handleCancel} on:escape={handleCancel}>
|
|
||||||
<div
|
|
||||||
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex flex-col place-content-center place-items-center gap-4 px-4 text-immich-primary dark:text-immich-dark-primary"
|
|
||||||
>
|
|
||||||
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">Edit description</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
|
||||||
<div class="m-4 flex flex-col gap-2">
|
|
||||||
<label class="immich-form-label" for="name">Description</label>
|
|
||||||
<!-- svelte-ignore a11y-autofocus -->
|
|
||||||
<textarea
|
|
||||||
class="immich-form-input focus:outline-none"
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
rows="5"
|
|
||||||
bind:value={description}
|
|
||||||
autofocus
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-8 flex w-full gap-4 px-4">
|
|
||||||
<Button color="gray" fullwidth on:click={handleCancel}>Cancel</Button>
|
|
||||||
<Button type="submit" fullwidth>Ok</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</FullScreenModal>
|
|
|
@ -19,6 +19,7 @@
|
||||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
import { getAssetType } from '$lib/utils/asset-utils';
|
import { getAssetType } from '$lib/utils/asset-utils';
|
||||||
import * as luxon from 'luxon';
|
import * as luxon from 'luxon';
|
||||||
|
import { autoGrowHeight } from '$lib/utils/autogrow';
|
||||||
|
|
||||||
const units: Intl.RelativeTimeFormatUnit[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
|
const units: Intl.RelativeTimeFormatUnit[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
|
||||||
|
|
||||||
|
@ -98,11 +99,6 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const autoGrow = () => {
|
|
||||||
textArea.style.height = '5px';
|
|
||||||
textArea.style.height = textArea.scrollHeight + 'px';
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeOptions = {
|
const timeOptions = {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: '2-digit',
|
month: '2-digit',
|
||||||
|
@ -293,7 +289,7 @@
|
||||||
bind:this={textArea}
|
bind:this={textArea}
|
||||||
bind:value={message}
|
bind:value={message}
|
||||||
placeholder={disabled ? 'Comments are disabled' : 'Say something'}
|
placeholder={disabled ? 'Comments are disabled' : 'Say something'}
|
||||||
on:input={autoGrow}
|
on:input={() => autoGrowHeight(textArea)}
|
||||||
on:keypress={handleEnter}
|
on:keypress={handleEnter}
|
||||||
class="h-[18px] {disabled
|
class="h-[18px] {disabled
|
||||||
? 'cursor-not-allowed'
|
? 'cursor-not-allowed'
|
||||||
|
|
|
@ -31,14 +31,17 @@
|
||||||
import ChangeLocation from '../shared-components/change-location.svelte';
|
import ChangeLocation from '../shared-components/change-location.svelte';
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { autoGrowHeight } from '$lib/utils/autogrow';
|
||||||
|
import { clickOutside } from '$lib/utils/click-outside';
|
||||||
|
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let albums: AlbumResponseDto[] = [];
|
export let albums: AlbumResponseDto[] = [];
|
||||||
export let albumId: string | null = null;
|
export let albumId: string | null = null;
|
||||||
|
|
||||||
let showAssetPath = false;
|
let showAssetPath = false;
|
||||||
let textarea: HTMLTextAreaElement;
|
let textArea: HTMLTextAreaElement;
|
||||||
let description: string;
|
let description: string;
|
||||||
|
let originalDescription: string;
|
||||||
let showEditFaces = false;
|
let showEditFaces = false;
|
||||||
let previousId: string;
|
let previousId: string;
|
||||||
|
|
||||||
|
@ -61,10 +64,10 @@
|
||||||
if (newAsset.id && !api.isSharedLink) {
|
if (newAsset.id && !api.isSharedLink) {
|
||||||
const { data } = await api.assetApi.getAssetById({ id: asset.id });
|
const { data } = await api.assetApi.getAssetById({ id: asset.id });
|
||||||
people = data?.people || [];
|
people = data?.people || [];
|
||||||
|
|
||||||
description = data.exifInfo?.description || '';
|
description = data.exifInfo?.description || '';
|
||||||
textarea.value = description;
|
|
||||||
autoGrowHeight();
|
|
||||||
}
|
}
|
||||||
|
originalDescription = description;
|
||||||
};
|
};
|
||||||
|
|
||||||
$: handleNewAsset(asset);
|
$: handleNewAsset(asset);
|
||||||
|
@ -99,6 +102,19 @@
|
||||||
closeViewer: void;
|
closeViewer: void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const handleKeypress = async (event: KeyboardEvent) => {
|
||||||
|
if (event.target !== textArea) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ctrl = event.ctrlKey;
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Enter':
|
||||||
|
if (ctrl && event.target === textArea) {
|
||||||
|
handleFocusOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getMegapixel = (width: number, height: number): number | undefined => {
|
const getMegapixel = (width: number, height: number): number | undefined => {
|
||||||
const megapixel = Math.round((height * width) / 1_000_000);
|
const megapixel = Math.round((height * width) / 1_000_000);
|
||||||
|
|
||||||
|
@ -112,21 +128,21 @@
|
||||||
const handleRefreshPeople = async () => {
|
const handleRefreshPeople = async () => {
|
||||||
await api.assetApi.getAssetById({ id: asset.id }).then((res) => {
|
await api.assetApi.getAssetById({ id: asset.id }).then((res) => {
|
||||||
people = res.data?.people || [];
|
people = res.data?.people || [];
|
||||||
textarea.value = res.data?.exifInfo?.description || '';
|
textArea.value = res.data?.exifInfo?.description || '';
|
||||||
});
|
});
|
||||||
showEditFaces = false;
|
showEditFaces = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const autoGrowHeight = () => {
|
|
||||||
textarea.style.height = 'auto';
|
|
||||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFocusIn = () => {
|
const handleFocusIn = () => {
|
||||||
dispatch('descriptionFocusIn');
|
dispatch('descriptionFocusIn');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFocusOut = async () => {
|
const handleFocusOut = async () => {
|
||||||
|
textArea.blur();
|
||||||
|
if (description === originalDescription) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalDescription = description;
|
||||||
dispatch('descriptionFocusOut');
|
dispatch('descriptionFocusOut');
|
||||||
try {
|
try {
|
||||||
await api.assetApi.updateAsset({
|
await api.assetApi.updateAsset({
|
||||||
|
@ -134,7 +150,7 @@
|
||||||
updateAssetDto: { description },
|
updateAssetDto: { description },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
handleError(error, 'Cannot update the description');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -170,6 +186,8 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={handleKeypress} />
|
||||||
|
|
||||||
<section class="relative p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
|
<section class="relative p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
|
||||||
<div class="flex place-items-center gap-2">
|
<div class="flex place-items-center gap-2">
|
||||||
<button
|
<button
|
||||||
|
@ -196,22 +214,26 @@
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<section class="mx-4 mt-10" style:display={!isOwner && description === '' ? 'none' : 'block'}>
|
{#if isOwner || description !== ''}
|
||||||
{#if !isOwner || api.isSharedLink}
|
<section class="px-4 mt-10">
|
||||||
<span class="break-words">{description}</span>
|
{#key asset.id}
|
||||||
{:else}
|
<textarea
|
||||||
<textarea
|
disabled={!isOwner || api.isSharedLink}
|
||||||
bind:this={textarea}
|
bind:this={textArea}
|
||||||
class="max-h-[500px]
|
class="max-h-[500px]
|
||||||
w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
|
w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
|
||||||
placeholder={!isOwner ? '' : 'Add a description'}
|
placeholder={!isOwner ? '' : 'Add a description'}
|
||||||
on:focusin={handleFocusIn}
|
on:focusin={handleFocusIn}
|
||||||
on:focusout={handleFocusOut}
|
on:focusout={handleFocusOut}
|
||||||
on:input={autoGrowHeight}
|
on:input={() => autoGrowHeight(textArea)}
|
||||||
bind:value={description}
|
bind:value={description}
|
||||||
/>
|
use:autoGrowHeight
|
||||||
{/if}
|
use:clickOutside
|
||||||
</section>
|
on:outclick={handleFocusOut}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if !api.isSharedLink && people.length > 0}
|
{#if !api.isSharedLink && people.length > 0}
|
||||||
<section class="px-4 py-4 text-sm">
|
<section class="px-4 py-4 text-sm">
|
||||||
|
@ -315,7 +337,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-sm">DETAILS</p>
|
<div class="flex h-10 w-full items-center justify-between text-sm">
|
||||||
|
<h2>DETAILS</h2>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if asset.exifInfo?.dateTimeOriginal && !asset.isReadOnly}
|
{#if asset.exifInfo?.dateTimeOriginal && !asset.isReadOnly}
|
||||||
|
|
5
web/src/lib/utils/autogrow.ts
Normal file
5
web/src/lib/utils/autogrow.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export const autoGrowHeight = (textarea: HTMLTextAreaElement, height = 'auto') => {
|
||||||
|
textarea.scrollHeight;
|
||||||
|
textarea.style.height = height;
|
||||||
|
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||||
|
};
|
|
@ -1,6 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { afterNavigate, goto } from '$app/navigation';
|
import { afterNavigate, goto } from '$app/navigation';
|
||||||
import EditDescriptionModal from '$lib/components/album-page/edit-description-modal.svelte';
|
|
||||||
import ShareInfoModal from '$lib/components/album-page/share-info-modal.svelte';
|
import ShareInfoModal from '$lib/components/album-page/share-info-modal.svelte';
|
||||||
import UserSelectionModal from '$lib/components/album-page/user-selection-modal.svelte';
|
import UserSelectionModal from '$lib/components/album-page/user-selection-modal.svelte';
|
||||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
|
@ -60,6 +59,7 @@
|
||||||
import AlbumOptions from '$lib/components/album-page/album-options.svelte';
|
import AlbumOptions from '$lib/components/album-page/album-options.svelte';
|
||||||
import UpdatePanel from '$lib/components/shared-components/update-panel.svelte';
|
import UpdatePanel from '$lib/components/shared-components/update-panel.svelte';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { autoGrowHeight } from '$lib/utils/autogrow';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@
|
||||||
let { slideshowState, slideshowShuffle } = slideshowStore;
|
let { slideshowState, slideshowShuffle } = slideshowStore;
|
||||||
|
|
||||||
let album = data.album;
|
let album = data.album;
|
||||||
|
let description = album.description;
|
||||||
|
|
||||||
$: album = data.album;
|
$: album = data.album;
|
||||||
|
|
||||||
|
@ -91,7 +92,6 @@
|
||||||
let backUrl: string = AppRoute.ALBUMS;
|
let backUrl: string = AppRoute.ALBUMS;
|
||||||
let viewMode = ViewMode.VIEW;
|
let viewMode = ViewMode.VIEW;
|
||||||
let titleInput: HTMLInputElement;
|
let titleInput: HTMLInputElement;
|
||||||
let isEditingDescription = false;
|
|
||||||
let isCreatingSharedAlbum = false;
|
let isCreatingSharedAlbum = false;
|
||||||
let currentAlbumName = album.albumName;
|
let currentAlbumName = album.albumName;
|
||||||
let contextMenuPosition: { x: number; y: number } = { x: 0, y: 0 };
|
let contextMenuPosition: { x: number; y: number } = { x: 0, y: 0 };
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
let reactions: ActivityResponseDto[] = [];
|
let reactions: ActivityResponseDto[] = [];
|
||||||
let globalWidth: number;
|
let globalWidth: number;
|
||||||
let assetGridWidth: number;
|
let assetGridWidth: number;
|
||||||
let textarea: HTMLTextAreaElement;
|
let textArea: HTMLTextAreaElement;
|
||||||
|
|
||||||
const assetStore = new AssetStore({ albumId: album.id });
|
const assetStore = new AssetStore({ albumId: album.id });
|
||||||
const assetInteractionStore = createAssetInteractionStore();
|
const assetInteractionStore = createAssetInteractionStore();
|
||||||
|
@ -123,12 +123,6 @@
|
||||||
$: showActivityStatus =
|
$: showActivityStatus =
|
||||||
album.sharedUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0);
|
album.sharedUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0);
|
||||||
|
|
||||||
$: {
|
|
||||||
if (textarea) {
|
|
||||||
textarea.value = album.description;
|
|
||||||
autoGrowHeight();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$: afterNavigate(({ from }) => {
|
$: afterNavigate(({ from }) => {
|
||||||
assetViewingStore.showAssetViewer(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
|
|
||||||
|
@ -149,13 +143,6 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const autoGrowHeight = () => {
|
|
||||||
// little hack so that the height of the text area is correctly initialized
|
|
||||||
textarea.scrollHeight;
|
|
||||||
textarea.style.height = '5px';
|
|
||||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleEnableActivity = async () => {
|
const handleToggleEnableActivity = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.albumApi.updateAlbumInfo({
|
const { data } = await api.albumApi.updateAlbumInfo({
|
||||||
|
@ -231,6 +218,19 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleKeypress = async (event: KeyboardEvent) => {
|
||||||
|
if (event.target !== textArea) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ctrl = event.ctrlKey;
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Enter':
|
||||||
|
if (ctrl && event.target === textArea) {
|
||||||
|
textArea.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleStartSlideshow = async () => {
|
const handleStartSlideshow = async () => {
|
||||||
const asset = $slideshowShuffle ? await assetStore.getRandomAsset() : assetStore.assets[0];
|
const asset = $slideshowShuffle ? await assetStore.getRandomAsset() : assetStore.assets[0];
|
||||||
if (asset) {
|
if (asset) {
|
||||||
|
@ -252,6 +252,14 @@
|
||||||
handleCloseSelectAssets();
|
handleCloseSelectAssets();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (viewMode === ViewMode.LINK_SHARING) {
|
||||||
|
viewMode = ViewMode.VIEW;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (viewMode === ViewMode.OPTIONS) {
|
||||||
|
viewMode = ViewMode.VIEW;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ($showAssetViewer) {
|
if ($showAssetViewer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -426,7 +434,10 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateDescription = async (description: string) => {
|
const handleUpdateDescription = async () => {
|
||||||
|
if (album.description === description) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await api.albumApi.updateAlbumInfo({
|
await api.albumApi.updateAlbumInfo({
|
||||||
id: album.id,
|
id: album.id,
|
||||||
|
@ -436,13 +447,14 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
album.description = description;
|
album.description = description;
|
||||||
isEditingDescription = false;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Error updating album description');
|
handleError(error, 'Error updating album description');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={handleKeypress} />
|
||||||
|
|
||||||
<div class="flex overflow-hidden" bind:clientWidth={globalWidth}>
|
<div class="flex overflow-hidden" bind:clientWidth={globalWidth}>
|
||||||
<div class="relative w-full shrink">
|
<div class="relative w-full shrink">
|
||||||
{#if $isMultiSelectState}
|
{#if $isMultiSelectState}
|
||||||
|
@ -640,24 +652,17 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- ALBUM DESCRIPTION -->
|
<!-- ALBUM DESCRIPTION -->
|
||||||
{#if isOwned || album.description}
|
<textarea
|
||||||
<button
|
class="w-full resize-none overflow-hidden text-black dark:text-white border-b-2 border-transparent border-gray-500 bg-transparent text-base outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:focus:border-immich-dark-primary hover:border-gray-400"
|
||||||
class="mb-12 mt-6 w-full border-b-2 border-transparent pb-2 text-left text-lg font-medium transition-colors hover:border-b-2 dark:text-gray-300"
|
bind:this={textArea}
|
||||||
on:click={() => (isEditingDescription = true)}
|
bind:value={description}
|
||||||
class:hover:border-gray-400={isOwned}
|
disabled={!isOwned}
|
||||||
disabled={!isOwned}
|
on:input={() => autoGrowHeight(textArea)}
|
||||||
title="Edit description"
|
on:focusout={handleUpdateDescription}
|
||||||
>
|
use:autoGrowHeight
|
||||||
<textarea
|
placeholder="Add description"
|
||||||
class="w-full bg-transparent resize-none overflow-hidden outline-none"
|
/>
|
||||||
bind:this={textarea}
|
|
||||||
bind:value={album.description}
|
|
||||||
placeholder="Add description"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -763,14 +768,6 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if isEditingDescription}
|
|
||||||
<EditDescriptionModal
|
|
||||||
{album}
|
|
||||||
on:close={() => (isEditingDescription = false)}
|
|
||||||
on:save={({ detail: description }) => handleUpdateDescription(description)}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<UpdatePanel {assetStore} />
|
<UpdatePanel {assetStore} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
Loading…
Reference in a new issue