@nocobase/portal-template-default 1.0.2 → 1.0.4
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/README.MD +10 -5
- package/package.json +1 -1
- package/registry/nocobase-ai/components/chat/chat-message.tsx +8 -6
- package/registry/nocobase-ai/components/chat/chat-messages.tsx +8 -3
- package/registry/nocobase-ai/components/chat/markdown-message.tsx +26 -0
- package/registry/nocobase-ai/components/chat/sub-agent-conversation.tsx +3 -3
- package/registry/nocobase-ai/demo/configuration-gate.tsx +83 -35
- package/registry/nocobase-i18n/README.md +23 -32
- package/registry/nocobase-i18n/components/language-switcher.tsx +1 -1
- package/registry/nocobase-i18n/demo/index.tsx +2 -2
- package/registry/nocobase-i18n/demo/prompt-generator.tsx +3 -2
- package/registry/nocobase-i18n/extension.tsx +3 -4
- package/registry/nocobase-i18n/index.ts +4 -3
- package/registry/nocobase-i18n/locales/en-US.ts +2 -52
- package/registry/nocobase-i18n/locales/index.ts +8 -0
- package/registry/nocobase-i18n/locales/zh-CN.ts +2 -52
- package/registry/nocobase-i18n/provider.tsx +18 -6
- package/registry/nocobase-i18n/server-resources.ts +13 -31
- package/registry/nocobase-i18n/tests/i18n-regression.mjs +14 -5
- package/registry/nocobase-route-surfaces/README.md +21 -0
- package/registry/nocobase-route-surfaces/demo/index.tsx +266 -0
- package/registry/nocobase-route-surfaces/demo/lazy-route.tsx +11 -0
- package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +90 -0
- package/registry/nocobase-route-surfaces/demo/scenarios.ts +95 -0
- package/registry/nocobase-route-surfaces/extension.tsx +119 -0
- package/registry/nocobase-route-surfaces/index.ts +7 -0
- package/registry/nocobase-route-surfaces/route-dialog.tsx +145 -0
- package/registry/nocobase-route-surfaces/route-drawer.tsx +186 -0
- package/registry/nocobase-route-surfaces/route-page.tsx +29 -0
- package/registry/nocobase-route-surfaces/route-surface-context.ts +13 -0
- package/registry/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +106 -0
- package/registry/nocobase-route-surfaces/use-route-surface-close.ts +15 -0
- package/registry/nocobase-route-surfaces/use-route-surface-state.ts +65 -0
- package/registry.config.json +30 -3
- package/src/App.tsx +65 -35
- package/src/app/extension.ts +1 -2
- package/src/app/extensions.tsx +0 -14
- package/src/components/app-shell/breadcrumb.tsx +67 -2
- package/src/components/app-shell/sidebar.tsx +2 -2
- package/src/components/resources/views/list-view.tsx +3 -2
- package/src/components/ui/sheet.tsx +22 -21
- package/src/lib/nocobase/client.ts +6 -1
- package/src/locales/en-US.ts +81 -0
- package/src/locales/zh-CN.ts +77 -0
- package/src/pages/users/create.tsx +76 -53
- package/src/pages/users/edit.tsx +87 -56
- package/src/pages/users/index.ts +3 -0
- package/src/pages/users/layout.tsx +15 -0
- package/src/pages/users/list.tsx +19 -21
- package/src/pages/users/role-badges.tsx +43 -0
- package/src/pages/users/role-detail.tsx +272 -0
- package/src/pages/users/role-utils.ts +6 -0
- package/src/pages/users/routes.ts +24 -0
- package/src/pages/users/show.tsx +195 -104
- package/src/pages/users/types.ts +13 -0
- package/src/providers/i18n/index.ts +3 -0
- package/{registry/nocobase-i18n → src/providers/i18n}/runtime.ts +63 -16
- package/src/providers/system-settings/context.ts +26 -0
- package/src/providers/system-settings/index.ts +2 -0
- package/src/providers/system-settings/provider.tsx +94 -0
- /package/{registry/nocobase-i18n → src/providers/i18n}/i18n-provider.ts +0 -0
- /package/{registry/nocobase-i18n → src/providers/i18n}/locale-store.ts +0 -0
package/registry.config.json
CHANGED
|
@@ -2,14 +2,41 @@
|
|
|
2
2
|
"name": "nocobase-ai",
|
|
3
3
|
"homepage": "https://www.nocobase.com",
|
|
4
4
|
"items": [
|
|
5
|
+
{
|
|
6
|
+
"name": "route-surfaces",
|
|
7
|
+
"type": "registry:lib",
|
|
8
|
+
"title": "NocoBase Route Surfaces",
|
|
9
|
+
"description": "URL-backed drawer, dialog, and page containers with nested drawer stacking and a shared close lifecycle.",
|
|
10
|
+
"dependencies": [
|
|
11
|
+
"@base-ui/react@^1.6.0",
|
|
12
|
+
"@refinedev/core@^5.0.8",
|
|
13
|
+
"lucide-react@^0.487.0",
|
|
14
|
+
"react-router@^7.0.2"
|
|
15
|
+
],
|
|
16
|
+
"registryDependencies": [
|
|
17
|
+
"alert-dialog",
|
|
18
|
+
"badge",
|
|
19
|
+
"button",
|
|
20
|
+
"card",
|
|
21
|
+
"input",
|
|
22
|
+
"label",
|
|
23
|
+
"select",
|
|
24
|
+
"textarea"
|
|
25
|
+
],
|
|
26
|
+
"docs": "Provides presentation containers plus an integrated Demo and Prompt generator. Route definitions, ACL guards, data fetching, and business content remain application-owned.",
|
|
27
|
+
"source": {
|
|
28
|
+
"root": "registry/nocobase-route-surfaces",
|
|
29
|
+
"target": "src/extensions/nocobase-route-surfaces",
|
|
30
|
+
"include": ["."]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
5
33
|
{
|
|
6
34
|
"name": "i18n",
|
|
7
35
|
"type": "registry:block",
|
|
8
36
|
"title": "NocoBase i18n",
|
|
9
|
-
"description": "Optional
|
|
37
|
+
"description": "Optional NocoBase server-language resources, user preference persistence, reusable language controls, and an integrated showcase for the Starter's built-in i18n runtime.",
|
|
10
38
|
"dependencies": [
|
|
11
39
|
"@refinedev/core@^5.0.8",
|
|
12
|
-
"i18next@^25.3.2",
|
|
13
40
|
"lucide-react@^0.487.0",
|
|
14
41
|
"react-router@^7.0.2"
|
|
15
42
|
],
|
|
@@ -23,7 +50,7 @@
|
|
|
23
50
|
"select",
|
|
24
51
|
"textarea"
|
|
25
52
|
],
|
|
26
|
-
"docs": "Requires a compatible NocoBase Starter with the
|
|
53
|
+
"docs": "Requires a compatible NocoBase Starter with the built-in i18n provider, shared System Settings bootstrap, and AppExtension Provider/UserMenuItems host fields. The Registry reuses systemSettings:get data and calls app:getLang only for registered dynamic namespaces such as lm-collections.",
|
|
27
54
|
"source": {
|
|
28
55
|
"root": "registry/nocobase-i18n",
|
|
29
56
|
"target": "src/extensions/nocobase-i18n",
|
package/src/App.tsx
CHANGED
|
@@ -5,7 +5,14 @@ import routerProvider, {
|
|
|
5
5
|
CatchAllNavigate,
|
|
6
6
|
UnsavedChangesNotifier,
|
|
7
7
|
} from "@refinedev/react-router";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
UserCreate,
|
|
10
|
+
UserEdit,
|
|
11
|
+
UserResourceLayout,
|
|
12
|
+
RoleDetailRoute,
|
|
13
|
+
userRoutes,
|
|
14
|
+
UserShow,
|
|
15
|
+
} from "./pages/users";
|
|
9
16
|
import { dataProvider } from "./providers/data";
|
|
10
17
|
import { Login } from "./pages/login";
|
|
11
18
|
import { Register } from "./pages/register";
|
|
@@ -20,7 +27,6 @@ import { TooltipProvider } from "./components/ui/tooltip";
|
|
|
20
27
|
import { BrandLogo } from "./components/app-shell/brand";
|
|
21
28
|
import {
|
|
22
29
|
AppExtensionProviders,
|
|
23
|
-
extensionI18nProvider,
|
|
24
30
|
extensionResources,
|
|
25
31
|
extensionRouteElements,
|
|
26
32
|
} from "./app/extensions";
|
|
@@ -31,14 +37,16 @@ import { AclBootstrap } from "./components/access-control/acl-bootstrap";
|
|
|
31
37
|
import { ResourceAccessGuard } from "./components/access-control/resource-access-guard";
|
|
32
38
|
import { NavigateToAccessibleResource } from "./components/access-control/navigate-to-accessible-resource";
|
|
33
39
|
import { UsersRound } from "lucide-react";
|
|
40
|
+
import { i18nProvider } from "./providers/i18n";
|
|
41
|
+
import { SystemSettingsProvider } from "./providers/system-settings";
|
|
34
42
|
|
|
35
43
|
const coreResources: ResourceProps[] = [
|
|
36
44
|
{
|
|
37
45
|
name: "users",
|
|
38
|
-
list:
|
|
39
|
-
create:
|
|
40
|
-
edit:
|
|
41
|
-
show:
|
|
46
|
+
list: userRoutes.list,
|
|
47
|
+
create: userRoutes.create,
|
|
48
|
+
edit: userRoutes.edit,
|
|
49
|
+
show: userRoutes.show,
|
|
42
50
|
meta: {
|
|
43
51
|
label: "Users",
|
|
44
52
|
singularLabel: "User",
|
|
@@ -65,24 +73,25 @@ function App() {
|
|
|
65
73
|
<BrowserRouter basename={basename || undefined}>
|
|
66
74
|
<ThemeProvider>
|
|
67
75
|
<TooltipProvider>
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
<SystemSettingsProvider>
|
|
77
|
+
<Refine
|
|
78
|
+
dataProvider={dataProvider}
|
|
79
|
+
notificationProvider={useNotificationProvider()}
|
|
80
|
+
routerProvider={routerProvider}
|
|
81
|
+
authProvider={authProvider}
|
|
82
|
+
accessControlProvider={accessControlProvider}
|
|
83
|
+
i18nProvider={i18nProvider}
|
|
84
|
+
resources={[...coreResources, ...extensionResources]}
|
|
85
|
+
options={{
|
|
86
|
+
syncWithLocation: true,
|
|
87
|
+
warnWhenUnsavedChanges: true,
|
|
88
|
+
disableTelemetry: true,
|
|
89
|
+
title: {
|
|
90
|
+
text: "NocoBase",
|
|
91
|
+
icon: <BrandLogo className="size-14 rounded-2xl" />,
|
|
92
|
+
},
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
86
95
|
<Routes>
|
|
87
96
|
<Route
|
|
88
97
|
element={
|
|
@@ -101,15 +110,10 @@ function App() {
|
|
|
101
110
|
}
|
|
102
111
|
>
|
|
103
112
|
<Route index element={<NavigateToAccessibleResource />} />
|
|
104
|
-
<Route
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<ResourceAccessGuard resource="users" action="list">
|
|
109
|
-
<UserList />
|
|
110
|
-
</ResourceAccessGuard>
|
|
111
|
-
}
|
|
112
|
-
/>
|
|
113
|
+
<Route
|
|
114
|
+
path="/users"
|
|
115
|
+
element={<UserResourceLayout />}
|
|
116
|
+
>
|
|
113
117
|
<Route
|
|
114
118
|
path="create"
|
|
115
119
|
element={
|
|
@@ -126,6 +130,14 @@ function App() {
|
|
|
126
130
|
</ResourceAccessGuard>
|
|
127
131
|
}
|
|
128
132
|
/>
|
|
133
|
+
<Route
|
|
134
|
+
path="roles/:roleName"
|
|
135
|
+
element={
|
|
136
|
+
<ResourceAccessGuard resource="roles" action="show">
|
|
137
|
+
<RoleDetailRoute returnTo="list" />
|
|
138
|
+
</ResourceAccessGuard>
|
|
139
|
+
}
|
|
140
|
+
/>
|
|
129
141
|
<Route
|
|
130
142
|
path="show/:id"
|
|
131
143
|
element={
|
|
@@ -133,7 +145,24 @@ function App() {
|
|
|
133
145
|
<UserShow />
|
|
134
146
|
</ResourceAccessGuard>
|
|
135
147
|
}
|
|
136
|
-
|
|
148
|
+
>
|
|
149
|
+
<Route
|
|
150
|
+
path="edit"
|
|
151
|
+
element={
|
|
152
|
+
<ResourceAccessGuard resource="users" action="edit">
|
|
153
|
+
<UserEdit returnTo="show" />
|
|
154
|
+
</ResourceAccessGuard>
|
|
155
|
+
}
|
|
156
|
+
/>
|
|
157
|
+
<Route
|
|
158
|
+
path="roles/:roleName"
|
|
159
|
+
element={
|
|
160
|
+
<ResourceAccessGuard resource="roles" action="show">
|
|
161
|
+
<RoleDetailRoute returnTo="show" />
|
|
162
|
+
</ResourceAccessGuard>
|
|
163
|
+
}
|
|
164
|
+
/>
|
|
165
|
+
</Route>
|
|
137
166
|
</Route>
|
|
138
167
|
{extensionRouteElements}
|
|
139
168
|
<Route path="*" element={<ErrorComponent />} />
|
|
@@ -159,7 +188,8 @@ function App() {
|
|
|
159
188
|
<Toaster />
|
|
160
189
|
<UnsavedChangesNotifier />
|
|
161
190
|
<DocumentTitleHandler appName="NocoBase" />
|
|
162
|
-
|
|
191
|
+
</Refine>
|
|
192
|
+
</SystemSettingsProvider>
|
|
163
193
|
</TooltipProvider>
|
|
164
194
|
</ThemeProvider>
|
|
165
195
|
</BrowserRouter>
|
package/src/app/extension.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResourceProps } from "@refinedev/core";
|
|
2
2
|
import type { ComponentType, PropsWithChildren, ReactElement } from "react";
|
|
3
3
|
|
|
4
4
|
export type AppExtension = {
|
|
@@ -7,5 +7,4 @@ export type AppExtension = {
|
|
|
7
7
|
routes?: ReactElement;
|
|
8
8
|
Provider?: ComponentType<PropsWithChildren>;
|
|
9
9
|
UserMenuItems?: ComponentType;
|
|
10
|
-
i18nProvider?: I18nProvider;
|
|
11
10
|
};
|
package/src/app/extensions.tsx
CHANGED
|
@@ -18,20 +18,6 @@ export const extensionRouteElements = appExtensions
|
|
|
18
18
|
.map((extension) => extension.routes)
|
|
19
19
|
.filter((routes): routes is NonNullable<typeof routes> => Boolean(routes));
|
|
20
20
|
|
|
21
|
-
const i18nExtensions = appExtensions.filter(
|
|
22
|
-
(extension) => extension.i18nProvider
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
if (i18nExtensions.length > 1) {
|
|
26
|
-
console.warn(
|
|
27
|
-
`Multiple app extensions provide i18nProvider (${i18nExtensions
|
|
28
|
-
.map((extension) => extension.id)
|
|
29
|
-
.join(", ")}). Using ${i18nExtensions[0].id}.`
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const extensionI18nProvider = i18nExtensions[0]?.i18nProvider;
|
|
34
|
-
|
|
35
21
|
export const extensionUserMenuItems = appExtensions
|
|
36
22
|
.filter((extension) => extension.UserMenuItems)
|
|
37
23
|
.map((extension) => ({
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
import { Fragment, useMemo } from "react";
|
|
4
4
|
import { Home } from "lucide-react";
|
|
5
5
|
import {
|
|
6
|
+
type IResourceItem,
|
|
6
7
|
matchResourceFromRoute,
|
|
7
8
|
useBreadcrumb,
|
|
9
|
+
useGetToPath,
|
|
8
10
|
useLink,
|
|
11
|
+
useParsed,
|
|
9
12
|
useResourceParams,
|
|
10
13
|
useTranslate,
|
|
11
14
|
useUserFriendlyName,
|
|
@@ -24,9 +27,42 @@ export function Breadcrumb() {
|
|
|
24
27
|
const Link = useLink();
|
|
25
28
|
const { breadcrumbs } = useBreadcrumb();
|
|
26
29
|
const { resources } = useResourceParams();
|
|
30
|
+
const { pathname } = useParsed();
|
|
31
|
+
const getToPath = useGetToPath();
|
|
27
32
|
const translate = useTranslate();
|
|
28
33
|
const getUserFriendlyName = useUserFriendlyName();
|
|
29
34
|
const rootRouteResource = matchResourceFromRoute("/", resources);
|
|
35
|
+
const resolvedBreadcrumbs = useMemo(() => {
|
|
36
|
+
if (breadcrumbs.length > 0) return breadcrumbs;
|
|
37
|
+
|
|
38
|
+
const closestMatch = findClosestRouteMatch(pathname, resources);
|
|
39
|
+
if (!closestMatch.resource) return breadcrumbs;
|
|
40
|
+
|
|
41
|
+
const items: Array<{ label: string; href?: string }> = [
|
|
42
|
+
{
|
|
43
|
+
label:
|
|
44
|
+
closestMatch.resource.meta?.label ?? closestMatch.resource.name,
|
|
45
|
+
href: getToPath({ resource: closestMatch.resource, action: "list" }),
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
const nestedAction = getNestedRouteAction(
|
|
49
|
+
pathname,
|
|
50
|
+
closestMatch.matchedSegmentCount
|
|
51
|
+
) ??
|
|
52
|
+
(closestMatch.action && closestMatch.action !== "list"
|
|
53
|
+
? closestMatch.action
|
|
54
|
+
: undefined);
|
|
55
|
+
if (nestedAction) {
|
|
56
|
+
items.push({
|
|
57
|
+
label: translate(
|
|
58
|
+
`buttons.${nestedAction}`,
|
|
59
|
+
nestedAction.charAt(0).toUpperCase() + nestedAction.slice(1)
|
|
60
|
+
),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return items;
|
|
65
|
+
}, [breadcrumbs, getToPath, pathname, resources, translate]);
|
|
30
66
|
|
|
31
67
|
const breadCrumbItems = useMemo(() => {
|
|
32
68
|
const list: {
|
|
@@ -47,7 +83,7 @@ export function Breadcrumb() {
|
|
|
47
83
|
),
|
|
48
84
|
});
|
|
49
85
|
|
|
50
|
-
for (const { label, href } of
|
|
86
|
+
for (const { label, href } of resolvedBreadcrumbs) {
|
|
51
87
|
const matchingResource = resources.find((resource) => {
|
|
52
88
|
const metaLabel = resource.meta?.label;
|
|
53
89
|
return (
|
|
@@ -80,9 +116,9 @@ export function Breadcrumb() {
|
|
|
80
116
|
|
|
81
117
|
return list;
|
|
82
118
|
}, [
|
|
83
|
-
breadcrumbs,
|
|
84
119
|
getUserFriendlyName,
|
|
85
120
|
Link,
|
|
121
|
+
resolvedBreadcrumbs,
|
|
86
122
|
resources,
|
|
87
123
|
rootRouteResource,
|
|
88
124
|
translate,
|
|
@@ -114,4 +150,33 @@ export function Breadcrumb() {
|
|
|
114
150
|
);
|
|
115
151
|
}
|
|
116
152
|
|
|
153
|
+
function findClosestRouteMatch(
|
|
154
|
+
pathname: string | undefined,
|
|
155
|
+
resources: IResourceItem[]
|
|
156
|
+
) {
|
|
157
|
+
const segments = pathname?.split("/").filter(Boolean) ?? [];
|
|
158
|
+
|
|
159
|
+
for (let length = segments.length; length >= 0; length -= 1) {
|
|
160
|
+
const route = `/${segments.slice(0, length).join("/")}`;
|
|
161
|
+
const match = matchResourceFromRoute(route, resources);
|
|
162
|
+
if (match.resource) {
|
|
163
|
+
return { ...match, matchedSegmentCount: length };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { resource: undefined, matchedSegmentCount: 0 };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getNestedRouteAction(
|
|
171
|
+
pathname: string | undefined,
|
|
172
|
+
matchedSegmentCount: number
|
|
173
|
+
) {
|
|
174
|
+
const segments = pathname?.split("/").filter(Boolean) ?? [];
|
|
175
|
+
const action = segments[matchedSegmentCount];
|
|
176
|
+
|
|
177
|
+
return action && ["create", "edit", "show", "clone"].includes(action)
|
|
178
|
+
? action
|
|
179
|
+
: undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
117
182
|
Breadcrumb.displayName = "Breadcrumb";
|
|
@@ -218,7 +218,7 @@ function SidebarDropdownItem({
|
|
|
218
218
|
selectedKey,
|
|
219
219
|
Link,
|
|
220
220
|
}: MenuItemProps & { Link: ReturnType<typeof useLink> }) {
|
|
221
|
-
const isSelected = item
|
|
221
|
+
const isSelected = isTreeItemSelected(item, selectedKey);
|
|
222
222
|
const displayName = useMenuItemLabel(item);
|
|
223
223
|
|
|
224
224
|
return (
|
|
@@ -239,7 +239,7 @@ function SidebarDropdownItem({
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
function SidebarItemLink({ item, selectedKey }: MenuItemProps) {
|
|
242
|
-
const isSelected = item
|
|
242
|
+
const isSelected = isTreeItemSelected(item, selectedKey);
|
|
243
243
|
|
|
244
244
|
return <SidebarButton item={item} isSelected={isSelected} asLink={true} />;
|
|
245
245
|
}
|
|
@@ -11,12 +11,13 @@ import { cn } from "@/lib/utils";
|
|
|
11
11
|
|
|
12
12
|
type ListViewProps = PropsWithChildren<{
|
|
13
13
|
className?: string;
|
|
14
|
+
resource?: string;
|
|
14
15
|
}>;
|
|
15
16
|
|
|
16
|
-
export function ListView({ children, className }: ListViewProps) {
|
|
17
|
+
export function ListView({ children, className, resource }: ListViewProps) {
|
|
17
18
|
return (
|
|
18
19
|
<div className={cn("flex flex-col", "gap-6", className)}>
|
|
19
|
-
<ListViewHeader />
|
|
20
|
+
<ListViewHeader resource={resource} />
|
|
20
21
|
{children}
|
|
21
22
|
</div>
|
|
22
23
|
);
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
|
|
3
3
|
|
|
4
|
-
import { cn } from "@/lib/utils"
|
|
5
|
-
import { Button } from "@/components/ui/button"
|
|
6
|
-
import { XIcon } from "lucide-react"
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { XIcon } from "lucide-react";
|
|
7
7
|
|
|
8
8
|
function Sheet({ ...props }: SheetPrimitive.Root.Props) {
|
|
9
|
-
return <SheetPrimitive.Root data-slot="sheet" {...props}
|
|
9
|
+
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
|
|
13
|
-
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props}
|
|
13
|
+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
|
|
17
|
-
return <SheetPrimitive.Close data-slot="sheet-close" {...props}
|
|
17
|
+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
|
|
21
|
-
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props}
|
|
21
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
|
|
@@ -31,7 +31,7 @@ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
|
|
|
31
31
|
)}
|
|
32
32
|
{...props}
|
|
33
33
|
/>
|
|
34
|
-
)
|
|
34
|
+
);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function SheetContent({
|
|
@@ -39,10 +39,12 @@ function SheetContent({
|
|
|
39
39
|
children,
|
|
40
40
|
side = "right",
|
|
41
41
|
showCloseButton = true,
|
|
42
|
+
closeLabel = "Close",
|
|
42
43
|
...props
|
|
43
44
|
}: SheetPrimitive.Popup.Props & {
|
|
44
|
-
side?: "top" | "right" | "bottom" | "left"
|
|
45
|
-
showCloseButton?: boolean
|
|
45
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
46
|
+
showCloseButton?: boolean;
|
|
47
|
+
closeLabel?: string;
|
|
46
48
|
}) {
|
|
47
49
|
return (
|
|
48
50
|
<SheetPortal>
|
|
@@ -68,14 +70,13 @@ function SheetContent({
|
|
|
68
70
|
/>
|
|
69
71
|
}
|
|
70
72
|
>
|
|
71
|
-
<XIcon
|
|
72
|
-
|
|
73
|
-
<span className="sr-only">Close</span>
|
|
73
|
+
<XIcon />
|
|
74
|
+
<span className="sr-only">{closeLabel}</span>
|
|
74
75
|
</SheetPrimitive.Close>
|
|
75
76
|
)}
|
|
76
77
|
</SheetPrimitive.Popup>
|
|
77
78
|
</SheetPortal>
|
|
78
|
-
)
|
|
79
|
+
);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
@@ -85,7 +86,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
85
86
|
className={cn("flex flex-col gap-0.5 p-4", className)}
|
|
86
87
|
{...props}
|
|
87
88
|
/>
|
|
88
|
-
)
|
|
89
|
+
);
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
@@ -95,7 +96,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
95
96
|
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
96
97
|
{...props}
|
|
97
98
|
/>
|
|
98
|
-
)
|
|
99
|
+
);
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
|
|
@@ -108,7 +109,7 @@ function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
|
|
|
108
109
|
)}
|
|
109
110
|
{...props}
|
|
110
111
|
/>
|
|
111
|
-
)
|
|
112
|
+
);
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
function SheetDescription({
|
|
@@ -121,7 +122,7 @@ function SheetDescription({
|
|
|
121
122
|
className={cn("text-sm text-muted-foreground", className)}
|
|
122
123
|
{...props}
|
|
123
124
|
/>
|
|
124
|
-
)
|
|
125
|
+
);
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
export {
|
|
@@ -133,4 +134,4 @@ export {
|
|
|
133
134
|
SheetFooter,
|
|
134
135
|
SheetTitle,
|
|
135
136
|
SheetDescription,
|
|
136
|
-
}
|
|
137
|
+
};
|
|
@@ -58,6 +58,7 @@ const unwrapPayload = (
|
|
|
58
58
|
|
|
59
59
|
export class NocoBaseClient {
|
|
60
60
|
private readonly apiOrigin?: string;
|
|
61
|
+
private runtimeLocale?: string;
|
|
61
62
|
|
|
62
63
|
constructor(
|
|
63
64
|
private readonly apiUrl = API_URL,
|
|
@@ -117,7 +118,7 @@ export class NocoBaseClient {
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
getLocale() {
|
|
120
|
-
return this.getStoredLocale() ?? getBrowserLocale();
|
|
121
|
+
return this.getStoredLocale() ?? this.runtimeLocale ?? getBrowserLocale();
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
setLocale(locale?: string | null) {
|
|
@@ -126,6 +127,10 @@ export class NocoBaseClient {
|
|
|
126
127
|
else localStorage.removeItem(NOCOBASE_LOCALE_KEY);
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
setRuntimeLocale(locale?: string | null) {
|
|
131
|
+
this.runtimeLocale = locale || undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
129
134
|
buildUrl(endpoint: string, query?: Record<string, QueryValue>) {
|
|
130
135
|
const base = `${this.apiUrl.replace(/\/$/, "")}/${endpoint.replace(
|
|
131
136
|
/^\//,
|
package/src/locales/en-US.ts
CHANGED
|
@@ -1,11 +1,63 @@
|
|
|
1
1
|
export const starter = {
|
|
2
|
+
"shell.workspace": "AI application workspace",
|
|
3
|
+
"shell.footer.freedom": "AI builds freely.",
|
|
4
|
+
"shell.footer.reliability": "NocoBase keeps it reliable.",
|
|
5
|
+
"auth.signOut": "Sign out",
|
|
6
|
+
"auth.signingOut": "Signing out...",
|
|
7
|
+
"buttons.apply": "Apply",
|
|
8
|
+
"buttons.clear": "Clear",
|
|
9
|
+
"buttons.undo": "Undo",
|
|
10
|
+
"pages.error.title": "Page not found.",
|
|
11
|
+
"pages.error.description": "The page you're looking for does not exist.",
|
|
12
|
+
"pages.error.backHome": "Back to homepage",
|
|
13
|
+
"table.filter.text.placeholder": "Filter by...",
|
|
14
|
+
"table.filter.numeric.placeholder": "Filter by...",
|
|
15
|
+
"table.filter.combobox.placeholder": "Select...",
|
|
16
|
+
"table.filter.combobox.search": "Search...",
|
|
17
|
+
"table.filter.combobox.noResults": "Results not found.",
|
|
18
|
+
"table.filter.operator.placeholder": "Search operator...",
|
|
19
|
+
"table.filter.operator.noResults": "No operator found.",
|
|
20
|
+
"table.filter.operator.eq": "Equals",
|
|
21
|
+
"table.filter.operator.ne": "Not equals",
|
|
22
|
+
"table.filter.operator.lt": "Less than",
|
|
23
|
+
"table.filter.operator.gt": "Greater than",
|
|
24
|
+
"table.filter.operator.lte": "Less than or equal",
|
|
25
|
+
"table.filter.operator.gte": "Greater than or equal",
|
|
26
|
+
"table.filter.operator.in": "Includes in an array",
|
|
27
|
+
"table.filter.operator.nin": "Not includes in an array",
|
|
28
|
+
"table.filter.operator.ina": "Includes in an array (case sensitive)",
|
|
29
|
+
"table.filter.operator.nina": "Not includes in an array (case sensitive)",
|
|
30
|
+
"table.filter.operator.contains": "Contains",
|
|
31
|
+
"table.filter.operator.ncontains": "Not contains",
|
|
32
|
+
"table.filter.operator.containss": "Contains (case sensitive)",
|
|
33
|
+
"table.filter.operator.ncontainss": "Not contains (case sensitive)",
|
|
34
|
+
"table.filter.operator.between": "Between",
|
|
35
|
+
"table.filter.operator.nbetween": "Not between",
|
|
36
|
+
"table.filter.operator.null": "Is null",
|
|
37
|
+
"table.filter.operator.nnull": "Is not null",
|
|
38
|
+
"table.filter.operator.startswith": "Starts with",
|
|
39
|
+
"table.filter.operator.nstartswith": "Not starts with",
|
|
40
|
+
"table.filter.operator.startswiths": "Starts with (case sensitive)",
|
|
41
|
+
"table.filter.operator.nstartswiths": "Not starts with (case sensitive)",
|
|
42
|
+
"table.filter.operator.endswith": "Ends with",
|
|
43
|
+
"table.filter.operator.nendswith": "Not ends with",
|
|
44
|
+
"table.filter.operator.endswiths": "Ends with (case sensitive)",
|
|
45
|
+
"table.filter.operator.nendswiths": "Not ends with (case sensitive)",
|
|
46
|
+
"table.filter.operator.eqs": "Equals (case sensitive)",
|
|
47
|
+
"table.filter.operator.nes": "Not equals (case sensitive)",
|
|
2
48
|
"buttons.create": "Create",
|
|
3
49
|
"buttons.edit": "Edit",
|
|
4
50
|
"buttons.show": "View",
|
|
5
51
|
"buttons.delete": "Delete",
|
|
6
52
|
"buttons.refresh": "Refresh",
|
|
7
53
|
"buttons.cancel": "Cancel",
|
|
54
|
+
"buttons.close": "Close",
|
|
8
55
|
"buttons.confirm": "Are you sure?",
|
|
56
|
+
"unsavedChanges.title": "Discard unsaved changes?",
|
|
57
|
+
"unsavedChanges.description":
|
|
58
|
+
"Your changes have not been saved. Leaving now will discard them.",
|
|
59
|
+
"unsavedChanges.stay": "Keep editing",
|
|
60
|
+
"unsavedChanges.leave": "Discard changes",
|
|
9
61
|
"views.create.title": "Create {{resource}}",
|
|
10
62
|
"views.create.description":
|
|
11
63
|
"Add a new {{resource}} to your NocoBase workspace.",
|
|
@@ -52,6 +104,14 @@ export const app = {
|
|
|
52
104
|
"users.actions.view": "View user",
|
|
53
105
|
"users.actions.edit": "Edit user",
|
|
54
106
|
"users.actions.delete": "Delete user",
|
|
107
|
+
"users.drawer.create.title": "Create user",
|
|
108
|
+
"users.drawer.create.description":
|
|
109
|
+
"Add a user who can sign in to this NocoBase application.",
|
|
110
|
+
"users.drawer.edit.title": "Edit user",
|
|
111
|
+
"users.drawer.edit.description":
|
|
112
|
+
"Update this user's identity and contact information.",
|
|
113
|
+
"users.drawer.show.description":
|
|
114
|
+
"Review this user's identity, contact information, and roles.",
|
|
55
115
|
"users.form.create.submit": "Create user",
|
|
56
116
|
"users.form.create.submitting": "Creating...",
|
|
57
117
|
"users.form.edit.submit": "Save changes",
|
|
@@ -75,8 +135,29 @@ export const app = {
|
|
|
75
135
|
"users.detail.timestamps": "Timestamps",
|
|
76
136
|
"users.detail.noRoles": "No assigned roles",
|
|
77
137
|
"users.detail.unnamed": "Unnamed user",
|
|
138
|
+
"users.detail.loadError.title": "Unable to load user",
|
|
139
|
+
"users.detail.loadError.description":
|
|
140
|
+
"The user may no longer exist, or you may not have permission to view it.",
|
|
78
141
|
"users.ai.table": "Users table",
|
|
79
142
|
"users.ai.detail": "User details",
|
|
80
143
|
"users.ai.createForm": "Create user form",
|
|
81
144
|
"users.ai.editForm": "Edit user form",
|
|
145
|
+
"common.yes": "Yes",
|
|
146
|
+
"common.no": "No",
|
|
147
|
+
"roles.drawer.description": "Review the role associated with this user.",
|
|
148
|
+
"roles.fields.name": "Name",
|
|
149
|
+
"roles.fields.title": "Title",
|
|
150
|
+
"roles.fields.description": "Description",
|
|
151
|
+
"roles.fields.default": "Default role",
|
|
152
|
+
"roles.fields.hidden": "Hidden",
|
|
153
|
+
"roles.fields.allowConfigure": "Can configure",
|
|
154
|
+
"roles.fields.createdAt": "Created at",
|
|
155
|
+
"roles.fields.updatedAt": "Updated at",
|
|
156
|
+
"roles.detail.identity": "Role identity",
|
|
157
|
+
"roles.detail.settings": "Settings",
|
|
158
|
+
"roles.detail.actions": "Allowed actions",
|
|
159
|
+
"roles.detail.timestamps": "Timestamps",
|
|
160
|
+
"roles.detail.unavailable":
|
|
161
|
+
"Additional role details are unavailable. Showing the relation data included with the user.",
|
|
162
|
+
"roles.ai.detail": "Role details",
|
|
82
163
|
} as const;
|