| <template> | |
| <div class="ruler"> | |
| <div | |
| class="h" | |
| :style="{ | |
| width: viewportStyles.width * canvasScale + 'px', | |
| left: viewportStyles.left + 'px', | |
| }" | |
| > | |
| <div | |
| class="ruler-marker-100" | |
| :class="{ 'hide': markerSize < 36, 'omit': markerSize < 72 }" | |
| v-for="marker in 20" | |
| :key="`h-marker-100-${marker}`" | |
| :style="{ width: markerSize + 'px' }" | |
| > | |
| <span v-if="marker * 100 <= viewportSize">{{ marker * 100 }}</span> | |
| </div> | |
| <div class="range" | |
| v-if="elementListRange" | |
| :style="{ | |
| left: elementListRange.minX * canvasScale + 'px', | |
| width: (elementListRange.maxX - elementListRange.minX) * canvasScale + 'px', | |
| }" | |
| ></div> | |
| </div> | |
| <div | |
| class="v" | |
| :style="{ | |
| height: viewportStyles.height * canvasScale + 'px', | |
| top: viewportStyles.top + 'px', | |
| }" | |
| > | |
| <div | |
| class="ruler-marker-100" | |
| :class="{ 'hide': markerSize < 36, 'omit': markerSize < 72 }" | |
| v-for="marker in 20" | |
| :key="`v-marker-100-${marker}`" | |
| :style="{ height: markerSize + 'px' }" | |
| > | |
| <span v-if="marker * 100 <= viewportSize * viewportRatio">{{ marker * 100 }}</span> | |
| </div> | |
| <div class="range" | |
| v-if="elementListRange" | |
| :style="{ | |
| top: elementListRange.minY * canvasScale + 'px', | |
| height: (elementListRange.maxY - elementListRange.minY) * canvasScale + 'px', | |
| }" | |
| ></div> | |
| </div> | |
| </div> | |
| </template> | |
| <script lang="ts" setup> | |
| import { watchEffect, computed, ref } from 'vue' | |
| import { storeToRefs } from 'pinia' | |
| import { useMainStore, useSlidesStore } from '@/store' | |
| import { getElementListRange } from '@/utils/element' | |
| import type { PPTElement } from '@/types/slides' | |
| interface ViewportStyles { | |
| top: number | |
| left: number | |
| width: number | |
| height: number | |
| } | |
| const props = defineProps<{ | |
| viewportStyles: ViewportStyles | |
| elementList: PPTElement[] | |
| }>() | |
| const { canvasScale, activeElementIdList } = storeToRefs(useMainStore()) | |
| const { viewportRatio, viewportSize } = storeToRefs(useSlidesStore()) | |
| const elementListRange = ref<null | ReturnType<typeof getElementListRange>>(null) | |
| watchEffect(() => { | |
| const els = props.elementList.filter(el => activeElementIdList.value.includes(el.id)) | |
| if (!els.length) return elementListRange.value = null | |
| elementListRange.value = getElementListRange(els) | |
| }) | |
| const markerSize = computed(() => { | |
| return props.viewportStyles.width * canvasScale.value / (viewportSize.value / 100) | |
| }) | |
| </script> | |
| <style lang="scss" scoped> | |
| .ruler { | |
| font-size: 12px; | |
| } | |
| .h { | |
| position: absolute; | |
| background-color: #fff; | |
| border: 1px solid $borderColor; | |
| height: 20px; | |
| top: 5px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| overflow: hidden; | |
| .range { | |
| position: absolute; | |
| top: 0; | |
| bottom: 0; | |
| background-color: rgba($color: $themeColor, $alpha: .1); | |
| } | |
| .ruler-marker-100 { | |
| height: 100%; | |
| line-height: 20px; | |
| text-align: right; | |
| flex-shrink: 0; | |
| padding-right: 5px; | |
| position: relative; | |
| &.hide span { | |
| display: none; | |
| } | |
| &.omit::before { | |
| display: none; | |
| } | |
| &:not(:last-child)::after { | |
| content: ''; | |
| width: .1px; | |
| height: 12px; | |
| position: absolute; | |
| right: 0; | |
| bottom: 0; | |
| background-color: #999; | |
| } | |
| &::before { | |
| content: ''; | |
| width: .1px; | |
| height: 8px; | |
| position: absolute; | |
| right: 50%; | |
| bottom: 0; | |
| background-color: #999; | |
| } | |
| } | |
| } | |
| .v { | |
| position: absolute; | |
| background-color: #fff; | |
| border: 1px solid $borderColor; | |
| width: 20px; | |
| left: 5px; | |
| overflow: hidden; | |
| .range { | |
| position: absolute; | |
| left: 0; | |
| right: 0; | |
| background-color: rgba($color: $themeColor, $alpha: .1); | |
| } | |
| .ruler-marker-100 { | |
| width: 100%; | |
| line-height: 20px; | |
| text-align: right; | |
| padding-bottom: 5px; | |
| position: relative; | |
| writing-mode: vertical-rl; | |
| &.hide span { | |
| display: none; | |
| } | |
| &.omit::before { | |
| display: none; | |
| } | |
| &:not(:last-child)::after { | |
| content: ''; | |
| height: .1px; | |
| width: 12px; | |
| position: absolute; | |
| bottom: 0; | |
| right: 0; | |
| background-color: #999; | |
| } | |
| &::before { | |
| content: ''; | |
| height: .1px; | |
| width: 8px; | |
| position: absolute; | |
| bottom: 50%; | |
| right: 0; | |
| background-color: #999; | |
| } | |
| } | |
| } | |
| </style> |