index.vue 3.76 KB
<script setup lang="ts">
  import { computed, CSSProperties, unref } from 'vue';
  import { NodeData } from '../../../types/node';
  import { RuleChainContextMenuItemType } from './index.type';
  import { Icon } from '/@/components/Icon';
  import { Divider } from 'ant-design-vue';

  const props = withDefaults(
    defineProps<{
      width?: number;
      itemHeight?: number;
      styles?: CSSProperties;
      axis?: Record<'x' | 'y', number>;
      items?: RuleChainContextMenuItemType[];
      nodeData?: NodeData;
    }>(),
    {
      width: 320,
      itemHeight: 48,
      axis: () => ({ x: 0, y: 0 }),
      items: () => [],
      nodeData: () => ({}),
    }
  );

  const getMenuHeight = computed(() => {
    const { items, itemHeight } = props;
    let dividerNumber = 0;
    let menuNumber = 1;
    for (const item of items) {
      if (item.divider) dividerNumber++;
      else menuNumber++;
    }

    return itemHeight * menuNumber + dividerNumber + 8;
  });

  const getStyle = computed((): CSSProperties => {
    const { axis, styles, width } = props;
    const { x, y } = axis;
    const menuHeight = unref(getMenuHeight);
    const menuWidth = width;
    const body = document.body;

    const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
    const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;

    return {
      ...styles,
      position: 'absolute',
      width: `${width}px`,
      left: `${left + 1}px`,
      top: `${top + 1}px`,
    };
  });

  const getNodeIconUrl = computed(() => {
    const { config } = props.nodeData;
    const { configurationDescriptor } = config || {};
    const { nodeDefinition } = configurationDescriptor || {};
    const { iconUrl } = nodeDefinition || {};
    return iconUrl;
  });

  const getNodeIcon = computed(() => {
    const { nodeData } = props;
    const { categoryConfig, config } = nodeData;
    const { icon: categoryIcon } = categoryConfig || {};
    const { configurationDescriptor } = config || {};
    const { nodeDefinition } = configurationDescriptor || {};
    const { icon } = nodeDefinition || {};

    return categoryIcon || icon;
  });

  const getTitleBackgroundColor = computed(() => {
    const { categoryConfig, config } = props.nodeData;
    const { backgroundColor: categoryBackgroundColor } = categoryConfig || {};
    const { backgroundColor } = config || {};
    return categoryBackgroundColor || backgroundColor;
  });
</script>

<template>
  <div
    :style="getStyle"
    class="bg-light-50 shadow-lg shadow-dark-50 z-50 rounded-md overflow-hidden pb-2"
  >
    <div
      v-if="nodeData"
      :style="{ backgroundColor: getTitleBackgroundColor, height: `${itemHeight}px` }"
      class="flex items-center p-2"
    >
      <Icon v-if="!getNodeIconUrl" :icon="getNodeIcon" class="svg:text-2xl" />
      <img v-if="getNodeIconUrl" :src="getNodeIconUrl" class="w-6 h-6" />
      <div class="ml-4">
        <div class="font-medium">{{ nodeData.config?.name }}</div>
        <div class="text-xs">{{ nodeData.data?.name }}</div>
      </div>
    </div>
    <div>
      <template v-for="item in items" :key="item.key">
        <div
          v-if="!item.divider"
          :style="{ height: `${itemHeight}px` }"
          class="px-4 flex items-center cursor-pointer hover:bg-neutral-100"
          :class="item.disabled && 'disables'"
          @click="(event) => !item.disabled && item?.handler?.(event)"
        >
          <Icon :icon="item.icon" class="svg:text-2xl" />
          <div class="flex-auto px-4">{{ item.label }}</div>
          <div class="flex items-center"> {{ item.shortcutKey }} </div>
        </div>
        <Divider v-if="item.divider" class="!m-0" />
      </template>
    </div>
  </div>
</template>

<style lang="less" scoped>
  .disables {
    @apply pointer-events-none text-gray-300;
  }
</style>