完成信号源显示,信号源拖拽开窗,替换窗口
This commit is contained in:
parent
74abf865fd
commit
267d827fdb
|
@ -19,6 +19,7 @@
|
|||
"qrcode.vue": "^3.3.3",
|
||||
"quasar": "^2.6.2",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"ts-md5": "^1.2.11",
|
||||
"v-viewer": "^3.0.9",
|
||||
"vconsole": "^3.14.6",
|
||||
|
|
|
@ -32,4 +32,5 @@ export namespace EventNamesDefine {
|
|||
export const CurrentConnectDisconnect = "onCurrentConnectDisconnect";
|
||||
export const CurrentConnectConnected = "onCurrentConnectConnected";
|
||||
export const CurrentClientChanged = "onCurrentClientChanged";
|
||||
export const DropToWall = "onDropToWall";
|
||||
}
|
||||
|
|
|
@ -44,11 +44,35 @@
|
|||
</div>
|
||||
<q-separator />
|
||||
|
||||
<q-tab-panels v-model="tab" animated class="fit">
|
||||
<q-tab-panel name="signal_source" class="full-width __panel_item">
|
||||
signal_sourcesignal_sourcesignal_sourcesignal_source
|
||||
<q-tab-panels v-model="tab" animated class="fit text-black">
|
||||
<q-tab-panel
|
||||
name="signal_source"
|
||||
class="full-width __panel_item q-pa-xs"
|
||||
>
|
||||
<q-scroll-area class="fit q-pr-md">
|
||||
<draggable
|
||||
class="row q-px-md"
|
||||
v-model="signal_sources"
|
||||
group="signal_source"
|
||||
item-key="uuid"
|
||||
:move="() => false"
|
||||
@drop="onSignalSourceDrop($event)"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="col-4 q-pr-sm q-pb-sm">
|
||||
<q-btn
|
||||
class="col-row fit overflow-hidden bg-accent text-white"
|
||||
:icon="getItemIcon(element.window_type)"
|
||||
>
|
||||
<span class="col q-ml-md text-left">
|
||||
{{ element.name }}
|
||||
</span>
|
||||
</q-btn>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</q-scroll-area>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="singal_polling" class="full-width __panel_item">
|
||||
singal_pollingsingal_pollingsingal_pollingsingal_polling
|
||||
</q-tab-panel>
|
||||
|
@ -132,14 +156,18 @@
|
|||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, Ref, ref } from "vue";
|
||||
import { defineComponent, Ref, ref, computed, watch } from "vue";
|
||||
import { useQuasar } from "quasar";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useStore } from "src/store";
|
||||
import draggable from "src/third_lib/vuedraggable/vuedraggable";
|
||||
import { SignalSourceEntity } from "src/entities/SignalSourceEntity";
|
||||
import { Common } from "src/common/Common";
|
||||
import EventBus, { EventNamesDefine } from "src/common/EventBus";
|
||||
|
||||
export default defineComponent({
|
||||
name: "PadBottomBarPage",
|
||||
components: {},
|
||||
components: { draggable },
|
||||
setup() {
|
||||
const $store = useStore();
|
||||
const $q = useQuasar();
|
||||
|
@ -148,14 +176,61 @@ export default defineComponent({
|
|||
const paused = ref(false);
|
||||
const muted = ref(false);
|
||||
|
||||
const signal_sources = computed({
|
||||
get: () => $store.state.signal_sources,
|
||||
set: (val) => null,
|
||||
});
|
||||
|
||||
const modes = computed({
|
||||
get: () => $store.state.modes,
|
||||
set: (val) => null,
|
||||
});
|
||||
|
||||
const pollings = computed({
|
||||
get: () => $store.state.pollings,
|
||||
set: (val) => null,
|
||||
});
|
||||
|
||||
const plans = computed({
|
||||
get: () => $store.state.plans,
|
||||
set: (val) => null,
|
||||
});
|
||||
|
||||
const tab = ref("signal_source");
|
||||
return {
|
||||
tab,
|
||||
paused,
|
||||
muted,
|
||||
signal_sources,
|
||||
modes,
|
||||
pollings,
|
||||
plans,
|
||||
loga(a: any) {
|
||||
console.log(a);
|
||||
},
|
||||
getItemIcon(item_type: string) {
|
||||
return Common.getSignalSourceIcon(item_type);
|
||||
},
|
||||
onSignalSourceDrop(evt: any) {
|
||||
console.log(evt);
|
||||
if (
|
||||
evt.data &&
|
||||
evt.event &&
|
||||
evt.event.changedTouches &&
|
||||
evt.event.changedTouches.length
|
||||
) {
|
||||
const touchX = evt.event.changedTouches[0].pageX;
|
||||
const touchY = evt.event.changedTouches[0].pageY;
|
||||
EventBus.getInstance().emit(EventNamesDefine.DropToWall, {
|
||||
data: evt.data,
|
||||
type: "signal_source",
|
||||
pos: {
|
||||
x: touchX,
|
||||
y: touchY,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
}"
|
||||
class="wall_content"
|
||||
>
|
||||
<div id="windows" style="position: absolute">
|
||||
<div id="windows" ref="dom_windows" style="position: absolute">
|
||||
<vue3-resize-drag
|
||||
:w="item.width * ($refs.wall_content?.clientWidth ?? 0)"
|
||||
:h="item.height * ($refs.wall_content?.clientHeight ?? 0)"
|
||||
|
@ -189,6 +189,7 @@ export default defineComponent({
|
|||
|
||||
const wall: Ref<any> = ref(null);
|
||||
const wall_content: Ref<any> = ref(null);
|
||||
const dom_windows: Ref<any> = ref(null);
|
||||
|
||||
const file_manage_dialog: Ref<any> = ref(null);
|
||||
|
||||
|
@ -534,6 +535,171 @@ export default defineComponent({
|
|||
}
|
||||
};
|
||||
|
||||
interface _IDropToWall {
|
||||
data: any;
|
||||
type: string;
|
||||
pos: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}
|
||||
EventBus.getInstance().on(
|
||||
EventNamesDefine.DropToWall,
|
||||
(evt: _IDropToWall) => {
|
||||
if (evt && evt.data) {
|
||||
// const x =
|
||||
// evt.pos.x -
|
||||
// (wall.value.parentElement?.offsetLeft ?? 0) -
|
||||
// wall_content.value.offsetLeft;
|
||||
// const y =
|
||||
// evt.pos.y -
|
||||
// (wall.value.parentElement?.offsetTop ?? 0) -
|
||||
// wall_content.value.offsetTop;
|
||||
|
||||
const dom = document.elementFromPoint(
|
||||
evt.pos.x,
|
||||
evt.pos.y
|
||||
) as HTMLElement;
|
||||
|
||||
const signal_sources = GlobalData.getInstance().signal_source.filter(
|
||||
(item) => (item as any)?.uuid == evt.data.uuid
|
||||
);
|
||||
|
||||
if (dom) {
|
||||
if (dom.classList.contains("wall_item_flag")) {
|
||||
// 开窗
|
||||
// 不知道为什么有1像素的差别
|
||||
const x =
|
||||
(dom.offsetLeft - wall_content.value.offsetLeft - 1) /
|
||||
wall_content.value.clientWidth;
|
||||
const y =
|
||||
(dom.offsetTop - wall_content.value.offsetTop - 1) /
|
||||
wall_content.value.clientHeight;
|
||||
const width = dom.offsetWidth / wall_content.value.clientWidth;
|
||||
const height = dom.offsetHeight / wall_content.value.clientHeight;
|
||||
|
||||
switch (evt.type) {
|
||||
case "polling":
|
||||
GlobalData.getInstance()
|
||||
.getCurrentClient()
|
||||
?.openPolling(
|
||||
new Protocol.OpenPollingRequestEntity(
|
||||
evt.data.uuid,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
)
|
||||
);
|
||||
break;
|
||||
case "signal_source" /**OpenPollingRequestEntity */:
|
||||
if (signal_sources.length) {
|
||||
const signal_source = signal_sources[0];
|
||||
if (signal_source) {
|
||||
GlobalData.getInstance()
|
||||
.getCurrentClient()
|
||||
?.openWindow(
|
||||
new Protocol.OpenWindowRequestEntity(
|
||||
signal_source.uuid,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (dom.classList.contains("window_flag")) {
|
||||
// 拖拽信号源替换窗口 后面还有一处替换窗口
|
||||
const rep_uuid = dom.getAttribute("uuid");
|
||||
if (rep_uuid) {
|
||||
let window = $store.state.windows.find(
|
||||
(item) => item.uuid == rep_uuid
|
||||
);
|
||||
if (window) {
|
||||
let client = GlobalData.getInstance().getCurrentClient();
|
||||
if (client) {
|
||||
let x = window.x;
|
||||
let y = window.y;
|
||||
let width = window.width;
|
||||
let height = window.height;
|
||||
|
||||
client.closeWindow(window.window_id);
|
||||
const open_window_request =
|
||||
new Protocol.OpenPollingRequestEntity(
|
||||
evt.data.uuid,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
);
|
||||
open_window_request.muted = window.muted;
|
||||
open_window_request.volume = window.volume;
|
||||
open_window_request.paused = window.paused;
|
||||
open_window_request.play_speed = window.play_speed;
|
||||
|
||||
setTimeout(() => {
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
switch (evt.type) {
|
||||
case "polling":
|
||||
{
|
||||
const open_polling_request =
|
||||
new Protocol.OpenPollingRequestEntity(
|
||||
evt.data.uuid,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
);
|
||||
open_polling_request.muted = window.muted;
|
||||
open_polling_request.volume = window.volume;
|
||||
open_polling_request.paused = window.paused;
|
||||
open_polling_request.play_speed = window.play_speed;
|
||||
GlobalData.getInstance()
|
||||
.getCurrentClient()
|
||||
?.openPolling(open_polling_request);
|
||||
}
|
||||
|
||||
break;
|
||||
case "signal_source":
|
||||
{
|
||||
if (signal_sources.length) {
|
||||
const signal_source = signal_sources[0];
|
||||
if (signal_source) {
|
||||
const open_window_request =
|
||||
new Protocol.OpenWindowRequestEntity(
|
||||
signal_source.uuid,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
);
|
||||
open_window_request.muted = window.muted;
|
||||
open_window_request.volume = window.volume;
|
||||
open_window_request.paused = window.paused;
|
||||
open_window_request.play_speed =
|
||||
window.play_speed;
|
||||
client?.openWindow(open_window_request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
wall,
|
||||
file_manage_dialog,
|
||||
|
@ -550,6 +716,7 @@ export default defineComponent({
|
|||
last_context_menu_pos_y,
|
||||
windows,
|
||||
wall_content,
|
||||
dom_windows,
|
||||
|
||||
// functions
|
||||
moveWindow,
|
||||
|
@ -699,18 +866,6 @@ export default defineComponent({
|
|||
if (event && event.changedTouches && event.changedTouches.length) {
|
||||
last_context_menu_pos_x.value = event.changedTouches[0].pageX;
|
||||
last_context_menu_pos_y.value = event.changedTouches[0].pageY;
|
||||
console.log(event.changedTouches[0]);
|
||||
|
||||
console.log(
|
||||
last_context_menu_pos_x.value -
|
||||
(wall.value.parentElement?.offsetLeft ?? 0) -
|
||||
wall_content.value.offsetLeft
|
||||
);
|
||||
console.log(
|
||||
last_context_menu_pos_y.value -
|
||||
(wall.value.parentElement?.offsetTop ?? 0) -
|
||||
wall_content.value.offsetTop
|
||||
);
|
||||
}
|
||||
},
|
||||
async openWindowByLocalFile(event: MouseEvent) {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
根据源仓库 https://github.com/zzz0908/vue3-resize-drag 修改而来
|
|
@ -0,0 +1,47 @@
|
|||
import { camelize } from "../util/string";
|
||||
import { events, isReadOnly } from "./sortableEvents";
|
||||
import { isHtmlAttribute } from "../util/tags";
|
||||
|
||||
function project(entries) {
|
||||
return entries.reduce((res, [key, value]) => {
|
||||
res[key] = value;
|
||||
return res;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function getComponentAttributes({ $attrs, componentData = {} }) {
|
||||
const attributes = project(
|
||||
Object.entries($attrs).filter(([key, _]) => isHtmlAttribute(key))
|
||||
);
|
||||
return {
|
||||
...attributes,
|
||||
...componentData
|
||||
};
|
||||
}
|
||||
|
||||
function createSortableOption({ $attrs, callBackBuilder }) {
|
||||
const options = project(getValidSortableEntries($attrs));
|
||||
Object.entries(callBackBuilder).forEach(([eventType, eventBuilder]) => {
|
||||
events[eventType].forEach(event => {
|
||||
options[`on${event}`] = eventBuilder(event);
|
||||
});
|
||||
});
|
||||
const draggable = `[data-draggable]${options.draggable || ""}`;
|
||||
return {
|
||||
...options,
|
||||
draggable
|
||||
};
|
||||
}
|
||||
|
||||
function getValidSortableEntries(value) {
|
||||
return Object.entries(value)
|
||||
.filter(([key, _]) => !isHtmlAttribute(key))
|
||||
.map(([key, value]) => [camelize(key), value])
|
||||
.filter(([key, _]) => !isReadOnly(key));
|
||||
}
|
||||
|
||||
export {
|
||||
getComponentAttributes,
|
||||
createSortableOption,
|
||||
getValidSortableEntries
|
||||
};
|
|
@ -0,0 +1,69 @@
|
|||
const getHtmlElementFromNode = ({ el }) => el;
|
||||
const addContext = (domElement, context) =>
|
||||
(domElement.__draggable_context = context);
|
||||
const getContext = domElement => domElement.__draggable_context;
|
||||
|
||||
class ComponentStructure {
|
||||
constructor({
|
||||
nodes: { header, default: defaultNodes, footer },
|
||||
root,
|
||||
realList
|
||||
}) {
|
||||
this.defaultNodes = defaultNodes;
|
||||
this.children = [...header, ...defaultNodes, ...footer];
|
||||
this.externalComponent = root.externalComponent;
|
||||
this.rootTransition = root.transition;
|
||||
this.tag = root.tag;
|
||||
this.realList = realList;
|
||||
}
|
||||
|
||||
get _isRootComponent() {
|
||||
return this.externalComponent || this.rootTransition;
|
||||
}
|
||||
|
||||
render(h, attributes) {
|
||||
const { tag, children, _isRootComponent } = this;
|
||||
const option = !_isRootComponent ? children : { default: () => children };
|
||||
return h(tag, attributes, option);
|
||||
}
|
||||
|
||||
updated() {
|
||||
const { defaultNodes, realList } = this;
|
||||
defaultNodes.forEach((node, index) => {
|
||||
addContext(getHtmlElementFromNode(node), {
|
||||
element: realList[index],
|
||||
index
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getUnderlyingVm(domElement) {
|
||||
return getContext(domElement);
|
||||
}
|
||||
|
||||
getVmIndexFromDomIndex(domIndex, element) {
|
||||
const { defaultNodes } = this;
|
||||
const { length } = defaultNodes;
|
||||
const domChildren = element.children;
|
||||
const domElement = domChildren.item(domIndex);
|
||||
|
||||
if (domElement === null) {
|
||||
return length;
|
||||
}
|
||||
const context = getContext(domElement);
|
||||
if (context) {
|
||||
return context.index;
|
||||
}
|
||||
|
||||
if (length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const firstDomListElement = getHtmlElementFromNode(defaultNodes[0]);
|
||||
const indexFirstDomListElement = [...domChildren].findIndex(
|
||||
element => element === firstDomListElement
|
||||
);
|
||||
return domIndex < indexFirstDomListElement ? 0 : length;
|
||||
}
|
||||
}
|
||||
|
||||
export { ComponentStructure };
|
|
@ -0,0 +1,56 @@
|
|||
import { ComponentStructure } from "./componentStructure";
|
||||
import { isHtmlTag, isTransition } from "../util/tags";
|
||||
import { resolveComponent, TransitionGroup } from "vue";
|
||||
|
||||
function getSlot(slots, key) {
|
||||
const slotValue = slots[key];
|
||||
return slotValue ? slotValue() : [];
|
||||
}
|
||||
|
||||
function computeNodes({ $slots, realList, getKey }) {
|
||||
const normalizedList = realList || [];
|
||||
const [header, footer] = ["header", "footer"].map(name =>
|
||||
getSlot($slots, name)
|
||||
);
|
||||
const { item } = $slots;
|
||||
if (!item) {
|
||||
throw new Error("draggable element must have an item slot");
|
||||
}
|
||||
const defaultNodes = normalizedList.flatMap((element, index) =>
|
||||
item({ element, index }).map(node => {
|
||||
node.key = getKey(element);
|
||||
node.props = { ...(node.props || {}), "data-draggable": true };
|
||||
return node;
|
||||
})
|
||||
);
|
||||
if (defaultNodes.length !== normalizedList.length) {
|
||||
throw new Error("Item slot must have only one child");
|
||||
}
|
||||
return {
|
||||
header,
|
||||
footer,
|
||||
default: defaultNodes
|
||||
};
|
||||
}
|
||||
|
||||
function getRootInformation(tag) {
|
||||
const transition = isTransition(tag);
|
||||
const externalComponent = !isHtmlTag(tag) && !transition;
|
||||
return {
|
||||
transition,
|
||||
externalComponent,
|
||||
tag: externalComponent
|
||||
? resolveComponent(tag)
|
||||
: transition
|
||||
? TransitionGroup
|
||||
: tag
|
||||
};
|
||||
}
|
||||
|
||||
function computeComponentStructure({ $slots, tag, realList, getKey }) {
|
||||
const nodes = computeNodes({ $slots, realList, getKey });
|
||||
const root = getRootInformation(tag);
|
||||
return new ComponentStructure({ nodes, root, realList });
|
||||
}
|
||||
|
||||
export { computeComponentStructure };
|
|
@ -0,0 +1,18 @@
|
|||
const manageAndEmit = ["Start", "Add", "Remove", "Update", "End"];
|
||||
const emit = ["Choose", "Unchoose", "Sort", "Filter", "Clone"];
|
||||
const manage = ["Move"];
|
||||
const eventHandlerNames = [manage, manageAndEmit, emit]
|
||||
.flatMap(events => events)
|
||||
.map(evt => `on${evt}`);
|
||||
|
||||
const events = {
|
||||
manage,
|
||||
manageAndEmit,
|
||||
emit
|
||||
};
|
||||
|
||||
function isReadOnly(eventName) {
|
||||
return eventHandlerNames.indexOf(eventName) !== -1;
|
||||
}
|
||||
|
||||
export { events, isReadOnly };
|
|
@ -0,0 +1 @@
|
|||
根据源仓库 https://github.com/SortableJS/vue.draggable.next 修改而来
|
|
@ -0,0 +1,9 @@
|
|||
function getConsole() {
|
||||
if (typeof window !== "undefined") {
|
||||
return window.console;
|
||||
}
|
||||
return global.console;
|
||||
}
|
||||
const console = getConsole();
|
||||
|
||||
export { console };
|
|
@ -0,0 +1,15 @@
|
|||
function removeNode(node) {
|
||||
if (node.parentElement !== null) {
|
||||
node.parentElement.removeChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
function insertNodeAt(fatherNode, node, position) {
|
||||
const refNode =
|
||||
position === 0
|
||||
? fatherNode.children[0]
|
||||
: fatherNode.children[position - 1].nextSibling;
|
||||
fatherNode.insertBefore(node, refNode);
|
||||
}
|
||||
|
||||
export { insertNodeAt, removeNode };
|
|
@ -0,0 +1,12 @@
|
|||
function cached(fn) {
|
||||
const cache = Object.create(null);
|
||||
return function cachedFn(str) {
|
||||
const hit = cache[str];
|
||||
return hit || (cache[str] = fn(str));
|
||||
};
|
||||
}
|
||||
|
||||
const regex = /-(\w)/g;
|
||||
const camelize = cached(str => str.replace(regex, (_, c) => c.toUpperCase()));
|
||||
|
||||
export { camelize };
|
|
@ -0,0 +1,138 @@
|
|||
const tags = [
|
||||
"a",
|
||||
"abbr",
|
||||
"address",
|
||||
"area",
|
||||
"article",
|
||||
"aside",
|
||||
"audio",
|
||||
"b",
|
||||
"base",
|
||||
"bdi",
|
||||
"bdo",
|
||||
"blockquote",
|
||||
"body",
|
||||
"br",
|
||||
"button",
|
||||
"canvas",
|
||||
"caption",
|
||||
"cite",
|
||||
"code",
|
||||
"col",
|
||||
"colgroup",
|
||||
"data",
|
||||
"datalist",
|
||||
"dd",
|
||||
"del",
|
||||
"details",
|
||||
"dfn",
|
||||
"dialog",
|
||||
"div",
|
||||
"dl",
|
||||
"dt",
|
||||
"em",
|
||||
"embed",
|
||||
"fieldset",
|
||||
"figcaption",
|
||||
"figure",
|
||||
"footer",
|
||||
"form",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"head",
|
||||
"header",
|
||||
"hgroup",
|
||||
"hr",
|
||||
"html",
|
||||
"i",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"ins",
|
||||
"kbd",
|
||||
"label",
|
||||
"legend",
|
||||
"li",
|
||||
"link",
|
||||
"main",
|
||||
"map",
|
||||
"mark",
|
||||
"math",
|
||||
"menu",
|
||||
"menuitem",
|
||||
"meta",
|
||||
"meter",
|
||||
"nav",
|
||||
"noscript",
|
||||
"object",
|
||||
"ol",
|
||||
"optgroup",
|
||||
"option",
|
||||
"output",
|
||||
"p",
|
||||
"param",
|
||||
"picture",
|
||||
"pre",
|
||||
"progress",
|
||||
"q",
|
||||
"rb",
|
||||
"rp",
|
||||
"rt",
|
||||
"rtc",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
"script",
|
||||
"section",
|
||||
"select",
|
||||
"slot",
|
||||
"small",
|
||||
"source",
|
||||
"span",
|
||||
"strong",
|
||||
"style",
|
||||
"sub",
|
||||
"summary",
|
||||
"sup",
|
||||
"svg",
|
||||
"table",
|
||||
"tbody",
|
||||
"td",
|
||||
"template",
|
||||
"textarea",
|
||||
"tfoot",
|
||||
"th",
|
||||
"thead",
|
||||
"time",
|
||||
"title",
|
||||
"tr",
|
||||
"track",
|
||||
"u",
|
||||
"ul",
|
||||
"var",
|
||||
"video",
|
||||
"wbr"
|
||||
];
|
||||
|
||||
function isHtmlTag(name) {
|
||||
return tags.includes(name);
|
||||
}
|
||||
|
||||
function isTransition(name) {
|
||||
return ["transition-group", "TransitionGroup"].includes(name);
|
||||
}
|
||||
|
||||
function isHtmlAttribute(value) {
|
||||
return (
|
||||
["id", "class", "role", "style"].includes(value) ||
|
||||
value.startsWith("data-") ||
|
||||
value.startsWith("aria-") ||
|
||||
value.startsWith("on")
|
||||
);
|
||||
}
|
||||
|
||||
export { isHtmlTag, isHtmlAttribute, isTransition };
|
|
@ -0,0 +1,74 @@
|
|||
declare const draggableComponent: import("vue").DefineComponent<{
|
||||
list: {
|
||||
type: ArrayConstructor;
|
||||
required: boolean;
|
||||
default: any;
|
||||
};
|
||||
modelValue: {
|
||||
type: ArrayConstructor;
|
||||
required: boolean;
|
||||
default: any;
|
||||
};
|
||||
itemKey: {
|
||||
type: (FunctionConstructor | StringConstructor)[];
|
||||
required: boolean;
|
||||
};
|
||||
clone: {
|
||||
type: FunctionConstructor;
|
||||
default: (original: any) => any;
|
||||
};
|
||||
tag: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
move: {
|
||||
type: FunctionConstructor;
|
||||
default: any;
|
||||
};
|
||||
componentData: {
|
||||
type: ObjectConstructor;
|
||||
required: boolean;
|
||||
default: any;
|
||||
};
|
||||
}, unknown, {
|
||||
error: boolean;
|
||||
}, {
|
||||
realList(): any;
|
||||
getKey(): any;
|
||||
}, {
|
||||
getUnderlyingVm(domElement: any): any;
|
||||
getUnderlyingPotencialDraggableComponent(htmElement: any): any;
|
||||
emitChanges(evt: any): void;
|
||||
alterList(onList: any): void;
|
||||
spliceList(): void;
|
||||
updatePosition(oldIndex: any, newIndex: any): void;
|
||||
getRelatedContextFromMoveEvent({ to, related }: {
|
||||
to: any;
|
||||
related: any;
|
||||
}): any;
|
||||
getVmIndexFromDomIndex(domIndex: any): any;
|
||||
onDragStart(evt: any): void;
|
||||
onDragAdd(evt: any): void;
|
||||
onDragRemove(evt: any): void;
|
||||
onDragUpdate(evt: any): void;
|
||||
computeFutureIndex(relatedContext: any, evt: any): any;
|
||||
onDragMove(evt: any, originalEvent: any): any;
|
||||
onDragEnd(): void;
|
||||
}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, any[], any, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<{
|
||||
move: Function;
|
||||
tag: string;
|
||||
clone: Function;
|
||||
list: unknown[];
|
||||
modelValue: unknown[];
|
||||
componentData: Record<string, any>;
|
||||
} & {
|
||||
itemKey?: string | Function;
|
||||
}>, {
|
||||
move: Function;
|
||||
tag: string;
|
||||
clone: Function;
|
||||
list: unknown[];
|
||||
modelValue: unknown[];
|
||||
componentData: Record<string, any>;
|
||||
}>;
|
||||
export default draggableComponent;
|
|
@ -0,0 +1,365 @@
|
|||
import Sortable from "sortablejs";
|
||||
import { insertNodeAt, removeNode } from "./util/htmlHelper";
|
||||
import { console } from "./util/console";
|
||||
import {
|
||||
getComponentAttributes,
|
||||
createSortableOption,
|
||||
getValidSortableEntries,
|
||||
} from "./core/componentBuilderHelper";
|
||||
import { computeComponentStructure } from "./core/renderHelper";
|
||||
import { events } from "./core/sortableEvents";
|
||||
import { h, defineComponent, nextTick } from "vue";
|
||||
|
||||
function emit(evtName, evtData) {
|
||||
nextTick(() => this.$emit(evtName.toLowerCase(), evtData));
|
||||
}
|
||||
|
||||
function manage(evtName) {
|
||||
return (evtData, originalElement) => {
|
||||
if (this.realList !== null) {
|
||||
return this[`onDrag${evtName}`](evtData, originalElement);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function manageAndEmit(evtName) {
|
||||
const delegateCallBack = manage.call(this, evtName);
|
||||
return (evtData, originalElement) => {
|
||||
delegateCallBack.call(this, evtData, originalElement);
|
||||
emit.call(this, evtName, evtData);
|
||||
};
|
||||
}
|
||||
|
||||
let draggingElement = null;
|
||||
|
||||
const props = {
|
||||
list: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
modelValue: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
itemKey: {
|
||||
type: [String, Function],
|
||||
required: true,
|
||||
},
|
||||
clone: {
|
||||
type: Function,
|
||||
default: (original) => {
|
||||
return original;
|
||||
},
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
default: "div",
|
||||
},
|
||||
move: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
componentData: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
|
||||
const emits = [
|
||||
"update:modelValue",
|
||||
"change",
|
||||
"drop",
|
||||
...[...events.manageAndEmit, ...events.emit].map((evt) => evt.toLowerCase()),
|
||||
];
|
||||
|
||||
const draggableComponent = defineComponent({
|
||||
name: "draggable",
|
||||
|
||||
inheritAttrs: false,
|
||||
|
||||
props,
|
||||
|
||||
emits,
|
||||
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
try {
|
||||
this.error = false;
|
||||
const { $slots, $attrs, tag, componentData, realList, getKey } = this;
|
||||
const componentStructure = computeComponentStructure({
|
||||
$slots,
|
||||
tag,
|
||||
realList,
|
||||
getKey,
|
||||
});
|
||||
this.componentStructure = componentStructure;
|
||||
const attributes = getComponentAttributes({ $attrs, componentData });
|
||||
return componentStructure.render(h, attributes);
|
||||
} catch (err) {
|
||||
this.error = true;
|
||||
return h("pre", { style: { color: "red" } }, err.stack);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.list !== null && this.modelValue !== null) {
|
||||
console.error(
|
||||
"modelValue and list props are mutually exclusive! Please set one or another."
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
|
||||
const { $attrs, $el, componentStructure } = this;
|
||||
componentStructure.updated();
|
||||
|
||||
const sortableOptions = createSortableOption({
|
||||
$attrs,
|
||||
callBackBuilder: {
|
||||
manageAndEmit: (event) => manageAndEmit.call(this, event),
|
||||
emit: (event) => emit.bind(this, event),
|
||||
manage: (event) => manage.call(this, event),
|
||||
},
|
||||
});
|
||||
const targetDomElement = $el.nodeType === 1 ? $el : $el.parentElement;
|
||||
|
||||
function AfxTestPlugin() {
|
||||
function AfxTest() {
|
||||
this.defaults = {};
|
||||
}
|
||||
|
||||
AfxTest.prototype = {
|
||||
dropGlobal: function dropGlobal(evt) {
|
||||
console.log("oooooooooooooooooooo");
|
||||
console.log(evt);
|
||||
try {
|
||||
self.$emit("drop", {
|
||||
data: evt.dragEl.__draggable_context.element,
|
||||
event: evt.originalEvent,
|
||||
});
|
||||
} catch {}
|
||||
},
|
||||
};
|
||||
|
||||
function _extends() {
|
||||
_extends =
|
||||
Object.assign ||
|
||||
function (target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i];
|
||||
|
||||
for (var key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
return _extends.apply(this, arguments);
|
||||
}
|
||||
return _extends(AfxTest, {
|
||||
pluginName: "afxtest",
|
||||
});
|
||||
}
|
||||
try {
|
||||
Sortable.mount(new AfxTestPlugin());
|
||||
} catch {}
|
||||
|
||||
sortableOptions.fallbackOnBody = true;
|
||||
this._sortable = new Sortable(targetDomElement, sortableOptions);
|
||||
this.targetDomElement = targetDomElement;
|
||||
targetDomElement.__draggable_component__ = this;
|
||||
},
|
||||
|
||||
updated() {
|
||||
this.componentStructure.updated();
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this._sortable !== undefined) this._sortable.destroy();
|
||||
},
|
||||
|
||||
computed: {
|
||||
realList() {
|
||||
const { list } = this;
|
||||
return list ? list : this.modelValue;
|
||||
},
|
||||
|
||||
getKey() {
|
||||
const { itemKey } = this;
|
||||
if (typeof itemKey === "function") {
|
||||
return itemKey;
|
||||
}
|
||||
return (element) => element[itemKey];
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
$attrs: {
|
||||
handler(newOptionValue) {
|
||||
const { _sortable } = this;
|
||||
if (!_sortable) return;
|
||||
getValidSortableEntries(newOptionValue).forEach(([key, value]) => {
|
||||
_sortable.option(key, value);
|
||||
});
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getUnderlyingVm(domElement) {
|
||||
return this.componentStructure.getUnderlyingVm(domElement) || null;
|
||||
},
|
||||
|
||||
getUnderlyingPotencialDraggableComponent(htmElement) {
|
||||
//TODO check case where you need to see component children
|
||||
return htmElement.__draggable_component__;
|
||||
},
|
||||
|
||||
emitChanges(evt) {
|
||||
nextTick(() => this.$emit("change", evt));
|
||||
},
|
||||
|
||||
alterList(onList) {
|
||||
if (this.list) {
|
||||
onList(this.list);
|
||||
return;
|
||||
}
|
||||
const newList = [...this.modelValue];
|
||||
onList(newList);
|
||||
this.$emit("update:modelValue", newList);
|
||||
},
|
||||
|
||||
spliceList() {
|
||||
const spliceList = (list) => list.splice(...arguments);
|
||||
this.alterList(spliceList);
|
||||
},
|
||||
|
||||
updatePosition(oldIndex, newIndex) {
|
||||
const updatePosition = (list) =>
|
||||
list.splice(newIndex, 0, list.splice(oldIndex, 1)[0]);
|
||||
this.alterList(updatePosition);
|
||||
},
|
||||
|
||||
getRelatedContextFromMoveEvent({ to, related }) {
|
||||
const component = this.getUnderlyingPotencialDraggableComponent(to);
|
||||
if (!component) {
|
||||
return { component };
|
||||
}
|
||||
const list = component.realList;
|
||||
const context = { list, component };
|
||||
if (to !== related && list) {
|
||||
const destination = component.getUnderlyingVm(related) || {};
|
||||
return { ...destination, ...context };
|
||||
}
|
||||
return context;
|
||||
},
|
||||
|
||||
getVmIndexFromDomIndex(domIndex) {
|
||||
return this.componentStructure.getVmIndexFromDomIndex(
|
||||
domIndex,
|
||||
this.targetDomElement
|
||||
);
|
||||
},
|
||||
|
||||
onDragStart(evt) {
|
||||
this.context = this.getUnderlyingVm(evt.item);
|
||||
evt.item._underlying_vm_ = this.clone(this.context.element);
|
||||
draggingElement = evt.item;
|
||||
},
|
||||
|
||||
onDragAdd(evt) {
|
||||
const element = evt.item._underlying_vm_;
|
||||
if (element === undefined) {
|
||||
return;
|
||||
}
|
||||
removeNode(evt.item);
|
||||
const newIndex = this.getVmIndexFromDomIndex(evt.newIndex);
|
||||
this.spliceList(newIndex, 0, element);
|
||||
const added = { element, newIndex };
|
||||
this.emitChanges({ added });
|
||||
},
|
||||
|
||||
onDragRemove(evt) {
|
||||
insertNodeAt(this.$el, evt.item, evt.oldIndex);
|
||||
if (evt.pullMode === "clone") {
|
||||
removeNode(evt.clone);
|
||||
return;
|
||||
}
|
||||
const { index: oldIndex, element } = this.context;
|
||||
this.spliceList(oldIndex, 1);
|
||||
const removed = { element, oldIndex };
|
||||
this.emitChanges({ removed });
|
||||
},
|
||||
|
||||
onDragUpdate(evt) {
|
||||
removeNode(evt.item);
|
||||
insertNodeAt(evt.from, evt.item, evt.oldIndex);
|
||||
const oldIndex = this.context.index;
|
||||
const newIndex = this.getVmIndexFromDomIndex(evt.newIndex);
|
||||
this.updatePosition(oldIndex, newIndex);
|
||||
const moved = { element: this.context.element, oldIndex, newIndex };
|
||||
this.emitChanges({ moved });
|
||||
},
|
||||
|
||||
computeFutureIndex(relatedContext, evt) {
|
||||
if (!relatedContext.element) {
|
||||
return 0;
|
||||
}
|
||||
const domChildren = [...evt.to.children].filter(
|
||||
(el) => el.style["display"] !== "none"
|
||||
);
|
||||
const currentDomIndex = domChildren.indexOf(evt.related);
|
||||
const currentIndex =
|
||||
relatedContext.component.getVmIndexFromDomIndex(currentDomIndex);
|
||||
const draggedInList = domChildren.indexOf(draggingElement) !== -1;
|
||||
return draggedInList || !evt.willInsertAfter
|
||||
? currentIndex
|
||||
: currentIndex + 1;
|
||||
},
|
||||
|
||||
onDragMove(evt, originalEvent) {
|
||||
const { move, realList } = this;
|
||||
if (!move || !realList) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const relatedContext = this.getRelatedContextFromMoveEvent(evt);
|
||||
const futureIndex = this.computeFutureIndex(relatedContext, evt);
|
||||
const draggedContext = {
|
||||
...this.context,
|
||||
futureIndex,
|
||||
};
|
||||
const sendEvent = {
|
||||
...evt,
|
||||
relatedContext,
|
||||
draggedContext,
|
||||
};
|
||||
return move(sendEvent, originalEvent);
|
||||
},
|
||||
|
||||
onDragEnd() {
|
||||
draggingElement = null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default draggableComponent;
|
|
@ -6669,6 +6669,11 @@ sort-keys@^2.0.0:
|
|||
dependencies:
|
||||
is-plain-obj "^1.0.0"
|
||||
|
||||
sortablejs@^1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.15.0.tgz#53230b8aa3502bb77a29e2005808ffdb4a5f7e2a"
|
||||
integrity sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==
|
||||
|
||||
source-list-map@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmmirror.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
|
||||
|
|
Loading…
Reference in New Issue