<template>
  <div
    v-loading.lock="loading"
    element-loading-background="rgba(0, 0, 0, 0.8)"
    class="l-flex--col c-designer"
    @contextmenu.prevent
  >
    <audio
      ref="audio"
      :src="bgmUrl"
      :muted.prop="muted"
      autoplay
      @ended="onAudioEnded"
      @error="onAudioError"
    />
    <div class="l-flex__none l-flex--row c-designer__header">
      <i
        class="c-designer__shortcut el-icon-arrow-left u-bold u-pointer"
        @click="onBack"
      />
      <span class="c-designer__name u-bold u-ellipsis">{{ program.name }}</span>
      <span class="c-sibling-item">{{ program.resolutionRatio }}</span>
      <div
        v-if="hasNext"
        class="c-sibling-item c-designer__shortcut u-pointer"
        @click="switchBgm"
      >
        <i class="o-next" />
      </div>
      <div
        v-if="hasAudio"
        class="c-sibling-item c-designer__shortcut u-pointer"
        @click="toggleMute"
      >
        <volume :muted="muted" />
      </div>
      <div class="l-flex__fill c-sibling-item" />
      <button
        v-if="hasWidgets"
        class="c-sibling-item o-button mini"
        @click="onClear"
      >
        <i class="o-button__icon el-icon-delete" />
        清空
      </button>
      <button
        class="c-sibling-item o-button mini"
        @click="onSave"
      >
        <i class="o-button__icon iconfont iconsave" />
        保存
      </button>
    </div>
    <div class="l-flex__fill l-flex">
      <div class="c-designer__side left c-side">
        <div class="c-side__tool">
          <div class="c-side__item">
            组件
          </div>
        </div>
        <el-scrollbar
          class="c-side__scrollbar"
          native
        >
          <div class="c-side__content mini">
            <div
              v-for="(widget, index) in layers"
              ref="widgetElements"
              :key="widget.id"
              class="o-layer"
              :class="{ active: widget.id === selectedWidgetId }"
              @mousedown="onLayerClick($event, index)"
            >
              <widget-shortcut
                class="dark"
                :widget="widget"
                editable
              />
            </div>
            <div
              v-if="node"
              ref="rootElement"
              key="root"
              class="o-layer"
              :class="{ active: !selectedWidgetId }"
              @click="onRootClick"
            >
              <widget-shortcut
                class="dark"
                :widget="node"
                :custom-style="backgroundStyles"
                source-key="bgm"
                background
                editable
              />
            </div>
          </div>
        </el-scrollbar>
      </div>
      <div class="c-designer__main">
        <div class="l-flex--row c-designer__tool">
          <div class="o-scale-slider">
            <div class="l-flex--row o-scale-slider__wrapper">
              <i
                class="el-icon-zoom-out u-pointer"
                @click="scaleDown"
              />
              <el-slider
                v-model="scale"
                class="o-scale-slider__slider"
                :min="minScale"
                :max="maxScale"
              />
              <i
                class="el-icon-zoom-in u-pointer"
                @click="scaleUp"
              />
            </div>
          </div>
          <div
            class="l-flex__none l-flex--row"
            @dragstart="widgetOnDragStart"
            @dragend="widgetOnDragEnd"
          >
            <div
              v-for="cfg in widgetConfigs"
              :key="cfg.key"
              :data-type="cfg.type"
              class="o-widget-cfg"
              draggable
            >
              <span class="o-widget-cfg__icon">
                <svg-icon :icon-class="cfg.icon" />
              </span>
              <span class="o-widget-cfg__text">{{ cfg.label }}</span>
            </div>
          </div>
        </div>
        <div
          ref="wrapper"
          class="c-designer__content"
          @mousedown="onRootClick"
        >
          <div
            class="c-designer__wrapper"
            :style="wrapperStyles"
          >
            <div
              ref="canvas"
              class="c-designer__canvas"
              :style="[transformStyles, styles]"
              @dragover.prevent
              @drop.prevent="widgetOnDrop"
            >
              <div
                class="c-designer__background has-bg"
                :style="backgroundStyles"
              />
              <div
                v-show="grid"
                class="c-designer__grid"
              />
              <widget
                v-for="(item, index) in widgets"
                :key="item.id"
                ref="widgets"
                :scale="100 / scale"
                :node="item"
                :root="node"
                @focus="onWidgetFocus(index)"
                @will-move="onWidgetWillMove"
                @blur="onWidgetBlur"
                @menu="onWidgetMenu($event, index)"
                @dblclick.native="onWidgetDblclick"
              />
            </div>
          </div>
        </div>
      </div>
      <div class="c-designer__side right c-side">
        <div class="c-side__tool">
          <div
            v-for="(tab, index) in dynamicOptions"
            :key="index"
            class="c-side__item u-pointer"
            :class="{ active: optionIndex === index }"
            @click="optionIndex = index"
          >
            {{ tab.label }}
          </div>
        </div>
        <div
          v-for="(tab, index) in dynamicOptions"
          v-show="optionIndex === index"
          :key="index"
          class="c-side__scrollbar c-side__content u-overflow-y--auto"
          :class="{ active: optionIndex === index }"
        >
          <dynamic-item
            v-for="item in tab.list"
            :key="item.key"
            class="c-sibling-item--v far"
            :root="node"
            :node="widget"
            :attr="item"
            @choose="onEditData"
          />
        </div>
      </div>
    </div>
    <content-menu
      :visible.sync="visibleContentMenu"
      :style-obj="styleObj"
      @delete="deleteLayer"
      @copy="copyLayer"
      @top="topLayer"
      @low="lowLayer"
      @up="upLayer"
      @down="downLayer"
    />
    <el-dialog
      :visible.sync="showAssets"
      :title="assetDialogType"
      custom-class="c-dialog large"
      :close-on-click-modal="false"
      :before-close="onCloseAssetsDialog"
    >
      <draggable
        v-if="showAssets"
        v-model="sources"
        class="l-grid--info mini"
        :class="{ dragging }"
        animation="300"
        @start="onSourceDragStart"
        @end="onSourceDragEnd"
      >
        <media-card
          v-for="(source, index) in sources"
          :key="index"
          class="o-card"
          :source="source"
          @click="onView"
        >
          <div
            v-if="needTypeTag"
            class="o-card__tag"
          >{{ source.tag }}</div>
          <i
            class="o-card__icon el-icon-delete has-active"
            @mousedown.stop
            @pointerdown.stop
            @click.stop="onDelAsset(index)"
          />
        </media-card>
      </draggable>
      <template #footer>
        <button
          class="o-button"
          @click="onAddAssets"
        >
          新增
        </button>
        <button
          class="o-button"
          @click="onSaveAssets"
        >
          确定
        </button>
        <button
          class="o-button cancel"
          @click="onCloseAssetsDialog"
        >
          取消
        </button>
      </template>
    </el-dialog>
    <el-dialog
      :visible.sync="showServerAssets"
      :title="assetDialogTitle"
      custom-class="c-dialog"
      append-to-body
    >
      <template v-if="showServerAssets">
        <grid-table :schema="assetSchema">
          <grid-table-item v-slot="item">
            <media-card
              :source="item"
              @dblclick="onChoosenAsset"
            />
          </grid-table-item>
        </grid-table>
      </template>
    </el-dialog>
    <confirm-dialog
      ref="assetsDialog"
      :title="assetDialogTitle"
      size
      append-to-body
      @confirm="onChoosenAssets"
      @cancel="showServerAssetsMulti = false"
    >
      <template v-if="showServerAssetsMulti">
        <grid-table
          ref="gridTable"
          :schema="assetSchema"
          :custom="!!fileDir"
        >
          <template #header>
            <i
              v-if="!!fileDir"
              class="o-icon medium o-icon--back el-icon-arrow-left u-bold u-pointer"
              @click="onDirBack"
            />
          </template>
          <grid-table-item v-slot="item">
            <media-card
              :source="item"
              @click="onToggleGrid"
            >
              <el-checkbox
                v-model="item.selected"
                class="o-card__checkbox"
              />
              <i
                class="o-card__play el-icon-video-play has-active u-pointer"
                @click.stop="onView(item)"
              />
              <i
                v-if="item.files"
                class="o-card__grid el-icon-s-grid has-active u-pointer"
                @click.stop="onChooseDir(item)"
              />
            </media-card>
          </grid-table-item>
          <div
            v-if="fileDir"
            class="l-flex__self l-grid u-overflow-y--auto"
          >
            <media-card
              v-for="file in fileDir.files"
              :key="file.keyName"
              :source="file"
              @click="onToggleGrid"
            >
              <el-checkbox
                v-model="file.selected"
                class="o-card__checkbox"
              />
              <i
                class="o-card__play el-icon-video-play has-active u-pointer"
                @click.stop="onView(file)"
              />
            </media-card>
          </div>
        </grid-table>
      </template>
    </confirm-dialog>
    <preview-dialog
      ref="previewDialog"
      @close="onClosePreview"
    />
    <confirm-dialog
      ref="rich"
      title="文本编辑"
      size
      append-to-body
      @confirm="onRichConfirm"
    >
      <toolbar
        style="border-bottom: 1px solid #ccc"
        mode="simple"
        :editor="editor"
        :default-config="toolbarConfig"
      />
      <editor
        v-model="richHtml"
        mode="simple"
        style="height: 500px; overflow: hidden;"
        :default-config="editorConfig"
        @onCreated="onEditorCreated"
        @customPaste="onCustomPaste"
      />
    </confirm-dialog>
  </div>
