This commit is contained in:
pipipi-pikachu 2020-12-22 22:37:26 +08:00
parent 845cecd553
commit 8ff1500b53
19 changed files with 149 additions and 103 deletions

View File

@ -18,7 +18,6 @@
"mitt": "^2.1.0",
"store2": "^2.12.0",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"vuedraggable": "^4.0.1",
"vuex": "^4.0.0-0"
},
@ -31,7 +30,6 @@
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-unit-jest": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",

View File

@ -1,5 +1,5 @@
<template>
<router-view/>
<Editor />
</template>
<script lang="ts">
@ -7,8 +7,13 @@ import { defineComponent, onMounted } from 'vue'
import { useStore } from 'vuex'
import { MutationTypes, ActionTypes, State } from '@/store'
import Editor from './views/Editor/index.vue'
export default defineComponent({
name: 'app',
components: {
Editor,
},
setup() {
const store = useStore<State>()

View File

@ -0,0 +1,23 @@
import { computed } from 'vue'
import { useStore } from 'vuex'
import { MutationTypes, State } from '@/store'
export default () => {
const store = useStore<State>()
const canvasPercentage = computed(() => store.state.canvasPercentage)
const scaleCanvas = (command: '+' | '-') => {
let percentage = canvasPercentage.value
const step = 5
const max = 120
const min = 60
if(command === '+' && percentage <= max) percentage += step
if(command === '-' && percentage >= min) percentage -= step
store.commit(MutationTypes.SET_CANVAS_PERCENTAGE, percentage)
}
return {
scaleCanvas,
}
}

View File

@ -1,6 +1,5 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/styles/global.scss'
@ -14,5 +13,4 @@ app.component('IconFont', IconFont)
app.use(contextmenu)
app.use(clickOutside)
app.use(store)
app.use(router)
app.mount('#app')

View File

@ -1,28 +0,0 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Editor from '@/views/Editor/index.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Editor',
component: Editor,
},
{
path: '/player',
name: 'Player',
component: () => import(/* webpackChunkName: "Player" */ '@/views/Player/index.vue'),
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
router.beforeEach((to, from) => {
if(to.name === 'Player' && from.name !== 'Editor') {
return router.push({ path: '/' })
}
})
export default router

View File

@ -3,10 +3,12 @@ export enum MutationTypes {
// editor
SET_ACTIVE_ELEMENT_ID_LIST = 'setActiveElementIdList',
SET_HANDLE_ELEMENT_ID = 'setHandleElementId',
SET_EDITOR_AREA_SHOW_SCALE = 'setEditorAreaShowScale',
SET_CANVAS_PERCENTAGE = 'setCanvasPercentage',
SET_CANVAS_SCALE = 'setCanvasScale',
SET_THUMBNAILS_FOCUS = 'setThumbnailsFocus',
SET_EDITORAREA_FOCUS = 'setEditorAreaFocus',
SET_DISABLE_HOTKEYS_STATE = 'setDisableHotkeysState',
SET_GRID_LINES_STATE = 'setGridLinesState',
SET_AVAILABLE_FONTS = 'setAvailableFonts',
// slides

View File

@ -13,10 +13,12 @@ export { MutationTypes, ActionTypes }
export interface State {
activeElementIdList: string[];
handleElementId: string;
editorAreaShowScale: number;
canvasPercentage: number;
canvasScale: number;
thumbnailsFocus: boolean;
editorAreaFocus: boolean;
disableHotkeys: boolean;
showGridLines: boolean;
availableFonts: FontName[];
slides: Slide[];
slideIndex: number;
@ -29,10 +31,12 @@ export interface State {
const state: State = {
activeElementIdList: [],
handleElementId: '',
editorAreaShowScale: 90,
canvasPercentage: 90,
canvasScale: 1,
thumbnailsFocus: false,
editorAreaFocus: false,
disableHotkeys: false,
showGridLines: false,
availableFonts: [],
slides: slides,
slideIndex: 0,

View File

@ -5,11 +5,6 @@ import { Slide, PPTElement } from '@/types/slides'
import { FONT_NAMES } from '@/configs/fontName'
import { isSupportFontFamily } from '@/utils/fontFamily'
interface AddSlideData {
index?: number;
slide: Slide | Slide[];
}
interface UpdateElementData {
elId: string | string[];
props: Partial<PPTElement>;
@ -30,8 +25,12 @@ export const mutations: MutationTree<State> = {
state.handleElementId = handleElementId
},
[MutationTypes.SET_EDITOR_AREA_SHOW_SCALE](state, scale: number) {
state.editorAreaShowScale = scale
[MutationTypes.SET_CANVAS_PERCENTAGE](state, percentage: number) {
state.canvasPercentage = percentage
},
[MutationTypes.SET_CANVAS_SCALE](state, scale: number) {
state.canvasScale = scale
},
[MutationTypes.SET_THUMBNAILS_FOCUS](state, isFocus: boolean) {
@ -46,6 +45,10 @@ export const mutations: MutationTree<State> = {
state.disableHotkeys = disable
},
[MutationTypes.SET_GRID_LINES_STATE](state, show: boolean) {
state.showGridLines = show
},
[MutationTypes.SET_AVAILABLE_FONTS](state) {
state.availableFonts = FONT_NAMES.filter(font => isSupportFontFamily(font.en))
},

View File

@ -39,10 +39,6 @@ export default defineComponent({
BorderLine,
},
props: {
canvasScale: {
type: Number,
required: true,
},
elementList: {
type: Array as PropType<PPTElement[]>,
required: true,
@ -55,6 +51,7 @@ export default defineComponent({
setup(props) {
const store = useStore<State>()
const activeElementIdList = computed(() => store.state.activeElementIdList)
const canvasScale = computed(() => store.state.canvasScale)
const localActiveElementList = computed(() => props.elementList.filter(el => activeElementIdList.value.includes(el.elId)))
const range = reactive({
@ -64,8 +61,8 @@ export default defineComponent({
maxY: 0,
})
const width = computed(() => (range.maxX - range.minX) * props.canvasScale)
const height = computed(() => (range.maxY - range.minY) * props.canvasScale)
const width = computed(() => (range.maxX - range.minX) * canvasScale.value)
const height = computed(() => (range.maxY - range.minY) * canvasScale.value)
const resizablePoints = computed(() => {
return [

View File

@ -3,36 +3,34 @@
class="slide-background"
:style="backgroundStyle"
>
<template v-if="isShowGridLines">
<template v-if="showGridLines">
<GridLines />
<GridLines :gridSize="100" gridColor="rgba(100, 100, 100, 0.3)" />
</template>
</div>
</template>
<script>
import { computed, defineComponent } from 'vue'
import GridLines from './GridLines'
<script lang="ts">
import { Ref, computed, defineComponent } from 'vue'
import { useStore } from 'vuex'
import { State } from '@/store'
import { Slide } from '@/types/slides'
import GridLines from './GridLines.vue'
export default defineComponent({
name: 'slide-background',
components: {
GridLines,
},
props: {
background: {
type: Array,
},
isShowGridLines: {
type: Boolean,
default: false,
},
},
setup(props) {
const backgroundStyle = computed(() => {
if(!props.background) return { backgroundColor: '#fff' }
setup() {
const store = useStore<State>()
const showGridLines = computed(() => store.state.showGridLines)
const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
const [type, value] = props.background
const backgroundStyle = computed(() => {
if(!currentSlide.value.background) return { backgroundColor: '#fff' }
const [type, value] = currentSlide.value.background
if(type === 'solid') return { backgroundColor: value }
else if(type === 'image') return { backgroundImage: `url(${value}` }
@ -40,6 +38,7 @@ export default defineComponent({
})
return {
showGridLines,
backgroundStyle,
}
},

View File

@ -9,11 +9,11 @@ import { getRectRotatedRange, AlignLine, uniqAlignLines } from '@/utils/element'
export default (
elementList: Ref<PPTElement[]>,
activeGroupElementId: Ref<string>,
canvasScale: Ref<number>,
alignmentLines: Ref<AlignmentLineProps[]>,
) => {
const store = useStore<State>()
const activeElementIdList = computed(() => store.state.activeElementIdList)
const canvasScale = computed(() => store.state.canvasScale)
const dragElement = (e: MouseEvent, element: PPTElement) => {
if(!activeElementIdList.value.includes(element.elId)) return

View File

@ -1,11 +1,12 @@
import { Ref, reactive } from 'vue'
import { Ref, reactive, computed } from 'vue'
import { useStore } from 'vuex'
import { State, MutationTypes } from '@/store'
import { PPTElement } from '@/types/slides'
import { getElementRange } from '@/utils/element'
export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | null>, canvasScale: Ref<number>) => {
export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | null>) => {
const store = useStore<State>()
const canvasScale = computed(() => store.state.canvasScale)
const mouseSelectionState = reactive({
isShow: false,

View File

@ -1,4 +1,4 @@
import { Ref } from 'vue'
import { Ref, computed } from 'vue'
import { useStore } from 'vuex'
import { State, MutationTypes } from '@/store'
import { PPTElement, PPTTextElement, PPTImageElement, PPTShapeElement } from '@/types/slides'
@ -12,8 +12,9 @@ export const getAngleFromCoordinate = (x: number, y: number) => {
return angle
}
export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | null>, canvasScale: Ref<number>) => {
export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | null>) => {
const store = useStore<State>()
const canvasScale = computed(() => store.state.canvasScale)
const rotateElement = (element: PPTTextElement | PPTImageElement | PPTShapeElement) => {
let isMouseDown = true

View File

@ -83,13 +83,13 @@ export const getOppositePoint = (direction: number, points: ReturnType<typeof ge
export default (
elementList: Ref<PPTElement[]>,
canvasScale: Ref<number>,
activeGroupElementId: Ref<string>,
alignmentLines: Ref<AlignmentLineProps[]>,
) => {
const store = useStore<State>()
const activeElementIdList = computed(() => store.state.activeElementIdList)
const ctrlOrShiftKeyActive: Ref<boolean> = computed(() => store.getters.ctrlOrShiftKeyActive)
const canvasScale = computed(() => store.state.canvasScale)
const scaleElement = (e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: ElementScaleHandler) => {
let isMouseDown = true

View File

@ -1,15 +1,14 @@
import { ref, computed, onMounted, onUnmounted, Ref } from 'vue'
import { ref, computed, onMounted, onUnmounted, Ref, watch } from 'vue'
import { useStore } from 'vuex'
import { State } from '@/store'
import { State, MutationTypes } from '@/store'
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
export default (canvasRef: Ref<HTMLElement | null>) => {
const canvasScale = ref(1)
const viewportLeft = ref(0)
const viewportTop = ref(0)
const store = useStore<State>()
const editorAreaShowScale = computed(() => store.state.editorAreaShowScale)
const canvasPercentage = computed(() => store.state.canvasPercentage)
const setViewportSize = () => {
if(!canvasRef.value) return
@ -17,19 +16,21 @@ export default (canvasRef: Ref<HTMLElement | null>) => {
const canvasHeight = canvasRef.value.clientHeight
if(canvasHeight / canvasWidth > VIEWPORT_ASPECT_RATIO) {
const viewportActualWidth = canvasWidth * (editorAreaShowScale.value / 100)
canvasScale.value = viewportActualWidth / VIEWPORT_SIZE
const viewportActualWidth = canvasWidth * (canvasPercentage.value / 100)
store.commit(MutationTypes.SET_CANVAS_SCALE, viewportActualWidth / VIEWPORT_SIZE)
viewportLeft.value = (canvasWidth - viewportActualWidth) / 2
viewportTop.value = (canvasHeight - viewportActualWidth * VIEWPORT_ASPECT_RATIO) / 2
}
else {
const viewportActualHeight = canvasHeight * (editorAreaShowScale.value / 100)
canvasScale.value = viewportActualHeight / (VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO)
const viewportActualHeight = canvasHeight * (canvasPercentage.value / 100)
store.commit(MutationTypes.SET_CANVAS_SCALE, viewportActualHeight / (VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO))
viewportLeft.value = (canvasWidth - viewportActualHeight / VIEWPORT_ASPECT_RATIO) / 2
viewportTop.value = (canvasHeight - viewportActualHeight) / 2
}
}
watch(canvasPercentage, setViewportSize)
const viewportStyles = computed(() => ({
width: VIEWPORT_SIZE,
height: VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO,
@ -47,7 +48,6 @@ export default (canvasRef: Ref<HTMLElement | null>) => {
})
return {
canvasScale,
viewportStyles,
}
}

View File

@ -2,6 +2,7 @@
<div
class="canvas"
ref="canvasRef"
@mousewheel="$event => mousewheelScaleCanvas($event)"
@mousedown="$event => handleClickBlankArea($event)"
v-contextmenu="contextmenus"
v-click-outside="removeEditorAreaFocus"
@ -26,11 +27,6 @@
:quadrant="mouseSelectionState.quadrant"
/>
<SlideBackground
:background="currentSlide?.background"
:isShowGridLines="isShowGridLines"
/>
<AlignmentLine
v-for="(line, index) in alignmentLines" :key="index"
:type="line.type" :axis="line.axis" :length="line.length"
@ -40,13 +36,13 @@
v-if="activeElementIdList.length > 1"
:elementList="elementList"
:scaleMultiElement="scaleMultiElement"
:canvasScale="canvasScale"
/>
<SlideBackground />
<EditableElement
v-for="(element, index) in elementList"
:key="element.elId"
:canvasScale="canvasScale"
:elementInfo="element"
:elementIndex="index + 1"
:isActive="activeElementIdList.includes(element.elId)"
@ -64,6 +60,7 @@
<script lang="ts">
import { computed, defineComponent, Ref, ref, watch, watchEffect } from 'vue'
import { useStore } from 'vuex'
import throttle from 'lodash/throttle'
import { State, MutationTypes } from '@/store'
import { ContextmenuItem } from '@/components/Contextmenu/types'
import { PPTElement, Slide } from '@/types/slides'
@ -80,6 +77,7 @@ import useDragElement from './hooks/useDragElement'
import useDeleteElement from '@/hooks/useDeleteElement'
import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
import useSelectAllElement from '@/hooks/useSelectAllElement'
import useScaleCanvas from '@/hooks/useScaleCanvas'
import EditableElement from '@/views/_common/_element/EditableElement.vue'
import MouseSelection from './MouseSelection.vue'
@ -102,6 +100,7 @@ export default defineComponent({
const activeElementIdList = computed(() => store.state.activeElementIdList)
const handleElementId = computed(() => store.state.handleElementId)
const editorAreaFocus = computed(() => store.state.editorAreaFocus)
const ctrlKeyState = computed(() => store.state.ctrlKeyState)
const ctrlOrShiftKeyActive: Ref<boolean> = computed(() => store.getters.ctrlOrShiftKeyActive)
const viewportRef = ref<HTMLElement | null>(null)
@ -121,14 +120,15 @@ export default defineComponent({
useDropImageElement(viewportRef)
const canvasRef = ref<HTMLElement | null>(null)
const { canvasScale, viewportStyles } = useViewportSize(canvasRef)
const canvasScale = computed(() => store.state.canvasScale)
const { viewportStyles } = useViewportSize(canvasRef)
const { mouseSelectionState, updateMouseSelection } = useMouseSelection(elementList, viewportRef, canvasScale)
const { mouseSelectionState, updateMouseSelection } = useMouseSelection(elementList, viewportRef)
const { dragElement } = useDragElement(elementList, activeGroupElementId, canvasScale, alignmentLines)
const { dragElement } = useDragElement(elementList, activeGroupElementId, alignmentLines)
const { selectElement } = useSelectElement(elementList, activeGroupElementId, dragElement)
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, canvasScale, activeGroupElementId, alignmentLines)
const { rotateElement } = useRotateElement(elementList, viewportRef, canvasScale)
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, activeGroupElementId, alignmentLines)
const { rotateElement } = useRotateElement(elementList, viewportRef)
const { selectAllElement } = useSelectAllElement()
const { deleteAllElements } = useDeleteElement()
@ -144,6 +144,17 @@ export default defineComponent({
if(editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, false)
}
const { scaleCanvas } = useScaleCanvas()
const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing: false })
const mousewheelScaleCanvas = (e: WheelEvent) => {
if(!ctrlKeyState.value) return
e.preventDefault()
if(e.deltaY > 0) throttleScaleCanvas('-')
else if(e.deltaY < 0) throttleScaleCanvas('+')
}
const contextmenus = (): ContextmenuItem[] => {
return [
{
@ -182,6 +193,7 @@ export default defineComponent({
rotateElement,
scaleElement,
scaleMultiElement,
mousewheelScaleCanvas,
contextmenus,
}
},

View File

@ -15,19 +15,41 @@
</div>
<div class="right-handler">
<IconFont class="handler-item viewport-size" type="icon-minus" />
<span class="text">100%</span>
<IconFont class="handler-item viewport-size" type="icon-plus" />
<IconFont class="handler-item viewport-size" type="icon-number" />
<IconFont class="handler-item viewport-size" type="icon-minus" @click="scaleCanvas('-')" />
<span class="text">{{canvasScalePercentage}}</span>
<IconFont class="handler-item viewport-size" type="icon-plus" @click="scaleCanvas('+')" />
<IconFont class="handler-item viewport-size" type="icon-number" @click="toggleGridLines()" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'
import { MutationTypes, State } from '@/store'
import useScaleCanvas from '@/hooks/useScaleCanvas'
export default defineComponent({
name: 'canvas-tool',
setup() {
const store = useStore<State>()
const canvasScale = computed(() => store.state.canvasScale)
const showGridLines = computed(() => store.state.showGridLines)
const canvasScalePercentage = computed(() => parseInt(canvasScale.value * 100 + '') + '%')
const { scaleCanvas } = useScaleCanvas()
const toggleGridLines = () => {
store.commit(MutationTypes.SET_GRID_LINES_STATE, !showGridLines.value)
}
return {
scaleCanvas,
canvasScalePercentage,
toggleGridLines,
}
},
})
</script>
@ -40,6 +62,7 @@ export default defineComponent({
justify-content: space-between;
padding: 0 10px;
font-size: 13px;
user-select: none;
}
.left-handler {
display: flex;
@ -59,6 +82,11 @@ export default defineComponent({
display: flex;
align-items: center;
.text {
width: 40px;
text-align: center;
}
.viewport-size {
font-size: 12px;
margin-top: -1px;

View File

@ -5,7 +5,7 @@
v-click-outside="() => setThumbnailsFocus(false)"
>
<div class="add-slide">
<span>+ 添加幻灯片</span>
<span @click="createSlide()">+ 添加幻灯片</span>
</div>
<draggable
class="thumbnail-list"
@ -130,6 +130,7 @@ export default defineComponent({
setThumbnailsFocus,
slides,
slideIndex,
createSlide,
changSlideIndex,
contextmenus,
fillDigit,

View File

@ -24,6 +24,8 @@
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { useStore } from 'vuex'
import { State } from '@/store'
import { PPTElement, PPTTextElement, PPTImageElement, PPTShapeElement, PPTLineElement } from '@/types/slides'
import { ContextmenuItem } from '@/components/Contextmenu/types'
@ -42,10 +44,6 @@ import TextElement from './TextElement/index.vue'
export default defineComponent({
name: 'editable-element',
props: {
canvasScale: {
type: Number,
required: true,
},
elementInfo: {
type: Object as PropType<PPTElement>,
required: true,
@ -88,6 +86,9 @@ export default defineComponent({
},
},
setup(props) {
const store = useStore<State>()
const canvasScale = computed(() => store.state.canvasScale)
const currentElementComponent = computed(() => {
const elementTypeMap = {
'image': ImageElement,
@ -180,6 +181,7 @@ export default defineComponent({
}
return {
canvasScale,
currentElementComponent,
contextmenus,
}