enhance(frontend): デバイス情報を出力できるように
This commit is contained in:
		
							parent
							
								
									3954837cfa
								
							
						
					
					
						commit
						8ebaaee65e
					
				|  | @ -5577,6 +5577,14 @@ export interface Locale extends ILocale { | |||
|      * 予約 | ||||
|      */ | ||||
|     "scheduled": string; | ||||
|     /** | ||||
|      * デバイス情報 | ||||
|      */ | ||||
|     "deviceInfo": string; | ||||
|     /** | ||||
|      * 技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。 | ||||
|      */ | ||||
|     "deviceInfoDescription": string; | ||||
|     "_compression": { | ||||
|         "_quality": { | ||||
|             /** | ||||
|  |  | |||
|  | @ -1389,6 +1389,8 @@ scheduleToPostOnX: "{x}に投稿を予約します" | |||
| scheduledToPostOnX: "{x}に投稿が予約されています" | ||||
| schedule: "予約" | ||||
| scheduled: "予約" | ||||
| deviceInfo: "デバイス情報" | ||||
| deviceInfoDescription: "技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。" | ||||
| 
 | ||||
| _compression: | ||||
|   _quality: | ||||
|  |  | |||
|  | @ -33,6 +33,7 @@ | |||
| 		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", | ||||
| 		"analytics": "0.8.19", | ||||
| 		"astring": "1.9.0", | ||||
| 		"bowser": "2.12.1", | ||||
| 		"broadcast-channel": "7.1.0", | ||||
| 		"buraha": "0.0.1", | ||||
| 		"canvas-confetti": "1.9.3", | ||||
|  | @ -105,6 +106,7 @@ | |||
| 		"@storybook/vue3-vite": "9.1.8", | ||||
| 		"@tabler/icons-webfont": "3.35.0", | ||||
| 		"@testing-library/vue": "8.1.0", | ||||
| 		"@types/bowser": "1.1.5", | ||||
| 		"@types/canvas-confetti": "1.9.0", | ||||
| 		"@types/estree": "1.0.8", | ||||
| 		"@types/matter-js": "0.20.2", | ||||
|  |  | |||
|  | @ -5,7 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 
 | ||||
| <!-- eslint-disable vue/no-v-html --> | ||||
| <template> | ||||
| <div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }, (darkMode ? $style.dark : $style.light)]" v-html="html"></div> | ||||
| <div | ||||
| 	:class="[$style.codeBlockRoot, { | ||||
| 		[$style.codeEditor]: codeEditor, | ||||
| 		[$style.outerStyle]: !codeEditor && withOuterStyle, | ||||
| 		[$style.dark]: darkMode, | ||||
| 		[$style.light]: !darkMode, | ||||
| 	}]" v-html="html"></div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
|  | @ -15,11 +21,15 @@ import type { BundledLanguage } from 'shiki/langs'; | |||
| import { getHighlighter, getTheme } from '@/utility/code-highlighter.js'; | ||||
| import { store } from '@/store.js'; | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	code: string; | ||||
| 	lang?: string; | ||||
| 	codeEditor?: boolean; | ||||
| }>(); | ||||
| 	withOuterStyle?: boolean; | ||||
| }>(), { | ||||
| 	codeEditor: false, | ||||
| 	withOuterStyle: true, | ||||
| }); | ||||
| 
 | ||||
| const highlighter = await getHighlighter(); | ||||
| const darkMode = store.r.darkMode; | ||||
|  | @ -73,17 +83,13 @@ watch(() => props.lang, (to) => { | |||
| 
 | ||||
| <style module lang="scss"> | ||||
| .codeBlockRoot :global(.shiki) { | ||||
| 	padding: 1em; | ||||
| 	margin: 0; | ||||
| 	overflow: auto; | ||||
| 	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; | ||||
| 
 | ||||
| 	color: var(--shiki-fallback); | ||||
| 	background-color: var(--shiki-fallback-bg); | ||||
| 
 | ||||
| 	& span { | ||||
| 		color: var(--shiki-fallback); | ||||
| 		background-color: var(--shiki-fallback-bg); | ||||
| 	} | ||||
| 
 | ||||
| 	& pre, | ||||
|  | @ -92,26 +98,40 @@ watch(() => props.lang, (to) => { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| .outerStyle.codeBlockRoot :global(.shiki) { | ||||
| 	padding: 1em; | ||||
| 	margin: 0; | ||||
| 	border-radius: 8px; | ||||
| 	border: 1px solid var(--MI_THEME-divider); | ||||
| 	background-color: var(--shiki-fallback-bg); | ||||
| } | ||||
| 
 | ||||
| .light.codeBlockRoot :global(.shiki) { | ||||
| 	color: var(--shiki-light); | ||||
| 	background-color: var(--shiki-light-bg); | ||||
| 
 | ||||
| 	& span { | ||||
| 		color: var(--shiki-light); | ||||
| 		background-color: var(--shiki-light-bg); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| .light.outerStyle.codeBlockRoot :global(.shiki), | ||||
| .light.codeEditor.codeBlockRoot :global(.shiki) { | ||||
| 	background-color: var(--shiki-light-bg); | ||||
| } | ||||
| 
 | ||||
| .dark.codeBlockRoot :global(.shiki) { | ||||
| 	color: var(--shiki-dark); | ||||
| 	background-color: var(--shiki-dark-bg); | ||||
| 
 | ||||
| 	& span { | ||||
| 		color: var(--shiki-dark); | ||||
| 		background-color: var(--shiki-dark-bg); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| .dark.outerStyle.codeBlockRoot :global(.shiki), | ||||
| .dark.codeEditor.codeBlockRoot :global(.shiki) { | ||||
| 	background-color: var(--shiki-dark-bg); | ||||
| } | ||||
| 
 | ||||
| .codeBlockRoot.codeEditor { | ||||
| 	min-width: 100%; | ||||
| 	height: 100%; | ||||
|  |  | |||
|  | @ -5,15 +5,32 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 
 | ||||
| <template> | ||||
| <div :class="$style.codeBlockRoot"> | ||||
| 	<button v-if="copyButton" :class="$style.codeBlockCopyButton" class="_button" @click="copy"> | ||||
| 	<button v-if="copyButton" :class="[$style.codeBlockCopyButton, { [$style.withOuterStyle]: withOuterStyle }]" class="_button" @click="copy"> | ||||
| 		<i class="ti ti-copy"></i> | ||||
| 	</button> | ||||
| 	<Suspense> | ||||
| 		<template #fallback> | ||||
| 			<MkLoading/> | ||||
| 			<pre | ||||
| 				class="_selectable" | ||||
| 				:class="[$style.codeBlockFallbackRoot, { | ||||
| 					[$style.outerStyle]: withOuterStyle, | ||||
| 				}]" | ||||
| 			><code :class="$style.codeBlockFallbackCode">Loading...</code></pre> | ||||
| 		</template> | ||||
| 		<XCode v-if="show && lang" class="_selectable" :code="code" :lang="lang"/> | ||||
| 		<pre v-else-if="show" class="_selectable" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre> | ||||
| 		<XCode | ||||
| 			v-if="show && lang" | ||||
| 			class="_selectable" | ||||
| 			:code="code" | ||||
| 			:lang="lang" | ||||
| 			:withOuterStyle="withOuterStyle" | ||||
| 		/> | ||||
| 		<pre | ||||
| 			v-else-if="show" | ||||
| 			class="_selectable" | ||||
| 			:class="[$style.codeBlockFallbackRoot, { | ||||
| 				[$style.outerStyle]: withOuterStyle, | ||||
| 			}]" | ||||
| 		><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre> | ||||
| 		<button v-else :class="$style.codePlaceholderRoot" @click="show = true"> | ||||
| 			<div :class="$style.codePlaceholderContainer"> | ||||
| 				<div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div> | ||||
|  | @ -26,8 +43,6 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { defineAsyncComponent, ref } from 'vue'; | ||||
| import * as os from '@/os.js'; | ||||
| import MkLoading from '@/components/global/MkLoading.vue'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; | ||||
| import { prefer } from '@/preferences.js'; | ||||
|  | @ -36,10 +51,12 @@ const props = withDefaults(defineProps<{ | |||
| 	code: string; | ||||
| 	forceShow?: boolean; | ||||
| 	copyButton?: boolean; | ||||
| 	withOuterStyle?: boolean; | ||||
| 	lang?: string; | ||||
| }>(), { | ||||
| 	copyButton: true, | ||||
| 	forceShow: false, | ||||
| 	withOuterStyle: true, | ||||
| }); | ||||
| 
 | ||||
| const show = ref(props.forceShow === true ? true : !prefer.s.dataSaver.code); | ||||
|  | @ -58,10 +75,16 @@ function copy() { | |||
| 
 | ||||
| .codeBlockCopyButton { | ||||
| 	position: absolute; | ||||
| 	top: 8px; | ||||
| 	right: 8px; | ||||
| 	opacity: 0.5; | ||||
| 
 | ||||
| 	top: 0; | ||||
| 	right: 0; | ||||
| 
 | ||||
| 	&.withOuterStyle { | ||||
| 		top: 8px; | ||||
| 		right: 8px; | ||||
| 	} | ||||
| 
 | ||||
| 	&:hover { | ||||
| 		opacity: 0.8; | ||||
| 	} | ||||
|  | @ -70,11 +93,17 @@ function copy() { | |||
| .codeBlockFallbackRoot { | ||||
| 	display: block; | ||||
| 	overflow-wrap: anywhere; | ||||
| 	padding: 1em; | ||||
| 	margin: 0; | ||||
| 	overflow: auto; | ||||
| } | ||||
| 
 | ||||
| .outerStyle.codeBlockFallbackRoot { | ||||
| 	background: var(--MI_THEME-bg); | ||||
| 	padding: 1em; | ||||
| 	margin: .5em 0; | ||||
| 	border-radius: 8px; | ||||
| 	border: 1px solid var(--MI_THEME-divider); | ||||
| } | ||||
| 
 | ||||
| .codeBlockFallbackCode { | ||||
| 	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; | ||||
| } | ||||
|  |  | |||
|  | @ -119,6 +119,10 @@ const props = withDefaults(defineProps<{ | |||
| 	canPage: true, | ||||
| }); | ||||
| 
 | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'headerClicked'): void; | ||||
| }>(); | ||||
| 
 | ||||
| const rootEl = useTemplateRef('rootEl'); | ||||
| const asPage = props.canPage && deviceKind === 'smartphone' && prefer.s['experimental.enableFolderPageView']; | ||||
| const bgSame = ref(false); | ||||
|  | @ -165,6 +169,8 @@ let pageId = pageFolderTeleportCount.value; | |||
| pageFolderTeleportCount.value += 1000; | ||||
| 
 | ||||
| async function toggle() { | ||||
| 	emit('headerClicked'); | ||||
| 
 | ||||
| 	if (asPage && !opened.value) { | ||||
| 		pageId++; | ||||
| 		const { dispose } = await popup(MkFolderPage, { | ||||
|  |  | |||
|  | @ -28,17 +28,37 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 					<span v-else style="opacity: 0.7;">({{ i18n.ts.none }})</span> | ||||
| 				</template> | ||||
| 			</MkKeyValue> | ||||
| 			<MkFolder @headerClicked="onHeaderClicked"> | ||||
| 				<template #icon><i class="ti ti-report-search"></i></template> | ||||
| 				<template #label>{{ i18n.ts.deviceInfo }}</template> | ||||
| 				<template #caption>{{ i18n.ts.deviceInfoDescription }}</template> | ||||
| 				<MkLoading v-if="userEnv == null" /> | ||||
| 				<MkCode v-else lang="json" :code="JSON.stringify(userEnv, null, 2)" style="max-height: 300px; overflow: auto;"/> | ||||
| 			</MkFolder> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </PageWithHeader> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { ref } from 'vue'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| import { instance } from '@/instance.js'; | ||||
| import { definePage } from '@/page.js'; | ||||
| import { getUserEnvironment } from '@/utility/get-user-environment.js'; | ||||
| import type { UserEnvironment } from '@/utility/get-user-environment.js'; | ||||
| import MkKeyValue from '@/components/MkKeyValue.vue'; | ||||
| import MkFolder from '@/components/MkFolder.vue'; | ||||
| import MkLink from '@/components/MkLink.vue'; | ||||
| import MkCode from '@/components/MkCode.vue'; | ||||
| 
 | ||||
| const userEnv = ref<UserEnvironment | null>(null); | ||||
| 
 | ||||
| async function onHeaderClicked() { | ||||
| 	if (userEnv.value == null) { | ||||
| 		userEnv.value = await getUserEnvironment(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| definePage(() => ({ | ||||
| 	title: i18n.ts.inquiry, | ||||
|  |  | |||
|  | @ -0,0 +1,54 @@ | |||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and misskey-project | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| export type UserEnvironment = { | ||||
| 	os: string; | ||||
| 	browser: string; | ||||
| 	screenWidth: number; | ||||
| 	screenHeight: number; | ||||
| 	viaGetHighEntropyValues: boolean; | ||||
| }; | ||||
| 
 | ||||
| export async function getUserEnvironment(): Promise<UserEnvironment> { | ||||
| 	if ('userAgentData' in navigator && navigator.userAgentData != null) { | ||||
| 		const uaData: any = await navigator.userAgentData.getHighEntropyValues([ | ||||
| 			'fullVersionList', | ||||
| 			'platformVersion', | ||||
| 		]); | ||||
| 
 | ||||
| 		let osVersion = 'v' + uaData.platformVersion; | ||||
| 
 | ||||
| 		if (uaData.platform === 'Windows' && uaData.platformVersion != null) { | ||||
| 			// https://learn.microsoft.com/ja-jp/microsoft-edge/web-platform/how-to-detect-win11
 | ||||
| 			const majorPlatformVersion = parseInt(uaData.platformVersion.split('.')[0]); | ||||
| 			if(majorPlatformVersion >= 13) { | ||||
| 				osVersion = '11 or later'; | ||||
| 			} else if (majorPlatformVersion > 0) { | ||||
| 				osVersion = '10'; | ||||
| 			} else { | ||||
| 				osVersion = '8.1 or earlier'; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		const browserData = uaData.fullVersionList.find((item) => !/^\s*not.+a.+brand\s*$/i.test(item.brand)); | ||||
| 		return { | ||||
| 			os: `${uaData.platform} ${osVersion}`, | ||||
| 			browser: browserData ? `${browserData.brand} v${browserData.version}` : 'Unknown', | ||||
| 			screenWidth: window.innerWidth, | ||||
| 			screenHeight: window.innerHeight, | ||||
| 			viaGetHighEntropyValues: true, | ||||
| 		}; | ||||
| 	} else { | ||||
| 		const Bowser = (await import('bowser')).default; | ||||
| 		const parsed = Bowser.parse(navigator.userAgent); | ||||
| 		return { | ||||
| 			os: `${parsed.os.name ?? 'Unknown'} ${parsed.os.version ?? ''} ${parsed.os.versionName ? `(${parsed.os.versionName})` : ''}`.trim(), | ||||
| 			browser: `${parsed.browser.name ?? 'Unknown'} ${parsed.browser.version ?? ''}`.trim(), | ||||
| 			screenWidth: window.innerWidth, | ||||
| 			screenHeight: window.innerHeight, | ||||
| 			viaGetHighEntropyValues: false, | ||||
| 		}; | ||||
| 	} | ||||
| } | ||||
|  | @ -757,6 +757,9 @@ importers: | |||
|       astring: | ||||
|         specifier: 1.9.0 | ||||
|         version: 1.9.0 | ||||
|       bowser: | ||||
|         specifier: 2.12.1 | ||||
|         version: 2.12.1 | ||||
|       broadcast-channel: | ||||
|         specifier: 7.1.0 | ||||
|         version: 7.1.0 | ||||
|  | @ -968,6 +971,9 @@ importers: | |||
|       '@testing-library/vue': | ||||
|         specifier: 8.1.0 | ||||
|         version: 8.1.0(@vue/compiler-sfc@3.5.22)(@vue/server-renderer@3.5.22(vue@3.5.22(typescript@5.9.2)))(vue@3.5.22(typescript@5.9.2)) | ||||
|       '@types/bowser': | ||||
|         specifier: 1.1.5 | ||||
|         version: 1.1.5 | ||||
|       '@types/canvas-confetti': | ||||
|         specifier: 1.9.0 | ||||
|         version: 1.9.0 | ||||
|  | @ -4592,6 +4598,10 @@ packages: | |||
|   '@types/body-parser@1.19.6': | ||||
|     resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} | ||||
| 
 | ||||
|   '@types/bowser@1.1.5': | ||||
|     resolution: {integrity: sha512-7qUcVaL3NZ40tLPPlbk9fKL++vT/BBT0vbq+R97KH1lnZQmcZwvqa3rnAcCE48ai2dZm9ETr9TnQXhztGsvPIw==} | ||||
|     deprecated: This is a stub types definition. bowser provides its own type definitions, so you do not need this installed. | ||||
| 
 | ||||
|   '@types/braces@3.0.1': | ||||
|     resolution: {integrity: sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==} | ||||
| 
 | ||||
|  | @ -5552,8 +5562,8 @@ packages: | |||
|   boolbase@1.0.0: | ||||
|     resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} | ||||
| 
 | ||||
|   bowser@2.11.0: | ||||
|     resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} | ||||
|   bowser@2.12.1: | ||||
|     resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==} | ||||
| 
 | ||||
|   brace-expansion@1.1.11: | ||||
|     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} | ||||
|  | @ -12101,14 +12111,14 @@ snapshots: | |||
|     dependencies: | ||||
|       '@aws-sdk/types': 3.862.0 | ||||
|       '@smithy/types': 4.4.0 | ||||
|       bowser: 2.11.0 | ||||
|       bowser: 2.12.1 | ||||
|       tslib: 2.8.1 | ||||
| 
 | ||||
|   '@aws-sdk/util-user-agent-browser@3.893.0': | ||||
|     dependencies: | ||||
|       '@aws-sdk/types': 3.893.0 | ||||
|       '@smithy/types': 4.5.0 | ||||
|       bowser: 2.11.0 | ||||
|       bowser: 2.12.1 | ||||
|       tslib: 2.8.1 | ||||
| 
 | ||||
|   '@aws-sdk/util-user-agent-node@3.873.0': | ||||
|  | @ -14668,7 +14678,7 @@ snapshots: | |||
|       '@smithy/property-provider': 4.1.0 | ||||
|       '@smithy/smithy-client': 4.6.0 | ||||
|       '@smithy/types': 4.4.0 | ||||
|       bowser: 2.11.0 | ||||
|       bowser: 2.12.1 | ||||
|       tslib: 2.8.1 | ||||
| 
 | ||||
|   '@smithy/util-defaults-mode-browser@4.1.4': | ||||
|  | @ -14676,7 +14686,7 @@ snapshots: | |||
|       '@smithy/property-provider': 4.1.1 | ||||
|       '@smithy/smithy-client': 4.6.4 | ||||
|       '@smithy/types': 4.5.0 | ||||
|       bowser: 2.11.0 | ||||
|       bowser: 2.12.1 | ||||
|       tslib: 2.8.1 | ||||
| 
 | ||||
|   '@smithy/util-defaults-mode-node@4.1.0': | ||||
|  | @ -15395,6 +15405,10 @@ snapshots: | |||
|       '@types/connect': 3.4.36 | ||||
|       '@types/node': 22.18.6 | ||||
| 
 | ||||
|   '@types/bowser@1.1.5': | ||||
|     dependencies: | ||||
|       bowser: 2.12.1 | ||||
| 
 | ||||
|   '@types/braces@3.0.1': {} | ||||
| 
 | ||||
|   '@types/canvas-confetti@1.9.0': {} | ||||
|  | @ -16578,7 +16592,7 @@ snapshots: | |||
| 
 | ||||
|   boolbase@1.0.0: {} | ||||
| 
 | ||||
|   bowser@2.11.0: {} | ||||
|   bowser@2.12.1: {} | ||||
| 
 | ||||
|   brace-expansion@1.1.11: | ||||
|     dependencies: | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue