@akinon/ui-shell-dev 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/akinon-favicon.png +0 -0
- package/dist/akinon-logo.png +0 -0
- package/dist/akinon-logo.svg +10 -0
- package/dist/assets/index-BFs1gfY5.css +1 -0
- package/dist/assets/index-Dk6Gr15i.js +408 -0
- package/dist/assets/jost-cyrillic-wght-normal-B0_vFdaS.woff2 +0 -0
- package/dist/assets/jost-latin-ext-wght-normal-B4kqP43q.woff2 +0 -0
- package/dist/assets/jost-latin-wght-normal-CfFW3YMY.woff2 +0 -0
- package/dist/index.html +2 -2
- package/dist/logo.png +0 -0
- package/dist/symbol.png +0 -0
- package/package.json +9 -9
- package/public/akinon-favicon.png +0 -0
- package/public/akinon-logo.png +0 -0
- package/public/akinon-logo.svg +10 -0
- package/public/logo.png +0 -0
- package/public/symbol.png +0 -0
- package/src/components/AppLayout.tsx +44 -48
- package/src/components/Header.tsx +33 -48
- package/src/components/HeaderSearch.tsx +12 -0
- package/src/components/HeaderUser.tsx +23 -0
- package/src/components/Navigation.tsx +55 -0
- package/src/components/PageContent.tsx +9 -0
- package/src/components/PageHeading.tsx +61 -0
- package/src/components/PluginTestPage.scss +26 -0
- package/src/components/PluginTestPage.tsx +105 -0
- package/src/components/WelcomePage.tsx +117 -0
- package/src/components/layout.scss +48 -0
- package/src/global.scss +33 -0
- package/src/main.tsx +8 -2
- package/src/pages/Dashboard.tsx +25 -0
- package/src/pages/External.tsx +15 -0
- package/src/pages/PluginTest.tsx +7 -0
- package/src/pages/pages.scss +9 -0
- package/src/providers.tsx +108 -0
- package/src/routes.tsx +44 -0
- package/src/server/dev-server.mjs +18 -15
- package/src/shell-app.tsx +13 -4
- package/src/types.ts +4 -0
- package/src/utils/navigation.tsx +65 -0
- package/dist/assets/index-CuIRnKVE.css +0 -1
- package/dist/assets/index-DUUd718F.js +0 -193
- package/src/components/Sidebar.tsx +0 -74
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useAppShell } from '@akinon/app-shell';
|
|
2
|
+
import { Button, Card, Divider, Tag } from '@akinon/ui-react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { useNavigate } from 'react-router-dom';
|
|
5
|
+
|
|
6
|
+
export const WelcomePage: React.FC = () => {
|
|
7
|
+
const navigate = useNavigate();
|
|
8
|
+
const { apps } = useAppShell();
|
|
9
|
+
const app = apps[0]; // Get the first (and likely only) app
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div>
|
|
13
|
+
<Card title="Application Configuration" style={{ marginBottom: '24px' }}>
|
|
14
|
+
<div
|
|
15
|
+
style={{
|
|
16
|
+
display: 'grid',
|
|
17
|
+
gridTemplateColumns: '1fr 1fr',
|
|
18
|
+
gap: '16px'
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
<div>
|
|
22
|
+
<strong>Application Name:</strong>
|
|
23
|
+
<p style={{ margin: '12px 0 4px 0' }}>
|
|
24
|
+
{app?.name || 'Development App'}
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
<div>
|
|
28
|
+
<strong>Application Type:</strong>
|
|
29
|
+
<p style={{ margin: '12px 0 4px 0' }}>
|
|
30
|
+
<Tag color={app?.type === 'full_page' ? 'blue' : 'green'}>
|
|
31
|
+
{app?.type === 'full_page' ? 'fullpage' : 'plugin'}
|
|
32
|
+
</Tag>
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
<div>
|
|
36
|
+
<strong>Development URL:</strong>
|
|
37
|
+
<p
|
|
38
|
+
style={{
|
|
39
|
+
margin: '12px 0 4px 0',
|
|
40
|
+
color: '#999',
|
|
41
|
+
fontFamily: 'monospace',
|
|
42
|
+
fontSize: '13px'
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
{app?.url || 'http://localhost:4001'}
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
<div>
|
|
49
|
+
<strong>Shell Theme:</strong>
|
|
50
|
+
<p style={{ margin: '12px 0 4px 0', color: '#999' }}>omnitron</p>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</Card>
|
|
54
|
+
|
|
55
|
+
<Card title="Quick Actions" style={{ marginBottom: '24px' }}>
|
|
56
|
+
<div style={{ display: 'flex', gap: '16px', flexWrap: 'wrap' }}>
|
|
57
|
+
<Button
|
|
58
|
+
type="primary"
|
|
59
|
+
onClick={() => navigate(`/external/${app?.slug}`)}
|
|
60
|
+
>
|
|
61
|
+
Launch Application
|
|
62
|
+
</Button>
|
|
63
|
+
{app?.type === 'plugin' && (
|
|
64
|
+
<Button onClick={() => navigate('/plugin-test')}>
|
|
65
|
+
Test Plugin Integration
|
|
66
|
+
</Button>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
</Card>
|
|
70
|
+
|
|
71
|
+
<Card title="Development Instructions">
|
|
72
|
+
<div style={{ lineHeight: '1.6' }}>
|
|
73
|
+
<h4 style={{ margin: '0 0 8px 0' }}>Getting Started:</h4>
|
|
74
|
+
<ol style={{ paddingLeft: '20px' }}>
|
|
75
|
+
<li>
|
|
76
|
+
Make sure your application is running at{' '}
|
|
77
|
+
<code>{app?.url || 'http://localhost:4001'}</code>
|
|
78
|
+
</li>
|
|
79
|
+
<li>
|
|
80
|
+
Click "Launch Application" to load your app in the shell
|
|
81
|
+
environment
|
|
82
|
+
</li>
|
|
83
|
+
{app?.type === 'plugin' && (
|
|
84
|
+
<li>
|
|
85
|
+
Use "Test Plugin Integration" to test your plugin in
|
|
86
|
+
different placeholder configurations
|
|
87
|
+
</li>
|
|
88
|
+
)}
|
|
89
|
+
<li>
|
|
90
|
+
The shell provides a realistic integration environment for testing
|
|
91
|
+
</li>
|
|
92
|
+
</ol>
|
|
93
|
+
|
|
94
|
+
<Divider />
|
|
95
|
+
|
|
96
|
+
<h4 style={{ margin: '16px 0 8px 0' }}>
|
|
97
|
+
Application Type:{' '}
|
|
98
|
+
{app?.type === 'full_page' ? 'fullpage' : 'plugin'}
|
|
99
|
+
</h4>
|
|
100
|
+
{app?.type === 'full_page' ? (
|
|
101
|
+
<p style={{ margin: 0, color: '#666' }}>
|
|
102
|
+
Your fullpage application will render as complete pages within the
|
|
103
|
+
shell environment, providing full control over the content area
|
|
104
|
+
and routing.
|
|
105
|
+
</p>
|
|
106
|
+
) : (
|
|
107
|
+
<p style={{ margin: 0, color: '#666' }}>
|
|
108
|
+
Your plugin application will render within designated placeholders
|
|
109
|
+
in the host application, providing component-level integration
|
|
110
|
+
capabilities.
|
|
111
|
+
</p>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
</Card>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
.akinon-layout {
|
|
2
|
+
height: 100vh;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.akinon-layout-sider {
|
|
6
|
+
overflow-x: hidden;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.akinon-layout-content {
|
|
10
|
+
height: calc(100vh - 64px - 40px); // Subtract header and footer
|
|
11
|
+
overflow-y: auto;
|
|
12
|
+
overflow-x: hidden;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.akinon-menu-item.akinon-menu-item-disabled {
|
|
16
|
+
opacity: 0.6;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.akinon-menu-item {
|
|
20
|
+
font-weight: 500 !important;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.akinon-menu-item-selected {
|
|
24
|
+
font-weight: 600 !important;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.log-messages {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
padding: 0.75rem;
|
|
31
|
+
|
|
32
|
+
.akinon-space-item {
|
|
33
|
+
width: 100%;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&-item {
|
|
37
|
+
border-radius: 10px;
|
|
38
|
+
border: 1px solid #fff;
|
|
39
|
+
padding: 0.75rem;
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
margin-bottom: 0.25rem;
|
|
43
|
+
|
|
44
|
+
& > div {
|
|
45
|
+
width: 100%;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/global.scss
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
*,
|
|
2
|
+
*::before,
|
|
3
|
+
*::after {
|
|
4
|
+
font-family: 'Jost Variable', sans-serif;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
html,
|
|
8
|
+
body,
|
|
9
|
+
#root {
|
|
10
|
+
height: 100%;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
main {
|
|
16
|
+
padding: 4px 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
::-webkit-scrollbar {
|
|
20
|
+
width: 8px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
::-webkit-scrollbar-track {
|
|
24
|
+
background-color: var(--color-ebonyClay-400);
|
|
25
|
+
border: none;
|
|
26
|
+
padding: 1px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
::-webkit-scrollbar-thumb {
|
|
30
|
+
background-color: var(--color-ebonyClay-700);
|
|
31
|
+
border-radius: 3px;
|
|
32
|
+
border: none;
|
|
33
|
+
}
|
package/src/main.tsx
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import '@akinon/fonts-jost-variable';
|
|
2
|
+
import '@akinon/ui-utils';
|
|
3
|
+
import './global.scss';
|
|
4
|
+
|
|
1
5
|
import React from 'react';
|
|
2
6
|
import ReactDOM from 'react-dom/client';
|
|
3
7
|
|
|
@@ -9,8 +13,10 @@ const config: ShellConfig = (
|
|
|
9
13
|
window as unknown as { __SHELL_CONFIG__?: ShellConfig }
|
|
10
14
|
).__SHELL_CONFIG__ || {
|
|
11
15
|
plugin: {
|
|
12
|
-
url: 'http://localhost:
|
|
13
|
-
path: './src'
|
|
16
|
+
url: 'http://localhost:4001',
|
|
17
|
+
path: './src',
|
|
18
|
+
name: 'Development App',
|
|
19
|
+
type: 'fullpage'
|
|
14
20
|
},
|
|
15
21
|
shell: {
|
|
16
22
|
port: 4000,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import './pages.scss';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
import { PageContent } from '../components/PageContent';
|
|
6
|
+
import { PageHeading } from '../components/PageHeading';
|
|
7
|
+
import { WelcomePage } from '../components/WelcomePage';
|
|
8
|
+
|
|
9
|
+
export const PageDashboard: React.FC = () => {
|
|
10
|
+
return (
|
|
11
|
+
<div className="page-dashboard">
|
|
12
|
+
<div className="page-heading-container">
|
|
13
|
+
<PageHeading
|
|
14
|
+
title="Welcome"
|
|
15
|
+
description="Development environment for testing and integrating your applications"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
<div className="page-content-container">
|
|
19
|
+
<PageContent>
|
|
20
|
+
<WelcomePage />
|
|
21
|
+
</PageContent>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import './pages.scss';
|
|
2
|
+
|
|
3
|
+
import { AppRenderer } from '@akinon/app-shell';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
export const PageExternal: React.FC<{ id: number; path?: string }> = ({
|
|
7
|
+
id,
|
|
8
|
+
path
|
|
9
|
+
}) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className="page-plugin-external">
|
|
12
|
+
<AppRenderer id={id} path={path} />
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { ApplicationActions, RegisteredApp } from '@akinon/app-shell';
|
|
2
|
+
import { AppShellProvider, type ShellNavigation } from '@akinon/app-shell';
|
|
3
|
+
import { notification } from '@akinon/ui-react';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { useNavigate, useSearchParams } from 'react-router-dom';
|
|
6
|
+
|
|
7
|
+
import type { ShellConfig } from './types';
|
|
8
|
+
|
|
9
|
+
interface ProvidersProps {
|
|
10
|
+
config: ShellConfig;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const Providers: React.FC<ProvidersProps> = ({ config, children }) => {
|
|
15
|
+
const [api, contextHolder] = notification.useNotification();
|
|
16
|
+
const [searchParams] = useSearchParams();
|
|
17
|
+
const navigate = useNavigate();
|
|
18
|
+
|
|
19
|
+
// Convert shell config to apps format for AppShellProvider
|
|
20
|
+
const apps: RegisteredApp[] = [
|
|
21
|
+
{
|
|
22
|
+
id: 1,
|
|
23
|
+
url: config.plugin.url,
|
|
24
|
+
slug: config.plugin.name.toLowerCase().replace(/\s+/g, '-'),
|
|
25
|
+
path: '', // Empty path for development like playground
|
|
26
|
+
type: config.plugin.type === 'fullpage' ? 'full_page' : 'plugin',
|
|
27
|
+
name: config.plugin.name
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const navigation: ShellNavigation = {
|
|
32
|
+
navigate: ({ id, path, external }: any) => {
|
|
33
|
+
const app = apps.find(app => app.id === id);
|
|
34
|
+
if (!app) return;
|
|
35
|
+
|
|
36
|
+
const uri = external ? path : `/external/${app.slug}${path || ''}`;
|
|
37
|
+
navigate(uri);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const data = {
|
|
42
|
+
lng: 'en',
|
|
43
|
+
username: 'Developer',
|
|
44
|
+
searchParams: Object.fromEntries([...searchParams])
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const defaultActions = {
|
|
48
|
+
showModalDialog: () => {
|
|
49
|
+
// Implementation for showing a modal dialog
|
|
50
|
+
},
|
|
51
|
+
showToast: (
|
|
52
|
+
content: string,
|
|
53
|
+
type: 'success' | 'error' | 'info' | 'warning' | 'loading' | 'destroy'
|
|
54
|
+
) => {
|
|
55
|
+
if (type === 'loading' || type === 'destroy') {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
api[type]({
|
|
59
|
+
message: content,
|
|
60
|
+
placement: 'top'
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
showErrorMessage: (title: string, content: string) => {
|
|
64
|
+
api.error({
|
|
65
|
+
message: title,
|
|
66
|
+
description: content,
|
|
67
|
+
placement: 'top'
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
showRichModal: () => {
|
|
71
|
+
// Rich modal implementation
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const customActions = {
|
|
76
|
+
reloadPage: () => window.location.reload(),
|
|
77
|
+
logMessage: (newItem: Object) => {
|
|
78
|
+
try {
|
|
79
|
+
const prevItems = JSON.parse(
|
|
80
|
+
localStorage.getItem('logMessages') || '[]'
|
|
81
|
+
);
|
|
82
|
+
localStorage.setItem(
|
|
83
|
+
'logMessages',
|
|
84
|
+
JSON.stringify([...prevItems, newItem])
|
|
85
|
+
);
|
|
86
|
+
} catch {}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const actions: ApplicationActions = {
|
|
91
|
+
...defaultActions,
|
|
92
|
+
actions: {
|
|
93
|
+
...customActions
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<AppShellProvider
|
|
99
|
+
apps={apps}
|
|
100
|
+
navigation={navigation}
|
|
101
|
+
data={data}
|
|
102
|
+
actions={actions}
|
|
103
|
+
>
|
|
104
|
+
{children}
|
|
105
|
+
{contextHolder}
|
|
106
|
+
</AppShellProvider>
|
|
107
|
+
);
|
|
108
|
+
};
|
package/src/routes.tsx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useAppShell } from '@akinon/app-shell';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Route, Routes as ReactRouterRoutes } from 'react-router-dom';
|
|
4
|
+
|
|
5
|
+
import { PageDashboard } from './pages/Dashboard';
|
|
6
|
+
import { PageExternal } from './pages/External';
|
|
7
|
+
import { PagePluginTest } from './pages/PluginTest';
|
|
8
|
+
|
|
9
|
+
export const Routes = () => {
|
|
10
|
+
const { apps, configs } = useAppShell();
|
|
11
|
+
const fullpageApps = apps.filter(app => app.type === 'full_page');
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<ReactRouterRoutes>
|
|
15
|
+
{fullpageApps.map(app => (
|
|
16
|
+
<Route key={app.id} path={`/external/${app.slug}`}>
|
|
17
|
+
<Route index element={<PageExternal id={app.id} path={app.path} />} />
|
|
18
|
+
|
|
19
|
+
{(configs.get(app.id) as any)?.menu?.map((sub: any) => {
|
|
20
|
+
return (
|
|
21
|
+
sub.path !== '/' && (
|
|
22
|
+
<Route
|
|
23
|
+
key={sub.path}
|
|
24
|
+
path={`/external/${app.slug}${sub.path}`}
|
|
25
|
+
element={
|
|
26
|
+
<PageExternal
|
|
27
|
+
id={app.id}
|
|
28
|
+
path={
|
|
29
|
+
app.subSlug ? `${app.subSlug}${sub.path}` : sub.path
|
|
30
|
+
}
|
|
31
|
+
/>
|
|
32
|
+
}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
})}
|
|
37
|
+
</Route>
|
|
38
|
+
))}
|
|
39
|
+
|
|
40
|
+
<Route path="/plugin-test" element={<PagePluginTest />} />
|
|
41
|
+
<Route path="/" element={<PageDashboard />} />
|
|
42
|
+
</ReactRouterRoutes>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -35,12 +35,13 @@ export class ShellDevServer {
|
|
|
35
35
|
async start() {
|
|
36
36
|
try {
|
|
37
37
|
console.log('🔧 Starting shell server initialization...');
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
const shellRoot = path.resolve(__dirname, '../..');
|
|
40
40
|
const distPath = path.resolve(shellRoot, 'dist');
|
|
41
41
|
// Only use production mode if explicitly set via env var
|
|
42
|
-
const isProduction =
|
|
43
|
-
|
|
42
|
+
const isProduction =
|
|
43
|
+
process.env.NODE_ENV === 'production' && fs.existsSync(distPath);
|
|
44
|
+
|
|
44
45
|
console.log(`📁 Shell root: ${shellRoot}`);
|
|
45
46
|
console.log(`🏭 Production mode: ${isProduction}`);
|
|
46
47
|
|
|
@@ -50,14 +51,14 @@ export class ShellDevServer {
|
|
|
50
51
|
if (isProduction) {
|
|
51
52
|
// Production: serve built assets
|
|
52
53
|
this.app.use('/assets', express.static(path.join(distPath, 'assets')));
|
|
53
|
-
|
|
54
|
+
|
|
54
55
|
this.app.get('*', (req, res, next) => {
|
|
55
56
|
if (req.path.startsWith('/api')) {
|
|
56
57
|
return next();
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
console.log(`📄 Serving production HTML for: ${req.url}`);
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
let template = fs.readFileSync(
|
|
62
63
|
path.resolve(distPath, 'index.html'),
|
|
63
64
|
'utf-8'
|
|
@@ -85,20 +86,18 @@ export class ShellDevServer {
|
|
|
85
86
|
|
|
86
87
|
console.log('✅ Vite server created successfully');
|
|
87
88
|
|
|
88
|
-
//
|
|
89
|
-
this.app.use(this.viteServer.middlewares);
|
|
90
|
-
|
|
91
|
-
console.log('✅ Vite middleware configured');
|
|
92
|
-
|
|
93
|
-
// Serve the shell app (only for HTML requests)
|
|
89
|
+
// Serve the shell app (only for HTML requests) - MUST be before Vite middleware
|
|
94
90
|
this.app.get('*', async (req, res, next) => {
|
|
95
|
-
if (
|
|
91
|
+
if (
|
|
92
|
+
req.path.startsWith('/api') ||
|
|
93
|
+
req.path.startsWith('/src') ||
|
|
94
|
+
req.path.startsWith('/@') ||
|
|
95
|
+
req.path.includes('.')
|
|
96
|
+
) {
|
|
96
97
|
return next();
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
try {
|
|
100
|
-
console.log(`📄 Serving development HTML for: ${req.url}`);
|
|
101
|
-
|
|
102
101
|
let template = fs.readFileSync(
|
|
103
102
|
path.resolve(__dirname, '../../index.html'),
|
|
104
103
|
'utf-8'
|
|
@@ -115,7 +114,6 @@ export class ShellDevServer {
|
|
|
115
114
|
`<script>window.__SHELL_CONFIG__ = ${JSON.stringify(this.config)};</script>`
|
|
116
115
|
);
|
|
117
116
|
|
|
118
|
-
console.log(`✅ HTML processed successfully for ${req.url}`);
|
|
119
117
|
res.status(200).set({ 'Content-Type': 'text/html' }).end(template);
|
|
120
118
|
} catch (error) {
|
|
121
119
|
console.error(`❌ Error serving ${req.url}:`, error);
|
|
@@ -123,6 +121,11 @@ export class ShellDevServer {
|
|
|
123
121
|
next(error);
|
|
124
122
|
}
|
|
125
123
|
});
|
|
124
|
+
|
|
125
|
+
// Use Vite middleware for assets and HMR - MUST be after our route handler
|
|
126
|
+
this.app.use(this.viteServer.middlewares);
|
|
127
|
+
|
|
128
|
+
console.log('✅ Vite middleware configured');
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
// Start server
|
package/src/shell-app.tsx
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { AkinonUiProvider } from '@akinon/ui-
|
|
1
|
+
import { AkinonUiProvider } from '@akinon/ui-react';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
3
4
|
|
|
4
5
|
import { AppLayout } from './components/AppLayout';
|
|
6
|
+
import { Providers } from './providers';
|
|
7
|
+
import { Routes } from './routes';
|
|
5
8
|
import type { ShellConfig } from './types';
|
|
6
9
|
|
|
7
10
|
interface ShellAppProps {
|
|
@@ -10,8 +13,14 @@ interface ShellAppProps {
|
|
|
10
13
|
|
|
11
14
|
export const ShellApp: React.FC<ShellAppProps> = ({ config }) => {
|
|
12
15
|
return (
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
+
<BrowserRouter>
|
|
17
|
+
<AkinonUiProvider>
|
|
18
|
+
<Providers config={config}>
|
|
19
|
+
<AppLayout>
|
|
20
|
+
<Routes />
|
|
21
|
+
</AppLayout>
|
|
22
|
+
</Providers>
|
|
23
|
+
</AkinonUiProvider>
|
|
24
|
+
</BrowserRouter>
|
|
16
25
|
);
|
|
17
26
|
};
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,8 @@ export interface ShellConfig {
|
|
|
2
2
|
plugin: {
|
|
3
3
|
url: string;
|
|
4
4
|
path: string;
|
|
5
|
+
name: string;
|
|
6
|
+
type: 'fullpage' | 'plugin';
|
|
5
7
|
};
|
|
6
8
|
shell: {
|
|
7
9
|
port: number;
|
|
@@ -38,3 +40,5 @@ export interface ThemeConfig {
|
|
|
38
40
|
height: number;
|
|
39
41
|
};
|
|
40
42
|
}
|
|
43
|
+
|
|
44
|
+
export type ViewType = 'welcome' | 'app' | 'plugin-test';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { RegisteredApp } from '@akinon/app-shell';
|
|
2
|
+
import type { TMenuItemType } from '@akinon/ui-react';
|
|
3
|
+
|
|
4
|
+
const STATIC_NAVIGATION_ITEMS: TMenuItemType[] = [
|
|
5
|
+
{
|
|
6
|
+
key: '/',
|
|
7
|
+
label: 'Welcome',
|
|
8
|
+
icon: 'dashboard'
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
key: '/placeholder-1',
|
|
12
|
+
label: 'Settings',
|
|
13
|
+
icon: 'ayarlar',
|
|
14
|
+
disabled: true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
key: '/placeholder-2',
|
|
18
|
+
label: 'Help & Support',
|
|
19
|
+
icon: 'bilgilendirme_servis',
|
|
20
|
+
disabled: true
|
|
21
|
+
}
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const PLUGIN_NAVIGATION_ITEMS: TMenuItemType[] = [
|
|
25
|
+
{
|
|
26
|
+
key: '/plugin-test',
|
|
27
|
+
label: 'Plugin Test',
|
|
28
|
+
icon: 'box'
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const getStaticNavigationItems = () => {
|
|
33
|
+
return STATIC_NAVIGATION_ITEMS;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const getFullpageNavigationItems = (
|
|
37
|
+
apps: RegisteredApp[],
|
|
38
|
+
configs: Map<number, any>
|
|
39
|
+
): TMenuItemType[] => {
|
|
40
|
+
return apps
|
|
41
|
+
.filter(app => app.type === 'full_page')
|
|
42
|
+
.map((app): TMenuItemType => {
|
|
43
|
+
return {
|
|
44
|
+
key: `/external/${app.slug}`,
|
|
45
|
+
label: app.name,
|
|
46
|
+
icon: 'stoklistesi',
|
|
47
|
+
children: configs
|
|
48
|
+
.get(app.id)
|
|
49
|
+
?.menu?.map((sub: any): TMenuItemType | false => {
|
|
50
|
+
if (sub?.child) return false;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
key: `/external/${app.slug}${sub.path}`,
|
|
54
|
+
label: sub.label,
|
|
55
|
+
icon: 'eksi'
|
|
56
|
+
};
|
|
57
|
+
})
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const getPluginNavigationItems = () => {
|
|
64
|
+
return PLUGIN_NAVIGATION_ITEMS;
|
|
65
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.akinon-layout-footer{text-align:center}.akinon-layout-header{padding-inline:1rem;height:3.5rem;border-bottom:1px solid var(--color-gray-900);display:flex;align-items:center;justify-content:space-between;line-height:unset;position:sticky;top:0;z-index:999}.akinon-layout-sider{position:sticky;top:0;height:100svh;overflow-y:auto}.akinon-layout-sider.left{inset-inline-start:0}.akinon-layout-sider.right{inset-inline-end:0}.akinon-layout-sider .logo-container{display:flex;flex-direction:row;align-items:center;height:3.5rem;gap:.8rem;background-color:var(--color-ebonyClay-600);padding-inline:1.5rem}.akinon-layout-sider .logo-container.collapsed{justify-content:center;padding-inline:1rem}.akinon-layout-sider .logo-container.collapsed .brand-image{display:none}
|