[Feature][UI] Toggle layout. (#11)
diff --git a/studio/components/log/index.module.scss b/studio/components/log/index.module.scss
index ad16ccc..d783027 100644
--- a/studio/components/log/index.module.scss
+++ b/studio/components/log/index.module.scss
@@ -28,3 +28,6 @@
}
}
}
+.log-wrap {
+ position: relative;
+}
diff --git a/studio/components/log/index.tsx b/studio/components/log/index.tsx
index f319cd1..c4758a4 100644
--- a/studio/components/log/index.tsx
+++ b/studio/components/log/index.tsx
@@ -17,6 +17,12 @@
import { NTabs, NTabPane, NLog, NConfigProvider } from 'naive-ui'
import { defineComponent, PropType } from 'vue'
+import {
+ ResizeHandler,
+ ResizedOptions,
+ HandlerPlacement
+} from '../resize-handler'
+import { useLayoutStore } from '@/store/layout'
import hljs from 'highlight.js/lib/core'
import styles from './index.module.scss'
@@ -48,15 +54,30 @@
]
}))
+ const layoutStore = useLayoutStore()
+
+ const onResized = (resized: ResizedOptions) => {
+ let height = layoutStore.editorHeight - resized.y
+ if (height < 40) height = 35
+ if (height > layoutStore.editorHeight) height = layoutStore.editorHeight
+ layoutStore.setLogHeight(height)
+ }
+
return () => {
return (
- <NTabs type='card' closable size='small'>
- <NTabPane name='运行日志'>
- <NConfigProvider hljs={hljs} class={styles.hljs}>
- <NLog log={props.value} language='studio-log' />
- </NConfigProvider>
- </NTabPane>
- </NTabs>
+ <div
+ class={styles['log-wrap']}
+ style={{ height: `${layoutStore.getLogHeight}px` }}
+ >
+ <NTabs type='card' closable size='small'>
+ <NTabPane name='运行日志'>
+ <NConfigProvider hljs={hljs} class={styles.hljs}>
+ <NLog log={props.value} language='studio-log' />
+ </NConfigProvider>
+ </NTabPane>
+ </NTabs>
+ <ResizeHandler placement={HandlerPlacement.T} onResized={onResized} />
+ </div>
)
}
}
diff --git a/studio/components/monaco/index.tsx b/studio/components/monaco/index.tsx
index a60c3ff..b1d85c7 100644
--- a/studio/components/monaco/index.tsx
+++ b/studio/components/monaco/index.tsx
@@ -19,7 +19,7 @@
import * as monaco from 'monaco-editor'
import { useFormItem } from 'naive-ui/es/_mixins'
import { call } from 'naive-ui/es/_utils'
-import { ResizeHandler, ResizedOptions } from '../resize-handler'
+import { useLayoutStore } from '@/store/layout'
import type {
MaybeArray,
OnUpdateValue,
@@ -51,10 +51,10 @@
props,
emits: ['change', 'focus', 'blur'],
setup(props, ctx) {
- const heightRef = ref(450)
const editorRef = ref()
let editor = null as monaco.editor.IStandaloneCodeEditor | null
const formItem = useFormItem({})
+ const layoutStore = useLayoutStore()
const initMonacoEditor = () => {
const dom = editorRef.value
@@ -89,26 +89,16 @@
onMounted(() => initMonacoEditor())
return () => (
- <>
- <div
- ref={editorRef}
- style={{
- height: `${heightRef.value}px`,
- width: '100%',
- border: '1px solid #eee'
- }}
- />
- <ResizeHandler
- placement='y'
- onResized={(resized: ResizedOptions) => {
- let height = resized.y
- if (height < 100) height = 100
- if (height > window.innerHeight * 0.5)
- height = window.innerHeight * 0.5
- heightRef.value = height
- }}
- />
- </>
+ <div
+ ref={editorRef}
+ style={{
+ height: `${
+ layoutStore.getEditorHeight - layoutStore.getLogHeight - 90
+ }px`,
+ width: '100%',
+ border: '1px solid #eee'
+ }}
+ />
)
}
})
diff --git a/studio/components/resize-handler/index.tsx b/studio/components/resize-handler/index.tsx
index 9872934..1d55809 100644
--- a/studio/components/resize-handler/index.tsx
+++ b/studio/components/resize-handler/index.tsx
@@ -27,6 +27,7 @@
}
export { ResizedOptions }
+export { HandlerPlacement } from './types'
export const ResizeHandler = defineComponent({
name: 'resize-handler',
@@ -52,7 +53,7 @@
const classes = getClasses(placement)
const onMouseDown = (ev: MouseEvent) => {
- document.body.style[`user-select-${placement}` as any] = 'none'
+ document.body.style[`user-select` as any] = 'none'
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
}
@@ -65,7 +66,7 @@
const onMouseUp = (ev: MouseEvent) => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
- document.body.style[`user-select-${placement}` as any] = 'auto'
+ document.body.style[`user-select` as any] = 'auto'
}
onMounted(() => {
diff --git a/studio/components/studio-content/index.module.scss b/studio/components/studio-content/index.module.scss
index 2fab968..a620cdb 100644
--- a/studio/components/studio-content/index.module.scss
+++ b/studio/components/studio-content/index.module.scss
@@ -22,8 +22,14 @@
.editor {
display: flex;
flex-direction: column;
+ height: 100%;
}
.tab {
flex: 1;
+ :global {
+ .n-tabs {
+ height: 100%;
+ }
+ }
}
diff --git a/studio/components/studio-content/index.tsx b/studio/components/studio-content/index.tsx
index 56adb6c..d8ede37 100644
--- a/studio/components/studio-content/index.tsx
+++ b/studio/components/studio-content/index.tsx
@@ -15,19 +15,25 @@
* limitations under the License.
*/
-import { defineComponent } from 'vue'
+import { defineComponent, ref, onMounted } from 'vue'
import { NDialogProvider, NLayoutContent } from 'naive-ui'
import { Toolbar } from '../toolbar'
import { Tabs } from '../tab'
+import { useLayoutStore } from '@/store/layout'
import styles from './index.module.scss'
export const StudioContent = defineComponent({
name: 'studio-content',
setup() {
+ const editorRef = ref()
+ const layoutStore = useLayoutStore()
+ onMounted(() => {
+ layoutStore.setEditorHeight(editorRef.value.clientHeight)
+ })
return () => (
<NDialogProvider>
<NLayoutContent class={styles['studio-content']}>
- <div class={styles['editor']}>
+ <div class={styles['editor']} ref={editorRef}>
<Toolbar />
<div class={styles['tab']}>
<Tabs />
diff --git a/studio/components/studio-header/index.module.scss b/studio/components/studio-header/index.module.scss
index 0d6163f..c9b9f8f 100644
--- a/studio/components/studio-header/index.module.scss
+++ b/studio/components/studio-header/index.module.scss
@@ -15,6 +15,21 @@
* limitations under the License.
*/
+@mixin icon() {
+ width: 18px;
+ height: 18px;
+ border: 1px solid var(--n-text-color);
+ &::before {
+ content: '';
+ display: block;
+ }
+}
+@mixin label-icon() {
+ @include icon();
+ margin-top: 6px;
+ border: 1px solid var(--n-option-text-color-active);
+}
+
.studio-header {
height: 40px;
line-height: 40px;
@@ -23,3 +38,30 @@
position: relative;
z-index: 30;
}
+.icon-button {
+ margin-top: 2px;
+}
+.icon-vertical {
+ @include icon();
+ &::before {
+ width: 6px;
+ height: 18px;
+ background-color: var(--n-text-color);
+ }
+}
+.label-icon-vertical {
+ @include label-icon();
+ &::before {
+ width: 6px;
+ height: 18px;
+ border-right: 1px solid var(--n-option-text-color-active);
+ }
+}
+.label-icon-horizontal {
+ @include label-icon();
+ &::before {
+ width: 18px;
+ height: 12px;
+ border-bottom: 1px solid var(--n-option-text-color-active);
+ }
+}
diff --git a/studio/components/studio-header/index.tsx b/studio/components/studio-header/index.tsx
index 6e029dd..a4f2b62 100644
--- a/studio/components/studio-header/index.tsx
+++ b/studio/components/studio-header/index.tsx
@@ -14,19 +14,56 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { defineComponent } from 'vue'
-import { NLayoutHeader, NGradientText, NSpace } from 'naive-ui'
+import { defineComponent, h } from 'vue'
+import {
+ NLayoutHeader,
+ NGradientText,
+ NSpace,
+ NDropdown,
+ NButton
+} from 'naive-ui'
+import { useLayoutStore } from '@/store/layout'
import styles from './index.module.scss'
export const StudioHeader = defineComponent({
name: 'studio-header',
setup() {
+ const layoutStore = useLayoutStore()
+ const onSelect = (key: string) => {
+ if (key === '1') {
+ layoutStore.toggleSider()
+ return
+ }
+ if (key === '2') {
+ layoutStore.toggleLog()
+ return
+ }
+ }
return () => (
<NLayoutHeader class={styles['studio-header']}>
<NSpace justify='space-between' align='center'>
<NGradientText type='primary' size={20}>
DolphinScheduler Studio
</NGradientText>
+ <NDropdown
+ trigger='click'
+ onSelect={onSelect}
+ options={[
+ {
+ key: '1',
+ label: () => h('div', { class: styles['label-icon-vertical'] })
+ },
+ {
+ key: '2',
+ label: () =>
+ h('div', { class: styles['label-icon-horizontal'] })
+ }
+ ]}
+ >
+ <NButton quaternary type='primary' class={styles['icon-button']}>
+ <div class={styles['icon-vertical']}></div>
+ </NButton>
+ </NDropdown>
</NSpace>
</NLayoutHeader>
)
diff --git a/studio/components/studio-sider/index.tsx b/studio/components/studio-sider/index.tsx
index b1b725e..eb8c2ae 100644
--- a/studio/components/studio-sider/index.tsx
+++ b/studio/components/studio-sider/index.tsx
@@ -19,14 +19,15 @@
import { ResizeHandler, ResizedOptions } from '../resize-handler'
import { SearchBar, Files } from '@/components'
import { useFile } from './use-file'
+import { useLayoutStore } from '@/store/layout'
import styles from './index.module.scss'
export const StudioSider = defineComponent({
name: 'studio-sider',
setup() {
- const widthRef = ref(300)
const inputRef = ref()
const fileRef = ref()
+ const layoutStore = useLayoutStore()
const {
state,
onCreateFile,
@@ -37,8 +38,18 @@
onRename
} = useFile(inputRef, fileRef)
+ const onResized = (resized: ResizedOptions) => {
+ let width = resized.x
+ if (width < 100) width = 100
+ if (width > window.innerWidth * 0.5) width = window.innerWidth * 0.5
+ layoutStore.setSiderWidth(width)
+ }
+
return () => (
- <NLayoutSider class={styles['studio-sider']} width={widthRef.value}>
+ <NLayoutSider
+ class={styles['studio-sider']}
+ width={layoutStore.getSiderWidth}
+ >
<NSpace
vertical
class={styles['studio-sider-content']}
@@ -59,14 +70,7 @@
ref={fileRef}
/>
</NSpace>
- <ResizeHandler
- onResized={(resized: ResizedOptions) => {
- let width = resized.x
- if (width < 100) width = 100
- if (width > window.innerWidth * 0.5) width = window.innerWidth * 0.5
- widthRef.value = width
- }}
- />
+ <ResizeHandler onResized={onResized} />
</NLayoutSider>
)
}
diff --git a/studio/store/layout/index.ts b/studio/store/layout/index.ts
new file mode 100644
index 0000000..cc06023
--- /dev/null
+++ b/studio/store/layout/index.ts
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { defineStore } from 'pinia'
+import type { ILayoutState } from './types'
+
+export const useLayoutStore = defineStore({
+ id: 'layout',
+ state: (): ILayoutState => ({
+ siderWidth: 300,
+ prevSiderWidth: 300,
+ logHeight: 400,
+ prevLogHeight: 400,
+ editorHeight: 0
+ }),
+ persist: true,
+ getters: {
+ getSiderWidth(): number {
+ return this.siderWidth
+ },
+ getLogHeight(): number {
+ return this.logHeight
+ },
+ getEditorHeight(): number {
+ return this.editorHeight
+ }
+ },
+ actions: {
+ toggleSider() {
+ if (this.siderWidth) this.prevSiderWidth = this.siderWidth
+ this.siderWidth = this.siderWidth ? 0 : this.prevSiderWidth
+ },
+ setSiderWidth(siderWidth: number) {
+ this.siderWidth = siderWidth
+ },
+ toggleLog() {
+ if (this.logHeight) this.prevLogHeight = this.logHeight
+ this.logHeight = this.logHeight ? 0 : this.prevLogHeight
+ },
+ setLogHeight(logHeight: number) {
+ this.logHeight = logHeight
+ },
+ setEditorHeight(editorHeight: number) {
+ this.editorHeight = editorHeight
+ }
+ }
+})
diff --git a/studio/store/layout/types.ts b/studio/store/layout/types.ts
new file mode 100644
index 0000000..01a9a2e
--- /dev/null
+++ b/studio/store/layout/types.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+interface ILayoutState {
+ siderWidth: number
+ prevSiderWidth: number
+ logHeight: number
+ prevLogHeight: number
+ editorHeight: number
+}
+
+export type { ILayoutState }