1
0
mirror of https://github.com/hotomoe/hotomoe synced 2024-11-25 15:46:17 +09:00
This commit is contained in:
syuilo 2021-04-11 21:09:35 +09:00
parent c22ff4c556
commit a88e486468
9 changed files with 312 additions and 61 deletions

View File

@ -20,12 +20,16 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.rbusrurv { .rbusrurv {
// CSS
--formXPadding: 32px;
--formYPadding: 32px;
line-height: 1.4em; line-height: 1.4em;
background: var(--bg); background: var(--bg);
padding: 32px; padding: var(--formYPadding) var(--formXPadding);
&:not(.wide).max-width_400px { &:not(.wide).max-width_400px {
padding: 32px 0; --formXPadding: 0px;
> ::v-deep(*) { > ::v-deep(*) {
._formPanel { ._formPanel {

View File

@ -10,9 +10,17 @@
} }
._formLabel { ._formLabel {
position: sticky;
top: var(--stickyTop, 0px);
background: var(--bg);
z-index: 2;
font-size: 80%; font-size: 80%;
padding: 0 16px 8px 16px; margin: -8px calc(var(--formXPadding) * -1) 0 calc(var(--formXPadding) * -1);
opacity: 0.8; padding: 8px calc(16px + var(--formXPadding)) 8px calc(16px + var(--formXPadding));
color: var(--fgTransparentWeak);
background: var(--X17);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
&:empty { &:empty {
display: none; display: none;

View File

@ -93,6 +93,10 @@ export default defineComponent({
os.pageWindow(this.to); os.pageWindow(this.to);
}, },
modalWindow() {
os.modalPageWindow(this.to);
},
popout() { popout() {
popout(this.to); popout(this.to);
}, },
@ -111,6 +115,8 @@ export default defineComponent({
if (this.behavior) { if (this.behavior) {
if (this.behavior === 'window') { if (this.behavior === 'window') {
return this.window(); return this.window();
} else if (this.behavior === 'modalWindow') {
return this.modalWindow();
} }
} }

View File

@ -0,0 +1,211 @@
<template>
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
<div class="hrmcaedk _popup _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }">
<div class="header">
<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button>
<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
<span class="title">
<XHeader :info="pageInfo" :with-back="false"/>
</span>
<button class="_button" @click="$refs.modal.close()"><Fa :icon="faTimes"/></button>
</div>
<div class="body _flat_">
<component :is="component" v-bind="props" :ref="changePage"/>
</div>
</div>
</MkModal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faExternalLinkAlt, faExpandAlt, faLink, faChevronLeft, faColumns, faTimes } from '@fortawesome/free-solid-svg-icons';
import MkModal from '@client/components/ui/modal.vue';
import XHeader from '@client/ui/_common_/header.vue';
import { popout } from '@client/scripts/popout';
import copyToClipboard from '@client/scripts/copy-to-clipboard';
import { resolve } from '@client/router';
import { url } from '@client/config';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
MkModal,
XHeader,
},
inject: {
sideViewHook: {
default: null
}
},
provide() {
return {
navHook: (path) => {
this.navigate(path);
}
};
},
props: {
initialPath: {
type: String,
required: true,
},
initialComponent: {
type: Object,
required: true,
},
initialProps: {
type: Object,
required: false,
default: () => {},
},
},
emits: ['closed'],
data() {
return {
width: 850,
height: 650,
pageInfo: null,
path: this.initialPath,
component: this.initialComponent,
props: this.initialProps,
history: [],
faChevronLeft, faTimes,
};
},
computed: {
url(): string {
return url + this.path;
},
contextmenu() {
return [{
type: 'label',
text: this.path,
}, {
icon: faExpandAlt,
text: this.$ts.showInPage,
action: this.expand
}, this.sideViewHook ? {
icon: faColumns,
text: this.$ts.openInSideView,
action: () => {
this.sideViewHook(this.path);
this.$refs.window.close();
}
} : undefined, {
icon: faExternalLinkAlt,
text: this.$ts.popout,
action: this.popout
}, null, {
icon: faExternalLinkAlt,
text: this.$ts.openInNewTab,
action: () => {
window.open(this.url, '_blank');
this.$refs.window.close();
}
}, {
icon: faLink,
text: this.$ts.copyLink,
action: () => {
copyToClipboard(this.url);
}
}];
},
},
methods: {
changePage(page) {
if (page == null) return;
if (page[symbols.PAGE_INFO]) {
this.pageInfo = page[symbols.PAGE_INFO];
}
},
navigate(path, record = true) {
if (record) this.history.push(this.path);
this.path = path;
const { component, props } = resolve(path);
this.component = component;
this.props = props;
},
back() {
this.navigate(this.history.pop(), false);
},
expand() {
this.$router.push(this.path);
this.$refs.window.close();
},
popout() {
popout(this.path, this.$el);
this.$refs.window.close();
},
},
});
</script>
<style lang="scss" scoped>
.hrmcaedk {
overflow: hidden;
display: flex;
flex-direction: column;
contain: content;
--root-margin: 24px;
@media (max-width: 500px) {
--root-margin: 16px;
}
> .header {
$height: 54px;
$height-narrow: 42px;
display: flex;
flex-shrink: 0;
box-shadow: 0px 1px var(--divider);
> button {
height: $height;
width: $height;
@media (max-width: 500px) {
height: $height-narrow;
width: $height-narrow;
}
}
> .title {
flex: 1;
line-height: $height;
padding-left: 32px;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events: none;
@media (max-width: 500px) {
line-height: $height-narrow;
padding-left: 16px;
}
}
> button + .title {
padding-left: 0;
}
}
> .body {
overflow: auto;
background: var(--bg);
}
}
</style>

View File

@ -1,6 +1,6 @@
<template> <template>
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> <MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
<div class="ebkgoccj _popup _narrow_" @keydown="onKeydown" :style="{ width: `${width}px`, height: height ? `${height}px` : null }"> <div class="ebkgoccj _popup _narrow_" @keydown="onKeydown" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }">
<div class="header"> <div class="header">
<button class="_button" v-if="withOkButton" @click="$emit('close')"><Fa :icon="faTimes"/></button> <button class="_button" v-if="withOkButton" @click="$emit('close')"><Fa :icon="faTimes"/></button>
<span class="title"> <span class="title">
@ -61,6 +61,11 @@ export default defineComponent({
required: false, required: false,
default: true, default: true,
}, },
scroll: {
type: Boolean,
required: false,
default: true,
},
}, },
emits: ['click', 'close', 'closed', 'ok'], emits: ['click', 'close', 'closed', 'ok'],

View File

@ -203,6 +203,15 @@ export function pageWindow(path: string) {
}, {}, 'closed'); }, {}, 'closed');
} }
export function modalPageWindow(path: string) {
const { component, props } = resolve(path);
popup(import('@client/components/modal-page-window.vue'), {
initialPath: path,
initialComponent: markRaw(component),
initialProps: props,
}, {}, 'closed');
}
export function dialog(props: Record<string, any>) { export function dialog(props: Record<string, any>) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup(import('@client/components/dialog.vue'), props, { popup(import('@client/components/dialog.vue'), props, {

View File

@ -1,6 +1,7 @@
<template> <template>
<div class="vvcocwet" :class="{ wide: !narrow }" ref="el"> <div class="vvcocwet" :class="{ wide: !narrow }" ref="el">
<FormBase class="nav" v-if="!narrow || page == null" :force-wide="!narrow"> <div class="nav" v-if="!narrow || page == null">
<FormBase>
<FormGroup> <FormGroup>
<template #label>{{ $ts.basicSettings }}</template> <template #label>{{ $ts.basicSettings }}</template>
<FormLink :active="page === 'profile'" replace to="/settings/profile"><template #icon><Fa :icon="faUser"/></template>{{ $ts.profile }}</FormLink> <FormLink :active="page === 'profile'" replace to="/settings/profile"><template #icon><Fa :icon="faUser"/></template>{{ $ts.profile }}</FormLink>
@ -35,6 +36,7 @@
<FormButton @click="logout" danger>{{ $ts.logout }}</FormButton> <FormButton @click="logout" danger>{{ $ts.logout }}</FormButton>
</FormGroup> </FormGroup>
</FormBase> </FormBase>
</div>
<div class="main"> <div class="main">
<component :is="component" :key="page" @info="onInfo" v-bind="pageProps"/> <component :is="component" :key="page" @info="onInfo" v-bind="pageProps"/>
</div> </div>
@ -64,7 +66,7 @@ export default defineComponent({
}, },
props: { props: {
page: { initialPage: {
type: String, type: String,
required: false required: false
} }
@ -75,6 +77,7 @@ export default defineComponent({
title: i18n.locale.settings, title: i18n.locale.settings,
icon: faCog icon: faCog
}); });
const page = ref(props.initialPage);
const narrow = ref(false); const narrow = ref(false);
const view = ref(null); const view = ref(null);
const el = ref(null); const el = ref(null);
@ -83,8 +86,8 @@ export default defineComponent({
}; };
const pageProps = ref({}); const pageProps = ref({});
const component = computed(() => { const component = computed(() => {
if (props.page == null) return null; if (page.value == null) return null;
switch (props.page) { switch (page.value) {
case 'profile': return defineAsyncComponent(() => import('./profile.vue')); case 'profile': return defineAsyncComponent(() => import('./profile.vue'));
case 'privacy': return defineAsyncComponent(() => import('./privacy.vue')); case 'privacy': return defineAsyncComponent(() => import('./privacy.vue'));
case 'reaction': return defineAsyncComponent(() => import('./reaction.vue')); case 'reaction': return defineAsyncComponent(() => import('./reaction.vue'));
@ -117,10 +120,10 @@ export default defineComponent({
case 'registry': return defineAsyncComponent(() => import('./registry.vue')); case 'registry': return defineAsyncComponent(() => import('./registry.vue'));
case 'experimental-features': return defineAsyncComponent(() => import('./experimental-features.vue')); case 'experimental-features': return defineAsyncComponent(() => import('./experimental-features.vue'));
} }
if (props.page.startsWith('registry/keys/system/')) { if (page.value.startsWith('registry/keys/system/')) {
return defineAsyncComponent(() => import('./registry.keys.vue')); return defineAsyncComponent(() => import('./registry.keys.vue'));
} }
if (props.page.startsWith('registry/value/system/')) { if (page.value.startsWith('registry/value/system/')) {
return defineAsyncComponent(() => import('./registry.value.vue')); return defineAsyncComponent(() => import('./registry.value.vue'));
} }
}); });
@ -128,12 +131,12 @@ export default defineComponent({
watch(component, () => { watch(component, () => {
pageProps.value = {}; pageProps.value = {};
if (props.page) { if (page.value) {
if (props.page.startsWith('registry/keys/system/')) { if (page.value.startsWith('registry/keys/system/')) {
pageProps.value.scope = props.page.replace('registry/keys/system/', '').split('/'); pageProps.value.scope = page.value.replace('registry/keys/system/', '').split('/');
} }
if (props.page.startsWith('registry/value/system/')) { if (page.value.startsWith('registry/value/system/')) {
const path = props.page.replace('registry/value/system/', '').split('/'); const path = page.value.replace('registry/value/system/', '').split('/');
pageProps.value.xKey = path.pop(); pageProps.value.xKey = path.pop();
pageProps.value.scope = path; pageProps.value.scope = path;
} }
@ -144,12 +147,20 @@ export default defineComponent({
}); });
}, { immediate: true }); }, { immediate: true });
watch(() => props.initialPage, () => {
page.value = props.initialPage;
});
onMounted(() => { onMounted(() => {
narrow.value = el.value.offsetWidth < 1025; narrow.value = el.value.offsetWidth < 800;
if (!narrow.value) {
page.value = 'profile';
}
}); });
return { return {
[symbols.PAGE_INFO]: INFO, [symbols.PAGE_INFO]: INFO,
page,
narrow, narrow,
view, view,
el, el,
@ -176,25 +187,20 @@ export default defineComponent({
display: flex; display: flex;
max-width: 1100px; max-width: 1100px;
margin: 0 auto; margin: 0 auto;
height: 100%;
> .nav { > .nav {
width: 32%; width: 32%;
box-sizing: border-box; box-sizing: border-box;
border-right: solid 0.5px var(--divider); border-right: solid 0.5px var(--divider);
overflow: auto;
} }
> .main { > .main {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
overflow: auto;
--baseContentWidth: 100%; --baseContentWidth: 100%;
::v-deep(._section) {
padding: 0 0 32px 0;
& + ._section {
padding-top: 32px;
}
}
} }
} }
} }

View File

@ -22,7 +22,7 @@ export const router = createRouter({
{ path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) }, { path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) },
{ path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, { path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
{ path: '/@:acct/room', props: true, component: page('room/room') }, { path: '/@:acct/room', props: true, component: page('room/room') },
{ path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ page: route.params.page || null }) }, { path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) },
{ path: '/announcements', component: page('announcements') }, { path: '/announcements', component: page('announcements') },
{ path: '/about', component: page('about') }, { path: '/about', component: page('about') },
{ path: '/about-misskey', component: page('about-misskey') }, { path: '/about-misskey', component: page('about-misskey') },

View File

@ -27,7 +27,7 @@
<Fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $ts.more }}</span> <Fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $ts.more }}</span>
<i v-if="otherNavItemIndicated"><Fa :icon="faCircle"/></i> <i v-if="otherNavItemIndicated"><Fa :icon="faCircle"/></i>
</button> </button>
<MkA class="item" active-class="active" to="/settings"> <MkA class="item" active-class="active" to="/settings" :behavior="settingsWindowed ? 'modalWindow' : null">
<Fa :icon="faCog" fixed-width/><span class="text">{{ $ts.settings }}</span> <Fa :icon="faCog" fixed-width/><span class="text">{{ $ts.settings }}</span>
</MkA> </MkA>
</div> </div>
@ -57,6 +57,7 @@ export default defineComponent({
connection: null, connection: null,
menuDef: sidebarDef, menuDef: sidebarDef,
iconOnly: false, iconOnly: false,
settingsWindowed: false,
faGripVertical, faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faProjectDiagram faGripVertical, faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faProjectDiagram
}; };
}, },
@ -102,6 +103,7 @@ export default defineComponent({
methods: { methods: {
calcViewState() { calcViewState() {
this.iconOnly = (window.innerWidth <= 1400) || (this.$store.state.sidebarDisplay === 'icon'); this.iconOnly = (window.innerWidth <= 1400) || (this.$store.state.sidebarDisplay === 'icon');
this.settingsWindowed = (window.innerWidth > 1400);
}, },
post() { post() {