整理した
This commit is contained in:
635
src/client/app/desktop/views/components/window.vue
Normal file
635
src/client/app/desktop/views/components/window.vue
Normal file
@ -0,0 +1,635 @@
|
||||
<template>
|
||||
<div class="mk-window" :data-flexible="isFlexible" @dragover="onDragover">
|
||||
<div class="bg" ref="bg" v-show="isModal" @click="onBgClick"></div>
|
||||
<div class="main" ref="main" tabindex="-1" :data-is-modal="isModal" @mousedown="onBodyMousedown" @keydown="onKeydown" :style="{ width, height }">
|
||||
<div class="body">
|
||||
<header ref="header"
|
||||
:class="{ withGradient }"
|
||||
@contextmenu.prevent="() => {}" @mousedown.prevent="onHeaderMousedown"
|
||||
>
|
||||
<h1><slot name="header"></slot></h1>
|
||||
<div>
|
||||
<button class="popout" v-if="popoutUrl" @mousedown.stop="() => {}" @click="popout" title="ポップアウト">%fa:R window-restore%</button>
|
||||
<button class="close" v-if="canClose" @mousedown.stop="() => {}" @click="close" title="閉じる">%fa:times%</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="handle top" v-if="canResize" @mousedown.prevent="onTopHandleMousedown"></div>
|
||||
<div class="handle right" v-if="canResize" @mousedown.prevent="onRightHandleMousedown"></div>
|
||||
<div class="handle bottom" v-if="canResize" @mousedown.prevent="onBottomHandleMousedown"></div>
|
||||
<div class="handle left" v-if="canResize" @mousedown.prevent="onLeftHandleMousedown"></div>
|
||||
<div class="handle top-left" v-if="canResize" @mousedown.prevent="onTopLeftHandleMousedown"></div>
|
||||
<div class="handle top-right" v-if="canResize" @mousedown.prevent="onTopRightHandleMousedown"></div>
|
||||
<div class="handle bottom-right" v-if="canResize" @mousedown.prevent="onBottomRightHandleMousedown"></div>
|
||||
<div class="handle bottom-left" v-if="canResize" @mousedown.prevent="onBottomLeftHandleMousedown"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as anime from 'animejs';
|
||||
import contains from '../../../common/scripts/contains';
|
||||
|
||||
const minHeight = 40;
|
||||
const minWidth = 200;
|
||||
|
||||
function dragListen(fn) {
|
||||
window.addEventListener('mousemove', fn);
|
||||
window.addEventListener('mouseleave', dragClear.bind(null, fn));
|
||||
window.addEventListener('mouseup', dragClear.bind(null, fn));
|
||||
}
|
||||
|
||||
function dragClear(fn) {
|
||||
window.removeEventListener('mousemove', fn);
|
||||
window.removeEventListener('mouseleave', dragClear);
|
||||
window.removeEventListener('mouseup', dragClear);
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
isModal: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
canClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '530px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
popoutUrl: {
|
||||
type: [String, Function],
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
preventMount: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isFlexible(): boolean {
|
||||
return this.height == null;
|
||||
},
|
||||
canResize(): boolean {
|
||||
return !this.isFlexible;
|
||||
},
|
||||
withGradient(): boolean {
|
||||
return (this as any).os.isSignedIn
|
||||
? (this as any).os.i.account.clientSettings.gradientWindowHeader != null
|
||||
? (this as any).os.i.account.clientSettings.gradientWindowHeader
|
||||
: false
|
||||
: false;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if (localStorage.getItem('autoPopout') == 'true' && this.popoutUrl) {
|
||||
this.popout();
|
||||
this.preventMount = true;
|
||||
} else {
|
||||
// ウィンドウをウィンドウシステムに登録
|
||||
(this as any).os.windows.add(this);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.preventMount) {
|
||||
this.$destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
const main = this.$refs.main as any;
|
||||
main.style.top = '15%';
|
||||
main.style.left = (window.innerWidth / 2) - (main.offsetWidth / 2) + 'px';
|
||||
|
||||
window.addEventListener('resize', this.onBrowserResize);
|
||||
|
||||
this.open();
|
||||
});
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
// ウィンドウをウィンドウシステムから削除
|
||||
(this as any).os.windows.remove(this);
|
||||
|
||||
window.removeEventListener('resize', this.onBrowserResize);
|
||||
},
|
||||
|
||||
methods: {
|
||||
open() {
|
||||
this.$emit('opening');
|
||||
|
||||
this.top();
|
||||
|
||||
const bg = this.$refs.bg as any;
|
||||
const main = this.$refs.main as any;
|
||||
|
||||
if (this.isModal) {
|
||||
bg.style.pointerEvents = 'auto';
|
||||
anime({
|
||||
targets: bg,
|
||||
opacity: 1,
|
||||
duration: 100,
|
||||
easing: 'linear'
|
||||
});
|
||||
}
|
||||
|
||||
main.style.pointerEvents = 'auto';
|
||||
anime({
|
||||
targets: main,
|
||||
opacity: 1,
|
||||
scale: [1.1, 1],
|
||||
duration: 200,
|
||||
easing: 'easeOutQuad'
|
||||
});
|
||||
|
||||
if (focus) main.focus();
|
||||
|
||||
setTimeout(() => {
|
||||
this.$emit('opened');
|
||||
}, 300);
|
||||
},
|
||||
|
||||
close() {
|
||||
this.$emit('before-close');
|
||||
|
||||
const bg = this.$refs.bg as any;
|
||||
const main = this.$refs.main as any;
|
||||
|
||||
if (this.isModal) {
|
||||
bg.style.pointerEvents = 'none';
|
||||
anime({
|
||||
targets: bg,
|
||||
opacity: 0,
|
||||
duration: 300,
|
||||
easing: 'linear'
|
||||
});
|
||||
}
|
||||
|
||||
main.style.pointerEvents = 'none';
|
||||
|
||||
anime({
|
||||
targets: main,
|
||||
opacity: 0,
|
||||
scale: 0.8,
|
||||
duration: 300,
|
||||
easing: [0.5, -0.5, 1, 0.5]
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.$destroy();
|
||||
this.$emit('closed');
|
||||
}, 300);
|
||||
},
|
||||
|
||||
popout() {
|
||||
const url = typeof this.popoutUrl == 'function' ? this.popoutUrl() : this.popoutUrl;
|
||||
|
||||
const main = this.$refs.main as any;
|
||||
|
||||
if (main) {
|
||||
const position = main.getBoundingClientRect();
|
||||
|
||||
const width = parseInt(getComputedStyle(main, '').width, 10);
|
||||
const height = parseInt(getComputedStyle(main, '').height, 10);
|
||||
const x = window.screenX + position.left;
|
||||
const y = window.screenY + position.top;
|
||||
|
||||
window.open(url, url,
|
||||
`width=${width}, height=${height}, top=${y}, left=${x}`);
|
||||
|
||||
this.close();
|
||||
} else {
|
||||
const x = window.top.outerHeight / 2 + window.top.screenY - (parseInt(this.height, 10) / 2);
|
||||
const y = window.top.outerWidth / 2 + window.top.screenX - (parseInt(this.width, 10) / 2);
|
||||
window.open(url, url,
|
||||
`width=${this.width}, height=${this.height}, top=${x}, left=${y}`);
|
||||
}
|
||||
},
|
||||
|
||||
// 最前面へ移動
|
||||
top() {
|
||||
let z = 0;
|
||||
|
||||
(this as any).os.windows.getAll().forEach(w => {
|
||||
if (w == this) return;
|
||||
const m = w.$refs.main;
|
||||
const mz = Number(document.defaultView.getComputedStyle(m, null).zIndex);
|
||||
if (mz > z) z = mz;
|
||||
});
|
||||
|
||||
if (z > 0) {
|
||||
(this.$refs.main as any).style.zIndex = z + 1;
|
||||
if (this.isModal) (this.$refs.bg as any).style.zIndex = z + 1;
|
||||
}
|
||||
},
|
||||
|
||||
onBgClick() {
|
||||
if (this.canClose) this.close();
|
||||
},
|
||||
|
||||
onBodyMousedown() {
|
||||
this.top();
|
||||
},
|
||||
|
||||
onHeaderMousedown(e) {
|
||||
const main = this.$refs.main as any;
|
||||
|
||||
if (!contains(main, document.activeElement)) main.focus();
|
||||
|
||||
const position = main.getBoundingClientRect();
|
||||
|
||||
const clickX = e.clientX;
|
||||
const clickY = e.clientY;
|
||||
const moveBaseX = clickX - position.left;
|
||||
const moveBaseY = clickY - position.top;
|
||||
const browserWidth = window.innerWidth;
|
||||
const browserHeight = window.innerHeight;
|
||||
const windowWidth = main.offsetWidth;
|
||||
const windowHeight = main.offsetHeight;
|
||||
|
||||
// 動かした時
|
||||
dragListen(me => {
|
||||
let moveLeft = me.clientX - moveBaseX;
|
||||
let moveTop = me.clientY - moveBaseY;
|
||||
|
||||
// 上はみ出し
|
||||
if (moveTop < 0) moveTop = 0;
|
||||
|
||||
// 左はみ出し
|
||||
if (moveLeft < 0) moveLeft = 0;
|
||||
|
||||
// 下はみ出し
|
||||
if (moveTop + windowHeight > browserHeight) moveTop = browserHeight - windowHeight;
|
||||
|
||||
// 右はみ出し
|
||||
if (moveLeft + windowWidth > browserWidth) moveLeft = browserWidth - windowWidth;
|
||||
|
||||
main.style.left = moveLeft + 'px';
|
||||
main.style.top = moveTop + 'px';
|
||||
});
|
||||
},
|
||||
|
||||
// 上ハンドル掴み時
|
||||
onTopHandleMousedown(e) {
|
||||
const main = this.$refs.main as any;
|
||||
|
||||
const base = e.clientY;
|
||||
const height = parseInt(getComputedStyle(main, '').height, 10);
|
||||
const top = parseInt(getComputedStyle(main, '').top, 10);
|
||||
|
||||
// 動かした時
|
||||
dragListen(me => {
|
||||
const move = me.clientY - base;
|
||||
if (top + move > 0) {
|
||||
if (height + -move > minHeight) {
|
||||
this.applyTransformHeight(height + -move);
|
||||
this.applyTransformTop(top + move);
|
||||
} else { // 最小の高さより小さくなろうとした時
|
||||
this.applyTransformHeight(minHeight);
|
||||
this.applyTransformTop(top + (height - minHeight));
|
||||
}
|
||||
} else { // 上のはみ出し時
|
||||
this.applyTransformHeight(top + height);
|
||||
this.applyTransformTop(0);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 右ハンドル掴み時
|
||||
onRightHandleMousedown(e) {
|
||||
const main = this.$refs.main as any;
|
||||
|
||||
const base = e.clientX;
|
||||
const width = parseInt(getComputedStyle(main, '').width, 10);
|
||||
const left = parseInt(getComputedStyle(main, '').left, 10);
|
||||
const browserWidth = window.innerWidth;
|
||||
|
||||
// 動かした時
|
||||
dragListen(me => {
|
||||
const move = me.clientX - base;
|
||||
if (left + width + move < browserWidth) {
|
||||
if (width + move > minWidth) {
|
||||
this.applyTransformWidth(width + move);
|
||||
} else { // 最小の幅より小さくなろうとした時
|
||||
this.applyTransformWidth(minWidth);
|
||||
}
|
||||
} else { // 右のはみ出し時
|
||||
this.applyTransformWidth(browserWidth - left);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 下ハンドル掴み時
|
||||
onBottomHandleMousedown(e) {
|
||||
const main = this.$refs.main as any;
|
||||
|
||||
const base = e.clientY;
|
||||
const height = parseInt(getComputedStyle(main, '').height, 10);
|
||||
const top = parseInt(getComputedStyle(main, '').top, 10);
|
||||
const browserHeight = window.innerHeight;
|
||||
|
||||
// 動かした時
|
||||
dragListen(me => {
|
||||
const move = me.clientY - base;
|
||||
if (top + height + move < browserHeight) {
|
||||
if (height + move > minHeight) {
|
||||
this.applyTransformHeight(height + move);
|
||||
} else { // 最小の高さより小さくなろうとした時
|
||||
this.applyTransformHeight(minHeight);
|
||||
}
|
||||
} else { // 下のはみ出し時
|
||||
this.applyTransformHeight(browserHeight - top);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 左ハンドル掴み時
|
||||
onLeftHandleMousedown(e) {
|
||||
const main = this.$refs.main as any;
|
||||
|
||||
const base = e.clientX;
|
||||
const width = parseInt(getComputedStyle(main, '').width, 10);
|
||||
const left = parseInt(getComputedStyle(main, '').left, 10);
|
||||
|
||||
// 動かした時
|
||||
dragListen(me => {
|
||||
const move = me.clientX - base;
|
||||
if (left + move > 0) {
|
||||
if (width + -move > minWidth) {
|
||||
this.applyTransformWidth(width + -move);
|
||||
this.applyTransformLeft(left + move);
|
||||
} else { // 最小の幅より小さくなろうとした時
|
||||
this.applyTransformWidth(minWidth);
|
||||
this.applyTransformLeft(left + (width - minWidth));
|
||||
}
|
||||
} else { // 左のはみ出し時
|
||||
this.applyTransformWidth(left + width);
|
||||
this.applyTransformLeft(0);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 左上ハンドル掴み時
|
||||
onTopLeftHandleMousedown(e) {
|
||||
this.onTopHandleMousedown(e);
|
||||
this.onLeftHandleMousedown(e);
|
||||
},
|
||||
|
||||
// 右上ハンドル掴み時
|
||||
onTopRightHandleMousedown(e) {
|
||||
this.onTopHandleMousedown(e);
|
||||
this.onRightHandleMousedown(e);
|
||||
},
|
||||
|
||||
// 右下ハンドル掴み時
|
||||
onBottomRightHandleMousedown(e) {
|
||||
this.onBottomHandleMousedown(e);
|
||||
this.onRightHandleMousedown(e);
|
||||
},
|
||||
|
||||
// 左下ハンドル掴み時
|
||||
onBottomLeftHandleMousedown(e) {
|
||||
this.onBottomHandleMousedown(e);
|
||||
this.onLeftHandleMousedown(e);
|
||||
},
|
||||
|
||||
// 高さを適用
|
||||
applyTransformHeight(height) {
|
||||
(this.$refs.main as any).style.height = height + 'px';
|
||||
},
|
||||
|
||||
// 幅を適用
|
||||
applyTransformWidth(width) {
|
||||
(this.$refs.main as any).style.width = width + 'px';
|
||||
},
|
||||
|
||||
// Y座標を適用
|
||||
applyTransformTop(top) {
|
||||
(this.$refs.main as any).style.top = top + 'px';
|
||||
},
|
||||
|
||||
// X座標を適用
|
||||
applyTransformLeft(left) {
|
||||
(this.$refs.main as any).style.left = left + 'px';
|
||||
},
|
||||
|
||||
onDragover(e) {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
},
|
||||
|
||||
onKeydown(e) {
|
||||
if (e.which == 27) { // Esc
|
||||
if (this.canClose) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onBrowserResize() {
|
||||
const main = this.$refs.main as any;
|
||||
const position = main.getBoundingClientRect();
|
||||
const browserWidth = window.innerWidth;
|
||||
const browserHeight = window.innerHeight;
|
||||
const windowWidth = main.offsetWidth;
|
||||
const windowHeight = main.offsetHeight;
|
||||
if (position.left < 0) main.style.left = 0;
|
||||
if (position.top < 0) main.style.top = 0;
|
||||
if (position.left + windowWidth > browserWidth) main.style.left = browserWidth - windowWidth + 'px';
|
||||
if (position.top + windowHeight > browserHeight) main.style.top = browserHeight - windowHeight + 'px';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
.mk-window
|
||||
display block
|
||||
|
||||
> .bg
|
||||
display block
|
||||
position fixed
|
||||
z-index 2000
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
background rgba(0, 0, 0, 0.7)
|
||||
opacity 0
|
||||
pointer-events none
|
||||
|
||||
> .main
|
||||
display block
|
||||
position fixed
|
||||
z-index 2000
|
||||
top 15%
|
||||
left 0
|
||||
margin 0
|
||||
opacity 0
|
||||
pointer-events none
|
||||
|
||||
&:focus
|
||||
&:not([data-is-modal])
|
||||
> .body
|
||||
box-shadow 0 0 0px 1px rgba($theme-color, 0.5), 0 2px 6px 0 rgba(0, 0, 0, 0.2)
|
||||
|
||||
> .handle
|
||||
$size = 8px
|
||||
|
||||
position absolute
|
||||
|
||||
&.top
|
||||
top -($size)
|
||||
left 0
|
||||
width 100%
|
||||
height $size
|
||||
cursor ns-resize
|
||||
|
||||
&.right
|
||||
top 0
|
||||
right -($size)
|
||||
width $size
|
||||
height 100%
|
||||
cursor ew-resize
|
||||
|
||||
&.bottom
|
||||
bottom -($size)
|
||||
left 0
|
||||
width 100%
|
||||
height $size
|
||||
cursor ns-resize
|
||||
|
||||
&.left
|
||||
top 0
|
||||
left -($size)
|
||||
width $size
|
||||
height 100%
|
||||
cursor ew-resize
|
||||
|
||||
&.top-left
|
||||
top -($size)
|
||||
left -($size)
|
||||
width $size * 2
|
||||
height $size * 2
|
||||
cursor nwse-resize
|
||||
|
||||
&.top-right
|
||||
top -($size)
|
||||
right -($size)
|
||||
width $size * 2
|
||||
height $size * 2
|
||||
cursor nesw-resize
|
||||
|
||||
&.bottom-right
|
||||
bottom -($size)
|
||||
right -($size)
|
||||
width $size * 2
|
||||
height $size * 2
|
||||
cursor nwse-resize
|
||||
|
||||
&.bottom-left
|
||||
bottom -($size)
|
||||
left -($size)
|
||||
width $size * 2
|
||||
height $size * 2
|
||||
cursor nesw-resize
|
||||
|
||||
> .body
|
||||
height 100%
|
||||
overflow hidden
|
||||
background #fff
|
||||
border-radius 6px
|
||||
box-shadow 0 2px 6px 0 rgba(0, 0, 0, 0.2)
|
||||
|
||||
> header
|
||||
$header-height = 40px
|
||||
|
||||
z-index 1001
|
||||
height $header-height
|
||||
overflow hidden
|
||||
white-space nowrap
|
||||
cursor move
|
||||
background #fff
|
||||
border-radius 6px 6px 0 0
|
||||
box-shadow 0 1px 0 rgba(#000, 0.1)
|
||||
|
||||
&.withGradient
|
||||
background linear-gradient(to bottom, #fff, #ececec)
|
||||
box-shadow 0 1px 0 rgba(#000, 0.15)
|
||||
|
||||
&, *
|
||||
user-select none
|
||||
|
||||
> h1
|
||||
pointer-events none
|
||||
display block
|
||||
margin 0 auto
|
||||
overflow hidden
|
||||
height $header-height
|
||||
text-overflow ellipsis
|
||||
text-align center
|
||||
font-size 1em
|
||||
line-height $header-height
|
||||
font-weight normal
|
||||
color #666
|
||||
|
||||
> div:last-child
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
display block
|
||||
z-index 1
|
||||
|
||||
> *
|
||||
display inline-block
|
||||
margin 0
|
||||
padding 0
|
||||
cursor pointer
|
||||
font-size 1em
|
||||
color rgba(#000, 0.4)
|
||||
border none
|
||||
outline none
|
||||
background transparent
|
||||
|
||||
&:hover
|
||||
color rgba(#000, 0.6)
|
||||
|
||||
&:active
|
||||
color darken(#000, 30%)
|
||||
|
||||
> [data-fa]
|
||||
padding 0
|
||||
width $header-height
|
||||
line-height $header-height
|
||||
text-align center
|
||||
|
||||
> .content
|
||||
height 100%
|
||||
|
||||
&:not([flexible])
|
||||
> .main > .body > .content
|
||||
height calc(100% - 40px)
|
||||
|
||||
</style>
|
Reference in New Issue
Block a user