添加放映部分

This commit is contained in:
pipipi-pikachu 2020-12-25 00:29:04 +08:00
parent c58a6a4d24
commit 636c27c37e
12 changed files with 321 additions and 28 deletions

View File

@ -1,26 +1,34 @@
<template>
<Editor />
<Editor v-if="!screening" />
<Screen v-else />
</template>
<script lang="ts">
import { defineComponent, onMounted } from 'vue'
import { computed, defineComponent, onMounted } from 'vue'
import { useStore } from 'vuex'
import { MutationTypes, ActionTypes, State } from '@/store'
import Editor from './views/Editor/index.vue'
import Screen from './views/Screen/index.vue'
export default defineComponent({
name: 'app',
components: {
Editor,
Screen,
},
setup() {
const store = useStore<State>()
const screening = computed(() => store.state.screening)
onMounted(() => {
store.commit(MutationTypes.SET_AVAILABLE_FONTS)
store.dispatch(ActionTypes.INIT_SNAPSHOT_DATABASE)
})
return {
screening,
}
},
})
</script>

View File

@ -6,6 +6,7 @@ export enum KEYS {
A = 'A',
G = 'G',
L = 'L',
F = 'F',
DELETE = 'DELETE',
UP = 'ARROWUP',
DOWN = 'ARROWDOWN',

13
src/global.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
interface HTMLElement {
webkitRequestFullScreen(options?: FullscreenOptions): Promise<void>;
mozRequestFullScreen(options?: FullscreenOptions): Promise<void>;
}
interface Document {
mozFullScreen: boolean;
webkitIsFullScreen: boolean;
webkitFullScreen: boolean;
mozCancelFullScreen(): Promise<void>;
webkitCancelFullScreen(): Promise<void>;
}

View File

@ -16,7 +16,6 @@ export const slides: Slide[] = [
width: 320,
height: 104,
rotate: 0,
fill: 'rgba(220, 220, 220, 0.8)',
shadow: {
h: 1,
v: 1,
@ -56,15 +55,6 @@ export const slides: Slide[] = [
width: 150,
height: 150,
rotate: 0,
outline: {
width: 6,
style: 'solid',
color: '#333'
},
clip: {
range: [[0, 0], [100, 100]],
shape: 'roundRect'
},
fixedRatio: true,
lock: false,
src: 'https://img.lessonplan.cn/IMG/Show/ppt/3ab74e91-c34f-499d-9711-166e423d4dd6/62d9adb3-e7a6-4dc4-a352-095cffb49f08/b1be1a2f-f893-47d3-a8a3-eac7d04d395f/1596159381259v2-b2c69096d25ae16bf6ca09e30add3e65_hd.jpg',

View File

@ -27,6 +27,9 @@ export enum MutationTypes {
// keyboard
SET_CTRL_KEY_STATE = 'setCtrlKeyState',
SET_SHIFT_KEY_STATE = 'setShiftKeyState',
// screen
SET_SCREENING = 'SET_SCREENING'
}
export enum ActionTypes {

View File

@ -26,6 +26,7 @@ export interface State {
snapshotLength: number;
ctrlKeyState: boolean;
shiftKeyState: boolean;
screening: boolean;
}
const state: State = {
@ -44,6 +45,7 @@ const state: State = {
snapshotLength: 0,
ctrlKeyState: false,
shiftKeyState: false,
screening: false,
}
export default createStore({

View File

@ -120,4 +120,10 @@ export const mutations: MutationTree<State> = {
[MutationTypes.SET_SHIFT_KEY_STATE](state, isActive: boolean) {
state.shiftKeyState = isActive
},
// screen
[MutationTypes.SET_SCREENING](state, screening) {
state.screening = screening
},
}

View File

@ -1,8 +1,21 @@
// 进入全屏
export const enterFullscreen = document.documentElement.requestFullscreen
export const enterFullscreen = () => {
const docElm = document.documentElement
if(docElm.requestFullscreen) docElm.requestFullscreen()
else if(docElm.mozRequestFullScreen) docElm.mozRequestFullScreen()
else if(docElm.webkitRequestFullScreen) docElm.webkitRequestFullScreen()
}
// 退出全屏
export const exitFullscreen = document.exitFullscreen
export const exitFullscreen = () => {
if(document.exitFullscreen) document.exitFullscreen()
else if(document.mozCancelFullScreen) document.mozCancelFullScreen()
else if(document.webkitCancelFullScreen) document.webkitCancelFullScreen()
}
// 判断是否全屏
export const isFullscreen = () => document.fullscreenEnabled
export const isFullscreen = () => (
document.mozFullScreen ||
document.webkitIsFullScreen ||
document.webkitFullScreen
)

View File

@ -2,6 +2,7 @@ import { computed, onMounted, onUnmounted } from 'vue'
import { useStore } from 'vuex'
import { State, MutationTypes } from '@/store'
import { KEYS } from '@/configs/hotkey'
import { enterFullscreen } from '@/utils/fullscreen'
import useSlideHandler from '@/hooks/useSlideHandler'
import useLockElement from '@/hooks/useLockElement'
@ -89,10 +90,20 @@ export default () => {
createSlide()
}
const enterScreening = () => {
enterFullscreen()
store.commit(MutationTypes.SET_SCREENING, true)
}
const keydownListener = (e: KeyboardEvent) => {
const { ctrlKey, shiftKey } = e
const key = e.key.toUpperCase()
if(ctrlKey && key === KEYS.F) {
e.preventDefault()
enterScreening()
}
if(ctrlKey && !ctrlKeyActive.value) store.commit(MutationTypes.SET_CTRL_KEY_STATE, true)
if(shiftKey && !shiftKeyActive.value) store.commit(MutationTypes.SET_SHIFT_KEY_STATE, true)

View File

@ -0,0 +1,68 @@
<template>
<div
class="screen-slide"
:style="{
width: VIEWPORT_SIZE + 'px',
height: VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO + 'px',
transform: `scale(${scale})`,
}"
>
<div class="background" :style="{ ...backgroundStyle }"></div>
<BaseElement
v-for="(element, index) in slide.elements"
:key="element.id"
:elementInfo="element"
:elementIndex="index + 1"
/>
</div>
</template>
<script lang="ts">
import { computed, PropType, defineComponent } from 'vue'
import { Slide } from '@/types/slides'
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
import BaseElement from '@/views/_common/_element/BaseElement.vue'
export default defineComponent({
name: 'screen-slide',
components: {
BaseElement,
},
props: {
slide: {
type: Object as PropType<Slide>,
required: true,
},
scale: {
type: Number,
required: true,
},
},
setup(props) {
const background = computed(() => props.slide.background)
const { backgroundStyle } = useSlideBackgroundStyle(background)
return {
backgroundStyle,
VIEWPORT_SIZE,
VIEWPORT_ASPECT_RATIO,
}
},
})
</script>
<style lang="scss" scoped>
.screen-slide {
position: absolute;
top: 0;
left: 0;
transform-origin: 0 0;
}
.background {
background-position: center;
background-size: cover;
position: absolute;
}
</style>

191
src/views/Screen/index.vue Normal file
View File

@ -0,0 +1,191 @@
<template>
<div class="hamster-ppt-screen">
<div
class="slide-list"
@mousewheel="$event => mousewheelListener($event)"
v-contextmenu="contextmenus"
>
<div
:class="[
'slide-item',
{
'show': index === slideIndex,
'prev': index < slideIndex,
'next': index > slideIndex,
}
]"
v-for="(slide, index) in slides"
:key="slide.id"
>
<div
class="slide-content"
:style="{
width: slideWidth + 'px',
height: slideHeight + 'px',
}"
>
<ScreenSlide :scale="scale" :slide="slide" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, onUnmounted, ref } from 'vue'
import { useStore } from 'vuex'
import throttle from 'lodash/throttle'
import { MutationTypes, State } from '@/store'
import { exitFullscreen, isFullscreen } from '@/utils/fullscreen'
import { VIEWPORT_ASPECT_RATIO, VIEWPORT_SIZE } from '@/configs/canvas'
import { KEYS } from '@/configs/hotkey'
import { ContextmenuItem } from '@/components/Contextmenu/types'
import ScreenSlide from './ScreenSlide.vue'
export default defineComponent({
name: 'screen',
components: {
ScreenSlide,
},
setup() {
const store = useStore<State>()
const slides = computed(() => store.state.slides)
const slideIndex = computed(() => store.state.slideIndex)
const slideWidth = ref(0)
const slideHeight = ref(0)
const scale = computed(() => slideWidth.value / VIEWPORT_SIZE)
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 windowResizeListener = () => {
setSlideContentSize()
if(!isFullscreen()) store.commit(MutationTypes.SET_SCREENING, false)
}
const turnPrevSlide = () => {
if(slideIndex.value <= 0) return
store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value - 1)
}
const turnNextSlide = () => {
if(slideIndex.value >= slides.value.length - 1) return
store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value + 1)
}
const keydownListener = (e: KeyboardEvent) => {
const key = e.key.toUpperCase()
if(key === KEYS.UP || key === KEYS.LEFT) turnPrevSlide()
else if(key === KEYS.DOWN || key === KEYS.RIGHT) turnNextSlide()
}
const mousewheelListener = throttle(function(e: WheelEvent) {
if(e.deltaY > 0) turnNextSlide()
else if(e.deltaY < 0) turnPrevSlide()
}, 500, { leading: true, trailing: false })
onMounted(() => {
window.addEventListener('resize', windowResizeListener)
document.addEventListener('keydown', keydownListener)
})
onUnmounted(() => {
window.removeEventListener('resize', windowResizeListener)
document.removeEventListener('keydown', keydownListener)
})
const contextmenus = (): ContextmenuItem[] => {
return [
{
text: '上一页',
disable: slideIndex.value <= 0,
handler: () => turnPrevSlide(),
},
{
text: '下一页',
disable: slideIndex.value >= slides.value.length - 1,
handler: () => turnNextSlide(),
},
{ divider: true },
{
text: '结束放映',
subText: 'ESC',
handler: exitFullscreen,
},
]
}
return {
slides,
slideIndex,
slideWidth,
slideHeight,
scale,
mousewheelListener,
contextmenus,
}
},
})
</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%;
transition-property: transform;
transition-duration: .4s;
&.show {
z-index: 2;
}
&.prev {
transform: translateX(-100%);
}
&.next {
transform: translateX(100%);
}
}
.slide-content {
background-color: #fff;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@ -1,13 +0,0 @@
<template>
<div class="hamster-ppt-slide-show">
slide-show
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'slide-show',
})
</script>