iceshrimp/src/client/app/desktop/views/components/drive.file.vue
syuilo e1098649b6 ✌️
2018-06-08 11:46:45 +09:00

320 lines
6.3 KiB
Vue

<template>
<div class="root file"
:data-is-selected="isSelected"
:data-is-contextmenu-showing="isContextmenuShowing"
@click="onClick"
draggable="true"
@dragstart="onDragstart"
@dragend="onDragend"
@contextmenu.prevent.stop="onContextmenu"
:title="title"
>
<div class="label" v-if="$store.state.i.avatarId == file.id"><img src="/assets/label.svg"/>
<p>%i18n:@avatar%</p>
</div>
<div class="label" v-if="$store.state.i.bannerId == file.id"><img src="/assets/label.svg"/>
<p>%i18n:@banner%</p>
</div>
<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
<img :src="`${file.url}?thumbnail&size=128`" alt="" @load="onThumbnailLoaded"/>
</div>
<p class="name">
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
<span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span>
</p>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import * as anime from 'animejs';
import contextmenu from '../../api/contextmenu';
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
export default Vue.extend({
props: ['file'],
data() {
return {
isContextmenuShowing: false,
isDragging: false
};
},
computed: {
browser(): any {
return this.$parent;
},
isSelected(): boolean {
return this.browser.selectedFiles.some(f => f.id == this.file.id);
},
title(): string {
return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.datasize)}`;
},
background(): string {
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3
? `rgb(${this.file.properties.avgColor.join(',')})`
: 'transparent';
}
},
methods: {
onClick() {
this.browser.chooseFile(this.file);
},
onContextmenu(e) {
this.isContextmenuShowing = true;
contextmenu((this as any).os)(e, [{
type: 'item',
text: '%i18n:@contextmenu.rename%',
icon: '%fa:i-cursor%',
action: this.rename
}, {
type: 'item',
text: '%i18n:@contextmenu.copy-url%',
icon: '%fa:link%',
action: this.copyUrl
}, {
type: 'link',
href: `${this.file.url}?download`,
text: '%i18n:@contextmenu.download%',
icon: '%fa:download%',
}, null, {
type: 'item',
text: '%i18n:common.delete%',
icon: '%fa:R trash-alt%',
action: this.deleteFile
}, null, {
type: 'nest',
text: '%i18n:@contextmenu.else-files%',
menu: [{
type: 'item',
text: '%i18n:@contextmenu.set-as-avatar%',
action: this.setAsAvatar
}, {
type: 'item',
text: '%i18n:@contextmenu.set-as-banner%',
action: this.setAsBanner
}]
}, {
type: 'nest',
text: '%i18n:@contextmenu.open-in-app%',
menu: [{
type: 'item',
text: '%i18n:@contextmenu.add-app%...',
action: this.addApp
}]
}], {
closed: () => {
this.isContextmenuShowing = false;
}
});
},
onDragstart(e) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('mk_drive_file', JSON.stringify(this.file));
this.isDragging = true;
// 親ブラウザに対して、ドラッグが開始されたフラグを立てる
// (=あなたの子供が、ドラッグを開始しましたよ)
this.browser.isDragSource = true;
},
onDragend(e) {
this.isDragging = false;
this.browser.isDragSource = false;
},
onThumbnailLoaded() {
if (this.file.properties.avgColor && this.file.properties.avgColor.length == 3) {
anime({
targets: this.$refs.thumbnail,
backgroundColor: `rgba(${this.file.properties.avgColor.join(',')}, 0)`,
duration: 100,
easing: 'linear'
});
}
},
rename() {
(this as any).apis.input({
title: '%i18n:@contextmenu.rename-file%',
placeholder: '%i18n:@contextmenu.input-new-file-name%',
default: this.file.name,
allowEmpty: false
}).then(name => {
(this as any).api('drive/files/update', {
fileId: this.file.id,
name: name
})
});
},
copyUrl() {
copyToClipboard(this.file.url);
(this as any).apis.dialog({
title: '%fa:check%%i18n:@contextmenu.copied%',
text: '%i18n:@contextmenu.copied-url-to-clipboard%',
actions: [{
text: '%i18n:common.ok%'
}]
});
},
setAsAvatar() {
(this as any).apis.updateAvatar(this.file);
},
setAsBanner() {
(this as any).apis.updateBanner(this.file);
},
addApp() {
alert('not implemented yet');
},
deleteFile() {
alert('not implemented yet');
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark)
padding 8px 0 0 0
height 180px
border-radius 4px
&, *
cursor pointer
&:hover
background rgba(#000, 0.05)
> .label
&:before
&:after
background #0b65a5
&:active
background rgba(#000, 0.1)
> .label
&:before
&:after
background #0b588c
&[data-is-selected]
background $theme-color
&:hover
background lighten($theme-color, 10%)
&:active
background darken($theme-color, 10%)
> .label
&:before
&:after
display none
> .name
color $theme-color-foreground
&[data-is-contextmenu-showing]
&:after
content ""
pointer-events none
position absolute
top -4px
right -4px
bottom -4px
left -4px
border 2px dashed rgba($theme-color, 0.3)
border-radius 4px
> .label
position absolute
top 0
left 0
pointer-events none
&:before
content ""
display block
position absolute
z-index 1
top 0
left 57px
width 28px
height 8px
background #0c7ac9
&:after
content ""
display block
position absolute
z-index 1
top 57px
left 0
width 8px
height 28px
background #0c7ac9
> img
position absolute
z-index 2
top 0
left 0
> p
position absolute
z-index 3
top 19px
left -28px
width 120px
margin 0
text-align center
line-height 28px
color #fff
transform rotate(-45deg)
> .thumbnail
width 128px
height 128px
margin auto
> img
display block
position absolute
top 0
left 0
right 0
bottom 0
margin auto
max-width 128px
max-height 128px
pointer-events none
> .name
display block
margin 4px 0 0 0
font-size 0.8em
text-align center
word-break break-all
color isDark ? #fff : #444
overflow hidden
> .ext
opacity 0.5
.root.file[data-darkmode]
root(true)
.root.file:not([data-darkmode])
root(false)
</style>