yumechi-no-kuni/src/client/os.ts
syuilo 7199e6f4e0
Migrate to Vue3 (#6587)
* Update reaction.vue

* fix  bug

* wip

* wip

* wjio

* wip

* Revert "wip"

This reverts commit e427f2160adf4e8a4147006e25a89854edab0033.

* wip

* wip

* wip

* Update init.ts

* Update drive-window.vue

* wip

* wip

* Use PascalCase for components

* Use PascalCase for components

* update dep

* wip

* wip

* wip

* Update init.ts

* wip

* Update paging.ts

* Update test.vue

* watch deep

* wip

* lint

* wip

* wip

* wip

* wip

* wiop

* wip

* Update webpack.config.ts

* alllow null poll

* wip

* wip

* wip

* wiop

* UI redesign & refactor (#6714)

* wip

* wip

* wip

* wip

* wip

* Update drive.vue

* Update word-mute.vue

* wip

* wip

* wip

* clean up

* wip

* Update default.vue

* wip

* Update notes.vue

* Update mfm.ts

* Update index.home.vue

* Update post-form.vue

* Update post-form-attaches.vue

* wip

* Update post-form.vue

* Update sidebar.vue

* wip

* wip

* Update index.vue

* wip

* Update default.vue

* Update index.vue

* Update index.vue

* wip

* Update post-form-attaches.vue

* Update note.vue

* wip

* clean up

* Update notes.vue

* wip

* wip

* Update ja-JP.yml

* wip

* wip

* Update index.vue

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update default.vue

* wip

* Update _dark.json5

* wip

* wip

* wip

* clean up

* wip

* wip

* Update index.vue

* Update test.vue

* wip

* wip

* fix

* wip

* wip

* wip

* wip

* clena yop

* wip

* wip

* Update store.ts

* Update messaging-room.vue

* Update default.widgets.vue

* fix

* wip

* wip

* Update modal.vue

* wip

* Update os.ts

* Update os.ts

* Update deck.vue

* Update init.ts

* wip

* Update ja-JP.yml

* v-sizeは単にwindowのresizeを監視するだけで良いかもしれない

* Update modal.vue

* wip

* Update tooltip.ts

* wip

* wip

* wip

* wip

* wip

* Update image-viewer.vue

* wip

* wip

* Update style.scss

* Update style.scss

* Update visitor.vue

* wip

* Update init.ts

* Update init.ts

* wip

* wip

* Update visitor.vue

* Update visitor.vue

* Update visitor.vue

* Update visitor.vue

* wip

* wip

* Update modal.vue

* Update header.vue

* Update menu.vue

* Update about.vue

* Update about-misskey.vue

* wip

* wip

* Update visitor.vue

* Update tooltip.ts

* wip

* Update drive.vue

* wip

* Update style.scss

* Update header.vue

* wip

* wip

* Update users.user.vue

* Update announcements.vue

* wip

* wip

* wip

* Update emojis.vue

* wip

* Update emojis.vue

* Update style.scss

* Update users.vue

* wip

* Update style.scss

* wip

* Update welcome.entrance.vue

* Update radio.vue

* Update size.ts

* Update emoji-edit-dialog.vue

* wip

* Update emojis.vue

* wip

* Update emojis.vue

* Update emojis.vue

* Update emojis.vue

* wip

* wip

* wip

* wip

* Update file-dialog.vue

* wip

* wip

* Update token-generate-window.vue

* Update notification-setting-window.vue

* wip

* wip

* Update _error_.vue

* Update ja-JP.yml

* wip

* wip

* Update store.ts

* Update emojis.vue

* Update emojis.vue

* Update emojis.vue

* Update announcements.vue

* Update store.ts

* wip

* Update page-editor.vue

* wip

* wip

* Update modal.vue

* wip

* Update select-file.ts

* Update timeline.vue

* Update emojis.vue

* Update os.ts

* wip

* Update user-select.vue

* Update mfm.ts

* Update get-file-info.ts

* Update drive.vue

* Update init.ts

* Update mfm.ts

* wip

* wip

* Update window.vue

* Update note.vue

* wip

* wip

* Update user-info.vue

* wip

* wip

* wip

* wip

* wip

* Update header.vue

* Update header.vue

* wip

* Update explore.vue

* wip

* wip

* wip

* Update webpack.config.ts

* wip

* wip

* wip

* wip

* wip

* wip

* Update autocomplete.ts

* wip

* wip

* wip

* Update toast.vue

* wip

* Update post-form-dialog.vue

* wip

* wip

* wip

* wip

* wip

* Update users.vue

* wip

* Update explore.vue

* wip

* wip

* wip

* Update package.json

* wip

* Update icon-dialog.vue

* wip

* wip

* Update user-preview.ts

* wip

* wip

* wip

* wip

* wip

* Update instance.vue

* Update user-name.vue

* Update federation.vue

* Update instance.vue

* wip

* wip

* Update tag.vue

* wip

* wip

* wip

* wip

* wip

* Update instance.vue

* wip

* Update os.ts

* Update os.ts

* wip

* wip

* wip

* Update router.ts

* wip

* Update init.ts

* Update note.vue

* Update messages.vue

* wip

* wip

* wip

* wip

* wip

* google

* wip

* wip

* wip

* wip

* Update theme-editor.vue

* wip

* wip

* Update room.vue

* Update channel-editor.vue

* wip

* Update window.vue

* Update window.vue

* wip

* Update window.vue

* Update window.vue

* wip

* Update menu.vue

* wip

* wip

* wip

* wip

* Update messaging-room.vue

* wip

* Update post-form.vue

* Update default.widgets.vue

* Update window.vue

* wip
2020-10-17 20:12:00 +09:00

364 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vue';
import { EventEmitter } from 'eventemitter3';
import Stream from '@/scripts/stream';
import { store } from '@/store';
import { apiUrl } from '@/config';
import MkPostFormDialog from '@/components/post-form-dialog.vue';
const ua = navigator.userAgent.toLowerCase();
export const isMobile = /mobile|iphone|ipad|android/.test(ua);
export const stream = new Stream();
export const pendingApiRequestsCount = ref(0);
export const windows = new Map();
export function api(endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) {
pendingApiRequestsCount.value++;
if (_DEV_) {
performance.mark(_PERF_PREFIX_ + 'api:begin');
}
const onFinally = () => {
pendingApiRequestsCount.value--;
if (_DEV_) {
performance.mark(_PERF_PREFIX_ + 'api:end');
performance.measure(_PERF_PREFIX_ + 'api',
_PERF_PREFIX_ + 'api:begin',
_PERF_PREFIX_ + 'api:end');
}
};
const promise = new Promise((resolve, reject) => {
// Append a credential
if (store.getters.isSignedIn) (data as any).i = store.state.i.token;
if (token !== undefined) (data as any).i = token;
// Send request
fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'omit',
cache: 'no-cache'
}).then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
resolve();
} else {
reject(body.error);
}
}).catch(reject);
});
promise.then(onFinally, onFinally);
return promise;
}
export function apiWithDialog(endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined, onSuccess?: (res: any) => void, onFailure?: (e: Error) => void) {
const showing = ref(true);
const state = ref('waiting');
const promise = api(endpoint, data, token);
promise.then(res => {
if (onSuccess) {
showing.value = false;
onSuccess(res);
} else {
state.value = 'success';
setTimeout(() => {
showing.value = false;
}, 1000);
}
}).catch(e => {
showing.value = false;
if (onFailure) {
onFailure(e);
} else {
dialog({
type: 'error',
text: e
});
}
});
popup(defineAsyncComponent(() => import('@/components/icon-dialog.vue')), {
type: state,
showing: showing
}, {}, 'closed');
return promise;
}
function isModule(x: any): x is typeof import('*.vue') {
return x.default != null;
}
export const popups = ref([]) as Ref<{
id: any;
component: any;
props: Record<string, any>;
}[]>;
export function popup(component: Component | typeof import('*.vue'), props: Record<string, any>, events = {}, disposeEvent?: string) {
if (isModule(component)) component = component.default;
markRaw(component);
const id = Math.random().toString(); // TODO: uuidとか使う
const dispose = () => {
if (_DEV_) console.log('os:popup close', id, component, props, events);
// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ
setTimeout(() => {
popups.value = popups.value.filter(popup => popup.id !== id);
}, 0);
};
const state = {
component,
props,
events: disposeEvent ? {
...events,
[disposeEvent]: dispose
} : events,
id,
};
if (_DEV_) console.log('os:popup open', id, component, props, events);
popups.value.push(state);
return {
dispose,
};
}
export function pageWindow(url: string, component: Component | typeof import('*.vue'), props: Record<string, any>) {
popup(defineAsyncComponent(() => import('@/components/page-window.vue')), {
initialUrl: url,
initialComponent: markRaw(component),
initialProps: props,
}, {}, 'closed');
}
export function dialog(props: Record<string, any>) {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/dialog.vue')), props, {
done: result => {
resolve(result ? result : { canceled: true });
},
}, 'closed');
});
}
export function success() {
return new Promise((resolve, reject) => {
const showing = ref(true);
setTimeout(() => {
showing.value = false;
}, 1000);
popup(defineAsyncComponent(() => import('@/components/icon-dialog.vue')), {
type: 'success',
showing: showing
}, {
done: () => resolve(),
}, 'closed');
});
}
export function waiting() {
return new Promise((resolve, reject) => {
const showing = ref(true);
popup(defineAsyncComponent(() => import('@/components/icon-dialog.vue')), {
type: 'waiting',
showing: showing
}, {
done: () => resolve(),
}, 'closed');
});
}
export function form(title, form) {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/form-dialog.vue')), { title, form }, {
done: result => {
resolve(result);
},
}, 'closed');
});
}
export async function selectUser() {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/user-select-dialog.vue')), {}, {
ok: user => {
resolve(user);
},
}, 'closed');
});
}
export async function selectDriveFile(multiple: boolean) {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/drive-window.vue')), {
type: 'file',
multiple
}, {
done: files => {
if (files) {
resolve(multiple ? files : files[0]);
}
},
}, 'closed');
});
}
export async function selectDriveFolder(multiple: boolean) {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/drive-window.vue')), {
type: 'folder',
multiple
}, {
done: folders => {
if (folders) {
resolve(multiple ? folders : folders[0]);
}
},
}, 'closed');
});
}
export async function pickEmoji(src?: HTMLElement) {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/emoji-picker.vue')), {
src
}, {
done: emoji => {
resolve(emoji);
},
}, 'closed');
});
}
export function modalMenu(items: any[], src?: HTMLElement, options?: { align?: string; viaKeyboard?: boolean }) {
return new Promise((resolve, reject) => {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/ui/modal-menu.vue')), {
items,
src,
align: options?.align,
viaKeyboard: options?.viaKeyboard
}, {
closed: () => {
resolve();
dispose();
},
});
});
}
export function contextMenu(items: any[], ev: MouseEvent) {
ev.preventDefault();
return new Promise((resolve, reject) => {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/ui/context-menu.vue')), {
items,
ev,
}, {
closed: () => {
resolve();
dispose();
},
});
});
}
export function post(props: Record<string, any>) {
return new Promise((resolve, reject) => {
// NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない
const { dispose } = popup(MkPostFormDialog, props, {
closed: () => {
resolve();
dispose();
},
});
});
}
export function sound(type: string) {
if (store.state.device.sfxVolume === 0) return;
const sound = store.state.device['sfx' + type.substr(0, 1).toUpperCase() + type.substr(1)];
if (sound == null) return;
const audio = new Audio(`/assets/sounds/${sound}.mp3`);
audio.volume = store.state.device.sfxVolume;
audio.play();
}
export const deckGlobalEvents = new EventEmitter();
export const uploads = ref([]);
export function upload(file: File, folder?: any, name?: string) {
if (folder && typeof folder == 'object') folder = folder.id;
return new Promise((resolve, reject) => {
const id = Math.random();
const reader = new FileReader();
reader.onload = (e) => {
const ctx = reactive({
id: id,
name: name || file.name || 'untitled',
progressMax: undefined,
progressValue: undefined,
img: window.URL.createObjectURL(file)
});
uploads.value.push(ctx);
const data = new FormData();
data.append('i', store.state.i.token);
data.append('force', 'true');
data.append('file', file);
if (folder) data.append('folderId', folder);
if (name) data.append('name', name);
const xhr = new XMLHttpRequest();
xhr.open('POST', apiUrl + '/drive/files/create', true);
xhr.onload = (e: any) => {
const driveFile = JSON.parse(e.target.response);
resolve(driveFile);
uploads.value = uploads.value.filter(x => x.id != id);
};
xhr.upload.onprogress = e => {
if (e.lengthComputable) {
ctx.progressMax = e.total;
ctx.progressValue = e.loaded;
}
};
xhr.send(data);
};
reader.readAsArrayBuffer(file);
});
}
/*
export function checkExistence(fileData: ArrayBuffer): Promise<any> {
return new Promise((resolve, reject) => {
const data = new FormData();
data.append('md5', getMD5(fileData));
os.api('drive/files/find-by-hash', {
md5: getMD5(fileData)
}).then(resp => {
resolve(resp.length > 0 ? resp[0] : null);
});
});
}*/