mirror of
https://github.com/Kvan7/Exiled-Exchange-2.git
synced 2026-09-23 21:25:41 +00:00
vue3
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
<div style="top: 0; left: 0; height: 100%; width: 100%; position: absolute;"
|
||||
:style="{ background: overlayBackground }"
|
||||
@click="handleBackgroundClick"></div>
|
||||
<template v-for="widget of widgets">
|
||||
<component :key="widget.wmId"
|
||||
<template v-for="widget of widgets" :key="widget.wmId">
|
||||
<component
|
||||
v-show="isVisible(widget.wmId)"
|
||||
:config="widget"
|
||||
:id="`widget-${widget.wmId}`"
|
||||
|
||||
+121
-97
@@ -28,19 +28,41 @@
|
||||
<div v-if="isHandleShown('cc')" :class="$style.mover" @mousedown="startMove('cc', $event)" style="left: calc(50% - 0.5rem); top: calc(50% - 0.5rem);"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isMoving" :class="[$style.mover, $style.active]" :style="moverPosition" @mousedown="startMove(anchor.pos, $event)"></div>
|
||||
<div v-if="isMoving" :class="[$style.mover, $style.active]" :style="moverPosition" @mousedown="startMove(config.anchor.pos, $event)"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed, inject, ref } from 'vue'
|
||||
import { Widget, Anchor, WidgetManager } from './interfaces'
|
||||
|
||||
function useRemovable (remove: () => void) {
|
||||
const isRemoving = ref(false)
|
||||
let tmid: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function startRemoveTimer () {
|
||||
isRemoving.value = true
|
||||
tmid = setTimeout(remove, 1000)
|
||||
}
|
||||
function cancelRemoveTimer () {
|
||||
isRemoving.value = false
|
||||
if (tmid !== null) {
|
||||
clearTimeout(tmid)
|
||||
tmid = null
|
||||
}
|
||||
}
|
||||
|
||||
return { startRemoveTimer, cancelRemoveTimer, isRemoving }
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
type: Object as PropType<Widget & { anchor: Anchor }>,
|
||||
required: true
|
||||
},
|
||||
moveHandles: {
|
||||
type: [String, Array],
|
||||
type: [String, Array] as PropType<string | string[]>,
|
||||
default: undefined
|
||||
},
|
||||
readonly: {
|
||||
@@ -56,148 +78,150 @@ export default {
|
||||
default: true
|
||||
}
|
||||
},
|
||||
inject: ['wm'],
|
||||
data () {
|
||||
return {
|
||||
isEditing: false,
|
||||
isMoving: false,
|
||||
isRemoving: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
anchor () {
|
||||
return this.config.anchor
|
||||
},
|
||||
moverPosition () {
|
||||
setup (props) {
|
||||
const wm = inject<WidgetManager>('wm')!
|
||||
|
||||
const moverPosition = computed(() => {
|
||||
const { anchor, wmZorder } = props.config
|
||||
return {
|
||||
'top': `max(0%, min(calc(${this.anchor.y}% - (1rem/2)), calc(100% - 1rem)))`,
|
||||
'left': `max(0%, min(calc(${this.anchor.x}% - (1rem/2)), calc(100% - 1rem)))`,
|
||||
'z-index': this.config.wmZorder
|
||||
'top': `max(0%, min(calc(${anchor.y}% - (1rem/2)), calc(100% - 1rem)))`,
|
||||
'left': `max(0%, min(calc(${anchor.x}% - (1rem/2)), calc(100% - 1rem)))`,
|
||||
'z-index': wmZorder
|
||||
}
|
||||
},
|
||||
widgetPosition () {
|
||||
let translate
|
||||
})
|
||||
|
||||
const widgetPosition = computed(() => {
|
||||
const { anchor, wmZorder } = props.config
|
||||
|
||||
// <top, center, bottom><left, center, right>
|
||||
if (this.anchor.pos === 'tl') {
|
||||
let translate
|
||||
if (anchor.pos === 'tl') {
|
||||
translate = undefined
|
||||
} else if (this.anchor.pos === 'tc') {
|
||||
} else if (anchor.pos === 'tc') {
|
||||
translate = 'translate(-50%, 0%)'
|
||||
} else if (this.anchor.pos === 'tr') {
|
||||
} else if (anchor.pos === 'tr') {
|
||||
translate = 'translate(-100%, 0%)'
|
||||
} else if (this.anchor.pos === 'cr') {
|
||||
} else if (anchor.pos === 'cr') {
|
||||
translate = 'translate(-100%, -50%)'
|
||||
} else if (this.anchor.pos === 'br') {
|
||||
} else if (anchor.pos === 'br') {
|
||||
translate = 'translate(-100%, -100%)'
|
||||
} else if (this.anchor.pos === 'bc') {
|
||||
} else if (anchor.pos === 'bc') {
|
||||
translate = 'translate(-50%, -100%)'
|
||||
} else if (this.anchor.pos === 'bl') {
|
||||
} else if (anchor.pos === 'bl') {
|
||||
translate = 'translate(0%, -100%)'
|
||||
} else if (this.anchor.pos === 'cl') {
|
||||
} else if (anchor.pos === 'cl') {
|
||||
translate = 'translate(0%, -50%)'
|
||||
} else if (this.anchor.pos === 'cc') {
|
||||
} else if (anchor.pos === 'cc') {
|
||||
translate = 'translate(-50%, -50%)'
|
||||
}
|
||||
|
||||
return {
|
||||
'top': `${this.anchor.y}%`,
|
||||
'left': `${this.anchor.x}%`,
|
||||
'top': `${anchor.y}%`,
|
||||
'left': `${anchor.x}%`,
|
||||
'transform': translate,
|
||||
'z-index': this.config.wmZorder
|
||||
'z-index': wmZorder
|
||||
}
|
||||
},
|
||||
actionsPosition () {
|
||||
if (this.anchor.x <= 50 && this.anchor.y <= 50) {
|
||||
})
|
||||
|
||||
const actionsPosition = computed(() => {
|
||||
const { anchor } = props.config
|
||||
|
||||
if (anchor.x <= 50 && anchor.y <= 50) {
|
||||
return {
|
||||
top: '0',
|
||||
left: '100%'
|
||||
}
|
||||
}
|
||||
if (this.anchor.x >= 50 && this.anchor.y <= 50) {
|
||||
if (anchor.x >= 50 && anchor.y <= 50) {
|
||||
return {
|
||||
top: '0',
|
||||
right: '100%'
|
||||
}
|
||||
}
|
||||
if (this.anchor.x >= 50 && this.anchor.y >= 50) {
|
||||
if (anchor.x >= 50 && anchor.y >= 50) {
|
||||
return {
|
||||
bottom: '0',
|
||||
right: '100%'
|
||||
}
|
||||
}
|
||||
if (this.anchor.x <= 50 && this.anchor.y >= 50) {
|
||||
if (anchor.x <= 50 && anchor.y >= 50) {
|
||||
return {
|
||||
bottom: '0',
|
||||
left: '100%'
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
shownHandles () {
|
||||
if (!this.moveHandles) {
|
||||
})
|
||||
|
||||
const shownHandles = computed(() => {
|
||||
if (!props.moveHandles) {
|
||||
return ['tl', 'tc', 'tr', 'cr', 'br', 'bc', 'bl', 'cl', 'cc']
|
||||
}
|
||||
if (this.moveHandles === 'center') {
|
||||
if (props.moveHandles === 'center') {
|
||||
return ['cc']
|
||||
}
|
||||
if (this.moveHandles === 'corners') {
|
||||
if (props.moveHandles === 'corners') {
|
||||
return ['tl', 'tr', 'br', 'bl']
|
||||
}
|
||||
if (this.moveHandles === 'top-bottom') {
|
||||
if (props.moveHandles === 'top-bottom') {
|
||||
return ['tl', 'tc', 'tr', 'br', 'bc', 'bl']
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
startMove (pos, e) {
|
||||
this.anchor.pos = pos
|
||||
this.updatePosition(e)
|
||||
document.addEventListener('mousemove', this.updatePosition)
|
||||
document.addEventListener('mouseup', this.endMove)
|
||||
},
|
||||
endMove () {
|
||||
document.removeEventListener('mousemove', this.updatePosition)
|
||||
document.removeEventListener('mouseup', this.endMove)
|
||||
},
|
||||
updatePosition (e) {
|
||||
this.anchor.x = Math.min(Math.max(e.clientX / window.innerWidth, 0), 1) * 100
|
||||
this.anchor.y = Math.min(Math.max(e.clientY / window.innerHeight, 0), 1) * 100
|
||||
},
|
||||
isHandleShown (pos) {
|
||||
return this.anchor.pos !== pos &&
|
||||
this.shownHandles.includes(pos)
|
||||
},
|
||||
toggleEdit () {
|
||||
this.isEditing = !this.isEditing
|
||||
this.isMoving = false
|
||||
},
|
||||
toggleMove () {
|
||||
this.isMoving = !this.isMoving
|
||||
this.isEditing = false
|
||||
},
|
||||
hide () {
|
||||
this.wm.hide(this.config.wmId)
|
||||
},
|
||||
remove () {
|
||||
this.wm.remove(this.config.wmId)
|
||||
},
|
||||
startRemoveTimer () {
|
||||
this.isRemoving = true
|
||||
this.removeTimeout = setTimeout(this.remove, 1000)
|
||||
},
|
||||
cancelRemoveTimer () {
|
||||
this.isRemoving = false
|
||||
clearTimeout(this.removeTimeout)
|
||||
this.removeTimeout = undefined
|
||||
},
|
||||
handleMouseDown () {
|
||||
if (this.config.wmId != null) {
|
||||
this.wm.bringToTop(this.config.wmId)
|
||||
})
|
||||
|
||||
function startMove (pos: string, e: MouseEvent) {
|
||||
props.config.anchor.pos = pos
|
||||
updatePosition(e)
|
||||
document.addEventListener('mousemove', updatePosition)
|
||||
document.addEventListener('mouseup', endMove)
|
||||
|
||||
function endMove () {
|
||||
document.removeEventListener('mousemove', updatePosition)
|
||||
document.removeEventListener('mouseup', endMove)
|
||||
}
|
||||
}
|
||||
|
||||
function updatePosition (e: MouseEvent) {
|
||||
props.config.anchor.x = Math.min(Math.max(e.clientX / window.innerWidth, 0), 1) * 100
|
||||
props.config.anchor.y = Math.min(Math.max(e.clientY / window.innerHeight, 0), 1) * 100
|
||||
}
|
||||
|
||||
const isEditing = ref(false)
|
||||
const isMoving = ref(false)
|
||||
|
||||
return {
|
||||
moverPosition,
|
||||
widgetPosition,
|
||||
actionsPosition,
|
||||
handleMouseDown () {
|
||||
// @TODO: why null check?
|
||||
if (props.config.wmId != null) {
|
||||
wm.bringToTop(props.config.wmId)
|
||||
}
|
||||
},
|
||||
startMove,
|
||||
isMoving,
|
||||
isEditing,
|
||||
isHandleShown (pos: string) {
|
||||
return props.config.anchor.pos !== pos &&
|
||||
shownHandles.value.includes(pos)
|
||||
},
|
||||
hide () {
|
||||
wm.hide(props.config.wmId)
|
||||
},
|
||||
toggleEdit () {
|
||||
isEditing.value = !isEditing.value
|
||||
isMoving.value = false
|
||||
},
|
||||
toggleMove () {
|
||||
isMoving.value = !isMoving.value
|
||||
isEditing.value = false
|
||||
},
|
||||
...useRemovable(() => {
|
||||
wm.remove(props.config.wmId)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="postcss" module>
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
<widget :config="config" :hideable="false" :removable="false" move-handles="corners" v-slot="{ isEditing }">
|
||||
<div class="widget-default-style">
|
||||
<div class="py-1 pr-1 flex items-center text-base">
|
||||
<template v-for="widget in widgets">
|
||||
<button :key="widget.wmId" @click="toggle(widget)"
|
||||
<template v-for="widget in widgets" :key="widget.wmId">
|
||||
<button @click="toggle(widget)"
|
||||
:class="widget.wmWants === 'show' ? 'border-gray-500' : 'border-gray-900'"
|
||||
class="bg-gray-800 rounded text-gray-100 ml-1 p-2 leading-none whitespace-no-wrap border">{{ widget.wmTitle || `#${widget.wmId}` }}</button>
|
||||
</template>
|
||||
<ui-popper :delayOnMouseOut="150">
|
||||
<template slot="reference">
|
||||
<ui-popover>
|
||||
<template #target>
|
||||
<button class="rounded text-gray-600 ml-1 px-2 py-1 leading-none"><i class="fas fa-ellipsis-h"></i></button>
|
||||
</template>
|
||||
<div class="popper">
|
||||
<div class="flex flex-col justify-center text-base text-left">
|
||||
<template #content>
|
||||
<div class="flex flex-col justify-center text-base">
|
||||
<!-- <button class="text-left hover:bg-gray-400 rounded px-1 whitespace-no-wrap">Chromatic calculator</button> -->
|
||||
<!-- <button class="text-left hover:bg-gray-400 rounded px-1 whitespace-no-wrap">Screen saver</button> -->
|
||||
<!-- add widget -->
|
||||
@@ -22,8 +22,8 @@
|
||||
<button class="text-left hover:bg-gray-400 rounded px-1 whitespace-no-wrap" @click="createOfType('image-strip')">{{ $t('Image strip') }}</button>
|
||||
<!-- <button class="text-left hover:bg-gray-400 rounded px-1 whitespace-no-wrap" @click="createOfType('TODO')">Image</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</ui-popper>
|
||||
</template>
|
||||
</ui-popover>
|
||||
</div>
|
||||
<div v-if="isEditing" class="text-gray-100 px-2 pb-1 whitespace-no-wrap">
|
||||
<ui-toggle v-model="config.alwaysShow">{{ $t('Show button for active widgets') }}</ui-toggle>
|
||||
@@ -32,39 +32,44 @@
|
||||
</widget>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Widget from './Widget'
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed, inject, ref } from 'vue'
|
||||
import { Widget as IWidget, WidgetManager, WidgetMenu } from './interfaces'
|
||||
import Widget from './Widget.vue'
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
components: { Widget },
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
type: Object as PropType<WidgetMenu>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
inject: ['wm'],
|
||||
computed: {
|
||||
widgets () {
|
||||
return this.wm.widgets.filter(widget =>
|
||||
setup (props) {
|
||||
const wm = inject<WidgetManager>('wm')!
|
||||
|
||||
const widgets = computed(() => {
|
||||
return wm.widgets.filter(widget =>
|
||||
!widget.wmFlags.includes('skip-menu') &&
|
||||
(this.config.alwaysShow || (widget.wmWants === 'hide'))
|
||||
(props.config.alwaysShow || (widget.wmWants === 'hide'))
|
||||
)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createOfType (type) {
|
||||
this.wm.create(type)
|
||||
},
|
||||
toggle (widget) {
|
||||
if (widget.wmWants === 'hide') {
|
||||
this.wm.show(widget.wmId)
|
||||
} else {
|
||||
this.wm.hide(widget.wmId)
|
||||
})
|
||||
|
||||
return {
|
||||
widgets,
|
||||
createOfType (type: string) {
|
||||
wm.create(type)
|
||||
},
|
||||
toggle (widget: IWidget) {
|
||||
if (widget.wmWants === 'hide') {
|
||||
wm.show(widget.wmId)
|
||||
} else {
|
||||
wm.hide(widget.wmId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="postcss" module>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
export interface Widget {
|
||||
wmId: number
|
||||
wmType: string
|
||||
wmTitle: string
|
||||
wmWants: 'show' | 'hide'
|
||||
wmZorder: number | 'exclusive' | null
|
||||
wmFlags: Array<WellKnownFlag | string>
|
||||
// ---------------
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type WellKnownFlag =
|
||||
'uninitialized' |
|
||||
'skip-menu' |
|
||||
'has-browser' |
|
||||
'invisible-on-blur' |
|
||||
'hide-on-blur' |
|
||||
'hide-on-blur(close)' |
|
||||
'hide-on-focus'
|
||||
|
||||
export interface Anchor {
|
||||
pos: string
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface WidgetManager {
|
||||
widgets: Widget[]
|
||||
show (wmId: number): void
|
||||
hide (wmId: number): void
|
||||
remove (wmId: number): void
|
||||
bringToTop (wmId: number): void
|
||||
create (wmType: string): void
|
||||
}
|
||||
|
||||
export interface WidgetMenu extends Widget {
|
||||
anchor: Anchor
|
||||
alwaysShow: boolean
|
||||
}
|
||||
@@ -3,19 +3,18 @@
|
||||
@mouseenter="isHovered = true"
|
||||
@mouseleave="isHovered = false">
|
||||
<img :class="$style.img" :src="isBundled ? require(`@/assets/images/${src}`) : src">
|
||||
<portal v-if="isHovered && !disabled" mount-to="body" append>
|
||||
<teleport v-if="isHovered && !disabled" to="body">
|
||||
<div :class="$style.imgFullscreenWrapper">
|
||||
<img :class="$style.imgFullscreen" :src="isBundled ? require(`@/assets/images/${src}`) : src">
|
||||
</div>
|
||||
</portal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { MountingPortal as Portal } from 'portal-vue'
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from 'vue'
|
||||
|
||||
export default {
|
||||
components: { Portal },
|
||||
export default defineComponent({
|
||||
name: 'FullscreenImage',
|
||||
props: {
|
||||
src: {
|
||||
@@ -27,17 +26,15 @@ export default {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
setup (props) {
|
||||
const isHovered = ref(false)
|
||||
const isBundled = computed(() => !props.src.includes('://'))
|
||||
return {
|
||||
isHovered: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isBundled () {
|
||||
return !this.src.includes('://')
|
||||
isHovered,
|
||||
isBundled
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="postcss" module>
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
<template>
|
||||
<input
|
||||
:value="value"
|
||||
:value="modelValue"
|
||||
@input="update"
|
||||
@mousewheel="noop">
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { debounce } from 'throttle-debounce'
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: 'UiInputDebounced',
|
||||
emits: ['update:modelValue'],
|
||||
props: {
|
||||
value: {},
|
||||
modelValue: {},
|
||||
delay: {
|
||||
type: Number,
|
||||
default: 350
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.update = debounce(this.delay, (e) => {
|
||||
this.$emit('input', e.target.value)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
noop () {
|
||||
// fix not working mouseScroll
|
||||
setup (props, ctx) {
|
||||
return {
|
||||
update: debounce(props.delay, (e: InputEvent) => {
|
||||
ctx.emit('update:modelValue', (e.target as HTMLInputElement).value)
|
||||
}),
|
||||
noop () {
|
||||
// fix not working mouseScroll
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+13
-8
@@ -1,25 +1,30 @@
|
||||
<template>
|
||||
<button
|
||||
@click="updateInput" class="flex items-center" style="height: 1.375rem;">
|
||||
<i v-if="value" class="fas fa-toggle-on pr-1 text-gray-300"></i>
|
||||
<i v-if="modelValue" class="fas fa-toggle-on pr-1 text-gray-300"></i>
|
||||
<i v-else class="fas fa-toggle-off pr-1 text-gray-600"></i>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UiToggle',
|
||||
emits: ['update:modelValue'],
|
||||
props: {
|
||||
value: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateInput () {
|
||||
this.$emit('input', !this.value)
|
||||
setup (props, ctx) {
|
||||
return {
|
||||
updateInput () {
|
||||
ctx.emit('update:modelValue', !props.modelValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user