github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/static_source/admin/src/views/Dashboard/editor/ViewCard.vue (about)

     1  <script setup lang="ts">
     2  import {computed, onMounted, onUnmounted, onUpdated, PropType, ref,} from "vue";
     3  import {Card, CardItem, Core, EventContextMenu, eventBus} from "@/views/Dashboard/core";
     4  import debounce from 'lodash.debounce'
     5  import Moveable from 'vue3-moveable'
     6  import {deepFlat} from "@daybrush/utils";
     7  import {VueSelecto} from "vue3-selecto";
     8  import {CardItemName} from "@/views/Dashboard/card_items";
     9  import {UUID} from "uuid-generator-ts";
    10  import {KeystrokeCaptureViewer} from "@/views/Dashboard/components";
    11  import {useAppStore} from "@/store/modules/app";
    12  
    13  const appStore = useAppStore()
    14  
    15  const currentID = ref('')
    16  const cardRef = ref(null)
    17  
    18  const eventHandler = (event: string, args: any[]) => {
    19    switch (event) {
    20      case 'selectedCardItem':
    21        if (!currentCard.value.active) {
    22          return
    23        }
    24        const itemIndex = args
    25        if (itemIndex === -1 || !currentCard.value.items.length || !currentCard.value.itemList[itemIndex]) {
    26          setSelectedTargets([])
    27          return
    28        }
    29        const target = currentCard.value.itemList[itemIndex];
    30        // target.classList.add("selected");
    31        setSelectedTargets([target]);
    32        break;
    33      case 'unselectedCardItem':
    34        if (currentCard.value.active) {
    35          return
    36        }
    37        setSelectedTargets([]);
    38        break;
    39    }
    40  }
    41  
    42  onMounted(() => {
    43    const uuid = new UUID()
    44    currentID.value = uuid.getDashFreeUUID()
    45  
    46    currentCard.value.document = cardRef.value
    47    currentCard.value.updateItemList()
    48  
    49    eventBus.subscribe(['selectedCardItem', 'unselectedCardItem'], eventHandler)
    50  })
    51  
    52  onUnmounted(() => {
    53    eventBus.unsubscribe(['selectedCardItem', 'unselectedCardItem'], eventHandler)
    54  })
    55  
    56  onUpdated(() => {
    57  
    58  })
    59  
    60  // ---------------------------------
    61  // common
    62  // ---------------------------------
    63  
    64  const zoom = ref(1);
    65  
    66  
    67  const props = defineProps({
    68    core: {
    69      type: Object as PropType<Core>,
    70    },
    71    card: {
    72      type: Object as PropType<Nullable<Card>>,
    73      default: () => null
    74    },
    75  })
    76  
    77  const reloadKey = ref(0);
    78  const reload = debounce(() => {
    79    reloadKey.value += 1
    80  }, 100)
    81  
    82  
    83  const currentCard = computed({
    84    get(): Card {
    85      return props.card as Card
    86    },
    87    set(val: Card) {
    88    }
    89  })
    90  
    91  const hover = ref(false)
    92  
    93  // ---------------------------------
    94  // component methods
    95  // ---------------------------------
    96  const getCardItemName = (item: CardItem): string => {
    97    //todo: check if item disabled
    98    return CardItemName(item.type);
    99  }
   100  
   101  const selectCardItem = (itemIndex: number) => {
   102    if (!currentCard.value.active) {
   103      props.core?.onSelectedCard(currentCard.value.id)
   104      eventBus.emit('selectedCard', currentCard.value.id)
   105    }
   106  
   107    currentCard.value.selectedItem = itemIndex;
   108    // if (itemIndex === -1 || !currentCard.value.items.length || !currentCard.value.items[itemIndex]) {
   109    //   targets.value = [];
   110    // } else {
   111    //   targets.value = [currentCard.value.items[itemIndex].target];
   112    // }
   113    // eventBus.emit('unselectedCardItem')
   114  }
   115  
   116  const onDrag = ({target, transform, beforeTranslate, left, top}: any) => {
   117    const classes = target.className.split(' ');
   118    target.style.transform = transform;
   119    for (const cl of classes) {
   120      if (cl.includes('item-index-')) {
   121        const index = parseInt(cl.replace("item-index-", ""))
   122        currentCard.value.items[index].transform = transform;
   123      }
   124    }
   125  }
   126  
   127  const setSelectedTargets = (target) => {
   128    selectoRef.value.setSelectedTargets(deepFlat(target));
   129    targets.value = target;
   130  };
   131  
   132  const onResize = ({target, width, height, clientX, clientY}: any) => {
   133    width = Math.round(width);
   134    height = Math.round(height);
   135  
   136    if (currentCard.value.selectedItem > -1) {
   137      currentCard.value.items[currentCard.value.selectedItem].width = width;
   138      currentCard.value.items[currentCard.value.selectedItem].height = height;
   139    }
   140    target.style.width = `${width}px`;
   141    target.style.height = `${height}px`;
   142  }
   143  
   144  const onRotate = ({target, transform, beforeRotate, clientX, clientY}: any) => {
   145    if (currentCard.value.selectedItem > -1) {
   146      currentCard.value.items[currentCard.value.selectedItem].transform = transform;
   147    }
   148    target.style.transform = transform;
   149  }
   150  
   151  const targets = ref([]);
   152  const moveableRef = ref(null);
   153  
   154  const onSnap = e => {
   155    // console.log(e.guidelines, e.elements);
   156  };
   157  
   158  // ---------------------------------
   159  // selecto methods
   160  // ---------------------------------
   161  
   162  const onDragGroup = ({events}) => {
   163    events.forEach(ev => {
   164      const classes = ev.target.className.split(' ');
   165      ev.target.style.transform = ev.transform;
   166      for (const cl of classes) {
   167        if (cl.includes('item-index-')) {
   168          const index = parseInt(cl.replace("item-index-", ""))
   169          currentCard.value.items[index].transform = ev.transform;
   170        }
   171      }
   172    });
   173  };
   174  
   175  const onRenderGroup = e => {
   176    e.events.forEach(ev => {
   177      ev.target.style.transform = ev.transform;
   178    });
   179  };
   180  
   181  const onClickGroup = e => {
   182    if (!e.moveableTarget) {
   183      setSelectedTargets([]);
   184      selectCardItem(-1);
   185      return;
   186    }
   187    if (e.isTrusted) {
   188      selectoRef.value.clickTarget(e.inputEvent, e.moveableTarget);
   189    }
   190  };
   191  
   192  // ---------------------------------
   193  // group methods
   194  // ---------------------------------
   195  
   196  //todo add group
   197  // https://daybrush.com/moveable/storybook/index.html?path=/story/combination-with-other-components--components-selecto-with-multiple-group
   198  // https://daybrush.com/moveable/storybook/index.html?path=/story/combination-with-other-components--components-selecto
   199  
   200  const selectoRef = ref(null);
   201  
   202  const onSelect = (e) => {
   203    e.added.forEach(el => {
   204      el.classList.add("selected");
   205    });
   206    e.removed.forEach(el => {
   207      el.classList.remove("selected");
   208    });
   209  }
   210  
   211  const onSelectEnd = (e) => {
   212    const {
   213      isDragStartEnd,
   214      isClick,
   215      added,
   216      removed,
   217      inputEvent,
   218      selected,
   219    } = e;
   220    const moveable = moveableRef.value;
   221    if (e.isDragStart) {
   222      e.inputEvent.preventDefault();
   223      moveable.waitToChangeTarget().then(() => {
   224        //   moveable.dragStart(e.inputEvent);
   225      });
   226    }
   227    targets.value = selected;
   228    if (selected && selected.length == 1) {
   229      const classes = selected[0].className.split(' ');
   230      for (const cl of classes) {
   231        if (cl.includes('item-index-')) {
   232          const index = parseInt(cl.replace("item-index-", ""))
   233          selectCardItem(index)
   234        }
   235      }
   236    }
   237  
   238    if (selected && selected.length == 0) {
   239      selectCardItem(-1)
   240    }
   241  };
   242  
   243  const onDragStart = (e) => {
   244    const moveable = moveableRef.value;
   245    const target = e.inputEvent.target;
   246    const flatted = targets.value.flat(3);
   247    if (moveable.isMoveableElement(target)
   248        || flatted.some(t => t === target || t && t.contains(target))
   249    ) {
   250      e.stop();
   251    }
   252  };
   253  
   254  const getCardStyle = () => {
   255    const style = {
   256      transform: `scale(${zoom.value})`,
   257    }
   258    if (currentCard.value?.template) {
   259      style['background-color'] = 'inherit'
   260    } else {
   261      if (currentCard.value?.background) {
   262        style['background-color'] = currentCard.value.background
   263      } else {
   264        if (currentCard.value?.backgroundAdaptive) {
   265          style['background-color'] = appStore.isDark ? '#232324' : '#F5F7FA'
   266        }
   267      }
   268    }
   269  
   270    return style
   271  }
   272  
   273  const onContextMenu = (e: MouseEvent, owner: 'card' | 'cardItem', cardItemId?: number) => {
   274    e.preventDefault();
   275    e.stopPropagation();
   276    eventBus.emit('eventContextMenu', {
   277      event: e,
   278      owner: owner,
   279      tabId: currentCard.value.dashboardTabId,
   280      cardId: currentCard.value.id,
   281      cardItemId: cardItemId,
   282    } as EventContextMenu)
   283  }
   284  
   285  defineOptions({
   286    inheritAttrs: false
   287  })
   288  </script>
   289  
   290  <template>
   291  
   292    <div
   293        class="item-card elements selecto-area prevent-select"
   294        ref="cardRef"
   295        :class="[{'active': currentCard.active}, `class-${currentCard.currentID}`]"
   296        :key="reloadKey"
   297        :style="getCardStyle()"
   298        @mouseover="hover = true"
   299        @touchstart="hover = true"
   300        @mouseleave="hover = false"
   301        @mouseout="hover = false"
   302        @contextmenu="onContextMenu($event, 'card')"
   303    >
   304      <div class="card-label">active</div>
   305  
   306      <KeystrokeCaptureViewer :card="currentCard" :core="core" :hover="hover"/>
   307  
   308      <component
   309          v-for="(item, index) in currentCard.items"
   310          :key="index"
   311          class="movable"
   312          :style="item.position"
   313          v-bind:class="['item-index-'+index, 'item-id-'+item.id]"
   314          :is="getCardItemName(item)"
   315          :item="item"
   316          :core="core"
   317          :editor="true"
   318          @contextmenu="onContextMenu($event, 'cardItem', item.id)"
   319      />
   320  
   321      <Moveable
   322          ref="moveableRef"
   323          :target="targets"
   324          @drag="onDrag"
   325          @dragGroup="onDragGroup"
   326          @renderGroup="onRenderGroup"
   327          @clickGroup="onClickGroup"
   328          @resize="onResize"
   329          @rotate="onRotate"
   330          @onSnap="onSnap"
   331          v-bind="currentCard.settings()"
   332      />
   333  
   334      <VueSelecto
   335          ref="selectoRef"
   336          :rootContainer="cardRef"
   337          :selectableTargets="['.class-'+currentCard.currentID+' .movable']"
   338          :hitRate="0"
   339          :selectByClick="true"
   340          :selectFromInside="false"
   341          :toggleContinueSelect="['shift']"
   342          :ratio="0"
   343          @dragStart="onDragStart"
   344          @selectEnd="onSelectEnd"
   345          @select="onSelect"
   346      />
   347    </div>
   348  
   349  </template>
   350  
   351  <style lang="less">
   352  .movable {
   353    position: absolute;
   354  }
   355  
   356  .card-label {
   357    display: none;
   358    position: absolute;
   359    top: 18px;
   360    right: -17px;
   361    width: 55px;
   362    height: 20px;
   363    background: #4af;
   364    padding: 0 6px;
   365    transform: rotate(90deg);
   366    z-index: 9999;
   367    opacity: 0.5;
   368    color: #eeeeee;
   369  }
   370  
   371  .item-card {
   372    position: relative;
   373    overflow: hidden;
   374    width: 100%;
   375    height: 100%;
   376  
   377    &.active {
   378      .card-label {
   379        display: inherit;
   380      }
   381    }
   382  }
   383  </style>