Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit 41940eb

Browse files
myan9starpit
authored andcommitted
feat: a new model NavResponse supporting side navigation menu
Fixes #3659
1 parent 1ae0ab5 commit 41940eb

File tree

12 files changed

+322
-243
lines changed

12 files changed

+322
-243
lines changed

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ export {
177177
Mode as MultiModalMode,
178178
MultiModalResponse
179179
} from './models/mmr/types'
180+
export { NavResponse, isNavResponse } from './models/NavResponse'
180181
export { isMultiModalResponse } from './models/mmr/is'
181182
export {
182183
Content,

packages/core/src/main/headless-pretty-print.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import ElementMimic from '../util/element-mimic'
2525
import { isTable, Row } from '../webapp/models/table'
2626
import { isMixedResponse, isMessageBearingEntity, Entity } from '../models/entity'
2727
import { isMultiModalResponse } from '../models/mmr/is'
28+
import { isNavResponse } from '../models/NavResponse'
2829
import { isHTML, isPromise } from '../util/types'
2930

3031
const log = console.log
@@ -398,7 +399,7 @@ export const print = (
398399
stream.write('\n')
399400
})
400401
return logger(colors.green(ok))
401-
} else if (isMultiModalResponse(msg)) {
402+
} else if (isMultiModalResponse(msg) || isNavResponse(msg)) {
402403
throw new Error('cannot format this response in headless mode')
403404
} else if (Array.isArray(msg)) {
404405
// msg is an array of stuff
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2020 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { Entity } from './entity'
18+
import { isMultiModalResponse } from './mmr/is'
19+
import { MultiModalResponse } from './mmr/types'
20+
21+
/**
22+
* A `NavResponse` is a collection of `MultiModalResponse` with menu navigation
23+
*
24+
*/
25+
export type NavResponse = Record<string, MultiModalResponse>
26+
27+
export function isNavResponse(entity: Entity): entity is NavResponse {
28+
const nav = entity as NavResponse
29+
return (
30+
typeof nav === 'object' &&
31+
Object.keys(nav).length !== 0 &&
32+
Object.keys(nav).every(menu => isMultiModalResponse(nav[menu]))
33+
)
34+
}

packages/core/src/models/entity.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { Table, Row, isTable } from '../webapp/models/table'
1818
import { ToolbarText } from '../webapp/views/toolbar-text'
1919
import { UsageModel } from '../core/usage-error'
2020
import { MultiModalResponse } from './mmr/types'
21+
import { NavResponse } from './NavResponse'
2122
import Presentation from '../webapp/views/presentation'
2223

2324
export interface MessageBearingEntity {
@@ -191,6 +192,7 @@ export type Entity<
191192
| ResourceModification
192193
| MixedResponse
193194
| MultiModalResponse
195+
| NavResponse
194196
| LowLevelLoop
195197
| UsageModel
196198
| Meta
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
{
2-
"modes": [
3-
{
4-
"mode": "about",
5-
"content": "about:content",
6-
"contentType": "text/markdown"
7-
},
8-
{ "mode": "tutorial", "label": "Getting Started", "contentFrom": "tutorial play @tutorials/getting-started" },
9-
{ "mode": "version", "contentFrom": "version --full" }
10-
]
2+
"nav": {
3+
"Kui": {
4+
"modes": [
5+
{
6+
"mode": "about",
7+
"content": "about:content",
8+
"contentType": "text/markdown"
9+
},
10+
{ "mode": "tutorial", "label": "Getting Started", "contentFrom": "tutorial play @tutorials/getting-started" },
11+
{ "mode": "version", "contentFrom": "version --full" }
12+
]
13+
}
14+
}
1115
}

plugins/plugin-core-support/about/src/about.ts

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
i18n,
2222
inElectron,
2323
MultiModalMode as Mode,
24-
MultiModalResponse,
24+
NavResponse,
2525
Presentation,
2626
Table,
2727
isStringWithOptionalContentType
@@ -45,39 +45,43 @@ async function getName(): Promise<string> {
4545
* @return a MultiModalResponse for `about`
4646
*
4747
*/
48-
const aboutWindow = async (): Promise<MultiModalResponse> => {
49-
const [name, modesFromAbout] = await Promise.all([
50-
getName(),
51-
import('@kui-shell/client/config.d/about.json').then(_ => _.modes as Mode[])
52-
])
53-
54-
const modes = modesFromAbout.map(
55-
(modeFromAbout): Mode => {
56-
// translate the label
57-
const label = clientStrings(modeFromAbout.label || modeFromAbout.mode)
58-
59-
if (isStringWithOptionalContentType(modeFromAbout)) {
60-
return Object.assign({}, modeFromAbout, {
61-
label,
62-
content: clientStrings(modeFromAbout.content) // translate content string
63-
})
64-
} else {
65-
return Object.assign({}, modeFromAbout, {
66-
label
67-
})
48+
const aboutWindow = async (): Promise<NavResponse> => {
49+
const [name, about] = await Promise.all([getName(), import('@kui-shell/client/config.d/about.json').then(_ => _.nav)])
50+
51+
const fullAbout = {}
52+
53+
for (const [title, mmr] of Object.entries(about)) {
54+
const modesFromAbout = mmr.modes
55+
const modes = modesFromAbout.map(
56+
(modeFromAbout): Mode => {
57+
// translate the label
58+
const label = clientStrings(modeFromAbout.label || modeFromAbout.mode)
59+
60+
if (isStringWithOptionalContentType(modeFromAbout)) {
61+
return Object.assign({}, modeFromAbout, {
62+
label,
63+
content: clientStrings(modeFromAbout.content) // translate content string
64+
})
65+
} else {
66+
return Object.assign({}, modeFromAbout, {
67+
label
68+
})
69+
}
6870
}
69-
}
70-
)
71-
72-
return {
73-
kind: 'about',
74-
presentation:
75-
(document.body.classList.contains('subwindow') && Presentation.SidecarFullscreen) || Presentation.SidecarThin,
76-
modes,
77-
metadata: {
78-
name
79-
}
71+
)
72+
73+
Object.assign(fullAbout, {
74+
[title]: {
75+
kind: 'about',
76+
modes,
77+
presentation:
78+
(document.body.classList.contains('subwindow') && Presentation.SidecarFullscreen) || Presentation.SidecarThin,
79+
metadata: { name }
80+
}
81+
})
8082
}
83+
84+
return fullAbout
8185
}
8286

8387
interface VersionOptions extends ParsedOptions {

plugins/plugin-sidecar/src/preload.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ import {
2020
isHeadless,
2121
PreloadRegistration,
2222
PreloadRegistrar,
23-
Presentation,
2423
isMultiModalResponse,
2524
MultiModalResponse,
25+
NavResponse,
26+
isNavResponse,
2627
REPL
2728
} from '@kui-shell/core'
2829

@@ -37,8 +38,8 @@ const registration: PreloadRegistration = async (registrar: PreloadRegistrar) =>
3738

3839
if (!isPopup()) {
3940
registrar.registerComponent({
40-
when: response => isMultiModalResponse(response) && response.presentation === Presentation.SidecarThin,
41-
render: async (entity: MultiModalResponse, tab: Tab, repl: REPL) => {
41+
when: isNavResponse,
42+
render: async (entity: NavResponse, tab: Tab, repl: REPL) => {
4243
return import(/* webpackMode: "lazy" */ './view/react2kui').then(_ => _.leftnav(entity, tab, repl))
4344
},
4445
priority: 10
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2020 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as React from 'react'
18+
import { REPL, getTabId, eventBus, KResponse, Tab as KuiTab } from '@kui-shell/core'
19+
20+
import TitleBar from './TitleBar'
21+
22+
import '../../../web/css/static/sidecar.css'
23+
import '../../../web/css/static/sidecar-main.css'
24+
25+
export interface Props<R extends KResponse> {
26+
repl: REPL
27+
tab: KuiTab
28+
response: R
29+
}
30+
31+
export abstract class BaseSidecar<R extends KResponse, State> extends React.PureComponent<Props<R>, State> {
32+
protected containerStyle() {
33+
return { display: 'flex', flex: 1, 'overflow-y': 'hidden', 'flex-direction': 'column' }
34+
}
35+
36+
protected onMaximize() {
37+
eventBus.emit(`/sidecar/maximize/${getTabId(this.props.tab)}`, this.props.tab)
38+
}
39+
40+
protected onRestore() {
41+
eventBus.emit(`/sidecar/restore/${getTabId(this.props.tab)}`, this.props.tab)
42+
}
43+
44+
protected onMinimize() {
45+
eventBus.emit(`/sidecar/minimize/${getTabId(this.props.tab)}`, this.props.tab)
46+
}
47+
48+
protected onClose() {
49+
eventBus.emit(`/sidecar/close/${getTabId(this.props.tab)}`, this.props.tab)
50+
}
51+
52+
protected viewId() {
53+
return 'kui-default-sidecar'
54+
}
55+
56+
protected isFixedWidth() {
57+
return false
58+
}
59+
60+
protected title(kind?: string, namespace?: string, fixedWidth = true, onClickNamespace?: () => void) {
61+
return (
62+
<TitleBar
63+
fixedWidth={fixedWidth}
64+
kind={kind}
65+
namespace={namespace}
66+
onClickNamespace={onClickNamespace}
67+
onScreenshot={() => this.props.repl.pexec('screenshot sidecar')}
68+
onMaximize={this.onMaximize.bind(this)}
69+
onRestore={this.onRestore.bind(this)}
70+
onMinimize={this.onMinimize.bind(this)}
71+
onClose={this.onClose.bind(this)}
72+
/>
73+
)
74+
}
75+
}

0 commit comments

Comments
 (0)