feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
export { TopBar } from './top-bar';
|
||||
export { SpaceAppList } from './space-app-list';
|
||||
@@ -0,0 +1,49 @@
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
padding: 6px 14px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
@apply coz-fg-secondary;
|
||||
|
||||
&:hover {
|
||||
@apply coz-mg-primary coz-fg-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
@apply coz-mg-primary coz-fg-primary;
|
||||
}
|
||||
|
||||
.item-link {
|
||||
margin-bottom: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tag-container {
|
||||
@apply flex items-center gap-4px;
|
||||
|
||||
.label {
|
||||
@apply px-4px text-foreground-2 font-semibold text-lg;
|
||||
}
|
||||
|
||||
.tag {
|
||||
@apply rounded-mini;
|
||||
|
||||
padding: 1px 6px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.label {
|
||||
@apply coz-fg-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed 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 { NavLink } from 'react-router-dom';
|
||||
import React, { type ReactNode } from 'react';
|
||||
|
||||
import { isString } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { SpaceAppEnum } from '@coze-arch/web-context';
|
||||
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
|
||||
import { Space, Badge } from '@coze-arch/coze-design';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { getFlags } from '@coze-arch/bot-flags';
|
||||
import { KnowledgeE2e, BotE2e } from '@coze-data/e2e';
|
||||
import { useSpaceApp } from '@coze-foundation/space-store';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface MenuItem {
|
||||
/**
|
||||
* 如果是string,需传入starling key,并且会由div包一层
|
||||
* 如果是function,则自定义label的实现,active表示是否是选中态
|
||||
*/
|
||||
label: string | ((active: boolean) => React.ReactNode);
|
||||
/** label 外的 badge,未来再扩展配置项 */
|
||||
badge?: string;
|
||||
app: SpaceAppEnum;
|
||||
/**
|
||||
* Q:为什么不叫 visible?FG 要取反,filter() 也要取反,很麻烦
|
||||
* A:为了兼容旧配置,缺省时认定为 visible。避免合码时无冲突 导致忽略掉新增配置的问题。
|
||||
*/
|
||||
invisible?: boolean;
|
||||
/** 目前(24.05.21)没发现用处,怀疑是以前的功能迭代掉了,@huangjian 说先留着 */
|
||||
icon?: ReactNode;
|
||||
/** 目前(24.05.21)没发现用处,怀疑是以前的功能迭代掉了,@huangjian 说先留着 */
|
||||
selectedIcon?: ReactNode;
|
||||
/** 自动化打标 */
|
||||
e2e?: string;
|
||||
}
|
||||
|
||||
const GET_MENU_SPACE_APP = (): Array<MenuItem> => [
|
||||
{
|
||||
label: 'menu_bots',
|
||||
app: SpaceAppEnum.BOT,
|
||||
e2e: BotE2e.BotTab,
|
||||
},
|
||||
{
|
||||
label: 'menu_plugins',
|
||||
app: SpaceAppEnum.PLUGIN,
|
||||
},
|
||||
{
|
||||
label: 'menu_workflows',
|
||||
app: SpaceAppEnum.WORKFLOW,
|
||||
},
|
||||
{
|
||||
label: 'imageflow_title',
|
||||
app: SpaceAppEnum.IMAGEFLOW,
|
||||
invisible: false,
|
||||
},
|
||||
{
|
||||
label: 'menu_datasets',
|
||||
app: SpaceAppEnum.KNOWLEDGE,
|
||||
e2e: KnowledgeE2e.KnowledgeTab,
|
||||
},
|
||||
{
|
||||
label: 'menu_widgets',
|
||||
app: SpaceAppEnum.WIDGET,
|
||||
invisible: !getFlags()['bot.builder.bot.builder.widget'],
|
||||
},
|
||||
{
|
||||
label: 'scene_resource_name',
|
||||
badge: 'scene_beta_sign',
|
||||
app: SpaceAppEnum.SOCIAL_SCENE,
|
||||
invisible: !getFlags()['bot.studio.social'],
|
||||
},
|
||||
];
|
||||
export const SpaceAppList = () => {
|
||||
const spaceApp = useSpaceApp();
|
||||
|
||||
const { id: spaceId } = useSpaceStore(store => store.space);
|
||||
|
||||
return (
|
||||
<Space spacing={4}>
|
||||
{GET_MENU_SPACE_APP()
|
||||
.filter(item => !item.invisible)
|
||||
.map(item => {
|
||||
const active = item.app === spaceApp;
|
||||
const tabContent = (
|
||||
<NavLink
|
||||
key={item.app}
|
||||
data-testid={item.e2e}
|
||||
to={`/space/${spaceId}/${item.app}`}
|
||||
className={s['item-link']}
|
||||
onClick={() => {
|
||||
sendTeaEvent(EVENT_NAMES.workspace_tab_expose, {
|
||||
tab_name: item.app,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{isString(item.label) ? (
|
||||
<div
|
||||
className={classNames({
|
||||
[s.item]: true,
|
||||
[s.active]: active,
|
||||
})}
|
||||
>
|
||||
{I18n.t(item.label as I18nKeysNoOptionsType)}
|
||||
</div>
|
||||
) : (
|
||||
item.label(active)
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
|
||||
return item.badge ? (
|
||||
<Badge
|
||||
type="alt"
|
||||
key={item.app}
|
||||
count={I18n.t(item.badge as I18nKeysNoOptionsType)}
|
||||
countStyle={{
|
||||
backgroundColor: 'var(--coz-mg-color-plus-emerald)',
|
||||
}}
|
||||
>
|
||||
{tabContent}
|
||||
</Badge>
|
||||
) : (
|
||||
tabContent
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
.topBar {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
.des {
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.name {
|
||||
max-width: calc(100% - 28px);
|
||||
word-break: break-word;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
height: 28px;
|
||||
@apply coz-fg-plus;
|
||||
}
|
||||
|
||||
.split {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
margin: 0 4px;
|
||||
border-bottom: none;
|
||||
background-color: var(--coz-stroke-primary);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed 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 React, { type JSX } from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozSetting } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Typography,
|
||||
Space,
|
||||
IconButton,
|
||||
Divider,
|
||||
Avatar,
|
||||
} from '@coze-arch/coze-design';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { SpaceAppList } from '../space-app-list';
|
||||
|
||||
import s from './index.module.less';
|
||||
interface TopBarProps {
|
||||
showActions?: boolean;
|
||||
showFilter?: boolean;
|
||||
isPersonal?: boolean;
|
||||
actions: JSX.Element;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
titleExtend?: JSX.Element;
|
||||
}
|
||||
|
||||
export const TopBar = (props: TopBarProps) => {
|
||||
const { Text } = Typography;
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
space: { name: spaceName, id: spaceId, icon_url: spaceIconUrl },
|
||||
} = useSpaceStore();
|
||||
const {
|
||||
showActions,
|
||||
showFilter,
|
||||
isPersonal,
|
||||
actions,
|
||||
children,
|
||||
className,
|
||||
titleExtend,
|
||||
} = props;
|
||||
const settingLabel = I18n.t('basic_setting');
|
||||
|
||||
return (
|
||||
<div className={classnames(s.topBar, className)}>
|
||||
<Space className="w-full flex justify-between mb-24px">
|
||||
<Space>
|
||||
<div className={s.des}>
|
||||
<Avatar
|
||||
src={spaceIconUrl}
|
||||
size="small"
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { content: spaceName },
|
||||
},
|
||||
}}
|
||||
className={classnames(s.name, '!max-w-[320px]')}
|
||||
>
|
||||
{spaceName}
|
||||
</Text>
|
||||
{titleExtend}
|
||||
</div>
|
||||
</Space>
|
||||
<Space spacing={8} className="flex items-center align-right">
|
||||
{showActions ? actions : null}
|
||||
{!isPersonal && (
|
||||
<>
|
||||
<Divider layout="horizontal" className={s.split} />
|
||||
<IconButton
|
||||
color="primary"
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={() => {
|
||||
sendTeaEvent(EVENT_NAMES.workspace_tab_expose, {
|
||||
tab_name: 'team_manage',
|
||||
});
|
||||
|
||||
navigate(`/space/${spaceId}/team`);
|
||||
}}
|
||||
icon={<IconCozSetting />}
|
||||
aria-label={settingLabel}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
</Space>
|
||||
<div className={s.tabs}>
|
||||
<Space className="w-full flex justify-between">
|
||||
<Space spacing={8} className="flex items-center align-left shrink-0">
|
||||
<SpaceAppList />
|
||||
</Space>
|
||||
<Space
|
||||
className="!flex items-center !overflow-hidden shrink-1"
|
||||
spacing={8}
|
||||
>
|
||||
{showFilter ? children : null}
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user