Compare commits
2 Commits
2bd53e3744
...
bf5a3bc343
| Author | SHA1 | Date | |
|---|---|---|---|
| bf5a3bc343 | |||
| e04353a5fa |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 329 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 782 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 788 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 550 KiB |
|
Before Width: | Height: | Size: 669 KiB |
|
Before Width: | Height: | Size: 289 KiB |
BIN
ScreenShot_2026-01-30_100758_517.png
Normal file
|
After Width: | Height: | Size: 448 KiB |
BIN
ScreenShot_2026-01-30_104551_602.png
Normal file
|
After Width: | Height: | Size: 424 KiB |
BIN
ScreenShot_2026-01-30_104642_622.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
ScreenShot_2026-01-30_131448_795.png
Normal file
|
After Width: | Height: | Size: 344 KiB |
BIN
ScreenShot_2026-01-30_175427_657.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
138
app/src/components/FolderCard/index.vue
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import type { Folder } from '@/store/useFoldersStore';
|
||||||
|
|
||||||
|
type FolderChild =
|
||||||
|
| { type: 'icon'; id: string; icon?: { name: string; img?: string; text?: string; bgColor?: string } }
|
||||||
|
| { type: 'widget'; id: string; widget?: { component: string } };
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
folder: Folder;
|
||||||
|
children: FolderChild[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'open'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const previewChildren = computed(() => props.children.slice(0, 4));
|
||||||
|
|
||||||
|
const initialText = (child: FolderChild) => {
|
||||||
|
if (child.type === 'icon') {
|
||||||
|
const label = child.icon?.text?.trim() || child.icon?.name || '';
|
||||||
|
return label ? label.charAt(0) : 'I';
|
||||||
|
}
|
||||||
|
return 'W';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<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>
|
||||||
|
<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%;
|
||||||
|
border-radius: var(--icon-radius, #{$border-radius-small});
|
||||||
|
background: linear-gradient(145deg, rgba(0, 122, 255, 0.12), rgba(0, 122, 255, 0.04));
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform $motion-duration-sm $motion-easing-standard, box-shadow $motion-duration-sm $motion-easing-standard;
|
||||||
|
box-shadow: $shadow-md;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: $shadow-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-template-rows: repeat(2, 1fr);
|
||||||
|
gap: 6px;
|
||||||
|
padding-top: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-chip {
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
color: #222;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-type='widget'] {
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-empty {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px dashed rgba(0, 0, 0, 0.15);
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
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>
|
||||||
329
app/src/components/FolderDialog/index.vue
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, nextTick, ref, watch } from 'vue';
|
||||||
|
import type { Folder } from '@/store/useFoldersStore';
|
||||||
|
|
||||||
|
type FolderChild =
|
||||||
|
| { type: 'icon'; id: string; icon?: { name: string; img?: string; text?: string; bgColor?: string; url?: string } }
|
||||||
|
| { type: 'widget'; id: string; widget?: { component: string } };
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
folder: Folder | null;
|
||||||
|
children: FolderChild[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'close'): void;
|
||||||
|
(e: 'rename', payload: { id: string; name: string }): void;
|
||||||
|
(e: 'remove', payload: { folderId: string; child: FolderChild }): void;
|
||||||
|
(e: 'open-item', payload: FolderChild): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const localName = ref('文件夹');
|
||||||
|
const editingName = ref(false);
|
||||||
|
const nameInputRef = ref<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.folder?.name,
|
||||||
|
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() || '文件夹';
|
||||||
|
emit('rename', { id: props.folder.id, 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') {
|
||||||
|
const label = child.icon?.text?.trim() || child.icon?.name || '';
|
||||||
|
return label ? label.charAt(0) : 'I';
|
||||||
|
}
|
||||||
|
return 'W';
|
||||||
|
};
|
||||||
|
|
||||||
|
const labelFor = (child: FolderChild) =>
|
||||||
|
child.type === 'icon' ? child.icon?.name ?? '未命名' : child.widget?.component ?? 'Widget';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<teleport to="body">
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '@/styles/tokens.scss';
|
||||||
|
|
||||||
|
.folder-dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
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(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;
|
||||||
|
align-items: center;
|
||||||
|
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;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .folder-item-remove {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
color: #222;
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-type='widget'] {
|
||||||
|
background: rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-item-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.7);
|
||||||
|
text-align: center;
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-item-remove {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 6px;
|
||||||
|
border: none;
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
color: #c0392b;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
transition: opacity $motion-duration-sm $motion-easing-standard, transform $motion-duration-sm $motion-easing-standard;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-empty {
|
||||||
|
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>
|
||||||
@ -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 @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>
|
<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>
|
||||||
|
<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">
|
<ul v-else-if="uiStore.contextMenu.itemType === 'desktop'" class="desktop-menu">
|
||||||
<li class="desktop-item" @click="handleDesktopAction('add-icon')">
|
<li class="desktop-item" @click="handleDesktopAction('add-icon')">
|
||||||
<span class="desktop-label">添加图标</span>
|
<span class="desktop-label">添加图标</span>
|
||||||
@ -74,6 +86,7 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
|||||||
import { useUIStore } from '@/store/useUIStore';
|
import { useUIStore } from '@/store/useUIStore';
|
||||||
import { useLayoutStore } from '@/store/useLayoutStore';
|
import { useLayoutStore } from '@/store/useLayoutStore';
|
||||||
import { useWidgetsStore } from '@/store/useWidgetsStore';
|
import { useWidgetsStore } from '@/store/useWidgetsStore';
|
||||||
|
import { useFoldersStore } from '@/store/useFoldersStore';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'add-icon'): void;
|
(event: 'add-icon'): void;
|
||||||
@ -85,6 +98,7 @@ const emit = defineEmits<{
|
|||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const layoutStore = useLayoutStore();
|
const layoutStore = useLayoutStore();
|
||||||
const widgetsStore = useWidgetsStore();
|
const widgetsStore = useWidgetsStore();
|
||||||
|
const foldersStore = useFoldersStore();
|
||||||
|
|
||||||
type ItemSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
|
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 widgetSizes: ItemSize[] = ['1x1', '1x2', '2x1', '2x2', '2x4'];
|
||||||
const iconSizes: 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 menuRef = ref<HTMLElement | null>(null);
|
||||||
const menuPosition = ref({ x: 0, y: 0 });
|
const menuPosition = ref({ x: 0, y: 0 });
|
||||||
@ -147,9 +162,11 @@ const close = () => {
|
|||||||
const deleteItem = () => {
|
const deleteItem = () => {
|
||||||
if (uiStore.contextMenu.itemId) {
|
if (uiStore.contextMenu.itemId) {
|
||||||
if (uiStore.contextMenu.itemType === 'icon') {
|
if (uiStore.contextMenu.itemType === 'icon') {
|
||||||
layoutStore.deleteIcon(uiStore.contextMenu.itemId);
|
uiStore.requestDelete(uiStore.contextMenu.itemId, 'icon');
|
||||||
} else if (uiStore.contextMenu.itemType === 'widget') {
|
} 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();
|
close();
|
||||||
@ -185,6 +202,13 @@ const changeIconSize = (newSize: ItemSize) => {
|
|||||||
close();
|
close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const changeFolderSize = (newSize: ItemSize) => {
|
||||||
|
if (uiStore.contextMenu.itemId) {
|
||||||
|
foldersStore.updateFolderSize(uiStore.contextMenu.itemId, newSize);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
const handleDesktopAction = (action: DesktopAction) => {
|
const handleDesktopAction = (action: DesktopAction) => {
|
||||||
if (action === 'add-icon') {
|
if (action === 'add-icon') {
|
||||||
emit('add-icon');
|
emit('add-icon');
|
||||||
|
|||||||
127
app/src/store/useFoldersStore.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
|
|
||||||
|
type FolderItemType = 'icon' | 'widget';
|
||||||
|
type FolderSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
|
||||||
|
|
||||||
|
export interface FolderItem {
|
||||||
|
id: string;
|
||||||
|
type: FolderItemType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Folder {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
items: FolderItem[];
|
||||||
|
groupId?: string;
|
||||||
|
size?: FolderSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FolderState {
|
||||||
|
folders: Folder[];
|
||||||
|
}
|
||||||
|
|
||||||
|
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: 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'))
|
||||||
|
.map(item => ({ id: item.id, type: item.type }))
|
||||||
|
: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadFolders = (): Folder[] => {
|
||||||
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (!raw) return [];
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw) as Folder[];
|
||||||
|
if (!Array.isArray(parsed)) return [];
|
||||||
|
return parsed.map(normalizeFolder);
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFoldersStore = defineStore('folders', {
|
||||||
|
state: (): FolderState => ({
|
||||||
|
folders: loadFolders(),
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
persist() {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.folders));
|
||||||
|
},
|
||||||
|
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: normalizeFolderName(payload.name),
|
||||||
|
items: payload.items ?? [],
|
||||||
|
groupId: payload.groupId ?? DEFAULT_GROUP_ID,
|
||||||
|
size: payload.size ?? '1x1',
|
||||||
|
});
|
||||||
|
this.folders.push(folder);
|
||||||
|
this.persist();
|
||||||
|
return folder;
|
||||||
|
},
|
||||||
|
renameFolder(folderId: string, name: string) {
|
||||||
|
const folder = this.folders.find(item => item.id === folderId);
|
||||||
|
if (!folder) return;
|
||||||
|
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) {
|
||||||
|
const folder = this.folders.find(f => f.id === folderId);
|
||||||
|
if (!folder) return;
|
||||||
|
const exists = folder.items.some(it => it.id === item.id && it.type === item.type);
|
||||||
|
if (!exists) {
|
||||||
|
folder.items.push({ id: item.id, type: item.type });
|
||||||
|
this.persist();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeItem(folderId: string, itemId: string, itemType: FolderItemType) {
|
||||||
|
const folder = this.folders.find(f => f.id === folderId);
|
||||||
|
if (!folder) return;
|
||||||
|
folder.items = folder.items.filter(item => !(item.id === itemId && item.type === itemType));
|
||||||
|
if (!folder.items.length) {
|
||||||
|
this.deleteFolder(folderId);
|
||||||
|
} else {
|
||||||
|
this.persist();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteFolder(folderId: string) {
|
||||||
|
const idx = this.folders.findIndex(f => f.id === folderId);
|
||||||
|
if (idx !== -1) {
|
||||||
|
this.folders.splice(idx, 1);
|
||||||
|
this.persist();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearEmptyFolders() {
|
||||||
|
const before = this.folders.length;
|
||||||
|
this.folders = this.folders.filter(folder => folder.items.length > 0);
|
||||||
|
if (this.folders.length !== before) {
|
||||||
|
this.persist();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useFoldersStore, import.meta.hot));
|
||||||
|
}
|
||||||
@ -1,18 +1,18 @@
|
|||||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
|
|
||||||
type IconSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
|
type IconSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
|
||||||
const DEFAULT_GROUP_ID = 'home';
|
const DEFAULT_GROUP_ID = 'home';
|
||||||
|
|
||||||
// 模拟数据(来自截图参考)
|
|
||||||
interface Icon {
|
interface Icon {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
size?: IconSize;
|
size?: IconSize;
|
||||||
img?: string; // 可选:用于图片图标(如徽标)
|
img?: string;
|
||||||
text?: string; // 可选:用于文字图标
|
text?: string;
|
||||||
bgColor?: string; // 可选:纯色背景
|
bgColor?: string;
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
|
folderId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DragState {
|
interface DragState {
|
||||||
@ -32,21 +32,21 @@ interface LayoutState {
|
|||||||
|
|
||||||
const defaultIcons: Icon[] = [
|
const defaultIcons: Icon[] = [
|
||||||
{ id: '1', name: '淘宝', url: 'https://taobao.com', bgColor: '#ff4f00' },
|
{ id: '1', name: '淘宝', url: 'https://taobao.com', bgColor: '#ff4f00' },
|
||||||
{ id: '2', name: '京东商城', url: 'https://jd.com', bgColor: '#e4393c' },
|
{ id: '2', name: '京东', url: 'https://jd.com', bgColor: '#e4393c' },
|
||||||
{ id: '3', name: '百度', url: 'https://baidu.com', bgColor: '#3388ff' },
|
{ id: '3', name: '百度', url: 'https://baidu.com', bgColor: '#3388ff' },
|
||||||
{ id: '4', name: '备忘录', url: '#', bgColor: '#f9ca24' },
|
{ id: '4', name: '电影', url: '#', bgColor: '#f9ca24' },
|
||||||
{ id: '5', name: '爱奇艺', url: 'https://iqiyi.com', bgColor: '#00be06' },
|
{ id: '5', name: '爱奇艺', url: 'https://iqiyi.com', bgColor: '#00be06' },
|
||||||
{ id: '6', name: '文件夹', url: '#', bgColor: '#4285f4' },
|
{ id: '6', name: '文件夹', url: '#', bgColor: '#4285f4' },
|
||||||
{ id: '7', name: '抖音', url: 'https://douyin.com', bgColor: '#222' },
|
{ id: '7', name: '抖音', url: 'https://douyin.com', bgColor: '#222' },
|
||||||
{ id: '8', name: '小浣熊', url: '#', bgColor: '#f0932b' },
|
{ id: '8', name: '微博', url: '#', bgColor: '#f0932b' },
|
||||||
{ id: '9', name: 'AiPPT', url: '#', bgColor: '#d63031' },
|
{ id: '9', name: 'AiPPT', url: '#', bgColor: '#d63031' },
|
||||||
{ id: '10', name: '电影日历', url: '#', bgColor: 'transparent', img: 'https://example.com/movie_poster.png' }, // 图片示例
|
{ id: '10', name: '电影日历', url: '#', bgColor: 'transparent', img: 'https://example.com/movie_poster.png' },
|
||||||
{ id: '11', name: '稿定设计', url: '#', bgColor: '#00aaff' },
|
{ id: '11', name: '产品设计', url: '#', bgColor: '#00aaff' },
|
||||||
{ id: '12', name: '壁纸', url: '#', bgColor: '#1dd1a1' },
|
{ id: '12', name: '插画', url: '#', bgColor: '#1dd1a1' },
|
||||||
{ id: '13', 'name': '即梦AI', url: '#', bgColor: '#6c5ce7' },
|
{ id: '13', name: '科普AI', url: '#', bgColor: '#6c5ce7' },
|
||||||
{ id: '14', name: '码上掘金', url: '#', bgColor: '#1e80ff' },
|
{ id: '14', name: '代码助手', url: '#', bgColor: '#1e80ff' },
|
||||||
{ id: '15', name: '扩展管理', url: '#', bgColor: '#7f8c8d' },
|
{ id: '15', name: '拓展管理', url: '#', bgColor: '#7f8c8d' },
|
||||||
{ id: '16', name: '书签管理', url: '#', bgColor: '#f1c40f' },
|
{ id: '16', name: '签名管理', url: '#', bgColor: '#f1c40f' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const defaultGroupById: Record<string, string> = {
|
const defaultGroupById: Record<string, string> = {
|
||||||
@ -75,6 +75,7 @@ const normalizeIcons = (icons: Icon[]): Icon[] =>
|
|||||||
...icon,
|
...icon,
|
||||||
size: icon.size ?? '1x1',
|
size: icon.size ?? '1x1',
|
||||||
groupId: icon.groupId ?? defaultGroupById[icon.id] ?? DEFAULT_GROUP_ID,
|
groupId: icon.groupId ?? defaultGroupById[icon.id] ?? DEFAULT_GROUP_ID,
|
||||||
|
folderId: icon.folderId,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const loadIcons = (): Icon[] => {
|
const loadIcons = (): Icon[] => {
|
||||||
@ -116,12 +117,9 @@ export const useLayoutStore = defineStore('layout', {
|
|||||||
const targetIndex = this.icons.findIndex(p => p.id === targetId);
|
const targetIndex = this.icons.findIndex(p => p.id === targetId);
|
||||||
|
|
||||||
if (draggedIndex !== -1 && targetIndex !== -1) {
|
if (draggedIndex !== -1 && targetIndex !== -1) {
|
||||||
// 直接交换
|
|
||||||
const draggedItem = this.icons[draggedIndex];
|
const draggedItem = this.icons[draggedIndex];
|
||||||
this.icons[draggedIndex] = this.icons[targetIndex];
|
this.icons[draggedIndex] = this.icons[targetIndex];
|
||||||
this.icons[targetIndex] = draggedItem;
|
this.icons[targetIndex] = draggedItem;
|
||||||
|
|
||||||
// 持久化顺序
|
|
||||||
localStorage.setItem('itab_icons', JSON.stringify(this.icons));
|
localStorage.setItem('itab_icons', JSON.stringify(this.icons));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -154,7 +152,6 @@ export const useLayoutStore = defineStore('layout', {
|
|||||||
const index = this.icons.findIndex(p => p.id === itemId);
|
const index = this.icons.findIndex(p => p.id === itemId);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.icons.splice(index, 1);
|
this.icons.splice(index, 1);
|
||||||
// 持久化顺序
|
|
||||||
localStorage.setItem('itab_icons', JSON.stringify(this.icons));
|
localStorage.setItem('itab_icons', JSON.stringify(this.icons));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,11 +5,12 @@ interface ContextMenuState {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
itemId: string | null;
|
itemId: string | null;
|
||||||
itemType?: 'icon' | 'widget' | 'desktop';
|
itemType?: 'icon' | 'widget' | 'folder' | 'desktop';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UIState {
|
interface UIState {
|
||||||
contextMenu: ContextMenuState;
|
contextMenu: ContextMenuState;
|
||||||
|
pendingDelete: { id: string; type: 'icon' | 'widget' | 'folder' } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUIStore = defineStore('ui', {
|
export const useUIStore = defineStore('ui', {
|
||||||
@ -20,14 +21,15 @@ export const useUIStore = defineStore('ui', {
|
|||||||
y: 0,
|
y: 0,
|
||||||
itemId: null,
|
itemId: null,
|
||||||
itemType: 'icon',
|
itemType: 'icon',
|
||||||
}
|
},
|
||||||
|
pendingDelete: null,
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
openContextMenu(
|
openContextMenu(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
itemId: string | null,
|
itemId: string | null,
|
||||||
itemType: 'icon' | 'widget' | 'desktop'
|
itemType: 'icon' | 'widget' | 'folder' | 'desktop'
|
||||||
) {
|
) {
|
||||||
this.contextMenu = {
|
this.contextMenu = {
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
@ -41,5 +43,11 @@ export const useUIStore = defineStore('ui', {
|
|||||||
this.contextMenu.isOpen = false;
|
this.contextMenu.isOpen = false;
|
||||||
this.contextMenu.itemId = null;
|
this.contextMenu.itemId = null;
|
||||||
},
|
},
|
||||||
|
requestDelete(id: string, type: 'icon' | 'widget' | 'folder') {
|
||||||
|
this.pendingDelete = { id, type };
|
||||||
|
},
|
||||||
|
clearPendingDelete() {
|
||||||
|
this.pendingDelete = null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
|
|
||||||
// 组件数据结构
|
// 缁勪欢鏁版嵁缁撴瀯
|
||||||
type WidgetSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
|
type WidgetSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';
|
||||||
const DEFAULT_GROUP_ID = 'home';
|
const DEFAULT_GROUP_ID = 'home';
|
||||||
interface Widget {
|
interface Widget {
|
||||||
id: string;
|
id: string;
|
||||||
component: string; // 渲染的组件名
|
component: string; // 娓叉煋鐨勭粍浠跺悕
|
||||||
size: WidgetSize; // 组件尺寸
|
size: WidgetSize; // 缁勪欢灏哄
|
||||||
gridPosition: { x: number; y: number }; // 网格位置
|
gridPosition: { x: number; y: number }; // 缃戞牸浣嶇疆
|
||||||
data?: any; // 组件数据
|
data?: any; // 缁勪欢鏁版嵁
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
|
folderId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WidgetsState {
|
interface WidgetsState {
|
||||||
@ -29,12 +30,12 @@ const defaultWidgets: Widget[] = [
|
|||||||
size: '2x1',
|
size: '2x1',
|
||||||
gridPosition: { x: 1, y: 0 },
|
gridPosition: { x: 1, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
tabs: ['百度', '微博', '抖音'],
|
tabs: ['热点', '新闻', '视频'],
|
||||||
items: [
|
items: [
|
||||||
{ title: '茅台确认“马茅”包装少写一撇', value: '780.9万' },
|
{ title: '平台确认新品发售', value: '780.9万' },
|
||||||
{ title: '双休不应成为奢侈品', value: '771.2万' },
|
{ title: '网友热议某话题', value: '771.2万' },
|
||||||
{ title: '突破8100亿元这场双向奔赴很燃', value: '761.8万' },
|
{ title: '新机性能实测数据', value: '761.8万' },
|
||||||
{ title: '第一个2万亿经济大区要来了', value: '752.2万' }
|
{ title: '首个万亿级经济区新闻', value: '752.2万' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -69,7 +70,7 @@ const loadWidgets = (): Widget[] => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 模拟数据(来自截图参考)
|
// 妯℃嫙鏁版嵁锛堟潵鑷埅鍥惧弬鑰冿級
|
||||||
export const useWidgetsStore = defineStore('widgets', {
|
export const useWidgetsStore = defineStore('widgets', {
|
||||||
state: (): WidgetsState => ({
|
state: (): WidgetsState => ({
|
||||||
widgets: loadWidgets(),
|
widgets: loadWidgets(),
|
||||||
@ -119,6 +120,15 @@ export const useWidgetsStore = defineStore('widgets', {
|
|||||||
localStorage.setItem('itab_widgets', JSON.stringify(this.widgets));
|
localStorage.setItem('itab_widgets', JSON.stringify(this.widgets));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateWidgetFolder(widgetId: string, folderId?: string, groupId?: string) {
|
||||||
|
const widget = this.widgets.find(w => w.id === widgetId);
|
||||||
|
if (!widget) return;
|
||||||
|
widget.folderId = folderId;
|
||||||
|
if (groupId) {
|
||||||
|
widget.groupId = groupId;
|
||||||
|
}
|
||||||
|
localStorage.setItem('itab_widgets', JSON.stringify(this.widgets));
|
||||||
|
},
|
||||||
setWidgetOrder(orderIds: string[]) {
|
setWidgetOrder(orderIds: string[]) {
|
||||||
const ordered: Widget[] = [];
|
const ordered: Widget[] = [];
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
@ -143,3 +153,4 @@ export const useWidgetsStore = defineStore('widgets', {
|
|||||||
if (import.meta.hot) {
|
if (import.meta.hot) {
|
||||||
import.meta.hot.accept(acceptHMRUpdate(useWidgetsStore, import.meta.hot));
|
import.meta.hot.accept(acceptHMRUpdate(useWidgetsStore, import.meta.hot));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
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
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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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;
|
||||||