Merge pull request #227 from spotify/rugvip/nonteties
packages/core: removed entity-related APIs
This commit is contained in:
@@ -3,7 +3,6 @@ import { BackstageTheme, createApp } from '@spotify-backstage/core';
|
||||
import React, { FC } from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import Root from './components/Root';
|
||||
import entities from './entities';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
@@ -25,7 +24,6 @@ const useStyles = makeStyles(theme => ({
|
||||
}));
|
||||
|
||||
const app = createApp();
|
||||
app.registerEntityKind(...entities);
|
||||
app.registerPlugin(...Object.values(plugins));
|
||||
const AppComponent = app.build();
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import React, { FC } from 'react';
|
||||
import { useEntityUri } from '@spotify-backstage/core';
|
||||
|
||||
const MockEntityPage: FC<{}> = () => {
|
||||
const uri = useEntityUri();
|
||||
return <span>Mock card for {uri}, replace with some userful plugin</span>;
|
||||
};
|
||||
|
||||
export default MockEntityPage;
|
||||
@@ -1,12 +0,0 @@
|
||||
import React, { FC } from 'react';
|
||||
import { Typography } from '@material-ui/core';
|
||||
|
||||
const MockEntityPage: FC<{}> = () => {
|
||||
return (
|
||||
<Typography style={{ padding: 24 }} variant="h3">
|
||||
This page is intentionally left blank
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
export default MockEntityPage;
|
||||
@@ -1,45 +0,0 @@
|
||||
import {
|
||||
createEntityKind,
|
||||
createWidgetView,
|
||||
createEntityPage,
|
||||
} from '@spotify-backstage/core';
|
||||
import ComputerIcon from '@material-ui/icons/Computer';
|
||||
import WebIcon from '@material-ui/icons/Web';
|
||||
import VerifiedUserIcon from '@material-ui/icons/VerifiedUser';
|
||||
import CloudIcon from '@material-ui/icons/Cloud';
|
||||
import TimelineIcon from '@material-ui/icons/Timeline';
|
||||
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
||||
import VpnKeyIcon from '@material-ui/icons/VpnKey';
|
||||
import DnsIcon from '@material-ui/icons/Dns';
|
||||
import MockEntityPage from './MockEntityPage';
|
||||
import MockEntityCard from './MockEntityCard';
|
||||
|
||||
/* SERVICE */
|
||||
const serviceOverviewPage = createWidgetView().add({
|
||||
size: 4,
|
||||
component: MockEntityCard,
|
||||
});
|
||||
|
||||
const serviceView = createEntityPage()
|
||||
.addPage('Overview', WebIcon, '/overview', serviceOverviewPage)
|
||||
.addComponent('Tests', VerifiedUserIcon, '/tests', MockEntityPage)
|
||||
.addComponent('Deployment', CloudIcon, '/deployment', MockEntityPage)
|
||||
.addComponent('Monitoring', TimelineIcon, '/monitoring', MockEntityPage)
|
||||
.addComponent('Service Levels', CheckCircleIcon, '/sla', MockEntityPage)
|
||||
.addComponent('Secrets', VpnKeyIcon, '/secrets', MockEntityPage)
|
||||
.addComponent('DNS', DnsIcon, '/dns', MockEntityPage);
|
||||
|
||||
const serviceEntity = createEntityKind({
|
||||
kind: 'service',
|
||||
title: 'Service',
|
||||
color: {
|
||||
primary: '#f00',
|
||||
secondary: '#ba5',
|
||||
},
|
||||
icon: ComputerIcon,
|
||||
pages: {
|
||||
view: serviceView,
|
||||
},
|
||||
});
|
||||
|
||||
export default [serviceEntity];
|
||||
@@ -1,25 +1,15 @@
|
||||
import AppBuilder from './app/AppBuilder';
|
||||
import EntityKind, { EntityConfig } from './entity/EntityKind';
|
||||
import WidgetViewBuilder from './widgetView/WidgetViewBuilder';
|
||||
import EntityPageBuilder from './entityView/EntityPageBuilder';
|
||||
import BackstagePlugin, { PluginConfig } from './plugin/Plugin';
|
||||
|
||||
export function createApp() {
|
||||
return new AppBuilder();
|
||||
}
|
||||
|
||||
export function createEntityKind(config: EntityConfig) {
|
||||
return new EntityKind(config);
|
||||
}
|
||||
|
||||
export function createWidgetView() {
|
||||
return new WidgetViewBuilder();
|
||||
}
|
||||
|
||||
export function createEntityPage() {
|
||||
return new EntityPageBuilder();
|
||||
}
|
||||
|
||||
export function createPlugin(config: PluginConfig): BackstagePlugin {
|
||||
return new BackstagePlugin(config);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React, { ComponentType, FC } from 'react';
|
||||
import { Route, Switch, useParams, Redirect } from 'react-router-dom';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { Route, Switch, Redirect } from 'react-router-dom';
|
||||
import { AppContextProvider } from './AppContext';
|
||||
import { App, AppComponentBuilder } from './types';
|
||||
import EntityKind, { EntityConfig } from '../entity/EntityKind';
|
||||
import { EntityContextProvider } from '../entityView/EntityContext';
|
||||
import { App } from './types';
|
||||
import BackstagePlugin from '../plugin/Plugin';
|
||||
import {
|
||||
IconComponent,
|
||||
@@ -13,49 +11,17 @@ import {
|
||||
} from '../../icons';
|
||||
|
||||
class AppImpl implements App {
|
||||
constructor(
|
||||
private readonly entities: Map<string, EntityKind>,
|
||||
private readonly systemIcons: SystemIcons,
|
||||
) {}
|
||||
|
||||
getEntityConfig(kind: string): EntityConfig {
|
||||
const entity = this.entities.get(kind);
|
||||
if (!entity) {
|
||||
throw new Error('EntityKind not found');
|
||||
}
|
||||
return entity.config;
|
||||
}
|
||||
constructor(private readonly systemIcons: SystemIcons) {}
|
||||
|
||||
getSystemIcon(key: SystemIconKey): IconComponent {
|
||||
return this.systemIcons[key];
|
||||
}
|
||||
}
|
||||
|
||||
function builtComponent(
|
||||
app: App,
|
||||
component: ComponentType<any> | AppComponentBuilder,
|
||||
) {
|
||||
if (component instanceof AppComponentBuilder) {
|
||||
return component.build(app);
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
export default class AppBuilder {
|
||||
private readonly entities = new Map<string, EntityKind>();
|
||||
private systemIcons = { ...defaultSystemIcons };
|
||||
private readonly plugins = new Set<BackstagePlugin>();
|
||||
|
||||
registerEntityKind(...entity: EntityKind[]) {
|
||||
for (const e of entity) {
|
||||
const { kind } = e.config;
|
||||
if (this.entities.has(e.config.kind)) {
|
||||
throw new Error(`EntityKind '${kind}' is already registered`);
|
||||
}
|
||||
this.entities.set(e.config.kind, e);
|
||||
}
|
||||
}
|
||||
|
||||
registerIcons(icons: Partial<SystemIcons>) {
|
||||
this.systemIcons = { ...this.systemIcons, ...icons };
|
||||
}
|
||||
@@ -70,49 +36,9 @@ export default class AppBuilder {
|
||||
}
|
||||
|
||||
build(): ComponentType<{}> {
|
||||
const app = new AppImpl(this.entities, this.systemIcons);
|
||||
const app = new AppImpl(this.systemIcons);
|
||||
|
||||
const entityRoutes = new Array<JSX.Element>();
|
||||
|
||||
for (const { config } of this.entities.values()) {
|
||||
const { kind, pages } = config;
|
||||
const basePath = `/entity/${kind}`;
|
||||
|
||||
if (pages.list) {
|
||||
const ListComponent = builtComponent(app, pages.list);
|
||||
|
||||
const Component: FC<{}> = () => (
|
||||
<EntityContextProvider config={config}>
|
||||
<ListComponent />
|
||||
</EntityContextProvider>
|
||||
);
|
||||
|
||||
const path = basePath;
|
||||
entityRoutes.push(
|
||||
<Route key={path} path={path} component={Component} />,
|
||||
);
|
||||
}
|
||||
|
||||
if (pages.view) {
|
||||
const ViewComponent = builtComponent(app, pages.view);
|
||||
|
||||
const Component: FC<{}> = () => {
|
||||
const { entityId } = useParams<{ entityId: string }>();
|
||||
return (
|
||||
<EntityContextProvider config={config} id={entityId}>
|
||||
<ViewComponent />
|
||||
</EntityContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const path = `${basePath}/:entityId`;
|
||||
entityRoutes.push(
|
||||
<Route key={path} path={path} component={Component} />,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const pluginRoutes = new Array<JSX.Element>();
|
||||
const routes = new Array<JSX.Element>();
|
||||
|
||||
for (const plugin of this.plugins.values()) {
|
||||
for (const output of plugin.output()) {
|
||||
@@ -120,7 +46,7 @@ export default class AppBuilder {
|
||||
case 'route': {
|
||||
const { path, component, options = {} } = output;
|
||||
const { exact = true } = options;
|
||||
pluginRoutes.push(
|
||||
routes.push(
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
@@ -133,7 +59,7 @@ export default class AppBuilder {
|
||||
case 'redirect-route': {
|
||||
const { path, target, options = {} } = output;
|
||||
const { exact = true } = options;
|
||||
pluginRoutes.push(
|
||||
routes.push(
|
||||
<Redirect key={path} path={path} to={target} exact={exact} />,
|
||||
);
|
||||
break;
|
||||
@@ -144,8 +70,6 @@ export default class AppBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
const routes = [...pluginRoutes, ...entityRoutes];
|
||||
|
||||
return () => (
|
||||
<AppContextProvider app={app}>
|
||||
<Switch>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { EntityConfig } from '../entity/EntityKind';
|
||||
import { IconComponent, SystemIconKey } from '../../icons';
|
||||
|
||||
export type App = {
|
||||
getEntityConfig(kind: string): EntityConfig;
|
||||
getSystemIcon(key: SystemIconKey): IconComponent;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { AppComponentBuilder } from '../app/types';
|
||||
import { IconComponent } from '../../icons';
|
||||
|
||||
export type EntityConfig = {
|
||||
kind: string;
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
color: {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
};
|
||||
pages: {
|
||||
list?: ComponentType<{}> | AppComponentBuilder;
|
||||
view?: ComponentType<{}> | AppComponentBuilder;
|
||||
};
|
||||
};
|
||||
|
||||
export default class EntityKind {
|
||||
constructor(readonly config: EntityConfig) {}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import React, { createContext, useContext, FC } from 'react';
|
||||
import { EntityConfig } from '../entity/EntityKind';
|
||||
|
||||
type Value = {
|
||||
config: EntityConfig;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
const Context = createContext<Value | undefined>(undefined);
|
||||
|
||||
type Props = {
|
||||
config: EntityConfig;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export const EntityContextProvider: FC<Props> = ({ config, id, children }) => (
|
||||
<Context.Provider value={{ config, id }} children={children} />
|
||||
);
|
||||
|
||||
export const useEntity = (): { kind: string; id: string } => {
|
||||
const value = useContext(Context);
|
||||
if (!value) {
|
||||
throw new Error('No entity context available');
|
||||
}
|
||||
if (!value.id) {
|
||||
throw new Error('Entity context does not contain entity id');
|
||||
}
|
||||
return { kind: value.config.kind, id: value.id };
|
||||
};
|
||||
|
||||
export const useEntityConfig = (): EntityConfig => {
|
||||
const value = useContext(Context);
|
||||
if (!value) {
|
||||
throw new Error('No entity context available');
|
||||
}
|
||||
return value.config;
|
||||
};
|
||||
|
||||
export const useEntityUri = (): string => {
|
||||
const { kind, id } = useEntity();
|
||||
return `entity:${kind}:${id}`;
|
||||
};
|
||||
@@ -1,114 +0,0 @@
|
||||
import React, { ComponentType } from 'react';
|
||||
import DefaultEntityPage from '../../components/DefaultEntityPage';
|
||||
import { App, AppComponentBuilder } from '../app/types';
|
||||
import BackstagePlugin from '../plugin/Plugin';
|
||||
import { EntityPageNavItem, EntityPageView } from './types';
|
||||
import { IconComponent } from '../../icons';
|
||||
|
||||
// type AppComponents = {
|
||||
// EntityPage: ComponentType<EntityPageProps>;
|
||||
// EntityPageNavbar: ComponentType<EntityPageNavbarProps>;
|
||||
// EntityPageHeader: ComponentType<EntityPageHeaderProps>;
|
||||
// };
|
||||
|
||||
type EntityPageRegistration =
|
||||
| {
|
||||
type: 'page';
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
path: string;
|
||||
page: AppComponentBuilder;
|
||||
}
|
||||
| {
|
||||
type: 'plugin';
|
||||
plugin: BackstagePlugin;
|
||||
}
|
||||
| {
|
||||
type: 'component';
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
path: string;
|
||||
component: ComponentType<any>;
|
||||
};
|
||||
|
||||
export default class EntityPageBuilder extends AppComponentBuilder {
|
||||
private readonly registrations = new Array<EntityPageRegistration>();
|
||||
|
||||
addPage(
|
||||
title: string,
|
||||
icon: IconComponent,
|
||||
path: string,
|
||||
page: AppComponentBuilder,
|
||||
): EntityPageBuilder {
|
||||
this.registrations.push({ type: 'page', title, icon, path, page });
|
||||
return this;
|
||||
}
|
||||
|
||||
addComponent(
|
||||
title: string,
|
||||
icon: IconComponent,
|
||||
path: string,
|
||||
component: ComponentType<any>,
|
||||
): EntityPageBuilder {
|
||||
this.registrations.push({
|
||||
type: 'component',
|
||||
title,
|
||||
icon,
|
||||
path,
|
||||
component,
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
register(plugin: BackstagePlugin): EntityPageBuilder {
|
||||
this.registrations.push({ type: 'plugin', plugin });
|
||||
return this;
|
||||
}
|
||||
|
||||
build(app: App): ComponentType<any> {
|
||||
const navItems = new Array<EntityPageNavItem>();
|
||||
const views = new Array<EntityPageView>();
|
||||
|
||||
for (const reg of this.registrations) {
|
||||
switch (reg.type) {
|
||||
case 'page': {
|
||||
const { title, icon, path, page } = reg;
|
||||
navItems.push({ title, icon, target: path });
|
||||
views.push({ path, component: page.build(app) });
|
||||
break;
|
||||
}
|
||||
case 'component': {
|
||||
const { title, icon, path, component } = reg;
|
||||
navItems.push({ title, icon, target: path });
|
||||
views.push({ path, component });
|
||||
break;
|
||||
}
|
||||
case 'plugin': {
|
||||
let added = false;
|
||||
for (const output of reg.plugin.output()) {
|
||||
switch (output.type) {
|
||||
case 'entity-page-nav-item':
|
||||
const { title, icon, target } = output;
|
||||
navItems.push({ title, icon, target });
|
||||
added = true;
|
||||
break;
|
||||
case 'entity-page-view-route':
|
||||
const { path, component } = output;
|
||||
views.push({ path, component });
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
throw new Error(
|
||||
`Plugin ${reg.plugin} was registered as entity view, but did not provide any output`,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return () => <DefaultEntityPage navItems={navItems} views={views} />;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { IconComponent } from '../../icons';
|
||||
|
||||
export type EntityPageNavItem = {
|
||||
icon: IconComponent;
|
||||
title: string;
|
||||
target: string;
|
||||
};
|
||||
|
||||
export type EntityPageView = {
|
||||
path: string;
|
||||
component: ComponentType<any>;
|
||||
};
|
||||
|
||||
export type EntityPageProps = {
|
||||
navItems: EntityPageNavItem[];
|
||||
views: EntityPageView[];
|
||||
};
|
||||
|
||||
export type EntityPageNavbarProps = {
|
||||
navItems: EntityPageNavItem[];
|
||||
};
|
||||
|
||||
export type EntityPageHeaderProps = {};
|
||||
@@ -1,7 +1,2 @@
|
||||
export * from './api';
|
||||
export { useApp } from './app/AppContext';
|
||||
export {
|
||||
useEntity,
|
||||
useEntityConfig,
|
||||
useEntityUri,
|
||||
} from './entityView/EntityContext';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { PluginOutput, RoutePath, RouteOptions } from './types';
|
||||
import { IconComponent } from '../../icons';
|
||||
import { Widget } from '../widgetView/types';
|
||||
|
||||
export type PluginConfig = {
|
||||
@@ -10,7 +9,6 @@ export type PluginConfig = {
|
||||
|
||||
export type PluginHooks = {
|
||||
router: RouterHooks;
|
||||
entityPage: EntityPageHooks;
|
||||
widgets: WidgetHooks;
|
||||
};
|
||||
|
||||
@@ -28,21 +26,6 @@ export type RouterHooks = {
|
||||
): void;
|
||||
};
|
||||
|
||||
type EntityPageSidebarItemOptions = {
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
target: RoutePath;
|
||||
};
|
||||
|
||||
export type EntityPageHooks = {
|
||||
navItem(options: EntityPageSidebarItemOptions): void;
|
||||
route(
|
||||
path: RoutePath,
|
||||
component: ComponentType<any>,
|
||||
options?: RouteOptions,
|
||||
): void;
|
||||
};
|
||||
|
||||
export type WidgetHooks = {
|
||||
add(widget: Widget): void;
|
||||
};
|
||||
@@ -63,47 +46,17 @@ export default class Plugin {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { id } = this.config;
|
||||
|
||||
const outputs = new Array<PluginOutput>();
|
||||
|
||||
this.config.register({
|
||||
router: {
|
||||
registerRoute(path, component, options) {
|
||||
if (path.startsWith('/entity/')) {
|
||||
throw new Error(
|
||||
`Plugin ${id} tried to register forbidden route ${path}`,
|
||||
);
|
||||
}
|
||||
outputs.push({ type: 'route', path, component, options });
|
||||
},
|
||||
registerRedirect(path, target, options) {
|
||||
if (path.startsWith('/entity/')) {
|
||||
throw new Error(
|
||||
`Plugin ${id} tried to register forbidden redirect ${path}`,
|
||||
);
|
||||
}
|
||||
outputs.push({ type: 'redirect-route', path, target, options });
|
||||
},
|
||||
},
|
||||
entityPage: {
|
||||
navItem({ title, icon, target }) {
|
||||
outputs.push({
|
||||
type: 'entity-page-nav-item',
|
||||
title,
|
||||
icon,
|
||||
target,
|
||||
});
|
||||
},
|
||||
route(path, component, options) {
|
||||
outputs.push({
|
||||
type: 'entity-page-view-route',
|
||||
path,
|
||||
component,
|
||||
options,
|
||||
});
|
||||
},
|
||||
},
|
||||
widgets: {
|
||||
add(widget: Widget) {
|
||||
outputs.push({ type: 'widget', widget });
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { IconComponent } from '../../icons';
|
||||
import { Widget } from '../widgetView/types';
|
||||
|
||||
export type RouteOptions = {
|
||||
@@ -28,23 +27,4 @@ export type WidgetOutput = {
|
||||
widget: Widget;
|
||||
};
|
||||
|
||||
export type EntityPageViewRouteOutput = {
|
||||
type: 'entity-page-view-route';
|
||||
path: RoutePath;
|
||||
component: ComponentType<any>;
|
||||
options?: RouteOptions;
|
||||
};
|
||||
|
||||
export type EntityPageNavItemOutput = {
|
||||
type: 'entity-page-nav-item';
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
target: RoutePath;
|
||||
};
|
||||
|
||||
export type PluginOutput =
|
||||
| RouteOutput
|
||||
| RedirectRouteOutput
|
||||
| WidgetOutput
|
||||
| EntityPageViewRouteOutput
|
||||
| EntityPageNavItemOutput;
|
||||
export type PluginOutput = RouteOutput | RedirectRouteOutput | WidgetOutput;
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import React, { FC } from 'react';
|
||||
import { makeStyles, Theme } from '@material-ui/core';
|
||||
import { EntityPageProps } from '../../api/entityView/types';
|
||||
import { useEntity } from '../../api';
|
||||
import { Switch, Route, Redirect } from 'react-router-dom';
|
||||
import DefaultEntityPageHeader from '../DefaultEntityPageHeader';
|
||||
import DefaultEntityPageNavbar from '../DefaultEntityPageNavbar';
|
||||
|
||||
const useStyles = makeStyles<Theme>({
|
||||
root: {
|
||||
display: 'grid',
|
||||
gridTemplateAreas: `
|
||||
'header header'
|
||||
'navbar content'
|
||||
`,
|
||||
gridTemplateRows: 'auto 1fr',
|
||||
gridTemplateColumns: 'auto 1fr',
|
||||
minHeight: '100%',
|
||||
},
|
||||
header: {
|
||||
gridArea: 'header',
|
||||
},
|
||||
navbar: {
|
||||
gridArea: 'navbar',
|
||||
},
|
||||
content: {
|
||||
gridArea: 'content',
|
||||
},
|
||||
});
|
||||
|
||||
const DefaultEntityPage: FC<EntityPageProps> = ({ navItems, views }) => {
|
||||
const classes = useStyles();
|
||||
const { kind, id } = useEntity();
|
||||
const basePath = `/entity/${kind}/${id}`;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.header}>
|
||||
<DefaultEntityPageHeader />
|
||||
</div>
|
||||
<div className={classes.navbar}>
|
||||
<DefaultEntityPageNavbar navItems={navItems} />
|
||||
</div>
|
||||
<div className={classes.content}>
|
||||
<Switch>
|
||||
{views.map(({ path, component }) => (
|
||||
<Route
|
||||
key={path}
|
||||
exact
|
||||
path={`${basePath}${path}`}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<Redirect from={basePath} to={`${basePath}${views[0].path}`} />
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DefaultEntityPage;
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './DefaultEntityPage';
|
||||
@@ -1,18 +0,0 @@
|
||||
import React, { FC } from 'react';
|
||||
import { Header, useEntity, useEntityConfig, pageTheme } from '../..';
|
||||
import { EntityPageHeaderProps } from '../../api/entityView/types';
|
||||
import { Theme } from '../../layout/Page/Page';
|
||||
|
||||
const DefaultEntityPageHeader: FC<EntityPageHeaderProps> = () => {
|
||||
const { id } = useEntity();
|
||||
const config = useEntityConfig();
|
||||
|
||||
// TODO(rugvip): provide theme through entity config
|
||||
return (
|
||||
<Theme.Provider value={pageTheme.service}>
|
||||
<Header title={`${config.title} - ${id}`} />
|
||||
</Theme.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default DefaultEntityPageHeader;
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './DefaultEntityPageHeader';
|
||||
@@ -1,37 +0,0 @@
|
||||
import React, { FC } from 'react';
|
||||
import { EntityPageNavbarProps } from '../../api/entityView/types';
|
||||
import { useEntityUri } from '../..';
|
||||
import { List, makeStyles, Theme } from '@material-ui/core';
|
||||
import NavbarItem from './NavbarItem';
|
||||
|
||||
const useStyles = makeStyles<Theme>({
|
||||
nav: {
|
||||
gridArea: 'pageNav',
|
||||
width: 220,
|
||||
transition: 'width 0.07s, height 0s',
|
||||
transitionTimingFunction: 'ease-in',
|
||||
backgroundColor: '#eeeeee',
|
||||
boxShadow: '0px 0 4px 0px rgba(0,0,0,0.35)',
|
||||
height: '100%',
|
||||
},
|
||||
list: {
|
||||
padding: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const DefaultEntityPageNavbar: FC<EntityPageNavbarProps> = ({ navItems }) => {
|
||||
const classes = useStyles();
|
||||
const entityUri = useEntityUri();
|
||||
|
||||
return (
|
||||
<nav className={classes.nav}>
|
||||
<List className={classes.list}>
|
||||
{navItems.map((navItem, index) => (
|
||||
<NavbarItem key={index} navItem={navItem} entityUri={entityUri} />
|
||||
))}
|
||||
</List>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default DefaultEntityPageNavbar;
|
||||
@@ -1,71 +0,0 @@
|
||||
import React, { FC } from 'react';
|
||||
import { EntityLink } from '../..';
|
||||
import {
|
||||
ListItem,
|
||||
makeStyles,
|
||||
Theme,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { EntityPageNavItem } from '../../api/entityView/types';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
root: {
|
||||
display: 'block',
|
||||
overflow: 'hidden',
|
||||
borderBottom: `1px solid #d9d9d9`,
|
||||
},
|
||||
label: {
|
||||
color: '#333',
|
||||
fontWeight: 'bolder',
|
||||
whiteSpace: 'nowrap',
|
||||
lineHeight: 1.0,
|
||||
},
|
||||
iconImg: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
icon: {
|
||||
margin: theme.spacing(0.5, 2, 0.5, 0),
|
||||
minWidth: 0,
|
||||
fontSize: 24,
|
||||
},
|
||||
expand: {
|
||||
color: 'white',
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
navItem: EntityPageNavItem;
|
||||
entityUri: string;
|
||||
};
|
||||
|
||||
const NavbarItem: FC<Props> = ({ navItem, entityUri }) => {
|
||||
const classes = useStyles();
|
||||
const IconComponent = navItem.icon;
|
||||
|
||||
return (
|
||||
<EntityLink
|
||||
className={classes.root}
|
||||
uri={entityUri}
|
||||
subPath={navItem.target}
|
||||
>
|
||||
<ListItem button className={`${classes.listItemGutters}`}>
|
||||
<ListItemIcon className={classes.icon}>
|
||||
<IconComponent fontSize="inherit" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant="subtitle1" className={classes.label}>
|
||||
{navItem.title}
|
||||
</Typography>
|
||||
}
|
||||
disableTypography
|
||||
/>
|
||||
</ListItem>
|
||||
</EntityLink>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavbarItem;
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './DefaultEntityPageNavbar';
|
||||
@@ -1,51 +0,0 @@
|
||||
import React, { FC } from 'react';
|
||||
import { Link, LinkProps } from 'react-router-dom';
|
||||
|
||||
type Props = Omit<LinkProps, 'to'> & {
|
||||
subPath?: string;
|
||||
} & (
|
||||
| {
|
||||
kind: string;
|
||||
id?: string;
|
||||
}
|
||||
| {
|
||||
uri: string;
|
||||
}
|
||||
);
|
||||
|
||||
export function buildPath(kind: string, id?: string, subPath?: string) {
|
||||
if (id) {
|
||||
if (subPath) {
|
||||
return `/entity/${kind}/${id}/${subPath.replace(/^\//, '')}`;
|
||||
}
|
||||
return `/entity/${kind}/${id}`;
|
||||
}
|
||||
return `/entity/${kind}`;
|
||||
}
|
||||
|
||||
const EntityLink: FC<Props> = ({ subPath, children, ...props }) => {
|
||||
if ('kind' in props) {
|
||||
const { kind, id } = props;
|
||||
return (
|
||||
<Link to={buildPath(kind, id, subPath)} {...props}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
const match = props.uri.match(/entity:([^:]+)(:[^:]+)?/);
|
||||
if (!match) {
|
||||
throw new TypeError(`Invalid entity uri: '${props.uri}'`);
|
||||
}
|
||||
|
||||
const [, kind, maybeId] = match;
|
||||
const id = maybeId ? maybeId.slice(1) : undefined;
|
||||
|
||||
return (
|
||||
<Link to={buildPath(kind, id, subPath)} {...props}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default EntityLink;
|
||||
@@ -1,16 +0,0 @@
|
||||
import React, { FC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useEntity } from '../../api';
|
||||
import { buildPath } from './EntityLink';
|
||||
|
||||
type Props = {
|
||||
view: string;
|
||||
};
|
||||
|
||||
const RelativeEntityLink: FC<Props> = ({ view, children }) => {
|
||||
const entity = useEntity();
|
||||
|
||||
return <Link to={buildPath(entity.kind, entity.id, view)}>{children}</Link>;
|
||||
};
|
||||
|
||||
export default RelativeEntityLink;
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default } from './EntityLink';
|
||||
export { default as RelativeEntityLink } from './RelativeEntityLink';
|
||||
@@ -1,8 +1,4 @@
|
||||
export * from './api';
|
||||
export {
|
||||
default as EntityLink,
|
||||
RelativeEntityLink,
|
||||
} from './components/EntityLink';
|
||||
export { default as Page } from './layout/Page';
|
||||
export { gradients, pageTheme, PageTheme } from './layout/Page';
|
||||
export { default as Content } from './layout/Content/Content';
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { FC } from 'react';
|
||||
import { Typography, Grid } from '@material-ui/core';
|
||||
import { Typography, Link, Grid } from '@material-ui/core';
|
||||
import HomePageTimer from '../HomepageTimer';
|
||||
import {
|
||||
Content,
|
||||
EntityLink,
|
||||
InfoCard,
|
||||
Header,
|
||||
Page,
|
||||
@@ -27,9 +26,9 @@ const HomePage: FC<{}> = () => {
|
||||
return {
|
||||
id,
|
||||
entity: (
|
||||
<EntityLink kind={kind} id={id}>
|
||||
<Link href={`entity/${kind}/${id}`}>
|
||||
<Typography color="primary">{id}</Typography>
|
||||
</EntityLink>
|
||||
</Link>
|
||||
),
|
||||
kind: <Typography>{kind}</Typography>,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user