misskey/src/web/app/desktop/tags/window.tag
こぴなたみぽ 9483558372 ✌️
2017-11-13 08:53:38 +09:00

549 lines
14 KiB
HTML

<mk-window data-flexible={ isFlexible } ondragover={ ondragover }>
<div class="bg" ref="bg" show={ isModal } onclick={ bgClick }></div>
<div class="main" ref="main" tabindex="-1" data-is-modal={ isModal } onmousedown={ onBodyMousedown } onkeydown={ onKeydown }>
<div class="body">
<header ref="header" onmousedown={ onHeaderMousedown }>
<h1 data-yield="header"><yield from="header"/></h1>
<div>
<button class="popout" if={ popoutUrl } onmousedown={ repelMove } onclick={ popout } title="ポップアウト"><i class="fa fa-external-link"></i></button>
<button class="close" if={ canClose } onmousedown={ repelMove } onclick={ close } title="閉じる"><i class="fa fa-times"></i></button>
</div>
</header>
<div class="content" data-yield="content"><yield from="content"/></div>
</div>
<div class="handle top" if={ canResize } onmousedown={ onTopHandleMousedown }></div>
<div class="handle right" if={ canResize } onmousedown={ onRightHandleMousedown }></div>
<div class="handle bottom" if={ canResize } onmousedown={ onBottomHandleMousedown }></div>
<div class="handle left" if={ canResize } onmousedown={ onLeftHandleMousedown }></div>
<div class="handle top-left" if={ canResize } onmousedown={ onTopLeftHandleMousedown }></div>
<div class="handle top-right" if={ canResize } onmousedown={ onTopRightHandleMousedown }></div>
<div class="handle bottom-right" if={ canResize } onmousedown={ onBottomRightHandleMousedown }></div>
<div class="handle bottom-left" if={ canResize } onmousedown={ onBottomLeftHandleMousedown }></div>
</div>
<style>
:scope
display block
> .bg
display block
position fixed
z-index 2048
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 2048
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 128
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)
&, *
user-select none
> h1
pointer-events none
display block
margin 0 auto
width s('calc(100% - (%s * 2))', $header-height)
overflow hidden
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 1.2em
color rgba(#000, 0.4)
border none
outline none
background transparent
&:hover
color rgba(#000, 0.6)
&:active
color darken(#000, 30%)
> i
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>
<script>
import anime from 'animejs';
import contains from '../../common/scripts/contains';
this.minHeight = 40;
this.minWidth = 200;
this.isModal = this.opts.isModal != null ? this.opts.isModal : false;
this.canClose = this.opts.canClose != null ? this.opts.canClose : true;
this.popoutUrl = this.opts.popout;
this.isFlexible = this.opts.height == null;
this.canResize = !this.isFlexible;
this.on('mount', () => {
this.refs.main.style.width = this.opts.width || '530px';
this.refs.main.style.height = this.opts.height || 'auto';
this.refs.main.style.top = '15%';
this.refs.main.style.left = (window.innerWidth / 2) - (this.refs.main.offsetWidth / 2) + 'px';
this.refs.header.addEventListener('contextmenu', e => {
e.preventDefault();
});
window.addEventListener('resize', this.onBrowserResize);
this.open();
});
this.on('unmount', () => {
window.removeEventListener('resize', this.onBrowserResize);
});
this.onBrowserResize = () => {
const position = this.refs.main.getBoundingClientRect();
const browserWidth = window.innerWidth;
const browserHeight = window.innerHeight;
const windowWidth = this.refs.main.offsetWidth;
const windowHeight = this.refs.main.offsetHeight;
if (position.left < 0) this.refs.main.style.left = 0;
if (position.top < 0) this.refs.main.style.top = 0;
if (position.left + windowWidth > browserWidth) this.refs.main.style.left = browserWidth - windowWidth + 'px';
if (position.top + windowHeight > browserHeight) this.refs.main.style.top = browserHeight - windowHeight + 'px';
};
this.open = () => {
this.trigger('opening');
this.top();
if (this.isModal) {
this.refs.bg.style.pointerEvents = 'auto';
anime({
targets: this.refs.bg,
opacity: 1,
duration: 100,
easing: 'linear'
});
}
this.refs.main.style.pointerEvents = 'auto';
anime({
targets: this.refs.main,
opacity: 1,
scale: [1.1, 1],
duration: 200,
easing: 'easeOutQuad'
});
//this.refs.main.focus();
setTimeout(() => {
this.trigger('opened');
}, 300);
};
this.popout = () => {
const position = this.refs.main.getBoundingClientRect();
const width = parseInt(getComputedStyle(this.refs.main, '').width, 10);
const left = parseInt(getComputedStyle(this.refs.main, '').left, 10);
const x = window.screenX + position.left;
const y = window.screenY + position.top;
window.open(this.popoutUrl,
this.popoutUrl,
`height=${height},width=${width},left=${x},top=${y}`);
this.close();
};
this.close = () => {
this.trigger('closing');
if (this.isModal) {
this.refs.bg.style.pointerEvents = 'none';
anime({
targets: this.refs.bg,
opacity: 0,
duration: 300,
easing: 'linear'
});
}
this.refs.main.style.pointerEvents = 'none';
anime({
targets: this.refs.main,
opacity: 0,
scale: 0.8,
duration: 300,
easing: [0.5, -0.5, 1, 0.5]
});
setTimeout(() => {
this.trigger('closed');
}, 300);
};
// 最前面へ移動します
this.top = () => {
let z = 0;
const ws = document.querySelectorAll('mk-window');
ws.forEach(w => {
if (w == this.root) return;
const m = w.querySelector(':scope > .main');
const mz = Number(document.defaultView.getComputedStyle(m, null).zIndex);
if (mz > z) z = mz;
});
if (z > 0) {
this.refs.main.style.zIndex = z + 1;
if (this.isModal) this.refs.bg.style.zIndex = z + 1;
}
};
this.repelMove = e => {
e.stopPropagation();
return true;
};
this.bgClick = () => {
if (this.canClose) this.close();
};
this.onBodyMousedown = () => {
this.top();
};
// ヘッダー掴み時
this.onHeaderMousedown = e => {
e.preventDefault();
if (!contains(this.refs.main, document.activeElement)) this.refs.main.focus();
const position = this.refs.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 = this.refs.main.offsetWidth;
const windowHeight = this.refs.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;
this.refs.main.style.left = moveLeft + 'px';
this.refs.main.style.top = moveTop + 'px';
});
};
// 上ハンドル掴み時
this.onTopHandleMousedown = e => {
e.preventDefault();
const base = e.clientY;
const height = parseInt(getComputedStyle(this.refs.main, '').height, 10);
const top = parseInt(getComputedStyle(this.refs.main, '').top, 10);
// 動かした時
dragListen(me => {
const move = me.clientY - base;
if (top + move > 0) {
if (height + -move > this.minHeight) {
this.applyTransformHeight(height + -move);
this.applyTransformTop(top + move);
} else { // 最小の高さより小さくなろうとした時
this.applyTransformHeight(this.minHeight);
this.applyTransformTop(top + (height - this.minHeight));
}
} else { // 上のはみ出し時
this.applyTransformHeight(top + height);
this.applyTransformTop(0);
}
});
};
// 右ハンドル掴み時
this.onRightHandleMousedown = e => {
e.preventDefault();
const base = e.clientX;
const width = parseInt(getComputedStyle(this.refs.main, '').width, 10);
const left = parseInt(getComputedStyle(this.refs.main, '').left, 10);
const browserWidth = window.innerWidth;
// 動かした時
dragListen(me => {
const move = me.clientX - base;
if (left + width + move < browserWidth) {
if (width + move > this.minWidth) {
this.applyTransformWidth(width + move);
} else { // 最小の幅より小さくなろうとした時
this.applyTransformWidth(this.minWidth);
}
} else { // 右のはみ出し時
this.applyTransformWidth(browserWidth - left);
}
});
};
// 下ハンドル掴み時
this.onBottomHandleMousedown = e => {
e.preventDefault();
const base = e.clientY;
const height = parseInt(getComputedStyle(this.refs.main, '').height, 10);
const top = parseInt(getComputedStyle(this.refs.main, '').top, 10);
const browserHeight = window.innerHeight;
// 動かした時
dragListen(me => {
const move = me.clientY - base;
if (top + height + move < browserHeight) {
if (height + move > this.minHeight) {
this.applyTransformHeight(height + move);
} else { // 最小の高さより小さくなろうとした時
this.applyTransformHeight(this.minHeight);
}
} else { // 下のはみ出し時
this.applyTransformHeight(browserHeight - top);
}
});
};
// 左ハンドル掴み時
this.onLeftHandleMousedown = e => {
e.preventDefault();
const base = e.clientX;
const width = parseInt(getComputedStyle(this.refs.main, '').width, 10);
const left = parseInt(getComputedStyle(this.refs.main, '').left, 10);
// 動かした時
dragListen(me => {
const move = me.clientX - base;
if (left + move > 0) {
if (width + -move > this.minWidth) {
this.applyTransformWidth(width + -move);
this.applyTransformLeft(left + move);
} else { // 最小の幅より小さくなろうとした時
this.applyTransformWidth(this.minWidth);
this.applyTransformLeft(left + (width - this.minWidth));
}
} else { // 左のはみ出し時
this.applyTransformWidth(left + width);
this.applyTransformLeft(0);
}
});
};
// 左上ハンドル掴み時
this.onTopLeftHandleMousedown = e => {
this.onTopHandleMousedown(e);
this.onLeftHandleMousedown(e);
};
// 右上ハンドル掴み時
this.onTopRightHandleMousedown = e => {
this.onTopHandleMousedown(e);
this.onRightHandleMousedown(e);
};
// 右下ハンドル掴み時
this.onBottomRightHandleMousedown = e => {
this.onBottomHandleMousedown(e);
this.onRightHandleMousedown(e);
};
// 左下ハンドル掴み時
this.onBottomLeftHandleMousedown = e => {
this.onBottomHandleMousedown(e);
this.onLeftHandleMousedown(e);
};
// 高さを適用
this.applyTransformHeight = height => {
this.refs.main.style.height = height + 'px';
};
// 幅を適用
this.applyTransformWidth = width => {
this.refs.main.style.width = width + 'px';
};
// Y座標を適用
this.applyTransformTop = top => {
this.refs.main.style.top = top + 'px';
};
// X座標を適用
this.applyTransformLeft = left => {
this.refs.main.style.left = left + 'px';
};
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);
}
this.ondragover = e => {
e.dataTransfer.dropEffect = 'none';
};
this.onKeydown = e => {
if (e.which == 27) { // Esc
if (this.canClose) {
e.preventDefault();
e.stopPropagation();
this.close();
}
}
};
</script>
</mk-window>