diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2018-02-15 12:36:42 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2018-02-15 12:36:42 +0900 |
| commit | ca604692628dcba95681964e8deec5ca75049c4e (patch) | |
| tree | 0e26d2d049e83db42f9e9c9e275052f6a90ba55d /src/web/app/common | |
| parent | wip (diff) | |
| download | sharkey-ca604692628dcba95681964e8deec5ca75049c4e.tar.gz sharkey-ca604692628dcba95681964e8deec5ca75049c4e.tar.bz2 sharkey-ca604692628dcba95681964e8deec5ca75049c4e.zip | |
wip
Diffstat (limited to 'src/web/app/common')
9 files changed, 714 insertions, 4 deletions
diff --git a/src/web/app/common/define-widget.ts b/src/web/app/common/define-widget.ts index 5102ee1abf..782a69a624 100644 --- a/src/web/app/common/define-widget.ts +++ b/src/web/app/common/define-widget.ts @@ -2,7 +2,7 @@ import Vue from 'vue'; export default function<T extends object>(data: { name: string; - props: T; + props?: T; }) { return Vue.extend({ props: { @@ -26,7 +26,7 @@ export default function<T extends object>(data: { }, data() { return { - props: data.props + props: data.props || {} }; }, watch: { diff --git a/src/web/app/common/views/components/widgets/calendar.vue b/src/web/app/common/views/components/widgets/calendar.vue new file mode 100644 index 0000000000..308f43cd99 --- /dev/null +++ b/src/web/app/common/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 '../../../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/common/views/components/widgets/donation.vue b/src/web/app/common/views/components/widgets/donation.vue new file mode 100644 index 0000000000..50adc531bf --- /dev/null +++ b/src/web/app/common/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 '../../../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/common/views/components/widgets/messaging.vue b/src/web/app/common/views/components/widgets/messaging.vue new file mode 100644 index 0000000000..19ef704310 --- /dev/null +++ b/src/web/app/common/views/components/widgets/messaging.vue @@ -0,0 +1,59 @@ +<template> +<div class="mkw-messaging"> + <p class="title" v-if="props.design == 0">%fa:comments%%i18n:desktop.tags.mk-messaging-home-widget.title%</p> + <mk-messaging ref="index" compact @navigate="navigate"/> +</div> +</template> + +<script lang="ts"> +import define from '../../../define-widget'; +export default define({ + name: 'messaging', + props: { + design: 0 + } +}).extend({ + methods: { + navigate(user) { + if (this.platform == 'desktop') { + this.wapi_openMessagingRoomWindow(user); + } else { + // TODO: open room page in new tab + } + }, + func() { + if (this.props.design == 1) { + this.props.design = 0; + } else { + this.props.design++; + } + } + } +}); +</script> + +<style lang="stylus" scoped> +.mkw-messaging + overflow hidden + background #fff + border solid 1px rgba(0, 0, 0, 0.075) + border-radius 6px + + > .title + z-index 2 + 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 + + > mk-messaging + max-height 250px + overflow auto + +</style> diff --git a/src/web/app/common/views/components/widgets/nav.vue b/src/web/app/common/views/components/widgets/nav.vue new file mode 100644 index 0000000000..77e1eea492 --- /dev/null +++ b/src/web/app/common/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 '../../../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/common/views/components/widgets/photo-stream.vue b/src/web/app/common/views/components/widgets/photo-stream.vue new file mode 100644 index 0000000000..12e568ca00 --- /dev/null +++ b/src/web/app/common/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 '../../../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.$root.$data.os.stream.getConnection(); + this.connectionId = this.$root.$data.os.stream.use(); + + this.connection.on('drive_file_created', this.onDriveFileCreated); + + this.$root.$data.os.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.$root.$data.os.stream.dispose(this.connectionId); + }, + methods: { + onStreamDriveFileCreated(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/common/views/components/widgets/profile.vue b/src/web/app/common/views/components/widgets/profile.vue index e589eb20b9..70902c7cf5 100644 --- a/src/web/app/common/views/components/widgets/profile.vue +++ b/src/web/app/common/views/components/widgets/profile.vue @@ -1,7 +1,7 @@ <template> <div class="mkw-profile" - data-compact={ data.design == 1 || data.design == 2 } - data-melt={ data.design == 2 } + :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)' : '' } diff --git a/src/web/app/common/views/components/widgets/slideshow.vue b/src/web/app/common/views/components/widgets/slideshow.vue new file mode 100644 index 0000000000..6dcd453e25 --- /dev/null +++ b/src/web/app/common/views/components/widgets/slideshow.vue @@ -0,0 +1,154 @@ +<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 Vue from 'vue'; +import * as anime from 'animejs'; +import define from '../../../define-widget'; +export default define({ + name: 'slideshow', + props: { + folder: undefined, + size: 0 + } +}).extend({ + data() { + return { + images: [], + fetching: true, + clock: null + }; + }, + mounted() { + Vue.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.$root.$data.os.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.wapi_selectDriveFolder().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/common/views/components/widgets/tips.vue b/src/web/app/common/views/components/widgets/tips.vue new file mode 100644 index 0000000000..f38ecfe441 --- /dev/null +++ b/src/web/app/common/views/components/widgets/tips.vue @@ -0,0 +1,109 @@ +<template> +<div class="mkw-tips"> + <p ref="tip">%fa:R lightbulb%<span v-html="tip"></span></p> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import * as anime from 'animejs'; +import define from '../../../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() { + Vue.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> |