diff options
Diffstat (limited to 'src/client/app/common/scripts')
23 files changed, 0 insertions, 3015 deletions
diff --git a/src/client/app/common/scripts/2fa.ts b/src/client/app/common/scripts/2fa.ts deleted file mode 100644 index f638cce156..0000000000 --- a/src/client/app/common/scripts/2fa.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function hexifyAB(buffer) { - return Array.from(new Uint8Array(buffer)) - .map(item => item.toString(16).padStart(2, 0)) - .join(''); -} diff --git a/src/client/app/common/scripts/check-for-update.ts b/src/client/app/common/scripts/check-for-update.ts deleted file mode 100644 index d487915766..0000000000 --- a/src/client/app/common/scripts/check-for-update.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { version as current } from '../../config'; - -export default async function($root: any, force = false, silent = false) { - const meta = await $root.getMeta(force); - const newer = meta.version; - - if (newer != current) { - localStorage.setItem('should-refresh', 'true'); - localStorage.setItem('v', newer); - - // Clear cache (service worker) - try { - if (navigator.serviceWorker.controller) { - navigator.serviceWorker.controller.postMessage('clear'); - } - - const registrations = await navigator.serviceWorker.getRegistrations(); - for (const registration of registrations) { - registration.unregister(); - } - } catch (e) { - console.error(e); - } - - /*if (!silent) { - $root.dialog({ - title: $root.$t('@.update-available-title'), - text: $root.$t('@.update-available', { newer, current }) - }); - }*/ - - return newer; - } else { - return null; - } -} diff --git a/src/client/app/common/scripts/collect-page-vars.ts b/src/client/app/common/scripts/collect-page-vars.ts deleted file mode 100644 index a4096fb2c2..0000000000 --- a/src/client/app/common/scripts/collect-page-vars.ts +++ /dev/null @@ -1,48 +0,0 @@ -export function collectPageVars(content) { - const pageVars = []; - const collect = (xs: any[]) => { - for (const x of xs) { - if (x.type === 'textInput') { - pageVars.push({ - name: x.name, - type: 'string', - value: x.default || '' - }); - } else if (x.type === 'textareaInput') { - pageVars.push({ - name: x.name, - type: 'string', - value: x.default || '' - }); - } else if (x.type === 'numberInput') { - pageVars.push({ - name: x.name, - type: 'number', - value: x.default || 0 - }); - } else if (x.type === 'switch') { - pageVars.push({ - name: x.name, - type: 'boolean', - value: x.default || false - }); - } else if (x.type === 'counter') { - pageVars.push({ - name: x.name, - type: 'number', - value: 0 - }); - } else if (x.type === 'radioButton') { - pageVars.push({ - name: x.name, - type: 'string', - value: x.default || '' - }); - } else if (x.children) { - collect(x.children); - } - } - }; - collect(content); - return pageVars; -} diff --git a/src/client/app/common/scripts/compose-notification.ts b/src/client/app/common/scripts/compose-notification.ts deleted file mode 100644 index ec854f2f4d..0000000000 --- a/src/client/app/common/scripts/compose-notification.ts +++ /dev/null @@ -1,74 +0,0 @@ -import getNoteSummary from '../../../../misc/get-note-summary'; -import getReactionEmoji from '../../../../misc/get-reaction-emoji'; -import getUserName from '../../../../misc/get-user-name'; - -type Notification = { - title: string; - body: string; - icon: string; - onclick?: any; -}; - -// TODO: i18n - -export default function(type, data): Notification { - switch (type) { - case 'driveFileCreated': - return { - title: 'File uploaded', - body: data.name, - icon: data.url - }; - - case 'unreadMessagingMessage': - return { - title: `New message from ${getUserName(data.user)}`, - body: data.text, // TODO: getMessagingMessageSummary(data), - icon: data.user.avatarUrl - }; - - case 'reversiInvited': - return { - title: 'Play reversi with me', - body: `You got reversi invitation from ${getUserName(data.parent)}`, - icon: data.parent.avatarUrl - }; - - case 'notification': - switch (data.type) { - case 'mention': - return { - title: `${getUserName(data.user)}:`, - body: getNoteSummary(data), - icon: data.user.avatarUrl - }; - - case 'reply': - return { - title: `You got reply from ${getUserName(data.user)}:`, - body: getNoteSummary(data), - icon: data.user.avatarUrl - }; - - case 'quote': - return { - title: `${getUserName(data.user)}:`, - body: getNoteSummary(data), - icon: data.user.avatarUrl - }; - - case 'reaction': - return { - title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`, - body: getNoteSummary(data.note), - icon: data.user.avatarUrl - }; - - default: - return null; - } - - default: - return null; - } -} diff --git a/src/client/app/common/scripts/contains.ts b/src/client/app/common/scripts/contains.ts deleted file mode 100644 index a5071b3f25..0000000000 --- a/src/client/app/common/scripts/contains.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default (parent, child) => { - let node = child.parentNode; - while (node) { - if (node == parent) return true; - node = node.parentNode; - } - return false; -}; diff --git a/src/client/app/common/scripts/copy-to-clipboard.ts b/src/client/app/common/scripts/copy-to-clipboard.ts deleted file mode 100644 index ab13cab970..0000000000 --- a/src/client/app/common/scripts/copy-to-clipboard.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Clipboardに値をコピー(TODO: 文字列以外も対応) - */ -export default val => { - // 空div 生成 - const tmp = document.createElement('div'); - // 選択用のタグ生成 - const pre = document.createElement('pre'); - - // 親要素のCSSで user-select: none だとコピーできないので書き換える - pre.style.webkitUserSelect = 'auto'; - pre.style.userSelect = 'auto'; - - tmp.appendChild(pre).textContent = val; - - // 要素を画面外へ - const s = tmp.style; - s.position = 'fixed'; - s.right = '200%'; - - // body に追加 - document.body.appendChild(tmp); - // 要素を選択 - document.getSelection().selectAllChildren(tmp); - - // クリップボードにコピー - const result = document.execCommand('copy'); - - // 要素削除 - document.body.removeChild(tmp); - - return result; -}; diff --git a/src/client/app/common/scripts/format-uptime.ts b/src/client/app/common/scripts/format-uptime.ts deleted file mode 100644 index 6550e4cc39..0000000000 --- a/src/client/app/common/scripts/format-uptime.ts +++ /dev/null @@ -1,25 +0,0 @@ - -/** - * Format like the uptime command - */ -export default function(sec) { - if (!sec) return sec; - - const day = Math.floor(sec / 86400); - const tod = sec % 86400; - - // Days part in string: 2 days, 1 day, null - const d = day >= 2 ? `${day} days` : day >= 1 ? `${day} day` : null; - - // Time part in string: 1 sec, 1 min, 1:01 - const t - = tod < 60 ? `${Math.floor(tod)} sec` - : tod < 3600 ? `${Math.floor(tod / 60)} min` - : `${Math.floor(tod / 60 / 60)}:${Math.floor((tod / 60) % 60).toString().padStart(2, '0')}`; - - let str = ''; - if (d) str += `${d}, `; - str += t; - - return str; -} diff --git a/src/client/app/common/scripts/gen-search-query.ts b/src/client/app/common/scripts/gen-search-query.ts deleted file mode 100644 index fc26cb7f78..0000000000 --- a/src/client/app/common/scripts/gen-search-query.ts +++ /dev/null @@ -1,31 +0,0 @@ -import parseAcct from '../../../../misc/acct/parse'; -import { host as localHost } from '../../config'; - -export async function genSearchQuery(v: any, q: string) { - let host: string; - let userId: string; - if (q.split(' ').some(x => x.startsWith('@'))) { - for (const at of q.split(' ').filter(x => x.startsWith('@')).map(x => x.substr(1))) { - if (at.includes('.')) { - if (at === localHost || at === '.') { - host = null; - } else { - host = at; - } - } else { - const user = await v.$root.api('users/show', parseAcct(at)).catch(x => null); - if (user) { - userId = user.id; - } else { - // todo: show error - } - } - } - - } - return { - query: q.split(' ').filter(x => !x.startsWith('/') && !x.startsWith('@')).join(' '), - host: host, - userId: userId - }; -} diff --git a/src/client/app/common/scripts/get-face.ts b/src/client/app/common/scripts/get-face.ts deleted file mode 100644 index 19f2bdb064..0000000000 --- a/src/client/app/common/scripts/get-face.ts +++ /dev/null @@ -1,11 +0,0 @@ -const faces = [ - '(=^・・^=)', - 'v(\'ω\')v', - '🐡( \'-\' 🐡 )フグパンチ!!!!', - '✌️(´・_・`)✌️', - '(。>﹏<。)', - '(Δ・x・Δ)', - '(コ`・ヘ・´ケ)' -]; - -export default () => faces[Math.floor(Math.random() * faces.length)]; diff --git a/src/client/app/common/scripts/get-md5.ts b/src/client/app/common/scripts/get-md5.ts deleted file mode 100644 index b002d762b1..0000000000 --- a/src/client/app/common/scripts/get-md5.ts +++ /dev/null @@ -1,10 +0,0 @@ -// スクリプトサイズがデカい -//import * as crypto from 'crypto'; - -export default (data: ArrayBuffer) => { - //const buf = new Buffer(data); - //const hash = crypto.createHash('md5'); - //hash.update(buf); - //return hash.digest('hex'); - return ''; -}; diff --git a/src/client/app/common/scripts/get-static-image-url.ts b/src/client/app/common/scripts/get-static-image-url.ts deleted file mode 100644 index 7460ca38f2..0000000000 --- a/src/client/app/common/scripts/get-static-image-url.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { url as instanceUrl } from '../../config'; -import * as url from '../../../../prelude/url'; - -export function getStaticImageUrl(baseUrl: string): string { - const u = new URL(baseUrl); - const dummy = `${u.host}${u.pathname}`; // 拡張子がないとキャッシュしてくれないCDNがあるので - return `${instanceUrl}/proxy/${dummy}?${url.query({ - url: u.href, - static: '1' - })}`; -} diff --git a/src/client/app/common/scripts/loading.ts b/src/client/app/common/scripts/loading.ts deleted file mode 100644 index 70a3a4c85e..0000000000 --- a/src/client/app/common/scripts/loading.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as NProgress from 'nprogress'; -NProgress.configure({ - trickleSpeed: 500, - showSpinner: false -}); - -const root = document.getElementsByTagName('html')[0]; - -export default { - start: () => { - root.classList.add('progress'); - NProgress.start(); - }, - done: () => { - root.classList.remove('progress'); - NProgress.done(); - }, - set: val => { - NProgress.set(val); - } -}; diff --git a/src/client/app/common/scripts/note-mixin.ts b/src/client/app/common/scripts/note-mixin.ts deleted file mode 100644 index 84e134cc32..0000000000 --- a/src/client/app/common/scripts/note-mixin.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { parse } from '../../../../mfm/parse'; -import { sum, unique } from '../../../../prelude/array'; -import shouldMuteNote from './should-mute-note'; -import MkNoteMenu from '../views/components/note-menu.vue'; -import MkReactionPicker from '../views/components/reaction-picker.vue'; -import pleaseLogin from './please-login'; -import i18n from '../../i18n'; - -function focus(el, fn) { - const target = fn(el); - if (target) { - if (target.hasAttribute('tabindex')) { - target.focus(); - } else { - focus(target, fn); - } - } -} - -type Opts = { - mobile?: boolean; -}; - -export default (opts: Opts = {}) => ({ - i18n: i18n(), - - data() { - return { - showContent: false, - hideThisNote: false, - openingMenu: false - }; - }, - - computed: { - keymap(): any { - return { - 'r': () => this.reply(true), - 'e|a|plus': () => this.react(true), - 'q': () => this.renote(true), - 'f|b': this.favorite, - 'delete|ctrl+d': this.del, - 'ctrl+q': this.renoteDirectly, - 'up|k|shift+tab': this.focusBefore, - 'down|j|tab': this.focusAfter, - //'esc': this.blur, - 'm|o': () => this.menu(true), - 's': this.toggleShowContent, - '1': () => this.reactDirectly('like'), - '2': () => this.reactDirectly('love'), - '3': () => this.reactDirectly('laugh'), - '4': () => this.reactDirectly('hmm'), - '5': () => this.reactDirectly('surprise'), - '6': () => this.reactDirectly('congrats'), - '7': () => this.reactDirectly('angry'), - '8': () => this.reactDirectly('confused'), - '9': () => this.reactDirectly('rip'), - '0': () => this.reactDirectly('pudding'), - }; - }, - - isRenote(): boolean { - return (this.note.renote && - this.note.text == null && - this.note.fileIds.length == 0 && - this.note.poll == null); - }, - - appearNote(): any { - return this.isRenote ? this.note.renote : this.note; - }, - - isMyNote(): boolean { - return this.$store.getters.isSignedIn && (this.$store.state.i.id === this.appearNote.userId); - }, - - reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; - }, - - title(): string { - return ''; - }, - - urls(): string[] { - if (this.appearNote.text) { - const ast = parse(this.appearNote.text); - // TODO: 再帰的にURL要素がないか調べる - const urls = unique(ast - .filter(t => ((t.node.type == 'url' || t.node.type == 'link') && t.node.props.url && !t.node.props.silent)) - .map(t => t.node.props.url)); - - // unique without hash - // [ http://a/#1, http://a/#2, http://b/#3 ] => [ http://a/#1, http://b/#3 ] - const removeHash = x => x.replace(/#[^#]*$/, ''); - - return urls.reduce((array, url) => { - const removed = removeHash(url); - if (!array.map(x => removeHash(x)).includes(removed)) array.push(url); - return array; - }, []); - } else { - return null; - } - } - }, - - created() { - this.hideThisNote = shouldMuteNote(this.$store.state.i, this.$store.state.settings, this.appearNote); - }, - - methods: { - reply(viaKeyboard = false) { - pleaseLogin(this.$root); - this.$root.$post({ - reply: this.appearNote, - animation: !viaKeyboard, - cb: () => { - this.focus(); - } - }); - }, - - renote(viaKeyboard = false) { - pleaseLogin(this.$root); - this.$root.$post({ - renote: this.appearNote, - animation: !viaKeyboard, - cb: () => { - this.focus(); - } - }); - }, - - renoteDirectly() { - (this as any).api('notes/create', { - renoteId: this.appearNote.id - }); - }, - - react(viaKeyboard = false) { - pleaseLogin(this.$root); - this.blur(); - const w = this.$root.new(MkReactionPicker, { - source: this.$refs.reactButton, - showFocus: viaKeyboard, - animation: !viaKeyboard - }); - w.$once('chosen', reaction => { - this.$root.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }).then(() => { - w.close(); - }); - }); - w.$once('closed', this.focus); - }, - - reactDirectly(reaction) { - this.$root.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, - - undoReact(note) { - const oldReaction = note.myReaction; - if (!oldReaction) return; - this.$root.api('notes/reactions/delete', { - noteId: note.id - }); - }, - - favorite() { - pleaseLogin(this.$root); - this.$root.api('notes/favorites/create', { - noteId: this.appearNote.id - }).then(() => { - this.$root.dialog({ - type: 'success', - splash: true - }); - }); - }, - - del() { - this.$root.dialog({ - type: 'warning', - text: this.$t('@.delete-confirm'), - showCancelButton: true - }).then(({ canceled }) => { - if (canceled) return; - - this.$root.api('notes/delete', { - noteId: this.appearNote.id - }); - }); - }, - - menu(viaKeyboard = false) { - if (this.openingMenu) return; - this.openingMenu = true; - const w = this.$root.new(MkNoteMenu, { - source: this.$refs.menuButton, - note: this.appearNote, - animation: !viaKeyboard - }).$once('closed', () => { - this.openingMenu = false; - this.focus(); - }); - this.$once('hook:beforeDestroy', () => { - w.destroyDom(); - }); - }, - - toggleShowContent() { - this.showContent = !this.showContent; - }, - - focus() { - this.$el.focus(); - }, - - blur() { - this.$el.blur(); - }, - - focusBefore() { - focus(this.$el, e => e.previousElementSibling); - }, - - focusAfter() { - focus(this.$el, e => e.nextElementSibling); - } - } -}); diff --git a/src/client/app/common/scripts/note-subscriber.ts b/src/client/app/common/scripts/note-subscriber.ts deleted file mode 100644 index 5b31a9f9d0..0000000000 --- a/src/client/app/common/scripts/note-subscriber.ts +++ /dev/null @@ -1,149 +0,0 @@ -import Vue from 'vue'; - -export default prop => ({ - data() { - return { - connection: null - }; - }, - - computed: { - $_ns_note_(): any { - return this[prop]; - }, - - $_ns_isRenote(): boolean { - return (this.$_ns_note_.renote != null && - this.$_ns_note_.text == null && - this.$_ns_note_.fileIds.length == 0 && - this.$_ns_note_.poll == null); - }, - - $_ns_target(): any { - return this.$_ns_isRenote ? this.$_ns_note_.renote : this.$_ns_note_; - }, - }, - - created() { - if (this.$store.getters.isSignedIn) { - this.connection = this.$root.stream; - } - }, - - mounted() { - this.capture(true); - - if (this.$store.getters.isSignedIn) { - this.connection.on('_connected_', this.onStreamConnected); - } - }, - - beforeDestroy() { - this.decapture(true); - - if (this.$store.getters.isSignedIn) { - this.connection.off('_connected_', this.onStreamConnected); - } - }, - - methods: { - capture(withHandler = false) { - if (this.$store.getters.isSignedIn) { - const data = { - id: this.$_ns_target.id - } as any; - - if ( - (this.$_ns_target.visibleUserIds || []).includes(this.$store.state.i.id) || - (this.$_ns_target.mentions || []).includes(this.$store.state.i.id) - ) { - data.read = true; - } - - this.connection.send('sn', data); - if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); - } - }, - - decapture(withHandler = false) { - if (this.$store.getters.isSignedIn) { - this.connection.send('un', { - id: this.$_ns_target.id - }); - if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); - } - }, - - onStreamConnected() { - this.capture(); - }, - - onStreamNoteUpdated(data) { - const { type, id, body } = data; - - if (id !== this.$_ns_target.id) return; - - switch (type) { - case 'reacted': { - const reaction = body.reaction; - - if (this.$_ns_target.reactions == null) { - Vue.set(this.$_ns_target, 'reactions', {}); - } - - if (this.$_ns_target.reactions[reaction] == null) { - Vue.set(this.$_ns_target.reactions, reaction, 0); - } - - // Increment the count - this.$_ns_target.reactions[reaction]++; - - if (body.userId == this.$store.state.i.id) { - Vue.set(this.$_ns_target, 'myReaction', reaction); - } - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - if (this.$_ns_target.reactions == null) { - return; - } - - if (this.$_ns_target.reactions[reaction] == null) { - return; - } - - // Decrement the count - if (this.$_ns_target.reactions[reaction] > 0) this.$_ns_target.reactions[reaction]--; - - if (body.userId == this.$store.state.i.id) { - Vue.set(this.$_ns_target, 'myReaction', null); - } - break; - } - - case 'pollVoted': { - const choice = body.choice; - this.$_ns_target.poll.choices[choice].votes++; - if (body.userId == this.$store.state.i.id) { - Vue.set(this.$_ns_target.poll.choices[choice], 'isVoted', true); - } - break; - } - - case 'deleted': { - Vue.set(this.$_ns_target, 'deletedAt', body.deletedAt); - Vue.set(this.$_ns_target, 'renote', null); - this.$_ns_target.text = null; - this.$_ns_target.fileIds = []; - this.$_ns_target.poll = null; - this.$_ns_target.geo = null; - this.$_ns_target.cw = null; - break; - } - } - }, - } -}); diff --git a/src/client/app/common/scripts/paging.ts b/src/client/app/common/scripts/paging.ts deleted file mode 100644 index b4f2ec1ae1..0000000000 --- a/src/client/app/common/scripts/paging.ts +++ /dev/null @@ -1,189 +0,0 @@ -import Vue from 'vue'; - -export default (opts) => ({ - data() { - return { - items: [], - queue: [], - offset: 0, - fetching: true, - moreFetching: false, - inited: false, - more: false - }; - }, - - computed: { - empty(): boolean { - return this.items.length == 0 && !this.fetching && this.inited; - }, - - error(): boolean { - return !this.fetching && !this.inited; - } - }, - - watch: { - queue(x) { - if (opts.onQueueChanged) opts.onQueueChanged(this, x); - }, - - pagination() { - this.init(); - } - }, - - created() { - opts.displayLimit = opts.displayLimit || 30; - this.init(); - }, - - mounted() { - if (opts.captureWindowScroll) { - this.isScrollTop = () => { - return window.scrollY <= 8; - }; - - window.addEventListener('scroll', this.onScroll, { passive: true }); - } else if (opts.isContainer) { - this.isScrollTop = () => { - return this.$el.scrollTop <= 8; - }; - - this.$el.addEventListener('scroll', this.onScroll, { passive: true }); - } - }, - - beforeDestroy() { - if (opts.captureWindowScroll) { - window.removeEventListener('scroll', this.onScroll); - } else if (opts.isContainer) { - this.$el.removeEventListener('scroll', this.onScroll); - } - }, - - methods: { - updateItem(i, item) { - Vue.set((this as any).items, i, item); - }, - - reload() { - this.queue = []; - this.items = []; - this.init(); - }, - - async init() { - this.fetching = true; - if (opts.beforeInit) opts.beforeInit(this); - let params = typeof this.pagination.params === 'function' ? this.pagination.params(true) : this.pagination.params; - if (params && params.then) params = await params; - await this.$root.api(this.pagination.endpoint, { - limit: (this.pagination.limit || 10) + 1, - ...params - }).then(x => { - if (x.length == (this.pagination.limit || 10) + 1) { - x.pop(); - this.items = x; - this.more = true; - } else { - this.items = x; - this.more = false; - } - this.offset = x.length; - this.inited = true; - this.fetching = false; - if (opts.onInited) opts.onInited(this); - }, e => { - this.fetching = false; - if (opts.onInited) opts.onInited(this); - }); - }, - - async fetchMore() { - if (!this.more || this.moreFetching || this.items.length === 0) return; - this.moreFetching = true; - let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params; - if (params && params.then) params = await params; - await this.$root.api(this.pagination.endpoint, { - limit: (this.pagination.limit || 10) + 1, - ...(this.pagination.endpoint === 'notes/search' ? { - offset: this.offset, - } : { - untilId: this.items[this.items.length - 1].id, - }), - ...params - }).then(x => { - if (x.length == (this.pagination.limit || 10) + 1) { - x.pop(); - this.items = this.items.concat(x); - this.more = true; - } else { - this.items = this.items.concat(x); - this.more = false; - } - this.offset += x.length; - this.moreFetching = false; - }, e => { - this.moreFetching = false; - }); - }, - - prepend(item, silent = false) { - if (opts.onPrepend) { - const cancel = opts.onPrepend(this, item, silent); - if (cancel) return; - } - - if (this.isScrollTop == null || this.isScrollTop()) { - // Prepend the item - this.items.unshift(item); - - // オーバーフローしたら古い投稿は捨てる - if (this.items.length >= opts.displayLimit) { - this.items = this.items.slice(0, opts.displayLimit); - this.more = true; - } - } else { - this.queue.push(item); - } - }, - - append(item) { - this.items.push(item); - }, - - releaseQueue() { - for (const n of this.queue) { - this.prepend(n, true); - } - this.queue = []; - }, - - onScroll() { - if (this.isScrollTop()) { - this.onTop(); - } - - if (this.$store.state.settings.fetchOnScroll) { - // 親要素が display none だったら弾く - // https://github.com/syuilo/misskey/issues/1569 - // http://d.hatena.ne.jp/favril/20091105/1257403319 - if (this.$el.offsetHeight == 0) return; - - const bottomPosition = opts.isContainer ? this.$el.scrollHeight : document.body.offsetHeight; - - const currentBottomPosition = opts.isContainer ? this.$el.scrollTop + this.$el.clientHeight : window.scrollY + window.innerHeight; - if (currentBottomPosition > (bottomPosition - 8)) this.onBottom(); - } - }, - - onTop() { - this.releaseQueue(); - }, - - onBottom() { - this.fetchMore(); - } - } -}); diff --git a/src/client/app/common/scripts/please-login.ts b/src/client/app/common/scripts/please-login.ts deleted file mode 100644 index 7125541bb1..0000000000 --- a/src/client/app/common/scripts/please-login.ts +++ /dev/null @@ -1,10 +0,0 @@ -export default ($root: any) => { - if ($root.$store.getters.isSignedIn) return; - - $root.dialog({ - title: $root.$t('@.signin-required'), - text: null - }); - - throw new Error('signin required'); -}; diff --git a/src/client/app/common/scripts/post-form.ts b/src/client/app/common/scripts/post-form.ts deleted file mode 100644 index 496782fd30..0000000000 --- a/src/client/app/common/scripts/post-form.ts +++ /dev/null @@ -1,537 +0,0 @@ -import insertTextAtCursor from 'insert-text-at-cursor'; -import { length } from 'stringz'; -import { toASCII } from 'punycode'; -import MkVisibilityChooser from '../views/components/visibility-chooser.vue'; -import getFace from './get-face'; -import { parse } from '../../../../mfm/parse'; -import { host, url } from '../../config'; -import i18n from '../../i18n'; -import { erase, unique } from '../../../../prelude/array'; -import extractMentions from '../../../../misc/extract-mentions'; -import { formatTimeString } from '../../../../misc/format-time-string'; - -export default (opts) => ({ - i18n: i18n(), - - components: { - XPostFormAttaches: () => import('../views/components/post-form-attaches.vue').then(m => m.default), - XPollEditor: () => import('../views/components/poll-editor.vue').then(m => m.default) - }, - - props: { - reply: { - type: Object, - required: false - }, - renote: { - type: Object, - required: false - }, - mention: { - type: Object, - required: false - }, - initialText: { - type: String, - required: false - }, - initialNote: { - type: Object, - required: false - }, - instant: { - type: Boolean, - required: false, - default: false - } - }, - - data() { - return { - posting: false, - text: '', - files: [], - uploadings: [], - poll: false, - pollChoices: [], - pollMultiple: false, - pollExpiration: [], - useCw: false, - cw: null, - geo: null, - visibility: 'public', - visibleUsers: [], - localOnly: false, - autocomplete: null, - draghover: false, - quoteId: null, - recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), - maxNoteTextLength: 1000 - }; - }, - - computed: { - draftId(): string { - return this.renote - ? `renote:${this.renote.id}` - : this.reply - ? `reply:${this.reply.id}` - : 'note'; - }, - - placeholder(): string { - const xs = [ - this.$t('@.note-placeholders.a'), - this.$t('@.note-placeholders.b'), - this.$t('@.note-placeholders.c'), - this.$t('@.note-placeholders.d'), - this.$t('@.note-placeholders.e'), - this.$t('@.note-placeholders.f') - ]; - const x = xs[Math.floor(Math.random() * xs.length)]; - - return this.renote - ? opts.mobile ? this.$t('@.post-form.option-quote-placeholder') : this.$t('@.post-form.quote-placeholder') - : this.reply - ? this.$t('@.post-form.reply-placeholder') - : x; - }, - - submitText(): string { - return this.renote - ? this.$t('@.post-form.renote') - : this.reply - ? this.$t('@.post-form.reply') - : this.$t('@.post-form.submit'); - }, - - canPost(): boolean { - return !this.posting && - (1 <= this.text.length || 1 <= this.files.length || this.poll || this.renote) && - (length(this.text.trim()) <= this.maxNoteTextLength) && - (!this.poll || this.pollChoices.length >= 2); - } - }, - - created() { - this.$root.getMeta().then(meta => { - this.maxNoteTextLength = meta.maxNoteTextLength; - }); - }, - - mounted() { - if (this.initialText) { - this.text = this.initialText; - } - - if (this.mention) { - this.text = this.mention.host ? `@${this.mention.username}@${toASCII(this.mention.host)}` : `@${this.mention.username}`; - this.text += ' '; - } - - if (this.reply && this.reply.user.host != null) { - this.text = `@${this.reply.user.username}@${toASCII(this.reply.user.host)} `; - } - - if (this.reply && this.reply.text != null) { - const ast = parse(this.reply.text); - - for (const x of extractMentions(ast)) { - const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`; - - // 自分は除外 - if (this.$store.state.i.username == x.username && x.host == null) continue; - if (this.$store.state.i.username == x.username && x.host == host) continue; - - // 重複は除外 - if (this.text.indexOf(`${mention} `) != -1) continue; - - this.text += `${mention} `; - } - } - - // デフォルト公開範囲 - this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility); - - if (this.reply && this.reply.localOnly) { - this.localOnly = true; - } - - // 公開以外へのリプライ時は元の公開範囲を引き継ぐ - if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) { - this.visibility = this.reply.visibility; - if (this.reply.visibility === 'specified') { - this.$root.api('users/show', { - userIds: this.reply.visibleUserIds.filter(uid => uid !== this.$store.state.i.id && uid !== this.reply.userId) - }).then(users => { - this.visibleUsers.push(...users); - }); - - if (this.reply.userId !== this.$store.state.i.id) { - this.$root.api('users/show', { userId: this.reply.userId }).then(user => { - this.visibleUsers.push(user); - }); - } - } - } - - // keep cw when reply - if (this.$store.state.settings.keepCw && this.reply && this.reply.cw) { - this.useCw = true; - this.cw = this.reply.cw; - } - - this.focus(); - - this.$nextTick(() => { - this.focus(); - }); - - this.$nextTick(() => { - // 書きかけの投稿を復元 - if (!this.instant && !this.mention) { - const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftId]; - if (draft) { - this.text = draft.data.text; - this.files = (draft.data.files || []).filter(e => e); - if (draft.data.poll) { - this.poll = true; - this.$nextTick(() => { - (this.$refs.poll as any).set(draft.data.poll); - }); - } - this.$emit('change-attached-files', this.files); - } - } - - // 削除して編集 - if (this.initialNote) { - const init = this.initialNote; - this.text = init.text ? init.text : ''; - this.files = init.files; - this.cw = init.cw; - this.useCw = init.cw != null; - if (init.poll) { - this.poll = true; - this.$nextTick(() => { - (this.$refs.poll as any).set({ - choices: init.poll.choices.map(c => c.text), - multiple: init.poll.multiple - }); - }); - } - // hack 位置情報投稿が動くようになったら適用する - this.geo = null; - this.visibility = init.visibility; - this.localOnly = init.localOnly; - this.quoteId = init.renote ? init.renote.id : null; - } - - this.$nextTick(() => this.watch()); - }); - }, - - methods: { - watch() { - this.$watch('text', () => this.saveDraft()); - this.$watch('poll', () => this.saveDraft()); - this.$watch('files', () => this.saveDraft()); - }, - - trimmedLength(text: string) { - return length(text.trim()); - }, - - addTag(tag: string) { - insertTextAtCursor(this.$refs.text, ` #${tag} `); - }, - - focus() { - (this.$refs.text as any).focus(); - }, - - chooseFile() { - (this.$refs.file as any).click(); - }, - - chooseFileFromDrive() { - this.$chooseDriveFile({ - multiple: true - }).then(files => { - for (const x of files) this.attachMedia(x); - }); - }, - - attachMedia(driveFile) { - this.files.push(driveFile); - this.$emit('change-attached-files', this.files); - }, - - detachMedia(id) { - this.files = this.files.filter(x => x.id != id); - this.$emit('change-attached-files', this.files); - }, - - onChangeFile() { - for (const x of Array.from((this.$refs.file as any).files)) this.upload(x); - }, - - upload(file: File, name?: string) { - (this.$refs.uploader as any).upload(file, this.$store.state.settings.uploadFolder, name); - }, - - onChangeUploadings(uploads) { - this.$emit('change-uploadings', uploads); - }, - - onPollUpdate() { - const got = this.$refs.poll.get(); - this.pollChoices = got.choices; - this.pollMultiple = got.multiple; - this.pollExpiration = [got.expiration, got.expiresAt || got.expiredAfter]; - this.saveDraft(); - }, - - setGeo() { - if (navigator.geolocation == null) { - this.$root.dialog({ - type: 'warning', - text: this.$t('@.post-form.geolocation-alert') - }); - return; - } - - navigator.geolocation.getCurrentPosition(pos => { - this.geo = pos.coords; - this.$emit('geo-attached', this.geo); - }, err => { - this.$root.dialog({ - type: 'error', - title: this.$t('@.post-form.error'), - text: err.message - }); - }, { - enableHighAccuracy: true - }); - }, - - removeGeo() { - this.geo = null; - this.$emit('geo-dettached'); - }, - - setVisibility() { - const w = this.$root.new(MkVisibilityChooser, { - source: this.$refs.visibilityButton, - currentVisibility: this.localOnly ? `local-${this.visibility}` : this.visibility - }); - w.$once('chosen', v => { - this.applyVisibility(v); - }); - this.$once('hook:beforeDestroy', () => { - w.close(); - }); - }, - - applyVisibility(v: string) { - const m = v.match(/^local-(.+)/); - if (m) { - this.localOnly = true; - this.visibility = m[1]; - } else { - this.localOnly = false; - this.visibility = v; - } - }, - - addVisibleUser() { - this.$root.dialog({ - title: this.$t('@.post-form.enter-username'), - user: true - }).then(({ canceled, result: user }) => { - if (canceled) return; - this.visibleUsers.push(user); - }); - }, - - removeVisibleUser(user) { - this.visibleUsers = erase(user, this.visibleUsers); - }, - - clear() { - this.text = ''; - this.files = []; - this.poll = false; - this.quoteId = null; - this.$emit('change-attached-files', this.files); - }, - - onKeydown(e) { - if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post(); - }, - - async onPaste(e: ClipboardEvent) { - for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { - if (item.kind == 'file') { - const file = item.getAsFile(); - const lio = file.name.lastIndexOf('.'); - const ext = lio >= 0 ? file.name.slice(lio) : ''; - const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.settings.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; - const name = this.$store.state.settings.pasteDialog - ? await this.$root.dialog({ - title: this.$t('@.post-form.enter-file-name'), - input: { - default: formatted - }, - allowEmpty: false - }).then(({ canceled, result }) => canceled ? false : result) - : formatted; - if (name) this.upload(file, name); - } - } - - const paste = e.clipboardData.getData('text'); - - if (!this.renote && !this.quoteId && paste.startsWith(url + '/notes/')) { - e.preventDefault(); - - this.$root.dialog({ - type: 'info', - text: this.$t('@.post-form.quote-question'), - showCancelButton: true - }).then(({ canceled }) => { - if (canceled) { - insertTextAtCursor(this.$refs.text, paste); - return; - } - - this.quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; - }); - } - }, - - onDragover(e) { - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == 'mk_drive_file'; - if (isFile || isDriveFile) { - e.preventDefault(); - this.draghover = true; - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } - }, - - onDragenter(e) { - this.draghover = true; - }, - - onDragleave(e) { - this.draghover = false; - }, - - onDrop(e): void { - this.draghover = false; - - // ファイルだったら - if (e.dataTransfer.files.length > 0) { - e.preventDefault(); - for (const x of Array.from(e.dataTransfer.files)) this.upload(x); - return; - } - - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData('mk_drive_file'); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.files.push(file); - this.$emit('change-attached-files', this.files); - e.preventDefault(); - } - //#endregion - }, - - async emoji() { - const Picker = await import('../../desktop/views/components/emoji-picker-dialog.vue').then(m => m.default); - const button = this.$refs.emoji; - const rect = button.getBoundingClientRect(); - const vm = this.$root.new(Picker, { - x: button.offsetWidth + rect.left + window.pageXOffset, - y: rect.top + window.pageYOffset - }); - vm.$once('chosen', emoji => { - insertTextAtCursor(this.$refs.text, emoji); - }); - this.$once('hook:beforeDestroy', () => { - vm.close(); - }); - }, - - saveDraft() { - if (this.instant) return; - - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - - data[this.draftId] = { - updatedAt: new Date(), - data: { - text: this.text, - files: this.files, - poll: this.poll && this.$refs.poll ? (this.$refs.poll as any).get() : undefined - } - }; - - localStorage.setItem('drafts', JSON.stringify(data)); - }, - - deleteDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - - delete data[this.draftId]; - - localStorage.setItem('drafts', JSON.stringify(data)); - }, - - kao() { - this.text += getFace(); - }, - - post() { - this.posting = true; - const viaMobile = opts.mobile && !this.$store.state.settings.disableViaMobile; - this.$root.api('notes/create', { - text: this.text == '' ? undefined : this.text, - fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, - replyId: this.reply ? this.reply.id : undefined, - renoteId: this.renote ? this.renote.id : this.quoteId ? this.quoteId : undefined, - poll: this.poll ? (this.$refs.poll as any).get() : undefined, - cw: this.useCw ? this.cw || '' : undefined, - visibility: this.visibility, - visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, - localOnly: this.localOnly, - geo: this.geo ? { - coordinates: [this.geo.longitude, this.geo.latitude], - altitude: this.geo.altitude, - accuracy: this.geo.accuracy, - altitudeAccuracy: this.geo.altitudeAccuracy, - heading: isNaN(this.geo.heading) ? null : this.geo.heading, - speed: this.geo.speed, - } : null, - viaMobile: viaMobile - }).then(data => { - this.clear(); - this.deleteDraft(); - this.$emit('posted'); - if (opts.onSuccess) opts.onSuccess(this); - }).catch(err => { - if (opts.onSuccess) opts.onFailure(this); - }).then(() => { - this.posting = false; - }); - - if (this.text && this.text != '') { - const hashtags = parse(this.text).filter(x => x.node.type === 'hashtag').map(x => x.node.props.hashtag); - const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; - localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); - } - }, - } -}); diff --git a/src/client/app/common/scripts/room/furniture.ts b/src/client/app/common/scripts/room/furniture.ts deleted file mode 100644 index 7734e32668..0000000000 --- a/src/client/app/common/scripts/room/furniture.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type RoomInfo = { - roomType: string; - carpetColor: string; - furnitures: Furniture[]; -}; - -export type Furniture = { - id: string; // 同じ家具が複数ある場合にそれぞれを識別するためのIDであり、家具IDではない - type: string; // こっちが家具ID(chairとか) - position: { - x: number; - y: number; - z: number; - }; - rotation: { - x: number; - y: number; - z: number; - }; - props?: Record<string, any>; -}; diff --git a/src/client/app/common/scripts/room/furnitures.json5 b/src/client/app/common/scripts/room/furnitures.json5 deleted file mode 100644 index 7c1a90a3f9..0000000000 --- a/src/client/app/common/scripts/room/furnitures.json5 +++ /dev/null @@ -1,397 +0,0 @@ -// 家具メタデータ - -// 家具にはユーザーが設定できるプロパティを設定可能です: -// -// props: { -// <propname>: <proptype> -// } -// -// proptype一覧: -// * image ... 画像選択ダイアログを出し、その画像のURLが格納されます -// * color ... 色選択コントロールを出し、選択された色が格納されます - -// 家具にカスタムテクスチャを適用できるようにするには、textureプロパティに以下の追加の情報を含めます: -// 便宜上そのUVのどの部分にカスタムテクスチャを貼り合わせるかのエリアをテクスチャエリアと呼びます。 -// UVは1024*1024だと仮定します。 -// -// <key>: { -// prop: <プロパティ名>, -// uv: { -// x: <テクスチャエリアX座標>, -// y: <テクスチャエリアY座標>, -// width: <テクスチャエリアの幅>, -// height: <テクスチャエリアの高さ>, -// }, -// } -// -// <key>には、カスタムテクスチャを適用したいメッシュ名を指定します -// <プロパティ名>には、カスタムテクスチャとして使用する画像を格納するプロパティ(前述)名を指定します - -// 家具にカスタムカラーを適用できるようにするには、colorプロパティに以下の追加の情報を含めます: -// -// <key>: <プロパティ名> -// -// <key>には、カスタムカラーを適用したいマテリアル名を指定します -// <プロパティ名>には、カスタムカラーとして使用する色を格納するプロパティ(前述)名を指定します - -[ - { - id: "milk", - place: "floor" - }, - { - id: "bed", - place: "floor" - }, - { - id: "low-table", - place: "floor", - props: { - color: 'color' - }, - color: { - Table: 'color' - } - }, - { - id: "desk", - place: "floor", - props: { - color: 'color' - }, - color: { - Board: 'color' - } - }, - { - id: "chair", - place: "floor", - props: { - color: 'color' - }, - color: { - Chair: 'color' - } - }, - { - id: "chair2", - place: "floor", - props: { - color1: 'color', - color2: 'color' - }, - color: { - Cushion: 'color1', - Leg: 'color2' - } - }, - { - id: "fan", - place: "wall" - }, - { - id: "pc", - place: "floor" - }, - { - id: "plant", - place: "floor" - }, - { - id: "plant2", - place: "floor" - }, - { - id: "eraser", - place: "floor" - }, - { - id: "pencil", - place: "floor" - }, - { - id: "pudding", - place: "floor" - }, - { - id: "cardboard-box", - place: "floor" - }, - { - id: "cardboard-box2", - place: "floor" - }, - { - id: "cardboard-box3", - place: "floor" - }, - { - id: "book", - place: "floor", - props: { - color: 'color' - }, - color: { - Cover: 'color' - } - }, - { - id: "book2", - place: "floor" - }, - { - id: "piano", - place: "floor" - }, - { - id: "facial-tissue", - place: "floor" - }, - { - id: "server", - place: "floor" - }, - { - id: "moon", - place: "floor" - }, - { - id: "corkboard", - place: "wall" - }, - { - id: "mousepad", - place: "floor", - props: { - color: 'color' - }, - color: { - Pad: 'color' - } - }, - { - id: "monitor", - place: "floor", - props: { - screen: 'image' - }, - texture: { - Screen: { - prop: 'screen', - uv: { - x: 0, - y: 434, - width: 1024, - height: 588, - }, - }, - }, - }, - { - id: "tv", - place: "floor", - props: { - screen: 'image' - }, - texture: { - Screen: { - prop: 'screen', - uv: { - x: 0, - y: 434, - width: 1024, - height: 588, - }, - }, - }, - }, - { - id: "keyboard", - place: "floor" - }, - { - id: "carpet-stripe", - place: "floor", - props: { - color1: 'color', - color2: 'color' - }, - color: { - CarpetAreaA: 'color1', - CarpetAreaB: 'color2' - }, - }, - { - id: "mat", - place: "floor", - props: { - color: 'color' - }, - color: { - Mat: 'color' - } - }, - { - id: "color-box", - place: "floor", - props: { - color: 'color' - }, - color: { - main: 'color' - } - }, - { - id: "wall-clock", - place: "wall" - }, - { - id: "cube", - place: "floor", - props: { - color: 'color' - }, - color: { - Cube: 'color' - } - }, - { - id: "photoframe", - place: "wall", - props: { - photo: 'image', - color: 'color' - }, - texture: { - Photo: { - prop: 'photo', - uv: { - x: 0, - y: 342, - width: 1024, - height: 683, - }, - }, - }, - color: { - Frame: 'color' - } - }, - { - id: "pinguin", - place: "floor", - props: { - body: 'color', - belly: 'color' - }, - color: { - Body: 'body', - Belly: 'belly', - } - }, - { - id: "rubik-cube", - place: "floor", - }, - { - id: "poster-h", - place: "wall", - props: { - picture: 'image' - }, - texture: { - Poster: { - prop: 'picture', - uv: { - x: 0, - y: 277, - width: 1024, - height: 745, - }, - }, - }, - }, - { - id: "poster-v", - place: "wall", - props: { - picture: 'image' - }, - texture: { - Poster: { - prop: 'picture', - uv: { - x: 0, - y: 0, - width: 745, - height: 1024, - }, - }, - }, - }, - { - id: "sofa", - place: "floor", - props: { - color: 'color' - }, - color: { - Sofa: 'color' - } - }, - { - id: "spiral", - place: "floor", - props: { - color: 'color' - }, - color: { - Step: 'color' - } - }, - { - id: "bin", - place: "floor", - props: { - color: 'color' - }, - color: { - Bin: 'color' - } - }, - { - id: "cup-noodle", - place: "floor" - }, - { - id: "holo-display", - place: "floor", - props: { - image: 'image' - }, - texture: { - Image_Front: { - prop: 'image', - uv: { - x: 0, - y: 0, - width: 1024, - height: 1024, - }, - }, - Image_Back: { - prop: 'image', - uv: { - x: 0, - y: 0, - width: 1024, - height: 1024, - }, - }, - }, - }, - { - id: 'energy-drink', - place: "floor", - } -] diff --git a/src/client/app/common/scripts/room/room.ts b/src/client/app/common/scripts/room/room.ts deleted file mode 100644 index c2a989c784..0000000000 --- a/src/client/app/common/scripts/room/room.ts +++ /dev/null @@ -1,776 +0,0 @@ -import autobind from 'autobind-decorator'; -import { v4 as uuid } from 'uuid'; -import * as THREE from 'three'; -import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; -import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; -import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; -import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; -import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js'; -import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'; -import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'; -import { Furniture, RoomInfo } from './furniture'; -import { query as urlQuery } from '../../../../../prelude/url'; -const furnitureDefs = require('./furnitures.json5'); - -THREE.ImageUtils.crossOrigin = ''; - -type Options = { - graphicsQuality: Room['graphicsQuality']; - onChangeSelect: Room['onChangeSelect']; - useOrthographicCamera: boolean; -}; - -/** - * MisskeyRoom Core Engine - */ -export class Room { - private clock: THREE.Clock; - private scene: THREE.Scene; - private renderer: THREE.WebGLRenderer; - private camera: THREE.PerspectiveCamera | THREE.OrthographicCamera; - private controls: OrbitControls; - private composer: EffectComposer; - private mixers: THREE.AnimationMixer[] = []; - private furnitureControl: TransformControls; - private roomInfo: RoomInfo; - private graphicsQuality: 'cheep' | 'low' | 'medium' | 'high' | 'ultra'; - private roomObj: THREE.Object3D; - private objects: THREE.Object3D[] = []; - private selectedObject: THREE.Object3D = null; - private onChangeSelect: Function; - private isTransformMode = false; - private renderFrameRequestId: number; - - private get canvas(): HTMLCanvasElement { - return this.renderer.domElement; - } - - private get furnitures(): Furniture[] { - return this.roomInfo.furnitures; - } - - private set furnitures(furnitures: Furniture[]) { - this.roomInfo.furnitures = furnitures; - } - - private get enableShadow() { - return this.graphicsQuality != 'cheep'; - } - - private get usePostFXs() { - return this.graphicsQuality !== 'cheep' && this.graphicsQuality !== 'low'; - } - - private get shadowQuality() { - return ( - this.graphicsQuality === 'ultra' ? 16384 : - this.graphicsQuality === 'high' ? 8192 : - this.graphicsQuality === 'medium' ? 4096 : - this.graphicsQuality === 'low' ? 1024 : - 0); // cheep - } - - constructor(user, isMyRoom, roomInfo: RoomInfo, container, options: Options) { - this.roomInfo = roomInfo; - this.graphicsQuality = options.graphicsQuality; - this.onChangeSelect = options.onChangeSelect; - - this.clock = new THREE.Clock(true); - - //#region Init a scene - this.scene = new THREE.Scene(); - - const width = window.innerWidth; - const height = window.innerHeight; - - //#region Init a renderer - this.renderer = new THREE.WebGLRenderer({ - antialias: false, - stencil: false, - alpha: false, - powerPreference: - this.graphicsQuality === 'ultra' ? 'high-performance' : - this.graphicsQuality === 'high' ? 'high-performance' : - this.graphicsQuality === 'medium' ? 'default' : - this.graphicsQuality === 'low' ? 'low-power' : - 'low-power' // cheep - }); - - this.renderer.setPixelRatio(window.devicePixelRatio); - this.renderer.setSize(width, height); - this.renderer.autoClear = false; - this.renderer.setClearColor(new THREE.Color(0x051f2d)); - this.renderer.shadowMap.enabled = this.enableShadow; - this.renderer.shadowMap.type = - this.graphicsQuality === 'ultra' ? THREE.PCFSoftShadowMap : - this.graphicsQuality === 'high' ? THREE.PCFSoftShadowMap : - this.graphicsQuality === 'medium' ? THREE.PCFShadowMap : - this.graphicsQuality === 'low' ? THREE.BasicShadowMap : - THREE.BasicShadowMap; // cheep - - container.appendChild(this.canvas); - //#endregion - - //#region Init a camera - this.camera = options.useOrthographicCamera - ? new THREE.OrthographicCamera( - width / - 2, width / 2, height / 2, height / - 2, -10, 10) - : new THREE.PerspectiveCamera(45, width / height); - - if (options.useOrthographicCamera) { - this.camera.position.x = 2; - this.camera.position.y = 2; - this.camera.position.z = 2; - this.camera.zoom = 100; - this.camera.updateProjectionMatrix(); - } else { - this.camera.position.x = 5; - this.camera.position.y = 2; - this.camera.position.z = 5; - } - - this.scene.add(this.camera); - //#endregion - - //#region AmbientLight - const ambientLight = new THREE.AmbientLight(0xffffff, 1); - this.scene.add(ambientLight); - //#endregion - - if (this.graphicsQuality !== 'cheep') { - //#region Room light - const roomLight = new THREE.SpotLight(0xffffff, 0.1); - - roomLight.position.set(0, 8, 0); - roomLight.castShadow = this.enableShadow; - roomLight.shadow.bias = -0.0001; - roomLight.shadow.mapSize.width = this.shadowQuality; - roomLight.shadow.mapSize.height = this.shadowQuality; - roomLight.shadow.camera.near = 0.1; - roomLight.shadow.camera.far = 9; - roomLight.shadow.camera.fov = 45; - - this.scene.add(roomLight); - //#endregion - } - - //#region Out light - const outLight1 = new THREE.SpotLight(0xffffff, 0.4); - outLight1.position.set(9, 3, -2); - outLight1.castShadow = this.enableShadow; - outLight1.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある - outLight1.shadow.mapSize.width = this.shadowQuality; - outLight1.shadow.mapSize.height = this.shadowQuality; - outLight1.shadow.camera.near = 6; - outLight1.shadow.camera.far = 15; - outLight1.shadow.camera.fov = 45; - this.scene.add(outLight1); - - const outLight2 = new THREE.SpotLight(0xffffff, 0.2); - outLight2.position.set(-2, 3, 9); - outLight2.castShadow = false; - outLight2.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある - outLight2.shadow.camera.near = 6; - outLight2.shadow.camera.far = 15; - outLight2.shadow.camera.fov = 45; - this.scene.add(outLight2); - //#endregion - - //#region Init a controller - this.controls = new OrbitControls(this.camera, this.canvas); - - this.controls.target.set(0, 1, 0); - this.controls.enableZoom = true; - this.controls.enablePan = isMyRoom; - this.controls.minPolarAngle = 0; - this.controls.maxPolarAngle = Math.PI / 2; - this.controls.minAzimuthAngle = 0; - this.controls.maxAzimuthAngle = Math.PI / 2; - this.controls.enableDamping = true; - this.controls.dampingFactor = 0.2; - this.controls.mouseButtons.LEFT = 1; - this.controls.mouseButtons.MIDDLE = 2; - this.controls.mouseButtons.RIGHT = 0; - //#endregion - - //#region POST FXs - if (!this.usePostFXs) { - this.composer = null; - } else { - const renderTarget = new THREE.WebGLRenderTarget(width, height, { - minFilter: THREE.LinearFilter, - magFilter: THREE.LinearFilter, - format: THREE.RGBFormat, - stencilBuffer: false, - }); - - const fxaa = new ShaderPass(FXAAShader); - fxaa.uniforms['resolution'].value = new THREE.Vector2(1 / width, 1 / height); - fxaa.renderToScreen = true; - - this.composer = new EffectComposer(this.renderer, renderTarget); - this.composer.addPass(new RenderPass(this.scene, this.camera)); - if (this.graphicsQuality === 'ultra') { - this.composer.addPass(new BloomPass(0.25, 30, 128.0, 512)); - } - this.composer.addPass(fxaa); - } - //#endregion - //#endregion - - //#region Label - //#region Avatar - const avatarUrl = `/proxy/?${urlQuery({ url: user.avatarUrl })}`; - - const textureLoader = new THREE.TextureLoader(); - textureLoader.crossOrigin = 'anonymous'; - - const iconTexture = textureLoader.load(avatarUrl); - iconTexture.wrapS = THREE.RepeatWrapping; - iconTexture.wrapT = THREE.RepeatWrapping; - iconTexture.anisotropy = 16; - - const avatarMaterial = new THREE.MeshBasicMaterial({ - map: iconTexture, - side: THREE.DoubleSide, - alphaTest: 0.5 - }); - - const iconGeometry = new THREE.PlaneGeometry(1, 1); - - const avatarObject = new THREE.Mesh(iconGeometry, avatarMaterial); - avatarObject.position.set(-3, 2.5, 2); - avatarObject.rotation.y = Math.PI / 2; - avatarObject.castShadow = false; - - this.scene.add(avatarObject); - //#endregion - - //#region Username - const name = user.username; - - new THREE.FontLoader().load('/assets/fonts/helvetiker_regular.typeface.json', font => { - const nameGeometry = new THREE.TextGeometry(name, { - size: 0.5, - height: 0, - curveSegments: 8, - font: font, - bevelThickness: 0, - bevelSize: 0, - bevelEnabled: false - }); - - const nameMaterial = new THREE.MeshLambertMaterial({ - color: 0xffffff - }); - - const nameObject = new THREE.Mesh(nameGeometry, nameMaterial); - nameObject.position.set(-3, 2.25, 1.25); - nameObject.rotation.y = Math.PI / 2; - nameObject.castShadow = false; - - this.scene.add(nameObject); - }); - //#endregion - //#endregion - - //#region Interaction - if (isMyRoom) { - this.furnitureControl = new TransformControls(this.camera, this.canvas); - this.scene.add(this.furnitureControl); - - // Hover highlight - this.canvas.onmousemove = this.onmousemove; - - // Click - this.canvas.onmousedown = this.onmousedown; - } - //#endregion - - //#region Init room - this.loadRoom(); - //#endregion - - //#region Load furnitures - for (const furniture of this.furnitures) { - this.loadFurniture(furniture).then(obj => { - this.scene.add(obj.scene); - this.objects.push(obj.scene); - }); - } - //#endregion - - // Start render - if (this.usePostFXs) { - this.renderWithPostFXs(); - } else { - this.renderWithoutPostFXs(); - } - } - - @autobind - private renderWithoutPostFXs() { - this.renderFrameRequestId = - window.requestAnimationFrame(this.renderWithoutPostFXs); - - // Update animations - const clock = this.clock.getDelta(); - for (const mixer of this.mixers) { - mixer.update(clock); - } - - this.controls.update(); - this.renderer.render(this.scene, this.camera); - } - - @autobind - private renderWithPostFXs() { - this.renderFrameRequestId = - window.requestAnimationFrame(this.renderWithPostFXs); - - // Update animations - const clock = this.clock.getDelta(); - for (const mixer of this.mixers) { - mixer.update(clock); - } - - this.controls.update(); - this.renderer.clear(); - this.composer.render(); - } - - @autobind - private loadRoom() { - const type = this.roomInfo.roomType; - new GLTFLoader().load(`/assets/room/rooms/${type}/${type}.glb`, gltf => { - gltf.scene.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - - child.receiveShadow = this.enableShadow; - - child.material = new THREE.MeshLambertMaterial({ - color: (child.material as THREE.MeshStandardMaterial).color, - map: (child.material as THREE.MeshStandardMaterial).map, - name: (child.material as THREE.MeshStandardMaterial).name, - }); - - // 異方性フィルタリング - if ((child.material as THREE.MeshLambertMaterial).map && this.graphicsQuality !== 'cheep') { - (child.material as THREE.MeshLambertMaterial).map.minFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshLambertMaterial).map.magFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshLambertMaterial).map.anisotropy = 8; - } - }); - - gltf.scene.position.set(0, 0, 0); - - this.scene.add(gltf.scene); - this.roomObj = gltf.scene; - if (this.roomInfo.roomType === 'default') { - this.applyCarpetColor(); - } - }); - } - - @autobind - private loadFurniture(furniture: Furniture) { - const def = furnitureDefs.find(d => d.id === furniture.type); - return new Promise<GLTF>((res, rej) => { - const loader = new GLTFLoader(); - loader.load(`/assets/room/furnitures/${furniture.type}/${furniture.type}.glb`, gltf => { - const model = gltf.scene; - - // Load animation - if (gltf.animations.length > 0) { - const mixer = new THREE.AnimationMixer(model); - this.mixers.push(mixer); - for (const clip of gltf.animations) { - mixer.clipAction(clip).play(); - } - } - - model.name = furniture.id; - model.position.x = furniture.position.x; - model.position.y = furniture.position.y; - model.position.z = furniture.position.z; - model.rotation.x = furniture.rotation.x; - model.rotation.y = furniture.rotation.y; - model.rotation.z = furniture.rotation.z; - - model.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - child.castShadow = this.enableShadow; - child.receiveShadow = this.enableShadow; - (child.material as THREE.MeshStandardMaterial).metalness = 0; - - // 異方性フィルタリング - if ((child.material as THREE.MeshStandardMaterial).map && this.graphicsQuality !== 'cheep') { - (child.material as THREE.MeshStandardMaterial).map.minFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshStandardMaterial).map.magFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshStandardMaterial).map.anisotropy = 8; - } - }); - - if (def.color) { // カスタムカラー - this.applyCustomColor(model); - } - - if (def.texture) { // カスタムテクスチャ - this.applyCustomTexture(model); - } - - res(gltf); - }, null, rej); - }); - } - - @autobind - private applyCarpetColor() { - this.roomObj.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - if (child.material && - (child.material as THREE.MeshStandardMaterial).name && - (child.material as THREE.MeshStandardMaterial).name === 'Carpet' - ) { - const colorHex = parseInt(this.roomInfo.carpetColor.substr(1), 16); - (child.material as THREE.MeshStandardMaterial).color.setHex(colorHex); - } - }); - } - - @autobind - private applyCustomColor(model: THREE.Object3D) { - const furniture = this.furnitures.find(furniture => furniture.id === model.name); - const def = furnitureDefs.find(d => d.id === furniture.type); - if (def.color == null) return; - model.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - for (const t of Object.keys(def.color)) { - if (!child.material || - !(child.material as THREE.MeshStandardMaterial).name || - (child.material as THREE.MeshStandardMaterial).name !== t - ) continue; - - const prop = def.color[t]; - const val = furniture.props ? furniture.props[prop] : undefined; - - if (val == null) continue; - - const colorHex = parseInt(val.substr(1), 16); - (child.material as THREE.MeshStandardMaterial).color.setHex(colorHex); - } - }); - } - - @autobind - private applyCustomTexture(model: THREE.Object3D) { - const furniture = this.furnitures.find(furniture => furniture.id === model.name); - const def = furnitureDefs.find(d => d.id === furniture.type); - if (def.texture == null) return; - - model.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - for (const t of Object.keys(def.texture)) { - if (child.name !== t) continue; - - const prop = def.texture[t].prop; - const val = furniture.props ? furniture.props[prop] : undefined; - - if (val == null) continue; - - const canvas = document.createElement('canvas'); - canvas.height = 1024; - canvas.width = 1024; - - child.material = new THREE.MeshLambertMaterial({ - emissive: 0x111111, - side: THREE.DoubleSide, - alphaTest: 0.5, - }); - - const img = new Image(); - img.crossOrigin = 'anonymous'; - img.onload = () => { - const uvInfo = def.texture[t].uv; - - const ctx = canvas.getContext('2d'); - ctx.drawImage(img, - 0, 0, img.width, img.height, - uvInfo.x, uvInfo.y, uvInfo.width, uvInfo.height); - - const texture = new THREE.Texture(canvas); - texture.wrapS = THREE.RepeatWrapping; - texture.wrapT = THREE.RepeatWrapping; - texture.anisotropy = 16; - texture.flipY = false; - - (child.material as THREE.MeshLambertMaterial).map = texture; - (child.material as THREE.MeshLambertMaterial).needsUpdate = true; - (child.material as THREE.MeshLambertMaterial).map.needsUpdate = true; - }; - img.src = val; - } - }); - } - - @autobind - private onmousemove(ev: MouseEvent) { - if (this.isTransformMode) return; - - const rect = (ev.target as HTMLElement).getBoundingClientRect(); - const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.canvas.width) * 2 - 1; - const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.canvas.height) * 2 + 1; - const pos = new THREE.Vector2(x, y); - - this.camera.updateMatrixWorld(); - - const raycaster = new THREE.Raycaster(); - raycaster.setFromCamera(pos, this.camera); - - const intersects = raycaster.intersectObjects(this.objects, true); - - for (const object of this.objects) { - if (this.isSelectedObject(object)) continue; - object.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000); - } - }); - } - - if (intersects.length > 0) { - const intersected = this.getRoot(intersects[0].object); - if (this.isSelectedObject(intersected)) return; - intersected.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0x191919); - } - }); - } - } - - @autobind - private onmousedown(ev: MouseEvent) { - if (this.isTransformMode) return; - if (ev.target !== this.canvas || ev.button !== 0) return; - - const rect = (ev.target as HTMLElement).getBoundingClientRect(); - const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.canvas.width) * 2 - 1; - const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.canvas.height) * 2 + 1; - const pos = new THREE.Vector2(x, y); - - this.camera.updateMatrixWorld(); - - const raycaster = new THREE.Raycaster(); - raycaster.setFromCamera(pos, this.camera); - - const intersects = raycaster.intersectObjects(this.objects, true); - - for (const object of this.objects) { - object.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000); - } - }); - } - - if (intersects.length > 0) { - const selectedObj = this.getRoot(intersects[0].object); - this.selectFurniture(selectedObj); - } else { - this.selectedObject = null; - this.onChangeSelect(null); - } - } - - @autobind - private getRoot(obj: THREE.Object3D): THREE.Object3D { - let found = false; - let x = obj.parent; - while (!found) { - if (x.parent.parent == null) { - found = true; - } else { - x = x.parent; - } - } - return x; - } - - @autobind - private isSelectedObject(obj: THREE.Object3D): boolean { - if (this.selectedObject == null) { - return false; - } else { - return obj.name === this.selectedObject.name; - } - } - - @autobind - private selectFurniture(obj: THREE.Object3D) { - this.selectedObject = obj; - this.onChangeSelect(obj); - obj.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0xff0000); - } - }); - } - - /** - * 家具の移動/回転モードにします - * @param type 移動か回転か - */ - @autobind - public enterTransformMode(type: 'translate' | 'rotate') { - this.isTransformMode = true; - this.furnitureControl.setMode(type); - this.furnitureControl.attach(this.selectedObject); - } - - /** - * 家具の移動/回転モードを終了します - */ - @autobind - public exitTransformMode() { - this.isTransformMode = false; - this.furnitureControl.detach(); - } - - /** - * 家具プロパティを更新します - * @param key プロパティ名 - * @param value 値 - */ - @autobind - public updateProp(key: string, value: any) { - const furniture = this.furnitures.find(furniture => furniture.id === this.selectedObject.name); - if (furniture.props == null) furniture.props = {}; - furniture.props[key] = value; - this.applyCustomColor(this.selectedObject); - this.applyCustomTexture(this.selectedObject); - } - - /** - * 部屋に家具を追加します - * @param type 家具の種類 - */ - @autobind - public addFurniture(type: string) { - const furniture = { - id: uuid(), - type: type, - position: { - x: 0, - y: 0, - z: 0, - }, - rotation: { - x: 0, - y: 0, - z: 0, - }, - }; - - this.furnitures.push(furniture); - - this.loadFurniture(furniture).then(obj => { - this.scene.add(obj.scene); - this.objects.push(obj.scene); - }); - } - - /** - * 現在選択されている家具を部屋から削除します - */ - @autobind - public removeFurniture() { - this.exitTransformMode(); - const obj = this.selectedObject; - this.scene.remove(obj); - this.objects = this.objects.filter(object => object.name !== obj.name); - this.furnitures = this.furnitures.filter(furniture => furniture.id !== obj.name); - this.selectedObject = null; - this.onChangeSelect(null); - } - - /** - * 全ての家具を部屋から削除します - */ - @autobind - public removeAllFurnitures() { - this.exitTransformMode(); - for (const obj of this.objects) { - this.scene.remove(obj); - } - this.objects = []; - this.furnitures = []; - this.selectedObject = null; - this.onChangeSelect(null); - } - - /** - * 部屋の床の色を変更します - * @param color 色 - */ - @autobind - public updateCarpetColor(color: string) { - this.roomInfo.carpetColor = color; - this.applyCarpetColor(); - } - - /** - * 部屋の種類を変更します - * @param type 種類 - */ - @autobind - public changeRoomType(type: string) { - this.roomInfo.roomType = type; - this.scene.remove(this.roomObj); - this.loadRoom(); - } - - /** - * 部屋データを取得します - */ - @autobind - public getRoomInfo() { - for (const obj of this.objects) { - const furniture = this.furnitures.find(f => f.id === obj.name); - furniture.position.x = obj.position.x; - furniture.position.y = obj.position.y; - furniture.position.z = obj.position.z; - furniture.rotation.x = obj.rotation.x; - furniture.rotation.y = obj.rotation.y; - furniture.rotation.z = obj.rotation.z; - } - - return this.roomInfo; - } - - /** - * 選択されている家具を取得します - */ - @autobind - public getSelectedObject() { - return this.selectedObject; - } - - @autobind - public findFurnitureById(id: string) { - return this.furnitures.find(furniture => furniture.id === id); - } - - /** - * レンダリングを終了します - */ - @autobind - public destroy() { - // Stop render loop - window.cancelAnimationFrame(this.renderFrameRequestId); - - this.controls.dispose(); - this.scene.dispose(); - } -} diff --git a/src/client/app/common/scripts/search.ts b/src/client/app/common/scripts/search.ts deleted file mode 100644 index 2897ed6318..0000000000 --- a/src/client/app/common/scripts/search.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { faHistory } from '@fortawesome/free-solid-svg-icons'; - -export async function search(v: any, q: string) { - q = q.trim(); - - if (q.startsWith('@') && !q.includes(' ')) { - v.$router.push(`/${q}`); - return; - } - - if (q.startsWith('#')) { - v.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`); - return; - } - - // like 2018/03/12 - if (/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/.test(q.replace(/-/g, '/'))) { - const date = new Date(q.replace(/-/g, '/')); - - // 日付しか指定されてない場合、例えば 2018/03/12 ならユーザーは - // 2018/03/12 のコンテンツを「含む」結果になることを期待するはずなので - // 23時間59分進める(そのままだと 2018/03/12 00:00:00 「まで」の - // 結果になってしまい、2018/03/12 のコンテンツは含まれない) - if (q.replace(/-/g, '/').match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/)) { - date.setHours(23, 59, 59, 999); - } - - v.$root.$emit('warp', date); - v.$root.dialog({ - icon: faHistory, - splash: true, - }); - return; - } - - if (q.startsWith('https://')) { - const dialog = v.$root.dialog({ - type: 'waiting', - text: v.$t('@.fetching-as-ap-object'), - showOkButton: false, - showCancelButton: false, - cancelableByBgClick: false - }); - - try { - const res = await v.$root.api('ap/show', { - uri: q - }); - dialog.close(); - if (res.type == 'User') { - v.$router.push(`/@${res.object.username}@${res.object.host}`); - } else if (res.type == 'Note') { - v.$router.push(`/notes/${res.object.id}`); - } - } catch (e) { - dialog.close(); - // TODO: Show error - } - - return; - } - - v.$router.push(`/search?q=${encodeURIComponent(q)}`); -} diff --git a/src/client/app/common/scripts/should-mute-note.ts b/src/client/app/common/scripts/should-mute-note.ts deleted file mode 100644 index 8fd7888628..0000000000 --- a/src/client/app/common/scripts/should-mute-note.ts +++ /dev/null @@ -1,19 +0,0 @@ -export default function(me, settings, note) { - const isMyNote = me && (note.userId == me.id); - const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null; - - const includesMutedWords = (text: string) => - text - ? settings.mutedWords.some(q => q.length > 0 && !q.some(word => - word.startsWith('/') && word.endsWith('/') ? !(new RegExp(word.substr(1, word.length - 2)).test(text)) : !text.includes(word))) - : false; - - return ( - (!isMyNote && note.reply && includesMutedWords(note.reply.text)) || - (!isMyNote && note.renote && includesMutedWords(note.renote.text)) || - (!settings.showMyRenotes && isMyNote && isPureRenote) || - (!settings.showRenotedMyNotes && isPureRenote && note.renote.userId == me.id) || - (!settings.showLocalRenotes && isPureRenote && note.renote.user.host == null) || - (!isMyNote && includesMutedWords(note.text)) - ); -} diff --git a/src/client/app/common/scripts/stream.ts b/src/client/app/common/scripts/stream.ts deleted file mode 100644 index a1b4223b55..0000000000 --- a/src/client/app/common/scripts/stream.ts +++ /dev/null @@ -1,301 +0,0 @@ -import autobind from 'autobind-decorator'; -import { EventEmitter } from 'eventemitter3'; -import ReconnectingWebsocket from 'reconnecting-websocket'; -import { wsUrl } from '../../config'; -import MiOS from '../../mios'; - -/** - * Misskey stream connection - */ -export default class Stream extends EventEmitter { - private stream: ReconnectingWebsocket; - public state: string; - private sharedConnectionPools: Pool[] = []; - private sharedConnections: SharedConnection[] = []; - private nonSharedConnections: NonSharedConnection[] = []; - - constructor(os: MiOS) { - super(); - - this.state = 'initializing'; - - const user = os.store.state.i; - - this.stream = new ReconnectingWebsocket(wsUrl + (user ? `?i=${user.token}` : ''), '', { minReconnectionDelay: 1 }); // https://github.com/pladaria/reconnecting-websocket/issues/91 - this.stream.addEventListener('open', this.onOpen); - this.stream.addEventListener('close', this.onClose); - this.stream.addEventListener('message', this.onMessage); - } - - @autobind - public useSharedConnection(channel: string): SharedConnection { - let pool = this.sharedConnectionPools.find(p => p.channel === channel); - - if (pool == null) { - pool = new Pool(this, channel); - this.sharedConnectionPools.push(pool); - } - - const connection = new SharedConnection(this, channel, pool); - this.sharedConnections.push(connection); - return connection; - } - - @autobind - public removeSharedConnection(connection: SharedConnection) { - this.sharedConnections = this.sharedConnections.filter(c => c !== connection); - } - - @autobind - public removeSharedConnectionPool(pool: Pool) { - this.sharedConnectionPools = this.sharedConnectionPools.filter(p => p !== pool); - } - - @autobind - public connectToChannel(channel: string, params?: any): NonSharedConnection { - const connection = new NonSharedConnection(this, channel, params); - this.nonSharedConnections.push(connection); - return connection; - } - - @autobind - public disconnectToChannel(connection: NonSharedConnection) { - this.nonSharedConnections = this.nonSharedConnections.filter(c => c !== connection); - } - - /** - * Callback of when open connection - */ - @autobind - private onOpen() { - const isReconnect = this.state == 'reconnecting'; - - this.state = 'connected'; - this.emit('_connected_'); - - // チャンネル再接続 - if (isReconnect) { - for (const p of this.sharedConnectionPools) - p.connect(); - for (const c of this.nonSharedConnections) - c.connect(); - } - } - - /** - * Callback of when close connection - */ - @autobind - private onClose() { - if (this.state == 'connected') { - this.state = 'reconnecting'; - this.emit('_disconnected_'); - } - } - - /** - * Callback of when received a message from connection - */ - @autobind - private onMessage(message) { - const { type, body } = JSON.parse(message.data); - - if (type == 'channel') { - const id = body.id; - - let connections: Connection[]; - - connections = this.sharedConnections.filter(c => c.id === id); - - if (connections.length === 0) { - connections = [this.nonSharedConnections.find(c => c.id === id)]; - } - - for (const c of connections.filter(c => c != null)) { - c.emit(body.type, body.body); - } - } else { - this.emit(type, body); - } - } - - /** - * Send a message to connection - */ - @autobind - public send(typeOrPayload, payload?) { - const data = payload === undefined ? typeOrPayload : { - type: typeOrPayload, - body: payload - }; - - this.stream.send(JSON.stringify(data)); - } - - /** - * Close this connection - */ - @autobind - public close() { - this.stream.removeEventListener('open', this.onOpen); - this.stream.removeEventListener('message', this.onMessage); - } -} - -class Pool { - public channel: string; - public id: string; - protected stream: Stream; - public users = 0; - private disposeTimerId: any; - private isConnected = false; - - constructor(stream: Stream, channel: string) { - this.channel = channel; - this.stream = stream; - - this.id = Math.random().toString().substr(2, 8); - - this.stream.on('_disconnected_', this.onStreamDisconnected); - } - - @autobind - private onStreamDisconnected() { - this.isConnected = false; - } - - @autobind - public inc() { - if (this.users === 0 && !this.isConnected) { - this.connect(); - } - - this.users++; - - // タイマー解除 - if (this.disposeTimerId) { - clearTimeout(this.disposeTimerId); - this.disposeTimerId = null; - } - } - - @autobind - public dec() { - this.users--; - - // そのコネクションの利用者が誰もいなくなったら - if (this.users === 0) { - // また直ぐに再利用される可能性があるので、一定時間待ち、 - // 新たな利用者が現れなければコネクションを切断する - this.disposeTimerId = setTimeout(() => { - this.disconnect(); - }, 3000); - } - } - - @autobind - public connect() { - if (this.isConnected) return; - this.isConnected = true; - this.stream.send('connect', { - channel: this.channel, - id: this.id - }); - } - - @autobind - private disconnect() { - this.stream.off('_disconnected_', this.onStreamDisconnected); - this.stream.send('disconnect', { id: this.id }); - this.stream.removeSharedConnectionPool(this); - } -} - -abstract class Connection extends EventEmitter { - public channel: string; - protected stream: Stream; - public abstract id: string; - - constructor(stream: Stream, channel: string) { - super(); - - this.stream = stream; - this.channel = channel; - } - - @autobind - public send(id: string, typeOrPayload, payload?) { - const type = payload === undefined ? typeOrPayload.type : typeOrPayload; - const body = payload === undefined ? typeOrPayload.body : payload; - - this.stream.send('ch', { - id: id, - type: type, - body: body - }); - } - - public abstract dispose(): void; -} - -class SharedConnection extends Connection { - private pool: Pool; - - public get id(): string { - return this.pool.id; - } - - constructor(stream: Stream, channel: string, pool: Pool) { - super(stream, channel); - - this.pool = pool; - this.pool.inc(); - } - - @autobind - public send(typeOrPayload, payload?) { - super.send(this.pool.id, typeOrPayload, payload); - } - - @autobind - public dispose() { - this.pool.dec(); - this.removeAllListeners(); - this.stream.removeSharedConnection(this); - } -} - -class NonSharedConnection extends Connection { - public id: string; - protected params: any; - - constructor(stream: Stream, channel: string, params?: any) { - super(stream, channel); - - this.params = params; - this.id = Math.random().toString().substr(2, 8); - - this.connect(); - } - - @autobind - public connect() { - this.stream.send('connect', { - channel: this.channel, - id: this.id, - params: this.params - }); - } - - @autobind - public send(typeOrPayload, payload?) { - super.send(this.id, typeOrPayload, payload); - } - - @autobind - public dispose() { - this.removeAllListeners(); - this.stream.send('disconnect', { id: this.id }); - this.stream.disconnectToChannel(this); - } -} |