Sfoglia il codice sorgente

feat: program dialog

Casper Dai 3 anni fa
parent
commit
a07f326f0c

+ 1 - 0
src/components/Schedule/ScheduleCalendar/index.vue

@@ -87,6 +87,7 @@
         <div class="o-conflict__desc">{{ conflict.desc }}</div>
       </div>
     </confirm-dialog>
+    <program-dialog ref="programDialog" />
     <schedule-dialog ref="scheduleDialog" />
   </schedule-wrapper>
 </template>

+ 2 - 1
src/components/Schedule/ScheduleSwiper/index.vue

@@ -93,6 +93,7 @@
         </div>
       </div>
     </template>
+    <program-dialog ref="programDialog" />
   </schedule-wrapper>
 </template>
 
@@ -233,7 +234,7 @@ export default {
       })
     },
     onView ({ id }) {
-      this.$viewProgram(id)
+      this.$refs.programDialog.show(id)
     },
     onDel (event, index) {
       this.events.splice(index, 1)

+ 3 - 1
src/components/Schedule/mixins/event.js

@@ -58,11 +58,13 @@ export default {
         this.editEvent(event)
       } else {
         switch (event.target.type) {
+          case EventTarget.PROGRAM:
+            this.$refs.programDialog.show(event.target.id)
+            break
           case EventTarget.RECUR:
             this.$refs.scheduleDialog.show(event.target.id)
             break
           default:
-            this.$viewProgram(event.target.id)
             break
         }
       }

+ 2 - 1
src/components/dialog/EventTargetDialog/index.vue

@@ -8,6 +8,7 @@
     v-bind="$attrs"
     @choosen="onChoosen"
   >
+    <program-dialog ref="programDialog" />
     <schedule-dialog ref="scheduleDialog" />
   </table-dialog>
 </template>
@@ -79,7 +80,7 @@ export default {
     onView ({ id }) {
       switch (this.$refs.tableDialog.getTable().getCondition().type) {
         case EventTarget.PROGRAM:
-          this.$viewProgram(id)
+          this.$refs.programDialog.show(id)
           break
         case EventTarget.RECUR:
           this.$refs.scheduleDialog.show(id)

+ 76 - 0
src/components/dialog/ProgramDialog/index.vue

@@ -0,0 +1,76 @@
+<template>
+  <el-dialog
+    :visible.sync="isPreviewing"
+    custom-class="c-dialog--preview program"
+    :before-close="onCloseScheduleDialog"
+    :close-on-click-modal="false"
+    append-to-body
+    v-on="$attrs"
+    @closed="onClosed"
+  >
+    <program
+      v-if="program"
+      :program="program"
+    />
+    <i
+      class="l-flex__none c-dialog--preview__close el-icon-close u-bold u-pointer"
+      @click="onCloseScheduleDialog"
+    />
+  </el-dialog>
+</template>
+
+<script>
+import { getProgram } from '@/api/program'
+import Program from '@/views/bigscreen/ast/Previewer'
+
+export default {
+  name: 'ProgramDialog',
+  components: {
+    Program
+  },
+  data () {
+    return {
+      program: null,
+      isPreviewing: false
+    }
+  },
+  methods: {
+    show (id) {
+      const loading = this.$showLoading()
+      getProgram(id).then(({ data }) => {
+        try {
+          const { id, status, name, resolutionRatio, itemJsonStr } = data
+          const [width, height] = resolutionRatio.split('x')
+          if (!width || !height) {
+            this.showMessage('error', '布局分辨率异常,请联系管理员')
+            return
+          }
+
+          this.program = {
+            id, status, name, resolutionRatio,
+            detail: {
+              width: Number(width),
+              height: Number(height),
+              ...JSON.parse(itemJsonStr)
+            }
+          }
+          this.isPreviewing = true
+        } catch (e) {
+          this.showMessage('error', '布局解析失败')
+        }
+      }).finally(() => {
+        this.$closeLoading(loading)
+      })
+    },
+    onCloseScheduleDialog () {
+      this.isPreviewing = false
+    },
+    showMessage (type, message) {
+      this.$message({ type, message })
+    },
+    onClosed () {
+      this.program = null
+    }
+  }
+}
+</script>

+ 5 - 1
src/components/dialog/ScheduleDialog/index.vue

@@ -3,8 +3,10 @@
     :visible.sync="isPreviewing"
     custom-class="c-dialog--preview"
     :before-close="onCloseScheduleDialog"
+    :close-on-click-modal="false"
     append-to-body
     v-on="$attrs"
+    @closed="onClosed"
   >
     <schedule
       v-if="scheduleId"
@@ -33,8 +35,10 @@ export default {
       this.isPreviewing = true
     },
     onCloseScheduleDialog () {
-      this.scheduleId = null
       this.isPreviewing = false
+    },
+    onClosed () {
+      this.scheduleId = null
     }
   }
 }

+ 2 - 2
src/global-api.js

@@ -19,10 +19,10 @@ export function injectGlobalApi (Vue, store) {
   Vue.prototype.$showLoading = showLoading
   Vue.prototype.$closeLoading = closeLoading
 
-  Vue.prototype.$viewProgram = function (id, type = 'view') {
+  Vue.prototype.$designProgram = function (id) {
     window.open(this.$router.resolve({
       name: 'program',
-      params: { type, id }
+      params: { id }
     }).href, '_blank')
   }
 }

+ 1 - 1
src/router/index.js

@@ -362,7 +362,7 @@ export const asyncRoutes = [
   {
     hidden: true,
     name: 'program',
-    path: '/cm/program/:type/:id',
+    path: '/cm/program/:id',
     component: () => import('@/views/bigscreen/ast/index'),
     props: true
   },

+ 8 - 2
src/scss/bem/_component.scss

@@ -78,9 +78,15 @@
   box-shadow: none;
   background-color: transparent;
 
-  &.large {
+  &.program {
+    width: auto;
+    height: auto;
+    min-height: 100%;
+
     .el-dialog__body {
-      height: 84%;
+      align-self: center;
+      min-height: 300px;
+      max-height: min-content;
     }
   }
 

+ 5 - 1
src/views/bigscreen/Program.vue

@@ -73,7 +73,11 @@ export default {
   methods: {
     onClick () {
       const { id, status } = this.program
-      this.$viewProgram(id, status !== State.SUBMITTED && status !== State.RESOLVED ? 'design' : 'view')
+      if (status !== State.SUBMITTED && status !== State.RESOLVED) {
+        this.$designProgram(id)
+      } else {
+        this.$emit('view', this.program)
+      }
     },
     onEdit () {
       const name = this.name

+ 3 - 1
src/views/bigscreen/ProgramDesigner.vue

@@ -42,6 +42,7 @@
         <program
           :key="item.id"
           :program="item"
+          @view="onView"
         >
           <!-- <div
             v-if="item.status !== 1 && item.status !== 2"
@@ -100,6 +101,7 @@
         />
       </div>
     </confirm-dialog>
+    <program-dialog ref="programDialog" />
   </wrapper>
 </template>
 
@@ -174,7 +176,7 @@ export default {
         this.active = `${State.READY}`
         this.$refs.table.resetCondition({ status: State.READY, name: this.program.name })
         setTimeout(() => {
-          this.$viewProgram(id, 'design')
+          this.$designProgram(id)
         }, 500)
       })
     },

+ 2 - 0
src/views/bigscreen/ProgramViewer.vue

@@ -13,6 +13,7 @@
         <program
           :key="item.id"
           :program="item"
+          @view="onView"
         >
           <div
             v-if="item.del"
@@ -23,6 +24,7 @@
         </program>
       </grid-table-item>
     </grid-table>
+    <program-dialog ref="programDialog" />
   </wrapper>
 </template>
 

+ 350 - 0
src/views/bigscreen/ast/Previewer.vue

@@ -0,0 +1,350 @@
+<template>
+  <div class="l-flex--col c-viewer">
+    <audio
+      v-if="bgmUrl"
+      ref="audio"
+      :src="bgmUrl"
+      :muted.prop="muted"
+      autoplay
+      @ended="onAudioEnded"
+      @error="onAudioError"
+    />
+    <div class="l-flex__none l-flex--row c-viewer__header">
+      <span class="c-viewer__name u-bold u-ellipsis">{{ program.name }}</span>
+      <span class="c-sibling-item">{{ program.resolutionRatio }}</span>
+      <div
+        v-if="hasAudio"
+        class="c-sibling-item c-viewer__shortcut u-pointer"
+        @click="toggleMute"
+      >
+        <volume :muted="muted" />
+      </div>
+      <div
+        v-if="hasNext"
+        class="c-sibling-item c-viewer__shortcut u-pointer"
+        @click="switchBgm"
+      >
+        <i class="o-next" />
+      </div>
+    </div>
+    <div class="l-flex__none l-flex">
+      <div class="l-flex__none l-flex--col c-viewer__asserts">
+        <div class="l-flex__none l-flex--row c-viewer__count has-padding--h">
+          <span class="l-flex__auto u-bold">素材</span>
+          <span class="l-flex__none c-viewer__count--info">{{ count }}</span>
+        </div>
+        <el-scrollbar
+          class="c-viewer__scrollbar"
+          native
+        >
+          <div class="c-viewer__list">
+            <div
+              v-for="widget in layers"
+              ref="widgetElements"
+              :key="widget.id"
+              class="o-layer"
+              :class="{ active: widget.id === selectedWidgetId }"
+              @click="onWidgetClick(widget, $event)"
+            >
+              <widget-shortcut
+                :widget="widget"
+                @view="onView"
+              />
+            </div>
+            <div
+              v-if="hasRootAssets"
+              ref="rootElement"
+              key="root"
+              class="o-layer"
+              :class="{ active: !selectedWidgetId }"
+              @click="onRootClick"
+            >
+              <widget-shortcut
+                :widget="node"
+                :custom-style="backgroundStyles"
+                source-key="bgm"
+                background
+                @view="onView"
+              />
+            </div>
+          </div>
+        </el-scrollbar>
+      </div>
+      <div
+        class="l-flex__none"
+        :style="canvasStyles"
+        @click="onRootClick"
+      >
+        <div
+          class="c-viewer__canvas has-bg"
+          :style="[transformStyles, styles]"
+        >
+          <div
+            class="c-viewer__background has-bg"
+            :style="backgroundStyles"
+          />
+          <widget
+            v-for="item in widgets"
+            :key="item.id"
+            :class="{ 'breathe-inset': item.id === selectedWidgetId }"
+            :node="item"
+            @click.native="onWidgetClick(item, $event)"
+          />
+        </div>
+      </div>
+    </div>
+    <preview-dialog
+      ref="previewDialog"
+      append-to-body
+      @close="onClose"
+    />
+  </div>
+</template>
+
+<script>
+import { AssetType } from '@/constant'
+import { WidgetType } from './core/constant'
+import mixin from './mixin'
+import Widget from './core/widget/WidgetViewer.vue'
+
+export default {
+  name: 'BigScreenPreviewer',
+  components: {
+    Widget
+  },
+  mixins: [mixin],
+  data () {
+    return {
+      selectedWidget: null,
+      canvasStyles: null
+    }
+  },
+  computed: {
+    layers () {
+      return this.widgets.filter(this.hasAssets).slice().reverse()
+    },
+    count () {
+      if (this.node) {
+        return this.node.bgm.length + this.node.backgroundImage.length + this.layers.reduce((total, { sources }) => total + sources.length, 0)
+      }
+      return 0
+    },
+    selectedWidgetId () {
+      return this.selectedWidget?.id
+    },
+    hasRootAssets () {
+      if (this.node) {
+        const { backgroundImage, bgm } = this.node
+        return backgroundImage.length > 0 || bgm.length > 0
+      }
+      return false
+    }
+  },
+  watch: {
+    selectedWidget () {
+      this.node && setTimeout(() => {
+        if (this.selectedWidget) {
+          this.$refs.widgetElements?.find(element => element.classList.contains('active'))?.scrollIntoView({ behavior: 'smooth', block: 'start' })
+        } else if (this.hasRootAssets) {
+          this.$refs.rootElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
+        }
+      })
+    }
+  },
+  methods: {
+    hasAssets (widget) {
+      switch (widget.type) {
+        case WidgetType.MEDIA:
+        case WidgetType.IMAGE:
+        case WidgetType.VIDEO:
+          return true
+        default:
+          return false
+      }
+    },
+    onWidgetClick (widget, evt) {
+      if (this.hasAssets(widget)) {
+        evt.stopPropagation()
+        if (!this.selectedWidget || this.selectedWidget.id !== widget.id) {
+          this.selectedWidget = widget
+        }
+      }
+    },
+    onRootClick () {
+      this.selectedWidget = null
+    },
+    onView (source) {
+      this.$muted = this.muted
+      switch (source.type) {
+        case AssetType.VIDEO:
+        case AssetType.AUDIO:
+          if (!this.muted) {
+            this.muted = true
+          }
+          break
+        default:
+          break
+      }
+      this.$refs.previewDialog.show(source)
+    },
+    onClose () {
+      this.muted = this.$muted
+    },
+    checkRatio () {
+      if (this.canvasStyles) {
+        return
+      }
+      const { width, height } = this.node
+      if (width <= 960 && height <= 540) {
+        this.canvasStyles = {}
+      } else {
+        const wScale = 960 / width
+        const hScale = 540 / height
+        this.scale = Math.min(wScale, hScale) * 100 | 0
+        if (wScale <= hScale) {
+          this.canvasStyles = {
+            width: '960px',
+            height: `${height * wScale}px`
+          }
+        } else {
+          this.canvasStyles = {
+            width: `${width * hScale}px`,
+            height: '540px'
+          }
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.c-viewer {
+  &__header {
+    height: 54px;
+    padding: 0 $spacing;
+    color: $black;
+    font-size: 12px;
+    line-height: 1;
+    border-bottom: 1px solid $border;
+    background-color: #fff;
+  }
+
+  &__shortcut {
+    display: inline-flex;
+    justify-content: center;
+    align-items: center;
+    width: 36px;
+    height: 36px;
+    font-size: 18px;
+    border-radius: 50%;
+    transition: background-color 0.4s;
+
+    &:hover {
+      background-color: $gray--light;
+    }
+
+    &:active {
+      background-color: $gray;
+    }
+  }
+
+  &__name {
+    max-width: 400px;
+    padding: 0 $spacing 0 6px;
+    font-size: 18px;
+  }
+
+  &__asserts {
+    width: 160px;
+    border-right: 1px solid $border;
+    background-color: #fff;
+  }
+
+  &__count {
+    height: 48px;
+    color: $black;
+    font-size: 16px;
+    line-height: 1;
+    background-color: #e8eaee;
+
+    &--info {
+      color: $info--dark;
+      font-size: 12px;
+    }
+  }
+
+  &__scrollbar {
+    flex: 1 1 0;
+    display: flex;
+    flex-direction: column;
+    min-height: 0;
+
+    ::v-deep {
+      .el-scrollbar__wrap {
+        flex: 1 1 0;
+        min-height: 0;
+        margin-bottom: 0 !important;
+      }
+    }
+  }
+
+  &__list {
+    padding: 10px;
+  }
+
+  &__canvas {
+    position: relative;
+    background-color: #000;
+    overflow: hidden;
+  }
+
+  &__background {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: currentColor;
+    z-index: -1;
+  }
+}
+
+.o-layer {
+  color: $black;
+  user-select: none;
+  border-radius: $radius;
+  background-color: #f4f7f8;
+  overflow: hidden;
+  cursor: pointer;
+
+  & + & {
+    margin-top: 10px;
+  }
+
+  &.active {
+    color: #fff;
+    outline: 2px solid $blue;
+    background-color: $blue;
+  }
+}
+
+.breathe-inset::after {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  animation: breathe-inset 1s ease-in-out infinite alternate;
+}
+
+@keyframes breathe-inset {
+  0% {
+    box-shadow: inset 0 0 20px 30px rgba($blue, 0.5);
+  }
+  100% {
+    box-shadow: inset 0 0 60px 30px $blue;
+  }
+}
+</style>

+ 2 - 2
src/views/bigscreen/ast/Viewer.vue

@@ -80,7 +80,7 @@
         @click="onRootClick"
       >
         <div
-          class="l-flex__fill c-viewer__canvas has-bg"
+          class="c-viewer__canvas has-bg"
           :style="[transformStyles, styles]"
         >
           <div
@@ -239,7 +239,7 @@ export default {
   }
 
   &__asserts {
-    width: 145px;
+    width: 160px;
     border-right: 1px solid $border;
     background-color: #fff;
   }

+ 8 - 16
src/views/bigscreen/ast/index.vue

@@ -1,10 +1,7 @@
 <script>
 import { mapGetters } from 'vuex'
 import { getProgram } from '@/api/program'
-import {
-  State,
-  Access
-} from '@/constant'
+import { Access } from '@/constant'
 import Designer from './Designer'
 import Viewer from './Viewer'
 
@@ -18,10 +15,6 @@ export default {
     next(false)
   },
   props: {
-    type: {
-      type: String,
-      default: 'view'
-    },
     id: {
       type: String,
       default: null
@@ -58,21 +51,20 @@ export default {
               this.showMessage('warning', '无权限')
               return
             }
-            const [width, height] = resolutionRatio.split('x')
-            if (!width || !height) {
-              this.showMessage('error', '布局分辨率异常,请联系管理员')
-              return
-            }
 
-            if (this.type !== 'design' || status === State.SUBMITTED || status === State.RESOLVED) {
-              this.activeComponent = 'Viewer'
-            } else if (this.accessSet.has(Access.MANAGE_CALENDAR) && (this.isSuperAdmin || createBy === this.userId)) {
+            if (this.accessSet.has(Access.MANAGE_CALENDAR) && (this.isSuperAdmin || createBy === this.userId)) {
               this.activeComponent = 'Designer'
             } else {
               this.showMessage('warning', '暂无编辑权限,请联系管理员')
               return
             }
 
+            const [width, height] = resolutionRatio.split('x')
+            if (!width || !height) {
+              this.showMessage('error', '布局分辨率异常,请联系管理员')
+              return
+            }
+
             this.program = {
               id, status, name, resolutionRatio,
               detail: {

+ 3 - 0
src/views/bigscreen/mixin.js

@@ -33,6 +33,9 @@ export default {
       deleteProgram(item).then(() => {
         this.$refs.table.decrease(1)
       })
+    },
+    onView ({ id }) {
+      this.$refs.programDialog.show(id)
     }
   }
 }

+ 2 - 1
src/views/device/timeline/index.vue

@@ -175,6 +175,7 @@
         @pagination="getDevices"
       />
     </div>
+    <program-dialog ref="programDialog" />
     <schedule-dialog ref="scheduleDialog" />
   </wrapper>
 </template>
@@ -459,7 +460,7 @@ export default {
       if (this.programProxy) {
         switch (this.programProxy.event.target.type) {
           case EventTarget.PROGRAM:
-            this.$viewProgram(this.programProxy.event.target.id)
+            this.$refs.programDialog.show(this.programProxy.event.target.id)
             break
           case EventTarget.RECUR:
             this.$refs.scheduleDialog.show(this.programProxy.event.target.id)

+ 4 - 4
src/views/realm/settings/components/Settings.vue

@@ -62,9 +62,9 @@ export default {
 <style lang="scss" scoped>
 .o-settings {
   align-self: flex-start;
-  display: flex;
-  align-items: flex-start;
-  flex-wrap: wrap;
-  gap: $spacing;
+  display: grid;
+  grid-template-columns: repeat(auto-fill, 120px);
+  grid-row-gap: $spacing;
+  grid-column-gap: $spacing;
 }
 </style>

+ 4 - 2
src/views/review/components/ReviewProgram.vue

@@ -2,7 +2,9 @@
   <schema-table
     ref="table"
     :schema="schema"
-  />
+  >
+    <program-dialog ref="programDialog" />
+  </schema-table>
 </template>
 
 <script>
@@ -43,7 +45,7 @@ export default {
       return program
     },
     onView ({ id }) {
-      this.$viewProgram(id)
+      this.$refs.programDialog.show(id)
     }
   }
 }

+ 2 - 1
src/views/review/components/ReviewPublish.vue

@@ -4,6 +4,7 @@
     :schema="schema"
     @row-click="onToggle"
   >
+    <program-dialog ref="programDialog" />
     <schedule-dialog ref="scheduleDialog" />
   </schema-table>
 </template>
@@ -108,7 +109,7 @@ export default {
           if (detail.target.type === EventTarget.RECUR) {
             this.viewSchedule(detail.target.id)
           } else {
-            this.$viewProgram(detail.target.id)
+            this.$refs.programDialog.show(detail.target.id)
           }
           break
         default:

+ 5 - 4
src/views/review/workflow/detail/index.vue

@@ -72,6 +72,7 @@
           @resolve="onResolve"
           @reject="onConfirmReject"
         />
+        <program-dialog ref="programDialog" />
         <schedule-dialog ref="scheduleDialog" />
       </schema-table>
       <div
@@ -622,7 +623,7 @@ export default {
       this.$refs.scheduleDialog.show(id)
     },
     viewProgram (id) {
-      this.$viewProgram(id)
+      this.$refs.programDialog.show(id)
     },
     // 查看
     onView (row) {
@@ -634,11 +635,11 @@ export default {
           )
           break
         case front2back['program']:
-          this.$viewProgram(id)
+          this.viewProgram(id)
           break
         case front2back['schedule']:
         case front2back['programRecur']:
-          this.$refs.scheduleDialog.show(id)
+          this.viewSchedule(id)
           break
         case front2back['publish']:
           this.onPublishView(row)
@@ -794,7 +795,7 @@ export default {
 .fl-end {
   justify-content: flex-end;
 }
-.applicant{
+.applicant {
   font-size: 14px;
   margin-left: 30px;
   font-weight: 400;

+ 1 - 24
src/views/review/workflow/index.vue

@@ -18,8 +18,7 @@ import { getPublishWorkflows } from '@/api/workflow'
 import {
   PublishType,
   EventPriority,
-  State,
-  EventTarget
+  State
 } from '@/constant'
 import { getEventDescription } from '@/utils/event'
 
@@ -136,28 +135,6 @@ export default {
     onToggle (row) {
       this.$refs.table.getInst().toggleRowExpansion(row)
     },
-    onView ({ target: { type, detail } }) {
-      switch (type) {
-        case PublishType.CALENDAR:
-          this.viewSchedule(detail)
-          break
-        case PublishType.EVENT:
-          if (detail.target.type === EventTarget.RECUR) {
-            this.viewSchedule(detail.target.id)
-          } else {
-            this.viewProgram(detail.target.id)
-          }
-          break
-        default:
-          break
-      }
-    },
-    viewSchedule (id) {
-      this.$refs.scheduleDialog.show(id)
-    },
-    viewProgram (id) {
-      this.$viewProgram(id)
-    },
     review (item) {
       this.$router.push({
         name: 'workflow-detail',

+ 3 - 5
src/views/review/workflow/mine/index.vue

@@ -28,6 +28,7 @@
       :schema="schema"
       @row-click="onToggle"
     />
+    <program-dialog ref="programDialog" />
     <schedule-dialog ref="scheduleDialog" />
   </wrapper>
 </template>
@@ -187,7 +188,7 @@ export default {
           if (detail.target.type === EventTarget.RECUR) {
             this.viewSchedule(detail.target.id)
           } else {
-            this.viewProgram(detail.target.id)
+            this.$refs.programDialog.show(detail.target.id)
           }
           break
         default:
@@ -219,11 +220,8 @@ export default {
         params: { id: `${id}` }
       })
     },
-    viewProgram (id) {
-      this.$viewProgram(id)
-    },
     editProgram (id) {
-      this.$viewProgram(id, 'design')
+      this.$designProgram(id)
     },
     restart (item) {
       calendarPublishRestart(item.workflowId, item.name).then(() => {

+ 2 - 1
src/views/schedule/deploy/index.vue

@@ -108,6 +108,7 @@
         </div>
       </template>
     </div>
+    <program-dialog ref="programDialog" />
   </wrapper>
 </template>
 
@@ -251,7 +252,7 @@ export default {
       this.eventOptions.schedule = { id, name }
     },
     onViewProgram () {
-      this.$viewProgram(this.programId)
+      this.$refs.programDialog.show(this.programId)
     },
     getPublishTarget () {
       if (this.eventOptions.type === PublishType.CALENDAR) {

+ 2 - 1
src/views/schedule/history/index.vue

@@ -10,6 +10,7 @@
       :schema="schema"
       @row-click="onToggle"
     />
+    <program-dialog ref="programDialog" />
     <schedule-dialog ref="scheduleDialog" />
   </wrapper>
 </template>
@@ -104,7 +105,7 @@ export default {
           if (detail.target.type === EventTarget.RECUR) {
             this.viewSchedule(detail.target.id)
           } else {
-            this.$viewProgram(detail.target.id)
+            this.$refs.programDialog.show(detail.target.id)
           }
           break
         default: