| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- <template>
- <div class="hamster-ppt-screen">
- <div
- class="slide-list"
- @mousewheel="$event => mousewheelListener($event)"
- v-contextmenu="contextmenus"
- >
- <div
- :class="[
- 'slide-item',
- `turning-mode-${slide.turningMode || 'slideY'}`,
- {
- 'current': index === slideIndex,
- 'before': index < slideIndex,
- 'after': index > slideIndex,
- 'hide': (index === slideIndex - 1 || index === slideIndex + 1) && slide.turningMode !== currentSlide.turningMode,
- }
- ]"
- v-for="(slide, index) in slides"
- :key="slide.id"
- >
- <div
- class="slide-content"
- :style="{
- width: slideWidth + 'px',
- height: slideHeight + 'px',
- }"
- >
- <ScreenSlide
- :slide="slide"
- :scale="scale"
- :animationIndex="animationIndex"
- />
- </div>
- </div>
- </div>
- <Modal
- v-model:visible="slideThumbnailModelVisible"
- :footer="null"
- centered
- :width="1020"
- :bodyStyle="{ padding: '50px 20px 20px 20px' }"
- >
- <SlideThumbnails :turnSlideToIndex="turnSlideToIndex" />
- </Modal>
- <div class="tools">
- <IconLeftC class="tool-btn" @click="execPrev()" />
- <IconRightC class="tool-btn" @click="execNext()" />
- <IconSearch class="tool-btn" @click="slideThumbnailModelVisible = true" />
- <Popover trigger="click" v-model:visible="writingBoardToolVisible">
- <template #content>
- <WritingBoardTool @close="writingBoardToolVisible = false" />
- </template>
- <IconWrite class="tool-btn" />
- </Popover>
- </div>
- </div>
- </template>
- <script lang="ts">
- import { computed, defineComponent, onMounted, onUnmounted, provide, ref } from 'vue'
- import { useStore } from 'vuex'
- import throttle from 'lodash/throttle'
- import { MutationTypes, State } from '@/store'
- import { Slide } from '@/types/slides'
- import { VIEWPORT_ASPECT_RATIO, VIEWPORT_SIZE } from '@/configs/canvas'
- import { KEYS } from '@/configs/hotkey'
- import { ContextmenuItem } from '@/components/Contextmenu/types'
- import { isFullscreen } from '@/utils/fullscreen'
- import useScreening from '@/hooks/useScreening'
- import ScreenSlide from './ScreenSlide.vue'
- import SlideThumbnails from './SlideThumbnails.vue'
- import WritingBoardTool from './WritingBoardTool.vue'
- export default defineComponent({
- name: 'screen',
- components: {
- ScreenSlide,
- SlideThumbnails,
- WritingBoardTool,
- },
- setup() {
- const store = useStore<State>()
- const slides = computed(() => store.state.slides)
- const slideIndex = computed(() => store.state.slideIndex)
- const currentSlide = computed<Slide>(() => store.getters.currentSlide)
- const slideWidth = ref(0)
- const slideHeight = ref(0)
- const scale = computed(() => slideWidth.value / VIEWPORT_SIZE)
- const slideThumbnailModelVisible = ref(false)
- const writingBoardToolVisible = ref(false)
- const setSlideContentSize = () => {
- const winWidth = document.body.clientWidth
- const winHeight = document.body.clientHeight
- let width, height
- if(winHeight / winWidth === VIEWPORT_ASPECT_RATIO) {
- width = winWidth
- height = winHeight
- }
- else if(winHeight / winWidth > VIEWPORT_ASPECT_RATIO) {
- width = winWidth
- height = winWidth * VIEWPORT_ASPECT_RATIO
- }
- else {
- width = winHeight / VIEWPORT_ASPECT_RATIO
- height = winHeight
- }
- slideWidth.value = width
- slideHeight.value = height
- }
- const { exitScreening } = useScreening()
- const windowResizeListener = () => {
- setSlideContentSize()
- if(!isFullscreen()) exitScreening()
- }
- const animationIndex = ref(0)
- const animations = computed(() => currentSlide.value.animations || [])
- const runAnimation = () => {
- const prefix = 'animate__'
- const animation = animations.value[animationIndex.value]
- animationIndex.value += 1
- const elRef = document.querySelector(`#screen-element-${animation.elId} [class^=base-element-]`)
- if(elRef) {
- const animationName = `${prefix}${animation.type}`
- elRef.classList.add(`${prefix}animated`, animationName)
- const handleAnimationEnd = () => {
- elRef.classList.remove(`${prefix}animated`, animationName)
- }
- elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
- }
- }
- const execPrev = () => {
- if(animations.value.length && animationIndex.value > 0) {
- animationIndex.value -= 1
- }
- else if(slideIndex.value > 0) {
- store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value - 1)
- const lastIndex = animations.value ? animations.value.length : 0
- animationIndex.value = lastIndex
- }
- }
- const execNext = () => {
- if(animations.value.length && animationIndex.value < animations.value.length) {
- runAnimation()
- }
- else if(slideIndex.value < slides.value.length - 1) {
- store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value + 1)
- animationIndex.value = 0
- }
- }
- const keydownListener = (e: KeyboardEvent) => {
- const key = e.key.toUpperCase()
- if(key === KEYS.UP || key === KEYS.LEFT) execPrev()
- else if(key === KEYS.DOWN || key === KEYS.RIGHT) execNext()
- }
- const mousewheelListener = throttle(function(e: WheelEvent) {
- if(e.deltaY < 0) execPrev()
- else if(e.deltaY > 0) execNext()
- }, 500, { leading: true, trailing: false })
- onMounted(() => {
- window.addEventListener('resize', windowResizeListener)
- document.addEventListener('keydown', keydownListener)
- })
- onUnmounted(() => {
- window.removeEventListener('resize', windowResizeListener)
- document.removeEventListener('keydown', keydownListener)
- })
- const turnPrevSlide = () => {
- store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value - 1)
- animationIndex.value = 0
- }
- const turnNextSlide = () => {
- store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value + 1)
- animationIndex.value = 0
- }
- const turnSlideToIndex = (index: number) => {
- slideThumbnailModelVisible.value = false
- store.commit(MutationTypes.UPDATE_SLIDE_INDEX, index)
- animationIndex.value = 0
- }
- const contextmenus = (): ContextmenuItem[] => {
- return [
- {
- text: '上一页',
- disable: slideIndex.value <= 0,
- handler: () => turnPrevSlide(),
- },
- {
- text: '下一页',
- disable: slideIndex.value >= slides.value.length - 1,
- handler: () => turnNextSlide(),
- },
- {
- text: '结束放映',
- subText: 'ESC',
- handler: exitScreening,
- },
- ]
- }
-
- provide('slideScale', scale)
- return {
- slides,
- slideIndex,
- currentSlide,
- slideWidth,
- slideHeight,
- scale,
- mousewheelListener,
- animationIndex,
- contextmenus,
- execPrev,
- execNext,
- slideThumbnailModelVisible,
- turnSlideToIndex,
- writingBoardToolVisible,
- }
- },
- })
- </script>
- <style lang="scss" scoped>
- .hamster-ppt-screen {
- width: 100%;
- height: 100%;
- position: relative;
- background-color: #111;
- }
- .slide-list {
- background: #1d1d1d;
- position: relative;
- width: 100%;
- height: 100%;
- }
- .slide-item {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- &.current {
- z-index: 2;
- }
- &.hide {
- opacity: 0;
- }
- &.turning-mode-no {
- &.before {
- transform: translateY(-100%);
- }
- &.after {
- transform: translateY(100%);
- }
- }
- &.turning-mode-fade {
- transition: opacity .75s;
- &.before {
- pointer-events: none;
- opacity: 0;
- }
- &.after {
- pointer-events: none;
- opacity: 0;
- }
- }
- &.turning-mode-slideX {
- transition: transform .35s;
- &.before {
- transform: translateX(-100%);
- }
- &.after {
- transform: translateX(100%);
- }
- }
- &.turning-mode-slideY {
- transition: transform .35s;
- &.before {
- transform: translateY(-100%);
- }
- &.after {
- transform: translateY(100%);
- }
- }
- }
- .slide-content {
- background-color: #fff;
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .tools {
- position: fixed;
- bottom: 8px;
- left: 8px;
- font-size: 25px;
- color: #666;
- z-index: 10;
- cursor: pointer;
- }
- .tool-btn {
- opacity: .35;
- &:hover {
- opacity: .7;
- }
- & + .tool-btn {
- margin-left: 8px;
- }
- }
- </style>
|