1
This commit is contained in:
BIN
ScreenShot_2026-01-30_100758_517.png
Normal file
BIN
ScreenShot_2026-01-30_100758_517.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 448 KiB |
BIN
ScreenShot_2026-01-30_104551_602.png
Normal file
BIN
ScreenShot_2026-01-30_104551_602.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 424 KiB |
BIN
ScreenShot_2026-01-30_104642_622.png
Normal file
BIN
ScreenShot_2026-01-30_104642_622.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
ScreenShot_2026-01-30_131448_795.png
Normal file
BIN
ScreenShot_2026-01-30_131448_795.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 344 KiB |
BIN
ScreenShot_2026-01-30_175427_657.png
Normal file
BIN
ScreenShot_2026-01-30_175427_657.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@ -1,4 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { Folder } from '@/store/useFoldersStore';
|
||||
|
||||
@ -27,27 +27,35 @@ const initialText = (child: FolderChild) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="folder-card" @click.stop="emit('open')">
|
||||
<div class="folder-badge">{{ props.folder.name || '组' }}</div>
|
||||
<div class="folder-preview">
|
||||
<div
|
||||
v-for="child in previewChildren"
|
||||
:key="`${child.type}-${child.id}`"
|
||||
class="folder-chip"
|
||||
:data-type="child.type"
|
||||
:style="child.type === 'icon' && child.icon?.bgColor ? { backgroundColor: child.icon.bgColor } : {}"
|
||||
>
|
||||
<img v-if="child.type === 'icon' && child.icon?.img" :src="child.icon.img" :alt="child.icon?.name" />
|
||||
<span v-else>{{ initialText(child) }}</span>
|
||||
<div class="folder-card-wrapper">
|
||||
<div class="folder-card" @click.stop="emit('open')">
|
||||
<div class="folder-preview">
|
||||
<div
|
||||
v-for="child in previewChildren"
|
||||
:key="`${child.type}-${child.id}`"
|
||||
class="folder-chip"
|
||||
:data-type="child.type"
|
||||
:style="child.type === 'icon' && child.icon?.bgColor ? { backgroundColor: child.icon.bgColor } : {}"
|
||||
>
|
||||
<img v-if="child.type === 'icon' && child.icon?.img" :src="child.icon.img" :alt="child.icon?.name" />
|
||||
<span v-else>{{ initialText(child) }}</span>
|
||||
</div>
|
||||
<div v-if="!previewChildren.length" class="folder-empty">空</div>
|
||||
</div>
|
||||
<div v-if="!previewChildren.length" class="folder-empty">空</div>
|
||||
</div>
|
||||
<div class="folder-label">{{ props.folder.name || '文件夹' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/styles/tokens.scss';
|
||||
|
||||
.folder-card-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.folder-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -66,19 +74,6 @@ const initialText = (child: FolderChild) => {
|
||||
}
|
||||
}
|
||||
|
||||
.folder-badge {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
background: rgba(0, 122, 255, 0.9);
|
||||
color: #fff;
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.folder-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -123,4 +118,21 @@ const initialText = (child: FolderChild) => {
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
.folder-label {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-top: var(--icon-label-margin-top);
|
||||
transform: translateX(-50%);
|
||||
font-size: var(--icon-label-font-size);
|
||||
color: $color-text-primary;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
visibility: var(--icon-label-visibility, visible);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import type { Folder } from '@/store/useFoldersStore';
|
||||
|
||||
type FolderChild =
|
||||
@ -19,23 +19,57 @@ const emit = defineEmits<{
|
||||
(e: 'open-item', payload: FolderChild): void;
|
||||
}>();
|
||||
|
||||
const localName = ref('组');
|
||||
const localName = ref('文件夹');
|
||||
const editingName = ref(false);
|
||||
const nameInputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
watch(
|
||||
() => props.folder?.name,
|
||||
name => {
|
||||
localName.value = name ?? '组';
|
||||
localName.value = name ?? '文件夹';
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.folder?.id,
|
||||
() => {
|
||||
editingName.value = false;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
() => {
|
||||
editingName.value = false;
|
||||
}
|
||||
);
|
||||
|
||||
const handleRename = () => {
|
||||
if (!props.folder) return;
|
||||
const name = localName.value.trim() || '组';
|
||||
const name = localName.value.trim() || '文件夹';
|
||||
emit('rename', { id: props.folder.id, name });
|
||||
};
|
||||
|
||||
const badgeLabel = computed(() => props.folder?.name ?? '组');
|
||||
const focusRenameInput = async () => {
|
||||
await nextTick();
|
||||
const input = nameInputRef.value;
|
||||
if (!input) return;
|
||||
input.focus();
|
||||
const len = input.value.length;
|
||||
input.setSelectionRange(len, len);
|
||||
};
|
||||
|
||||
const startRename = () => {
|
||||
editingName.value = true;
|
||||
};
|
||||
|
||||
const finishRename = () => {
|
||||
editingName.value = false;
|
||||
handleRename();
|
||||
};
|
||||
|
||||
const badgeLabel = computed(() => localName.value || '文件夹');
|
||||
|
||||
const initialText = (child: FolderChild) => {
|
||||
if (child.type === 'icon') {
|
||||
@ -44,56 +78,66 @@ const initialText = (child: FolderChild) => {
|
||||
}
|
||||
return 'W';
|
||||
};
|
||||
|
||||
const labelFor = (child: FolderChild) =>
|
||||
child.type === 'icon' ? child.icon?.name ?? '未命名' : child.widget?.component ?? 'Widget';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<div v-if="open && folder" class="folder-dialog-overlay" @click.self="emit('close')">
|
||||
<div class="folder-dialog">
|
||||
<header class="folder-dialog__header">
|
||||
<input
|
||||
v-model="localName"
|
||||
class="folder-name-input"
|
||||
@keyup.enter="handleRename"
|
||||
@blur="handleRename"
|
||||
/>
|
||||
<span class="folder-badge">{{ badgeLabel }}</span>
|
||||
<button class="close-btn" type="button" @click="emit('close')">×</button>
|
||||
</header>
|
||||
<div class="folder-dialog__body">
|
||||
<div
|
||||
v-for="child in children"
|
||||
:key="`${child.type}-${child.id}`"
|
||||
class="folder-row"
|
||||
>
|
||||
<div
|
||||
class="row-thumb"
|
||||
:data-type="child.type"
|
||||
:style="child.type === 'icon' && child.icon?.bgColor ? { backgroundColor: child.icon.bgColor } : {}"
|
||||
>
|
||||
<img v-if="child.type === 'icon' && child.icon?.img" :src="child.icon.img" :alt="child.icon?.name" />
|
||||
<span v-else>{{ initialText(child) }}</span>
|
||||
</div>
|
||||
<div class="row-title">
|
||||
<div class="row-name">
|
||||
{{ child.type === 'icon' ? child.icon?.name : child.widget?.component || 'Widget' }}
|
||||
</div>
|
||||
<div class="row-sub" v-if="child.type === 'icon' && child.icon?.url">{{ child.icon.url }}</div>
|
||||
</div>
|
||||
<div class="row-actions">
|
||||
<button type="button" class="link-btn" @click="emit('open-item', child)">打开</button>
|
||||
<button
|
||||
type="button"
|
||||
class="link-btn danger"
|
||||
@click="emit('remove', { folderId: folder.id, child })"
|
||||
>
|
||||
移出
|
||||
<div
|
||||
class="folder-dialog-overlay"
|
||||
:class="{ 'is-open': open && folder }"
|
||||
@click.self="emit('close')"
|
||||
>
|
||||
<transition name="folder-dialog">
|
||||
<div v-if="open && folder" class="folder-dialog-wrap">
|
||||
<div class="folder-title">
|
||||
<transition name="folder-title" mode="out-in" @after-enter="focusRenameInput">
|
||||
<button v-if="!editingName" key="view" type="button" class="folder-title-pill" @click="startRename">
|
||||
{{ badgeLabel }}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
v-else
|
||||
key="edit"
|
||||
ref="nameInputRef"
|
||||
v-model="localName"
|
||||
class="folder-title-input"
|
||||
@keyup.enter="finishRename"
|
||||
@blur="finishRename"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
<div class="folder-dialog">
|
||||
<div v-if="children.length" class="folder-grid">
|
||||
<div
|
||||
v-for="child in children"
|
||||
:key="`${child.type}-${child.id}`"
|
||||
class="folder-item"
|
||||
@click="emit('open-item', child)"
|
||||
>
|
||||
<div
|
||||
class="folder-item-icon"
|
||||
:data-type="child.type"
|
||||
:style="child.type === 'icon' && child.icon?.bgColor ? { backgroundColor: child.icon.bgColor } : {}"
|
||||
>
|
||||
<img v-if="child.type === 'icon' && child.icon?.img" :src="child.icon.img" :alt="child.icon?.name" />
|
||||
<span v-else>{{ initialText(child) }}</span>
|
||||
</div>
|
||||
<div class="folder-item-label">{{ labelFor(child) }}</div>
|
||||
<button
|
||||
type="button"
|
||||
class="folder-item-remove"
|
||||
@click.stop="emit('remove', { folderId: folder.id, child })"
|
||||
>
|
||||
移出
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="folder-empty">空</div>
|
||||
</div>
|
||||
<div v-if="!children.length" class="folder-empty">还没有内容</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
@ -104,96 +148,119 @@ const initialText = (child: FolderChild) => {
|
||||
.folder-dialog-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
backdrop-filter: blur(6px);
|
||||
background: rgba(10, 12, 20, 0.32);
|
||||
backdrop-filter: blur(18px) saturate(140%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(140%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: $z-index-menu;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.folder-dialog-overlay.is-open {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.folder-dialog-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.folder-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.folder-title-pill {
|
||||
min-width: 180px;
|
||||
height: 36px;
|
||||
padding: 0 16px;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.folder-title-input {
|
||||
min-width: 180px;
|
||||
height: 36px;
|
||||
padding: 0 16px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
background: rgba(255, 255, 255, 0.38);
|
||||
backdrop-filter: blur(22px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(22px) saturate(160%);
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.folder-dialog {
|
||||
width: min(520px, 90vw);
|
||||
max-height: 80vh;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 80px rgba(0, 0, 0, 0.18);
|
||||
overflow: hidden;
|
||||
width: min(720px, 90vw);
|
||||
min-height: 200px;
|
||||
padding: 22px 26px 26px;
|
||||
border-radius: 22px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
background: rgba(255, 255, 255, 0.38);
|
||||
backdrop-filter: blur(22px) saturate(160%);
|
||||
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.folder-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.folder-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.folder-dialog__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
gap: 6px;
|
||||
padding: 6px 4px 10px;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
transition: transform $motion-duration-sm $motion-easing-standard, background $motion-duration-sm $motion-easing-standard;
|
||||
|
||||
.folder-name-input {
|
||||
flex: 1;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
transition: border-color $motion-duration-sm $motion-easing-standard, box-shadow $motion-duration-sm $motion-easing-standard;
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: #007aff;
|
||||
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.15);
|
||||
&:hover .folder-item-remove {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.folder-badge {
|
||||
background: rgba(0, 122, 255, 0.12);
|
||||
color: #0a63d1;
|
||||
border-radius: 999px;
|
||||
padding: 6px 12px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.folder-dialog__body {
|
||||
padding: 12px 18px 18px;
|
||||
overflow: auto;
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.folder-row {
|
||||
display: grid;
|
||||
grid-template-columns: 54px 1fr auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 12px;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.row-thumb {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 12px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
.folder-item-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
color: #222;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
@ -207,62 +274,56 @@ const initialText = (child: FolderChild) => {
|
||||
}
|
||||
}
|
||||
|
||||
.row-title {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.row-name {
|
||||
font-weight: 600;
|
||||
color: #1f1f1f;
|
||||
font-size: 15px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.row-sub {
|
||||
.folder-item-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 4px;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
text-align: center;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.row-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.link-btn {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
.folder-item-remove {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 6px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 6px 10px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
color: #c0392b;
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #222;
|
||||
transition: background $motion-duration-sm $motion-easing-standard, transform $motion-duration-sm $motion-easing-standard;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 122, 255, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: #c0392b;
|
||||
background: rgba(192, 57, 43, 0.08);
|
||||
|
||||
&:hover {
|
||||
background: rgba(192, 57, 43, 0.15);
|
||||
}
|
||||
}
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
transition: opacity $motion-duration-sm $motion-easing-standard, transform $motion-duration-sm $motion-easing-standard;
|
||||
}
|
||||
|
||||
.folder-empty {
|
||||
text-align: center;
|
||||
padding: 26px 12px;
|
||||
color: #666;
|
||||
border: 1px dashed rgba(0, 0, 0, 0.08);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 160px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.folder-dialog-enter-active {
|
||||
transition: transform 220ms $motion-easing-standard;
|
||||
}
|
||||
|
||||
.folder-dialog-enter-from {
|
||||
transform: translateY(14px) scale(0.98);
|
||||
}
|
||||
|
||||
.folder-title-enter-active,
|
||||
.folder-title-leave-active {
|
||||
transition: opacity 90ms $motion-easing-standard;
|
||||
}
|
||||
|
||||
.folder-title-enter-from,
|
||||
.folder-title-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -34,6 +34,18 @@
|
||||
<li @click="handleEditWidget"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"></path></svg>编辑组件</li>
|
||||
<li class="menu-danger" @click="deleteItem"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><path d="M3 6h18"></path><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>删除</li>
|
||||
</ul>
|
||||
<ul v-else-if="uiStore.contextMenu.itemType === 'folder'">
|
||||
<li class="layout-section">
|
||||
<div class="layout-title">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>
|
||||
<span>布局</span>
|
||||
</div>
|
||||
<div class="widget-size-options">
|
||||
<span v-for="size in folderSizes" :key="size" @click="changeFolderSize(size)" class="size-option">{{ size }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu-danger" @click="deleteItem"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="menu-icon"><path d="M3 6h18"></path><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>删除文件夹</li>
|
||||
</ul>
|
||||
<ul v-else-if="uiStore.contextMenu.itemType === 'desktop'" class="desktop-menu">
|
||||
<li class="desktop-item" @click="handleDesktopAction('add-icon')">
|
||||
<span class="desktop-label">添加图标</span>
|
||||
@ -74,6 +86,7 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { useUIStore } from '@/store/useUIStore';
|
||||
import { useLayoutStore } from '@/store/useLayoutStore';
|
||||
import { useWidgetsStore } from '@/store/useWidgetsStore';
|
||||
import { useFoldersStore } from '@/store/useFoldersStore';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'add-icon'): void;
|
||||
@ -85,6 +98,7 @@ const emit = defineEmits<{
|
||||
const uiStore = useUIStore();
|
||||
const layoutStore = useLayoutStore();
|
||||
const widgetsStore = useWidgetsStore();
|
||||
const foldersStore = useFoldersStore();
|
||||
|
||||
type ItemSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
|
||||
|
||||
@ -92,6 +106,7 @@ type DesktopAction = 'add-icon' | 'wallpaper' | 'search' | 'backup' | 'settings'
|
||||
|
||||
const widgetSizes: ItemSize[] = ['1x1', '1x2', '2x1', '2x2', '2x4'];
|
||||
const iconSizes: ItemSize[] = ['1x1', '1x2', '2x1', '2x2', '2x4'];
|
||||
const folderSizes: ItemSize[] = ['1x1', '1x2', '2x1', '2x2', '2x4'];
|
||||
|
||||
const menuRef = ref<HTMLElement | null>(null);
|
||||
const menuPosition = ref({ x: 0, y: 0 });
|
||||
@ -147,9 +162,11 @@ const close = () => {
|
||||
const deleteItem = () => {
|
||||
if (uiStore.contextMenu.itemId) {
|
||||
if (uiStore.contextMenu.itemType === 'icon') {
|
||||
layoutStore.deleteIcon(uiStore.contextMenu.itemId);
|
||||
uiStore.requestDelete(uiStore.contextMenu.itemId, 'icon');
|
||||
} else if (uiStore.contextMenu.itemType === 'widget') {
|
||||
widgetsStore.deleteWidget(uiStore.contextMenu.itemId);
|
||||
uiStore.requestDelete(uiStore.contextMenu.itemId, 'widget');
|
||||
} else if (uiStore.contextMenu.itemType === 'folder') {
|
||||
uiStore.requestDelete(uiStore.contextMenu.itemId, 'folder');
|
||||
}
|
||||
}
|
||||
close();
|
||||
@ -185,6 +202,13 @@ const changeIconSize = (newSize: ItemSize) => {
|
||||
close();
|
||||
};
|
||||
|
||||
const changeFolderSize = (newSize: ItemSize) => {
|
||||
if (uiStore.contextMenu.itemId) {
|
||||
foldersStore.updateFolderSize(uiStore.contextMenu.itemId, newSize);
|
||||
}
|
||||
close();
|
||||
};
|
||||
|
||||
const handleDesktopAction = (action: DesktopAction) => {
|
||||
if (action === 'add-icon') {
|
||||
emit('add-icon');
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||
|
||||
type FolderItemType = 'icon' | 'widget';
|
||||
type FolderSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
|
||||
|
||||
export interface FolderItem {
|
||||
id: string;
|
||||
@ -12,6 +13,7 @@ export interface Folder {
|
||||
name: string;
|
||||
items: FolderItem[];
|
||||
groupId?: string;
|
||||
size?: FolderSize;
|
||||
}
|
||||
|
||||
interface FolderState {
|
||||
@ -21,10 +23,17 @@ interface FolderState {
|
||||
const DEFAULT_GROUP_ID = 'home';
|
||||
const STORAGE_KEY = 'itab_folders';
|
||||
|
||||
const normalizeFolderName = (name?: string) => {
|
||||
const trimmed = (name ?? '').trim();
|
||||
if (!trimmed || trimmed === '组') return '文件夹';
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
const normalizeFolder = (folder: Folder): Folder => ({
|
||||
id: folder.id,
|
||||
name: folder.name || '组',
|
||||
name: normalizeFolderName(folder.name),
|
||||
groupId: folder.groupId ?? DEFAULT_GROUP_ID,
|
||||
size: folder.size ?? '1x1',
|
||||
items: Array.isArray(folder.items)
|
||||
? folder.items
|
||||
.filter(item => item && typeof item.id === 'string' && (item.type === 'icon' || item.type === 'widget'))
|
||||
@ -52,13 +61,14 @@ export const useFoldersStore = defineStore('folders', {
|
||||
persist() {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.folders));
|
||||
},
|
||||
addFolder(payload: { name?: string; items?: FolderItem[]; groupId?: string }) {
|
||||
addFolder(payload: { name?: string; items?: FolderItem[]; groupId?: string; size?: FolderSize }) {
|
||||
const nextId = `folder-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
||||
const folder: Folder = normalizeFolder({
|
||||
id: nextId,
|
||||
name: payload.name || '组',
|
||||
name: normalizeFolderName(payload.name),
|
||||
items: payload.items ?? [],
|
||||
groupId: payload.groupId ?? DEFAULT_GROUP_ID,
|
||||
size: payload.size ?? '1x1',
|
||||
});
|
||||
this.folders.push(folder);
|
||||
this.persist();
|
||||
@ -67,7 +77,13 @@ export const useFoldersStore = defineStore('folders', {
|
||||
renameFolder(folderId: string, name: string) {
|
||||
const folder = this.folders.find(item => item.id === folderId);
|
||||
if (!folder) return;
|
||||
folder.name = name || '组';
|
||||
folder.name = normalizeFolderName(name);
|
||||
this.persist();
|
||||
},
|
||||
updateFolderSize(folderId: string, size: FolderSize) {
|
||||
const folder = this.folders.find(item => item.id === folderId);
|
||||
if (!folder) return;
|
||||
folder.size = size;
|
||||
this.persist();
|
||||
},
|
||||
addItem(folderId: string, item: FolderItem) {
|
||||
|
||||
@ -5,11 +5,12 @@ interface ContextMenuState {
|
||||
x: number;
|
||||
y: number;
|
||||
itemId: string | null;
|
||||
itemType?: 'icon' | 'widget' | 'desktop';
|
||||
itemType?: 'icon' | 'widget' | 'folder' | 'desktop';
|
||||
}
|
||||
|
||||
interface UIState {
|
||||
contextMenu: ContextMenuState;
|
||||
pendingDelete: { id: string; type: 'icon' | 'widget' | 'folder' } | null;
|
||||
}
|
||||
|
||||
export const useUIStore = defineStore('ui', {
|
||||
@ -20,14 +21,15 @@ export const useUIStore = defineStore('ui', {
|
||||
y: 0,
|
||||
itemId: null,
|
||||
itemType: 'icon',
|
||||
}
|
||||
},
|
||||
pendingDelete: null,
|
||||
}),
|
||||
actions: {
|
||||
openContextMenu(
|
||||
x: number,
|
||||
y: number,
|
||||
itemId: string | null,
|
||||
itemType: 'icon' | 'widget' | 'desktop'
|
||||
itemType: 'icon' | 'widget' | 'folder' | 'desktop'
|
||||
) {
|
||||
this.contextMenu = {
|
||||
isOpen: true,
|
||||
@ -41,5 +43,11 @@ export const useUIStore = defineStore('ui', {
|
||||
this.contextMenu.isOpen = false;
|
||||
this.contextMenu.itemId = null;
|
||||
},
|
||||
requestDelete(id: string, type: 'icon' | 'widget' | 'folder') {
|
||||
this.pendingDelete = { id, type };
|
||||
},
|
||||
clearPendingDelete() {
|
||||
this.pendingDelete = null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
218
app/src/vendor/muuri-src/Animator/Animator.js
vendored
Normal file
218
app/src/vendor/muuri-src/Animator/Animator.js
vendored
Normal file
@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import getCurrentStyles from '../utils/getCurrentStyles';
|
||||
import getUnprefixedPropName from '../utils/getUnprefixedPropName';
|
||||
import isFunction from '../utils/isFunction';
|
||||
import isNative from '../utils/isNative';
|
||||
import setStyles from '../utils/setStyles';
|
||||
|
||||
var HAS_WEB_ANIMATIONS = !!(Element && isFunction(Element.prototype.animate));
|
||||
var HAS_NATIVE_WEB_ANIMATIONS = !!(Element && isNative(Element.prototype.animate));
|
||||
|
||||
/**
|
||||
* Item animation handler powered by Web Animations API.
|
||||
*
|
||||
* @class
|
||||
* @param {HTMLElement} element
|
||||
*/
|
||||
function Animator(element) {
|
||||
this._element = element;
|
||||
this._animation = null;
|
||||
this._duration = 0;
|
||||
this._easing = '';
|
||||
this._callback = null;
|
||||
this._props = [];
|
||||
this._values = [];
|
||||
this._isDestroyed = false;
|
||||
this._onFinish = this._onFinish.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public prototype methods
|
||||
* ************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Start instance's animation. Automatically stops current animation if it is
|
||||
* running.
|
||||
*
|
||||
* @public
|
||||
* @param {Object} propsFrom
|
||||
* @param {Object} propsTo
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.duration=300]
|
||||
* @param {String} [options.easing='ease']
|
||||
* @param {Function} [options.onFinish]
|
||||
*/
|
||||
Animator.prototype.start = function (propsFrom, propsTo, options) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var element = this._element;
|
||||
var opts = options || {};
|
||||
|
||||
// If we don't have web animations available let's not animate.
|
||||
if (!HAS_WEB_ANIMATIONS) {
|
||||
setStyles(element, propsTo);
|
||||
this._callback = isFunction(opts.onFinish) ? opts.onFinish : null;
|
||||
this._onFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
var animation = this._animation;
|
||||
var currentProps = this._props;
|
||||
var currentValues = this._values;
|
||||
var duration = opts.duration || 300;
|
||||
var easing = opts.easing || 'ease';
|
||||
var cancelAnimation = false;
|
||||
var propName, propCount, propIndex;
|
||||
|
||||
// If we have an existing animation running, let's check if it needs to be
|
||||
// cancelled or if it can continue running.
|
||||
if (animation) {
|
||||
propCount = 0;
|
||||
|
||||
// Cancel animation if duration or easing has changed.
|
||||
if (duration !== this._duration || easing !== this._easing) {
|
||||
cancelAnimation = true;
|
||||
}
|
||||
|
||||
// Check if the requested animation target props and values match with the
|
||||
// current props and values.
|
||||
if (!cancelAnimation) {
|
||||
for (propName in propsTo) {
|
||||
++propCount;
|
||||
propIndex = currentProps.indexOf(propName);
|
||||
if (propIndex === -1 || propsTo[propName] !== currentValues[propIndex]) {
|
||||
cancelAnimation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the target props count matches current props count. This is
|
||||
// needed for the edge case scenario where target props contain the same
|
||||
// styles as current props, but the current props have some additional
|
||||
// props.
|
||||
if (propCount !== currentProps.length) {
|
||||
cancelAnimation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel animation (if required).
|
||||
if (cancelAnimation) animation.cancel();
|
||||
|
||||
// Store animation callback.
|
||||
this._callback = isFunction(opts.onFinish) ? opts.onFinish : null;
|
||||
|
||||
// If we have a running animation that does not need to be cancelled, let's
|
||||
// call it a day here and let it run.
|
||||
if (animation && !cancelAnimation) return;
|
||||
|
||||
// Store target props and values to instance.
|
||||
currentProps.length = currentValues.length = 0;
|
||||
for (propName in propsTo) {
|
||||
currentProps.push(propName);
|
||||
currentValues.push(propsTo[propName]);
|
||||
}
|
||||
|
||||
// Start the animation. We need to provide unprefixed property names to the
|
||||
// Web Animations polyfill if it is being used. If we have native Web
|
||||
// Animations available we need to provide prefixed properties instead.
|
||||
this._duration = duration;
|
||||
this._easing = easing;
|
||||
this._animation = element.animate(
|
||||
[
|
||||
createFrame(propsFrom, HAS_NATIVE_WEB_ANIMATIONS),
|
||||
createFrame(propsTo, HAS_NATIVE_WEB_ANIMATIONS),
|
||||
],
|
||||
{
|
||||
duration: duration,
|
||||
easing: easing,
|
||||
}
|
||||
);
|
||||
this._animation.onfinish = this._onFinish;
|
||||
|
||||
// Set the end styles. This makes sure that the element stays at the end
|
||||
// values after animation is finished.
|
||||
setStyles(element, propsTo);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop instance's current animation if running.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
Animator.prototype.stop = function () {
|
||||
if (this._isDestroyed || !this._animation) return;
|
||||
this._animation.cancel();
|
||||
this._animation = this._callback = null;
|
||||
this._props.length = this._values.length = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Read the current values of the element's animated styles from the DOM.
|
||||
*
|
||||
* @public
|
||||
* @return {Object}
|
||||
*/
|
||||
Animator.prototype.getCurrentStyles = function () {
|
||||
return getCurrentStyles(element, currentProps);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the item is being animated currently.
|
||||
*
|
||||
* @public
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Animator.prototype.isAnimating = function () {
|
||||
return !!this._animation;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the instance and stop current animation if it is running.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
Animator.prototype.destroy = function () {
|
||||
if (this._isDestroyed) return;
|
||||
this.stop();
|
||||
this._element = null;
|
||||
this._isDestroyed = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Private prototype methods
|
||||
* *************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Animation end handler.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Animator.prototype._onFinish = function () {
|
||||
var callback = this._callback;
|
||||
this._animation = this._callback = null;
|
||||
this._props.length = this._values.length = 0;
|
||||
callback && callback();
|
||||
};
|
||||
|
||||
/**
|
||||
* Private helpers
|
||||
* ***************
|
||||
*/
|
||||
|
||||
function createFrame(props, prefix) {
|
||||
var frame = {};
|
||||
for (var prop in props) {
|
||||
frame[prefix ? prop : getUnprefixedPropName(prop)] = props[prop];
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
export default Animator;
|
||||
740
app/src/vendor/muuri-src/AutoScroller/AutoScroller.js
vendored
Normal file
740
app/src/vendor/muuri-src/AutoScroller/AutoScroller.js
vendored
Normal file
@ -0,0 +1,740 @@
|
||||
/**
|
||||
* Muuri AutoScroller
|
||||
* Copyright (c) 2019-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md
|
||||
*/
|
||||
|
||||
import { addAutoScrollTick, cancelAutoScrollTick } from '../ticker';
|
||||
import { LEFT, RIGHT, UP, DOWN, AXIS_X, AXIS_Y, FORWARD, BACKWARD } from './constants';
|
||||
import ScrollRequest from './ScrollRequest';
|
||||
import ScrollAction from './ScrollAction';
|
||||
import Pool from './Pool';
|
||||
import getIntersectionScore from '../utils/getIntersectionScore';
|
||||
import isFunction from '../utils/isFunction';
|
||||
import {
|
||||
getScrollElement,
|
||||
getScrollLeft,
|
||||
getScrollTop,
|
||||
getScrollLeftMax,
|
||||
getScrollTopMax,
|
||||
getContentRect,
|
||||
getItemAutoScrollSettings,
|
||||
prepareItemScrollSync,
|
||||
applyItemScrollSync,
|
||||
computeThreshold,
|
||||
} from './utils';
|
||||
|
||||
var RECT_1 = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
|
||||
var RECT_2 = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
|
||||
export default function AutoScroller() {
|
||||
this._isDestroyed = false;
|
||||
this._isTicking = false;
|
||||
this._tickTime = 0;
|
||||
this._tickDeltaTime = 0;
|
||||
this._items = [];
|
||||
this._actions = [];
|
||||
this._requests = {};
|
||||
this._requests[AXIS_X] = {};
|
||||
this._requests[AXIS_Y] = {};
|
||||
this._requestOverlapCheck = {};
|
||||
this._dragPositions = {};
|
||||
this._dragDirections = {};
|
||||
this._overlapCheckInterval = 150;
|
||||
|
||||
this._requestPool = new Pool(
|
||||
function () {
|
||||
return new ScrollRequest();
|
||||
},
|
||||
function (request) {
|
||||
request.reset();
|
||||
}
|
||||
);
|
||||
|
||||
this._actionPool = new Pool(
|
||||
function () {
|
||||
return new ScrollAction();
|
||||
},
|
||||
function (action) {
|
||||
action.reset();
|
||||
}
|
||||
);
|
||||
|
||||
this._readTick = this._readTick.bind(this);
|
||||
this._writeTick = this._writeTick.bind(this);
|
||||
}
|
||||
|
||||
AutoScroller.AXIS_X = AXIS_X;
|
||||
AutoScroller.AXIS_Y = AXIS_Y;
|
||||
AutoScroller.FORWARD = FORWARD;
|
||||
AutoScroller.BACKWARD = BACKWARD;
|
||||
AutoScroller.LEFT = LEFT;
|
||||
AutoScroller.RIGHT = RIGHT;
|
||||
AutoScroller.UP = UP;
|
||||
AutoScroller.DOWN = DOWN;
|
||||
|
||||
AutoScroller.smoothSpeed = function (maxSpeed, acceleration, deceleration) {
|
||||
return function (item, element, data) {
|
||||
var targetSpeed = 0;
|
||||
if (!data.isEnding) {
|
||||
if (data.threshold > 0) {
|
||||
var factor = data.threshold - Math.max(0, data.distance);
|
||||
targetSpeed = (maxSpeed / data.threshold) * factor;
|
||||
} else {
|
||||
targetSpeed = maxSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
var currentSpeed = data.speed;
|
||||
var nextSpeed = targetSpeed;
|
||||
|
||||
if (currentSpeed === targetSpeed) {
|
||||
return nextSpeed;
|
||||
}
|
||||
|
||||
if (currentSpeed < targetSpeed) {
|
||||
nextSpeed = currentSpeed + acceleration * (data.deltaTime / 1000);
|
||||
return Math.min(targetSpeed, nextSpeed);
|
||||
} else {
|
||||
nextSpeed = currentSpeed - deceleration * (data.deltaTime / 1000);
|
||||
return Math.max(targetSpeed, nextSpeed);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
AutoScroller.pointerHandle = function (pointerSize) {
|
||||
var rect = { left: 0, top: 0, width: 0, height: 0 };
|
||||
var size = pointerSize || 1;
|
||||
return function (item, x, y, w, h, pX, pY) {
|
||||
rect.left = pX - size * 0.5;
|
||||
rect.top = pY - size * 0.5;
|
||||
rect.width = size;
|
||||
rect.height = size;
|
||||
return rect;
|
||||
};
|
||||
};
|
||||
|
||||
AutoScroller.prototype._readTick = function (time) {
|
||||
if (this._isDestroyed) return;
|
||||
if (time && this._tickTime) {
|
||||
this._tickDeltaTime = time - this._tickTime;
|
||||
this._tickTime = time;
|
||||
this._updateRequests();
|
||||
this._updateActions();
|
||||
} else {
|
||||
this._tickTime = time;
|
||||
this._tickDeltaTime = 0;
|
||||
}
|
||||
};
|
||||
|
||||
AutoScroller.prototype._writeTick = function () {
|
||||
if (this._isDestroyed) return;
|
||||
this._applyActions();
|
||||
addAutoScrollTick(this._readTick, this._writeTick);
|
||||
};
|
||||
|
||||
AutoScroller.prototype._startTicking = function () {
|
||||
this._isTicking = true;
|
||||
addAutoScrollTick(this._readTick, this._writeTick);
|
||||
};
|
||||
|
||||
AutoScroller.prototype._stopTicking = function () {
|
||||
this._isTicking = false;
|
||||
this._tickTime = 0;
|
||||
this._tickDeltaTime = 0;
|
||||
cancelAutoScrollTick();
|
||||
};
|
||||
|
||||
AutoScroller.prototype._getItemHandleRect = function (item, handle, rect) {
|
||||
var itemDrag = item._drag;
|
||||
|
||||
if (handle) {
|
||||
var ev = itemDrag._dragMoveEvent || itemDrag._dragStartEvent;
|
||||
var data = handle(
|
||||
item,
|
||||
itemDrag._clientX,
|
||||
itemDrag._clientY,
|
||||
item._width,
|
||||
item._height,
|
||||
ev.clientX,
|
||||
ev.clientY
|
||||
);
|
||||
rect.left = data.left;
|
||||
rect.top = data.top;
|
||||
rect.width = data.width;
|
||||
rect.height = data.height;
|
||||
} else {
|
||||
rect.left = itemDrag._clientX;
|
||||
rect.top = itemDrag._clientY;
|
||||
rect.width = item._width;
|
||||
rect.height = item._height;
|
||||
}
|
||||
|
||||
rect.right = rect.left + rect.width;
|
||||
rect.bottom = rect.top + rect.height;
|
||||
|
||||
return rect;
|
||||
};
|
||||
|
||||
AutoScroller.prototype._requestItemScroll = function (
|
||||
item,
|
||||
axis,
|
||||
element,
|
||||
direction,
|
||||
threshold,
|
||||
distance,
|
||||
maxValue
|
||||
) {
|
||||
var reqMap = this._requests[axis];
|
||||
var request = reqMap[item._id];
|
||||
|
||||
if (request) {
|
||||
if (request.element !== element || request.direction !== direction) {
|
||||
request.reset();
|
||||
}
|
||||
} else {
|
||||
request = this._requestPool.pick();
|
||||
}
|
||||
|
||||
request.item = item;
|
||||
request.element = element;
|
||||
request.direction = direction;
|
||||
request.threshold = threshold;
|
||||
request.distance = distance;
|
||||
request.maxValue = maxValue;
|
||||
reqMap[item._id] = request;
|
||||
};
|
||||
|
||||
AutoScroller.prototype._cancelItemScroll = function (item, axis) {
|
||||
var reqMap = this._requests[axis];
|
||||
var request = reqMap[item._id];
|
||||
if (!request) return;
|
||||
if (request.action) request.action.removeRequest(request);
|
||||
this._requestPool.release(request);
|
||||
delete reqMap[item._id];
|
||||
};
|
||||
|
||||
AutoScroller.prototype._checkItemOverlap = function (item, checkX, checkY) {
|
||||
var settings = getItemAutoScrollSettings(item);
|
||||
var targets = isFunction(settings.targets) ? settings.targets(item) : settings.targets;
|
||||
var threshold = settings.threshold;
|
||||
var safeZone = settings.safeZone;
|
||||
|
||||
if (!targets || !targets.length) {
|
||||
checkX && this._cancelItemScroll(item, AXIS_X);
|
||||
checkY && this._cancelItemScroll(item, AXIS_Y);
|
||||
return;
|
||||
}
|
||||
|
||||
var dragDirections = this._dragDirections[item._id];
|
||||
var dragDirectionX = dragDirections[0];
|
||||
var dragDirectionY = dragDirections[1];
|
||||
|
||||
if (!dragDirectionX && !dragDirectionY) {
|
||||
checkX && this._cancelItemScroll(item, AXIS_X);
|
||||
checkY && this._cancelItemScroll(item, AXIS_Y);
|
||||
return;
|
||||
}
|
||||
|
||||
var itemRect = this._getItemHandleRect(item, settings.handle, RECT_1);
|
||||
var testRect = RECT_2;
|
||||
|
||||
var target = null;
|
||||
var testElement = null;
|
||||
var testAxisX = true;
|
||||
var testAxisY = true;
|
||||
var testScore = 0;
|
||||
var testPriority = 0;
|
||||
var testThreshold = null;
|
||||
var testDirection = null;
|
||||
var testDistance = 0;
|
||||
var testMaxScrollX = 0;
|
||||
var testMaxScrollY = 0;
|
||||
|
||||
var xElement = null;
|
||||
var xPriority = -Infinity;
|
||||
var xThreshold = 0;
|
||||
var xScore = 0;
|
||||
var xDirection = null;
|
||||
var xDistance = 0;
|
||||
var xMaxScroll = 0;
|
||||
|
||||
var yElement = null;
|
||||
var yPriority = -Infinity;
|
||||
var yThreshold = 0;
|
||||
var yScore = 0;
|
||||
var yDirection = null;
|
||||
var yDistance = 0;
|
||||
var yMaxScroll = 0;
|
||||
|
||||
for (var i = 0; i < targets.length; i++) {
|
||||
target = targets[i];
|
||||
testAxisX = checkX && dragDirectionX && target.axis !== AXIS_Y;
|
||||
testAxisY = checkY && dragDirectionY && target.axis !== AXIS_X;
|
||||
testPriority = target.priority || 0;
|
||||
|
||||
// Ignore this item if it's x-axis and y-axis priority is lower than
|
||||
// the currently matching item's.
|
||||
if ((!testAxisX || testPriority < xPriority) && (!testAxisY || testPriority < yPriority)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
testElement = getScrollElement(target.element || target);
|
||||
testMaxScrollX = testAxisX ? getScrollLeftMax(testElement) : -1;
|
||||
testMaxScrollY = testAxisY ? getScrollTopMax(testElement) : -1;
|
||||
|
||||
// Ignore this item if there is no possibility to scroll.
|
||||
if (!testMaxScrollX && !testMaxScrollY) continue;
|
||||
|
||||
testRect = getContentRect(testElement, testRect);
|
||||
testScore = getIntersectionScore(itemRect, testRect);
|
||||
|
||||
// Ignore this item if it's not overlapping at all with the dragged item.
|
||||
if (testScore <= 0) continue;
|
||||
|
||||
// Test x-axis.
|
||||
if (
|
||||
testAxisX &&
|
||||
testPriority >= xPriority &&
|
||||
testMaxScrollX > 0 &&
|
||||
(testPriority > xPriority || testScore > xScore)
|
||||
) {
|
||||
testDirection = null;
|
||||
testThreshold = computeThreshold(
|
||||
typeof target.threshold === 'number' ? target.threshold : threshold,
|
||||
safeZone,
|
||||
itemRect.width,
|
||||
testRect.width
|
||||
);
|
||||
if (dragDirectionX === RIGHT) {
|
||||
testDistance = testRect.right + testThreshold.offset - itemRect.right;
|
||||
if (testDistance <= testThreshold.value && getScrollLeft(testElement) < testMaxScrollX) {
|
||||
testDirection = RIGHT;
|
||||
}
|
||||
} else if (dragDirectionX === LEFT) {
|
||||
testDistance = itemRect.left - (testRect.left - testThreshold.offset);
|
||||
if (testDistance <= testThreshold.value && getScrollLeft(testElement) > 0) {
|
||||
testDirection = LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
if (testDirection !== null) {
|
||||
xElement = testElement;
|
||||
xPriority = testPriority;
|
||||
xThreshold = testThreshold.value;
|
||||
xScore = testScore;
|
||||
xDirection = testDirection;
|
||||
xDistance = testDistance;
|
||||
xMaxScroll = testMaxScrollX;
|
||||
}
|
||||
}
|
||||
|
||||
// Test y-axis.
|
||||
if (
|
||||
testAxisY &&
|
||||
testPriority >= yPriority &&
|
||||
testMaxScrollY > 0 &&
|
||||
(testPriority > yPriority || testScore > yScore)
|
||||
) {
|
||||
testDirection = null;
|
||||
testThreshold = computeThreshold(
|
||||
typeof target.threshold === 'number' ? target.threshold : threshold,
|
||||
safeZone,
|
||||
itemRect.height,
|
||||
testRect.height
|
||||
);
|
||||
if (dragDirectionY === DOWN) {
|
||||
testDistance = testRect.bottom + testThreshold.offset - itemRect.bottom;
|
||||
if (testDistance <= testThreshold.value && getScrollTop(testElement) < testMaxScrollY) {
|
||||
testDirection = DOWN;
|
||||
}
|
||||
} else if (dragDirectionY === UP) {
|
||||
testDistance = itemRect.top - (testRect.top - testThreshold.offset);
|
||||
if (testDistance <= testThreshold.value && getScrollTop(testElement) > 0) {
|
||||
testDirection = UP;
|
||||
}
|
||||
}
|
||||
|
||||
if (testDirection !== null) {
|
||||
yElement = testElement;
|
||||
yPriority = testPriority;
|
||||
yThreshold = testThreshold.value;
|
||||
yScore = testScore;
|
||||
yDirection = testDirection;
|
||||
yDistance = testDistance;
|
||||
yMaxScroll = testMaxScrollY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request or cancel x-axis scroll.
|
||||
if (checkX) {
|
||||
if (xElement) {
|
||||
this._requestItemScroll(
|
||||
item,
|
||||
AXIS_X,
|
||||
xElement,
|
||||
xDirection,
|
||||
xThreshold,
|
||||
xDistance,
|
||||
xMaxScroll
|
||||
);
|
||||
} else {
|
||||
this._cancelItemScroll(item, AXIS_X);
|
||||
}
|
||||
}
|
||||
|
||||
// Request or cancel y-axis scroll.
|
||||
if (checkY) {
|
||||
if (yElement) {
|
||||
this._requestItemScroll(
|
||||
item,
|
||||
AXIS_Y,
|
||||
yElement,
|
||||
yDirection,
|
||||
yThreshold,
|
||||
yDistance,
|
||||
yMaxScroll
|
||||
);
|
||||
} else {
|
||||
this._cancelItemScroll(item, AXIS_Y);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AutoScroller.prototype._updateScrollRequest = function (scrollRequest) {
|
||||
var item = scrollRequest.item;
|
||||
var settings = getItemAutoScrollSettings(item);
|
||||
var targets = isFunction(settings.targets) ? settings.targets(item) : settings.targets;
|
||||
var targetCount = (targets && targets.length) || 0;
|
||||
var threshold = settings.threshold;
|
||||
var safeZone = settings.safeZone;
|
||||
var itemRect = this._getItemHandleRect(item, settings.handle, RECT_1);
|
||||
var testRect = RECT_2;
|
||||
var target = null;
|
||||
var testElement = null;
|
||||
var testIsAxisX = false;
|
||||
var testScore = null;
|
||||
var testThreshold = null;
|
||||
var testDistance = null;
|
||||
var testScroll = null;
|
||||
var testMaxScroll = null;
|
||||
var hasReachedEnd = null;
|
||||
|
||||
for (var i = 0; i < targetCount; i++) {
|
||||
target = targets[i];
|
||||
|
||||
// Make sure we have a matching element.
|
||||
testElement = getScrollElement(target.element || target);
|
||||
if (testElement !== scrollRequest.element) continue;
|
||||
|
||||
// Make sure we have a matching axis.
|
||||
testIsAxisX = !!(AXIS_X & scrollRequest.direction);
|
||||
if (testIsAxisX) {
|
||||
if (target.axis === AXIS_Y) continue;
|
||||
} else {
|
||||
if (target.axis === AXIS_X) continue;
|
||||
}
|
||||
|
||||
// Stop scrolling if there is no room to scroll anymore.
|
||||
testMaxScroll = testIsAxisX ? getScrollLeftMax(testElement) : getScrollTopMax(testElement);
|
||||
if (testMaxScroll <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
testRect = getContentRect(testElement, testRect);
|
||||
testScore = getIntersectionScore(itemRect, testRect);
|
||||
|
||||
// Stop scrolling if dragged item is not overlapping with the scroll
|
||||
// element anymore.
|
||||
if (testScore <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Compute threshold and edge offset.
|
||||
testThreshold = computeThreshold(
|
||||
typeof target.threshold === 'number' ? target.threshold : threshold,
|
||||
safeZone,
|
||||
testIsAxisX ? itemRect.width : itemRect.height,
|
||||
testIsAxisX ? testRect.width : testRect.height
|
||||
);
|
||||
|
||||
// Compute distance (based on current direction).
|
||||
if (scrollRequest.direction === LEFT) {
|
||||
testDistance = itemRect.left - (testRect.left - testThreshold.offset);
|
||||
} else if (scrollRequest.direction === RIGHT) {
|
||||
testDistance = testRect.right + testThreshold.offset - itemRect.right;
|
||||
} else if (scrollRequest.direction === UP) {
|
||||
testDistance = itemRect.top - (testRect.top - testThreshold.offset);
|
||||
} else {
|
||||
testDistance = testRect.bottom + testThreshold.offset - itemRect.bottom;
|
||||
}
|
||||
|
||||
// Stop scrolling if threshold is not exceeded.
|
||||
if (testDistance > testThreshold.value) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop scrolling if we have reached the end of the scroll value.
|
||||
testScroll = testIsAxisX ? getScrollLeft(testElement) : getScrollTop(testElement);
|
||||
hasReachedEnd =
|
||||
FORWARD & scrollRequest.direction ? testScroll >= testMaxScroll : testScroll <= 0;
|
||||
if (hasReachedEnd) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Scrolling can continue, let's update the values.
|
||||
scrollRequest.maxValue = testMaxScroll;
|
||||
scrollRequest.threshold = testThreshold.value;
|
||||
scrollRequest.distance = testDistance;
|
||||
scrollRequest.isEnding = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Before we end the request, let's see if we need to stop the scrolling
|
||||
// smoothly or immediately.
|
||||
if (settings.smoothStop === true && scrollRequest.speed > 0) {
|
||||
if (hasReachedEnd === null) hasReachedEnd = scrollRequest.hasReachedEnd();
|
||||
scrollRequest.isEnding = hasReachedEnd ? false : true;
|
||||
} else {
|
||||
scrollRequest.isEnding = false;
|
||||
}
|
||||
|
||||
return scrollRequest.isEnding;
|
||||
};
|
||||
|
||||
AutoScroller.prototype._updateRequests = function () {
|
||||
var items = this._items;
|
||||
var requestsX = this._requests[AXIS_X];
|
||||
var requestsY = this._requests[AXIS_Y];
|
||||
var item, reqX, reqY, checkTime, needsCheck, checkX, checkY;
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
item = items[i];
|
||||
checkTime = this._requestOverlapCheck[item._id];
|
||||
needsCheck = checkTime > 0 && this._tickTime - checkTime > this._overlapCheckInterval;
|
||||
|
||||
checkX = true;
|
||||
reqX = requestsX[item._id];
|
||||
if (reqX && reqX.isActive) {
|
||||
checkX = !this._updateScrollRequest(reqX);
|
||||
if (checkX) {
|
||||
needsCheck = true;
|
||||
this._cancelItemScroll(item, AXIS_X);
|
||||
}
|
||||
}
|
||||
|
||||
checkY = true;
|
||||
reqY = requestsY[item._id];
|
||||
if (reqY && reqY.isActive) {
|
||||
checkY = !this._updateScrollRequest(reqY);
|
||||
if (checkY) {
|
||||
needsCheck = true;
|
||||
this._cancelItemScroll(item, AXIS_Y);
|
||||
}
|
||||
}
|
||||
|
||||
if (needsCheck) {
|
||||
this._requestOverlapCheck[item._id] = 0;
|
||||
this._checkItemOverlap(item, checkX, checkY);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AutoScroller.prototype._requestAction = function (request, axis) {
|
||||
var actions = this._actions;
|
||||
var isAxisX = axis === AXIS_X;
|
||||
var action = null;
|
||||
|
||||
for (var i = 0; i < actions.length; i++) {
|
||||
action = actions[i];
|
||||
|
||||
// If the action's request does not match the request's -> skip.
|
||||
if (request.element !== action.element) {
|
||||
action = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the request and action share the same element, but the request slot
|
||||
// for the requested axis is already reserved let's ignore and cancel this
|
||||
// request.
|
||||
if (isAxisX ? action.requestX : action.requestY) {
|
||||
this._cancelItemScroll(request.item, axis);
|
||||
return;
|
||||
}
|
||||
|
||||
// Seems like we have found our action, let's break the loop.
|
||||
break;
|
||||
}
|
||||
|
||||
if (!action) action = this._actionPool.pick();
|
||||
action.element = request.element;
|
||||
action.addRequest(request);
|
||||
|
||||
request.tick(this._tickDeltaTime);
|
||||
actions.push(action);
|
||||
};
|
||||
|
||||
AutoScroller.prototype._updateActions = function () {
|
||||
var items = this._items;
|
||||
var requests = this._requests;
|
||||
var actions = this._actions;
|
||||
var itemId;
|
||||
var reqX;
|
||||
var reqY;
|
||||
var i;
|
||||
|
||||
// Generate actions.
|
||||
for (i = 0; i < items.length; i++) {
|
||||
itemId = items[i]._id;
|
||||
reqX = requests[AXIS_X][itemId];
|
||||
reqY = requests[AXIS_Y][itemId];
|
||||
if (reqX) this._requestAction(reqX, AXIS_X);
|
||||
if (reqY) this._requestAction(reqY, AXIS_Y);
|
||||
}
|
||||
|
||||
// Compute actions' scroll values.
|
||||
for (i = 0; i < actions.length; i++) {
|
||||
actions[i].computeScrollValues();
|
||||
}
|
||||
};
|
||||
|
||||
AutoScroller.prototype._applyActions = function () {
|
||||
var actions = this._actions;
|
||||
var items = this._items;
|
||||
var i;
|
||||
|
||||
// No actions -> no scrolling.
|
||||
if (!actions.length) return;
|
||||
|
||||
// Scroll all the required elements.
|
||||
for (i = 0; i < actions.length; i++) {
|
||||
actions[i].scroll();
|
||||
this._actionPool.release(actions[i]);
|
||||
}
|
||||
|
||||
// Reset actions.
|
||||
actions.length = 0;
|
||||
|
||||
// Sync the item position immediately after all the auto-scrolling business is
|
||||
// finished. Without this procedure the items will jitter during auto-scroll
|
||||
// (in some cases at least) since the drag scroll handler is async (bound to
|
||||
// raf tick). Note that this procedure should not emit any dragScroll events,
|
||||
// because otherwise they would be emitted twice for the same event.
|
||||
for (i = 0; i < items.length; i++) prepareItemScrollSync(items[i]);
|
||||
for (i = 0; i < items.length; i++) applyItemScrollSync(items[i]);
|
||||
};
|
||||
|
||||
AutoScroller.prototype._updateDragDirection = function (item) {
|
||||
var dragPositions = this._dragPositions[item._id];
|
||||
var dragDirections = this._dragDirections[item._id];
|
||||
var x1 = item._drag._left;
|
||||
var y1 = item._drag._top;
|
||||
if (dragPositions.length) {
|
||||
var x2 = dragPositions[0];
|
||||
var y2 = dragPositions[1];
|
||||
dragDirections[0] = x1 > x2 ? RIGHT : x1 < x2 ? LEFT : dragDirections[0] || 0;
|
||||
dragDirections[1] = y1 > y2 ? DOWN : y1 < y2 ? UP : dragDirections[1] || 0;
|
||||
}
|
||||
dragPositions[0] = x1;
|
||||
dragPositions[1] = y1;
|
||||
};
|
||||
|
||||
AutoScroller.prototype.addItem = function (item) {
|
||||
if (this._isDestroyed) return;
|
||||
var index = this._items.indexOf(item);
|
||||
if (index === -1) {
|
||||
this._items.push(item);
|
||||
this._requestOverlapCheck[item._id] = this._tickTime;
|
||||
this._dragDirections[item._id] = [0, 0];
|
||||
this._dragPositions[item._id] = [];
|
||||
if (!this._isTicking) this._startTicking();
|
||||
}
|
||||
};
|
||||
|
||||
AutoScroller.prototype.updateItem = function (item) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
// Make sure the item still exists in the auto-scroller.
|
||||
if (!this._dragDirections[item._id]) return;
|
||||
|
||||
this._updateDragDirection(item);
|
||||
if (!this._requestOverlapCheck[item._id]) {
|
||||
this._requestOverlapCheck[item._id] = this._tickTime;
|
||||
}
|
||||
};
|
||||
|
||||
AutoScroller.prototype.removeItem = function (item) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var index = this._items.indexOf(item);
|
||||
if (index === -1) return;
|
||||
|
||||
var itemId = item._id;
|
||||
|
||||
var reqX = this._requests[AXIS_X][itemId];
|
||||
if (reqX) {
|
||||
this._cancelItemScroll(item, AXIS_X);
|
||||
delete this._requests[AXIS_X][itemId];
|
||||
}
|
||||
|
||||
var reqY = this._requests[AXIS_Y][itemId];
|
||||
if (reqY) {
|
||||
this._cancelItemScroll(item, AXIS_Y);
|
||||
delete this._requests[AXIS_Y][itemId];
|
||||
}
|
||||
|
||||
delete this._requestOverlapCheck[itemId];
|
||||
delete this._dragPositions[itemId];
|
||||
delete this._dragDirections[itemId];
|
||||
this._items.splice(index, 1);
|
||||
|
||||
if (this._isTicking && !this._items.length) {
|
||||
this._stopTicking();
|
||||
}
|
||||
};
|
||||
|
||||
AutoScroller.prototype.isItemScrollingX = function (item) {
|
||||
var reqX = this._requests[AXIS_X][item._id];
|
||||
return !!(reqX && reqX.isActive);
|
||||
};
|
||||
|
||||
AutoScroller.prototype.isItemScrollingY = function (item) {
|
||||
var reqY = this._requests[AXIS_Y][item._id];
|
||||
return !!(reqY && reqY.isActive);
|
||||
};
|
||||
|
||||
AutoScroller.prototype.isItemScrolling = function (item) {
|
||||
return this.isItemScrollingX(item) || this.isItemScrollingY(item);
|
||||
};
|
||||
|
||||
AutoScroller.prototype.destroy = function () {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var items = this._items.slice(0);
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
this.removeItem(items[i]);
|
||||
}
|
||||
|
||||
this._actions.length = 0;
|
||||
this._requestPool.reset();
|
||||
this._actionPool.reset();
|
||||
|
||||
this._isDestroyed = true;
|
||||
};
|
||||
19
app/src/vendor/muuri-src/AutoScroller/LICENSE.md
vendored
Normal file
19
app/src/vendor/muuri-src/AutoScroller/LICENSE.md
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2019, Niklas Rämö
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
26
app/src/vendor/muuri-src/AutoScroller/Pool.js
vendored
Normal file
26
app/src/vendor/muuri-src/AutoScroller/Pool.js
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Muuri AutoScroller
|
||||
* Copyright (c) 2019-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md
|
||||
*/
|
||||
|
||||
export default function Pool(createItem, releaseItem) {
|
||||
this.pool = [];
|
||||
this.createItem = createItem;
|
||||
this.releaseItem = releaseItem;
|
||||
}
|
||||
|
||||
Pool.prototype.pick = function () {
|
||||
return this.pool.pop() || this.createItem();
|
||||
};
|
||||
|
||||
Pool.prototype.release = function (item) {
|
||||
this.releaseItem(item);
|
||||
if (this.pool.indexOf(item) !== -1) return;
|
||||
this.pool.push(item);
|
||||
};
|
||||
|
||||
Pool.prototype.reset = function () {
|
||||
this.pool.length = 0;
|
||||
};
|
||||
66
app/src/vendor/muuri-src/AutoScroller/ScrollAction.js
vendored
Normal file
66
app/src/vendor/muuri-src/AutoScroller/ScrollAction.js
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Muuri AutoScroller
|
||||
* Copyright (c) 2019-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md
|
||||
*/
|
||||
|
||||
import { getScrollLeft, getScrollTop } from './utils';
|
||||
import { AXIS_X } from './constants';
|
||||
|
||||
export default function ScrollAction() {
|
||||
this.element = null;
|
||||
this.requestX = null;
|
||||
this.requestY = null;
|
||||
this.scrollLeft = 0;
|
||||
this.scrollTop = 0;
|
||||
}
|
||||
|
||||
ScrollAction.prototype.reset = function () {
|
||||
if (this.requestX) this.requestX.action = null;
|
||||
if (this.requestY) this.requestY.action = null;
|
||||
this.element = null;
|
||||
this.requestX = null;
|
||||
this.requestY = null;
|
||||
this.scrollLeft = 0;
|
||||
this.scrollTop = 0;
|
||||
};
|
||||
|
||||
ScrollAction.prototype.addRequest = function (request) {
|
||||
if (AXIS_X & request.direction) {
|
||||
this.removeRequest(this.requestX);
|
||||
this.requestX = request;
|
||||
} else {
|
||||
this.removeRequest(this.requestY);
|
||||
this.requestY = request;
|
||||
}
|
||||
request.action = this;
|
||||
};
|
||||
|
||||
ScrollAction.prototype.removeRequest = function (request) {
|
||||
if (!request) return;
|
||||
if (this.requestX === request) {
|
||||
this.requestX = null;
|
||||
request.action = null;
|
||||
} else if (this.requestY === request) {
|
||||
this.requestY = null;
|
||||
request.action = null;
|
||||
}
|
||||
};
|
||||
|
||||
ScrollAction.prototype.computeScrollValues = function () {
|
||||
this.scrollLeft = this.requestX ? this.requestX.value : getScrollLeft(this.element);
|
||||
this.scrollTop = this.requestY ? this.requestY.value : getScrollTop(this.element);
|
||||
};
|
||||
|
||||
ScrollAction.prototype.scroll = function () {
|
||||
var element = this.element;
|
||||
if (!element) return;
|
||||
|
||||
if (element.scrollTo) {
|
||||
element.scrollTo(this.scrollLeft, this.scrollTop);
|
||||
} else {
|
||||
element.scrollLeft = this.scrollLeft;
|
||||
element.scrollTop = this.scrollTop;
|
||||
}
|
||||
};
|
||||
108
app/src/vendor/muuri-src/AutoScroller/ScrollRequest.js
vendored
Normal file
108
app/src/vendor/muuri-src/AutoScroller/ScrollRequest.js
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Muuri AutoScroller
|
||||
* Copyright (c) 2019-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md
|
||||
*/
|
||||
|
||||
import isFunction from '../utils/isFunction';
|
||||
import { AXIS_X, FORWARD } from './constants';
|
||||
import { getScrollLeft, getScrollTop, getItemAutoScrollSettings } from './utils';
|
||||
|
||||
export default function ScrollRequest() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
ScrollRequest.prototype.reset = function () {
|
||||
if (this.isActive) this.onStop();
|
||||
this.item = null;
|
||||
this.element = null;
|
||||
this.isActive = false;
|
||||
this.isEnding = false;
|
||||
this.direction = null;
|
||||
this.value = null;
|
||||
this.maxValue = 0;
|
||||
this.threshold = 0;
|
||||
this.distance = 0;
|
||||
this.speed = 0;
|
||||
this.duration = 0;
|
||||
this.action = null;
|
||||
};
|
||||
|
||||
ScrollRequest.prototype.hasReachedEnd = function () {
|
||||
return FORWARD & this.direction ? this.value >= this.maxValue : this.value <= 0;
|
||||
};
|
||||
|
||||
ScrollRequest.prototype.computeCurrentScrollValue = function () {
|
||||
if (this.value === null) {
|
||||
return AXIS_X & this.direction ? getScrollLeft(this.element) : getScrollTop(this.element);
|
||||
}
|
||||
return Math.max(0, Math.min(this.value, this.maxValue));
|
||||
};
|
||||
|
||||
ScrollRequest.prototype.computeNextScrollValue = function (deltaTime) {
|
||||
var delta = this.speed * (deltaTime / 1000);
|
||||
var nextValue = FORWARD & this.direction ? this.value + delta : this.value - delta;
|
||||
return Math.max(0, Math.min(nextValue, this.maxValue));
|
||||
};
|
||||
|
||||
ScrollRequest.prototype.computeSpeed = (function () {
|
||||
var data = {
|
||||
direction: null,
|
||||
threshold: 0,
|
||||
distance: 0,
|
||||
value: 0,
|
||||
maxValue: 0,
|
||||
deltaTime: 0,
|
||||
duration: 0,
|
||||
isEnding: false,
|
||||
};
|
||||
|
||||
return function (deltaTime) {
|
||||
var item = this.item;
|
||||
var speed = getItemAutoScrollSettings(item).speed;
|
||||
|
||||
if (isFunction(speed)) {
|
||||
data.direction = this.direction;
|
||||
data.threshold = this.threshold;
|
||||
data.distance = this.distance;
|
||||
data.value = this.value;
|
||||
data.maxValue = this.maxValue;
|
||||
data.duration = this.duration;
|
||||
data.speed = this.speed;
|
||||
data.deltaTime = deltaTime;
|
||||
data.isEnding = this.isEnding;
|
||||
return speed(item, this.element, data);
|
||||
} else {
|
||||
return speed;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
ScrollRequest.prototype.tick = function (deltaTime) {
|
||||
if (!this.isActive) {
|
||||
this.isActive = true;
|
||||
this.onStart();
|
||||
}
|
||||
this.value = this.computeCurrentScrollValue();
|
||||
this.speed = this.computeSpeed(deltaTime);
|
||||
this.value = this.computeNextScrollValue(deltaTime);
|
||||
this.duration += deltaTime;
|
||||
return this.value;
|
||||
};
|
||||
|
||||
ScrollRequest.prototype.onStart = function () {
|
||||
var item = this.item;
|
||||
var onStart = getItemAutoScrollSettings(item).onStart;
|
||||
if (isFunction(onStart)) onStart(item, this.element, this.direction);
|
||||
};
|
||||
|
||||
ScrollRequest.prototype.onStop = function () {
|
||||
var item = this.item;
|
||||
var onStop = getItemAutoScrollSettings(item).onStop;
|
||||
if (isFunction(onStop)) onStop(item, this.element, this.direction);
|
||||
// Manually nudge sort to happen. There's a good chance that the item is still
|
||||
// after the scroll stops which means that the next sort will be triggered
|
||||
// only after the item is moved or it's parent scrolled.
|
||||
if (item._drag) item._drag.sort();
|
||||
};
|
||||
15
app/src/vendor/muuri-src/AutoScroller/constants.js
vendored
Normal file
15
app/src/vendor/muuri-src/AutoScroller/constants.js
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Muuri AutoScroller
|
||||
* Copyright (c) 2019-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md
|
||||
*/
|
||||
|
||||
export var AXIS_X = 1;
|
||||
export var AXIS_Y = 2;
|
||||
export var FORWARD = 4;
|
||||
export var BACKWARD = 8;
|
||||
export var LEFT = AXIS_X | BACKWARD;
|
||||
export var RIGHT = AXIS_X | FORWARD;
|
||||
export var UP = AXIS_Y | BACKWARD;
|
||||
export var DOWN = AXIS_Y | FORWARD;
|
||||
139
app/src/vendor/muuri-src/AutoScroller/utils.js
vendored
Normal file
139
app/src/vendor/muuri-src/AutoScroller/utils.js
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Muuri AutoScroller
|
||||
* Copyright (c) 2019-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md
|
||||
*/
|
||||
|
||||
import getStyleAsFloat from '../utils/getStyleAsFloat';
|
||||
|
||||
var DOC_ELEM = document.documentElement;
|
||||
var BODY = document.body;
|
||||
var THRESHOLD_DATA = { value: 0, offset: 0 };
|
||||
|
||||
/**
|
||||
* @param {HTMLElement|Window} element
|
||||
* @returns {HTMLElement|Window}
|
||||
*/
|
||||
export function getScrollElement(element) {
|
||||
if (element === window || element === DOC_ELEM || element === BODY) {
|
||||
return window;
|
||||
} else {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement|Window} element
|
||||
* @returns {Number}
|
||||
*/
|
||||
export function getScrollLeft(element) {
|
||||
return element === window ? element.pageXOffset : element.scrollLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement|Window} element
|
||||
* @returns {Number}
|
||||
*/
|
||||
export function getScrollTop(element) {
|
||||
return element === window ? element.pageYOffset : element.scrollTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement|Window} element
|
||||
* @returns {Number}
|
||||
*/
|
||||
export function getScrollLeftMax(element) {
|
||||
if (element === window) {
|
||||
return DOC_ELEM.scrollWidth - DOC_ELEM.clientWidth;
|
||||
} else {
|
||||
return element.scrollWidth - element.clientWidth;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement|Window} element
|
||||
* @returns {Number}
|
||||
*/
|
||||
export function getScrollTopMax(element) {
|
||||
if (element === window) {
|
||||
return DOC_ELEM.scrollHeight - DOC_ELEM.clientHeight;
|
||||
} else {
|
||||
return element.scrollHeight - element.clientHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get window's or element's client rectangle data relative to the element's
|
||||
* content dimensions (includes inner size + padding, excludes scrollbars,
|
||||
* borders and margins).
|
||||
*
|
||||
* @param {HTMLElement|Window} element
|
||||
* @returns {Rectangle}
|
||||
*/
|
||||
export function getContentRect(element, result) {
|
||||
result = result || {};
|
||||
|
||||
if (element === window) {
|
||||
result.width = DOC_ELEM.clientWidth;
|
||||
result.height = DOC_ELEM.clientHeight;
|
||||
result.left = 0;
|
||||
result.right = result.width;
|
||||
result.top = 0;
|
||||
result.bottom = result.height;
|
||||
} else {
|
||||
var bcr = element.getBoundingClientRect();
|
||||
var borderLeft = element.clientLeft || getStyleAsFloat(element, 'border-left-width');
|
||||
var borderTop = element.clientTop || getStyleAsFloat(element, 'border-top-width');
|
||||
result.width = element.clientWidth;
|
||||
result.height = element.clientHeight;
|
||||
result.left = bcr.left + borderLeft;
|
||||
result.right = result.left + result.width;
|
||||
result.top = bcr.top + borderTop;
|
||||
result.bottom = result.top + result.height;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Item} item
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getItemAutoScrollSettings(item) {
|
||||
return item._drag._getGrid()._settings.dragAutoScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Item} item
|
||||
*/
|
||||
export function prepareItemScrollSync(item) {
|
||||
if (!item._drag) return;
|
||||
item._drag._prepareScroll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Item} item
|
||||
*/
|
||||
export function applyItemScrollSync(item) {
|
||||
if (!item._drag || !item._isActive) return;
|
||||
var drag = item._drag;
|
||||
drag._scrollDiffX = drag._scrollDiffY = 0;
|
||||
item._setTranslate(drag._left, drag._top);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute threshold value and edge offset.
|
||||
*
|
||||
* @param {Number} threshold
|
||||
* @param {Number} safeZone
|
||||
* @param {Number} itemSize
|
||||
* @param {Number} targetSize
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function computeThreshold(threshold, safeZone, itemSize, targetSize) {
|
||||
THRESHOLD_DATA.value = Math.min(targetSize / 2, threshold);
|
||||
THRESHOLD_DATA.offset =
|
||||
Math.max(0, itemSize + THRESHOLD_DATA.value * 2 + targetSize * safeZone - targetSize) / 2;
|
||||
return THRESHOLD_DATA;
|
||||
}
|
||||
575
app/src/vendor/muuri-src/Dragger/Dragger.js
vendored
Normal file
575
app/src/vendor/muuri-src/Dragger/Dragger.js
vendored
Normal file
@ -0,0 +1,575 @@
|
||||
/**
|
||||
* Muuri Dragger
|
||||
* Copyright (c) 2018-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/Dragger/LICENSE.md
|
||||
*/
|
||||
|
||||
import { HAS_TOUCH_EVENTS, HAS_POINTER_EVENTS, HAS_MS_POINTER_EVENTS } from '../constants';
|
||||
|
||||
import Emitter from '../Emitter/Emitter';
|
||||
import EdgeHack from './EdgeHack';
|
||||
|
||||
import getPrefixedPropName from '../utils/getPrefixedPropName';
|
||||
import hasPassiveEvents from '../utils/hasPassiveEvents';
|
||||
|
||||
var ua = window.navigator.userAgent.toLowerCase();
|
||||
var isEdge = ua.indexOf('edge') > -1;
|
||||
var isIE = ua.indexOf('trident') > -1;
|
||||
var isFirefox = ua.indexOf('firefox') > -1;
|
||||
var isAndroid = ua.indexOf('android') > -1;
|
||||
|
||||
var listenerOptions = hasPassiveEvents() ? { passive: true } : false;
|
||||
|
||||
var taProp = 'touchAction';
|
||||
var taPropPrefixed = getPrefixedPropName(document.documentElement.style, taProp);
|
||||
var taDefaultValue = 'auto';
|
||||
|
||||
/**
|
||||
* Creates a new Dragger instance for an element.
|
||||
*
|
||||
* @public
|
||||
* @class
|
||||
* @param {HTMLElement} element
|
||||
* @param {Object} [cssProps]
|
||||
*/
|
||||
function Dragger(element, cssProps) {
|
||||
this._element = element;
|
||||
this._emitter = new Emitter();
|
||||
this._isDestroyed = false;
|
||||
this._cssProps = {};
|
||||
this._touchAction = '';
|
||||
this._isActive = false;
|
||||
|
||||
this._pointerId = null;
|
||||
this._startTime = 0;
|
||||
this._startX = 0;
|
||||
this._startY = 0;
|
||||
this._currentX = 0;
|
||||
this._currentY = 0;
|
||||
|
||||
this._onStart = this._onStart.bind(this);
|
||||
this._onMove = this._onMove.bind(this);
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onEnd = this._onEnd.bind(this);
|
||||
|
||||
// Can't believe had to build a freaking class for a hack!
|
||||
this._edgeHack = null;
|
||||
if ((isEdge || isIE) && (HAS_POINTER_EVENTS || HAS_MS_POINTER_EVENTS)) {
|
||||
this._edgeHack = new EdgeHack(this);
|
||||
}
|
||||
|
||||
// Apply initial CSS props.
|
||||
this.setCssProps(cssProps);
|
||||
|
||||
// If touch action was not provided with initial CSS props let's assume it's
|
||||
// auto.
|
||||
if (!this._touchAction) {
|
||||
this.setTouchAction(taDefaultValue);
|
||||
}
|
||||
|
||||
// Prevent native link/image dragging for the item and it's children.
|
||||
element.addEventListener('dragstart', Dragger._preventDefault, false);
|
||||
|
||||
// Listen to start event.
|
||||
element.addEventListener(Dragger._inputEvents.start, this._onStart, listenerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected properties
|
||||
* ********************
|
||||
*/
|
||||
|
||||
Dragger._pointerEvents = {
|
||||
start: 'pointerdown',
|
||||
move: 'pointermove',
|
||||
cancel: 'pointercancel',
|
||||
end: 'pointerup',
|
||||
};
|
||||
|
||||
Dragger._msPointerEvents = {
|
||||
start: 'MSPointerDown',
|
||||
move: 'MSPointerMove',
|
||||
cancel: 'MSPointerCancel',
|
||||
end: 'MSPointerUp',
|
||||
};
|
||||
|
||||
Dragger._touchEvents = {
|
||||
start: 'touchstart',
|
||||
move: 'touchmove',
|
||||
cancel: 'touchcancel',
|
||||
end: 'touchend',
|
||||
};
|
||||
|
||||
Dragger._mouseEvents = {
|
||||
start: 'mousedown',
|
||||
move: 'mousemove',
|
||||
cancel: '',
|
||||
end: 'mouseup',
|
||||
};
|
||||
|
||||
Dragger._inputEvents = (function () {
|
||||
if (HAS_TOUCH_EVENTS) return Dragger._touchEvents;
|
||||
if (HAS_POINTER_EVENTS) return Dragger._pointerEvents;
|
||||
if (HAS_MS_POINTER_EVENTS) return Dragger._msPointerEvents;
|
||||
return Dragger._mouseEvents;
|
||||
})();
|
||||
|
||||
Dragger._emitter = new Emitter();
|
||||
|
||||
Dragger._emitterEvents = {
|
||||
start: 'start',
|
||||
move: 'move',
|
||||
end: 'end',
|
||||
cancel: 'cancel',
|
||||
};
|
||||
|
||||
Dragger._activeInstances = [];
|
||||
|
||||
/**
|
||||
* Protected static methods
|
||||
* ************************
|
||||
*/
|
||||
|
||||
Dragger._preventDefault = function (e) {
|
||||
if (e.preventDefault && e.cancelable !== false) e.preventDefault();
|
||||
};
|
||||
|
||||
Dragger._activateInstance = function (instance) {
|
||||
var index = Dragger._activeInstances.indexOf(instance);
|
||||
if (index > -1) return;
|
||||
|
||||
Dragger._activeInstances.push(instance);
|
||||
Dragger._emitter.on(Dragger._emitterEvents.move, instance._onMove);
|
||||
Dragger._emitter.on(Dragger._emitterEvents.cancel, instance._onCancel);
|
||||
Dragger._emitter.on(Dragger._emitterEvents.end, instance._onEnd);
|
||||
|
||||
if (Dragger._activeInstances.length === 1) {
|
||||
Dragger._bindListeners();
|
||||
}
|
||||
};
|
||||
|
||||
Dragger._deactivateInstance = function (instance) {
|
||||
var index = Dragger._activeInstances.indexOf(instance);
|
||||
if (index === -1) return;
|
||||
|
||||
Dragger._activeInstances.splice(index, 1);
|
||||
Dragger._emitter.off(Dragger._emitterEvents.move, instance._onMove);
|
||||
Dragger._emitter.off(Dragger._emitterEvents.cancel, instance._onCancel);
|
||||
Dragger._emitter.off(Dragger._emitterEvents.end, instance._onEnd);
|
||||
|
||||
if (!Dragger._activeInstances.length) {
|
||||
Dragger._unbindListeners();
|
||||
}
|
||||
};
|
||||
|
||||
Dragger._bindListeners = function () {
|
||||
window.addEventListener(Dragger._inputEvents.move, Dragger._onMove, listenerOptions);
|
||||
window.addEventListener(Dragger._inputEvents.end, Dragger._onEnd, listenerOptions);
|
||||
if (Dragger._inputEvents.cancel) {
|
||||
window.addEventListener(Dragger._inputEvents.cancel, Dragger._onCancel, listenerOptions);
|
||||
}
|
||||
};
|
||||
|
||||
Dragger._unbindListeners = function () {
|
||||
window.removeEventListener(Dragger._inputEvents.move, Dragger._onMove, listenerOptions);
|
||||
window.removeEventListener(Dragger._inputEvents.end, Dragger._onEnd, listenerOptions);
|
||||
if (Dragger._inputEvents.cancel) {
|
||||
window.removeEventListener(Dragger._inputEvents.cancel, Dragger._onCancel, listenerOptions);
|
||||
}
|
||||
};
|
||||
|
||||
Dragger._getEventPointerId = function (event) {
|
||||
// If we have pointer id available let's use it.
|
||||
if (typeof event.pointerId === 'number') {
|
||||
return event.pointerId;
|
||||
}
|
||||
|
||||
// For touch events let's get the first changed touch's identifier.
|
||||
if (event.changedTouches) {
|
||||
return event.changedTouches[0] ? event.changedTouches[0].identifier : null;
|
||||
}
|
||||
|
||||
// For mouse/other events let's provide a static id.
|
||||
return 1;
|
||||
};
|
||||
|
||||
Dragger._getTouchById = function (event, id) {
|
||||
// If we have a pointer event return the whole event if there's a match, and
|
||||
// null otherwise.
|
||||
if (typeof event.pointerId === 'number') {
|
||||
return event.pointerId === id ? event : null;
|
||||
}
|
||||
|
||||
// For touch events let's check if there's a changed touch object that matches
|
||||
// the pointerId in which case return the touch object.
|
||||
if (event.changedTouches) {
|
||||
for (var i = 0; i < event.changedTouches.length; i++) {
|
||||
if (event.changedTouches[i].identifier === id) {
|
||||
return event.changedTouches[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// For mouse/other events let's assume there's only one pointer and just
|
||||
// return the event.
|
||||
return event;
|
||||
};
|
||||
|
||||
Dragger._onMove = function (e) {
|
||||
Dragger._emitter.emit(Dragger._emitterEvents.move, e);
|
||||
};
|
||||
|
||||
Dragger._onCancel = function (e) {
|
||||
Dragger._emitter.emit(Dragger._emitterEvents.cancel, e);
|
||||
};
|
||||
|
||||
Dragger._onEnd = function (e) {
|
||||
Dragger._emitter.emit(Dragger._emitterEvents.end, e);
|
||||
};
|
||||
|
||||
/**
|
||||
* Private prototype methods
|
||||
* *************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reset current drag operation (if any).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Dragger.prototype._reset = function () {
|
||||
this._pointerId = null;
|
||||
this._startTime = 0;
|
||||
this._startX = 0;
|
||||
this._startY = 0;
|
||||
this._currentX = 0;
|
||||
this._currentY = 0;
|
||||
this._isActive = false;
|
||||
Dragger._deactivateInstance(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a custom dragger event from a raw event.
|
||||
*
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {(PointerEvent|TouchEvent|MouseEvent)} e
|
||||
* @returns {Object}
|
||||
*/
|
||||
Dragger.prototype._createEvent = function (type, e) {
|
||||
var touch = this._getTrackedTouch(e);
|
||||
return {
|
||||
// Hammer.js compatibility interface.
|
||||
type: type,
|
||||
srcEvent: e,
|
||||
distance: this.getDistance(),
|
||||
deltaX: this.getDeltaX(),
|
||||
deltaY: this.getDeltaY(),
|
||||
deltaTime: type === Dragger._emitterEvents.start ? 0 : this.getDeltaTime(),
|
||||
isFirst: type === Dragger._emitterEvents.start,
|
||||
isFinal: type === Dragger._emitterEvents.end || type === Dragger._emitterEvents.cancel,
|
||||
pointerType: e.pointerType || (e.touches ? 'touch' : 'mouse'),
|
||||
// Partial Touch API interface.
|
||||
identifier: this._pointerId,
|
||||
screenX: touch.screenX,
|
||||
screenY: touch.screenY,
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY,
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY,
|
||||
target: touch.target,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit a raw event as dragger event internally.
|
||||
*
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {(PointerEvent|TouchEvent|MouseEvent)} e
|
||||
*/
|
||||
Dragger.prototype._emit = function (type, e) {
|
||||
this._emitter.emit(type, this._createEvent(type, e));
|
||||
};
|
||||
|
||||
/**
|
||||
* If the provided event is a PointerEvent this method will return it if it has
|
||||
* the same pointerId as the instance. If the provided event is a TouchEvent
|
||||
* this method will try to look for a Touch instance in the changedTouches that
|
||||
* has an identifier matching this instance's pointerId. If the provided event
|
||||
* is a MouseEvent (or just any other event than PointerEvent or TouchEvent)
|
||||
* it will be returned immediately.
|
||||
*
|
||||
* @private
|
||||
* @param {(PointerEvent|TouchEvent|MouseEvent)} e
|
||||
* @returns {?(Touch|PointerEvent|MouseEvent)}
|
||||
*/
|
||||
Dragger.prototype._getTrackedTouch = function (e) {
|
||||
if (this._pointerId === null) return null;
|
||||
return Dragger._getTouchById(e, this._pointerId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler for start event.
|
||||
*
|
||||
* @private
|
||||
* @param {(PointerEvent|TouchEvent|MouseEvent)} e
|
||||
*/
|
||||
Dragger.prototype._onStart = function (e) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
// If pointer id is already assigned let's return early.
|
||||
if (this._pointerId !== null) return;
|
||||
|
||||
// Get (and set) pointer id.
|
||||
this._pointerId = Dragger._getEventPointerId(e);
|
||||
if (this._pointerId === null) return;
|
||||
|
||||
// Setup initial data and emit start event.
|
||||
var touch = this._getTrackedTouch(e);
|
||||
this._startX = this._currentX = touch.clientX;
|
||||
this._startY = this._currentY = touch.clientY;
|
||||
this._startTime = Date.now();
|
||||
this._isActive = true;
|
||||
this._emit(Dragger._emitterEvents.start, e);
|
||||
|
||||
// If the drag procedure was not reset within the start procedure let's
|
||||
// activate the instance (start listening to move/cancel/end events).
|
||||
if (this._isActive) {
|
||||
Dragger._activateInstance(this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler for move event.
|
||||
*
|
||||
* @private
|
||||
* @param {(PointerEvent|TouchEvent|MouseEvent)} e
|
||||
*/
|
||||
Dragger.prototype._onMove = function (e) {
|
||||
var touch = this._getTrackedTouch(e);
|
||||
if (!touch) return;
|
||||
this._currentX = touch.clientX;
|
||||
this._currentY = touch.clientY;
|
||||
this._emit(Dragger._emitterEvents.move, e);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler for cancel event.
|
||||
*
|
||||
* @private
|
||||
* @param {(PointerEvent|TouchEvent|MouseEvent)} e
|
||||
*/
|
||||
Dragger.prototype._onCancel = function (e) {
|
||||
if (!this._getTrackedTouch(e)) return;
|
||||
this._emit(Dragger._emitterEvents.cancel, e);
|
||||
this._reset();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler for end event.
|
||||
*
|
||||
* @private
|
||||
* @param {(PointerEvent|TouchEvent|MouseEvent)} e
|
||||
*/
|
||||
Dragger.prototype._onEnd = function (e) {
|
||||
if (!this._getTrackedTouch(e)) return;
|
||||
this._emit(Dragger._emitterEvents.end, e);
|
||||
this._reset();
|
||||
};
|
||||
|
||||
/**
|
||||
* Public prototype methods
|
||||
* ************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if the element is being dragged at the moment.
|
||||
*
|
||||
* @public
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Dragger.prototype.isActive = function () {
|
||||
return this._isActive;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set element's touch-action CSS property.
|
||||
*
|
||||
* @public
|
||||
* @param {String} value
|
||||
*/
|
||||
Dragger.prototype.setTouchAction = function (value) {
|
||||
// Store unmodified touch action value (we trust user input here).
|
||||
this._touchAction = value;
|
||||
|
||||
// Set touch-action style.
|
||||
if (taPropPrefixed) {
|
||||
this._cssProps[taPropPrefixed] = '';
|
||||
this._element.style[taPropPrefixed] = value;
|
||||
}
|
||||
|
||||
// If we have an unsupported touch-action value let's add a special listener
|
||||
// that prevents default action on touch start event. A dirty hack, but best
|
||||
// we can do for now. The other options would be to somehow polyfill the
|
||||
// unsupported touch action behavior with custom heuristics which sounds like
|
||||
// a can of worms. We do a special exception here for Firefox Android which's
|
||||
// touch-action does not work properly if the dragged element is moved in the
|
||||
// the DOM tree on touchstart.
|
||||
if (HAS_TOUCH_EVENTS) {
|
||||
this._element.removeEventListener(Dragger._touchEvents.start, Dragger._preventDefault, true);
|
||||
if (this._element.style[taPropPrefixed] !== value || (isFirefox && isAndroid)) {
|
||||
this._element.addEventListener(Dragger._touchEvents.start, Dragger._preventDefault, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update element's CSS properties. Accepts an object with camel cased style
|
||||
* props with value pairs as it's first argument.
|
||||
*
|
||||
* @public
|
||||
* @param {Object} [newProps]
|
||||
*/
|
||||
Dragger.prototype.setCssProps = function (newProps) {
|
||||
if (!newProps) return;
|
||||
|
||||
var currentProps = this._cssProps;
|
||||
var element = this._element;
|
||||
var prop;
|
||||
var prefixedProp;
|
||||
|
||||
// Reset current props.
|
||||
for (prop in currentProps) {
|
||||
element.style[prop] = currentProps[prop];
|
||||
delete currentProps[prop];
|
||||
}
|
||||
|
||||
// Set new props.
|
||||
for (prop in newProps) {
|
||||
// Make sure we have a value for the prop.
|
||||
if (!newProps[prop]) continue;
|
||||
|
||||
// Special handling for touch-action.
|
||||
if (prop === taProp) {
|
||||
this.setTouchAction(newProps[prop]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get prefixed prop and skip if it does not exist.
|
||||
prefixedProp = getPrefixedPropName(element.style, prop);
|
||||
if (!prefixedProp) continue;
|
||||
|
||||
// Store the prop and add the style.
|
||||
currentProps[prefixedProp] = '';
|
||||
element.style[prefixedProp] = newProps[prop];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* How much the pointer has moved on x-axis from start position, in pixels.
|
||||
* Positive value indicates movement from left to right.
|
||||
*
|
||||
* @public
|
||||
* @returns {Number}
|
||||
*/
|
||||
Dragger.prototype.getDeltaX = function () {
|
||||
return this._currentX - this._startX;
|
||||
};
|
||||
|
||||
/**
|
||||
* How much the pointer has moved on y-axis from start position, in pixels.
|
||||
* Positive value indicates movement from top to bottom.
|
||||
*
|
||||
* @public
|
||||
* @returns {Number}
|
||||
*/
|
||||
Dragger.prototype.getDeltaY = function () {
|
||||
return this._currentY - this._startY;
|
||||
};
|
||||
|
||||
/**
|
||||
* How far (in pixels) has pointer moved from start position.
|
||||
*
|
||||
* @public
|
||||
* @returns {Number}
|
||||
*/
|
||||
Dragger.prototype.getDistance = function () {
|
||||
var x = this.getDeltaX();
|
||||
var y = this.getDeltaY();
|
||||
return Math.sqrt(x * x + y * y);
|
||||
};
|
||||
|
||||
/**
|
||||
* How long has pointer been dragged.
|
||||
*
|
||||
* @public
|
||||
* @returns {Number}
|
||||
*/
|
||||
Dragger.prototype.getDeltaTime = function () {
|
||||
return this._startTime ? Date.now() - this._startTime : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind drag event listeners.
|
||||
*
|
||||
* @public
|
||||
* @param {String} eventName
|
||||
* - 'start', 'move', 'cancel' or 'end'.
|
||||
* @param {Function} listener
|
||||
*/
|
||||
Dragger.prototype.on = function (eventName, listener) {
|
||||
this._emitter.on(eventName, listener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind drag event listeners.
|
||||
*
|
||||
* @public
|
||||
* @param {String} eventName
|
||||
* - 'start', 'move', 'cancel' or 'end'.
|
||||
* @param {Function} listener
|
||||
*/
|
||||
Dragger.prototype.off = function (eventName, listener) {
|
||||
this._emitter.off(eventName, listener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the instance and unbind all drag event listeners.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
Dragger.prototype.destroy = function () {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var element = this._element;
|
||||
|
||||
if (this._edgeHack) this._edgeHack.destroy();
|
||||
|
||||
// Reset data and deactivate the instance.
|
||||
this._reset();
|
||||
|
||||
// Destroy emitter.
|
||||
this._emitter.destroy();
|
||||
|
||||
// Unbind event handlers.
|
||||
element.removeEventListener(Dragger._inputEvents.start, this._onStart, listenerOptions);
|
||||
element.removeEventListener('dragstart', Dragger._preventDefault, false);
|
||||
element.removeEventListener(Dragger._touchEvents.start, Dragger._preventDefault, true);
|
||||
|
||||
// Reset styles.
|
||||
for (var prop in this._cssProps) {
|
||||
element.style[prop] = this._cssProps[prop];
|
||||
delete this._cssProps[prop];
|
||||
}
|
||||
|
||||
// Reset data.
|
||||
this._element = null;
|
||||
|
||||
// Mark as destroyed.
|
||||
this._isDestroyed = true;
|
||||
};
|
||||
|
||||
export default Dragger;
|
||||
119
app/src/vendor/muuri-src/Dragger/EdgeHack.js
vendored
Normal file
119
app/src/vendor/muuri-src/Dragger/EdgeHack.js
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Muuri Dragger
|
||||
* Copyright (c) 2018-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/Dragger/LICENSE.md
|
||||
*/
|
||||
|
||||
import { HAS_POINTER_EVENTS, HAS_MS_POINTER_EVENTS } from '../constants';
|
||||
|
||||
var pointerout = HAS_POINTER_EVENTS ? 'pointerout' : HAS_MS_POINTER_EVENTS ? 'MSPointerOut' : '';
|
||||
var waitDuration = 100;
|
||||
|
||||
/**
|
||||
* If you happen to use Edge or IE on a touch capable device there is a
|
||||
* a specific case where pointercancel and pointerend events are never emitted,
|
||||
* even though one them should always be emitted when you release your finger
|
||||
* from the screen. The bug appears specifically when Muuri shifts the dragged
|
||||
* element's position in the DOM after pointerdown event, IE and Edge don't like
|
||||
* that behaviour and quite often forget to emit the pointerend/pointercancel
|
||||
* event. But, they do emit pointerout event so we utilize that here.
|
||||
* Specifically, if there has been no pointermove event within 100 milliseconds
|
||||
* since the last pointerout event we force cancel the drag operation. This hack
|
||||
* works surprisingly well 99% of the time. There is that 1% chance there still
|
||||
* that dragged items get stuck but it is what it is.
|
||||
*
|
||||
* @class
|
||||
* @param {Dragger} dragger
|
||||
*/
|
||||
function EdgeHack(dragger) {
|
||||
if (!pointerout) return;
|
||||
|
||||
this._dragger = dragger;
|
||||
this._timeout = null;
|
||||
this._outEvent = null;
|
||||
this._isActive = false;
|
||||
|
||||
this._addBehaviour = this._addBehaviour.bind(this);
|
||||
this._removeBehaviour = this._removeBehaviour.bind(this);
|
||||
this._onTimeout = this._onTimeout.bind(this);
|
||||
this._resetData = this._resetData.bind(this);
|
||||
this._onStart = this._onStart.bind(this);
|
||||
this._onOut = this._onOut.bind(this);
|
||||
|
||||
this._dragger.on('start', this._onStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
EdgeHack.prototype._addBehaviour = function () {
|
||||
if (this._isActive) return;
|
||||
this._isActive = true;
|
||||
this._dragger.on('move', this._resetData);
|
||||
this._dragger.on('cancel', this._removeBehaviour);
|
||||
this._dragger.on('end', this._removeBehaviour);
|
||||
window.addEventListener(pointerout, this._onOut);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
EdgeHack.prototype._removeBehaviour = function () {
|
||||
if (!this._isActive) return;
|
||||
this._dragger.off('move', this._resetData);
|
||||
this._dragger.off('cancel', this._removeBehaviour);
|
||||
this._dragger.off('end', this._removeBehaviour);
|
||||
window.removeEventListener(pointerout, this._onOut);
|
||||
this._resetData();
|
||||
this._isActive = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
EdgeHack.prototype._resetData = function () {
|
||||
window.clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
this._outEvent = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {(PointerEvent|TouchEvent|MouseEvent)} e
|
||||
*/
|
||||
EdgeHack.prototype._onStart = function (e) {
|
||||
if (e.pointerType === 'mouse') return;
|
||||
this._addBehaviour();
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {(PointerEvent|TouchEvent|MouseEvent)} e
|
||||
*/
|
||||
EdgeHack.prototype._onOut = function (e) {
|
||||
if (!this._dragger._getTrackedTouch(e)) return;
|
||||
this._resetData();
|
||||
this._outEvent = e;
|
||||
this._timeout = window.setTimeout(this._onTimeout, waitDuration);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
EdgeHack.prototype._onTimeout = function () {
|
||||
var e = this._outEvent;
|
||||
this._resetData();
|
||||
if (this._dragger.isActive()) this._dragger._onCancel(e);
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
EdgeHack.prototype.destroy = function () {
|
||||
if (!pointerout) return;
|
||||
this._dragger.off('start', this._onStart);
|
||||
this._removeBehaviour();
|
||||
};
|
||||
|
||||
export default EdgeHack;
|
||||
19
app/src/vendor/muuri-src/Dragger/LICENSE.md
vendored
Normal file
19
app/src/vendor/muuri-src/Dragger/LICENSE.md
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018, Niklas Rämö
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
208
app/src/vendor/muuri-src/Emitter/Emitter.js
vendored
Normal file
208
app/src/vendor/muuri-src/Emitter/Emitter.js
vendored
Normal file
@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Muuri Emitter
|
||||
* Copyright (c) 2018-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/Emitter/LICENSE.md
|
||||
*/
|
||||
|
||||
/**
|
||||
* Event emitter constructor.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
function Emitter() {
|
||||
this._events = {};
|
||||
this._queue = [];
|
||||
this._counter = 0;
|
||||
this._clearOnEmit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public prototype methods
|
||||
* ************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bind an event listener.
|
||||
*
|
||||
* @public
|
||||
* @param {String} event
|
||||
* @param {Function} listener
|
||||
* @returns {Emitter}
|
||||
*/
|
||||
Emitter.prototype.on = function (event, listener) {
|
||||
if (!this._events || !event || !listener) return this;
|
||||
|
||||
// Get listeners queue and create it if it does not exist.
|
||||
var listeners = this._events[event];
|
||||
if (!listeners) listeners = this._events[event] = [];
|
||||
|
||||
// Add the listener to the queue.
|
||||
listeners.push(listener);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind all event listeners that match the provided listener function.
|
||||
*
|
||||
* @public
|
||||
* @param {String} event
|
||||
* @param {Function} listener
|
||||
* @returns {Emitter}
|
||||
*/
|
||||
Emitter.prototype.off = function (event, listener) {
|
||||
if (!this._events || !event || !listener) return this;
|
||||
|
||||
// Get listeners and return immediately if none is found.
|
||||
var listeners = this._events[event];
|
||||
if (!listeners || !listeners.length) return this;
|
||||
|
||||
// Remove all matching listeners.
|
||||
var index;
|
||||
while ((index = listeners.indexOf(listener)) !== -1) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind all listeners of the provided event.
|
||||
*
|
||||
* @public
|
||||
* @param {String} event
|
||||
* @returns {Emitter}
|
||||
*/
|
||||
Emitter.prototype.clear = function (event) {
|
||||
if (!this._events || !event) return this;
|
||||
|
||||
var listeners = this._events[event];
|
||||
if (listeners) {
|
||||
listeners.length = 0;
|
||||
delete this._events[event];
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit all listeners in a specified event with the provided arguments.
|
||||
*
|
||||
* @public
|
||||
* @param {String} event
|
||||
* @param {...*} [args]
|
||||
* @returns {Emitter}
|
||||
*/
|
||||
Emitter.prototype.emit = function (event) {
|
||||
if (!this._events || !event) {
|
||||
this._clearOnEmit = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Get event listeners and quit early if there's no listeners.
|
||||
var listeners = this._events[event];
|
||||
if (!listeners || !listeners.length) {
|
||||
this._clearOnEmit = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
var queue = this._queue;
|
||||
var startIndex = queue.length;
|
||||
var argsLength = arguments.length - 1;
|
||||
var args;
|
||||
|
||||
// If we have more than 3 arguments let's put the arguments in an array and
|
||||
// apply it to the listeners.
|
||||
if (argsLength > 3) {
|
||||
args = [];
|
||||
args.push.apply(args, arguments);
|
||||
args.shift();
|
||||
}
|
||||
|
||||
// Add the current listeners to the callback queue before we process them.
|
||||
// This is necessary to guarantee that all of the listeners are called in
|
||||
// correct order even if new event listeners are removed/added during
|
||||
// processing and/or events are emitted during processing.
|
||||
queue.push.apply(queue, listeners);
|
||||
|
||||
// Reset the event's listeners if need be.
|
||||
if (this._clearOnEmit) {
|
||||
listeners.length = 0;
|
||||
this._clearOnEmit = false;
|
||||
}
|
||||
|
||||
// Increment queue counter. This is needed for the scenarios where emit is
|
||||
// triggered while the queue is already processing. We need to keep track of
|
||||
// how many "queue processors" there are active so that we can safely reset
|
||||
// the queue in the end when the last queue processor is finished.
|
||||
++this._counter;
|
||||
|
||||
// Process the queue (the specific part of it for this emit).
|
||||
var i = startIndex;
|
||||
var endIndex = queue.length;
|
||||
for (; i < endIndex; i++) {
|
||||
// prettier-ignore
|
||||
argsLength === 0 ? queue[i]() :
|
||||
argsLength === 1 ? queue[i](arguments[1]) :
|
||||
argsLength === 2 ? queue[i](arguments[1], arguments[2]) :
|
||||
argsLength === 3 ? queue[i](arguments[1], arguments[2], arguments[3]) :
|
||||
queue[i].apply(null, args);
|
||||
|
||||
// Stop processing if the emitter is destroyed.
|
||||
if (!this._events) return this;
|
||||
}
|
||||
|
||||
// Decrement queue process counter.
|
||||
--this._counter;
|
||||
|
||||
// Reset the queue if there are no more queue processes running.
|
||||
if (!this._counter) queue.length = 0;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit all listeners in a specified event with the provided arguments and
|
||||
* remove the event's listeners just before calling the them. This method allows
|
||||
* the emitter to serve as a queue where all listeners are called only once.
|
||||
*
|
||||
* @public
|
||||
* @param {String} event
|
||||
* @param {...*} [args]
|
||||
* @returns {Emitter}
|
||||
*/
|
||||
Emitter.prototype.burst = function () {
|
||||
if (!this._events) return this;
|
||||
this._clearOnEmit = true;
|
||||
this.emit.apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check how many listeners there are for a specific event.
|
||||
*
|
||||
* @public
|
||||
* @param {String} event
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Emitter.prototype.countListeners = function (event) {
|
||||
if (!this._events) return 0;
|
||||
var listeners = this._events[event];
|
||||
return listeners ? listeners.length : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy emitter instance. Basically just removes all bound listeners.
|
||||
*
|
||||
* @public
|
||||
* @returns {Emitter}
|
||||
*/
|
||||
Emitter.prototype.destroy = function () {
|
||||
if (!this._events) return this;
|
||||
this._queue.length = this._counter = 0;
|
||||
this._events = null;
|
||||
return this;
|
||||
};
|
||||
|
||||
export default Emitter;
|
||||
19
app/src/vendor/muuri-src/Emitter/LICENSE.md
vendored
Normal file
19
app/src/vendor/muuri-src/Emitter/LICENSE.md
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018, Niklas Rämö
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1781
app/src/vendor/muuri-src/Grid/Grid.js
vendored
Normal file
1781
app/src/vendor/muuri-src/Grid/Grid.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
420
app/src/vendor/muuri-src/Item/Item.js
vendored
Normal file
420
app/src/vendor/muuri-src/Item/Item.js
vendored
Normal file
@ -0,0 +1,420 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { GRID_INSTANCES, ITEM_ELEMENT_MAP } from '../constants';
|
||||
|
||||
import ItemDrag from './ItemDrag';
|
||||
import ItemDragPlaceholder from './ItemDragPlaceholder';
|
||||
import ItemDragRelease from './ItemDragRelease';
|
||||
import ItemLayout from './ItemLayout';
|
||||
import ItemMigrate from './ItemMigrate';
|
||||
import ItemVisibility from './ItemVisibility';
|
||||
import Emitter from '../Emitter/Emitter';
|
||||
|
||||
import addClass from '../utils/addClass';
|
||||
import createUid from '../utils/createUid';
|
||||
import getStyle from '../utils/getStyle';
|
||||
import getStyleAsFloat from '../utils/getStyleAsFloat';
|
||||
import getTranslateString from '../utils/getTranslateString';
|
||||
import removeClass from '../utils/removeClass';
|
||||
import transformProp from '../utils/transformProp';
|
||||
|
||||
/**
|
||||
* Creates a new Item instance for a Grid instance.
|
||||
*
|
||||
* @class
|
||||
* @param {Grid} grid
|
||||
* @param {HTMLElement} element
|
||||
* @param {Boolean} [isActive]
|
||||
*/
|
||||
function Item(grid, element, isActive) {
|
||||
var settings = grid._settings;
|
||||
|
||||
// Store item/element pair to a map (for faster item querying by element).
|
||||
if (ITEM_ELEMENT_MAP) {
|
||||
if (ITEM_ELEMENT_MAP.has(element)) {
|
||||
throw new Error('You can only create one Muuri Item per element!');
|
||||
} else {
|
||||
ITEM_ELEMENT_MAP.set(element, this);
|
||||
}
|
||||
}
|
||||
|
||||
this._id = createUid();
|
||||
this._gridId = grid._id;
|
||||
this._element = element;
|
||||
this._isDestroyed = false;
|
||||
this._left = 0;
|
||||
this._top = 0;
|
||||
this._width = 0;
|
||||
this._height = 0;
|
||||
this._marginLeft = 0;
|
||||
this._marginRight = 0;
|
||||
this._marginTop = 0;
|
||||
this._marginBottom = 0;
|
||||
this._tX = undefined;
|
||||
this._tY = undefined;
|
||||
this._sortData = null;
|
||||
this._emitter = new Emitter();
|
||||
|
||||
// If the provided item element is not a direct child of the grid container
|
||||
// element, append it to the grid container. Note, we are indeed reading the
|
||||
// DOM here but it's a property that does not cause reflowing.
|
||||
if (element.parentNode !== grid._element) {
|
||||
grid._element.appendChild(element);
|
||||
}
|
||||
|
||||
// Set item class.
|
||||
addClass(element, settings.itemClass);
|
||||
|
||||
// If isActive is not defined, let's try to auto-detect it. Note, we are
|
||||
// indeed reading the DOM here but it's a property that does not cause
|
||||
// reflowing.
|
||||
if (typeof isActive !== 'boolean') {
|
||||
isActive = getStyle(element, 'display') !== 'none';
|
||||
}
|
||||
|
||||
// Set up active state (defines if the item is considered part of the layout
|
||||
// or not).
|
||||
this._isActive = isActive;
|
||||
|
||||
// Setup visibility handler.
|
||||
this._visibility = new ItemVisibility(this);
|
||||
|
||||
// Set up layout handler.
|
||||
this._layout = new ItemLayout(this);
|
||||
|
||||
// Set up migration handler data.
|
||||
this._migrate = new ItemMigrate(this);
|
||||
|
||||
// Set up drag handler.
|
||||
this._drag = settings.dragEnabled ? new ItemDrag(this) : null;
|
||||
|
||||
// Set up release handler. Note that although this is fully linked to dragging
|
||||
// this still needs to be always instantiated to handle migration scenarios
|
||||
// correctly.
|
||||
this._dragRelease = new ItemDragRelease(this);
|
||||
|
||||
// Set up drag placeholder handler. Note that although this is fully linked to
|
||||
// dragging this still needs to be always instantiated to handle migration
|
||||
// scenarios correctly.
|
||||
this._dragPlaceholder = new ItemDragPlaceholder(this);
|
||||
|
||||
// Note! You must call the following methods before you start using the
|
||||
// instance. They are deliberately not called in the end as it would cause
|
||||
// potentially a massive amount of reflows if multiple items were instantiated
|
||||
// in a loop.
|
||||
// this._refreshDimensions();
|
||||
// this._refreshSortData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Public prototype methods
|
||||
* ************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the instance grid reference.
|
||||
*
|
||||
* @public
|
||||
* @returns {Grid}
|
||||
*/
|
||||
Item.prototype.getGrid = function () {
|
||||
return GRID_INSTANCES[this._gridId];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the instance element.
|
||||
*
|
||||
* @public
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
Item.prototype.getElement = function () {
|
||||
return this._element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get instance element's cached width.
|
||||
*
|
||||
* @public
|
||||
* @returns {Number}
|
||||
*/
|
||||
Item.prototype.getWidth = function () {
|
||||
return this._width;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get instance element's cached height.
|
||||
*
|
||||
* @public
|
||||
* @returns {Number}
|
||||
*/
|
||||
Item.prototype.getHeight = function () {
|
||||
return this._height;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get instance element's cached margins.
|
||||
*
|
||||
* @public
|
||||
* @returns {Object}
|
||||
* - The returned object contains left, right, top and bottom properties
|
||||
* which indicate the item element's cached margins.
|
||||
*/
|
||||
Item.prototype.getMargin = function () {
|
||||
return {
|
||||
left: this._marginLeft,
|
||||
right: this._marginRight,
|
||||
top: this._marginTop,
|
||||
bottom: this._marginBottom,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get instance element's cached position.
|
||||
*
|
||||
* @public
|
||||
* @returns {Object}
|
||||
* - The returned object contains left and top properties which indicate the
|
||||
* item element's cached position in the grid.
|
||||
*/
|
||||
Item.prototype.getPosition = function () {
|
||||
return {
|
||||
left: this._left,
|
||||
top: this._top,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the item active?
|
||||
*
|
||||
* @public
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Item.prototype.isActive = function () {
|
||||
return this._isActive;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the item visible?
|
||||
*
|
||||
* @public
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Item.prototype.isVisible = function () {
|
||||
return !!this._visibility && !this._visibility._isHidden;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the item being animated to visible?
|
||||
*
|
||||
* @public
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Item.prototype.isShowing = function () {
|
||||
return !!(this._visibility && this._visibility._isShowing);
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the item being animated to hidden?
|
||||
*
|
||||
* @public
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Item.prototype.isHiding = function () {
|
||||
return !!(this._visibility && this._visibility._isHiding);
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the item positioning?
|
||||
*
|
||||
* @public
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Item.prototype.isPositioning = function () {
|
||||
return !!(this._layout && this._layout._isActive);
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the item being dragged (or queued for dragging)?
|
||||
*
|
||||
* @public
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Item.prototype.isDragging = function () {
|
||||
return !!(this._drag && this._drag._isActive);
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the item being released?
|
||||
*
|
||||
* @public
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Item.prototype.isReleasing = function () {
|
||||
return !!(this._dragRelease && this._dragRelease._isActive);
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the item destroyed?
|
||||
*
|
||||
* @public
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Item.prototype.isDestroyed = function () {
|
||||
return this._isDestroyed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Private prototype methods
|
||||
* *************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Recalculate item's dimensions.
|
||||
*
|
||||
* @private
|
||||
* @param {Boolean} [force=false]
|
||||
*/
|
||||
Item.prototype._refreshDimensions = function (force) {
|
||||
if (this._isDestroyed) return;
|
||||
if (force !== true && this._visibility._isHidden) return;
|
||||
|
||||
var element = this._element;
|
||||
var dragPlaceholder = this._dragPlaceholder;
|
||||
var rect = element.getBoundingClientRect();
|
||||
|
||||
// Calculate width and height.
|
||||
this._width = rect.width;
|
||||
this._height = rect.height;
|
||||
|
||||
// Calculate margins (ignore negative margins).
|
||||
this._marginLeft = Math.max(0, getStyleAsFloat(element, 'margin-left'));
|
||||
this._marginRight = Math.max(0, getStyleAsFloat(element, 'margin-right'));
|
||||
this._marginTop = Math.max(0, getStyleAsFloat(element, 'margin-top'));
|
||||
this._marginBottom = Math.max(0, getStyleAsFloat(element, 'margin-bottom'));
|
||||
|
||||
// Keep drag placeholder's dimensions synced with the item's.
|
||||
if (dragPlaceholder) dragPlaceholder.updateDimensions();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch and store item's sort data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Item.prototype._refreshSortData = function () {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var data = (this._sortData = {});
|
||||
var getters = this.getGrid()._settings.sortData;
|
||||
var prop;
|
||||
|
||||
for (prop in getters) {
|
||||
data[prop] = getters[prop](this, this._element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add item to layout.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Item.prototype._addToLayout = function (left, top) {
|
||||
if (this._isActive === true) return;
|
||||
this._isActive = true;
|
||||
this._left = left || 0;
|
||||
this._top = top || 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove item from layout.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Item.prototype._removeFromLayout = function () {
|
||||
if (this._isActive === false) return;
|
||||
this._isActive = false;
|
||||
this._left = 0;
|
||||
this._top = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the layout procedure can be skipped for the item.
|
||||
*
|
||||
* @private
|
||||
* @param {Number} left
|
||||
* @param {Number} top
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Item.prototype._canSkipLayout = function (left, top) {
|
||||
return (
|
||||
this._left === left &&
|
||||
this._top === top &&
|
||||
!this._migrate._isActive &&
|
||||
!this._layout._skipNextAnimation &&
|
||||
!this._dragRelease.isJustReleased()
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the provided left and top arguments as the item element's translate
|
||||
* values in the DOM. This method keeps track of the currently applied
|
||||
* translate values and skips the update operation if the provided values are
|
||||
* identical to the currently applied values. Returns `false` if there was no
|
||||
* need for update and `true` if the translate value was updated.
|
||||
*
|
||||
* @private
|
||||
* @param {Number} left
|
||||
* @param {Number} top
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Item.prototype._setTranslate = function (left, top) {
|
||||
if (this._tX === left && this._tY === top) return false;
|
||||
this._tX = left;
|
||||
this._tY = top;
|
||||
this._element.style[transformProp] = getTranslateString(left, top);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy item instance.
|
||||
*
|
||||
* @private
|
||||
* @param {Boolean} [removeElement=false]
|
||||
*/
|
||||
Item.prototype._destroy = function (removeElement) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var element = this._element;
|
||||
var grid = this.getGrid();
|
||||
var settings = grid._settings;
|
||||
|
||||
// Destroy handlers.
|
||||
this._dragPlaceholder.destroy();
|
||||
this._dragRelease.destroy();
|
||||
this._migrate.destroy();
|
||||
this._layout.destroy();
|
||||
this._visibility.destroy();
|
||||
if (this._drag) this._drag.destroy();
|
||||
|
||||
// Destroy emitter.
|
||||
this._emitter.destroy();
|
||||
|
||||
// Remove item class.
|
||||
removeClass(element, settings.itemClass);
|
||||
|
||||
// Remove element from DOM.
|
||||
if (removeElement) element.parentNode.removeChild(element);
|
||||
|
||||
// Remove item/element pair from map.
|
||||
if (ITEM_ELEMENT_MAP) ITEM_ELEMENT_MAP.delete(element);
|
||||
|
||||
// Reset state.
|
||||
this._isActive = false;
|
||||
this._isDestroyed = true;
|
||||
};
|
||||
|
||||
export default Item;
|
||||
1741
app/src/vendor/muuri-src/Item/ItemDrag.js
vendored
Normal file
1741
app/src/vendor/muuri-src/Item/ItemDrag.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
442
app/src/vendor/muuri-src/Item/ItemDragPlaceholder.js
vendored
Normal file
442
app/src/vendor/muuri-src/Item/ItemDragPlaceholder.js
vendored
Normal file
@ -0,0 +1,442 @@
|
||||
/**
|
||||
* Copyright (c) 2018-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import {
|
||||
addPlaceholderLayoutTick,
|
||||
cancelPlaceholderLayoutTick,
|
||||
addPlaceholderResizeTick,
|
||||
cancelPlaceholderResizeTick,
|
||||
} from '../ticker';
|
||||
|
||||
import {
|
||||
EVENT_BEFORE_SEND,
|
||||
EVENT_DRAG_RELEASE_END,
|
||||
EVENT_LAYOUT_START,
|
||||
EVENT_HIDE_START,
|
||||
} from '../constants';
|
||||
|
||||
import Animator from '../Animator/Animator';
|
||||
|
||||
import addClass from '../utils/addClass';
|
||||
import getTranslateString from '../utils/getTranslateString';
|
||||
import getTranslate from '../utils/getTranslate';
|
||||
import isFunction from '../utils/isFunction';
|
||||
import setStyles from '../utils/setStyles';
|
||||
import removeClass from '../utils/removeClass';
|
||||
import transformProp from '../utils/transformProp';
|
||||
|
||||
/**
|
||||
* Drag placeholder.
|
||||
*
|
||||
* @class
|
||||
* @param {Item} item
|
||||
*/
|
||||
function ItemDragPlaceholder(item) {
|
||||
this._item = item;
|
||||
this._animation = new Animator();
|
||||
this._element = null;
|
||||
this._className = '';
|
||||
this._didMigrate = false;
|
||||
this._resetAfterLayout = false;
|
||||
this._left = 0;
|
||||
this._top = 0;
|
||||
this._transX = 0;
|
||||
this._transY = 0;
|
||||
this._nextTransX = 0;
|
||||
this._nextTransY = 0;
|
||||
|
||||
// Bind animation handlers.
|
||||
this._setupAnimation = this._setupAnimation.bind(this);
|
||||
this._startAnimation = this._startAnimation.bind(this);
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
|
||||
// Bind event handlers.
|
||||
this._onLayoutStart = this._onLayoutStart.bind(this);
|
||||
this._onLayoutEnd = this._onLayoutEnd.bind(this);
|
||||
this._onReleaseEnd = this._onReleaseEnd.bind(this);
|
||||
this._onMigrate = this._onMigrate.bind(this);
|
||||
this._onHide = this._onHide.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private prototype methods
|
||||
* *************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update placeholder's dimensions to match the item's dimensions.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemDragPlaceholder.prototype._updateDimensions = function () {
|
||||
if (!this.isActive()) return;
|
||||
setStyles(this._element, {
|
||||
width: this._item._width + 'px',
|
||||
height: this._item._height + 'px',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Move placeholder to a new position.
|
||||
*
|
||||
* @private
|
||||
* @param {Item[]} items
|
||||
* @param {Boolean} isInstant
|
||||
*/
|
||||
ItemDragPlaceholder.prototype._onLayoutStart = function (items, isInstant) {
|
||||
var item = this._item;
|
||||
|
||||
// If the item is not part of the layout anymore reset placeholder.
|
||||
if (items.indexOf(item) === -1) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
var nextLeft = item._left;
|
||||
var nextTop = item._top;
|
||||
var currentLeft = this._left;
|
||||
var currentTop = this._top;
|
||||
|
||||
// Keep track of item layout position.
|
||||
this._left = nextLeft;
|
||||
this._top = nextTop;
|
||||
|
||||
// If item's position did not change, and the item did not migrate and the
|
||||
// layout is not instant and we can safely skip layout.
|
||||
if (!isInstant && !this._didMigrate && currentLeft === nextLeft && currentTop === nextTop) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Slots data is calculated with item margins added to them so we need to add
|
||||
// item's left and top margin to the slot data to get the placeholder's
|
||||
// next position.
|
||||
var nextX = nextLeft + item._marginLeft;
|
||||
var nextY = nextTop + item._marginTop;
|
||||
|
||||
// Just snap to new position without any animations if no animation is
|
||||
// required or if placeholder moves between grids.
|
||||
var grid = item.getGrid();
|
||||
var animEnabled = !isInstant && grid._settings.layoutDuration > 0;
|
||||
if (!animEnabled || this._didMigrate) {
|
||||
// Cancel potential (queued) layout tick.
|
||||
cancelPlaceholderLayoutTick(item._id);
|
||||
|
||||
// Snap placeholder to correct position.
|
||||
this._element.style[transformProp] = getTranslateString(nextX, nextY);
|
||||
this._animation.stop();
|
||||
|
||||
// Move placeholder inside correct container after migration.
|
||||
if (this._didMigrate) {
|
||||
grid.getElement().appendChild(this._element);
|
||||
this._didMigrate = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the placeholder's layout animation in the next tick. We do this to
|
||||
// avoid layout thrashing.
|
||||
this._nextTransX = nextX;
|
||||
this._nextTransY = nextY;
|
||||
addPlaceholderLayoutTick(item._id, this._setupAnimation, this._startAnimation);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare placeholder for layout animation.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemDragPlaceholder.prototype._setupAnimation = function () {
|
||||
if (!this.isActive()) return;
|
||||
|
||||
var translate = getTranslate(this._element);
|
||||
this._transX = translate.x;
|
||||
this._transY = translate.y;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start layout animation.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemDragPlaceholder.prototype._startAnimation = function () {
|
||||
if (!this.isActive()) return;
|
||||
|
||||
var animation = this._animation;
|
||||
var currentX = this._transX;
|
||||
var currentY = this._transY;
|
||||
var nextX = this._nextTransX;
|
||||
var nextY = this._nextTransY;
|
||||
|
||||
// If placeholder is already in correct position let's just stop animation
|
||||
// and be done with it.
|
||||
if (currentX === nextX && currentY === nextY) {
|
||||
if (animation.isAnimating()) {
|
||||
this._element.style[transformProp] = getTranslateString(nextX, nextY);
|
||||
animation.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise let's start the animation.
|
||||
var settings = this._item.getGrid()._settings;
|
||||
var currentStyles = {};
|
||||
var targetStyles = {};
|
||||
currentStyles[transformProp] = getTranslateString(currentX, currentY);
|
||||
targetStyles[transformProp] = getTranslateString(nextX, nextY);
|
||||
animation.start(currentStyles, targetStyles, {
|
||||
duration: settings.layoutDuration,
|
||||
easing: settings.layoutEasing,
|
||||
onFinish: this._onLayoutEnd,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Layout end handler.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemDragPlaceholder.prototype._onLayoutEnd = function () {
|
||||
if (this._resetAfterLayout) {
|
||||
this.reset();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Drag end handler. This handler is called when dragReleaseEnd event is
|
||||
* emitted and receives the event data as it's argument.
|
||||
*
|
||||
* @private
|
||||
* @param {Item} item
|
||||
*/
|
||||
ItemDragPlaceholder.prototype._onReleaseEnd = function (item) {
|
||||
if (item._id === this._item._id) {
|
||||
// If the placeholder is not animating anymore we can safely reset it.
|
||||
if (!this._animation.isAnimating()) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the placeholder item is still animating here, let's wait for it to
|
||||
// finish it's animation.
|
||||
this._resetAfterLayout = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Migration start handler. This handler is called when beforeSend event is
|
||||
* emitted and receives the event data as it's argument.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} data
|
||||
* @param {Item} data.item
|
||||
* @param {Grid} data.fromGrid
|
||||
* @param {Number} data.fromIndex
|
||||
* @param {Grid} data.toGrid
|
||||
* @param {Number} data.toIndex
|
||||
*/
|
||||
ItemDragPlaceholder.prototype._onMigrate = function (data) {
|
||||
// Make sure we have a matching item.
|
||||
if (data.item !== this._item) return;
|
||||
|
||||
var grid = this._item.getGrid();
|
||||
var nextGrid = data.toGrid;
|
||||
|
||||
// Unbind listeners from current grid.
|
||||
grid.off(EVENT_DRAG_RELEASE_END, this._onReleaseEnd);
|
||||
grid.off(EVENT_LAYOUT_START, this._onLayoutStart);
|
||||
grid.off(EVENT_BEFORE_SEND, this._onMigrate);
|
||||
grid.off(EVENT_HIDE_START, this._onHide);
|
||||
|
||||
// Bind listeners to the next grid.
|
||||
nextGrid.on(EVENT_DRAG_RELEASE_END, this._onReleaseEnd);
|
||||
nextGrid.on(EVENT_LAYOUT_START, this._onLayoutStart);
|
||||
nextGrid.on(EVENT_BEFORE_SEND, this._onMigrate);
|
||||
nextGrid.on(EVENT_HIDE_START, this._onHide);
|
||||
|
||||
// Mark the item as migrated.
|
||||
this._didMigrate = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset placeholder if the associated item is hidden.
|
||||
*
|
||||
* @private
|
||||
* @param {Item[]} items
|
||||
*/
|
||||
ItemDragPlaceholder.prototype._onHide = function (items) {
|
||||
if (items.indexOf(this._item) > -1) this.reset();
|
||||
};
|
||||
|
||||
/**
|
||||
* Public prototype methods
|
||||
* ************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create placeholder. Note that this method only writes to DOM and does not
|
||||
* read anything from DOM so it should not cause any additional layout
|
||||
* thrashing when it's called at the end of the drag start procedure.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
ItemDragPlaceholder.prototype.create = function () {
|
||||
// If we already have placeholder set up we can skip the initiation logic.
|
||||
if (this.isActive()) {
|
||||
this._resetAfterLayout = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var item = this._item;
|
||||
var grid = item.getGrid();
|
||||
var settings = grid._settings;
|
||||
var animation = this._animation;
|
||||
|
||||
// Keep track of layout position.
|
||||
this._left = item._left;
|
||||
this._top = item._top;
|
||||
|
||||
// Create placeholder element.
|
||||
var element;
|
||||
if (isFunction(settings.dragPlaceholder.createElement)) {
|
||||
element = settings.dragPlaceholder.createElement(item);
|
||||
} else {
|
||||
element = document.createElement('div');
|
||||
}
|
||||
this._element = element;
|
||||
|
||||
// Update element to animation instance.
|
||||
animation._element = element;
|
||||
|
||||
// Add placeholder class to the placeholder element.
|
||||
this._className = settings.itemPlaceholderClass || '';
|
||||
if (this._className) {
|
||||
addClass(element, this._className);
|
||||
}
|
||||
|
||||
// Set initial styles.
|
||||
setStyles(element, {
|
||||
position: 'absolute',
|
||||
left: '0px',
|
||||
top: '0px',
|
||||
width: item._width + 'px',
|
||||
height: item._height + 'px',
|
||||
});
|
||||
|
||||
// Set initial position.
|
||||
element.style[transformProp] = getTranslateString(
|
||||
item._left + item._marginLeft,
|
||||
item._top + item._marginTop
|
||||
);
|
||||
|
||||
// Bind event listeners.
|
||||
grid.on(EVENT_LAYOUT_START, this._onLayoutStart);
|
||||
grid.on(EVENT_DRAG_RELEASE_END, this._onReleaseEnd);
|
||||
grid.on(EVENT_BEFORE_SEND, this._onMigrate);
|
||||
grid.on(EVENT_HIDE_START, this._onHide);
|
||||
|
||||
// onCreate hook.
|
||||
if (isFunction(settings.dragPlaceholder.onCreate)) {
|
||||
settings.dragPlaceholder.onCreate(item, element);
|
||||
}
|
||||
|
||||
// Insert the placeholder element to the grid.
|
||||
grid.getElement().appendChild(element);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset placeholder data.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
ItemDragPlaceholder.prototype.reset = function () {
|
||||
if (!this.isActive()) return;
|
||||
|
||||
var element = this._element;
|
||||
var item = this._item;
|
||||
var grid = item.getGrid();
|
||||
var settings = grid._settings;
|
||||
var animation = this._animation;
|
||||
|
||||
// Reset flag.
|
||||
this._resetAfterLayout = false;
|
||||
|
||||
// Cancel potential (queued) layout tick.
|
||||
cancelPlaceholderLayoutTick(item._id);
|
||||
cancelPlaceholderResizeTick(item._id);
|
||||
|
||||
// Reset animation instance.
|
||||
animation.stop();
|
||||
animation._element = null;
|
||||
|
||||
// Unbind event listeners.
|
||||
grid.off(EVENT_DRAG_RELEASE_END, this._onReleaseEnd);
|
||||
grid.off(EVENT_LAYOUT_START, this._onLayoutStart);
|
||||
grid.off(EVENT_BEFORE_SEND, this._onMigrate);
|
||||
grid.off(EVENT_HIDE_START, this._onHide);
|
||||
|
||||
// Remove placeholder class from the placeholder element.
|
||||
if (this._className) {
|
||||
removeClass(element, this._className);
|
||||
this._className = '';
|
||||
}
|
||||
|
||||
// Remove element.
|
||||
element.parentNode.removeChild(element);
|
||||
this._element = null;
|
||||
|
||||
// onRemove hook. Note that here we use the current grid's onRemove callback
|
||||
// so if the item has migrated during drag the onRemove method will not be
|
||||
// the originating grid's method.
|
||||
if (isFunction(settings.dragPlaceholder.onRemove)) {
|
||||
settings.dragPlaceholder.onRemove(item, element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if placeholder is currently active (visible).
|
||||
*
|
||||
* @public
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
ItemDragPlaceholder.prototype.isActive = function () {
|
||||
return !!this._element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get placeholder element.
|
||||
*
|
||||
* @public
|
||||
* @returns {?HTMLElement}
|
||||
*/
|
||||
ItemDragPlaceholder.prototype.getElement = function () {
|
||||
return this._element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update placeholder's dimensions to match the item's dimensions. Note that
|
||||
* the updating is done asynchronously in the next tick to avoid layout
|
||||
* thrashing.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
ItemDragPlaceholder.prototype.updateDimensions = function () {
|
||||
if (!this.isActive()) return;
|
||||
addPlaceholderResizeTick(this._item._id, this._updateDimensions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy placeholder instance.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
ItemDragPlaceholder.prototype.destroy = function () {
|
||||
this.reset();
|
||||
this._animation.destroy();
|
||||
this._item = this._animation = null;
|
||||
};
|
||||
|
||||
export default ItemDragPlaceholder;
|
||||
183
app/src/vendor/muuri-src/Item/ItemDragRelease.js
vendored
Normal file
183
app/src/vendor/muuri-src/Item/ItemDragRelease.js
vendored
Normal file
@ -0,0 +1,183 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { EVENT_DRAG_RELEASE_START, EVENT_DRAG_RELEASE_END } from '../constants';
|
||||
|
||||
import addClass from '../utils/addClass';
|
||||
import getTranslate from '../utils/getTranslate';
|
||||
import removeClass from '../utils/removeClass';
|
||||
|
||||
/**
|
||||
* The release process handler constructor. Although this might seem as proper
|
||||
* fit for the drag process this needs to be separated into it's own logic
|
||||
* because there might be a scenario where drag is disabled, but the release
|
||||
* process still needs to be implemented (dragging from a grid to another).
|
||||
*
|
||||
* @class
|
||||
* @param {Item} item
|
||||
*/
|
||||
function ItemDragRelease(item) {
|
||||
this._item = item;
|
||||
this._isActive = false;
|
||||
this._isDestroyed = false;
|
||||
this._isPositioningStarted = false;
|
||||
this._containerDiffX = 0;
|
||||
this._containerDiffY = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public prototype methods
|
||||
* ************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Start the release process of an item.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
ItemDragRelease.prototype.start = function () {
|
||||
if (this._isDestroyed || this._isActive) return;
|
||||
|
||||
var item = this._item;
|
||||
var grid = item.getGrid();
|
||||
var settings = grid._settings;
|
||||
|
||||
this._isActive = true;
|
||||
addClass(item._element, settings.itemReleasingClass);
|
||||
if (!settings.dragRelease.useDragContainer) {
|
||||
this._placeToGrid();
|
||||
}
|
||||
grid._emit(EVENT_DRAG_RELEASE_START, item);
|
||||
|
||||
// Let's start layout manually _only_ if there is no unfinished layout in
|
||||
// about to finish.
|
||||
if (!grid._nextLayoutData) item._layout.start(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* End the release process of an item. This method can be used to abort an
|
||||
* ongoing release process (animation) or finish the release process.
|
||||
*
|
||||
* @public
|
||||
* @param {Boolean} [abort=false]
|
||||
* - Should the release be aborted? When true, the release end event won't be
|
||||
* emitted. Set to true only when you need to abort the release process
|
||||
* while the item is animating to it's position.
|
||||
* @param {Number} [left]
|
||||
* - The element's current translateX value (optional).
|
||||
* @param {Number} [top]
|
||||
* - The element's current translateY value (optional).
|
||||
*/
|
||||
ItemDragRelease.prototype.stop = function (abort, left, top) {
|
||||
if (this._isDestroyed || !this._isActive) return;
|
||||
|
||||
var item = this._item;
|
||||
var grid = item.getGrid();
|
||||
|
||||
if (!abort && (left === undefined || top === undefined)) {
|
||||
left = item._left;
|
||||
top = item._top;
|
||||
}
|
||||
|
||||
var didReparent = this._placeToGrid(left, top);
|
||||
this._reset(didReparent);
|
||||
|
||||
if (!abort) grid._emit(EVENT_DRAG_RELEASE_END, item);
|
||||
};
|
||||
|
||||
ItemDragRelease.prototype.isJustReleased = function () {
|
||||
return this._isActive && this._isPositioningStarted === false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy instance.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
ItemDragRelease.prototype.destroy = function () {
|
||||
if (this._isDestroyed) return;
|
||||
this.stop(true);
|
||||
this._item = null;
|
||||
this._isDestroyed = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Private prototype methods
|
||||
* *************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Move the element back to the grid container element if it does not exist
|
||||
* there already.
|
||||
*
|
||||
* @private
|
||||
* @param {Number} [left]
|
||||
* - The element's current translateX value (optional).
|
||||
* @param {Number} [top]
|
||||
* - The element's current translateY value (optional).
|
||||
* @returns {Boolean}
|
||||
* - Returns `true` if the element was reparented.
|
||||
*/
|
||||
ItemDragRelease.prototype._placeToGrid = function (left, top) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var item = this._item;
|
||||
var element = item._element;
|
||||
var container = item.getGrid()._element;
|
||||
var didReparent = false;
|
||||
|
||||
if (element && element.dataset && element.dataset.mergeHidden === '1') {
|
||||
if (element.parentNode) {
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (element.parentNode !== container) {
|
||||
if (left === undefined || top === undefined) {
|
||||
var translate = getTranslate(element);
|
||||
left = translate.x - this._containerDiffX;
|
||||
top = translate.y - this._containerDiffY;
|
||||
}
|
||||
|
||||
container.appendChild(element);
|
||||
item._setTranslate(left, top);
|
||||
didReparent = true;
|
||||
}
|
||||
|
||||
this._containerDiffX = 0;
|
||||
this._containerDiffY = 0;
|
||||
|
||||
return didReparent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset data and remove releasing class.
|
||||
*
|
||||
* @private
|
||||
* @param {Boolean} [needsReflow]
|
||||
*/
|
||||
ItemDragRelease.prototype._reset = function (needsReflow) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var item = this._item;
|
||||
var releasingClass = item.getGrid()._settings.itemReleasingClass;
|
||||
|
||||
this._isActive = false;
|
||||
this._isPositioningStarted = false;
|
||||
this._containerDiffX = 0;
|
||||
this._containerDiffY = 0;
|
||||
|
||||
// If the element was just reparented we need to do a forced reflow to remove
|
||||
// the class gracefully.
|
||||
if (releasingClass) {
|
||||
// eslint-disable-next-line
|
||||
if (needsReflow) item._element.clientWidth;
|
||||
removeClass(item._element, releasingClass);
|
||||
}
|
||||
};
|
||||
|
||||
export default ItemDragRelease;
|
||||
315
app/src/vendor/muuri-src/Item/ItemLayout.js
vendored
Normal file
315
app/src/vendor/muuri-src/Item/ItemLayout.js
vendored
Normal file
@ -0,0 +1,315 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { addLayoutTick, cancelLayoutTick } from '../ticker';
|
||||
|
||||
import Animator from '../Animator/Animator';
|
||||
|
||||
import addClass from '../utils/addClass';
|
||||
import getTranslate from '../utils/getTranslate';
|
||||
import getTranslateString from '../utils/getTranslateString';
|
||||
import isFunction from '../utils/isFunction';
|
||||
import removeClass from '../utils/removeClass';
|
||||
import transformProp from '../utils/transformProp';
|
||||
|
||||
var MIN_ANIMATION_DISTANCE = 2;
|
||||
|
||||
/**
|
||||
* Layout manager for Item instance, handles the positioning of an item.
|
||||
*
|
||||
* @class
|
||||
* @param {Item} item
|
||||
*/
|
||||
function ItemLayout(item) {
|
||||
var element = item._element;
|
||||
var elementStyle = element.style;
|
||||
|
||||
this._item = item;
|
||||
this._isActive = false;
|
||||
this._isDestroyed = false;
|
||||
this._isInterrupted = false;
|
||||
this._currentStyles = {};
|
||||
this._targetStyles = {};
|
||||
this._nextLeft = 0;
|
||||
this._nextTop = 0;
|
||||
this._offsetLeft = 0;
|
||||
this._offsetTop = 0;
|
||||
this._skipNextAnimation = false;
|
||||
this._animOptions = {
|
||||
onFinish: this._finish.bind(this),
|
||||
duration: 0,
|
||||
easing: 0,
|
||||
};
|
||||
|
||||
// Set element's initial position styles.
|
||||
elementStyle.left = '0px';
|
||||
elementStyle.top = '0px';
|
||||
item._setTranslate(0, 0);
|
||||
|
||||
this._animation = new Animator(element);
|
||||
this._queue = 'layout-' + item._id;
|
||||
|
||||
// Bind animation handlers and finish method.
|
||||
this._setupAnimation = this._setupAnimation.bind(this);
|
||||
this._startAnimation = this._startAnimation.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public prototype methods
|
||||
* ************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Start item layout based on it's current data.
|
||||
*
|
||||
* @public
|
||||
* @param {Boolean} instant
|
||||
* @param {Function} [onFinish]
|
||||
*/
|
||||
ItemLayout.prototype.start = function (instant, onFinish) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var item = this._item;
|
||||
var release = item._dragRelease;
|
||||
var gridSettings = item.getGrid()._settings;
|
||||
var isPositioning = this._isActive;
|
||||
var isJustReleased = release.isJustReleased();
|
||||
var animDuration = isJustReleased
|
||||
? gridSettings.dragRelease.duration
|
||||
: gridSettings.layoutDuration;
|
||||
var animEasing = isJustReleased ? gridSettings.dragRelease.easing : gridSettings.layoutEasing;
|
||||
var animEnabled = !instant && !this._skipNextAnimation && animDuration > 0;
|
||||
|
||||
// If the item is currently positioning cancel potential queued layout tick
|
||||
// and process current layout callback queue with interrupted flag on.
|
||||
if (isPositioning) {
|
||||
cancelLayoutTick(item._id);
|
||||
item._emitter.burst(this._queue, true, item);
|
||||
}
|
||||
|
||||
// Mark release positioning as started.
|
||||
if (isJustReleased) release._isPositioningStarted = true;
|
||||
|
||||
// Push the callback to the callback queue.
|
||||
if (isFunction(onFinish)) {
|
||||
item._emitter.on(this._queue, onFinish);
|
||||
}
|
||||
|
||||
// Reset animation skipping flag.
|
||||
this._skipNextAnimation = false;
|
||||
|
||||
// If no animations are needed, easy peasy!
|
||||
if (!animEnabled) {
|
||||
this._updateOffsets();
|
||||
item._setTranslate(this._nextLeft, this._nextTop);
|
||||
this._animation.stop();
|
||||
this._finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's make sure an ongoing animation's callback is cancelled before going
|
||||
// further. Without this there's a chance that the animation will finish
|
||||
// before the next tick and mess up our logic.
|
||||
if (this._animation.isAnimating()) {
|
||||
this._animation._animation.onfinish = null;
|
||||
}
|
||||
|
||||
// Kick off animation to be started in the next tick.
|
||||
this._isActive = true;
|
||||
this._animOptions.easing = animEasing;
|
||||
this._animOptions.duration = animDuration;
|
||||
this._isInterrupted = isPositioning;
|
||||
addLayoutTick(item._id, this._setupAnimation, this._startAnimation);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop item's position animation if it is currently animating.
|
||||
*
|
||||
* @public
|
||||
* @param {Boolean} processCallbackQueue
|
||||
* @param {Number} [left]
|
||||
* @param {Number} [top]
|
||||
*/
|
||||
ItemLayout.prototype.stop = function (processCallbackQueue, left, top) {
|
||||
if (this._isDestroyed || !this._isActive) return;
|
||||
|
||||
var item = this._item;
|
||||
|
||||
// Cancel animation init.
|
||||
cancelLayoutTick(item._id);
|
||||
|
||||
// Stop animation.
|
||||
if (this._animation.isAnimating()) {
|
||||
if (left === undefined || top === undefined) {
|
||||
var translate = getTranslate(item._element);
|
||||
left = translate.x;
|
||||
top = translate.y;
|
||||
}
|
||||
item._setTranslate(left, top);
|
||||
this._animation.stop();
|
||||
}
|
||||
|
||||
// Remove positioning class.
|
||||
removeClass(item._element, item.getGrid()._settings.itemPositioningClass);
|
||||
|
||||
// Reset active state.
|
||||
this._isActive = false;
|
||||
|
||||
// Process callback queue if needed.
|
||||
if (processCallbackQueue) {
|
||||
item._emitter.burst(this._queue, true, item);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the instance and stop current animation if it is running.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
ItemLayout.prototype.destroy = function () {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var elementStyle = this._item._element.style;
|
||||
|
||||
this.stop(true, 0, 0);
|
||||
this._item._emitter.clear(this._queue);
|
||||
this._animation.destroy();
|
||||
|
||||
elementStyle[transformProp] = '';
|
||||
elementStyle.left = '';
|
||||
elementStyle.top = '';
|
||||
|
||||
this._item = null;
|
||||
this._currentStyles = null;
|
||||
this._targetStyles = null;
|
||||
this._animOptions = null;
|
||||
this._isDestroyed = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Private prototype methods
|
||||
* *************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculate and update item's current layout offset data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemLayout.prototype._updateOffsets = function () {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var item = this._item;
|
||||
var migrate = item._migrate;
|
||||
var release = item._dragRelease;
|
||||
|
||||
this._offsetLeft = release._isActive
|
||||
? release._containerDiffX
|
||||
: migrate._isActive
|
||||
? migrate._containerDiffX
|
||||
: 0;
|
||||
|
||||
this._offsetTop = release._isActive
|
||||
? release._containerDiffY
|
||||
: migrate._isActive
|
||||
? migrate._containerDiffY
|
||||
: 0;
|
||||
|
||||
this._nextLeft = this._item._left + this._offsetLeft;
|
||||
this._nextTop = this._item._top + this._offsetTop;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish item layout procedure.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemLayout.prototype._finish = function () {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var item = this._item;
|
||||
var migrate = item._migrate;
|
||||
var release = item._dragRelease;
|
||||
|
||||
// Update internal translate values.
|
||||
item._tX = this._nextLeft;
|
||||
item._tY = this._nextTop;
|
||||
|
||||
// Mark the item as inactive and remove positioning classes.
|
||||
if (this._isActive) {
|
||||
this._isActive = false;
|
||||
removeClass(item._element, item.getGrid()._settings.itemPositioningClass);
|
||||
}
|
||||
|
||||
// Finish up release and migration.
|
||||
if (release._isActive) release.stop();
|
||||
if (migrate._isActive) migrate.stop();
|
||||
|
||||
// Process the callback queue.
|
||||
item._emitter.burst(this._queue, false, item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare item for layout animation.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemLayout.prototype._setupAnimation = function () {
|
||||
var item = this._item;
|
||||
if (item._tX === undefined || item._tY === undefined) {
|
||||
var translate = getTranslate(item._element);
|
||||
item._tX = translate.x;
|
||||
item._tY = translate.y;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start layout animation.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemLayout.prototype._startAnimation = function () {
|
||||
var item = this._item;
|
||||
var settings = item.getGrid()._settings;
|
||||
var isInstant = this._animOptions.duration <= 0;
|
||||
|
||||
// Let's update the offset data and target styles.
|
||||
this._updateOffsets();
|
||||
|
||||
var xDiff = Math.abs(item._left - (item._tX - this._offsetLeft));
|
||||
var yDiff = Math.abs(item._top - (item._tY - this._offsetTop));
|
||||
|
||||
// If there is no need for animation or if the item is already in correct
|
||||
// position (or near it) let's finish the process early.
|
||||
if (isInstant || (xDiff < MIN_ANIMATION_DISTANCE && yDiff < MIN_ANIMATION_DISTANCE)) {
|
||||
if (xDiff || yDiff || this._isInterrupted) {
|
||||
item._setTranslate(this._nextLeft, this._nextTop);
|
||||
}
|
||||
this._animation.stop();
|
||||
this._finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set item's positioning class if needed.
|
||||
if (!this._isInterrupted) {
|
||||
addClass(item._element, settings.itemPositioningClass);
|
||||
}
|
||||
|
||||
// Get current/next styles for animation.
|
||||
this._currentStyles[transformProp] = getTranslateString(item._tX, item._tY);
|
||||
this._targetStyles[transformProp] = getTranslateString(this._nextLeft, this._nextTop);
|
||||
|
||||
// Set internal translation values to undefined for the duration of the
|
||||
// animation since they will be changing on each animation frame for the
|
||||
// duration of the animation and tracking them would mean reading the DOM on
|
||||
// each frame, which is pretty darn expensive.
|
||||
item._tX = item._tY = undefined;
|
||||
|
||||
// Start animation.
|
||||
this._animation.start(this._currentStyles, this._targetStyles, this._animOptions);
|
||||
};
|
||||
|
||||
export default ItemLayout;
|
||||
287
app/src/vendor/muuri-src/Item/ItemMigrate.js
vendored
Normal file
287
app/src/vendor/muuri-src/Item/ItemMigrate.js
vendored
Normal file
@ -0,0 +1,287 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { EVENT_BEFORE_SEND, EVENT_BEFORE_RECEIVE, EVENT_SEND, EVENT_RECEIVE } from '../constants';
|
||||
|
||||
import ItemDrag from './ItemDrag';
|
||||
|
||||
import addClass from '../utils/addClass';
|
||||
import getOffsetDiff from '../utils/getOffsetDiff';
|
||||
import getTranslate from '../utils/getTranslate';
|
||||
import arrayInsert from '../utils/arrayInsert';
|
||||
import normalizeArrayIndex from '../utils/normalizeArrayIndex';
|
||||
import removeClass from '../utils/removeClass';
|
||||
|
||||
/**
|
||||
* The migrate process handler constructor.
|
||||
*
|
||||
* @class
|
||||
* @param {Item} item
|
||||
*/
|
||||
function ItemMigrate(item) {
|
||||
// Private props.
|
||||
this._item = item;
|
||||
this._isActive = false;
|
||||
this._isDestroyed = false;
|
||||
this._container = false;
|
||||
this._containerDiffX = 0;
|
||||
this._containerDiffY = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public prototype methods
|
||||
* ************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Start the migrate process of an item.
|
||||
*
|
||||
* @public
|
||||
* @param {Grid} targetGrid
|
||||
* @param {(HTMLElement|Number|Item)} position
|
||||
* @param {HTMLElement} [container]
|
||||
*/
|
||||
ItemMigrate.prototype.start = function (targetGrid, position, container) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var item = this._item;
|
||||
var element = item._element;
|
||||
var isActive = item.isActive();
|
||||
var isVisible = item.isVisible();
|
||||
var grid = item.getGrid();
|
||||
var settings = grid._settings;
|
||||
var targetSettings = targetGrid._settings;
|
||||
var targetElement = targetGrid._element;
|
||||
var targetItems = targetGrid._items;
|
||||
var currentIndex = grid._items.indexOf(item);
|
||||
var targetContainer = container || document.body;
|
||||
var targetIndex;
|
||||
var targetItem;
|
||||
var currentContainer;
|
||||
var offsetDiff;
|
||||
var containerDiff;
|
||||
var translate;
|
||||
var translateX;
|
||||
var translateY;
|
||||
var currentVisClass;
|
||||
var nextVisClass;
|
||||
|
||||
// Get target index.
|
||||
if (typeof position === 'number') {
|
||||
targetIndex = normalizeArrayIndex(targetItems, position, 1);
|
||||
} else {
|
||||
targetItem = targetGrid.getItem(position);
|
||||
if (!targetItem) return;
|
||||
targetIndex = targetItems.indexOf(targetItem);
|
||||
}
|
||||
|
||||
// Get current translateX and translateY values if needed.
|
||||
if (item.isPositioning() || this._isActive || item.isReleasing()) {
|
||||
translate = getTranslate(element);
|
||||
translateX = translate.x;
|
||||
translateY = translate.y;
|
||||
}
|
||||
|
||||
// Abort current positioning.
|
||||
if (item.isPositioning()) {
|
||||
item._layout.stop(true, translateX, translateY);
|
||||
}
|
||||
|
||||
// Abort current migration.
|
||||
if (this._isActive) {
|
||||
translateX -= this._containerDiffX;
|
||||
translateY -= this._containerDiffY;
|
||||
this.stop(true, translateX, translateY);
|
||||
}
|
||||
|
||||
// Abort current release.
|
||||
if (item.isReleasing()) {
|
||||
translateX -= item._dragRelease._containerDiffX;
|
||||
translateY -= item._dragRelease._containerDiffY;
|
||||
item._dragRelease.stop(true, translateX, translateY);
|
||||
}
|
||||
|
||||
// Stop current visibility animation.
|
||||
item._visibility.stop(true);
|
||||
|
||||
// Destroy current drag.
|
||||
if (item._drag) item._drag.destroy();
|
||||
|
||||
// Emit beforeSend event.
|
||||
if (grid._hasListeners(EVENT_BEFORE_SEND)) {
|
||||
grid._emit(EVENT_BEFORE_SEND, {
|
||||
item: item,
|
||||
fromGrid: grid,
|
||||
fromIndex: currentIndex,
|
||||
toGrid: targetGrid,
|
||||
toIndex: targetIndex,
|
||||
});
|
||||
}
|
||||
|
||||
// Emit beforeReceive event.
|
||||
if (targetGrid._hasListeners(EVENT_BEFORE_RECEIVE)) {
|
||||
targetGrid._emit(EVENT_BEFORE_RECEIVE, {
|
||||
item: item,
|
||||
fromGrid: grid,
|
||||
fromIndex: currentIndex,
|
||||
toGrid: targetGrid,
|
||||
toIndex: targetIndex,
|
||||
});
|
||||
}
|
||||
|
||||
// Update item class.
|
||||
if (settings.itemClass !== targetSettings.itemClass) {
|
||||
removeClass(element, settings.itemClass);
|
||||
addClass(element, targetSettings.itemClass);
|
||||
}
|
||||
|
||||
// Update visibility class.
|
||||
currentVisClass = isVisible ? settings.itemVisibleClass : settings.itemHiddenClass;
|
||||
nextVisClass = isVisible ? targetSettings.itemVisibleClass : targetSettings.itemHiddenClass;
|
||||
if (currentVisClass !== nextVisClass) {
|
||||
removeClass(element, currentVisClass);
|
||||
addClass(element, nextVisClass);
|
||||
}
|
||||
|
||||
// Move item instance from current grid to target grid.
|
||||
grid._items.splice(currentIndex, 1);
|
||||
arrayInsert(targetItems, item, targetIndex);
|
||||
|
||||
// Update item's grid id reference.
|
||||
item._gridId = targetGrid._id;
|
||||
|
||||
// If item is active we need to move the item inside the target container for
|
||||
// the duration of the (potential) animation if it's different than the
|
||||
// current container.
|
||||
if (isActive) {
|
||||
currentContainer = element.parentNode;
|
||||
if (targetContainer !== currentContainer) {
|
||||
targetContainer.appendChild(element);
|
||||
offsetDiff = getOffsetDiff(targetContainer, currentContainer, true);
|
||||
if (!translate) {
|
||||
translate = getTranslate(element);
|
||||
translateX = translate.x;
|
||||
translateY = translate.y;
|
||||
}
|
||||
item._setTranslate(translateX + offsetDiff.left, translateY + offsetDiff.top);
|
||||
}
|
||||
}
|
||||
// If item is not active let's just append it to the target grid's element.
|
||||
else {
|
||||
targetElement.appendChild(element);
|
||||
}
|
||||
|
||||
// Update child element's styles to reflect the current visibility state.
|
||||
item._visibility.setStyles(
|
||||
isVisible ? targetSettings.visibleStyles : targetSettings.hiddenStyles
|
||||
);
|
||||
|
||||
// Get offset diff for the migration data, if the item is active.
|
||||
if (isActive) {
|
||||
containerDiff = getOffsetDiff(targetContainer, targetElement, true);
|
||||
}
|
||||
|
||||
// Update item's cached dimensions.
|
||||
item._refreshDimensions();
|
||||
|
||||
// Reset item's sort data.
|
||||
item._sortData = null;
|
||||
|
||||
// Create new drag handler.
|
||||
item._drag = targetSettings.dragEnabled ? new ItemDrag(item) : null;
|
||||
|
||||
// Setup migration data.
|
||||
if (isActive) {
|
||||
this._isActive = true;
|
||||
this._container = targetContainer;
|
||||
this._containerDiffX = containerDiff.left;
|
||||
this._containerDiffY = containerDiff.top;
|
||||
} else {
|
||||
this._isActive = false;
|
||||
this._container = null;
|
||||
this._containerDiffX = 0;
|
||||
this._containerDiffY = 0;
|
||||
}
|
||||
|
||||
// Emit send event.
|
||||
if (grid._hasListeners(EVENT_SEND)) {
|
||||
grid._emit(EVENT_SEND, {
|
||||
item: item,
|
||||
fromGrid: grid,
|
||||
fromIndex: currentIndex,
|
||||
toGrid: targetGrid,
|
||||
toIndex: targetIndex,
|
||||
});
|
||||
}
|
||||
|
||||
// Emit receive event.
|
||||
if (targetGrid._hasListeners(EVENT_RECEIVE)) {
|
||||
targetGrid._emit(EVENT_RECEIVE, {
|
||||
item: item,
|
||||
fromGrid: grid,
|
||||
fromIndex: currentIndex,
|
||||
toGrid: targetGrid,
|
||||
toIndex: targetIndex,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* End the migrate process of an item. This method can be used to abort an
|
||||
* ongoing migrate process (animation) or finish the migrate process.
|
||||
*
|
||||
* @public
|
||||
* @param {Boolean} [abort=false]
|
||||
* - Should the migration be aborted?
|
||||
* @param {Number} [left]
|
||||
* - The element's current translateX value (optional).
|
||||
* @param {Number} [top]
|
||||
* - The element's current translateY value (optional).
|
||||
*/
|
||||
ItemMigrate.prototype.stop = function (abort, left, top) {
|
||||
if (this._isDestroyed || !this._isActive) return;
|
||||
|
||||
var item = this._item;
|
||||
var element = item._element;
|
||||
var grid = item.getGrid();
|
||||
var gridElement = grid._element;
|
||||
var translate;
|
||||
|
||||
if (this._container !== gridElement) {
|
||||
if (left === undefined || top === undefined) {
|
||||
if (abort) {
|
||||
translate = getTranslate(element);
|
||||
left = translate.x - this._containerDiffX;
|
||||
top = translate.y - this._containerDiffY;
|
||||
} else {
|
||||
left = item._left;
|
||||
top = item._top;
|
||||
}
|
||||
}
|
||||
|
||||
gridElement.appendChild(element);
|
||||
item._setTranslate(left, top);
|
||||
}
|
||||
|
||||
this._isActive = false;
|
||||
this._container = null;
|
||||
this._containerDiffX = 0;
|
||||
this._containerDiffY = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy instance.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
ItemMigrate.prototype.destroy = function () {
|
||||
if (this._isDestroyed) return;
|
||||
this.stop(true);
|
||||
this._item = null;
|
||||
this._isDestroyed = true;
|
||||
};
|
||||
|
||||
export default ItemMigrate;
|
||||
325
app/src/vendor/muuri-src/Item/ItemVisibility.js
vendored
Normal file
325
app/src/vendor/muuri-src/Item/ItemVisibility.js
vendored
Normal file
@ -0,0 +1,325 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { addVisibilityTick, cancelVisibilityTick } from '../ticker';
|
||||
|
||||
import Animator from '../Animator/Animator';
|
||||
|
||||
import addClass from '../utils/addClass';
|
||||
import getCurrentStyles from '../utils/getCurrentStyles';
|
||||
import isFunction from '../utils/isFunction';
|
||||
import removeClass from '../utils/removeClass';
|
||||
import setStyles from '../utils/setStyles';
|
||||
|
||||
/**
|
||||
* Visibility manager for Item instance, handles visibility of an item.
|
||||
*
|
||||
* @class
|
||||
* @param {Item} item
|
||||
*/
|
||||
function ItemVisibility(item) {
|
||||
var isActive = item._isActive;
|
||||
var element = item._element;
|
||||
var childElement = element.children[0];
|
||||
var settings = item.getGrid()._settings;
|
||||
|
||||
if (!childElement) {
|
||||
throw new Error('No valid child element found within item element.');
|
||||
}
|
||||
|
||||
this._item = item;
|
||||
this._isDestroyed = false;
|
||||
this._isHidden = !isActive;
|
||||
this._isHiding = false;
|
||||
this._isShowing = false;
|
||||
this._childElement = childElement;
|
||||
this._currentStyleProps = [];
|
||||
this._animation = new Animator(childElement);
|
||||
this._queue = 'visibility-' + item._id;
|
||||
this._finishShow = this._finishShow.bind(this);
|
||||
this._finishHide = this._finishHide.bind(this);
|
||||
|
||||
element.style.display = isActive ? '' : 'none';
|
||||
addClass(element, isActive ? settings.itemVisibleClass : settings.itemHiddenClass);
|
||||
this.setStyles(isActive ? settings.visibleStyles : settings.hiddenStyles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public prototype methods
|
||||
* ************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Show item.
|
||||
*
|
||||
* @public
|
||||
* @param {Boolean} instant
|
||||
* @param {Function} [onFinish]
|
||||
*/
|
||||
ItemVisibility.prototype.show = function (instant, onFinish) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var item = this._item;
|
||||
var element = item._element;
|
||||
var callback = isFunction(onFinish) ? onFinish : null;
|
||||
var grid = item.getGrid();
|
||||
var settings = grid._settings;
|
||||
|
||||
// If item is visible call the callback and be done with it.
|
||||
if (!this._isShowing && !this._isHidden) {
|
||||
callback && callback(false, item);
|
||||
return;
|
||||
}
|
||||
|
||||
// If item is showing and does not need to be shown instantly, let's just
|
||||
// push callback to the callback queue and be done with it.
|
||||
if (this._isShowing && !instant) {
|
||||
callback && item._emitter.on(this._queue, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the item is hiding or hidden process the current visibility callback
|
||||
// queue with the interrupted flag active, update classes and set display
|
||||
// to block if necessary.
|
||||
if (!this._isShowing) {
|
||||
item._emitter.burst(this._queue, true, item);
|
||||
removeClass(element, settings.itemHiddenClass);
|
||||
addClass(element, settings.itemVisibleClass);
|
||||
if (!this._isHiding) element.style.display = '';
|
||||
}
|
||||
|
||||
// Push callback to the callback queue.
|
||||
callback && item._emitter.on(this._queue, callback);
|
||||
|
||||
// Update visibility states.
|
||||
this._isShowing = true;
|
||||
this._isHiding = this._isHidden = false;
|
||||
|
||||
// Finally let's start show animation.
|
||||
this._startAnimation(true, instant, this._finishShow);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide item.
|
||||
*
|
||||
* @public
|
||||
* @param {Boolean} instant
|
||||
* @param {Function} [onFinish]
|
||||
*/
|
||||
ItemVisibility.prototype.hide = function (instant, onFinish) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var item = this._item;
|
||||
var element = item._element;
|
||||
var callback = isFunction(onFinish) ? onFinish : null;
|
||||
var grid = item.getGrid();
|
||||
var settings = grid._settings;
|
||||
|
||||
// If item is already hidden call the callback and be done with it.
|
||||
if (!this._isHiding && this._isHidden) {
|
||||
callback && callback(false, item);
|
||||
return;
|
||||
}
|
||||
|
||||
// If item is hiding and does not need to be hidden instantly, let's just
|
||||
// push callback to the callback queue and be done with it.
|
||||
if (this._isHiding && !instant) {
|
||||
callback && item._emitter.on(this._queue, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the item is showing or visible process the current visibility callback
|
||||
// queue with the interrupted flag active, update classes and set display
|
||||
// to block if necessary.
|
||||
if (!this._isHiding) {
|
||||
item._emitter.burst(this._queue, true, item);
|
||||
addClass(element, settings.itemHiddenClass);
|
||||
removeClass(element, settings.itemVisibleClass);
|
||||
}
|
||||
|
||||
// Push callback to the callback queue.
|
||||
callback && item._emitter.on(this._queue, callback);
|
||||
|
||||
// Update visibility states.
|
||||
this._isHidden = this._isHiding = true;
|
||||
this._isShowing = false;
|
||||
|
||||
// Finally let's start hide animation.
|
||||
this._startAnimation(false, instant, this._finishHide);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop current hiding/showing process.
|
||||
*
|
||||
* @public
|
||||
* @param {Boolean} processCallbackQueue
|
||||
*/
|
||||
ItemVisibility.prototype.stop = function (processCallbackQueue) {
|
||||
if (this._isDestroyed) return;
|
||||
if (!this._isHiding && !this._isShowing) return;
|
||||
|
||||
var item = this._item;
|
||||
|
||||
cancelVisibilityTick(item._id);
|
||||
this._animation.stop();
|
||||
if (processCallbackQueue) {
|
||||
item._emitter.burst(this._queue, true, item);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset all existing visibility styles and apply new visibility styles to the
|
||||
* visibility element. This method should be used to set styles when there is a
|
||||
* chance that the current style properties differ from the new ones (basically
|
||||
* on init and on migrations).
|
||||
*
|
||||
* @public
|
||||
* @param {Object} styles
|
||||
*/
|
||||
ItemVisibility.prototype.setStyles = function (styles) {
|
||||
var childElement = this._childElement;
|
||||
var currentStyleProps = this._currentStyleProps;
|
||||
this._removeCurrentStyles();
|
||||
for (var prop in styles) {
|
||||
currentStyleProps.push(prop);
|
||||
childElement.style[prop] = styles[prop];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the instance and stop current animation if it is running.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
ItemVisibility.prototype.destroy = function () {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var item = this._item;
|
||||
var element = item._element;
|
||||
var grid = item.getGrid();
|
||||
var settings = grid._settings;
|
||||
|
||||
this.stop(true);
|
||||
item._emitter.clear(this._queue);
|
||||
this._animation.destroy();
|
||||
this._removeCurrentStyles();
|
||||
removeClass(element, settings.itemVisibleClass);
|
||||
removeClass(element, settings.itemHiddenClass);
|
||||
element.style.display = '';
|
||||
|
||||
// Reset state.
|
||||
this._isHiding = this._isShowing = false;
|
||||
this._isDestroyed = this._isHidden = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Private prototype methods
|
||||
* *************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Start visibility animation.
|
||||
*
|
||||
* @private
|
||||
* @param {Boolean} toVisible
|
||||
* @param {Boolean} [instant]
|
||||
* @param {Function} [onFinish]
|
||||
*/
|
||||
ItemVisibility.prototype._startAnimation = function (toVisible, instant, onFinish) {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
var item = this._item;
|
||||
var animation = this._animation;
|
||||
var childElement = this._childElement;
|
||||
var settings = item.getGrid()._settings;
|
||||
var targetStyles = toVisible ? settings.visibleStyles : settings.hiddenStyles;
|
||||
var duration = toVisible ? settings.showDuration : settings.hideDuration;
|
||||
var easing = toVisible ? settings.showEasing : settings.hideEasing;
|
||||
var isInstant = instant || duration <= 0;
|
||||
var currentStyles;
|
||||
|
||||
// No target styles? Let's quit early.
|
||||
if (!targetStyles) {
|
||||
onFinish && onFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel queued visibility tick.
|
||||
cancelVisibilityTick(item._id);
|
||||
|
||||
// If we need to apply the styles instantly without animation.
|
||||
if (isInstant) {
|
||||
setStyles(childElement, targetStyles);
|
||||
animation.stop();
|
||||
onFinish && onFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's make sure an ongoing animation's callback is cancelled before going
|
||||
// further. Without this there's a chance that the animation will finish
|
||||
// before the next tick and mess up our logic.
|
||||
if (animation.isAnimating()) {
|
||||
animation._animation.onfinish = null;
|
||||
}
|
||||
|
||||
// Start the animation in the next tick (to avoid layout thrashing).
|
||||
addVisibilityTick(
|
||||
item._id,
|
||||
function () {
|
||||
currentStyles = getCurrentStyles(childElement, targetStyles);
|
||||
},
|
||||
function () {
|
||||
animation.start(currentStyles, targetStyles, {
|
||||
duration: duration,
|
||||
easing: easing,
|
||||
onFinish: onFinish,
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish show procedure.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemVisibility.prototype._finishShow = function () {
|
||||
if (this._isHidden) return;
|
||||
this._isShowing = false;
|
||||
this._item._emitter.burst(this._queue, false, this._item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish hide procedure.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemVisibility.prototype._finishHide = function () {
|
||||
if (!this._isHidden) return;
|
||||
var item = this._item;
|
||||
this._isHiding = false;
|
||||
item._layout.stop(true, 0, 0);
|
||||
item._element.style.display = 'none';
|
||||
item._emitter.burst(this._queue, false, item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove currently applied visibility related inline style properties.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ItemVisibility.prototype._removeCurrentStyles = function () {
|
||||
var childElement = this._childElement;
|
||||
var currentStyleProps = this._currentStyleProps;
|
||||
|
||||
for (var i = 0; i < currentStyleProps.length; i++) {
|
||||
childElement.style[currentStyleProps[i]] = '';
|
||||
}
|
||||
|
||||
currentStyleProps.length = 0;
|
||||
};
|
||||
|
||||
export default ItemVisibility;
|
||||
19
app/src/vendor/muuri-src/Packer/LICENSE.md
vendored
Normal file
19
app/src/vendor/muuri-src/Packer/LICENSE.md
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016, Niklas Rämö
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
283
app/src/vendor/muuri-src/Packer/Packer.js
vendored
Normal file
283
app/src/vendor/muuri-src/Packer/Packer.js
vendored
Normal file
@ -0,0 +1,283 @@
|
||||
/**
|
||||
* Muuri Packer
|
||||
* Copyright (c) 2016-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/Packer/LICENSE.md
|
||||
*/
|
||||
|
||||
import PackerProcessor, {
|
||||
createWorkerProcessors,
|
||||
destroyWorkerProcessors,
|
||||
isWorkerProcessorsSupported,
|
||||
} from './PackerProcessor';
|
||||
|
||||
export var FILL_GAPS = 1;
|
||||
export var HORIZONTAL = 2;
|
||||
export var ALIGN_RIGHT = 4;
|
||||
export var ALIGN_BOTTOM = 8;
|
||||
export var ROUNDING = 16;
|
||||
export var PACKET_INDEX_ID = 0;
|
||||
export var PACKET_INDEX_WIDTH = 1;
|
||||
export var PACKET_INDEX_HEIGHT = 2;
|
||||
export var PACKET_INDEX_OPTIONS = 3;
|
||||
export var PACKET_HEADER_SLOTS = 4;
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @param {Number} [numWorkers=0]
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.fillGaps=false]
|
||||
* @param {Boolean} [options.horizontal=false]
|
||||
* @param {Boolean} [options.alignRight=false]
|
||||
* @param {Boolean} [options.alignBottom=false]
|
||||
* @param {Boolean} [options.rounding=false]
|
||||
*/
|
||||
function Packer(numWorkers, options) {
|
||||
this._options = 0;
|
||||
this._processor = null;
|
||||
this._layoutQueue = [];
|
||||
this._layouts = {};
|
||||
this._layoutCallbacks = {};
|
||||
this._layoutWorkers = {};
|
||||
this._layoutWorkerData = {};
|
||||
this._workers = [];
|
||||
this._onWorkerMessage = this._onWorkerMessage.bind(this);
|
||||
|
||||
// Set initial options.
|
||||
this.setOptions(options);
|
||||
|
||||
// Init the worker(s) or the processor if workers can't be used.
|
||||
numWorkers = typeof numWorkers === 'number' ? Math.max(0, numWorkers) : 0;
|
||||
if (numWorkers && isWorkerProcessorsSupported()) {
|
||||
try {
|
||||
this._workers = createWorkerProcessors(numWorkers, this._onWorkerMessage);
|
||||
} catch (e) {
|
||||
this._processor = new PackerProcessor();
|
||||
}
|
||||
} else {
|
||||
this._processor = new PackerProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
Packer.prototype._sendToWorker = function () {
|
||||
if (!this._layoutQueue.length || !this._workers.length) return;
|
||||
|
||||
var layoutId = this._layoutQueue.shift();
|
||||
var worker = this._workers.pop();
|
||||
var data = this._layoutWorkerData[layoutId];
|
||||
|
||||
delete this._layoutWorkerData[layoutId];
|
||||
this._layoutWorkers[layoutId] = worker;
|
||||
worker.postMessage(data.buffer, [data.buffer]);
|
||||
};
|
||||
|
||||
Packer.prototype._onWorkerMessage = function (msg) {
|
||||
var data = new Float32Array(msg.data);
|
||||
var layoutId = data[PACKET_INDEX_ID];
|
||||
var layout = this._layouts[layoutId];
|
||||
var callback = this._layoutCallbacks[layoutId];
|
||||
var worker = this._layoutWorkers[layoutId];
|
||||
|
||||
if (layout) delete this._layouts[layoutId];
|
||||
if (callback) delete this._layoutCallbacks[layoutId];
|
||||
if (worker) delete this._layoutWorkers[layoutId];
|
||||
|
||||
if (layout && callback) {
|
||||
layout.width = data[PACKET_INDEX_WIDTH];
|
||||
layout.height = data[PACKET_INDEX_HEIGHT];
|
||||
layout.slots = data.subarray(PACKET_HEADER_SLOTS, data.length);
|
||||
this._finalizeLayout(layout);
|
||||
callback(layout);
|
||||
}
|
||||
|
||||
if (worker) {
|
||||
this._workers.push(worker);
|
||||
this._sendToWorker();
|
||||
}
|
||||
};
|
||||
|
||||
Packer.prototype._finalizeLayout = function (layout) {
|
||||
var grid = layout._grid;
|
||||
var isHorizontal = layout._settings & HORIZONTAL;
|
||||
var isBorderBox = grid._boxSizing === 'border-box';
|
||||
|
||||
delete layout._grid;
|
||||
delete layout._settings;
|
||||
|
||||
layout.styles = {};
|
||||
|
||||
if (isHorizontal) {
|
||||
layout.styles.width =
|
||||
(isBorderBox ? layout.width + grid._borderLeft + grid._borderRight : layout.width) + 'px';
|
||||
} else {
|
||||
layout.styles.height =
|
||||
(isBorderBox ? layout.height + grid._borderTop + grid._borderBottom : layout.height) + 'px';
|
||||
}
|
||||
|
||||
return layout;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.fillGaps]
|
||||
* @param {Boolean} [options.horizontal]
|
||||
* @param {Boolean} [options.alignRight]
|
||||
* @param {Boolean} [options.alignBottom]
|
||||
* @param {Boolean} [options.rounding]
|
||||
*/
|
||||
Packer.prototype.setOptions = function (options) {
|
||||
if (!options) return;
|
||||
|
||||
var fillGaps;
|
||||
if (typeof options.fillGaps === 'boolean') {
|
||||
fillGaps = options.fillGaps ? FILL_GAPS : 0;
|
||||
} else {
|
||||
fillGaps = this._options & FILL_GAPS;
|
||||
}
|
||||
|
||||
var horizontal;
|
||||
if (typeof options.horizontal === 'boolean') {
|
||||
horizontal = options.horizontal ? HORIZONTAL : 0;
|
||||
} else {
|
||||
horizontal = this._options & HORIZONTAL;
|
||||
}
|
||||
|
||||
var alignRight;
|
||||
if (typeof options.alignRight === 'boolean') {
|
||||
alignRight = options.alignRight ? ALIGN_RIGHT : 0;
|
||||
} else {
|
||||
alignRight = this._options & ALIGN_RIGHT;
|
||||
}
|
||||
|
||||
var alignBottom;
|
||||
if (typeof options.alignBottom === 'boolean') {
|
||||
alignBottom = options.alignBottom ? ALIGN_BOTTOM : 0;
|
||||
} else {
|
||||
alignBottom = this._options & ALIGN_BOTTOM;
|
||||
}
|
||||
|
||||
var rounding;
|
||||
if (typeof options.rounding === 'boolean') {
|
||||
rounding = options.rounding ? ROUNDING : 0;
|
||||
} else {
|
||||
rounding = this._options & ROUNDING;
|
||||
}
|
||||
|
||||
this._options = fillGaps | horizontal | alignRight | alignBottom | rounding;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {Grid} grid
|
||||
* @param {Number} layoutId
|
||||
* @param {Item[]} items
|
||||
* @param {Number} width
|
||||
* @param {Number} height
|
||||
* @param {Function} callback
|
||||
* @returns {?Function}
|
||||
*/
|
||||
Packer.prototype.createLayout = function (grid, layoutId, items, width, height, callback) {
|
||||
if (this._layouts[layoutId]) {
|
||||
throw new Error('A layout with the provided id is currently being processed.');
|
||||
}
|
||||
|
||||
var horizontal = this._options & HORIZONTAL;
|
||||
var layout = {
|
||||
id: layoutId,
|
||||
items: items,
|
||||
slots: null,
|
||||
width: horizontal ? 0 : width,
|
||||
height: !horizontal ? 0 : height,
|
||||
// Temporary data, which will be removed before sending the layout data
|
||||
// outside of Packer's context.
|
||||
_grid: grid,
|
||||
_settings: this._options,
|
||||
};
|
||||
|
||||
// If there are no items let's call the callback immediately.
|
||||
if (!items.length) {
|
||||
layout.slots = [];
|
||||
this._finalizeLayout(layout);
|
||||
callback(layout);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create layout synchronously if needed.
|
||||
if (this._processor) {
|
||||
layout.slots = window.Float32Array
|
||||
? new Float32Array(items.length * 2)
|
||||
: new Array(items.length * 2);
|
||||
this._processor.computeLayout(layout, layout._settings);
|
||||
this._finalizeLayout(layout);
|
||||
callback(layout);
|
||||
return;
|
||||
}
|
||||
|
||||
// Worker data.
|
||||
var data = new Float32Array(PACKET_HEADER_SLOTS + items.length * 2);
|
||||
|
||||
// Worker data header.
|
||||
data[PACKET_INDEX_ID] = layoutId;
|
||||
data[PACKET_INDEX_WIDTH] = layout.width;
|
||||
data[PACKET_INDEX_HEIGHT] = layout.height;
|
||||
data[PACKET_INDEX_OPTIONS] = layout._settings;
|
||||
|
||||
// Worker data items.
|
||||
var i, j, item;
|
||||
for (i = 0, j = PACKET_HEADER_SLOTS - 1, item; i < items.length; i++) {
|
||||
item = items[i];
|
||||
data[++j] = item._width + item._marginLeft + item._marginRight;
|
||||
data[++j] = item._height + item._marginTop + item._marginBottom;
|
||||
}
|
||||
|
||||
this._layoutQueue.push(layoutId);
|
||||
this._layouts[layoutId] = layout;
|
||||
this._layoutCallbacks[layoutId] = callback;
|
||||
this._layoutWorkerData[layoutId] = data;
|
||||
|
||||
this._sendToWorker();
|
||||
|
||||
return this.cancelLayout.bind(this, layoutId);
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {Number} layoutId
|
||||
*/
|
||||
Packer.prototype.cancelLayout = function (layoutId) {
|
||||
var layout = this._layouts[layoutId];
|
||||
if (!layout) return;
|
||||
|
||||
delete this._layouts[layoutId];
|
||||
delete this._layoutCallbacks[layoutId];
|
||||
|
||||
if (this._layoutWorkerData[layoutId]) {
|
||||
delete this._layoutWorkerData[layoutId];
|
||||
var queueIndex = this._layoutQueue.indexOf(layoutId);
|
||||
if (queueIndex > -1) this._layoutQueue.splice(queueIndex, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
Packer.prototype.destroy = function () {
|
||||
// Move all currently used workers back in the workers array.
|
||||
for (var key in this._layoutWorkers) {
|
||||
this._workers.push(this._layoutWorkers[key]);
|
||||
}
|
||||
|
||||
// Destroy all instance's workers.
|
||||
destroyWorkerProcessors(this._workers);
|
||||
|
||||
// Reset data.
|
||||
this._workers.length = 0;
|
||||
this._layoutQueue.length = 0;
|
||||
this._layouts = {};
|
||||
this._layoutCallbacks = {};
|
||||
this._layoutWorkers = {};
|
||||
this._layoutWorkerData = {};
|
||||
};
|
||||
|
||||
export default Packer;
|
||||
597
app/src/vendor/muuri-src/Packer/PackerProcessor.js
vendored
Normal file
597
app/src/vendor/muuri-src/Packer/PackerProcessor.js
vendored
Normal file
@ -0,0 +1,597 @@
|
||||
/**
|
||||
* Muuri Packer
|
||||
* Copyright (c) 2016-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/Packer/LICENSE.md
|
||||
*/
|
||||
|
||||
function createPackerProcessor(isWorker) {
|
||||
var FILL_GAPS = 1;
|
||||
var HORIZONTAL = 2;
|
||||
var ALIGN_RIGHT = 4;
|
||||
var ALIGN_BOTTOM = 8;
|
||||
var ROUNDING = 16;
|
||||
|
||||
var EPS = 0.001;
|
||||
var MIN_SLOT_SIZE = 0.5;
|
||||
|
||||
// Rounds number first to three decimal precision and then floors the result
|
||||
// to two decimal precision.
|
||||
// Math.floor(Math.round(number * 1000) / 10) / 100
|
||||
function roundNumber(number) {
|
||||
return ((((number * 1000 + 0.5) << 0) / 10) << 0) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
*/
|
||||
function PackerProcessor() {
|
||||
this.currentRects = [];
|
||||
this.nextRects = [];
|
||||
this.rectTarget = {};
|
||||
this.rectStore = [];
|
||||
this.slotSizes = [];
|
||||
this.rectId = 0;
|
||||
this.slotIndex = -1;
|
||||
this.slotData = { left: 0, top: 0, width: 0, height: 0 };
|
||||
this.sortRectsLeftTop = this.sortRectsLeftTop.bind(this);
|
||||
this.sortRectsTopLeft = this.sortRectsTopLeft.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a layout object as an argument and computes positions (slots) for the
|
||||
* layout items. Also computes the final width and height of the layout. The
|
||||
* provided layout object's slots array is mutated as well as the width and
|
||||
* height properties.
|
||||
*
|
||||
* @param {Object} layout
|
||||
* @param {Number} layout.width
|
||||
* - The start (current) width of the layout in pixels.
|
||||
* @param {Number} layout.height
|
||||
* - The start (current) height of the layout in pixels.
|
||||
* @param {(Item[]|Number[])} layout.items
|
||||
* - List of Muuri.Item instances or a list of item dimensions
|
||||
* (e.g [ item1Width, item1Height, item2Width, item2Height, ... ]).
|
||||
* @param {(Array|Float32Array)} layout.slots
|
||||
* - An Array/Float32Array instance which's length should equal to
|
||||
* the amount of items times two. The position (width and height) of each
|
||||
* item will be written into this array.
|
||||
* @param {Number} settings
|
||||
* - The layout's settings as bitmasks.
|
||||
* @returns {Object}
|
||||
*/
|
||||
PackerProcessor.prototype.computeLayout = function (layout, settings) {
|
||||
var items = layout.items;
|
||||
var slots = layout.slots;
|
||||
var fillGaps = !!(settings & FILL_GAPS);
|
||||
var horizontal = !!(settings & HORIZONTAL);
|
||||
var alignRight = !!(settings & ALIGN_RIGHT);
|
||||
var alignBottom = !!(settings & ALIGN_BOTTOM);
|
||||
var rounding = !!(settings & ROUNDING);
|
||||
var isPreProcessed = typeof items[0] === 'number';
|
||||
var i, bump, item, slotWidth, slotHeight, slot;
|
||||
|
||||
// No need to go further if items do not exist.
|
||||
if (!items.length) return layout;
|
||||
|
||||
// Compute slots for the items.
|
||||
bump = isPreProcessed ? 2 : 1;
|
||||
for (i = 0; i < items.length; i += bump) {
|
||||
// If items are pre-processed it means that items array contains only
|
||||
// the raw dimensions of the items. Otherwise we assume it is an array
|
||||
// of normal Muuri items.
|
||||
if (isPreProcessed) {
|
||||
slotWidth = items[i];
|
||||
slotHeight = items[i + 1];
|
||||
} else {
|
||||
item = items[i];
|
||||
slotWidth = item._width + item._marginLeft + item._marginRight;
|
||||
slotHeight = item._height + item._marginTop + item._marginBottom;
|
||||
}
|
||||
|
||||
// If rounding is enabled let's round the item's width and height to
|
||||
// make the layout algorithm a bit more stable. This has a performance
|
||||
// cost so don't use this if not necessary.
|
||||
if (rounding) {
|
||||
slotWidth = roundNumber(slotWidth);
|
||||
slotHeight = roundNumber(slotHeight);
|
||||
}
|
||||
|
||||
// Get slot data.
|
||||
slot = this.computeNextSlot(layout, slotWidth, slotHeight, fillGaps, horizontal);
|
||||
|
||||
// Update layout width/height.
|
||||
if (horizontal) {
|
||||
if (slot.left + slot.width > layout.width) {
|
||||
layout.width = slot.left + slot.width;
|
||||
}
|
||||
} else {
|
||||
if (slot.top + slot.height > layout.height) {
|
||||
layout.height = slot.top + slot.height;
|
||||
}
|
||||
}
|
||||
|
||||
// Add item slot data to layout slots.
|
||||
slots[++this.slotIndex] = slot.left;
|
||||
slots[++this.slotIndex] = slot.top;
|
||||
|
||||
// Store the size too (for later usage) if needed.
|
||||
if (alignRight || alignBottom) {
|
||||
this.slotSizes.push(slot.width, slot.height);
|
||||
}
|
||||
}
|
||||
|
||||
// If the alignment is set to right we need to adjust the results.
|
||||
if (alignRight) {
|
||||
for (i = 0; i < slots.length; i += 2) {
|
||||
slots[i] = layout.width - (slots[i] + this.slotSizes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// If the alignment is set to bottom we need to adjust the results.
|
||||
if (alignBottom) {
|
||||
for (i = 1; i < slots.length; i += 2) {
|
||||
slots[i] = layout.height - (slots[i] + this.slotSizes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset stuff.
|
||||
this.slotSizes.length = 0;
|
||||
this.currentRects.length = 0;
|
||||
this.nextRects.length = 0;
|
||||
this.rectStore.length = 0;
|
||||
this.rectId = 0;
|
||||
this.slotIndex = -1;
|
||||
|
||||
return layout;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate next slot in the layout. Returns a slot object with position and
|
||||
* dimensions data. The returned object is reused between calls.
|
||||
*
|
||||
* @param {Object} layout
|
||||
* @param {Number} slotWidth
|
||||
* @param {Number} slotHeight
|
||||
* @param {Boolean} fillGaps
|
||||
* @param {Boolean} horizontal
|
||||
* @returns {Object}
|
||||
*/
|
||||
PackerProcessor.prototype.computeNextSlot = function (
|
||||
layout,
|
||||
slotWidth,
|
||||
slotHeight,
|
||||
fillGaps,
|
||||
horizontal
|
||||
) {
|
||||
var slot = this.slotData;
|
||||
var currentRects = this.currentRects;
|
||||
var nextRects = this.nextRects;
|
||||
var ignoreCurrentRects = false;
|
||||
var rect;
|
||||
var rectId;
|
||||
var shards;
|
||||
var i;
|
||||
var j;
|
||||
|
||||
// Reset new slots.
|
||||
nextRects.length = 0;
|
||||
|
||||
// Set item slot initial data.
|
||||
slot.left = null;
|
||||
slot.top = null;
|
||||
slot.width = slotWidth;
|
||||
slot.height = slotHeight;
|
||||
|
||||
// Try to find position for the slot from the existing free spaces in the
|
||||
// layout.
|
||||
for (i = 0; i < currentRects.length; i++) {
|
||||
rectId = currentRects[i];
|
||||
if (!rectId) continue;
|
||||
rect = this.getRect(rectId);
|
||||
if (slot.width <= rect.width + EPS && slot.height <= rect.height + EPS) {
|
||||
slot.left = rect.left;
|
||||
slot.top = rect.top;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no position was found for the slot let's position the slot to
|
||||
// the bottom left (in vertical mode) or top right (in horizontal mode) of
|
||||
// the layout.
|
||||
if (slot.left === null) {
|
||||
if (horizontal) {
|
||||
slot.left = layout.width;
|
||||
slot.top = 0;
|
||||
} else {
|
||||
slot.left = 0;
|
||||
slot.top = layout.height;
|
||||
}
|
||||
|
||||
// If gaps don't need filling let's throw away all the current free spaces
|
||||
// (currentRects).
|
||||
if (!fillGaps) {
|
||||
ignoreCurrentRects = true;
|
||||
}
|
||||
}
|
||||
|
||||
// In vertical mode, if the slot's bottom overlaps the layout's bottom.
|
||||
if (!horizontal && slot.top + slot.height > layout.height + EPS) {
|
||||
// If slot is not aligned to the left edge, create a new free space to the
|
||||
// left of the slot.
|
||||
if (slot.left > MIN_SLOT_SIZE) {
|
||||
nextRects.push(this.addRect(0, layout.height, slot.left, Infinity));
|
||||
}
|
||||
|
||||
// If slot is not aligned to the right edge, create a new free space to
|
||||
// the right of the slot.
|
||||
if (slot.left + slot.width < layout.width - MIN_SLOT_SIZE) {
|
||||
nextRects.push(
|
||||
this.addRect(
|
||||
slot.left + slot.width,
|
||||
layout.height,
|
||||
layout.width - slot.left - slot.width,
|
||||
Infinity
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Update layout height.
|
||||
layout.height = slot.top + slot.height;
|
||||
}
|
||||
|
||||
// In horizontal mode, if the slot's right overlaps the layout's right edge.
|
||||
if (horizontal && slot.left + slot.width > layout.width + EPS) {
|
||||
// If slot is not aligned to the top, create a new free space above the
|
||||
// slot.
|
||||
if (slot.top > MIN_SLOT_SIZE) {
|
||||
nextRects.push(this.addRect(layout.width, 0, Infinity, slot.top));
|
||||
}
|
||||
|
||||
// If slot is not aligned to the bottom, create a new free space below
|
||||
// the slot.
|
||||
if (slot.top + slot.height < layout.height - MIN_SLOT_SIZE) {
|
||||
nextRects.push(
|
||||
this.addRect(
|
||||
layout.width,
|
||||
slot.top + slot.height,
|
||||
Infinity,
|
||||
layout.height - slot.top - slot.height
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Update layout width.
|
||||
layout.width = slot.left + slot.width;
|
||||
}
|
||||
|
||||
// Clean up the current free spaces making sure none of them overlap with
|
||||
// the slot. Split all overlapping free spaces into smaller shards that do
|
||||
// not overlap with the slot.
|
||||
if (!ignoreCurrentRects) {
|
||||
if (fillGaps) i = 0;
|
||||
for (; i < currentRects.length; i++) {
|
||||
rectId = currentRects[i];
|
||||
if (!rectId) continue;
|
||||
rect = this.getRect(rectId);
|
||||
shards = this.splitRect(rect, slot);
|
||||
for (j = 0; j < shards.length; j++) {
|
||||
rectId = shards[j];
|
||||
rect = this.getRect(rectId);
|
||||
// Make sure that the free space is within the boundaries of the
|
||||
// layout. This routine is critical to the algorithm as it makes sure
|
||||
// that there are no leftover spaces with infinite height/width.
|
||||
// It's also essential that we don't compare values absolutely to each
|
||||
// other but leave a little headroom (EPSILON) to get rid of false
|
||||
// positives.
|
||||
if (
|
||||
horizontal ? rect.left + EPS < layout.width - EPS : rect.top + EPS < layout.height - EPS
|
||||
) {
|
||||
nextRects.push(rectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize and sort all the new free spaces that will be used in the next
|
||||
// iteration. This procedure is critical to make the bin-packing algorithm
|
||||
// work. The free spaces have to be in correct order in the beginning of the
|
||||
// next iteration.
|
||||
if (nextRects.length > 1) {
|
||||
this.purgeRects(nextRects).sort(horizontal ? this.sortRectsLeftTop : this.sortRectsTopLeft);
|
||||
}
|
||||
|
||||
// Finally we need to make sure that `this.currentRects` points to
|
||||
// `nextRects` array as that is used in the next iteration's beginning when
|
||||
// we try to find a space for the next slot.
|
||||
this.currentRects = nextRects;
|
||||
this.nextRects = currentRects;
|
||||
|
||||
return slot;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a new rectangle to the rectangle store. Returns the id of the new
|
||||
* rectangle.
|
||||
*
|
||||
* @param {Number} left
|
||||
* @param {Number} top
|
||||
* @param {Number} width
|
||||
* @param {Number} height
|
||||
* @returns {Number}
|
||||
*/
|
||||
PackerProcessor.prototype.addRect = function (left, top, width, height) {
|
||||
var rectId = ++this.rectId;
|
||||
this.rectStore[rectId] = left || 0;
|
||||
this.rectStore[++this.rectId] = top || 0;
|
||||
this.rectStore[++this.rectId] = width || 0;
|
||||
this.rectStore[++this.rectId] = height || 0;
|
||||
return rectId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get rectangle data from the rectangle store by id. Optionally you can
|
||||
* provide a target object where the rectangle data will be written in. By
|
||||
* default an internal object is reused as a target object.
|
||||
*
|
||||
* @param {Number} id
|
||||
* @param {Object} [target]
|
||||
* @returns {Object}
|
||||
*/
|
||||
PackerProcessor.prototype.getRect = function (id, target) {
|
||||
if (!target) target = this.rectTarget;
|
||||
target.left = this.rectStore[id] || 0;
|
||||
target.top = this.rectStore[++id] || 0;
|
||||
target.width = this.rectStore[++id] || 0;
|
||||
target.height = this.rectStore[++id] || 0;
|
||||
return target;
|
||||
};
|
||||
|
||||
/**
|
||||
* Punch a hole into a rectangle and return the shards (1-4).
|
||||
*
|
||||
* @param {Object} rect
|
||||
* @param {Object} hole
|
||||
* @returns {Number[]}
|
||||
*/
|
||||
PackerProcessor.prototype.splitRect = (function () {
|
||||
var shards = [];
|
||||
var width = 0;
|
||||
var height = 0;
|
||||
return function (rect, hole) {
|
||||
// Reset old shards.
|
||||
shards.length = 0;
|
||||
|
||||
// If the slot does not overlap with the hole add slot to the return data
|
||||
// as is. Note that in this case we are eager to keep the slot as is if
|
||||
// possible so we use the EPSILON in favour of that logic.
|
||||
if (
|
||||
rect.left + rect.width <= hole.left + EPS ||
|
||||
hole.left + hole.width <= rect.left + EPS ||
|
||||
rect.top + rect.height <= hole.top + EPS ||
|
||||
hole.top + hole.height <= rect.top + EPS
|
||||
) {
|
||||
shards.push(this.addRect(rect.left, rect.top, rect.width, rect.height));
|
||||
return shards;
|
||||
}
|
||||
|
||||
// Left split.
|
||||
width = hole.left - rect.left;
|
||||
if (width >= MIN_SLOT_SIZE) {
|
||||
shards.push(this.addRect(rect.left, rect.top, width, rect.height));
|
||||
}
|
||||
|
||||
// Right split.
|
||||
width = rect.left + rect.width - (hole.left + hole.width);
|
||||
if (width >= MIN_SLOT_SIZE) {
|
||||
shards.push(this.addRect(hole.left + hole.width, rect.top, width, rect.height));
|
||||
}
|
||||
|
||||
// Top split.
|
||||
height = hole.top - rect.top;
|
||||
if (height >= MIN_SLOT_SIZE) {
|
||||
shards.push(this.addRect(rect.left, rect.top, rect.width, height));
|
||||
}
|
||||
|
||||
// Bottom split.
|
||||
height = rect.top + rect.height - (hole.top + hole.height);
|
||||
if (height >= MIN_SLOT_SIZE) {
|
||||
shards.push(this.addRect(rect.left, hole.top + hole.height, rect.width, height));
|
||||
}
|
||||
|
||||
return shards;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Check if a rectangle is fully within another rectangle.
|
||||
*
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
PackerProcessor.prototype.isRectAWithinRectB = function (a, b) {
|
||||
return (
|
||||
a.left + EPS >= b.left &&
|
||||
a.top + EPS >= b.top &&
|
||||
a.left + a.width - EPS <= b.left + b.width &&
|
||||
a.top + a.height - EPS <= b.top + b.height
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Loops through an array of rectangle ids and resets all that are fully
|
||||
* within another rectangle in the array. Resetting in this case means that
|
||||
* the rectangle id value is replaced with zero.
|
||||
*
|
||||
* @param {Number[]} rectIds
|
||||
* @returns {Number[]}
|
||||
*/
|
||||
PackerProcessor.prototype.purgeRects = (function () {
|
||||
var rectA = {};
|
||||
var rectB = {};
|
||||
return function (rectIds) {
|
||||
var i = rectIds.length;
|
||||
var j;
|
||||
|
||||
while (i--) {
|
||||
j = rectIds.length;
|
||||
if (!rectIds[i]) continue;
|
||||
this.getRect(rectIds[i], rectA);
|
||||
while (j--) {
|
||||
if (!rectIds[j] || i === j) continue;
|
||||
this.getRect(rectIds[j], rectB);
|
||||
if (this.isRectAWithinRectB(rectA, rectB)) {
|
||||
rectIds[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rectIds;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Sort rectangles with top-left gravity.
|
||||
*
|
||||
* @param {Number} aId
|
||||
* @param {Number} bId
|
||||
* @returns {Number}
|
||||
*/
|
||||
PackerProcessor.prototype.sortRectsTopLeft = (function () {
|
||||
var rectA = {};
|
||||
var rectB = {};
|
||||
return function (aId, bId) {
|
||||
this.getRect(aId, rectA);
|
||||
this.getRect(bId, rectB);
|
||||
|
||||
return rectA.top < rectB.top && rectA.top + EPS < rectB.top
|
||||
? -1
|
||||
: rectA.top > rectB.top && rectA.top - EPS > rectB.top
|
||||
? 1
|
||||
: rectA.left < rectB.left && rectA.left + EPS < rectB.left
|
||||
? -1
|
||||
: rectA.left > rectB.left && rectA.left - EPS > rectB.left
|
||||
? 1
|
||||
: 0;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Sort rectangles with left-top gravity.
|
||||
*
|
||||
* @param {Number} aId
|
||||
* @param {Number} bId
|
||||
* @returns {Number}
|
||||
*/
|
||||
PackerProcessor.prototype.sortRectsLeftTop = (function () {
|
||||
var rectA = {};
|
||||
var rectB = {};
|
||||
return function (aId, bId) {
|
||||
this.getRect(aId, rectA);
|
||||
this.getRect(bId, rectB);
|
||||
return rectA.left < rectB.left && rectA.left + EPS < rectB.left
|
||||
? -1
|
||||
: rectA.left > rectB.left && rectA.left - EPS < rectB.left
|
||||
? 1
|
||||
: rectA.top < rectB.top && rectA.top + EPS < rectB.top
|
||||
? -1
|
||||
: rectA.top > rectB.top && rectA.top - EPS > rectB.top
|
||||
? 1
|
||||
: 0;
|
||||
};
|
||||
})();
|
||||
|
||||
if (isWorker) {
|
||||
var PACKET_INDEX_WIDTH = 1;
|
||||
var PACKET_INDEX_HEIGHT = 2;
|
||||
var PACKET_INDEX_OPTIONS = 3;
|
||||
var PACKET_HEADER_SLOTS = 4;
|
||||
var processor = new PackerProcessor();
|
||||
|
||||
self.onmessage = function (msg) {
|
||||
var data = new Float32Array(msg.data);
|
||||
var items = data.subarray(PACKET_HEADER_SLOTS, data.length);
|
||||
var slots = new Float32Array(items.length);
|
||||
var settings = data[PACKET_INDEX_OPTIONS];
|
||||
var layout = {
|
||||
items: items,
|
||||
slots: slots,
|
||||
width: data[PACKET_INDEX_WIDTH],
|
||||
height: data[PACKET_INDEX_HEIGHT],
|
||||
};
|
||||
|
||||
// Compute the layout (width / height / slots).
|
||||
processor.computeLayout(layout, settings);
|
||||
|
||||
// Copy layout data to the return data.
|
||||
data[PACKET_INDEX_WIDTH] = layout.width;
|
||||
data[PACKET_INDEX_HEIGHT] = layout.height;
|
||||
data.set(layout.slots, PACKET_HEADER_SLOTS);
|
||||
|
||||
// Send layout back to the main thread.
|
||||
postMessage(data.buffer, [data.buffer]);
|
||||
};
|
||||
}
|
||||
|
||||
return PackerProcessor;
|
||||
}
|
||||
|
||||
var PackerProcessor = createPackerProcessor();
|
||||
export default PackerProcessor;
|
||||
|
||||
//
|
||||
// WORKER UTILS
|
||||
//
|
||||
|
||||
var blobUrl = null;
|
||||
var activeWorkers = [];
|
||||
|
||||
export function createWorkerProcessors(amount, onmessage) {
|
||||
var workers = [];
|
||||
|
||||
if (amount > 0) {
|
||||
if (!blobUrl) {
|
||||
blobUrl = URL.createObjectURL(
|
||||
new Blob(['(' + createPackerProcessor.toString() + ')(true)'], {
|
||||
type: 'application/javascript',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (var i = 0, worker; i < amount; i++) {
|
||||
worker = new Worker(blobUrl);
|
||||
if (onmessage) worker.onmessage = onmessage;
|
||||
workers.push(worker);
|
||||
activeWorkers.push(worker);
|
||||
}
|
||||
}
|
||||
|
||||
return workers;
|
||||
}
|
||||
|
||||
export function destroyWorkerProcessors(workers) {
|
||||
var worker;
|
||||
var index;
|
||||
|
||||
for (var i = 0; i < workers.length; i++) {
|
||||
worker = workers[i];
|
||||
worker.onmessage = null;
|
||||
worker.onerror = null;
|
||||
worker.onmessageerror = null;
|
||||
worker.terminate();
|
||||
|
||||
index = activeWorkers.indexOf(worker);
|
||||
if (index > -1) activeWorkers.splice(index, 1);
|
||||
}
|
||||
|
||||
if (blobUrl && !activeWorkers.length) {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
blobUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
export function isWorkerProcessorsSupported() {
|
||||
return !!(window.Worker && window.URL && window.Blob);
|
||||
}
|
||||
19
app/src/vendor/muuri-src/Ticker/LICENSE.md
vendored
Normal file
19
app/src/vendor/muuri-src/Ticker/LICENSE.md
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018, Niklas Rämö
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
94
app/src/vendor/muuri-src/Ticker/Ticker.js
vendored
Normal file
94
app/src/vendor/muuri-src/Ticker/Ticker.js
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Muuri Ticker
|
||||
* Copyright (c) 2018-present, Niklas Rämö <inramo@gmail.com>
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/src/Ticker/LICENSE.md
|
||||
*/
|
||||
|
||||
import raf from '../utils/raf';
|
||||
|
||||
/**
|
||||
* A ticker system for handling DOM reads and writes in an efficient way.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
function Ticker(numLanes) {
|
||||
this._nextStep = null;
|
||||
this._lanes = [];
|
||||
this._stepQueue = [];
|
||||
this._stepCallbacks = {};
|
||||
this._step = this._step.bind(this);
|
||||
for (var i = 0; i < numLanes; i++) {
|
||||
this._lanes.push(new TickerLane());
|
||||
}
|
||||
}
|
||||
|
||||
Ticker.prototype._step = function (time) {
|
||||
var lanes = this._lanes;
|
||||
var stepQueue = this._stepQueue;
|
||||
var stepCallbacks = this._stepCallbacks;
|
||||
var i, j, id, laneQueue, laneCallbacks, laneIndices;
|
||||
|
||||
this._nextStep = null;
|
||||
|
||||
for (i = 0; i < lanes.length; i++) {
|
||||
laneQueue = lanes[i].queue;
|
||||
laneCallbacks = lanes[i].callbacks;
|
||||
laneIndices = lanes[i].indices;
|
||||
for (j = 0; j < laneQueue.length; j++) {
|
||||
id = laneQueue[j];
|
||||
if (!id) continue;
|
||||
stepQueue.push(id);
|
||||
stepCallbacks[id] = laneCallbacks[id];
|
||||
delete laneCallbacks[id];
|
||||
delete laneIndices[id];
|
||||
}
|
||||
laneQueue.length = 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < stepQueue.length; i++) {
|
||||
id = stepQueue[i];
|
||||
if (stepCallbacks[id]) stepCallbacks[id](time);
|
||||
delete stepCallbacks[id];
|
||||
}
|
||||
|
||||
stepQueue.length = 0;
|
||||
};
|
||||
|
||||
Ticker.prototype.add = function (laneIndex, id, callback) {
|
||||
this._lanes[laneIndex].add(id, callback);
|
||||
if (!this._nextStep) this._nextStep = raf(this._step);
|
||||
};
|
||||
|
||||
Ticker.prototype.remove = function (laneIndex, id) {
|
||||
this._lanes[laneIndex].remove(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* A lane for ticker.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
function TickerLane() {
|
||||
this.queue = [];
|
||||
this.indices = {};
|
||||
this.callbacks = {};
|
||||
}
|
||||
|
||||
TickerLane.prototype.add = function (id, callback) {
|
||||
var index = this.indices[id];
|
||||
if (index !== undefined) this.queue[index] = undefined;
|
||||
this.queue.push(id);
|
||||
this.callbacks[id] = callback;
|
||||
this.indices[id] = this.queue.length - 1;
|
||||
};
|
||||
|
||||
TickerLane.prototype.remove = function (id) {
|
||||
var index = this.indices[id];
|
||||
if (index === undefined) return;
|
||||
this.queue[index] = undefined;
|
||||
delete this.callbacks[id];
|
||||
delete this.indices[id];
|
||||
};
|
||||
|
||||
export default Ticker;
|
||||
43
app/src/vendor/muuri-src/constants.js
vendored
Normal file
43
app/src/vendor/muuri-src/constants.js
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
export var GRID_INSTANCES = {};
|
||||
export var ITEM_ELEMENT_MAP = typeof Map === 'function' ? new Map() : null;
|
||||
|
||||
export var ACTION_SWAP = 'swap';
|
||||
export var ACTION_MOVE = 'move';
|
||||
|
||||
export var EVENT_SYNCHRONIZE = 'synchronize';
|
||||
export var EVENT_LAYOUT_START = 'layoutStart';
|
||||
export var EVENT_LAYOUT_END = 'layoutEnd';
|
||||
export var EVENT_LAYOUT_ABORT = 'layoutAbort';
|
||||
export var EVENT_ADD = 'add';
|
||||
export var EVENT_REMOVE = 'remove';
|
||||
export var EVENT_SHOW_START = 'showStart';
|
||||
export var EVENT_SHOW_END = 'showEnd';
|
||||
export var EVENT_HIDE_START = 'hideStart';
|
||||
export var EVENT_HIDE_END = 'hideEnd';
|
||||
export var EVENT_FILTER = 'filter';
|
||||
export var EVENT_SORT = 'sort';
|
||||
export var EVENT_MOVE = 'move';
|
||||
export var EVENT_SEND = 'send';
|
||||
export var EVENT_BEFORE_SEND = 'beforeSend';
|
||||
export var EVENT_RECEIVE = 'receive';
|
||||
export var EVENT_BEFORE_RECEIVE = 'beforeReceive';
|
||||
export var EVENT_DRAG_INIT = 'dragInit';
|
||||
export var EVENT_DRAG_START = 'dragStart';
|
||||
export var EVENT_DRAG_MOVE = 'dragMove';
|
||||
export var EVENT_DRAG_SCROLL = 'dragScroll';
|
||||
export var EVENT_DRAG_END = 'dragEnd';
|
||||
export var EVENT_DRAG_RELEASE_START = 'dragReleaseStart';
|
||||
export var EVENT_DRAG_RELEASE_END = 'dragReleaseEnd';
|
||||
export var EVENT_DESTROY = 'destroy';
|
||||
|
||||
export var HAS_TOUCH_EVENTS = 'ontouchstart' in window;
|
||||
export var HAS_POINTER_EVENTS = !!window.PointerEvent;
|
||||
export var HAS_MS_POINTER_EVENTS = !!window.navigator.msPointerEnabled;
|
||||
|
||||
export var MAX_SAFE_FLOAT32_INTEGER = 16777216;
|
||||
596
app/src/vendor/muuri-src/index.d.ts
vendored
Normal file
596
app/src/vendor/muuri-src/index.d.ts
vendored
Normal file
@ -0,0 +1,596 @@
|
||||
export interface StyleDeclaration {
|
||||
[styleProperty: string]: string;
|
||||
}
|
||||
|
||||
export type EventListener = (...args: any[]) => any;
|
||||
|
||||
export interface DraggerCssProps {
|
||||
touchAction?: string;
|
||||
userSelect?: string;
|
||||
userDrag?: string;
|
||||
tapHighlightColor?: string;
|
||||
touchCallout?: string;
|
||||
contentZooming?: string;
|
||||
}
|
||||
|
||||
export interface DraggerEvent {
|
||||
type: 'start' | 'move' | 'end' | 'cancel';
|
||||
srcEvent: PointerEvent | TouchEvent | MouseEvent;
|
||||
distance: number;
|
||||
deltaX: number;
|
||||
deltaY: number;
|
||||
deltaTime: number;
|
||||
isFirst: boolean;
|
||||
isFinal: boolean;
|
||||
pointerType: 'mouse' | 'pen' | 'touch';
|
||||
identifier: number;
|
||||
screenX: number;
|
||||
screenY: number;
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
pageX: number;
|
||||
pageY: number;
|
||||
target: HTMLElement;
|
||||
}
|
||||
|
||||
export interface DraggerStartEvent extends DraggerEvent {
|
||||
type: 'start';
|
||||
distance: 0;
|
||||
deltaX: 0;
|
||||
deltaY: 0;
|
||||
deltaTime: 0;
|
||||
isFirst: true;
|
||||
isFinal: false;
|
||||
}
|
||||
|
||||
export interface DraggerMoveEvent extends DraggerEvent {
|
||||
type: 'move';
|
||||
isFirst: false;
|
||||
isFinal: false;
|
||||
}
|
||||
|
||||
export interface DraggerEndEvent extends DraggerEvent {
|
||||
type: 'end';
|
||||
isFirst: false;
|
||||
isFinal: true;
|
||||
}
|
||||
|
||||
export interface DraggerCancelEvent extends DraggerEvent {
|
||||
type: 'cancel';
|
||||
isFirst: false;
|
||||
isFinal: true;
|
||||
}
|
||||
|
||||
export interface DraggerEvents {
|
||||
start(event: DraggerStartEvent): any;
|
||||
move(event: DraggerMoveEvent): any;
|
||||
end(event: DraggerMoveEvent): any;
|
||||
cancel(event: DraggerCancelEvent): any;
|
||||
}
|
||||
|
||||
export interface ScrollEvent extends Event {
|
||||
type: 'scroll';
|
||||
}
|
||||
|
||||
export interface GridEvents {
|
||||
synchronize(): any;
|
||||
layoutStart(items: Item[], isInstant: boolean): any;
|
||||
layoutEnd(items: Item[]): any;
|
||||
layoutAbort(items: Item[]): any;
|
||||
add(items: Item[]): any;
|
||||
remove(items: Item[], indices: number[]): any;
|
||||
showStart(items: Item[]): any;
|
||||
showEnd(items: Item[]): any;
|
||||
hideStart(items: Item[]): any;
|
||||
hideEnd(items: Item[]): any;
|
||||
filter(shownItems: Item[], hiddenItems: Item[]): any;
|
||||
sort(currentOrder: Item[], previousOrder: Item[]): any;
|
||||
move(data: { item: Item; fromIndex: number; toIndex: number; action: 'move' | 'swap' }): any;
|
||||
send(data: { item: Item; fromGrid: Grid; fromIndex: number; toGrid: Grid; toIndex: number }): any;
|
||||
beforeSend(data: {
|
||||
item: Item;
|
||||
fromGrid: Grid;
|
||||
fromIndex: number;
|
||||
toGrid: Grid;
|
||||
toIndex: number;
|
||||
}): any;
|
||||
receive(data: {
|
||||
item: Item;
|
||||
fromGrid: Grid;
|
||||
fromIndex: number;
|
||||
toGrid: Grid;
|
||||
toIndex: number;
|
||||
}): any;
|
||||
beforeReceive(data: {
|
||||
item: Item;
|
||||
fromGrid: Grid;
|
||||
fromIndex: number;
|
||||
toGrid: Grid;
|
||||
toIndex: number;
|
||||
}): any;
|
||||
dragInit(item: Item, event: DraggerStartEvent | DraggerMoveEvent): any;
|
||||
dragStart(item: Item, event: DraggerStartEvent | DraggerMoveEvent): any;
|
||||
dragMove(item: Item, event: DraggerMoveEvent): any;
|
||||
dragScroll(item: Item, event: ScrollEvent): any;
|
||||
dragEnd(item: Item, event: DraggerEndEvent | DraggerCancelEvent): any;
|
||||
dragReleaseStart(item: Item): any;
|
||||
dragReleaseEnd(item: Item): any;
|
||||
destroy(): any;
|
||||
}
|
||||
|
||||
export interface LayoutData {
|
||||
id: number;
|
||||
items: Item[];
|
||||
slots: number[];
|
||||
styles?: StyleDeclaration | null;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface LayoutOptions {
|
||||
fillGaps?: boolean;
|
||||
horizontal?: boolean;
|
||||
alignRight?: boolean;
|
||||
alignBottom?: boolean;
|
||||
rounding?: boolean;
|
||||
}
|
||||
|
||||
export type LayoutOnFinish = (items: Item[], isAborted: boolean) => any;
|
||||
|
||||
export type LayoutFunctionCallback = (layout: LayoutData) => any;
|
||||
|
||||
export type LayoutFunctionCancel = (...args: any[]) => any;
|
||||
|
||||
export type LayoutFunction = (
|
||||
grid: Grid,
|
||||
id: number,
|
||||
items: Item[],
|
||||
gridWidth: number,
|
||||
gridHeight: number,
|
||||
callback: LayoutFunctionCallback
|
||||
) => void | undefined | LayoutFunctionCancel;
|
||||
|
||||
export type SortDataGetter = (item: Item, element: HTMLElement) => any;
|
||||
|
||||
export type DragStartPredicate = (
|
||||
item: Item,
|
||||
event: DraggerStartEvent | DraggerMoveEvent | DraggerEndEvent | DraggerCancelEvent
|
||||
) => boolean | undefined;
|
||||
|
||||
export interface DragStartPredicateOptions {
|
||||
distance?: number;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export type DragSortGetter = (this: Grid, item: Item) => Grid[] | null | void | undefined;
|
||||
|
||||
export interface DragSortHeuristics {
|
||||
sortInterval?: number;
|
||||
minDragDistance?: number;
|
||||
minBounceBackAngle?: number;
|
||||
}
|
||||
|
||||
export type DragSortPredicateResult = {
|
||||
grid: Grid;
|
||||
index: number;
|
||||
action: 'move' | 'swap';
|
||||
} | null;
|
||||
|
||||
export type DragSortPredicate = (item: Item, event: DraggerMoveEvent) => DragSortPredicateResult;
|
||||
|
||||
export interface DragSortPredicateOptions {
|
||||
threshold?: number;
|
||||
action?: 'move' | 'swap';
|
||||
migrateAction?: 'move' | 'swap';
|
||||
}
|
||||
|
||||
export interface DragReleaseOptions {
|
||||
duration?: number;
|
||||
easing?: string;
|
||||
useDragContainer?: boolean;
|
||||
}
|
||||
|
||||
export type DragPlaceholderCreateElement = (item: Item) => HTMLElement;
|
||||
|
||||
export type DragPlaceholderOnCreate = (item: Item, placeholderElement: HTMLElement) => any;
|
||||
|
||||
export type DragPlaceholderOnRemove = (item: Item, placeholderElement: HTMLElement) => any;
|
||||
|
||||
export interface DragPlaceholderOptions {
|
||||
enabled?: boolean;
|
||||
createElement?: DragPlaceholderCreateElement | null;
|
||||
onCreate?: DragPlaceholderOnCreate | null;
|
||||
onRemove?: DragPlaceholderOnRemove | null;
|
||||
}
|
||||
|
||||
export interface DragAutoScrollTarget {
|
||||
element: Window | HTMLElement;
|
||||
axis?: number;
|
||||
priority?: number;
|
||||
threshold?: number;
|
||||
}
|
||||
|
||||
export type DragAutoScrollTargets = Array<Window | HTMLElement | DragAutoScrollTarget>;
|
||||
|
||||
export type DragAutoScrollTargetsGetter = (item: Item) => DragAutoScrollTargets;
|
||||
|
||||
export type DragAutoScrollOnStart = (
|
||||
item: Item,
|
||||
scrollElement: Window | HTMLElement,
|
||||
scrollDirection: number
|
||||
) => any;
|
||||
|
||||
export type DragAutoScrollOnStop = (
|
||||
item: Item,
|
||||
scrollElement: Window | HTMLElement,
|
||||
scrollDirection: number
|
||||
) => any;
|
||||
|
||||
export type DragAutoScrollHandle = (
|
||||
item: Item,
|
||||
itemClientX: number,
|
||||
itemClientY: number,
|
||||
itemWidth: number,
|
||||
itemHeight: number,
|
||||
pointerClientX: number,
|
||||
pointerClientY: number
|
||||
) => {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export type DragAutoScrollSpeed = (
|
||||
item: Item,
|
||||
scrollElement: Window | HTMLElement,
|
||||
scrollData: {
|
||||
direction: number;
|
||||
threshold: number;
|
||||
distance: number;
|
||||
value: number;
|
||||
maxValue: number;
|
||||
duration: number;
|
||||
speed: number;
|
||||
deltaTime: number;
|
||||
isEnding: boolean;
|
||||
}
|
||||
) => number;
|
||||
|
||||
export interface DragAutoScrollOptions {
|
||||
targets?: DragAutoScrollTargets | DragAutoScrollTargetsGetter;
|
||||
handle?: DragAutoScrollHandle | null;
|
||||
threshold?: number;
|
||||
safeZone?: number;
|
||||
speed?: number | DragAutoScrollSpeed;
|
||||
sortDuringScroll?: boolean;
|
||||
smoothStop?: boolean;
|
||||
onStart?: DragAutoScrollOnStart | null;
|
||||
onStop?: DragAutoScrollOnStop | null;
|
||||
}
|
||||
|
||||
export interface GridOptions {
|
||||
items?: HTMLElement[] | NodeList | HTMLCollection | string;
|
||||
showDuration?: number;
|
||||
showEasing?: string;
|
||||
visibleStyles?: StyleDeclaration;
|
||||
hideDuration?: number;
|
||||
hideEasing?: string;
|
||||
hiddenStyles?: StyleDeclaration;
|
||||
layout?: LayoutOptions | LayoutFunction;
|
||||
layoutOnResize?: boolean | number;
|
||||
layoutOnInit?: boolean;
|
||||
layoutDuration?: number;
|
||||
layoutEasing?: string;
|
||||
sortData?: { [key: string]: SortDataGetter } | null;
|
||||
dragEnabled?: boolean;
|
||||
dragHandle?: string | null;
|
||||
dragContainer?: HTMLElement | null;
|
||||
dragStartPredicate?: DragStartPredicateOptions | DragStartPredicate;
|
||||
dragAxis?: 'x' | 'y' | 'xy';
|
||||
dragSort?: boolean | DragSortGetter;
|
||||
dragSortLock?: ((item: Item, event: DraggerEvent | null) => boolean) | null;
|
||||
dragSortHeuristics?: DragSortHeuristics;
|
||||
dragSortPredicate?: DragSortPredicateOptions | DragSortPredicate;
|
||||
dragRelease?: DragReleaseOptions;
|
||||
dragCssProps?: DraggerCssProps;
|
||||
dragPlaceholder?: DragPlaceholderOptions;
|
||||
dragAutoScroll?: DragAutoScrollOptions;
|
||||
containerClass?: string;
|
||||
itemClass?: string;
|
||||
itemVisibleClass?: string;
|
||||
itemHiddenClass?: string;
|
||||
itemPositioningClass?: string;
|
||||
itemDraggingClass?: string;
|
||||
itemReleasingClass?: string;
|
||||
itemPlaceholderClass?: string;
|
||||
}
|
||||
|
||||
//
|
||||
// CLASSES
|
||||
//
|
||||
|
||||
export class Item {
|
||||
constructor(grid: Grid, element: HTMLElement, isActive?: boolean);
|
||||
getGrid(): Grid | undefined;
|
||||
getElement(): HTMLElement | undefined;
|
||||
getWidth(): number;
|
||||
getHeight(): number;
|
||||
getMargin(): { left: number; right: number; top: number; bottom: number };
|
||||
getPosition(): { left: number; top: number };
|
||||
isActive(): boolean;
|
||||
isVisible(): boolean;
|
||||
isShowing(): boolean;
|
||||
isHiding(): boolean;
|
||||
isPositioning(): boolean;
|
||||
isDragging(): boolean;
|
||||
isReleasing(): boolean;
|
||||
isDestroyed(): boolean;
|
||||
}
|
||||
|
||||
export class ItemLayout {
|
||||
constructor(item: Item);
|
||||
start(instant: boolean, onFinish?: (isInterrupted: boolean, item: Item) => any): void;
|
||||
stop(processCallbackQueue: boolean, targetStyles?: StyleDeclaration): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export class ItemVisibility {
|
||||
constructor(item: Item);
|
||||
show(instant: boolean, onFinish?: (isInterrupted: boolean, item: Item) => any): void;
|
||||
hide(instant: boolean, onFinish?: (isInterrupted: boolean, item: Item) => any): void;
|
||||
stop(processCallbackQueue: boolean, applyCurrentStyles?: boolean): void;
|
||||
setStyles(styles: StyleDeclaration): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export class ItemMigrate {
|
||||
constructor(item: Item);
|
||||
start(targetGrid: Grid, position: HTMLElement | number | Item, container?: HTMLElement): void;
|
||||
stop(abort?: boolean, left?: number, top?: number): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export class ItemDrag {
|
||||
constructor(item: Item);
|
||||
static autoScroller: AutoScroller;
|
||||
static defaultStartPredicate(
|
||||
item: Item,
|
||||
event: DraggerEvent,
|
||||
options?: DragStartPredicateOptions
|
||||
): boolean | undefined;
|
||||
static defaultSortPredicate(
|
||||
item: Item,
|
||||
options?: DragSortPredicateOptions
|
||||
): DragSortPredicateResult;
|
||||
stop(): void;
|
||||
sort(force?: boolean): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export class ItemDragRelease {
|
||||
constructor(item: Item);
|
||||
start(): void;
|
||||
stop(abort?: boolean, left?: number, top?: number): void;
|
||||
isJustReleased(): boolean;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export class ItemDragPlaceholder {
|
||||
constructor(item: Item);
|
||||
create(): void;
|
||||
reset(): void;
|
||||
isActive(): boolean;
|
||||
getElement(): HTMLElement | null;
|
||||
updateDimensions(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export class Emitter {
|
||||
constructor();
|
||||
on(event: string, listener: EventListener): this;
|
||||
off(event: string, listener: EventListener): this;
|
||||
clear(event: string): this;
|
||||
emit(event: string, ...args: any[]): this;
|
||||
burst(event: string, ...args: any[]): this;
|
||||
countListeners(event: string): number;
|
||||
destroy(): this;
|
||||
}
|
||||
|
||||
export class Animator {
|
||||
constructor(element: HTMLElement);
|
||||
start(
|
||||
propsFrom: StyleDeclaration,
|
||||
propsTo: StyleDeclaration,
|
||||
options?: {
|
||||
duration?: number;
|
||||
easing?: string;
|
||||
onFinish?: (...args: any[]) => any;
|
||||
}
|
||||
): void;
|
||||
stop(applyCurrentStyles?: boolean): void;
|
||||
isAnimating(): boolean;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export class Dragger {
|
||||
constructor(element: HTMLElement, cssProps?: DraggerCssProps);
|
||||
isActive(): boolean;
|
||||
setTouchAction(touchAction: string): void;
|
||||
setCssProps(props?: DraggerCssProps): void;
|
||||
getDeltaX(): number;
|
||||
getDeltaY(): number;
|
||||
getDistance(): number;
|
||||
getDeltaTime(): number;
|
||||
on<T extends keyof DraggerEvents>(event: T, listener: DraggerEvents[T]): void;
|
||||
off<T extends keyof DraggerEvents>(event: T, listener: DraggerEvents[T]): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export class AutoScroller {
|
||||
constructor();
|
||||
static AXIS_X: 1;
|
||||
static AXIS_Y: 2;
|
||||
static FORWARD: 4;
|
||||
static BACKWARD: 8;
|
||||
static LEFT: 9;
|
||||
static RIGHT: 5;
|
||||
static UP: 10;
|
||||
static DOWN: 6;
|
||||
static smoothSpeed(
|
||||
maxSpeed: number,
|
||||
acceleration: number,
|
||||
deceleration: number
|
||||
): DragAutoScrollSpeed;
|
||||
static pointerHandle(pointerSize: number): DragAutoScrollHandle;
|
||||
addItem(item: Item): void;
|
||||
updateItem(item: Item): void;
|
||||
removeItem(item: Item): void;
|
||||
isItemScrollingX(item: Item): boolean;
|
||||
isItemScrollingY(item: Item): boolean;
|
||||
isItemScrolling(item: Item): boolean;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export class Packer {
|
||||
constructor(numWorkers?: number, options?: LayoutOptions);
|
||||
setOptions(options?: LayoutOptions): void;
|
||||
createLayout(
|
||||
grid: Grid,
|
||||
id: number,
|
||||
items: Item[],
|
||||
width: number,
|
||||
height: number,
|
||||
callback: LayoutFunctionCallback
|
||||
): LayoutFunctionCancel | void;
|
||||
cancelLayout(id: number): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export default class Grid {
|
||||
constructor(element: string | HTMLElement, options?: GridOptions);
|
||||
|
||||
static Item: typeof Item;
|
||||
|
||||
static ItemLayout: typeof ItemLayout;
|
||||
|
||||
static ItemVisibility: typeof ItemVisibility;
|
||||
|
||||
static ItemMigrate: typeof ItemMigrate;
|
||||
|
||||
static ItemDrag: typeof ItemDrag;
|
||||
|
||||
static ItemDragRelease: typeof ItemDragRelease;
|
||||
|
||||
static ItemDragPlaceholder: typeof ItemDragPlaceholder;
|
||||
|
||||
static Emitter: typeof Emitter;
|
||||
|
||||
static Animator: typeof Animator;
|
||||
|
||||
static Dragger: typeof Dragger;
|
||||
|
||||
static Packer: typeof Packer;
|
||||
|
||||
static AutoScroller: typeof AutoScroller;
|
||||
|
||||
static defaultPacker: Packer;
|
||||
|
||||
static defaultOptions: GridOptions;
|
||||
|
||||
on<T extends keyof GridEvents>(event: T, listener: GridEvents[T]): this;
|
||||
|
||||
off<T extends keyof GridEvents>(event: T, listener: GridEvents[T]): this;
|
||||
|
||||
getElement(): HTMLElement;
|
||||
|
||||
getItem(target: HTMLElement | number | Item): Item | null;
|
||||
|
||||
getItems(targets?: HTMLElement | number | Item | Array<HTMLElement | number | Item>): Item[];
|
||||
|
||||
refreshItems(items?: Item[], force?: boolean): this;
|
||||
|
||||
refreshSortData(items?: Item[]): this;
|
||||
|
||||
synchronize(): this;
|
||||
|
||||
layout(instant?: boolean, onFinish?: LayoutOnFinish): this;
|
||||
|
||||
add(
|
||||
elements: HTMLElement | HTMLElement[] | NodeList | HTMLCollection,
|
||||
options?: {
|
||||
index?: number;
|
||||
active?: boolean;
|
||||
layout?: boolean | 'instant' | LayoutOnFinish;
|
||||
}
|
||||
): Item[];
|
||||
|
||||
remove(
|
||||
items: Item[],
|
||||
options?: {
|
||||
removeElements?: boolean;
|
||||
layout?: boolean | 'instant' | LayoutOnFinish;
|
||||
}
|
||||
): Item[];
|
||||
|
||||
show(
|
||||
items: Item[],
|
||||
options?: {
|
||||
instant?: boolean;
|
||||
syncWithLayout?: boolean;
|
||||
onFinish?: (items: Item[]) => any;
|
||||
layout?: boolean | 'instant' | LayoutOnFinish;
|
||||
}
|
||||
): this;
|
||||
|
||||
hide(
|
||||
items: Item[],
|
||||
options?: {
|
||||
instant?: boolean;
|
||||
syncWithLayout?: boolean;
|
||||
onFinish?: (items: Item[]) => any;
|
||||
layout?: boolean | 'instant' | LayoutOnFinish;
|
||||
}
|
||||
): this;
|
||||
|
||||
filter(
|
||||
predicate: string | ((item: Item) => boolean),
|
||||
options?: {
|
||||
instant?: boolean;
|
||||
syncWithLayout?: boolean;
|
||||
onFinish?: (items: Item[]) => any;
|
||||
layout?: boolean | 'instant' | LayoutOnFinish;
|
||||
}
|
||||
): this;
|
||||
|
||||
sort(
|
||||
comparer: ((a: Item, b: Item) => number) | string | Item[],
|
||||
options?: {
|
||||
descending?: boolean;
|
||||
layout?: boolean | 'instant' | LayoutOnFinish;
|
||||
}
|
||||
): this;
|
||||
|
||||
move(
|
||||
item: HTMLElement | number | Item,
|
||||
position: HTMLElement | number | Item,
|
||||
options?: {
|
||||
action?: 'move' | 'swap';
|
||||
layout?: boolean | 'instant' | LayoutOnFinish;
|
||||
}
|
||||
): this;
|
||||
|
||||
send(
|
||||
item: HTMLElement | number | Item,
|
||||
targetGrid: Grid,
|
||||
position: HTMLElement | number | Item,
|
||||
options?: {
|
||||
appendTo?: HTMLElement;
|
||||
layoutSender?: boolean | 'instant' | LayoutOnFinish;
|
||||
layoutReceiver?: boolean | 'instant' | LayoutOnFinish;
|
||||
}
|
||||
): this;
|
||||
|
||||
destroy(removeElements?: boolean): this;
|
||||
}
|
||||
|
||||
export as namespace Muuri;
|
||||
7
app/src/vendor/muuri-src/index.js
vendored
Normal file
7
app/src/vendor/muuri-src/index.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
export { default } from './Grid/Grid';
|
||||
126
app/src/vendor/muuri-src/ticker.js
vendored
Normal file
126
app/src/vendor/muuri-src/ticker.js
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import Ticker from './Ticker/Ticker';
|
||||
|
||||
var LAYOUT_READ = 'layoutRead';
|
||||
var LAYOUT_WRITE = 'layoutWrite';
|
||||
var VISIBILITY_READ = 'visibilityRead';
|
||||
var VISIBILITY_WRITE = 'visibilityWrite';
|
||||
var DRAG_START_READ = 'dragStartRead';
|
||||
var DRAG_START_WRITE = 'dragStartWrite';
|
||||
var DRAG_MOVE_READ = 'dragMoveRead';
|
||||
var DRAG_MOVE_WRITE = 'dragMoveWrite';
|
||||
var DRAG_SCROLL_READ = 'dragScrollRead';
|
||||
var DRAG_SCROLL_WRITE = 'dragScrollWrite';
|
||||
var DRAG_SORT_READ = 'dragSortRead';
|
||||
var PLACEHOLDER_LAYOUT_READ = 'placeholderLayoutRead';
|
||||
var PLACEHOLDER_LAYOUT_WRITE = 'placeholderLayoutWrite';
|
||||
var PLACEHOLDER_RESIZE_WRITE = 'placeholderResizeWrite';
|
||||
var AUTO_SCROLL_READ = 'autoScrollRead';
|
||||
var AUTO_SCROLL_WRITE = 'autoScrollWrite';
|
||||
var DEBOUNCE_READ = 'debounceRead';
|
||||
|
||||
var LANE_READ = 0;
|
||||
var LANE_READ_TAIL = 1;
|
||||
var LANE_WRITE = 2;
|
||||
|
||||
var ticker = new Ticker(3);
|
||||
export default ticker;
|
||||
|
||||
export function addLayoutTick(itemId, read, write) {
|
||||
ticker.add(LANE_READ, LAYOUT_READ + itemId, read);
|
||||
ticker.add(LANE_WRITE, LAYOUT_WRITE + itemId, write);
|
||||
}
|
||||
|
||||
export function cancelLayoutTick(itemId) {
|
||||
ticker.remove(LANE_READ, LAYOUT_READ + itemId);
|
||||
ticker.remove(LANE_WRITE, LAYOUT_WRITE + itemId);
|
||||
}
|
||||
|
||||
export function addVisibilityTick(itemId, read, write) {
|
||||
ticker.add(LANE_READ, VISIBILITY_READ + itemId, read);
|
||||
ticker.add(LANE_WRITE, VISIBILITY_WRITE + itemId, write);
|
||||
}
|
||||
|
||||
export function cancelVisibilityTick(itemId) {
|
||||
ticker.remove(LANE_READ, VISIBILITY_READ + itemId);
|
||||
ticker.remove(LANE_WRITE, VISIBILITY_WRITE + itemId);
|
||||
}
|
||||
|
||||
export function addDragStartTick(itemId, read, write) {
|
||||
ticker.add(LANE_READ, DRAG_START_READ + itemId, read);
|
||||
ticker.add(LANE_WRITE, DRAG_START_WRITE + itemId, write);
|
||||
}
|
||||
|
||||
export function cancelDragStartTick(itemId) {
|
||||
ticker.remove(LANE_READ, DRAG_START_READ + itemId);
|
||||
ticker.remove(LANE_WRITE, DRAG_START_WRITE + itemId);
|
||||
}
|
||||
|
||||
export function addDragMoveTick(itemId, read, write) {
|
||||
ticker.add(LANE_READ, DRAG_MOVE_READ + itemId, read);
|
||||
ticker.add(LANE_WRITE, DRAG_MOVE_WRITE + itemId, write);
|
||||
}
|
||||
|
||||
export function cancelDragMoveTick(itemId) {
|
||||
ticker.remove(LANE_READ, DRAG_MOVE_READ + itemId);
|
||||
ticker.remove(LANE_WRITE, DRAG_MOVE_WRITE + itemId);
|
||||
}
|
||||
|
||||
export function addDragScrollTick(itemId, read, write) {
|
||||
ticker.add(LANE_READ, DRAG_SCROLL_READ + itemId, read);
|
||||
ticker.add(LANE_WRITE, DRAG_SCROLL_WRITE + itemId, write);
|
||||
}
|
||||
|
||||
export function cancelDragScrollTick(itemId) {
|
||||
ticker.remove(LANE_READ, DRAG_SCROLL_READ + itemId);
|
||||
ticker.remove(LANE_WRITE, DRAG_SCROLL_WRITE + itemId);
|
||||
}
|
||||
|
||||
export function addDragSortTick(itemId, read) {
|
||||
ticker.add(LANE_READ_TAIL, DRAG_SORT_READ + itemId, read);
|
||||
}
|
||||
|
||||
export function cancelDragSortTick(itemId) {
|
||||
ticker.remove(LANE_READ_TAIL, DRAG_SORT_READ + itemId);
|
||||
}
|
||||
|
||||
export function addPlaceholderLayoutTick(itemId, read, write) {
|
||||
ticker.add(LANE_READ, PLACEHOLDER_LAYOUT_READ + itemId, read);
|
||||
ticker.add(LANE_WRITE, PLACEHOLDER_LAYOUT_WRITE + itemId, write);
|
||||
}
|
||||
|
||||
export function cancelPlaceholderLayoutTick(itemId) {
|
||||
ticker.remove(LANE_READ, PLACEHOLDER_LAYOUT_READ + itemId);
|
||||
ticker.remove(LANE_WRITE, PLACEHOLDER_LAYOUT_WRITE + itemId);
|
||||
}
|
||||
|
||||
export function addPlaceholderResizeTick(itemId, write) {
|
||||
ticker.add(LANE_WRITE, PLACEHOLDER_RESIZE_WRITE + itemId, write);
|
||||
}
|
||||
|
||||
export function cancelPlaceholderResizeTick(itemId) {
|
||||
ticker.remove(LANE_WRITE, PLACEHOLDER_RESIZE_WRITE + itemId);
|
||||
}
|
||||
|
||||
export function addAutoScrollTick(read, write) {
|
||||
ticker.add(LANE_READ, AUTO_SCROLL_READ, read);
|
||||
ticker.add(LANE_WRITE, AUTO_SCROLL_WRITE, write);
|
||||
}
|
||||
|
||||
export function cancelAutoScrollTick() {
|
||||
ticker.remove(LANE_READ, AUTO_SCROLL_READ);
|
||||
ticker.remove(LANE_WRITE, AUTO_SCROLL_WRITE);
|
||||
}
|
||||
|
||||
export function addDebounceTick(debounceId, read) {
|
||||
ticker.add(LANE_READ, DEBOUNCE_READ + debounceId, read);
|
||||
}
|
||||
|
||||
export function cancelDebounceTick(debounceId) {
|
||||
ticker.remove(LANE_READ, DEBOUNCE_READ + debounceId);
|
||||
}
|
||||
25
app/src/vendor/muuri-src/utils/addClass.js
vendored
Normal file
25
app/src/vendor/muuri-src/utils/addClass.js
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import elementMatches from './elementMatches';
|
||||
|
||||
/**
|
||||
* Add class to an element.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @param {String} className
|
||||
*/
|
||||
export default function addClass(element, className) {
|
||||
if (!className) return;
|
||||
|
||||
if (element.classList) {
|
||||
element.classList.add(className);
|
||||
} else {
|
||||
if (!elementMatches(element, '.' + className)) {
|
||||
element.className += ' ' + className;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
app/src/vendor/muuri-src/utils/arrayInsert.js
vendored
Normal file
25
app/src/vendor/muuri-src/utils/arrayInsert.js
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
var tempArray = [];
|
||||
var numberType = 'number';
|
||||
|
||||
/**
|
||||
* Insert an item or an array of items to array to a specified index. Mutates
|
||||
* the array. The index can be negative in which case the items will be added
|
||||
* to the end of the array.
|
||||
*
|
||||
* @param {Array} array
|
||||
* @param {*} items
|
||||
* @param {Number} [index=-1]
|
||||
*/
|
||||
export default function arrayInsert(array, items, index) {
|
||||
var startIndex = typeof index === numberType ? index : -1;
|
||||
if (startIndex < 0) startIndex = array.length - startIndex + 1;
|
||||
|
||||
array.splice.apply(array, tempArray.concat(startIndex, 0, items));
|
||||
tempArray.length = 0;
|
||||
}
|
||||
30
app/src/vendor/muuri-src/utils/arrayMove.js
vendored
Normal file
30
app/src/vendor/muuri-src/utils/arrayMove.js
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import normalizeArrayIndex from './normalizeArrayIndex';
|
||||
|
||||
/**
|
||||
* Move array item to another index.
|
||||
*
|
||||
* @param {Array} array
|
||||
* @param {Number} fromIndex
|
||||
* - Index (positive or negative) of the item that will be moved.
|
||||
* @param {Number} toIndex
|
||||
* - Index (positive or negative) where the item should be moved to.
|
||||
*/
|
||||
export default function arrayMove(array, fromIndex, toIndex) {
|
||||
// Make sure the array has two or more items.
|
||||
if (array.length < 2) return;
|
||||
|
||||
// Normalize the indices.
|
||||
var from = normalizeArrayIndex(array, fromIndex);
|
||||
var to = normalizeArrayIndex(array, toIndex);
|
||||
|
||||
// Add target item to the new position.
|
||||
if (from !== to) {
|
||||
array.splice(to, 0, array.splice(from, 1)[0]);
|
||||
}
|
||||
}
|
||||
33
app/src/vendor/muuri-src/utils/arraySwap.js
vendored
Normal file
33
app/src/vendor/muuri-src/utils/arraySwap.js
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import normalizeArrayIndex from './normalizeArrayIndex';
|
||||
|
||||
/**
|
||||
* Swap array items.
|
||||
*
|
||||
* @param {Array} array
|
||||
* @param {Number} index
|
||||
* - Index (positive or negative) of the item that will be swapped.
|
||||
* @param {Number} withIndex
|
||||
* - Index (positive or negative) of the other item that will be swapped.
|
||||
*/
|
||||
export default function arraySwap(array, index, withIndex) {
|
||||
// Make sure the array has two or more items.
|
||||
if (array.length < 2) return;
|
||||
|
||||
// Normalize the indices.
|
||||
var indexA = normalizeArrayIndex(array, index);
|
||||
var indexB = normalizeArrayIndex(array, withIndex);
|
||||
var temp;
|
||||
|
||||
// Swap the items.
|
||||
if (indexA !== indexB) {
|
||||
temp = array[indexA];
|
||||
array[indexA] = array[indexB];
|
||||
array[indexB] = temp;
|
||||
}
|
||||
}
|
||||
15
app/src/vendor/muuri-src/utils/createUid.js
vendored
Normal file
15
app/src/vendor/muuri-src/utils/createUid.js
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
var id = 0;
|
||||
|
||||
/**
|
||||
* Returns a unique numeric id (increments a base value on every call).
|
||||
* @returns {Number}
|
||||
*/
|
||||
export default function createUid() {
|
||||
return ++id;
|
||||
}
|
||||
64
app/src/vendor/muuri-src/utils/debounce.js
vendored
Normal file
64
app/src/vendor/muuri-src/utils/debounce.js
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { addDebounceTick, cancelDebounceTick } from '../ticker';
|
||||
|
||||
var debounceId = 0;
|
||||
|
||||
/**
|
||||
* Returns a function, that, as long as it continues to be invoked, will not
|
||||
* be triggered. The function will be called after it stops being called for
|
||||
* N milliseconds. The returned function accepts one argument which, when
|
||||
* being `true`, cancels the debounce function immediately. When the debounce
|
||||
* function is canceled it cannot be invoked again.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Number} durationMs
|
||||
* @returns {Function}
|
||||
*/
|
||||
export default function debounce(fn, durationMs) {
|
||||
var id = ++debounceId;
|
||||
var timer = 0;
|
||||
var lastTime = 0;
|
||||
var isCanceled = false;
|
||||
var tick = function (time) {
|
||||
if (isCanceled) return;
|
||||
|
||||
if (lastTime) timer -= time - lastTime;
|
||||
lastTime = time;
|
||||
|
||||
if (timer > 0) {
|
||||
addDebounceTick(id, tick);
|
||||
} else {
|
||||
timer = lastTime = 0;
|
||||
fn();
|
||||
}
|
||||
};
|
||||
|
||||
return function (cancel) {
|
||||
if (isCanceled) return;
|
||||
|
||||
if (durationMs <= 0) {
|
||||
if (cancel !== true) fn();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cancel === true) {
|
||||
isCanceled = true;
|
||||
timer = lastTime = 0;
|
||||
tick = undefined;
|
||||
cancelDebounceTick(id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer <= 0) {
|
||||
timer = durationMs;
|
||||
tick(0);
|
||||
} else {
|
||||
timer = durationMs;
|
||||
}
|
||||
};
|
||||
}
|
||||
28
app/src/vendor/muuri-src/utils/elementMatches.js
vendored
Normal file
28
app/src/vendor/muuri-src/utils/elementMatches.js
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
var ElProto = window.Element.prototype;
|
||||
var matchesFn =
|
||||
ElProto.matches ||
|
||||
ElProto.matchesSelector ||
|
||||
ElProto.webkitMatchesSelector ||
|
||||
ElProto.mozMatchesSelector ||
|
||||
ElProto.msMatchesSelector ||
|
||||
ElProto.oMatchesSelector ||
|
||||
function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if element matches a CSS selector.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} selector
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export default function elementMatches(el, selector) {
|
||||
return matchesFn.call(el, selector);
|
||||
}
|
||||
29
app/src/vendor/muuri-src/utils/getContainingBlock.js
vendored
Normal file
29
app/src/vendor/muuri-src/utils/getContainingBlock.js
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import getStyle from './getStyle';
|
||||
import isTransformed from './isTransformed';
|
||||
|
||||
/**
|
||||
* Returns an absolute positioned element's containing block, which is
|
||||
* considered to be the closest ancestor element that the target element's
|
||||
* positioning is relative to. Disclaimer: this only works as intended for
|
||||
* absolute positioned elements.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @returns {(Document|Element)}
|
||||
*/
|
||||
export default function getContainingBlock(element) {
|
||||
// As long as the containing block is an element, static and not
|
||||
// transformed, try to get the element's parent element and fallback to
|
||||
// document. https://github.com/niklasramo/mezr/blob/0.6.1/mezr.js#L339
|
||||
var doc = document;
|
||||
var res = element || doc;
|
||||
while (res && res !== doc && getStyle(res, 'position') === 'static' && !isTransformed(res)) {
|
||||
res = res.parentElement || doc;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
33
app/src/vendor/muuri-src/utils/getCurrentStyles.js
vendored
Normal file
33
app/src/vendor/muuri-src/utils/getCurrentStyles.js
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import getStyle from './getStyle';
|
||||
import getStyleName from './getStyleName';
|
||||
|
||||
/**
|
||||
* Get current values of the provided styles definition object or array.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @param {(Object|Array} styles
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function getCurrentStyles(element, styles) {
|
||||
var result = {};
|
||||
var prop, i;
|
||||
|
||||
if (Array.isArray(styles)) {
|
||||
for (i = 0; i < styles.length; i++) {
|
||||
prop = styles[i];
|
||||
result[prop] = getStyle(element, getStyleName(prop));
|
||||
}
|
||||
} else {
|
||||
for (prop in styles) {
|
||||
result[prop] = getStyle(element, getStyleName(prop));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
21
app/src/vendor/muuri-src/utils/getIntersectionArea.js
vendored
Normal file
21
app/src/vendor/muuri-src/utils/getIntersectionArea.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import isOverlapping from './isOverlapping';
|
||||
|
||||
/**
|
||||
* Calculate intersection area between two rectangle.
|
||||
*
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
export default function getIntersectionArea(a, b) {
|
||||
if (!isOverlapping(a, b)) return 0;
|
||||
var width = Math.min(a.left + a.width, b.left + b.width) - Math.max(a.left, b.left);
|
||||
var height = Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top);
|
||||
return width * height;
|
||||
}
|
||||
22
app/src/vendor/muuri-src/utils/getIntersectionScore.js
vendored
Normal file
22
app/src/vendor/muuri-src/utils/getIntersectionScore.js
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import getIntersectionArea from './getIntersectionArea';
|
||||
|
||||
/**
|
||||
* Calculate how many percent the intersection area of two rectangles is from
|
||||
* the maximum potential intersection area between the rectangles.
|
||||
*
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
export default function getIntersectionScore(a, b) {
|
||||
var area = getIntersectionArea(a, b);
|
||||
if (!area) return 0;
|
||||
var maxArea = Math.min(a.width, b.width) * Math.min(a.height, b.height);
|
||||
return (area / maxArea) * 100;
|
||||
}
|
||||
90
app/src/vendor/muuri-src/utils/getOffsetDiff.js
vendored
Normal file
90
app/src/vendor/muuri-src/utils/getOffsetDiff.js
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import getContainingBlock from './getContainingBlock';
|
||||
import getStyleAsFloat from './getStyleAsFloat';
|
||||
|
||||
var offsetA = {};
|
||||
var offsetB = {};
|
||||
var offsetDiff = {};
|
||||
|
||||
/**
|
||||
* Returns the element's document offset, which in practice means the vertical
|
||||
* and horizontal distance between the element's northwest corner and the
|
||||
* document's northwest corner. Note that this function always returns the same
|
||||
* object so be sure to read the data from it instead using it as a reference.
|
||||
*
|
||||
* @param {(Document|Element|Window)} element
|
||||
* @param {Object} [offsetData]
|
||||
* - Optional data object where the offset data will be inserted to. If not
|
||||
* provided a new object will be created for the return data.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getOffset(element, offsetData) {
|
||||
var offset = offsetData || {};
|
||||
var rect;
|
||||
|
||||
// Set up return data.
|
||||
offset.left = 0;
|
||||
offset.top = 0;
|
||||
|
||||
// Document's offsets are always 0.
|
||||
if (element === document) return offset;
|
||||
|
||||
// Add viewport scroll left/top to the respective offsets.
|
||||
offset.left = window.pageXOffset || 0;
|
||||
offset.top = window.pageYOffset || 0;
|
||||
|
||||
// Window's offsets are the viewport scroll left/top values.
|
||||
if (element.self === window.self) return offset;
|
||||
|
||||
// Add element's client rects to the offsets.
|
||||
rect = element.getBoundingClientRect();
|
||||
offset.left += rect.left;
|
||||
offset.top += rect.top;
|
||||
|
||||
// Exclude element's borders from the offset.
|
||||
offset.left += getStyleAsFloat(element, 'border-left-width');
|
||||
offset.top += getStyleAsFloat(element, 'border-top-width');
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the offset difference two elements.
|
||||
*
|
||||
* @param {HTMLElement} elemA
|
||||
* @param {HTMLElement} elemB
|
||||
* @param {Boolean} [compareContainingBlocks=false]
|
||||
* - When this is set to true the containing blocks of the provided elements
|
||||
* will be used for calculating the difference. Otherwise the provided
|
||||
* elements will be compared directly.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export default function getOffsetDiff(elemA, elemB, compareContainingBlocks) {
|
||||
offsetDiff.left = 0;
|
||||
offsetDiff.top = 0;
|
||||
|
||||
// If elements are same let's return early.
|
||||
if (elemA === elemB) return offsetDiff;
|
||||
|
||||
// Compare containing blocks if necessary.
|
||||
if (compareContainingBlocks) {
|
||||
elemA = getContainingBlock(elemA);
|
||||
elemB = getContainingBlock(elemB);
|
||||
|
||||
// If containing blocks are identical, let's return early.
|
||||
if (elemA === elemB) return offsetDiff;
|
||||
}
|
||||
|
||||
// Finally, let's calculate the offset diff.
|
||||
getOffset(elemA, offsetA);
|
||||
getOffset(elemB, offsetB);
|
||||
offsetDiff.left = offsetB.left - offsetA.left;
|
||||
offsetDiff.top = offsetB.top - offsetA.top;
|
||||
|
||||
return offsetDiff;
|
||||
}
|
||||
34
app/src/vendor/muuri-src/utils/getPrefixedPropName.js
vendored
Normal file
34
app/src/vendor/muuri-src/utils/getPrefixedPropName.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Forked from hammer.js:
|
||||
* https://github.com/hammerjs/hammer.js/blob/563b5b1e4bfbb5796798dd286cd57b7c56f1eb9e/src/utils/prefixed.js
|
||||
*/
|
||||
|
||||
// Playing it safe here, test all potential prefixes capitalized and lowercase.
|
||||
var vendorPrefixes = ['', 'webkit', 'moz', 'ms', 'o', 'Webkit', 'Moz', 'MS', 'O'];
|
||||
var cache = {};
|
||||
|
||||
/**
|
||||
* Get prefixed CSS property name when given a non-prefixed CSS property name.
|
||||
* Returns null if the property is not supported at all.
|
||||
*
|
||||
* @param {CSSStyleDeclaration} style
|
||||
* @param {String} prop
|
||||
* @returns {String}
|
||||
*/
|
||||
export default function getPrefixedPropName(style, prop) {
|
||||
var prefixedProp = cache[prop] || '';
|
||||
if (prefixedProp) return prefixedProp;
|
||||
|
||||
var camelProp = prop[0].toUpperCase() + prop.slice(1);
|
||||
var i = 0;
|
||||
while (i < vendorPrefixes.length) {
|
||||
prefixedProp = vendorPrefixes[i] ? vendorPrefixes[i] + camelProp : prop;
|
||||
if (prefixedProp in style) {
|
||||
cache[prop] = prefixedProp;
|
||||
return prefixedProp;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
42
app/src/vendor/muuri-src/utils/getScrollableAncestors.js
vendored
Normal file
42
app/src/vendor/muuri-src/utils/getScrollableAncestors.js
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import isScrollable from './isScrollable';
|
||||
|
||||
/**
|
||||
* Collect element's ancestors that are potentially scrollable elements. The
|
||||
* provided element is also also included in the check, meaning that if it is
|
||||
* scrollable it is added to the result array.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @param {Array} [result]
|
||||
* @returns {Array}
|
||||
*/
|
||||
export default function getScrollableAncestors(element, result) {
|
||||
result = result || [];
|
||||
|
||||
// Find scroll parents.
|
||||
while (element && element !== document) {
|
||||
// If element is inside ShadowDOM let's get it's host node from the real
|
||||
// DOM and continue looping.
|
||||
if (element.getRootNode && element instanceof DocumentFragment) {
|
||||
element = element.getRootNode().host;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If element is scrollable let's add it to the scrollable list.
|
||||
if (isScrollable(element)) {
|
||||
result.push(element);
|
||||
}
|
||||
|
||||
element = element.parentNode;
|
||||
}
|
||||
|
||||
// Always add window to the results.
|
||||
result.push(window);
|
||||
|
||||
return result;
|
||||
}
|
||||
25
app/src/vendor/muuri-src/utils/getStyle.js
vendored
Normal file
25
app/src/vendor/muuri-src/utils/getStyle.js
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
var cache = typeof WeakMap === 'function' ? new WeakMap() : null;
|
||||
|
||||
/**
|
||||
* Returns the computed value of an element's style property as a string.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @param {String} style
|
||||
* @returns {String}
|
||||
*/
|
||||
export default function getStyle(element, style) {
|
||||
var styles = cache && cache.get(element);
|
||||
|
||||
if (!styles) {
|
||||
styles = window.getComputedStyle(element, null);
|
||||
if (cache) cache.set(element, styles);
|
||||
}
|
||||
|
||||
return styles.getPropertyValue(style);
|
||||
}
|
||||
19
app/src/vendor/muuri-src/utils/getStyleAsFloat.js
vendored
Normal file
19
app/src/vendor/muuri-src/utils/getStyleAsFloat.js
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import getStyle from './getStyle';
|
||||
|
||||
/**
|
||||
* Returns the computed value of an element's style property transformed into
|
||||
* a float value.
|
||||
*
|
||||
* @param {HTMLElement} el
|
||||
* @param {String} style
|
||||
* @returns {Number}
|
||||
*/
|
||||
export default function getStyleAsFloat(el, style) {
|
||||
return parseFloat(getStyle(el, style)) || 0;
|
||||
}
|
||||
32
app/src/vendor/muuri-src/utils/getStyleName.js
vendored
Normal file
32
app/src/vendor/muuri-src/utils/getStyleName.js
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
var styleNameRegEx = /([A-Z])/g;
|
||||
var prefixRegex = /^(webkit-|moz-|ms-|o-)/;
|
||||
var msPrefixRegex = /^(-m-s-)/;
|
||||
|
||||
/**
|
||||
* Transforms a camel case style property to kebab case style property. Handles
|
||||
* vendor prefixed properties elegantly as well, e.g. "WebkitTransform" and
|
||||
* "webkitTransform" are both transformed into "-webkit-transform".
|
||||
*
|
||||
* @param {String} property
|
||||
* @returns {String}
|
||||
*/
|
||||
export default function getStyleName(property) {
|
||||
// Initial slicing, turns "fooBarProp" into "foo-bar-prop".
|
||||
var styleName = property.replace(styleNameRegEx, '-$1').toLowerCase();
|
||||
|
||||
// Handle properties that start with "webkit", "moz", "ms" or "o" prefix (we
|
||||
// need to add an extra '-' to the beginnig).
|
||||
styleName = styleName.replace(prefixRegex, '-$1');
|
||||
|
||||
// Handle properties that start with "MS" prefix (we need to transform the
|
||||
// "-m-s-" into "-ms-").
|
||||
styleName = styleName.replace(msPrefixRegex, '-ms-');
|
||||
|
||||
return styleName;
|
||||
}
|
||||
43
app/src/vendor/muuri-src/utils/getTranslate.js
vendored
Normal file
43
app/src/vendor/muuri-src/utils/getTranslate.js
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import getStyle from './getStyle';
|
||||
import transformStyle from './transformStyle';
|
||||
|
||||
var translateValue = {};
|
||||
var transformNone = 'none';
|
||||
var rxMat3d = /^matrix3d/;
|
||||
var rxMatTx = /([^,]*,){4}/;
|
||||
var rxMat3dTx = /([^,]*,){12}/;
|
||||
var rxNextItem = /[^,]*,/;
|
||||
|
||||
/**
|
||||
* Returns the element's computed translateX and translateY values as a floats.
|
||||
* The returned object is always the same object and updated every time this
|
||||
* function is called.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @returns {Object}
|
||||
*/
|
||||
export default function getTranslate(element) {
|
||||
translateValue.x = 0;
|
||||
translateValue.y = 0;
|
||||
|
||||
var transform = getStyle(element, transformStyle);
|
||||
if (!transform || transform === transformNone) {
|
||||
return translateValue;
|
||||
}
|
||||
|
||||
// Transform style can be in either matrix3d(...) or matrix(...).
|
||||
var isMat3d = rxMat3d.test(transform);
|
||||
var tX = transform.replace(isMat3d ? rxMat3dTx : rxMatTx, '');
|
||||
var tY = tX.replace(rxNextItem, '');
|
||||
|
||||
translateValue.x = parseFloat(tX) || 0;
|
||||
translateValue.y = parseFloat(tY) || 0;
|
||||
|
||||
return translateValue;
|
||||
}
|
||||
17
app/src/vendor/muuri-src/utils/getTranslateString.js
vendored
Normal file
17
app/src/vendor/muuri-src/utils/getTranslateString.js
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transform translateX and translateY value into CSS transform style
|
||||
* property's value.
|
||||
*
|
||||
* @param {Number} x
|
||||
* @param {Number} y
|
||||
* @returns {String}
|
||||
*/
|
||||
export default function getTranslateString(x, y) {
|
||||
return 'translateX(' + x + 'px) translateY(' + y + 'px)';
|
||||
}
|
||||
29
app/src/vendor/muuri-src/utils/getUnprefixedPropName.js
vendored
Normal file
29
app/src/vendor/muuri-src/utils/getUnprefixedPropName.js
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
var unprefixRegEx = /^(webkit|moz|ms|o|Webkit|Moz|MS|O)(?=[A-Z])/;
|
||||
var cache = {};
|
||||
|
||||
/**
|
||||
* Remove any potential vendor prefixes from a property name.
|
||||
*
|
||||
* @param {String} prop
|
||||
* @returns {String}
|
||||
*/
|
||||
export default function getUnprefixedPropName(prop) {
|
||||
var result = cache[prop];
|
||||
if (result) return result;
|
||||
|
||||
result = prop.replace(unprefixRegEx, '');
|
||||
|
||||
if (result !== prop) {
|
||||
result = result[0].toLowerCase() + result.slice(1);
|
||||
}
|
||||
|
||||
cache[prop] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
27
app/src/vendor/muuri-src/utils/hasPassiveEvents.js
vendored
Normal file
27
app/src/vendor/muuri-src/utils/hasPassiveEvents.js
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if passive events are supported.
|
||||
* https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export default function hasPassiveEvents() {
|
||||
var isPassiveEventsSupported = false;
|
||||
|
||||
try {
|
||||
var passiveOpts = Object.defineProperty({}, 'passive', {
|
||||
get: function () {
|
||||
isPassiveEventsSupported = true;
|
||||
},
|
||||
});
|
||||
window.addEventListener('testPassive', null, passiveOpts);
|
||||
window.removeEventListener('testPassive', null, passiveOpts);
|
||||
} catch (e) {}
|
||||
|
||||
return isPassiveEventsSupported;
|
||||
}
|
||||
17
app/src/vendor/muuri-src/utils/isFunction.js
vendored
Normal file
17
app/src/vendor/muuri-src/utils/isFunction.js
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
var functionType = 'function';
|
||||
|
||||
/**
|
||||
* Check if a value is a function.
|
||||
*
|
||||
* @param {*} val
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export default function isFunction(val) {
|
||||
return typeof val === functionType;
|
||||
}
|
||||
26
app/src/vendor/muuri-src/utils/isNative.js
vendored
Normal file
26
app/src/vendor/muuri-src/utils/isNative.js
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import isFunction from './isFunction';
|
||||
|
||||
var nativeCode = '[native code]';
|
||||
|
||||
/**
|
||||
* Check if a value (e.g. a method or constructor) is native code. Good for
|
||||
* detecting when a polyfill is used and when not.
|
||||
*
|
||||
* @param {*} feat
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export default function isNative(feat) {
|
||||
var S = window.Symbol;
|
||||
return !!(
|
||||
feat &&
|
||||
isFunction(S) &&
|
||||
isFunction(S.toString) &&
|
||||
S(feat).toString().indexOf(nativeCode) > -1
|
||||
);
|
||||
}
|
||||
19
app/src/vendor/muuri-src/utils/isNodeList.js
vendored
Normal file
19
app/src/vendor/muuri-src/utils/isNodeList.js
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
var htmlCollectionType = '[object HTMLCollection]';
|
||||
var nodeListType = '[object NodeList]';
|
||||
|
||||
/**
|
||||
* Check if a value is a node list or a html collection.
|
||||
*
|
||||
* @param {*} val
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export default function isNodeList(val) {
|
||||
var type = Object.prototype.toString.call(val);
|
||||
return type === htmlCollectionType || type === nodeListType;
|
||||
}
|
||||
21
app/src/vendor/muuri-src/utils/isOverlapping.js
vendored
Normal file
21
app/src/vendor/muuri-src/utils/isOverlapping.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if two rectangles are overlapping.
|
||||
*
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
export default function isOverlapping(a, b) {
|
||||
return !(
|
||||
a.left + a.width <= b.left ||
|
||||
b.left + b.width <= a.left ||
|
||||
a.top + a.height <= b.top ||
|
||||
b.top + b.height <= a.top
|
||||
);
|
||||
}
|
||||
19
app/src/vendor/muuri-src/utils/isPlainObject.js
vendored
Normal file
19
app/src/vendor/muuri-src/utils/isPlainObject.js
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
var objectType = 'object';
|
||||
var objectToStringType = '[object Object]';
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
/**
|
||||
* Check if a value is a plain object.
|
||||
*
|
||||
* @param {*} val
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export default function isPlainObject(val) {
|
||||
return typeof val === objectType && toString.call(val) === objectToStringType;
|
||||
}
|
||||
31
app/src/vendor/muuri-src/utils/isScrollable.js
vendored
Normal file
31
app/src/vendor/muuri-src/utils/isScrollable.js
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import getStyle from './getStyle';
|
||||
|
||||
/**
|
||||
* Check if overflow style value is scrollable.
|
||||
*
|
||||
* @param {String} value
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function isScrollableOverflow(value) {
|
||||
return value === 'auto' || value === 'scroll' || value === 'overlay';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an element is scrollable.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export default function isScrollable(element) {
|
||||
return (
|
||||
isScrollableOverflow(getStyle(element, 'overflow')) ||
|
||||
isScrollableOverflow(getStyle(element, 'overflow-x')) ||
|
||||
isScrollableOverflow(getStyle(element, 'overflow-y'))
|
||||
);
|
||||
}
|
||||
35
app/src/vendor/muuri-src/utils/isTransformed.js
vendored
Normal file
35
app/src/vendor/muuri-src/utils/isTransformed.js
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import getStyle from './getStyle';
|
||||
import transformStyle from './transformStyle';
|
||||
|
||||
var transformNone = 'none';
|
||||
var displayInline = 'inline';
|
||||
var displayNone = 'none';
|
||||
var displayStyle = 'display';
|
||||
|
||||
/**
|
||||
* Returns true if element is transformed, false if not. In practice the
|
||||
* element's display value must be anything else than "none" or "inline" as
|
||||
* well as have a valid transform value applied in order to be counted as a
|
||||
* transformed element.
|
||||
*
|
||||
* Borrowed from Mezr (v0.6.1):
|
||||
* https://github.com/niklasramo/mezr/blob/0.6.1/mezr.js#L661
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export default function isTransformed(element) {
|
||||
var transform = getStyle(element, transformStyle);
|
||||
if (!transform || transform === transformNone) return false;
|
||||
|
||||
var display = getStyle(element, displayStyle);
|
||||
if (display === displayInline || display === displayNone) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
7
app/src/vendor/muuri-src/utils/noop.js
vendored
Normal file
7
app/src/vendor/muuri-src/utils/noop.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
export default function noop() {}
|
||||
21
app/src/vendor/muuri-src/utils/normalizeArrayIndex.js
vendored
Normal file
21
app/src/vendor/muuri-src/utils/normalizeArrayIndex.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/**
|
||||
* Normalize array index. Basically this function makes sure that the provided
|
||||
* array index is within the bounds of the provided array and also transforms
|
||||
* negative index to the matching positive index. The third (optional) argument
|
||||
* allows you to define offset for array's length in case you are adding items
|
||||
* to the array or removing items from the array.
|
||||
*
|
||||
* @param {Array} array
|
||||
* @param {Number} index
|
||||
* @param {Number} [sizeOffset]
|
||||
*/
|
||||
export default function normalizeArrayIndex(array, index, sizeOffset) {
|
||||
var maxIndex = Math.max(0, array.length - 1 + (sizeOffset || 0));
|
||||
return index > maxIndex ? maxIndex : index < 0 ? Math.max(maxIndex + index + 1, 0) : index;
|
||||
}
|
||||
25
app/src/vendor/muuri-src/utils/raf.js
vendored
Normal file
25
app/src/vendor/muuri-src/utils/raf.js
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
var dt = 1000 / 60;
|
||||
|
||||
var raf = (
|
||||
window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function (callback) {
|
||||
return this.setTimeout(function () {
|
||||
callback(Date.now());
|
||||
}, dt);
|
||||
}
|
||||
).bind(window);
|
||||
|
||||
/**
|
||||
* @param {Function} callback
|
||||
* @returns {Number}
|
||||
*/
|
||||
export default raf;
|
||||
27
app/src/vendor/muuri-src/utils/removeClass.js
vendored
Normal file
27
app/src/vendor/muuri-src/utils/removeClass.js
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import elementMatches from './elementMatches';
|
||||
|
||||
/**
|
||||
* Remove class from an element.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @param {String} className
|
||||
*/
|
||||
export default function removeClass(element, className) {
|
||||
if (!className) return;
|
||||
|
||||
if (element.classList) {
|
||||
element.classList.remove(className);
|
||||
} else {
|
||||
if (elementMatches(element, '.' + className)) {
|
||||
element.className = (' ' + element.className + ' ')
|
||||
.replace(' ' + className + ' ', ' ')
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
app/src/vendor/muuri-src/utils/setStyles.js
vendored
Normal file
17
app/src/vendor/muuri-src/utils/setStyles.js
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set inline styles to an element.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @param {Object} styles
|
||||
*/
|
||||
export default function setStyles(element, styles) {
|
||||
for (var prop in styles) {
|
||||
element.style[prop] = styles[prop];
|
||||
}
|
||||
}
|
||||
17
app/src/vendor/muuri-src/utils/toArray.js
vendored
Normal file
17
app/src/vendor/muuri-src/utils/toArray.js
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import isNodeList from './isNodeList';
|
||||
|
||||
/**
|
||||
* Converts a value to an array or clones an array.
|
||||
*
|
||||
* @param {*} val
|
||||
* @returns {Array}
|
||||
*/
|
||||
export default function toArray(val) {
|
||||
return isNodeList(val) ? Array.prototype.slice.call(val) : Array.prototype.concat(val);
|
||||
}
|
||||
11
app/src/vendor/muuri-src/utils/transformProp.js
vendored
Normal file
11
app/src/vendor/muuri-src/utils/transformProp.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import getPrefixedPropName from './getPrefixedPropName';
|
||||
|
||||
var transformProp = getPrefixedPropName(document.documentElement.style, 'transform') || 'transform';
|
||||
|
||||
export default transformProp;
|
||||
12
app/src/vendor/muuri-src/utils/transformStyle.js
vendored
Normal file
12
app/src/vendor/muuri-src/utils/transformStyle.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Haltu Oy
|
||||
* Released under the MIT license
|
||||
* https://github.com/haltu/muuri/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import transformProp from './transformProp';
|
||||
import getStyleName from './getStyleName';
|
||||
|
||||
var transformStyle = getStyleName(transformProp);
|
||||
|
||||
export default transformStyle;
|
||||
@ -8,6 +8,7 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
'muuri': path.resolve(__dirname, './src/vendor/muuri-src/index.js'),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user