wip
This commit is contained in:
		
							parent
							
								
									b2a6257f93
								
							
						
					
					
						commit
						a1e57841e7
					
				
					 22 changed files with 304 additions and 372 deletions
				
			
		|  | @ -13,6 +13,7 @@ | |||
| 		"vue/html-self-closing": false, | ||||
| 		"vue/no-unused-vars": false, | ||||
| 		"no-console": 0, | ||||
| 		"no-unused-vars": 0 | ||||
| 		"no-unused-vars": 0, | ||||
| 		"no-empty": 0 | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -16,6 +16,38 @@ declare const _API_URL_: string; | |||
| declare const _SW_PUBLICKEY_: string; | ||||
| //#endregion
 | ||||
| 
 | ||||
| export type API = { | ||||
| 	chooseDriveFile: (opts: { | ||||
| 		title?: string; | ||||
| 		currentFolder?: any; | ||||
| 		multiple?: boolean; | ||||
| 	}) => Promise<any>; | ||||
| 
 | ||||
| 	chooseDriveFolder: (opts: { | ||||
| 		title?: string; | ||||
| 		currentFolder?: any; | ||||
| 	}) => Promise<any>; | ||||
| 
 | ||||
| 	dialog: (opts: { | ||||
| 		title: string; | ||||
| 		text: string; | ||||
| 		actions: Array<{ | ||||
| 			text: string; | ||||
| 			id?: string; | ||||
| 		}>; | ||||
| 	}) => Promise<string>; | ||||
| 
 | ||||
| 	input: (opts: { | ||||
| 		title: string; | ||||
| 		placeholder?: string; | ||||
| 		default?: string; | ||||
| 	}) => Promise<string>; | ||||
| 
 | ||||
| 	post: () => void; | ||||
| 
 | ||||
| 	notify: (message: string) => void; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Misskey Operating System | ||||
|  */ | ||||
|  | @ -49,6 +81,8 @@ export default class MiOS extends EventEmitter { | |||
| 		return localStorage.getItem('debug') == 'true'; | ||||
| 	} | ||||
| 
 | ||||
| 	public apis: API; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * A connection manager of home stream | ||||
| 	 */ | ||||
|  |  | |||
							
								
								
									
										21
									
								
								src/web/app/common/scripts/fuck-ad-block.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/web/app/common/scripts/fuck-ad-block.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| require('fuckadblock'); | ||||
| 
 | ||||
| declare const fuckAdBlock: any; | ||||
| 
 | ||||
| export default (os) => { | ||||
| 	function adBlockDetected() { | ||||
| 		os.apis.dialog({ | ||||
| 			title: '%fa:exclamation-triangle%広告ブロッカーを無効にしてください', | ||||
| 			text: '<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。', | ||||
| 			actins: [{ | ||||
| 				text: 'OK' | ||||
| 			}] | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	if (fuckAdBlock === undefined) { | ||||
| 		adBlockDetected(); | ||||
| 	} else { | ||||
| 		fuckAdBlock.onDetected(adBlockDetected); | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										10
									
								
								src/web/app/desktop/api/notify.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/web/app/desktop/api/notify.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| import Notification from '../views/components/ui-notification.vue'; | ||||
| 
 | ||||
| export default function(message) { | ||||
| 	const vm = new Notification({ | ||||
| 		propsData: { | ||||
| 			message | ||||
| 		} | ||||
| 	}).$mount(); | ||||
| 	document.body.appendChild(vm.$el); | ||||
| } | ||||
							
								
								
									
										95
									
								
								src/web/app/desktop/api/update-avatar.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/web/app/desktop/api/update-avatar.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | |||
| import OS from '../../common/mios'; | ||||
| import { apiUrl } from '../../config'; | ||||
| import CropWindow from '../views/components/crop-window.vue'; | ||||
| import ProgressDialog from '../views/components/progress-dialog.vue'; | ||||
| 
 | ||||
| export default (os: OS) => (cb, file = null) => { | ||||
| 	const fileSelected = file => { | ||||
| 
 | ||||
| 		const w = new CropWindow({ | ||||
| 			propsData: { | ||||
| 				file: file, | ||||
| 				title: 'アバターとして表示する部分を選択', | ||||
| 				aspectRatio: 1 / 1 | ||||
| 			} | ||||
| 		}).$mount(); | ||||
| 
 | ||||
| 		w.$once('cropped', blob => { | ||||
| 			const data = new FormData(); | ||||
| 			data.append('i', os.i.token); | ||||
| 			data.append('file', blob, file.name + '.cropped.png'); | ||||
| 
 | ||||
| 			os.api('drive/folders/find', { | ||||
| 				name: 'アイコン' | ||||
| 			}).then(iconFolder => { | ||||
| 				if (iconFolder.length === 0) { | ||||
| 					os.api('drive/folders/create', { | ||||
| 						name: 'アイコン' | ||||
| 					}).then(iconFolder => { | ||||
| 						upload(data, iconFolder); | ||||
| 					}); | ||||
| 				} else { | ||||
| 					upload(data, iconFolder[0]); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
| 
 | ||||
| 		w.$once('skipped', () => { | ||||
| 			set(file); | ||||
| 		}); | ||||
| 
 | ||||
| 		document.body.appendChild(w.$el); | ||||
| 	}; | ||||
| 
 | ||||
| 	const upload = (data, folder) => { | ||||
| 		const dialog = new ProgressDialog({ | ||||
| 			propsData: { | ||||
| 				title: '新しいアバターをアップロードしています' | ||||
| 			} | ||||
| 		}).$mount(); | ||||
| 		document.body.appendChild(dialog.$el); | ||||
| 
 | ||||
| 		if (folder) data.append('folder_id', folder.id); | ||||
| 
 | ||||
| 		const xhr = new XMLHttpRequest(); | ||||
| 		xhr.open('POST', apiUrl + '/drive/files/create', true); | ||||
| 		xhr.onload = e => { | ||||
| 			const file = JSON.parse((e.target as any).response); | ||||
| 			(dialog as any).close(); | ||||
| 			set(file); | ||||
| 		}; | ||||
| 
 | ||||
| 		xhr.upload.onprogress = e => { | ||||
| 			if (e.lengthComputable) (dialog as any).updateProgress(e.loaded, e.total); | ||||
| 		}; | ||||
| 
 | ||||
| 		xhr.send(data); | ||||
| 	}; | ||||
| 
 | ||||
| 	const set = file => { | ||||
| 		os.api('i/update', { | ||||
| 			avatar_id: file.id | ||||
| 		}).then(i => { | ||||
| 			os.apis.dialog({ | ||||
| 				title: '%fa:info-circle%アバターを更新しました', | ||||
| 				text: '新しいアバターが反映されるまで時間がかかる場合があります。', | ||||
| 				actions: [{ | ||||
| 					text: 'わかった' | ||||
| 				}] | ||||
| 			}); | ||||
| 
 | ||||
| 			if (cb) cb(i); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| 	if (file) { | ||||
| 		fileSelected(file); | ||||
| 	} else { | ||||
| 		os.apis.chooseDriveFile({ | ||||
| 			multiple: false, | ||||
| 			title: '%fa:image%アバターにする画像を選択' | ||||
| 		}).then(file => { | ||||
| 			fileSelected(file); | ||||
| 		}); | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										95
									
								
								src/web/app/desktop/api/update-banner.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/web/app/desktop/api/update-banner.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | |||
| import OS from '../../common/mios'; | ||||
| import { apiUrl } from '../../config'; | ||||
| import CropWindow from '../views/components/crop-window.vue'; | ||||
| import ProgressDialog from '../views/components/progress-dialog.vue'; | ||||
| 
 | ||||
| export default (os: OS) => (cb, file = null) => { | ||||
| 	const fileSelected = file => { | ||||
| 
 | ||||
| 		const w = new CropWindow({ | ||||
| 			propsData: { | ||||
| 				file: file, | ||||
| 				title: 'バナーとして表示する部分を選択', | ||||
| 				aspectRatio: 16 / 9 | ||||
| 			} | ||||
| 		}).$mount(); | ||||
| 
 | ||||
| 		w.$once('cropped', blob => { | ||||
| 			const data = new FormData(); | ||||
| 			data.append('i', os.i.token); | ||||
| 			data.append('file', blob, file.name + '.cropped.png'); | ||||
| 
 | ||||
| 			os.api('drive/folders/find', { | ||||
| 				name: 'バナー' | ||||
| 			}).then(bannerFolder => { | ||||
| 				if (bannerFolder.length === 0) { | ||||
| 					os.api('drive/folders/create', { | ||||
| 						name: 'バナー' | ||||
| 					}).then(iconFolder => { | ||||
| 						upload(data, iconFolder); | ||||
| 					}); | ||||
| 				} else { | ||||
| 					upload(data, bannerFolder[0]); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
| 
 | ||||
| 		w.$once('skipped', () => { | ||||
| 			set(file); | ||||
| 		}); | ||||
| 
 | ||||
| 		document.body.appendChild(w.$el); | ||||
| 	}; | ||||
| 
 | ||||
| 	const upload = (data, folder) => { | ||||
| 		const dialog = new ProgressDialog({ | ||||
| 			propsData: { | ||||
| 				title: '新しいバナーをアップロードしています' | ||||
| 			} | ||||
| 		}).$mount(); | ||||
| 		document.body.appendChild(dialog.$el); | ||||
| 
 | ||||
| 		if (folder) data.append('folder_id', folder.id); | ||||
| 
 | ||||
| 		const xhr = new XMLHttpRequest(); | ||||
| 		xhr.open('POST', apiUrl + '/drive/files/create', true); | ||||
| 		xhr.onload = e => { | ||||
| 			const file = JSON.parse((e.target as any).response); | ||||
| 			(dialog as any).close(); | ||||
| 			set(file); | ||||
| 		}; | ||||
| 
 | ||||
| 		xhr.upload.onprogress = e => { | ||||
| 			if (e.lengthComputable) (dialog as any).updateProgress(e.loaded, e.total); | ||||
| 		}; | ||||
| 
 | ||||
| 		xhr.send(data); | ||||
| 	}; | ||||
| 
 | ||||
| 	const set = file => { | ||||
| 		os.api('i/update', { | ||||
| 			avatar_id: file.id | ||||
| 		}).then(i => { | ||||
| 			os.apis.dialog({ | ||||
| 				title: '%fa:info-circle%バナーを更新しました', | ||||
| 				text: '新しいバナーが反映されるまで時間がかかる場合があります。', | ||||
| 				actions: [{ | ||||
| 					text: 'わかった' | ||||
| 				}] | ||||
| 			}); | ||||
| 
 | ||||
| 			if (cb) cb(i); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| 	if (file) { | ||||
| 		fileSelected(file); | ||||
| 	} else { | ||||
| 		os.apis.chooseDriveFile({ | ||||
| 			multiple: false, | ||||
| 			title: '%fa:image%バナーにする画像を選択' | ||||
| 		}).then(file => { | ||||
| 			fileSelected(file); | ||||
| 		}); | ||||
| 	} | ||||
| }; | ||||
|  | @ -6,7 +6,7 @@ | |||
| import './style.styl'; | ||||
| 
 | ||||
| import init from '../init'; | ||||
| import fuckAdBlock from './scripts/fuck-ad-block'; | ||||
| import fuckAdBlock from '../common/scripts/fuck-ad-block'; | ||||
| import HomeStreamManager from '../common/scripts/streaming/home-stream-manager'; | ||||
| import composeNotification from '../common/scripts/compose-notification'; | ||||
| 
 | ||||
|  | @ -15,6 +15,9 @@ import chooseDriveFile from './api/choose-drive-file'; | |||
| import dialog from './api/dialog'; | ||||
| import input from './api/input'; | ||||
| import post from './api/post'; | ||||
| import notify from './api/notify'; | ||||
| import updateAvatar from './api/update-avatar'; | ||||
| import updateBanner from './api/update-banner'; | ||||
| 
 | ||||
| import MkIndex from './views/pages/index.vue'; | ||||
| import MkUser from './views/pages/user/user.vue'; | ||||
|  | @ -25,24 +28,27 @@ import MkDrive from './views/pages/drive.vue'; | |||
|  * init | ||||
|  */ | ||||
| init(async (launch) => { | ||||
| 	/** | ||||
| 	 * Fuck AD Block | ||||
| 	 */ | ||||
| 	fuckAdBlock(); | ||||
| 
 | ||||
| 	// Register directives
 | ||||
| 	require('./views/directives'); | ||||
| 
 | ||||
| 	// Register components
 | ||||
| 	require('./views/components'); | ||||
| 
 | ||||
| 	const app = launch({ | ||||
| 	const [app, os] = launch(os => ({ | ||||
| 		chooseDriveFolder, | ||||
| 		chooseDriveFile, | ||||
| 		dialog, | ||||
| 		input, | ||||
| 		post | ||||
| 	}); | ||||
| 		post, | ||||
| 		notify, | ||||
| 		updateAvatar: updateAvatar(os), | ||||
| 		updateBanner: updateBanner(os) | ||||
| 	})); | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Fuck AD Block | ||||
| 	 */ | ||||
| 	fuckAdBlock(os); | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Init Notification | ||||
|  |  | |||
|  | @ -1,16 +0,0 @@ | |||
| import * as riot from 'riot'; | ||||
| 
 | ||||
| export default (title, text, buttons, canThrough?, onThrough?) => { | ||||
| 	const dialog = document.body.appendChild(document.createElement('mk-dialog')); | ||||
| 	const controller = riot.observable(); | ||||
| 	(riot as any).mount(dialog, { | ||||
| 		controller: controller, | ||||
| 		title: title, | ||||
| 		text: text, | ||||
| 		buttons: buttons, | ||||
| 		canThrough: canThrough, | ||||
| 		onThrough: onThrough | ||||
| 	}); | ||||
| 	controller.trigger('open'); | ||||
| 	return controller; | ||||
| }; | ||||
|  | @ -1,20 +0,0 @@ | |||
| require('fuckadblock'); | ||||
| import dialog from './dialog'; | ||||
| 
 | ||||
| declare const fuckAdBlock: any; | ||||
| 
 | ||||
| export default () => { | ||||
| 	if (fuckAdBlock === undefined) { | ||||
| 		adBlockDetected(); | ||||
| 	} else { | ||||
| 		fuckAdBlock.onDetected(adBlockDetected); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| function adBlockDetected() { | ||||
| 	dialog('%fa:exclamation-triangle%広告ブロッカーを無効にしてください', | ||||
| 		'<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。', | ||||
| 	[{ | ||||
| 		text: 'OK' | ||||
| 	}]); | ||||
| } | ||||
|  | @ -1,12 +0,0 @@ | |||
| import * as riot from 'riot'; | ||||
| 
 | ||||
| export default (title, placeholder, defaultValue, onOk, onCancel) => { | ||||
| 	const dialog = document.body.appendChild(document.createElement('mk-input-dialog')); | ||||
| 	return (riot as any).mount(dialog, { | ||||
| 		title: title, | ||||
| 		placeholder: placeholder, | ||||
| 		'default': defaultValue, | ||||
| 		onOk: onOk, | ||||
| 		onCancel: onCancel | ||||
| 	}); | ||||
| }; | ||||
|  | @ -1,8 +0,0 @@ | |||
| import dialog from './dialog'; | ||||
| 
 | ||||
| export default () => { | ||||
| 	dialog('%fa:exclamation-triangle%Not implemented yet', | ||||
| 		'要求された操作は実装されていません。<br>→<a href="https://github.com/syuilo/misskey" target="_blank">Misskeyの開発に参加する</a>', [{ | ||||
| 		text: 'OK' | ||||
| 	}]); | ||||
| }; | ||||
|  | @ -1,8 +0,0 @@ | |||
| import * as riot from 'riot'; | ||||
| 
 | ||||
| export default message => { | ||||
| 	const notification = document.body.appendChild(document.createElement('mk-ui-notification')); | ||||
| 	(riot as any).mount(notification, { | ||||
| 		message: message | ||||
| 	}); | ||||
| }; | ||||
|  | @ -1,61 +0,0 @@ | |||
| /** | ||||
|  * 要素をスクロールに追従させる | ||||
|  */ | ||||
| export default class ScrollFollower { | ||||
| 	private follower: Element; | ||||
| 	private containerTop: number; | ||||
| 	private topPadding: number; | ||||
| 
 | ||||
| 	constructor(follower: Element, topPadding: number) { | ||||
| 		//#region
 | ||||
| 		this.follow = this.follow.bind(this); | ||||
| 		//#endregion
 | ||||
| 
 | ||||
| 		this.follower = follower; | ||||
| 		this.containerTop = follower.getBoundingClientRect().top; | ||||
| 		this.topPadding = topPadding; | ||||
| 
 | ||||
| 		window.addEventListener('scroll', this.follow); | ||||
| 		window.addEventListener('resize', this.follow); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 追従解除 | ||||
| 	 */ | ||||
| 	public dispose() { | ||||
| 		window.removeEventListener('scroll', this.follow); | ||||
| 		window.removeEventListener('resize', this.follow); | ||||
| 	} | ||||
| 
 | ||||
| 	private follow() { | ||||
| 		const windowBottom = window.scrollY + window.innerHeight; | ||||
| 		const windowTop = window.scrollY + this.topPadding; | ||||
| 
 | ||||
| 		const rect = this.follower.getBoundingClientRect(); | ||||
| 		const followerBottom = (rect.top + window.scrollY) + rect.height; | ||||
| 		const screenHeight = window.innerHeight - this.topPadding; | ||||
| 
 | ||||
| 		// スクロールの上部(+余白)がフォロワーコンテナの上部よりも上方にある
 | ||||
| 		if (window.scrollY + this.topPadding < this.containerTop) { | ||||
| 			// フォロワーをコンテナの最上部に合わせる
 | ||||
| 			(this.follower.parentNode as any).style.marginTop = '0px'; | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// スクロールの下部がフォロワーの下部よりも下方にある かつ 表示領域の縦幅がフォロワーの縦幅よりも狭い
 | ||||
| 		if (windowBottom > followerBottom && rect.height > screenHeight) { | ||||
| 			// フォロワーの下部をスクロール下部に合わせる
 | ||||
| 			const top = (windowBottom - rect.height) - this.containerTop; | ||||
| 			(this.follower.parentNode as any).style.marginTop = `${top}px`; | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// スクロールの上部(+余白)がフォロワーの上部よりも上方にある または 表示領域の縦幅がフォロワーの縦幅よりも広い
 | ||||
| 		if (windowTop < rect.top + window.scrollY || rect.height < screenHeight) { | ||||
| 			// フォロワーの上部をスクロール上部(+余白)に合わせる
 | ||||
| 			const top = windowTop - this.containerTop; | ||||
| 			(this.follower.parentNode as any).style.marginTop = `${top}px`; | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -1,88 +0,0 @@ | |||
| declare const _API_URL_: string; | ||||
| 
 | ||||
| import * as riot from 'riot'; | ||||
| import dialog from './dialog'; | ||||
| import api from '../../common/scripts/api'; | ||||
| 
 | ||||
| export default (I, cb, file = null) => { | ||||
| 	const fileSelected = file => { | ||||
| 		const cropper = (riot as any).mount(document.body.appendChild(document.createElement('mk-crop-window')), { | ||||
| 			file: file, | ||||
| 			title: 'アバターとして表示する部分を選択', | ||||
| 			aspectRatio: 1 / 1 | ||||
| 		})[0]; | ||||
| 
 | ||||
| 		cropper.on('cropped', blob => { | ||||
| 			const data = new FormData(); | ||||
| 			data.append('i', I.token); | ||||
| 			data.append('file', blob, file.name + '.cropped.png'); | ||||
| 
 | ||||
| 			api(I, 'drive/folders/find', { | ||||
| 				name: 'アイコン' | ||||
| 			}).then(iconFolder => { | ||||
| 				if (iconFolder.length === 0) { | ||||
| 					api(I, 'drive/folders/create', { | ||||
| 						name: 'アイコン' | ||||
| 					}).then(iconFolder => { | ||||
| 						upload(data, iconFolder); | ||||
| 					}); | ||||
| 				} else { | ||||
| 					upload(data, iconFolder[0]); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
| 
 | ||||
| 		cropper.on('skipped', () => { | ||||
| 			set(file); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| 	const upload = (data, folder) => { | ||||
| 		const progress = (riot as any).mount(document.body.appendChild(document.createElement('mk-progress-dialog')), { | ||||
| 			title: '新しいアバターをアップロードしています' | ||||
| 		})[0]; | ||||
| 
 | ||||
| 		if (folder) data.append('folder_id', folder.id); | ||||
| 
 | ||||
| 		const xhr = new XMLHttpRequest(); | ||||
| 		xhr.open('POST', _API_URL_ + '/drive/files/create', true); | ||||
| 		xhr.onload = e => { | ||||
| 			const file = JSON.parse((e.target as any).response); | ||||
| 			progress.close(); | ||||
| 			set(file); | ||||
| 		}; | ||||
| 
 | ||||
| 		xhr.upload.onprogress = e => { | ||||
| 			if (e.lengthComputable) progress.updateProgress(e.loaded, e.total); | ||||
| 		}; | ||||
| 
 | ||||
| 		xhr.send(data); | ||||
| 	}; | ||||
| 
 | ||||
| 	const set = file => { | ||||
| 		api(I, 'i/update', { | ||||
| 			avatar_id: file.id | ||||
| 		}).then(i => { | ||||
| 			dialog('%fa:info-circle%アバターを更新しました', | ||||
| 				'新しいアバターが反映されるまで時間がかかる場合があります。', | ||||
| 			[{ | ||||
| 				text: 'わかった' | ||||
| 			}]); | ||||
| 
 | ||||
| 			if (cb) cb(i); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| 	if (file) { | ||||
| 		fileSelected(file); | ||||
| 	} else { | ||||
| 		const browser = (riot as any).mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { | ||||
| 			multiple: false, | ||||
| 			title: '%fa:image%アバターにする画像を選択' | ||||
| 		})[0]; | ||||
| 
 | ||||
| 		browser.one('selected', file => { | ||||
| 			fileSelected(file); | ||||
| 		}); | ||||
| 	} | ||||
| }; | ||||
|  | @ -1,88 +0,0 @@ | |||
| declare const _API_URL_: string; | ||||
| 
 | ||||
| import * as riot from 'riot'; | ||||
| import dialog from './dialog'; | ||||
| import api from '../../common/scripts/api'; | ||||
| 
 | ||||
| export default (I, cb, file = null) => { | ||||
| 	const fileSelected = file => { | ||||
| 		const cropper = (riot as any).mount(document.body.appendChild(document.createElement('mk-crop-window')), { | ||||
| 			file: file, | ||||
| 			title: 'バナーとして表示する部分を選択', | ||||
| 			aspectRatio: 16 / 9 | ||||
| 		})[0]; | ||||
| 
 | ||||
| 		cropper.on('cropped', blob => { | ||||
| 			const data = new FormData(); | ||||
| 			data.append('i', I.token); | ||||
| 			data.append('file', blob, file.name + '.cropped.png'); | ||||
| 
 | ||||
| 			api(I, 'drive/folders/find', { | ||||
| 				name: 'バナー' | ||||
| 			}).then(iconFolder => { | ||||
| 				if (iconFolder.length === 0) { | ||||
| 					api(I, 'drive/folders/create', { | ||||
| 						name: 'バナー' | ||||
| 					}).then(iconFolder => { | ||||
| 						upload(data, iconFolder); | ||||
| 					}); | ||||
| 				} else { | ||||
| 					upload(data, iconFolder[0]); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
| 
 | ||||
| 		cropper.on('skipped', () => { | ||||
| 			set(file); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| 	const upload = (data, folder) => { | ||||
| 		const progress = (riot as any).mount(document.body.appendChild(document.createElement('mk-progress-dialog')), { | ||||
| 			title: '新しいバナーをアップロードしています' | ||||
| 		})[0]; | ||||
| 
 | ||||
| 		if (folder) data.append('folder_id', folder.id); | ||||
| 
 | ||||
| 		const xhr = new XMLHttpRequest(); | ||||
| 		xhr.open('POST', _API_URL_ + '/drive/files/create', true); | ||||
| 		xhr.onload = e => { | ||||
| 			const file = JSON.parse((e.target as any).response); | ||||
| 			progress.close(); | ||||
| 			set(file); | ||||
| 		}; | ||||
| 
 | ||||
| 		xhr.upload.onprogress = e => { | ||||
| 			if (e.lengthComputable) progress.updateProgress(e.loaded, e.total); | ||||
| 		}; | ||||
| 
 | ||||
| 		xhr.send(data); | ||||
| 	}; | ||||
| 
 | ||||
| 	const set = file => { | ||||
| 		api(I, 'i/update', { | ||||
| 			banner_id: file.id | ||||
| 		}).then(i => { | ||||
| 			dialog('%fa:info-circle%バナーを更新しました', | ||||
| 				'新しいバナーが反映されるまで時間がかかる場合があります。', | ||||
| 			[{ | ||||
| 				text: 'わかりました。' | ||||
| 			}]); | ||||
| 
 | ||||
| 			if (cb) cb(i); | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
| 	if (file) { | ||||
| 		fileSelected(file); | ||||
| 	} else { | ||||
| 		const browser = (riot as any).mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { | ||||
| 			multiple: false, | ||||
| 			title: '%fa:image%バナーにする画像を選択' | ||||
| 		})[0]; | ||||
| 
 | ||||
| 		browser.one('selected', file => { | ||||
| 			fileSelected(file); | ||||
| 		}); | ||||
| 	} | ||||
| }; | ||||
|  | @ -5,7 +5,7 @@ | |||
| 		<header v-html="title"></header> | ||||
| 		<div class="body" v-html="text"></div> | ||||
| 		<div class="buttons"> | ||||
| 			<button v-for="button in buttons" @click="click(button)" :key="button.id">{{ button.text }}</button> | ||||
| 			<button v-for="button in buttons" @click="click(button)">{{ button.text }}</button> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  |  | |||
|  | @ -40,7 +40,6 @@ import Vue from 'vue'; | |||
| import * as Sortable from 'sortablejs'; | ||||
| import Autocomplete from '../../scripts/autocomplete'; | ||||
| import getKao from '../../../common/scripts/get-kao'; | ||||
| import notify from '../../scripts/notify'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['reply', 'repost'], | ||||
|  | @ -200,13 +199,13 @@ export default Vue.extend({ | |||
| 				this.clear(); | ||||
| 				this.deleteDraft(); | ||||
| 				this.$emit('posted'); | ||||
| 				notify(this.repost | ||||
| 				(this as any).apis.notify(this.repost | ||||
| 					? '%i18n:desktop.tags.mk-post-form.reposted%' | ||||
| 					: this.reply | ||||
| 						? '%i18n:desktop.tags.mk-post-form.replied%' | ||||
| 						: '%i18n:desktop.tags.mk-post-form.posted%'); | ||||
| 			}).catch(err => { | ||||
| 				notify(this.repost | ||||
| 				(this as any).apis.notify(this.repost | ||||
| 					? '%i18n:desktop.tags.mk-post-form.repost-failed%' | ||||
| 					: this.reply | ||||
| 						? '%i18n:desktop.tags.mk-post-form.reply-failed%' | ||||
|  |  | |||
|  | @ -16,7 +16,6 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import notify from '../../scripts/notify'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['post'], | ||||
|  | @ -33,9 +32,9 @@ export default Vue.extend({ | |||
| 				repost_id: this.post.id | ||||
| 			}).then(data => { | ||||
| 				this.$emit('posted'); | ||||
| 				notify('%i18n:desktop.tags.mk-repost-form.success%'); | ||||
| 				(this as any).apis.notify('%i18n:desktop.tags.mk-repost-form.success%'); | ||||
| 			}).catch(err => { | ||||
| 				notify('%i18n:desktop.tags.mk-repost-form.failure%'); | ||||
| 				(this as any).apis.notify('%i18n:desktop.tags.mk-repost-form.failure%'); | ||||
| 			}).then(() => { | ||||
| 				this.wait = false; | ||||
| 			}); | ||||
|  |  | |||
|  | @ -27,7 +27,6 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import notify from '../../scripts/notify'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
|  | @ -59,7 +58,7 @@ export default Vue.extend({ | |||
| 				description: this.description || null, | ||||
| 				birthday: this.birthday || null | ||||
| 			}).then(() => { | ||||
| 				notify('プロフィールを更新しました'); | ||||
| 				(this as any).apis.notify('プロフィールを更新しました'); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -11,24 +11,26 @@ import * as anime from 'animejs'; | |||
| export default Vue.extend({ | ||||
| 	props: ['message'], | ||||
| 	mounted() { | ||||
| 		anime({ | ||||
| 			targets: this.$el, | ||||
| 			opacity: 1, | ||||
| 			translateY: [-64, 0], | ||||
| 			easing: 'easeOutElastic', | ||||
| 			duration: 500 | ||||
| 		}); | ||||
| 
 | ||||
| 		setTimeout(() => { | ||||
| 		this.$nextTick(() => { | ||||
| 			anime({ | ||||
| 				targets: this.$el, | ||||
| 				opacity: 0, | ||||
| 				translateY: -64, | ||||
| 				duration: 500, | ||||
| 				easing: 'easeInElastic', | ||||
| 				complete: () => this.$destroy() | ||||
| 				opacity: 1, | ||||
| 				translateY: [-64, 0], | ||||
| 				easing: 'easeOutElastic', | ||||
| 				duration: 500 | ||||
| 			}); | ||||
| 		}, 6000); | ||||
| 
 | ||||
| 			setTimeout(() => { | ||||
| 				anime({ | ||||
| 					targets: this.$el, | ||||
| 					opacity: 0, | ||||
| 					translateY: -64, | ||||
| 					duration: 500, | ||||
| 					easing: 'easeInElastic', | ||||
| 					complete: () => this.$destroy() | ||||
| 				}); | ||||
| 			}, 6000); | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -22,7 +22,6 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import updateBanner from '../../../scripts/update-banner'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'], | ||||
|  | @ -53,7 +52,7 @@ export default Vue.extend({ | |||
| 		onBannerClick() { | ||||
| 			if (!(this as any).os.isSignedIn || (this as any).os.i.id != this.user.id) return; | ||||
| 
 | ||||
| 			updateBanner((this as any).os.i, i => { | ||||
| 			(this as any).apis.updateBanner((this as any).os.i, i => { | ||||
| 				this.user.banner_url = i.banner_url; | ||||
| 			}); | ||||
| 		} | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ Vue.mixin({ | |||
| import App from './app.vue'; | ||||
| 
 | ||||
| import checkForUpdate from './common/scripts/check-for-update'; | ||||
| import MiOS from './common/mios'; | ||||
| import MiOS, { API } from './common/mios'; | ||||
| 
 | ||||
| /** | ||||
|  * APP ENTRY POINT! | ||||
|  | @ -79,59 +79,32 @@ if (localStorage.getItem('should-refresh') == 'true') { | |||
| 	location.reload(true); | ||||
| } | ||||
| 
 | ||||
| type API = { | ||||
| 	chooseDriveFile: (opts: { | ||||
| 		title?: string; | ||||
| 		currentFolder?: any; | ||||
| 		multiple?: boolean; | ||||
| 	}) => Promise<any>; | ||||
| 
 | ||||
| 	chooseDriveFolder: (opts: { | ||||
| 		title?: string; | ||||
| 		currentFolder?: any; | ||||
| 	}) => Promise<any>; | ||||
| 
 | ||||
| 	dialog: (opts: { | ||||
| 		title: string; | ||||
| 		text: string; | ||||
| 		actions: Array<{ | ||||
| 			text: string; | ||||
| 			id: string; | ||||
| 		}>; | ||||
| 	}) => Promise<string>; | ||||
| 
 | ||||
| 	input: (opts: { | ||||
| 		title: string; | ||||
| 		placeholder?: string; | ||||
| 		default?: string; | ||||
| 	}) => Promise<string>; | ||||
| 
 | ||||
| 	post: () => void; | ||||
| }; | ||||
| 
 | ||||
| // MiOSを初期化してコールバックする
 | ||||
| export default (callback: (launch: (api: API) => Vue) => void, sw = false) => { | ||||
| export default (callback: (launch: (api: (os: MiOS) => API) => [Vue, MiOS]) => void, sw = false) => { | ||||
| 	const os = new MiOS(sw); | ||||
| 
 | ||||
| 	os.init(() => { | ||||
| 		// アプリ基底要素マウント
 | ||||
| 		document.body.innerHTML = '<div id="app"></div>'; | ||||
| 
 | ||||
| 		const launch = (api: API) => { | ||||
| 		const launch = (api: (os: MiOS) => API) => { | ||||
| 			os.apis = api(os); | ||||
| 			Vue.mixin({ | ||||
| 				created() { | ||||
| 					(this as any).os = os; | ||||
| 					(this as any).api = os.api; | ||||
| 					(this as any).apis = api; | ||||
| 					(this as any).apis = os.apis; | ||||
| 				} | ||||
| 			}); | ||||
| 
 | ||||
| 			return new Vue({ | ||||
| 			const app = new Vue({ | ||||
| 				router: new VueRouter({ | ||||
| 					mode: 'history' | ||||
| 				}), | ||||
| 				render: createEl => createEl(App) | ||||
| 			}).$mount('#app'); | ||||
| 
 | ||||
| 			return [app, os] as [Vue, MiOS]; | ||||
| 		}; | ||||
| 
 | ||||
| 		try { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue