Skip to content

Commit

Permalink
feat: add opacity control
Browse files Browse the repository at this point in the history
  • Loading branch information
motea927 committed Jan 12, 2024
1 parent abce3d9 commit b1b6ede
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 41 deletions.
11 changes: 4 additions & 7 deletions playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ export default defineConfig({
style: {
position: 'absolute',
margin: 'auto',
top: '0',
right: '0',
bottom: '0',
left: '0',
inset: '0',
width: '13.34rem',
height: '7.5rem',
backgroundImage: 'url(https://picsum.photos/200/300)'
}
height: '7.5rem'
},
imageUrl: 'https://picsum.photos/200/300'
}
})
// VueDevTools()
Expand Down
43 changes: 30 additions & 13 deletions popup/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
<template>
<main class="fixed right-0 top-1/2 transform -translate-y-1/2 z-[999]">
<main class="fixed right-2 top-1/2 transform -translate-y-1/2 z-[999]">
<div class="flex flex-col p-2 divide-y shadow-xl rounded-xl">
<Component
v-for="icon in icons"
:is="icon"
class="p-2"
:is-open="state.isOpen"
<IconEye :isOpen="state.isOpen" @click-eye="state.isOpen = !state.isOpen" />

<IconDraggable
@clickDrag="state.isDraggable = !state.isDraggable"
:is-draggable="state.isDraggable"
@click-eye="state.isOpen = !state.isOpen"
@click-drag="state.isDraggable = !state.isDraggable"
/>

<IconImageUpload
@upload-image="(base64: string) => (state.layoutPreview.imageUrl = base64)"
/>

<IconOpacity :opacity="state.layoutPreview.opacity" @operation="handleClickOperation" />
</div>

<Teleport to="body">
<LayoutPreview v-show="state.isOpen" :is-draggable="state.isDraggable" />
<LayoutPreview
v-show="state.isOpen"
:is-draggable="state.isDraggable"
:image-url="state.layoutPreview.imageUrl"
:opacity="state.layoutPreview.opacity"
/>
</Teleport>
</main>
</template>
Expand All @@ -22,22 +30,31 @@
import { useStorage } from '@vueuse/core'
import IconEye from '@/components/icons/IconEye.vue'
import IconSetting from '@/components/icons/IconSetting.vue'
import IconDraggable from '@/components/icons/IconDraggable.vue'
import IconImageUpload from '@/components/icons/IconImageUpload.vue'
import IconOpacity from '@/components/icons/IconOpacity.vue'
import LayoutPreview from '@/components/LayoutPreview.vue'
const icons = [IconEye, IconDraggable, IconSetting]
const state = useStorage(
'unplugin-overlay-layout',
{
isOpen: true,
isDraggable: false,
layoutPreview: {
style: ''
style: '',
imageUrl: window._unpluginOverlayLayout.layoutPreview?.imageUrl || '',
opacity: window._unpluginOverlayLayout.layoutPreview?.opacity || 50
}
},
sessionStorage
)
const handleClickOperation = (offset: number) => {
if (offset > 0) {
state.value.layoutPreview.opacity = Math.min(100, state.value.layoutPreview.opacity + offset)
} else {
state.value.layoutPreview.opacity = Math.max(0, state.value.layoutPreview.opacity + offset)
}
}
</script>
24 changes: 19 additions & 5 deletions popup/src/components/LayoutPreview.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div ref="el" :style="[style, ...computedDragStyle]" class="z-[990]"></div>
<div ref="el" :style="mergedStyle" class="z-[990] bg-no-repeat bg-center"></div>
</template>

<script setup lang="ts">
Expand All @@ -9,19 +9,29 @@ import { useDraggable } from '@vueuse/core'
const props = defineProps<{
isDraggable: boolean
imageUrl: string
opacity: number
}>()
const style = window._unpluginOverlayLayout?.layoutPreview?.style || {}
const defaultStyle = computed<StyleValue>(() => {
return {
backgroundSize: '100% auto',
backgroundImage: `url(${props.imageUrl})`
}
})
const userStyle = window._unpluginOverlayLayout.layoutPreview?.style || {}
const el = ref<HTMLElement | null>(null)
const { style: dragStyle } = useDraggable(el)
const computedDragStyle = computed<StyleValue>(() => {
const controlStyle = computed<StyleValue[]>(() => {
const pointerEventsStyle = props.isDraggable ? '' : 'pointer-events: none;'
const commonControlStyle = [pointerEventsStyle, { opacity: `${props.opacity}%` }]
// init value, do not change style
if (dragStyle.value === 'left:0px;top:0px;') {
return [pointerEventsStyle]
return [...commonControlStyle]
}
return [
Expand All @@ -30,7 +40,11 @@ const computedDragStyle = computed<StyleValue>(() => {
position: 'fixed',
margin: 'unset'
},
pointerEventsStyle
...commonControlStyle
]
})
const mergedStyle = computed<StyleValue>(() => {
return [defaultStyle.value, userStyle, ...controlStyle.value]
})
</script>
7 changes: 7 additions & 0 deletions popup/src/components/icons/IconContainer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<template>
<button class="flex flex-col items-center justify-center gap-1 p-2">
<slot />
</button>
</template>

<script setup lang="ts"></script>
9 changes: 7 additions & 2 deletions popup/src/components/icons/IconDraggable.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
<template>
<button @click="$emit('clickDrag')">
<IconContainer @click="emit('clickDrag')">
<Icon
:icon="`fluent:drag-20-regular`"
:class="[props.isDraggable ? 'text-green-600' : 'opacity-20']"
/>
</button>
</IconContainer>
</template>

<script setup lang="ts">
import IconContainer from '@/components/icons/IconContainer.vue'
import { Icon } from '@iconify/vue'
const props = defineProps<{
isDraggable: boolean
}>()
const emit = defineEmits<{
(e: 'clickDrag'): void
}>()
</script>
11 changes: 7 additions & 4 deletions popup/src/components/icons/IconEye.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
<template>
<button @click="$emit('clickEye')">
<IconContainer @click="emit('clickEye')">
<Icon :icon="props.isOpen ? 'iconamoon:eye-off' : 'iconamoon:eye'" />
</button>
</IconContainer>
</template>

<script setup lang="ts">
import IconContainer from '@/components/icons/IconContainer.vue'
import { Icon } from '@iconify/vue'
const props = defineProps<{ isOpen: boolean }>()
</script>
<style scoped></style>
const emit = defineEmits<{
(e: 'clickEye'): void
}>()
</script>
49 changes: 49 additions & 0 deletions popup/src/components/icons/IconImageUpload.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<template>
<IconContainer @click="handleClickImageUpload">
<Icon :icon="`uil:image-upload`" />
</IconContainer>
</template>

<script setup lang="ts">
import IconContainer from '@/components/icons/IconContainer.vue'
import { useFileSystemAccess } from '@vueuse/core'
import { Icon } from '@iconify/vue'
const emit = defineEmits<{
(e: 'uploadImage', base64: string): void
}>()
const { data, file, open } = useFileSystemAccess()
const transformFileToBase64 = (file: File) =>
new Promise((resolve, reject) => {
const fileReader = new FileReader()
fileReader.onload = () => {
resolve(fileReader.result)
}
fileReader.onerror = reject
fileReader.readAsDataURL(file)
})
const handleClickImageUpload = async () => {
const pickerOpts = {
types: [
{
description: 'Images',
accept: {
'image/*': ['.png', '.gif', '.jpeg', '.jpg']
}
}
],
excludeAcceptAllOption: true,
multiple: false
}
await open(pickerOpts)
if (!file.value) return
const base64 = await transformFileToBase64(file.value)
emit('uploadImage', base64 as string)
}
</script>
52 changes: 52 additions & 0 deletions popup/src/components/icons/IconOpacity.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<IconContainer
v-for="icon in icons"
:key="icon.name"
@click="handleClickOperation(icon.offset)"
:class="icon.className"
>
<Icon :icon="icon.name" />
<p v-if="icon.offset > 0" class="text-xs">{{ props.opacity }} %</p>
</IconContainer>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import IconContainer from '@/components/icons/IconContainer.vue'
import { Icon } from '@iconify/vue'
type Icon = {
offset: number
name: string
className: string[] | string
}
const props = defineProps<{ opacity: number }>()
const emit = defineEmits<{
(e: 'operation', offset: number): void
}>()
const icons = computed<Array<Icon>>(() => {
const disabledClassName = ['opacity-50', 'cursor-not-allowed']
return [
{
offset: 10,
name: 'mdi:eye-plus-outline',
className: props.opacity >= 100 ? disabledClassName : ''
},
{
offset: -10,
name: 'mdi:eye-minus-outline',
className: props.opacity <= 0 ? disabledClassName : ''
}
]
})
const handleClickOperation = (offset: number) => {
if (offset > 0 && props.opacity === 100) return
if (offset < 0 && props.opacity === 0) return
emit('operation', offset)
}
</script>
5 changes: 3 additions & 2 deletions popup/src/components/icons/IconSetting.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<template>
<button>
<IconSetting>
<Icon :icon="`uil:setting`" />
</button>
</IconSetting>
</template>

<script setup lang="ts">
import IconSetting from '@/components/icons/IconSetting.vue'
import { Icon } from '@iconify/vue'
</script>
6 changes: 2 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,12 @@ export const unpluginFactory: UnpluginFactory<
)
},
transformIndexHtml(html: string) {
console.log(html)
const injectInlineScript = html.replace(
'</head>',
`
<script type="text/javascript">
window._unpluginOverlayLayout = {}
window._unpluginOverlayLayout = ${JSON.stringify(options)}
console.log(window._unpluginOverlayLayout)
window._unpluginOverlayLayout = {};
window._unpluginOverlayLayout = ${JSON.stringify(options)};
</script>
</head>
`
Expand Down
Loading

0 comments on commit b1b6ede

Please sign in to comment.