mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 00:47:09 +00:00
fix(control): Drag and Drop in DisplayView was completely buggy, now its just a little bit buggy
This commit is contained in:
@@ -11,22 +11,25 @@
|
||||
import DisplayObject from './DisplayObject.svelte';
|
||||
import {
|
||||
all_displays_of_group_selected,
|
||||
get_display_ids_in_group,
|
||||
set_select_for_group,
|
||||
set_new_display_order
|
||||
update_db_displays,
|
||||
local_displays,
|
||||
set_new_display_order,
|
||||
} from '$lib/ts/stores/displays';
|
||||
import DNDGrip from '$lib/components/DNDGrip.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import type { MenuOption } from '$lib/ts/types';
|
||||
import type { Display, DisplayIdGroup, MenuOption } from '$lib/ts/types';
|
||||
import { selected_display_ids } from '$lib/ts/stores/select';
|
||||
import { liveQuery, type Observable } from 'dexie';
|
||||
import { onMount } from 'svelte';
|
||||
import { get_uuid } from '$lib/ts/utils';
|
||||
|
||||
let {
|
||||
display_group_id,
|
||||
display_id_group,
|
||||
get_display_menu_options,
|
||||
close_pinned_display
|
||||
}: {
|
||||
display_group_id: string;
|
||||
display_id_group: DisplayIdGroup;
|
||||
get_display_menu_options: (display_id: string) => MenuOption[];
|
||||
close_pinned_display: () => void;
|
||||
} = $props();
|
||||
@@ -34,14 +37,14 @@
|
||||
let all_selected: Observable<boolean> | undefined = $state();
|
||||
$effect(() => {
|
||||
const sdi = $selected_display_ids;
|
||||
all_selected = liveQuery(() => all_displays_of_group_selected(display_group_id, sdi));
|
||||
all_selected = liveQuery(() => all_displays_of_group_selected(display_id_group.id, sdi));
|
||||
});
|
||||
let display_ids_in_group = liveQuery(() => get_display_ids_in_group(display_group_id));
|
||||
|
||||
let hovering_selectable = $state(false);
|
||||
|
||||
async function select_all_of_this_group() {
|
||||
const new_value = !($all_selected || false);
|
||||
await set_select_for_group(display_group_id, new_value);
|
||||
await set_select_for_group(display_id_group.id, new_value);
|
||||
}
|
||||
|
||||
async function handle_consider(e: CustomEvent) {
|
||||
@@ -49,14 +52,22 @@
|
||||
|
||||
if (items.length !== 1 && info.trigger === TRIGGERS.DRAG_STARTED) {
|
||||
$is_display_drag = true;
|
||||
// add_empty_display_group();
|
||||
// add empty display group if its not just one group
|
||||
if (!($local_displays.length === 1 && $local_displays[0].displays.length === 1)) {
|
||||
$local_displays.push({
|
||||
id: get_uuid(),
|
||||
displays: []
|
||||
});
|
||||
}
|
||||
}
|
||||
await set_new_display_order(items);
|
||||
set_new_display_order(display_id_group.id, items);
|
||||
}
|
||||
|
||||
async function handle_finalize(e: CustomEvent) {
|
||||
set_new_display_order(display_id_group.id, e.detail.items);
|
||||
// $local_displays = $local_displays.filter((g) => g.displays.length !== 0);
|
||||
await update_db_displays();
|
||||
$is_display_drag = false;
|
||||
await set_new_display_order(e.detail.items);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -77,7 +88,7 @@
|
||||
<div
|
||||
class="flex flex-col min-w-0 pl-2 py-2 gap-2 w-full"
|
||||
use:dragHandleZone={{
|
||||
items: $display_ids_in_group || [],
|
||||
items: display_id_group.displays,
|
||||
type: 'item',
|
||||
flipDurationMs: dnd_flip_duration_ms,
|
||||
dropTargetStyle: { outline: 'none' }
|
||||
@@ -85,18 +96,15 @@
|
||||
onconsider={handle_consider}
|
||||
onfinalize={handle_finalize}
|
||||
>
|
||||
{#each $display_ids_in_group || [] as display (display.id)}
|
||||
{#each display_id_group.displays as display_id_object (display_id_object.id)}
|
||||
<!-- Each Group -->
|
||||
<section
|
||||
animate:flip={{ duration: $is_group_drag ? 0 : dnd_flip_duration_ms, easing: cubicOut }}
|
||||
class="outline-none"
|
||||
role="figure"
|
||||
>
|
||||
<DisplayObject {display} {get_display_menu_options} {close_pinned_display} />
|
||||
<section class="outline-none" role="figure">
|
||||
<!-- animate:flip={{ duration: $is_group_drag ? 0 : dnd_flip_duration_ms, easing: cubicOut }} -->
|
||||
<DisplayObject {display_id_object} {get_display_menu_options} {close_pinned_display} />
|
||||
</section>
|
||||
{/each}
|
||||
|
||||
{#if ($display_ids_in_group || []).length === 0}
|
||||
{#if display_id_group.displays.length === 0}
|
||||
<div class="min-h-10 h-full w-full pl-2 py-2 flex justify-center items-center">
|
||||
Hier in leere neue Gruppe ablegen
|
||||
</div>
|
||||
|
||||
@@ -8,38 +8,40 @@
|
||||
import DNDGrip from '$lib/components/DNDGrip.svelte';
|
||||
import { Menu, Pin, PinOff, VideoOff } from 'lucide-svelte';
|
||||
import OnlineState from './OnlineState.svelte';
|
||||
import type { Display, MenuOption } from '$lib/ts/types';
|
||||
import type { Display, DisplayIdObject, MenuOption } from '$lib/ts/types';
|
||||
import { is_selected, select, selected_display_ids } from '$lib/ts/stores/select';
|
||||
import { screenshot_loop } from '$lib/ts/stores/displays';
|
||||
import { get_display_by_id, screenshot_loop } from '$lib/ts/stores/displays';
|
||||
import { change_file_path, current_file_path } from '$lib/ts/stores/files';
|
||||
import { liveQuery } from 'dexie';
|
||||
|
||||
let {
|
||||
display,
|
||||
display_id_object,
|
||||
get_display_menu_options,
|
||||
close_pinned_display
|
||||
}: {
|
||||
display: Display;
|
||||
display_id_object: DisplayIdObject;
|
||||
get_display_menu_options: (display_id: string) => MenuOption[];
|
||||
close_pinned_display: () => void;
|
||||
} = $props();
|
||||
|
||||
let hovering_unselectable = $state(false);
|
||||
let display = liveQuery(() => get_display_by_id(display_id_object.id));
|
||||
|
||||
async function onclick(e: Event) {
|
||||
e.stopPropagation();
|
||||
select(selected_display_ids, display.id, 'toggle');
|
||||
select(selected_display_ids, display_id_object.id, 'toggle');
|
||||
|
||||
// force file view update
|
||||
await change_file_path($current_file_path);
|
||||
}
|
||||
|
||||
async function on_preview_click(e: MouseEvent) {
|
||||
if ($pinned_display_id === display.id) {
|
||||
if ($pinned_display_id === display_id_object.id) {
|
||||
close_pinned_display();
|
||||
} else {
|
||||
$pinned_display_id = display.id;
|
||||
$pinned_display_id = display_id_object.id;
|
||||
}
|
||||
await screenshot_loop(display.id);
|
||||
await screenshot_loop(display_id_object.id);
|
||||
e.stopPropagation();
|
||||
}
|
||||
</script>
|
||||
@@ -52,7 +54,7 @@
|
||||
if (e.key === 'Enter' || e.key === ' ') onclick(e);
|
||||
}}
|
||||
{onclick}
|
||||
class="p-1 {get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
|
||||
class="p-1 {get_selectable_color_classes(is_selected(display_id_object.id, $selected_display_ids), {
|
||||
bg: true,
|
||||
hover: true,
|
||||
active: !hovering_unselectable,
|
||||
@@ -68,12 +70,12 @@
|
||||
onclick={on_preview_click}
|
||||
>
|
||||
<div class="flex h-full w-full items-center justify-center">
|
||||
{#if $pinned_display_id === display.id}
|
||||
{#if $pinned_display_id === display_id_object.id}
|
||||
<div class="size-[50%]">
|
||||
<Pin class="size-full" />
|
||||
</div>
|
||||
{:else if display.preview.url}
|
||||
<img src={display.preview.url} alt="preview" class="w-full object-cover bg-black" />
|
||||
{:else if $display && $display.preview.url}
|
||||
<img src={$display.preview.url} alt="preview" class="w-full object-cover bg-black" />
|
||||
{:else}
|
||||
<!-- No Signal -->
|
||||
<div class="size-full bg-black flex justify-center items-center">
|
||||
@@ -84,11 +86,11 @@
|
||||
|
||||
<!-- Hover Effect -->
|
||||
<span
|
||||
class="pointer-events-none absolute inset-0 {$pinned_display_id === display.id
|
||||
class="pointer-events-none absolute inset-0 {$pinned_display_id === display_id_object.id
|
||||
? 'bg-stone-700'
|
||||
: 'bg-stone-700/70'} opacity-0 transition-opacity duration-200 flex items-center justify-center group-hover:opacity-100"
|
||||
>
|
||||
{#if $pinned_display_id === display.id}
|
||||
{#if $pinned_display_id === display_id_object.id}
|
||||
<PinOff class="size-[50%]" aria-hidden="true" />
|
||||
{:else}
|
||||
<Pin class="size-[50%]" aria-hidden="true" />
|
||||
@@ -100,20 +102,20 @@
|
||||
class="h-full flex flex-col justify-center gap-1 select-none
|
||||
min-w-0 basis-0 flex-1"
|
||||
>
|
||||
<div class="text-xl font-bold truncate w-full" title={display.name}>
|
||||
{display.name}
|
||||
<div class="text-xl font-bold truncate w-full" title={$display?.name ?? ''}>
|
||||
{$display?.name ?? ''}
|
||||
</div>
|
||||
|
||||
<OnlineState
|
||||
selected={is_selected(display.id, $selected_display_ids)}
|
||||
status={display.status}
|
||||
selected={is_selected(display_id_object.id, $selected_display_ids)}
|
||||
status={$display?.status ?? null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right Controls -->
|
||||
<div class="flex flex-row h-full items-center gap-2 pr-3">
|
||||
<DNDGrip
|
||||
bg={get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
|
||||
bg={get_selectable_color_classes(is_selected(display_id_object.id, $selected_display_ids), {
|
||||
bg: true
|
||||
})}
|
||||
/>
|
||||
@@ -125,16 +127,16 @@
|
||||
>
|
||||
<Button
|
||||
bg="bg-transparent"
|
||||
hover_bg={get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
|
||||
hover_bg={get_selectable_color_classes(is_selected(display_id_object.id, $selected_display_ids), {
|
||||
bg: true
|
||||
})}
|
||||
active_bg={get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
|
||||
active_bg={get_selectable_color_classes(is_selected(display_id_object.id, $selected_display_ids), {
|
||||
bg: true
|
||||
})}
|
||||
click_function={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
menu_options={get_display_menu_options(display.id)}
|
||||
menu_options={get_display_menu_options(display_id_object.id)}
|
||||
>
|
||||
<Menu />
|
||||
</Button>
|
||||
|
||||
@@ -293,7 +293,6 @@ async function start_task_processing() {
|
||||
|
||||
async function start_task_loop() {
|
||||
while (tasks.length > 0) {
|
||||
console.log('NOCH', tasks.length, 'ZU TUN');
|
||||
const current_task = tasks[0];
|
||||
if (current_task.data.type === 'upload') {
|
||||
await upload(current_task);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { get } from 'svelte/store';
|
||||
import type { Display, DisplayGroup, DisplayStatus } from '../types';
|
||||
import { get, writable, type Writable } from 'svelte/store';
|
||||
import type {
|
||||
Display,
|
||||
DisplayGroup,
|
||||
DisplayIdGroup,
|
||||
DisplayIdObject,
|
||||
DisplayStatus
|
||||
} from '../types';
|
||||
import { is_selected, select, selected_display_ids } from './select';
|
||||
import { get_uuid, image_content_hash } from '../utils';
|
||||
import { get_screenshot } from '../api_handler';
|
||||
@@ -7,6 +13,8 @@ import { delete_and_deselect_unique_files_from_display } from './files';
|
||||
import { db } from '../database';
|
||||
import { dev } from '$app/environment';
|
||||
|
||||
export const local_displays: Writable<DisplayIdGroup[]> = writable<DisplayIdGroup[]>([]);
|
||||
|
||||
export async function is_display_name_taken(name: string): Promise<boolean> {
|
||||
const exists = await db.displays.where('name').equals(name).first();
|
||||
return !!exists;
|
||||
@@ -169,26 +177,62 @@ export async function get_display_groups(): Promise<DisplayGroup[]> {
|
||||
return await db.display_groups.orderBy('position').toArray();
|
||||
}
|
||||
|
||||
export async function get_display_ids_in_group(display_group_id: string): Promise<Display[]> {
|
||||
const displays: Display[] = await db.displays
|
||||
.where('group_id')
|
||||
.equals(display_group_id)
|
||||
.sortBy('position');
|
||||
return displays;
|
||||
export async function update_local_displays() {
|
||||
const display_groups = await db.display_groups.orderBy('position').toArray();
|
||||
const displays = await db.displays.orderBy('position').toArray();
|
||||
const out: DisplayIdGroup[] = [];
|
||||
for (const group of display_groups) {
|
||||
out.push({
|
||||
id: group.id,
|
||||
displays: (await displays).filter((d) => d.group_id === group.id).map((d) => ({ id: d.id }))
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export async function set_new_display_order(new_ordered_items: Display[]) {
|
||||
for (let i = 0; i < new_ordered_items.length; i++) {
|
||||
new_ordered_items[i].position = i;
|
||||
await db.displays.put(new_ordered_items[i]);
|
||||
export async function update_db_displays() {
|
||||
// local_displays.update((groups) => groups.filter((g) => g.displays.length !== 0));
|
||||
const filtered_local_display_groups = get(local_displays)
|
||||
// .filter(
|
||||
// (group) => group.displays.length !== 0
|
||||
// );
|
||||
// local_displays.set(filtered_local_display_groups);
|
||||
const db_display_group_ids = (await db.display_groups.toArray()).map((group) => group.id);
|
||||
console.log('JOJ', filtered_local_display_groups, db_display_group_ids);
|
||||
const local_display_group_ids = filtered_local_display_groups.map((group) => group.id);
|
||||
|
||||
const display_group_ids_to_delete = db_display_group_ids.filter(
|
||||
(group) => !local_display_group_ids.includes(group)
|
||||
);
|
||||
await db.display_groups.where('id').anyOf(display_group_ids_to_delete).delete();
|
||||
|
||||
for (let i = 0; i < filtered_local_display_groups.length; i++) {
|
||||
const group = filtered_local_display_groups[i];
|
||||
if (db_display_group_ids.includes(group.id)) {
|
||||
await db.display_groups.update(group.id, { position: i });
|
||||
} else {
|
||||
await db.display_groups.put({
|
||||
id: group.id,
|
||||
position: i
|
||||
});
|
||||
}
|
||||
|
||||
for (let j = 0; j < group.displays.length; j++) {
|
||||
const display_id = group.displays[j].id;
|
||||
await db.displays.update(display_id, { position: j, group_id: group.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function set_new_display_group_order(new_ordered_items: DisplayGroup[]) {
|
||||
for (let i = 0; i < new_ordered_items.length; i++) {
|
||||
new_ordered_items[i].position = i;
|
||||
await db.display_groups.put(new_ordered_items[i]);
|
||||
}
|
||||
export function set_new_display_order(display_id_group_id: string, new_data: DisplayIdObject[]) {
|
||||
local_displays.update((local_displays: DisplayIdGroup[]) => {
|
||||
for (const display_id_group of local_displays) {
|
||||
if (display_id_group.id === display_id_group_id) {
|
||||
display_id_group.displays = new_data;
|
||||
}
|
||||
}
|
||||
return local_displays;
|
||||
});
|
||||
}
|
||||
|
||||
if (dev) {
|
||||
|
||||
@@ -37,20 +37,20 @@ export type FileTransferTask = {
|
||||
bytes_total: number; // if type === 'sync' -> bytes_total = file_size * 2 (1x download + 1x upload)
|
||||
};
|
||||
|
||||
export type FileTransferTaskData = {
|
||||
type: 'upload',
|
||||
file: File,
|
||||
} | {
|
||||
type: "sync",
|
||||
destination_displays: ShortDisplay[],
|
||||
}
|
||||
|
||||
export type FileTransferTaskData =
|
||||
| {
|
||||
type: 'upload';
|
||||
file: File;
|
||||
}
|
||||
| {
|
||||
type: 'sync';
|
||||
destination_displays: ShortDisplay[];
|
||||
};
|
||||
|
||||
export type ShortDisplay = {
|
||||
id: string;
|
||||
ip: string;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export type FileOnDisplay = {
|
||||
display_id: string;
|
||||
@@ -64,7 +64,7 @@ export type FileLoadingData = {
|
||||
percentage: number;
|
||||
bytes_per_second: number;
|
||||
seconds_until_finish: number;
|
||||
}
|
||||
};
|
||||
|
||||
export type Inode = {
|
||||
path: string;
|
||||
@@ -101,6 +101,15 @@ export type DisplayGroup = {
|
||||
position: number;
|
||||
};
|
||||
|
||||
export type DisplayIdGroup = {
|
||||
id: string;
|
||||
displays: DisplayIdObject[];
|
||||
};
|
||||
|
||||
export type DisplayIdObject = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type PreviewObject = {
|
||||
currently_updating: boolean;
|
||||
url: string | null;
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import {
|
||||
all_displays_of_group_selected,
|
||||
get_display_by_id,
|
||||
get_display_groups,
|
||||
set_select_for_group,
|
||||
set_new_display_group_order
|
||||
local_displays,
|
||||
update_local_displays,
|
||||
update_db_displays,
|
||||
get_display_groups
|
||||
} from '$lib/ts/stores/displays';
|
||||
import {
|
||||
change_height,
|
||||
@@ -19,15 +21,16 @@
|
||||
import { type Display, type DisplayGroup, type MenuOption } from '$lib/ts/types';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import OnlineState from '../lib/components/OnlineState.svelte';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { Menu, Pencil, PinOff, Trash2, VideoOff, ZoomIn, ZoomOut } from 'lucide-svelte';
|
||||
import { selected_display_ids } from '$lib/ts/stores/select';
|
||||
import { dragHandleZone } from 'svelte-dnd-action';
|
||||
import { flip } from 'svelte/animate';
|
||||
import DisplayGroupObject from '../lib/components/DisplayGroupObject.svelte';
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
||||
import HighlightedText from '$lib/components/HighlightedText.svelte';
|
||||
import { liveQuery, type Observable } from 'dexie';
|
||||
import { onMount } from 'svelte';
|
||||
import { flip } from 'svelte/animate';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
|
||||
let {
|
||||
handle_display_deletion,
|
||||
@@ -43,7 +46,16 @@
|
||||
const pdi = $pinned_display_id;
|
||||
pinned_display = liveQuery(() => get_display_by_id(pdi || ''));
|
||||
});
|
||||
|
||||
let display_groups = liveQuery(() => get_display_groups());
|
||||
let display_id_groups = liveQuery(() => update_local_displays());
|
||||
$effect(() => {
|
||||
const current_displays = $display_id_groups;
|
||||
if (!$is_group_drag && !$is_display_drag && current_displays) {
|
||||
$local_displays = current_displays;
|
||||
}
|
||||
});
|
||||
|
||||
let all_groups_selected: Observable<boolean> | undefined = $state();
|
||||
$effect(() => {
|
||||
const d = $display_groups;
|
||||
@@ -234,51 +246,52 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-h-0 overflow-y-auto" bind:this={displays_scroll_box}>
|
||||
<div
|
||||
class="min-h-full p-2 flex flex-col gap-4"
|
||||
use:dragHandleZone={{
|
||||
items: $display_groups || [],
|
||||
type: 'group',
|
||||
flipDurationMs: dnd_flip_duration_ms,
|
||||
dropFromOthersDisabled: true,
|
||||
dropTargetStyle: { outline: 'none' }
|
||||
}}
|
||||
onconsider={async (e: CustomEvent) => {
|
||||
$is_group_drag = true;
|
||||
await set_new_display_group_order(e.detail.items);
|
||||
}}
|
||||
onfinalize={async (e: CustomEvent) => {
|
||||
await set_new_display_group_order(e.detail.items);
|
||||
$is_group_drag = false;
|
||||
}}
|
||||
>
|
||||
{#if ($display_groups || []).length === 0}
|
||||
<div class="text-stone-500 px-10 py-6 leading-relaxed text-center">
|
||||
Es wurden noch keine Bildschirme hinzugefügt. Klicke oben rechts auf
|
||||
<HighlightedText fg="text-stone-450" className="p-1!">
|
||||
<Menu class="inline pb-1" />
|
||||
</HighlightedText>
|
||||
und
|
||||
<HighlightedText fg="text-stone-450">Neuen Bildschirm hinzufügen</HighlightedText>.
|
||||
</div>
|
||||
{:else}
|
||||
{#each $display_groups || [] as display_group (display_group.id)}
|
||||
<div class="min-h-0 overflow-y-auto overflow-x-hidden" bind:this={displays_scroll_box}>
|
||||
{#if $local_displays.length === 0}
|
||||
<div class="text-stone-500 px-10 py-6 leading-relaxed text-center">
|
||||
Es wurden noch keine Bildschirme hinzugefügt. Klicke oben rechts auf
|
||||
<HighlightedText fg="text-stone-450" className="p-1!">
|
||||
<Menu class="inline pb-1" />
|
||||
</HighlightedText>
|
||||
und
|
||||
<HighlightedText fg="text-stone-450">Neuen Bildschirm hinzufügen</HighlightedText>.
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="min-h-full p-2 flex flex-col gap-4"
|
||||
use:dragHandleZone={{
|
||||
items: $local_displays,
|
||||
type: 'group',
|
||||
flipDurationMs: dnd_flip_duration_ms,
|
||||
dropFromOthersDisabled: true,
|
||||
dropTargetStyle: { outline: 'none' }
|
||||
}}
|
||||
onconsider={(e: CustomEvent) => {
|
||||
$is_group_drag = true;
|
||||
$local_displays = e.detail.items;
|
||||
}}
|
||||
onfinalize={async (e: CustomEvent) => {
|
||||
$local_displays = e.detail.items;
|
||||
await update_db_displays();
|
||||
$is_group_drag = false;
|
||||
}}
|
||||
>
|
||||
{#each $local_displays as display_id_group (display_id_group.id)}
|
||||
<!-- Each Group -->
|
||||
<section
|
||||
out:scale={{ duration: dnd_flip_duration_ms, easing: cubicOut }}
|
||||
animate:flip={{ duration: dnd_flip_duration_ms, easing: cubicOut }}
|
||||
class="outline-none"
|
||||
>
|
||||
<!-- out:scale={{ duration: dnd_flip_duration_ms, easing: cubicOut }}
|
||||
animate:flip={{ duration: dnd_flip_duration_ms, easing: cubicOut }} -->
|
||||
<DisplayGroupObject
|
||||
display_group_id={display_group.id}
|
||||
{display_id_group}
|
||||
{get_display_menu_options}
|
||||
{close_pinned_display}
|
||||
/>
|
||||
</section>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
|
||||
Reference in New Issue
Block a user