</template>

<script>
import domToImage from 'dom-to-image'
import { updateProgram } from '@/api/program'
import {
  getAssets,
  getThumbnailUrl
} from '@/api/asset'
import {
  State,
  AssetType,
  AssetTypeInfo,
  AssetTag,
  AssetTagInfo
} from '@/constant'
import {
  widgets,
  isMediaWidget,
  normalize,
  getOptions,
  copy,
  fix,
  getDuration,
  getUsedAssets,
  toJSON
} from './core/utils'
import mixin from './mixin'
import Draggable from 'vuedraggable'
import Widget from './core/widget/Widget.vue'
import ContentMenu from './components/ContentMenu.vue'
import DynamicItem from './components/DynamicItem.vue'
import {
  Editor,
  Toolbar
} from '@wangeditor/editor-for-vue'
import { DomEditor } from '@wangeditor/editor'
import '@wangeditor/editor/dist/css/style.css'

export default {
  name: 'BigScreenDesigner',
  components: {
    Draggable,
    Widget,
    ContentMenu,
    DynamicItem,
    Editor,
    Toolbar
  },
  mixins: [mixin],
  data () {
    return {
      loading: false,
      snapping: false,
      padding: 16,
      optionIndex: 0,
      widgetConfigs: widgets,
      grid: false,
      visibleContentMenu: false,
      styleObj: null,
      widgetAttr: null,
      showAssets: false,
      sources: null,
      dragging: false,
      dragScale: false,
      showServerAssets: false,
      showServerAssetsMulti: false,
      fileDir: null,
      editor: null,
      richHtml: '',
      toolbarConfig: {
        toolbarKeys: [
          'headerSelect',
          '|',
          'bold',
          // 'color',
          // '|',
          // 'fontSize',
          // 'lineHeight',
          '|',
          'justifyLeft',
          'justifyCenter',
          'justifyRight'
        ]
      },
      editorConfig: {
        placeholder: '请输入内容...'
      }
    }
  },
  computed: {
    hasWidgets () {
      return this.widgets.length > 0
    },
    dynamicOptions () {
      if (this.widget) {
        return getOptions(this.widget.type)
      }
      return []
    },
    layers () {
      return this.widgets.slice().reverse()
    },
    assetType () {
      if (this.widgetAttr?.type === 'data') {
        return this.widgetAttr.options.type
      }
      return []
    },
    assetSchema () {
      return {
        condition: { status: State.AVAILABLE, tag: AssetTag.AD, type: this.assetType[0], originalName: '' },
        list: getAssets,
        transform: this.transformAsset,
        filters: [
          { key: 'tag', type: 'select', options: [
            { value: AssetTag.AD, label: AssetTagInfo[AssetTag.AD] },
            { value: AssetTag.PUBLICITY, label: AssetTagInfo[AssetTag.PUBLICITY] },
            { value: AssetTag.LOCAL_PUBLICITY, label: AssetTagInfo[AssetTag.LOCAL_PUBLICITY] }
          ] },
          this.assetType.length > 1
            ? {
              key: 'type', type: 'select', options: this.assetType.map(type => {
                return {
                  value: type,
                  label: this.getAssetType(type)
                }
              })
            }
            : null,
          { key: 'originalName', type: 'search', placeholder: '媒资名称' }
        ].filter(Boolean)
      }
    },
    needTypeTag () {
      return this.assetType.length > 1
    },
    assetDialogType () {
      return this.assetType.length
        ? this.assetType.length > 1
          ? '媒资'
          : this.getAssetType(this.assetType[0])
        : ''
    },
    assetDialogTitle () {
      return this.fileDir
        ? this.fileDir.name
        : `请选择${this.assetDialogType}`
    },
    selectedWidgetId () {
      return this.widget?.id
    },
    wrapperStyles () {
      return this.node
        ? {
          width: `${this.node.width * this.scale / 100}px`,
          height: `${this.node.height * this.scale / 100}px`
        }
        : null
    }
  },
  watch: {
    selectedWidgetIndex (val, old) {
      if (old > -1) {
        const id = this.widgets[old]?.id
        id && this.$refs.widgets[this.$refs.widgets.findIndex(w => w.node.id === id)]?.$refs.draggable.setActive(false)
      }
      if (val > -1) {
        const id = this.widgets[val]?.id
        id && this.$refs.widgets[this.$refs.widgets.findIndex(w => w.node.id === id)]?.$refs.draggable.setActive(true)
      }
      this.optionIndex = 0
    },
    widget () {
      this.node && setTimeout(() => {
        if (this.selectedWidgetIndex === -1) {
          this.$refs.rootElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
        } else {
          this.$refs.widgetElements.find(element => element.classList.contains('active')).scrollIntoView({ behavior: 'smooth', block: 'start' })
        }
      })
    }
  },
  beforeDestroy () {
    if (this.editor) {
      this.editor.destroy()
    }
  },
  methods: {
    onEditorCreated (editor) {
      this.editor = Object.seal(editor)
      this.$nextTick(() => {
        console.log(DomEditor.getToolbar(editor).getConfig())
      })
    },
    onCustomPaste (editor, event, callback) {
      // 返回 true ，继续默认的粘贴行为
      callback(true)

      this.$nextTick(() => {
        editor.setHtml(editor.getHtml().replace(/text-indent[^;]+;/g, ''))
      })
    },
    onRichConfirm (done) {
      this.widget[this.widgetAttr.key] = this.richHtml
      done()
    },
    getAssetType (type) {
      return AssetTypeInfo[type] || '媒资'
    },
    onClear () {
      this.$confirm(
        '清空后所有数据将丢失',
        '清空画布',
        { type: 'warning' }
      ).then(() => {
        this.selectedWidgetIndex = -1
        const { width, height } = this.node
        this.initCanvas({ width, height })
      })
    },
    widgetOnDragStart (evt) {
      evt.dataTransfer.setData('type', evt.target.dataset.type)
      this.dragging = true
    },
    widgetOnDragEnd () {
      this.dragging = false
    },
    widgetOnDrop (evt) {
      const type = evt.dataTransfer.getData('type')
      if (type) {
        const node = normalize({ type })
        if (this.widgets.length || !isMediaWidget(type)) {
          let { offsetX: left, offsetY: top } = evt
          top -= node.height / 2
          if (top < 0) {
            top = 0
          }
          left -= node.width / 2
          if (left < 0) {
            left = 0
          }
          node.top = top
          node.left = left
        } else {
          node.top = 0
          node.left = 0
          node.width = this.node.width
          node.height = this.node.height
        }
        this.node.widgets.push(node)
        this.$nextTick(() => {
          this.selectedWidgetIndex = this.widgets.length - 1
        })
      }
    },
    onWidgetFocus (index) {
      this.selectedWidgetIndex = index
      this.visibleContentMenu = false
    },
    onWidgetWillMove () {
      this.grid = true
    },
    onWidgetBlur () {
      this.grid = false
    },
    onWidgetMenu (evt, index) {
      this.selectedWidgetIndex = index
      this.onRightClick(evt)
    },
    onWidgetDblclick () {
      const attr = this.dynamicOptions[0]?.list.find(({ type }) => type === 'data')
      if (attr) {
        this.onEditData(attr)
      }
    },
    onLayerClick (evt, index) {
      this.selectedWidgetIndex = this.widgets.length - 1 - index
      this.onRightClick(evt)
    },
    onRootClick () {
      this.selectedWidgetIndex = -1
    },
    onRightClick (evt) {
      const { button, clientY: top, clientX: left } = evt
      if (button !== 2) {
        return
      }
      evt.stopPropagation()
      if (top || left) {
        this.styleObj = {
          display: 'block',
          top: `${top}px`,
          left: `${left}px`
        }
      }
      this.visibleContentMenu = true
    },
    // 删除
    deleteLayer () {
      this.widgets.splice(this.selectedWidgetIndex, 1)
      this.$nextTick(() => {
        this.selectedWidgetIndex = -1
      })
    },
    // 复制
    copyLayer () {
      this.widgets.splice(this.selectedWidgetIndex + 1, 0, copy(this.widget))
      this.$message({
        type: 'success',
        message: '复制成功，已为您选择复制后的组件'
      })
      this.$nextTick(() => {
        this.selectedWidgetIndex += 1
      })
    },
    // 置顶
    topLayer () {
      if (this.selectedWidgetIndex < this.widgets.length - 1) {
        this.widgets.push(this.widgets.splice(
          this.selectedWidgetIndex,
          1
        )[0])
        this.$nextTick(() => {
          this.selectedWidgetIndex = this.widgets.length - 1
        })
      }
    },
    // 置底
    lowLayer () {
      if (this.selectedWidgetIndex > 0) {
        this.widgets.unshift(this.widgets.splice(
          this.selectedWidgetIndex,
          1
        )[0])
        this.$nextTick(() => {
          this.selectedWidgetIndex = 0
        })
      }
    },
    // 上移一层
    upLayer () {
      if (this.selectedWidgetIndex < this.widgets.length - 1) {
        this.widgets[this.selectedWidgetIndex] = this.widgets.splice(
          this.selectedWidgetIndex + 1,
          1,
          this.widgets[this.selectedWidgetIndex]
        )[0]
        this.$nextTick(() => {
          this.selectedWidgetIndex += 1
        })
      }
    },
    // 下移一层
    downLayer () {
      if (this.selectedWidgetIndex > 0) {
        this.widgets[this.selectedWidgetIndex] = this.widgets.splice(
          this.selectedWidgetIndex - 1,
          1,
          this.widgets[this.selectedWidgetIndex]
        )[0]
        this.$nextTick(() => {
          this.selectedWidgetIndex -= 1
        })
      }
    },
    changeAttr (value) {
      const { key, options } = this.widgetAttr
      this.widget[key] = value
      if (options) {
        const { callback } = options
        callback && callback.call(this.widget, this.node)
      }
    },
    onEditData (attr) {
      this.widgetAttr = attr
      switch (attr.type) {
        case 'data':
          if (attr.options && attr.options.solo) {
            this.showServerAssets = true
          } else {
            this.sources = this.widget[attr.key].map(this.transformDataToSource)
            this.showAssets = true
          }
          break
        case 'rich':
          this.richHtml = this.widget[attr.key]
          this.$refs.rich.show()
          break
        default:
          break
      }
    },
    onCloseAssetsDialog () {
      this.widgetAttr = null
      this.showAssets = false
      this.showServerAssets = false
    },
    transformAsset ({ type, originalName, keyName, thumbnail, duration, size, md5, childrenData }) {
      const asset = {
        selected: false,
        type,
        name: originalName,
        keyName,
        size,
        md5,
        thumbnail
      }
      switch (type) {
        case AssetType.IMAGE:
          asset.duration = this.widget.interval
          asset.thumb = getThumbnailUrl(thumbnail)
          break
        case AssetType.PPT:
        case AssetType.PDF:
        case AssetType.DOC:
          asset.thumb = getThumbnailUrl(childrenData[0].keyName)
          asset.files = (childrenData || []).map(({ type, keyName, size, md5 }, index) => {
            return {
              type, originalName, keyName, size, md5,
              selected: false,
              name: `第${index + 1}页`,
              url: keyName,
              thumb: getThumbnailUrl(keyName)
            }
          })
          break
        default:
          asset.duration = Number(duration) || 0
          if (thumbnail) {
            asset.thumb = getThumbnailUrl(thumbnail)
          } else {
            asset.icon = `${type === AssetType.VIDEO ? 'video' : 'audio'}-bg`
          }
          break
      }
      return this.addTag(asset)
    },
    transformToData ({ type, name, keyName, thumbnail, duration, size, md5 }) {
      const source = {
        type,
        name,
        keyName,
        size,
        md5,
        duration
      }
      if (type !== AssetType.IMAGE && thumbnail) {
        source.thumbnail = thumbnail
      }
      return source
    },
    transformDataToSource (data) {
      const source = { ...data }
      if (data.thumbnail) {
        source.thumb = getThumbnailUrl(data.thumbnail)
      } else if (source.type === AssetType.IMAGE) {
        source.thumb = getThumbnailUrl(data.keyName)
      } else {
        switch (source.type) {
          case AssetType.VIDEO:
            source.icon = 'video-bg'
            break
          case AssetType.AUDIO:
            source.icon = 'audio-bg'
            break
          default:
            break
        }
      }
      return this.addTag(source)
    },
    addTag (data) {
      if (this.needTypeTag) {
        data.tag = this.getAssetType(data.type)
      }
      return data
    },
    onChoosenAsset (asset) {
      this.changeAttr([this.transformToData(asset)])
      this.onCloseAssetsDialog()
    },
    onAddAssets () {
      this.fileDir = null
      this.showServerAssetsMulti = true
      this.$refs.assetsDialog.show()
    },
    onToggleGrid (asset) {
      asset.selected = !asset.selected
    },
    transformFileToAsset ({ type, originalName, name, keyName, thumb, size, md5 }) {
      const asset = {
        type, size, md5, keyName, thumb,
        name: `${originalName}${name}`,
        duration: this.widget.interval
      }
      this.addTag(asset)
      return asset
    },
    onChoosenAssets (done) {
      const assets = this.fileDir
        ? this.fileDir.files.filter(({ selected }) => selected).map(this.transformFileToAsset)
        : this.$refs.gridTable.getData().filter(({ selected }) => selected).map(asset => asset.files?.map(this.transformFileToAsset) || asset)
      if (assets.length) {
        this.sources = this.sources.concat(...assets)
      }
      this.showServerAssetsMulti = false
      done()
    },
    onChooseDir (fileDir) {
      fileDir.files.forEach(file => {
        file.selected = false
      })
      this.fileDir = fileDir
    },
    onDirBack () {
      this.fileDir = null
    },
    onSaveAssets () {
      this.changeAttr(this.sources.map(this.transformToData))
      this.onCloseAssetsDialog()
    },
    onView ({ type, keyName, files }) {
      this.onViewAsset({ type: type || this.widgetAttr.options.type, url: keyName, files })
    },
    onViewAsset (asset) {
      this.$muted = this.muted
      if (!this.muted) {
        this.muted = true
      }
      this.$refs.previewDialog.show(asset)
    },
    onClosePreview () {
      this.muted = this.$muted
    },
    onDelAsset (index) {
      this.sources.splice(index, 1)
    },
    onSourceDragStart () {
      this.dragging = true
    },
    onSourceDragEnd () {
      this.dragging = false
    },
    scaleDown () {
      this.scale = Math.max(this.scale - 10, this.minScale)
    },
    scaleUp () {
      this.scale = Math.min(this.scale + 10, this.maxScale)
    },
    onSave () {
      this.check().then(status => {
        this.selectedWidgetIndex = -1
        this.loading = true
        setTimeout(() => {
          this._save(status).finally(() => {
            this.loading = false
          })
        }, 100)
      })
    },
    check () {
      const { state, message } = fix(this.node)
      if (message) {
        return this.$confirm(
          message,
          '继续保存',
          { type: 'warning' }
        ).then(() => state)
      }
      return Promise.resolve(state)
    },
    async _save (status) {
      try {
        const base64 = await this.snap()
        const result = await updateProgram({
          id: this.program.id,
          duration: getDuration(this.node),
          itemJsonStr: JSON.stringify(toJSON(this.node)),
          keyNameList: getUsedAssets(this.node),
          base64,
          status
        })
        if (result) {
          if (window.opener) {
            window.opener.postMessage({
              type: 'PROGRAM_UPDATED',
              id: this.program.id
            })
            this.$message({
              type: 'success',
              dangerouslyUseHTMLString: true,
              duration: 5000,
              message: `保存成功, <span style="text-decoration: underline; cursor: pointer;" onclick="window.close()">返回节目列表</span>？`
            })
          } else {
            this.$message({
              type: 'success',
              message: '保存成功'
            })
          }
        } else {
          this.$message({
            type: 'warning',
            message: '保存失败'
          })
        }
      } catch (e) {
        console.warn(e)
        this.$message({
          type: 'warning',
          message: '保存失败'
        })
      }
    },
    snap () {
      this.snapping = true
      return domToImage.toJpeg(this.$refs.canvas, {
        filter (node) {
          const { tagName } = node
          if (tagName === 'CANVAS') {
            return /^data:.+;base64,.+/.test(node.toDataURL())
          }
          return tagName !== 'VIDEO' && tagName !== 'IFRAME'
        },
        width: this.node.width * this.scale / 100 | 0,
        height: this.node.height * this.scale / 100 | 0,
        quality: 0.1
      }).finally(() => {
        this.snapping = false
      })
    }
  }
}
</script>

<style lang="scss" scoped>
$theme: #181b23;
$dark: #121418;
$light: #1f232e;
$hover: lighten($theme, 10%);
$active: darken($theme, 5%);
$border: #242835;

.c-designer {
  height: 100%;
  min-width: 1080px;
  min-height: 600px;
  background-color: $theme;
  overflow: hidden;

  &__header {
    height: 53px;
    padding: 0 24px 0 10px;
    color: $info;
    font-size: 12px;
    line-height: 1;
    border-bottom: 1px solid $border;
  }

  &__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: $hover;
    }

    &:active {
      background-color: $active;
    }
  }

  &__name {
    max-width: 400px;
    padding: 0 $spacing 0 6px;
    font-size: 18px;
  }

  &__side {
    flex: none;
    min-height: 0;

    &.left {
      width: 144px;
    }

    &.right {
      width: 196px;
    }
  }

  &__main {
    flex: 1 0 0;
    display: flex;
    flex-direction: column;
    position: relative;
    min-width: 0;
  }

  &__content {
    flex: 1 0 0;
    position: relative;
    padding: 16px;
    overflow: auto;
    background: $dark url("~@/assets/dot.png") 0 0 / 16px 16px repeat;
  }

  &__tool {
    flex: 0 0 60px;
    color: $info;
    border-left: 1px solid $border;
    border-right: 1px solid $border;
  }

  &__wrapper {
    min-width: 100%;
    min-height: 100%;
  }

  &__canvas {
    position: relative;
    background-color: #000;
    overflow: visible;
  }

  &__background {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: currentColor;
    z-index: -1;
  }

  &__grid {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-size: 30px 30px, 30px 30px;
    background-image: linear-gradient(hsla(0, 0%, 100%, 0.1) 1px, transparent 0),
      linear-gradient(90deg, hsla(0, 0%, 100%, 0.1) 1px, transparent 0);
  }
}

.c-side {
  display: flex;
  flex-direction: column;

  &__tool {
    flex: none;
    display: flex;
    border-bottom: 1px solid $border;
    background-color: $light;
  }

  &__item {
    flex: 1 0 0;
    color: #9fa6b8;
    font-size: 16px;
    text-align: center;
    line-height: 60px;
    user-select: none;

    &.active {
      color: $blue;
      background-color: $theme;
    }
  }

  &__content {
    padding: 10px 16px;

    &.mini {
      padding: 10px;
    }
  }

  &__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;
      }
    }
  }
}

.o-widget-cfg {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  user-select: none;
  cursor: pointer;

  & + & {
    margin-left: 32px;
  }

  &.active {
    background-color: #31455d;
  }

  &__icon {
    flex: none;
    display: inline-flex;
    justify-content: center;
    align-items: center;
    width: 32px;
    height: 32px;
    color: #fff;
    font-size: 22px;
    border-radius: $radius;
    background-color: $blue;
  }

  &__text {
    margin-top: 6px;
    color: #9fa5b8;
    font-size: 12px;
    line-height: 1;
  }
}

.o-layer {
  color: $gray--dark;
  user-select: none;
  border-radius: $radius;
  background-color: #252d42;
  overflow: hidden;
  cursor: pointer;

  & + & {
    margin-top: 10px;
  }

  &.active {
    color: #fff;
    outline: 2px solid $blue;
    background-color: $blue;
  }
}

.l-grid--info.dragging .o-card .o-card__icon {
  display: none;
}

.o-card {
  &:hover &__icon {
    display: inline-block;
  }

  &__icon {
    display: none;
    position: absolute;
    top: 0;
    right: 0;
    padding: 6px;
    font-size: 16px;
    border-radius: 50%;
    background-color: rgba(#000, 0.4);
    z-index: 9;
  }

  &__tag {
    position: absolute;
    top: 0;
    left: 0;
    padding: 4px 6px;
    font-size: 12px;
    line-height: 1;
    border-bottom-right-radius: $radius;
    background-color: rgba(#000, 0.4);
  }

  &__checkbox {
    position: absolute;
    top: 10px;
    left: 10px;
    pointer-events: none;
    z-index: 9;
  }

  &__play {
    position: absolute;
    top: 50%;
    left: 50%;
    padding: 2px;
    font-size: 24px;
    border-radius: 50%;
    background-color: rgba(#000, 0.4);
    transform: translate(-50%, -50%);
  }

  &__grid {
    position: absolute;
    top: 10px;
    right: 10px;
    font-size: 18px;
    z-index: 9;
  }
}

.o-scale-slider {
  justify-content: flex-start;
  min-width: 48px;
  width: auto;
  padding: 0 $spacing;
  font-size: 20px;

  &__wrapper {
    width: 184px;
    transition: width 0.1s;
    overflow: hidden;
  }

  &__slider {
    width: 100px;
    margin: 0 16px;
  }

  .el-icon-zoom-in + .svg-icon {
    margin-left: 16px;
  }

  .svg-icon {
    font-size: 16px;
  }
}
</style>
