summaryrefslogtreecommitdiff
path: root/src/web/app/common
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-02-15 12:36:42 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-02-15 12:36:42 +0900
commitca604692628dcba95681964e8deec5ca75049c4e (patch)
tree0e26d2d049e83db42f9e9c9e275052f6a90ba55d /src/web/app/common
parentwip (diff)
downloadsharkey-ca604692628dcba95681964e8deec5ca75049c4e.tar.gz
sharkey-ca604692628dcba95681964e8deec5ca75049c4e.tar.bz2
sharkey-ca604692628dcba95681964e8deec5ca75049c4e.zip
wip
Diffstat (limited to 'src/web/app/common')
-rw-r--r--src/web/app/common/define-widget.ts4
-rw-r--r--src/web/app/common/views/components/widgets/calendar.vue192
-rw-r--r--src/web/app/common/views/components/widgets/donation.vue45
-rw-r--r--src/web/app/common/views/components/widgets/messaging.vue59
-rw-r--r--src/web/app/common/views/components/widgets/nav.vue29
-rw-r--r--src/web/app/common/views/components/widgets/photo-stream.vue122
-rw-r--r--src/web/app/common/views/components/widgets/profile.vue4
-rw-r--r--src/web/app/common/views/components/widgets/slideshow.vue154
-rw-r--r--src/web/app/common/views/components/widgets/tips.vue109
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>