diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2018-02-18 23:51:41 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2018-02-18 23:51:41 +0900 |
| commit | e01b9d3f16dacb6504c69a65b2f3c3e0f85c4094 (patch) | |
| tree | c503ed5aea35864ee8d46804039727cf309e4a67 /src/web/app/desktop/views/components/widgets | |
| parent | wip (diff) | |
| download | misskey-e01b9d3f16dacb6504c69a65b2f3c3e0f85c4094.tar.gz misskey-e01b9d3f16dacb6504c69a65b2f3c3e0f85c4094.tar.bz2 misskey-e01b9d3f16dacb6504c69a65b2f3c3e0f85c4094.zip | |
wip
Diffstat (limited to 'src/web/app/desktop/views/components/widgets')
9 files changed, 845 insertions, 1 deletions
diff --git a/src/web/app/desktop/views/components/widgets/calendar.vue b/src/web/app/desktop/views/components/widgets/calendar.vue new file mode 100644 index 0000000000..8574bf59f9 --- /dev/null +++ b/src/web/app/desktop/views/components/widgets/calendar.vue @@ -0,0 +1,192 @@ +<template> +<div class="mkw-calendar" + :data-melt="props.design == 1" + :data-special="special" +> + <div class="calendar" :data-is-holiday="isHoliday"> + <p class="month-and-year"> + <span class="year">{{ year }}年</span> + <span class="month">{{ month }}月</span> + </p> + <p class="day">{{ day }}日</p> + <p class="week-day">{{ weekDay }}曜日</p> + </div> + <div class="info"> + <div> + <p>今日:<b>{{ dayP.toFixed(1) }}%</b></p> + <div class="meter"> + <div class="val" :style="{ width: `${dayP}%` }"></div> + </div> + </div> + <div> + <p>今月:<b>{{ monthP.toFixed(1) }}%</b></p> + <div class="meter"> + <div class="val" :style="{ width: `${monthP}%` }"></div> + </div> + </div> + <div> + <p>今年:<b>{{ yearP.toFixed(1) }}%</b></p> + <div class="meter"> + <div class="val" :style="{ width: `${yearP}%` }"></div> + </div> + </div> + </div> +</div> +</template> + +<script lang="ts"> +import define from '../../../../common/define-widget'; +export default define({ + name: 'calendar', + props: { + design: 0 + } +}).extend({ + data() { + return { + now: new Date(), + year: null, + month: null, + day: null, + weekDay: null, + yearP: null, + dayP: null, + monthP: null, + isHoliday: null, + special: null, + clock: null + }; + }, + created() { + this.tick(); + this.clock = setInterval(this.tick, 1000); + }, + beforeDestroy() { + clearInterval(this.clock); + }, + methods: { + func() { + if (this.props.design == 2) { + this.props.design = 0; + } else { + this.props.design++; + } + }, + tick() { + const now = new Date(); + const nd = now.getDate(); + const nm = now.getMonth(); + const ny = now.getFullYear(); + + this.year = ny; + this.month = nm + 1; + this.day = nd; + this.weekDay = ['日', '月', '火', '水', '木', '金', '土'][now.getDay()]; + + const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime(); + const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/; + const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime(); + const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime(); + const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime(); + const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime(); + + this.dayP = dayNumer / dayDenom * 100; + this.monthP = monthNumer / monthDenom * 100; + this.yearP = yearNumer / yearDenom * 100; + + this.isHoliday = now.getDay() == 0 || now.getDay() == 6; + + this.special = + nm == 0 && nd == 1 ? 'on-new-years-day' : + false; + } + } +}); +</script> + +<style lang="stylus" scoped> +.mkw-calendar + padding 16px 0 + color #777 + background #fff + border solid 1px rgba(0, 0, 0, 0.075) + border-radius 6px + + &[data-special='on-new-years-day'] + border-color #ef95a0 + + &[data-melt] + background transparent + border none + + &:after + content "" + display block + clear both + + > .calendar + float left + width 60% + text-align center + + &[data-is-holiday] + > .day + color #ef95a0 + + > p + margin 0 + line-height 18px + font-size 14px + + > span + margin 0 4px + + > .day + margin 10px 0 + line-height 32px + font-size 28px + + > .info + display block + float left + width 40% + padding 0 16px 0 0 + + > div + margin-bottom 8px + + &:last-child + margin-bottom 4px + + > p + margin 0 0 2px 0 + font-size 12px + line-height 18px + color #888 + + > b + margin-left 2px + + > .meter + width 100% + overflow hidden + background #eee + border-radius 8px + + > .val + height 4px + background $theme-color + + &:nth-child(1) + > .meter > .val + background #f7796c + + &:nth-child(2) + > .meter > .val + background #a1de41 + + &:nth-child(3) + > .meter > .val + background #41ddde + +</style> diff --git a/src/web/app/desktop/views/components/widgets/donation.vue b/src/web/app/desktop/views/components/widgets/donation.vue new file mode 100644 index 0000000000..b3e0658a4b --- /dev/null +++ b/src/web/app/desktop/views/components/widgets/donation.vue @@ -0,0 +1,45 @@ +<template> +<div class="mkw-donation"> + <article> + <h1>%fa:heart%%i18n:desktop.tags.mk-donation-home-widget.title%</h1> + <p> + {{ '%i18n:desktop.tags.mk-donation-home-widget.text%'.substr(0, '%i18n:desktop.tags.mk-donation-home-widget.text%'.indexOf('{')) }} + <a href="/syuilo" data-user-preview="@syuilo">@syuilo</a> + {{ '%i18n:desktop.tags.mk-donation-home-widget.text%'.substr('%i18n:desktop.tags.mk-donation-home-widget.text%'.indexOf('}') + 1) }} + </p> + </article> +</div> +</template> + +<script lang="ts"> +import define from '../../../../common/define-widget'; +export default define({ + name: 'donation' +}); +</script> + +<style lang="stylus" scoped> +.mkw-donation + background #fff + border solid 1px #ead8bb + border-radius 6px + + > article + padding 20px + + > h1 + margin 0 0 5px 0 + font-size 1em + color #888 + + > [data-fa] + margin-right 0.25em + + > p + display block + z-index 1 + margin 0 + font-size 0.8em + color #999 + +</style> diff --git a/src/web/app/desktop/views/components/widgets/messaging.vue b/src/web/app/desktop/views/components/widgets/messaging.vue index f31acc5c63..733989b782 100644 --- a/src/web/app/desktop/views/components/widgets/messaging.vue +++ b/src/web/app/desktop/views/components/widgets/messaging.vue @@ -6,7 +6,7 @@ </template> <script lang="ts"> -import define from '../../../define-widget'; +import define from '../../../../common/define-widget'; export default define({ name: 'messaging', props: { diff --git a/src/web/app/desktop/views/components/widgets/nav.vue b/src/web/app/desktop/views/components/widgets/nav.vue new file mode 100644 index 0000000000..a782ad62bf --- /dev/null +++ b/src/web/app/desktop/views/components/widgets/nav.vue @@ -0,0 +1,29 @@ +<template> +<div class="mkw-nav"> + <mk-nav-links/> +</div> +</template> + +<script lang="ts"> +import define from '../../../../common/define-widget'; +export default define({ + name: 'nav' +}); +</script> + +<style lang="stylus" scoped> +.mkw-nav + padding 16px + font-size 12px + color #aaa + background #fff + border solid 1px rgba(0, 0, 0, 0.075) + border-radius 6px + + a + color #999 + + i + color #ccc + +</style> diff --git a/src/web/app/desktop/views/components/widgets/notifications.vue b/src/web/app/desktop/views/components/widgets/notifications.vue new file mode 100644 index 0000000000..2d613fa232 --- /dev/null +++ b/src/web/app/desktop/views/components/widgets/notifications.vue @@ -0,0 +1,70 @@ +<template> +<div class="mkw-notifications"> + <template v-if="!props.compact"> + <p class="title">%fa:R bell%%i18n:desktop.tags.mk-notifications-home-widget.title%</p> + <button @click="settings" title="%i18n:desktop.tags.mk-notifications-home-widget.settings%">%fa:cog%</button> + </template> + <mk-notifications/> +</div> +</template> + +<script lang="ts"> +import define from '../../../../common/define-widget'; +export default define({ + name: 'notifications', + props: { + compact: false + } +}).extend({ + methods: { + settings() { + alert('not implemented yet'); + }, + func() { + this.props.compact = !this.props.compact; + } + } +}); +</script> + +<style lang="stylus" scoped> +.mkw-notifications + background #fff + border solid 1px rgba(0, 0, 0, 0.075) + border-radius 6px + + > .title + z-index 1 + margin 0 + padding 0 16px + line-height 42px + font-size 0.9em + font-weight bold + color #888 + box-shadow 0 1px rgba(0, 0, 0, 0.07) + + > [data-fa] + margin-right 4px + + > button + position absolute + z-index 2 + top 0 + right 0 + padding 0 + width 42px + font-size 0.9em + line-height 42px + color #ccc + + &:hover + color #aaa + + &:active + color #999 + + > .mk-notifications + max-height 300px + overflow auto + +</style> diff --git a/src/web/app/desktop/views/components/widgets/photo-stream.vue b/src/web/app/desktop/views/components/widgets/photo-stream.vue new file mode 100644 index 0000000000..a3f37e8c7e --- /dev/null +++ b/src/web/app/desktop/views/components/widgets/photo-stream.vue @@ -0,0 +1,122 @@ +<template> +<div class="mkw-photo-stream" :data-melt="props.design == 2"> + <p class="title" v-if="props.design == 0">%fa:camera%%i18n:desktop.tags.mk-photo-stream-home-widget.title%</p> + <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> + <div class="stream" v-if="!fetching && images.length > 0"> + <div v-for="image in images" :key="image.id" class="img" :style="`background-image: url(${image.url}?thumbnail&size=256)`"></div> + </div> + <p class="empty" v-if="!fetching && images.length == 0">%i18n:desktop.tags.mk-photo-stream-home-widget.no-photos%</p> +</div> +</template> + +<script lang="ts"> +import define from '../../../../common/define-widget'; +export default define({ + name: 'photo-stream', + props: { + design: 0 + } +}).extend({ + data() { + return { + images: [], + fetching: true, + connection: null, + connectionId: null + }; + }, + mounted() { + this.connection = (this as any).os.stream.getConnection(); + this.connectionId = (this as any).os.stream.use(); + + this.connection.on('drive_file_created', this.onDriveFileCreated); + + (this as any).api('drive/stream', { + type: 'image/*', + limit: 9 + }).then(images => { + this.fetching = false; + this.images = images; + }); + }, + beforeDestroy() { + this.connection.off('drive_file_created', this.onDriveFileCreated); + (this as any).os.stream.dispose(this.connectionId); + }, + methods: { + onDriveFileCreated(file) { + if (/^image\/.+$/.test(file.type)) { + this.images.unshift(file); + if (this.images.length > 9) this.images.pop(); + } + }, + func() { + if (this.props.design == 2) { + this.props.design = 0; + } else { + this.props.design++; + } + } + } +}); +</script> + +<style lang="stylus" scoped> +.mkw-photo-stream + background #fff + border solid 1px rgba(0, 0, 0, 0.075) + border-radius 6px + + &[data-melt] + background transparent !important + border none !important + + > .stream + padding 0 + + > .img + border solid 4px transparent + border-radius 8px + + > .title + z-index 1 + margin 0 + padding 0 16px + line-height 42px + font-size 0.9em + font-weight bold + color #888 + box-shadow 0 1px rgba(0, 0, 0, 0.07) + + > [data-fa] + margin-right 4px + + > .stream + display -webkit-flex + display -moz-flex + display -ms-flex + display flex + justify-content center + flex-wrap wrap + padding 8px + + > .img + flex 1 1 33% + width 33% + height 80px + background-position center center + background-size cover + border solid 2px transparent + border-radius 4px + + > .fetching + > .empty + margin 0 + padding 16px + text-align center + color #aaa + + > [data-fa] + margin-right 4px + +</style> diff --git a/src/web/app/desktop/views/components/widgets/profile.vue b/src/web/app/desktop/views/components/widgets/profile.vue new file mode 100644 index 0000000000..9a0d40a5c0 --- /dev/null +++ b/src/web/app/desktop/views/components/widgets/profile.vue @@ -0,0 +1,125 @@ +<template> +<div class="mkw-profile" + :data-compact="props.design == 1 || props.design == 2" + :data-melt="props.design == 2" +> + <div class="banner" + style={ I.banner_url ? 'background-image: url(' + I.banner_url + '?thumbnail&size=256)' : '' } + title="クリックでバナー編集" + @click="wapi_setBanner" + ></div> + <img class="avatar" + src={ I.avatar_url + '?thumbnail&size=96' } + @click="wapi_setAvatar" + alt="avatar" + title="クリックでアバター編集" + v-user-preview={ I.id } + /> + <a class="name" href={ '/' + I.username }>{ I.name }</a> + <p class="username">@{ I.username }</p> +</div> +</template> + +<script lang="ts"> +import define from '../../../../common/define-widget'; +export default define({ + name: 'profile', + props: { + design: 0 + } +}).extend({ + methods: { + func() { + if (this.props.design == 2) { + this.props.design = 0; + } else { + this.props.design++; + } + } + } +}); +</script> + +<style lang="stylus" scoped> +.mkw-profile + overflow hidden + background #fff + border solid 1px rgba(0, 0, 0, 0.075) + border-radius 6px + + &[data-compact] + > .banner:before + content "" + display block + width 100% + height 100% + background rgba(0, 0, 0, 0.5) + + > .avatar + top ((100px - 58px) / 2) + left ((100px - 58px) / 2) + border none + border-radius 100% + box-shadow 0 0 16px rgba(0, 0, 0, 0.5) + + > .name + position absolute + top 0 + left 92px + margin 0 + line-height 100px + color #fff + text-shadow 0 0 8px rgba(0, 0, 0, 0.5) + + > .username + display none + + &[data-melt] + background transparent !important + border none !important + + > .banner + visibility hidden + + > .avatar + box-shadow none + + > .name + color #666 + text-shadow none + + > .banner + height 100px + background-color #f5f5f5 + background-size cover + background-position center + cursor pointer + + > .avatar + display block + position absolute + top 76px + left 16px + width 58px + height 58px + margin 0 + border solid 3px #fff + border-radius 8px + vertical-align bottom + cursor pointer + + > .name + display block + margin 10px 0 0 84px + line-height 16px + font-weight bold + color #555 + + > .username + display block + margin 4px 0 8px 84px + line-height 16px + font-size 0.9em + color #999 + +</style> diff --git a/src/web/app/desktop/views/components/widgets/slideshow.vue b/src/web/app/desktop/views/components/widgets/slideshow.vue new file mode 100644 index 0000000000..beda350666 --- /dev/null +++ b/src/web/app/desktop/views/components/widgets/slideshow.vue @@ -0,0 +1,153 @@ +<template> +<div class="mkw-slideshow"> + <div @click="choose"> + <p v-if="data.folder === undefined">クリックしてフォルダを指定してください</p> + <p v-if="data.folder !== undefined && images.length == 0 && !fetching">このフォルダには画像がありません</p> + <div ref="slideA" class="slide a"></div> + <div ref="slideB" class="slide b"></div> + </div> + <button @click="resize">%fa:expand%</button> +</div> +</template> + +<script lang="ts"> +import * as anime from 'animejs'; +import define from '../../../../common/define-widget'; +export default define({ + name: 'slideshow', + props: { + folder: undefined, + size: 0 + } +}).extend({ + data() { + return { + images: [], + fetching: true, + clock: null + }; + }, + mounted() { + this.$nextTick(() => { + this.applySize(); + }); + + if (this.props.folder !== undefined) { + this.fetch(); + } + + this.clock = setInterval(this.change, 10000); + }, + beforeDestroy() { + clearInterval(this.clock); + }, + methods: { + applySize() { + let h; + + if (this.props.size == 1) { + h = 250; + } else { + h = 170; + } + + this.$el.style.height = `${h}px`; + }, + resize() { + if (this.props.size == 1) { + this.props.size = 0; + } else { + this.props.size++; + } + + this.applySize(); + }, + change() { + if (this.images.length == 0) return; + + const index = Math.floor(Math.random() * this.images.length); + const img = `url(${ this.images[index].url }?thumbnail&size=1024)`; + + (this.$refs.slideB as any).style.backgroundImage = img; + + anime({ + targets: this.$refs.slideB, + opacity: 1, + duration: 1000, + easing: 'linear', + complete: () => { + (this.$refs.slideA as any).style.backgroundImage = img; + anime({ + targets: this.$refs.slideB, + opacity: 0, + duration: 0 + }); + } + }); + }, + fetch() { + this.fetching = true; + + (this as any).api('drive/files', { + folder_id: this.props.folder, + type: 'image/*', + limit: 100 + }).then(images => { + this.fetching = false; + this.images = images; + (this.$refs.slideA as any).style.backgroundImage = ''; + (this.$refs.slideB as any).style.backgroundImage = ''; + this.change(); + }); + }, + choose() { + (this as any).apis.chooseDriveFolder().then(folder => { + this.props.folder = folder ? folder.id : null; + this.fetch(); + }); + } + } +}); +</script> + +<style lang="stylus" scoped> +.mkw-slideshow + overflow hidden + background #fff + border solid 1px rgba(0, 0, 0, 0.075) + border-radius 6px + + &:hover > button + display block + + > button + position absolute + left 0 + bottom 0 + display none + padding 4px + font-size 24px + color #fff + text-shadow 0 0 8px #000 + + > div + width 100% + height 100% + cursor pointer + + > * + pointer-events none + + > .slide + position absolute + top 0 + left 0 + width 100% + height 100% + background-size cover + background-position center + + &.b + opacity 0 + +</style> diff --git a/src/web/app/desktop/views/components/widgets/tips.vue b/src/web/app/desktop/views/components/widgets/tips.vue new file mode 100644 index 0000000000..2991fbc3b9 --- /dev/null +++ b/src/web/app/desktop/views/components/widgets/tips.vue @@ -0,0 +1,108 @@ +<template> +<div class="mkw-tips"> + <p ref="tip">%fa:R lightbulb%<span v-html="tip"></span></p> +</div> +</template> + +<script lang="ts"> +import * as anime from 'animejs'; +import define from '../../../../common/define-widget'; + +const tips = [ + '<kbd>t</kbd>でタイムラインにフォーカスできます', + '<kbd>p</kbd>または<kbd>n</kbd>で投稿フォームを開きます', + '投稿フォームにはファイルをドラッグ&ドロップできます', + '投稿フォームにクリップボードにある画像データをペーストできます', + 'ドライブにファイルをドラッグ&ドロップしてアップロードできます', + 'ドライブでファイルをドラッグしてフォルダ移動できます', + 'ドライブでフォルダをドラッグしてフォルダ移動できます', + 'ホームは設定からカスタマイズできます', + 'MisskeyはMIT Licenseです', + 'タイムマシンウィジェットを利用すると、簡単に過去のタイムラインに遡れます', + '投稿の ... をクリックして、投稿をユーザーページにピン留めできます', + 'ドライブの容量は(デフォルトで)1GBです', + '投稿に添付したファイルは全てドライブに保存されます', + 'ホームのカスタマイズ中、ウィジェットを右クリックしてデザインを変更できます', + 'タイムライン上部にもウィジェットを設置できます', + '投稿をダブルクリックすると詳細が見れます', + '「**」でテキストを囲むと**強調表示**されます', + 'チャンネルウィジェットを利用すると、よく利用するチャンネルを素早く確認できます', + 'いくつかのウィンドウはブラウザの外に切り離すことができます', + 'カレンダーウィジェットのパーセンテージは、経過の割合を示しています', + 'APIを利用してbotの開発なども行えます', + 'MisskeyはLINEを通じてでも利用できます', + 'まゆかわいいよまゆ', + 'Misskeyは2014年にサービスを開始しました', + '対応ブラウザではMisskeyを開いていなくても通知を受け取れます' +] + +export default define({ + name: 'tips' +}).extend({ + data() { + return { + tip: null, + clock: null + }; + }, + mounted() { + this.$nextTick(() => { + this.set(); + }); + + this.clock = setInterval(this.change, 20000); + }, + beforeDestroy() { + clearInterval(this.clock); + }, + methods: { + set() { + this.tip = tips[Math.floor(Math.random() * tips.length)]; + }, + change() { + anime({ + targets: this.$refs.tip, + opacity: 0, + duration: 500, + easing: 'linear', + complete: this.set + }); + + setTimeout(() => { + anime({ + targets: this.$refs.tip, + opacity: 1, + duration: 500, + easing: 'linear' + }); + }, 500); + } + } +}); +</script> + +<style lang="stylus" scoped> +.mkw-tips + overflow visible !important + + > p + display block + margin 0 + padding 0 12px + text-align center + font-size 0.7em + color #999 + + > [data-fa] + margin-right 4px + + kbd + display inline + padding 0 6px + margin 0 2px + font-size 1em + font-family inherit + border solid 1px #999 + border-radius 2px + +</style> |