muffin-admin 1.36.3 → 1.36.5

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.
Files changed (47) hide show
  1. package/dist/{dashboard.js → cjs/dashboard.js} +2 -3
  2. package/package.json +8 -12
  3. package/src/admin.jsx +78 -0
  4. package/src/authprovider.js +85 -0
  5. package/src/buttons/ActionButton.jsx +99 -0
  6. package/src/buttons/LinkButton.jsx +20 -0
  7. package/src/dashboard.jsx +74 -0
  8. package/src/dataprovider.js +94 -0
  9. package/src/fields/AvatarField.jsx +33 -0
  10. package/src/fields/FKField.jsx +27 -0
  11. package/src/fields/JsonField.jsx +26 -0
  12. package/src/hooks/useAction.js +9 -0
  13. package/src/index.js +14 -0
  14. package/src/inputs/FKInput.jsx +41 -0
  15. package/src/inputs/JsonInput.jsx +62 -0
  16. package/src/resources.jsx +36 -0
  17. package/src/ui.jsx +107 -0
  18. package/src/utils.js +38 -0
  19. package/src/views/create.jsx +30 -0
  20. package/src/views/edit.jsx +41 -0
  21. package/src/views/list.jsx +86 -0
  22. package/src/views/show.jsx +42 -0
  23. package/src/web.js +3 -0
  24. package/.eslintrc.json +0 -44
  25. package/Makefile +0 -18
  26. package/jsconfig.json +0 -9
  27. package/webpack.config.js +0 -45
  28. /package/dist/{admin.js → cjs/admin.js} +0 -0
  29. /package/dist/{authprovider.js → cjs/authprovider.js} +0 -0
  30. /package/dist/{buttons → cjs/buttons}/ActionButton.js +0 -0
  31. /package/dist/{buttons → cjs/buttons}/LinkButton.js +0 -0
  32. /package/dist/{dataprovider.js → cjs/dataprovider.js} +0 -0
  33. /package/dist/{fields → cjs/fields}/AvatarField.js +0 -0
  34. /package/dist/{fields → cjs/fields}/FKField.js +0 -0
  35. /package/dist/{fields → cjs/fields}/JsonField.js +0 -0
  36. /package/dist/{hooks → cjs/hooks}/useAction.js +0 -0
  37. /package/dist/{index.js → cjs/index.js} +0 -0
  38. /package/dist/{inputs → cjs/inputs}/FKInput.js +0 -0
  39. /package/dist/{inputs → cjs/inputs}/JsonInput.js +0 -0
  40. /package/dist/{resources.js → cjs/resources.js} +0 -0
  41. /package/dist/{ui.js → cjs/ui.js} +0 -0
  42. /package/dist/{utils.js → cjs/utils.js} +0 -0
  43. /package/dist/{views → cjs/views}/create.js +0 -0
  44. /package/dist/{views → cjs/views}/edit.js +0 -0
  45. /package/dist/{views → cjs/views}/list.js +0 -0
  46. /package/dist/{views → cjs/views}/show.js +0 -0
  47. /package/dist/{web.js → cjs/web.js} +0 -0
@@ -4,8 +4,6 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  var _react = _interopRequireDefault(require("react"));
5
5
  var _material = require("@mui/material");
6
6
  var _utils = require("./utils");
7
- // import ReactJSON from "react-json-view";
8
-
9
7
  var AdminTableView = function AdminTableView(_ref) {
10
8
  var src = _ref.src;
11
9
  return /*#__PURE__*/_react["default"].createElement(_material.Table, null, /*#__PURE__*/_react["default"].createElement(_material.TableBody, null, src.map(function (row, idx) {
@@ -53,7 +51,7 @@ var AdminCard = function AdminCard(_ref2) {
53
51
  // Process dashboard
54
52
  (0, _utils.setupAdmin)("dashboard", function (props) {
55
53
  if (!props) return;
56
- return function () {
54
+ var Dashboard = function Dashboard() {
57
55
  return /*#__PURE__*/_react["default"].createElement(_material.Grid, {
58
56
  container: true,
59
57
  spacing: 1
@@ -61,4 +59,5 @@ var AdminCard = function AdminCard(_ref2) {
61
59
  src: props
62
60
  }));
63
61
  };
62
+ return Dashboard;
64
63
  });
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "muffin-admin",
3
- "version": "1.36.3",
3
+ "version": "1.36.5",
4
4
  "description": "NPM package for https://github.com/klen/muffin-admin",
5
- "main": "dist/index.js",
5
+ "files": [
6
+ "src",
7
+ "dist"
8
+ ],
9
+ "main": "dist/cjs/index.js",
10
+ "module": "dist/esm/index.js",
6
11
  "scripts": {
7
12
  "test": "echo \"Error: no test specified\" && exit 1"
8
13
  },
@@ -19,7 +24,7 @@
19
24
  "dependencies": {
20
25
  "js-cookie": "^3.0.1",
21
26
  "react": "^18.2.0",
22
- "react-admin": "^4.4.3",
27
+ "react-admin": "^4.5.4",
23
28
  "react-dom": "^18.2.0",
24
29
  "validator": "^13.7.0"
25
30
  },
@@ -35,14 +40,5 @@
35
40
  "webpack": "^5.24.4",
36
41
  "webpack-cli": "^5.0.0",
37
42
  "webpack-dev-server": "^4.3.1"
38
- },
39
- "babel": {
40
- "presets": [
41
- "@babel/preset-env",
42
- "@babel/preset-react"
43
- ],
44
- "plugins": [
45
- "@babel/plugin-transform-runtime"
46
- ]
47
43
  }
48
44
  }
package/src/admin.jsx ADDED
@@ -0,0 +1,78 @@
1
+ import React from "react"
2
+
3
+ import {
4
+ Admin,
5
+ AppBar,
6
+ Layout,
7
+ Login,
8
+ ToggleThemeButton,
9
+ defaultTheme,
10
+ } from "react-admin"
11
+ import {
12
+ IconButton,
13
+ SvgIcon,
14
+ Tooltip,
15
+ Typography,
16
+ createTheme,
17
+ } from "@mui/material"
18
+ import * as icons from "@mui/icons-material"
19
+
20
+ import authProvider from "./authprovider"
21
+ import dataProvider from "./dataprovider"
22
+ import { processAdmin, setupAdmin } from "./utils"
23
+
24
+ import "./dashboard"
25
+ import "./resources"
26
+
27
+ const darkTheme = createTheme({
28
+ palette: { mode: "dark" },
29
+ })
30
+
31
+ // Initialize the admin
32
+ setupAdmin("admin", (props) => {
33
+ const { apiUrl, auth, adminProps, appBarLinks, dashboard, resources } = props
34
+
35
+ let appBar = (props) => (
36
+ <AppBar {...props}>
37
+ <Typography
38
+ variant="h6"
39
+ color="inherit"
40
+ id="react-admin-title"
41
+ style={{
42
+ flex: 1,
43
+ textOverflow: "ellipsis",
44
+ whiteSpace: "nowrap",
45
+ overflow: "hidden",
46
+ }}
47
+ />
48
+ <ToggleThemeButton lightTheme={defaultTheme} darkTheme={darkTheme} />
49
+ {appBarLinks.map((info) => (
50
+ <Tooltip key={info.url} title={info.title}>
51
+ <IconButton color="inherit" href={info.url}>
52
+ <SvgIcon component={icons[info.icon]} />
53
+ </IconButton>
54
+ </Tooltip>
55
+ ))}
56
+ </AppBar>
57
+ )
58
+
59
+ return (
60
+ <Admin
61
+ authProvider={processAdmin("auth", auth)}
62
+ dataProvider={processAdmin("data", apiUrl)}
63
+ dashboard={processAdmin("dashboard", dashboard)}
64
+ loginPage={processAdmin("login", auth)}
65
+ layout={(props) => <Layout appBar={appBar} {...props} />}
66
+ {...adminProps}
67
+ >
68
+ {processAdmin("resources", resources)}
69
+ </Admin>
70
+ )
71
+ })
72
+
73
+ // Initialize authentication and data providers
74
+ setupAdmin("auth", authProvider)
75
+ setupAdmin("data", dataProvider)
76
+
77
+ // Initialize login page
78
+ setupAdmin("login", () => Login)
@@ -0,0 +1,85 @@
1
+ import Cookies from "js-cookie"
2
+
3
+ import { makeRequest, processAdmin, requestHeaders, setupAdmin } from "./utils"
4
+
5
+ setupAdmin(
6
+ "auth-get",
7
+ ({ storage }) =>
8
+ (name) =>
9
+ storage == "localstorage" ? localStorage.getItem(name) : Cookies.get(name)
10
+ )
11
+
12
+ setupAdmin("auth-set", ({ storage }) => (name, value) => {
13
+ if (storage == "localstorage") {
14
+ localStorage.setItem(name, value)
15
+ requestHeaders["Authorization"] = value
16
+ } else Cookies.set(name, value)
17
+ })
18
+
19
+ export default (props) => {
20
+ const { identityURL, authorizeURL, logoutURL, required, storage_name } = props
21
+
22
+ const authGet = processAdmin("auth-get", props)
23
+ const authSet = processAdmin("auth-set", props)
24
+
25
+ // Initialize request headers
26
+ authSet(storage_name, authGet(storage_name))
27
+
28
+ let getIdentity = async () => {
29
+ if (identityURL) {
30
+ let { json } = await makeRequest(identityURL)
31
+ return json
32
+ }
33
+ }
34
+
35
+ if (required)
36
+ return {
37
+ login: async (data) => {
38
+ if (!authorizeURL) throw { message: "Authorization is not supported" }
39
+
40
+ let { json } = await makeRequest(authorizeURL, { data, method: "POST" })
41
+ authSet(storage_name, json)
42
+ },
43
+
44
+ checkError: async (error) => {
45
+ const { status } = error
46
+
47
+ if (status == 401 || status == 403) {
48
+ // Clean storage
49
+ authSet(storage_name, "")
50
+
51
+ throw {
52
+ message: "Invalid authorization",
53
+ redirectTo: logoutURL,
54
+ logoutUser: !logoutURL,
55
+ }
56
+ }
57
+ },
58
+
59
+ checkAuth: async () => {
60
+ const auth = authGet(storage_name)
61
+ if (!auth) throw { message: "Authorization required" }
62
+
63
+ if (!identityURL) return auth
64
+
65
+ let user = await getIdentity()
66
+ if (!user) throw { message: "Authorization required" }
67
+
68
+ return user
69
+ },
70
+
71
+ logout: async () => {
72
+ // Clean storage
73
+ authSet(storage_name, "")
74
+
75
+ if (logoutURL) window.location = logoutURL
76
+ },
77
+
78
+ getIdentity,
79
+
80
+ getPermissions: () => {
81
+ const role = authGet(storage_name + "_role")
82
+ return role ? Promise.resolve(role) : Promise.reject()
83
+ },
84
+ }
85
+ }
@@ -0,0 +1,99 @@
1
+ import React from "react"
2
+
3
+ import {
4
+ Button,
5
+ useRefresh,
6
+ useUnselectAll,
7
+ useNotify,
8
+ useRecordContext,
9
+ } from "react-admin"
10
+ import * as icons from "@mui/icons-material"
11
+
12
+ import useAction from "../hooks/useAction"
13
+
14
+ export const BulkActionButton = ({
15
+ label,
16
+ icon,
17
+ title,
18
+ action,
19
+ resource,
20
+ selectedIds,
21
+ }) => {
22
+ const refresh = useRefresh(),
23
+ unselectAll = useUnselectAll(resource),
24
+ notify = useNotify(),
25
+ Icon = icons[icon]
26
+
27
+ const mutation = useAction(resource, action)
28
+
29
+ let onClick = () => {
30
+ mutation.mutate(
31
+ {
32
+ action,
33
+ ids: selectedIds,
34
+ },
35
+ {
36
+ onSuccess: ({ data }) => {
37
+ if (data && data.message) notify(data.message, { type: "success" })
38
+ refresh()
39
+ unselectAll()
40
+ },
41
+ onError: (err) => {
42
+ notify(typeof err === "string" ? err : err.message, { type: "error" })
43
+ },
44
+ }
45
+ )
46
+ }
47
+
48
+ return (
49
+ <Button
50
+ label={label}
51
+ title={title}
52
+ onClick={onClick}
53
+ disabled={mutation.isLoading}
54
+ >
55
+ {Icon && <Icon />}
56
+ </Button>
57
+ )
58
+ }
59
+
60
+ export const ActionButton = (props) => {
61
+ const { icon, label, title, resource, action } = props
62
+ const record = useRecordContext()
63
+ const mutation = useAction(resource, action)
64
+
65
+ const refresh = useRefresh(),
66
+ notify = useNotify(),
67
+ Icon = icons[icon]
68
+
69
+ let onClick = () => {
70
+ mutation.mutate(
71
+ { record },
72
+ {
73
+ onSuccess: ({ data }) => {
74
+ refresh()
75
+ if (data && data.redirectTo) window.location = data.redirectTo
76
+ if (data && data.message) notify(data.message, { type: "success" })
77
+ },
78
+ onError: (error) => {
79
+ notify(typeof error === "string" ? error : error.message, {
80
+ type: "error",
81
+ })
82
+ },
83
+ }
84
+ )
85
+ }
86
+
87
+ return (
88
+ <Button
89
+ label={label}
90
+ title={title}
91
+ onClick={onClick}
92
+ disabled={mutation.isLoading}
93
+ >
94
+ {Icon && <Icon />}
95
+ </Button>
96
+ )
97
+ }
98
+
99
+ export default ActionButton
@@ -0,0 +1,20 @@
1
+ import React from "react"
2
+ import { Button } from "react-admin"
3
+ import { Link } from "react-router-dom"
4
+ import * as icons from "@mui/icons-material"
5
+
6
+ const LinkButton = ({ label, icon, title, resource, filters }) => {
7
+ const Icon = icons[icon] || icon
8
+ let linkParams = {
9
+ pathname: `/${resource}`,
10
+ search: filters ? `filter=${JSON.stringify(filters)}` : undefined,
11
+ }
12
+
13
+ return (
14
+ <Button label={label} title={title} component={Link} to={linkParams}>
15
+ {Icon && <Icon />}
16
+ </Button>
17
+ )
18
+ }
19
+
20
+ export default LinkButton
@@ -0,0 +1,74 @@
1
+ import React from "react"
2
+
3
+ import {
4
+ Grid,
5
+ Card,
6
+ CardContent,
7
+ Typography,
8
+ Table,
9
+ TableBody,
10
+ TableRow,
11
+ TableCell,
12
+ } from "@mui/material"
13
+
14
+ import { setupAdmin } from "./utils"
15
+
16
+ const AdminTableView = ({ src }) => (
17
+ <Table>
18
+ <TableBody>
19
+ {src.map((row, idx) => (
20
+ <TableRow key={idx} hover>
21
+ {row.map((cell, idx) => (
22
+ <TableCell key={idx}>{cell}</TableCell>
23
+ ))}
24
+ </TableRow>
25
+ ))}
26
+ </TableBody>
27
+ </Table>
28
+ )
29
+
30
+ const AdminCard = ({ src }) => {
31
+ if (Array.isArray(src)) {
32
+ return (
33
+ <Grid container item spacing={2}>
34
+ {src.map((card, idx) => (
35
+ <AdminCard key={idx} src={card} />
36
+ ))}
37
+ </Grid>
38
+ )
39
+ }
40
+
41
+ let { title, value } = src
42
+
43
+ return (
44
+ <Grid item xs>
45
+ <Card>
46
+ <CardContent>
47
+ <Typography
48
+ variant="h5"
49
+ component="h2"
50
+ m={2}
51
+ style={{ textAlign: "center" }}
52
+ >
53
+ {title}
54
+ </Typography>
55
+ {(Array.isArray(value) && <AdminTableView src={value} />) || (
56
+ <pre>{JSON.stringify(value, null, 2)}</pre>
57
+ )}
58
+ </CardContent>
59
+ </Card>
60
+ </Grid>
61
+ )
62
+ }
63
+
64
+ // Process dashboard
65
+ setupAdmin("dashboard", (props) => {
66
+ if (!props) return
67
+
68
+ const Dashboard = () => (
69
+ <Grid container spacing={1}>
70
+ <AdminCard src={props} />
71
+ </Grid>
72
+ )
73
+ return Dashboard
74
+ })
@@ -0,0 +1,94 @@
1
+ import { makeRequest } from "./utils"
2
+
3
+ export default (apiUrl) => {
4
+ const methods = {
5
+ getList: async (resource, { filter, pagination, sort }) => {
6
+ const query = {}
7
+ if (filter) query.where = JSON.stringify(filter)
8
+ if (pagination) {
9
+ const { page, perPage: limit } = pagination
10
+ query.limit = limit
11
+ query.offset = limit * (page - 1)
12
+ }
13
+ if (sort) {
14
+ const { field, order } = sort
15
+ query.sort = order == "ASC" ? field : `-${field}`
16
+ }
17
+
18
+ const { headers, json } = await makeRequest(`${apiUrl}/${resource}`, {
19
+ query,
20
+ })
21
+ return {
22
+ data: json,
23
+ total: parseInt(headers.get("x-total"), 10),
24
+ }
25
+ },
26
+
27
+ getOne: async (resource, { id }) => {
28
+ const { json } = await makeRequest(`${apiUrl}/${resource}/${id}`)
29
+ return { data: json }
30
+ },
31
+
32
+ create: async (resource, { data }) => {
33
+ const { json } = await makeRequest(`${apiUrl}/${resource}`, {
34
+ data,
35
+ method: "POST",
36
+ })
37
+ return { data: json }
38
+ },
39
+
40
+ update: async (resource, { id, data }) => {
41
+ const { json } = await makeRequest(`${apiUrl}/${resource}/${id}`, {
42
+ data,
43
+ method: "PUT",
44
+ })
45
+ return { data: json }
46
+ },
47
+
48
+ updateMany: async (resource, { ids, data }) => {
49
+ await Promise.all(ids.map((id) => methods.update(resource, { id, data })))
50
+ return { data: ids }
51
+ },
52
+
53
+ delete: async (resource, { id }) => {
54
+ await makeRequest(`${apiUrl}/${resource}/${id}`, {
55
+ method: "DELETE",
56
+ })
57
+ return { data: { id } }
58
+ },
59
+
60
+ deleteMany: async (resource, { ids }) => {
61
+ await makeRequest(`${apiUrl}/${resource}`, {
62
+ data: ids,
63
+ method: "DELETE",
64
+ })
65
+ return { data: ids }
66
+ },
67
+
68
+ getMany: (resource, { ids }) => {
69
+ return methods.getList(resource, { filter: { id: { $in: ids } } })
70
+ },
71
+
72
+ getManyReference: async (resource, { target, id, filter, ...opts }) => {
73
+ filter = filter || {}
74
+ filter[target] = id
75
+ return await methods.getList(resource, { filter, ...opts })
76
+ },
77
+
78
+ runAction: async (resource, action, props) => {
79
+ const { payload, ids, record } = props
80
+ action = action.replace(/^\/+/, "")
81
+ if (record) {
82
+ action = action.replace(/\{([^}]+)\}/, (_, field) => record[field])
83
+ }
84
+ const { json } = await makeRequest(`${apiUrl}/${action}`, {
85
+ query: { ids },
86
+ method: "POST",
87
+ data: payload,
88
+ })
89
+ return { data: json }
90
+ },
91
+ }
92
+
93
+ return methods
94
+ }
@@ -0,0 +1,33 @@
1
+ import React from "react"
2
+ import get from "lodash/get"
3
+ import { useRecordContext } from "ra-core"
4
+ import Avatar from "@mui/material/Avatar"
5
+
6
+ const AvatarField = ({ source, alt, style, nameProp, ...props }) => {
7
+ const record = useRecordContext(props)
8
+
9
+ let value = get(record, source),
10
+ name = record[nameProp]
11
+
12
+ const letters = name
13
+ ? name
14
+ .trim(" ")
15
+ .split(/\s+/)
16
+ .slice(0, 2)
17
+ .map((n) => n[0].toUpperCase())
18
+ .join("")
19
+ : ""
20
+
21
+ return (
22
+ <Avatar src={value} alt={alt} style={style}>
23
+ {letters}
24
+ </Avatar>
25
+ )
26
+ }
27
+
28
+ AvatarField.displayName = "AvatarField"
29
+ AvatarField.defaultProps = {
30
+ addLabel: true,
31
+ }
32
+
33
+ export default AvatarField
@@ -0,0 +1,27 @@
1
+ import React from "react"
2
+ import { ReferenceField, TextField } from "react-admin"
3
+
4
+ const FKField = ({ refSource, refID, link, reference, source, ...props }) => {
5
+ return (
6
+ <ReferenceField
7
+ link={link || "show"}
8
+ reference={reference}
9
+ source={source}
10
+ {...props}
11
+ >
12
+ <>
13
+ <TextField source={refSource} />
14
+ {" (#"}
15
+ <TextField source={refID || "id"} />
16
+ {")"}
17
+ </>
18
+ </ReferenceField>
19
+ )
20
+ }
21
+
22
+ FKField.displayName = "FKField"
23
+ FKField.defaultProps = {
24
+ addLabel: true,
25
+ }
26
+
27
+ export default FKField
@@ -0,0 +1,26 @@
1
+ import get from "lodash/get"
2
+
3
+ import React, { useState } from "react"
4
+ import { Button, FunctionField, useRecordContext } from "react-admin"
5
+
6
+ export default function JsonField({ label, source }) {
7
+ const [expand, setExpand] = useState(false)
8
+ const record = useRecordContext()
9
+ const src = JSON.stringify(get(record, source), null, 2)
10
+ const retval = (
11
+ <div>
12
+ <p></p>
13
+ {label}
14
+ <Button
15
+ variant="contained"
16
+ color="primary"
17
+ size="small"
18
+ label={expand ? "colapse" : "expand"}
19
+ onClick={() => setExpand(!expand)}
20
+ />
21
+ <p>{expand && <pre>{src}</pre>}</p>
22
+ </div>
23
+ )
24
+
25
+ return <FunctionField render={() => retval} />
26
+ }
@@ -0,0 +1,9 @@
1
+ import { useDataProvider } from "react-admin"
2
+ import { useMutation } from "react-query"
3
+
4
+ export default function useAction(resource, action) {
5
+ const dataProvider = useDataProvider()
6
+ return useMutation((params) =>
7
+ dataProvider.runAction(resource, action, params)
8
+ )
9
+ }
package/src/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import { createRoot } from "react-dom/client"
2
+
3
+ import "./admin"
4
+ import { processAdmin } from "./utils"
5
+
6
+ export * from "./utils"
7
+
8
+ export async function initAdmin(prefix = "") {
9
+ const response = await fetch(`${prefix}/ra.json`)
10
+ const props = await response.json()
11
+ const container = document.getElementById("root")
12
+ const root = createRoot(container)
13
+ root.render(processAdmin("admin", props))
14
+ }
@@ -0,0 +1,41 @@
1
+ import React from "react"
2
+ import { ReferenceInput, AutocompleteInput } from "react-admin"
3
+
4
+ const FKInput = ({
5
+ refProp,
6
+ refSource,
7
+ fullWidth,
8
+ reference,
9
+ source,
10
+ ...props
11
+ }) => {
12
+ let filterToQuery = (search) => {
13
+ let filters = {}
14
+ filters[refProp] = search
15
+ return filters
16
+ }
17
+
18
+ refSource = refSource || "id"
19
+
20
+ let renderText = (record) => {
21
+ let pk = record[refSource]
22
+ return (pk && `${record[refProp]} (#${pk})`) || ""
23
+ }
24
+
25
+ return (
26
+ <ReferenceInput
27
+ reference={reference}
28
+ source={source}
29
+ filterToQuery={filterToQuery}
30
+ {...props}
31
+ >
32
+ <AutocompleteInput
33
+ source={refSource}
34
+ optionText={renderText}
35
+ fullWidth={fullWidth}
36
+ />
37
+ </ReferenceInput>
38
+ )
39
+ }
40
+
41
+ export default FKInput
@@ -0,0 +1,62 @@
1
+ import React from "react"
2
+ import { TextInput } from "react-admin"
3
+ import isJSON from "validator/lib/isJSON"
4
+
5
+ const DEFAULT_ERRORTEXT = "Invalid JSON"
6
+
7
+ const parseFunction = (json) => {
8
+ let retval = json
9
+ try {
10
+ retval = JSON.parse(json)
11
+ } finally {
12
+ return retval
13
+ }
14
+ }
15
+
16
+ /**
17
+ *
18
+ * `JsonInput` validates if the entered value is JSON or not. If entered value is not a invalid JSON, `JsonInput` will throw an error.
19
+ * Default error message is: `Invalid JSON` and can be overridden using `errortext` prop.
20
+ *
21
+ * @example
22
+ * <JsonInput source='config' label='JSON Config' errortext='Enter a valid JSON'/>
23
+ *
24
+ * or use translate function:
25
+ *
26
+ * @example
27
+ * <JsonInput source='config' label={translate('resources.resource_name.fields.config')} errortext={translate('myroot.validate.json')}/>
28
+ *
29
+ * By default, `JsonInput` parses and returns the entered string as object. Instead, to send string directly, please pass `parse` prop as `false`
30
+ *
31
+ * @example
32
+ * <JsonInput source='config' label='JSON Config' parse={false}/>
33
+ */
34
+ export default function JsonInput(props) {
35
+ const {
36
+ validate = [],
37
+ errortext = DEFAULT_ERRORTEXT,
38
+ multiline = true,
39
+ parse = true,
40
+ ...rest
41
+ } = props
42
+ // const errorobj = { message: errortext }
43
+
44
+ const validateJSON = (value) => {
45
+ if (!value || typeof value === "object") return undefined
46
+ return isJSON(value) ? undefined : errortext
47
+ }
48
+ validate.push(validateJSON)
49
+ rest.validate = validate
50
+
51
+ const formatJSON = (json) => {
52
+ let retval = json
53
+ if (retval && typeof retval === "object")
54
+ retval = JSON.stringify(retval, null, 2)
55
+ return retval
56
+ }
57
+ rest.format = formatJSON
58
+ //
59
+ if (parse) rest.parse = parseFunction
60
+ if (multiline) rest.multiline = true
61
+ return <TextInput {...rest} />
62
+ }
@@ -0,0 +1,36 @@
1
+ import React from "react"
2
+
3
+ import { Resource } from "react-admin"
4
+ import * as icons from "@mui/icons-material"
5
+
6
+ import { checkParams, processAdmin, setupAdmin } from "./utils"
7
+ import "./views/list"
8
+ import "./views/show"
9
+ import "./views/edit"
10
+ import "./views/create"
11
+
12
+ // Initialize Resources Components
13
+ setupAdmin("resources", (resources) =>
14
+ resources.map((res) => processAdmin("resource", res, res.name))
15
+ )
16
+
17
+ // Initialize a resource's component
18
+ setupAdmin(
19
+ "resource",
20
+ checkParams((props, res) => {
21
+ let { create, edit, icon, list, name, show, ...resProps } = props
22
+
23
+ return (
24
+ <Resource
25
+ key={name}
26
+ name={name}
27
+ icon={icons[icon]}
28
+ create={processAdmin("create", create, res)}
29
+ edit={processAdmin("edit", edit, res)}
30
+ list={processAdmin("list", list, res)}
31
+ show={processAdmin("show", show, res)}
32
+ {...resProps}
33
+ />
34
+ )
35
+ })
36
+ )
package/src/ui.jsx ADDED
@@ -0,0 +1,107 @@
1
+ import React from "react"
2
+
3
+ import { required } from "react-admin"
4
+ import {
5
+ BooleanField,
6
+ ChipField,
7
+ DateField,
8
+ EmailField,
9
+ ImageField,
10
+ FileField,
11
+ NumberField,
12
+ RichTextField,
13
+ TextField,
14
+ UrlField,
15
+ SelectField,
16
+ ArrayField,
17
+ ReferenceField,
18
+ ReferenceManyField,
19
+ ReferenceArrayField,
20
+ } from "react-admin"
21
+ import {
22
+ BooleanInput,
23
+ NullableBooleanInput,
24
+ DateInput,
25
+ DateTimeInput,
26
+ ImageInput,
27
+ FileInput,
28
+ NumberInput,
29
+ PasswordInput,
30
+ TextInput,
31
+ AutocompleteInput,
32
+ RadioButtonGroupInput,
33
+ SelectInput,
34
+ ArrayInput,
35
+ AutocompleteArrayInput,
36
+ CheckboxGroupInput,
37
+ ReferenceArrayInput,
38
+ ReferenceInput,
39
+ } from "react-admin"
40
+
41
+ import AvatarField from "./fields/AvatarField"
42
+ import FKField from "./fields/FKField"
43
+ import FKInput from "./inputs/FKInput"
44
+ import JsonField from "./fields/JsonField"
45
+ import JsonInput from "./inputs/JsonInput"
46
+
47
+ const ui = {
48
+ // Fields
49
+ BooleanField,
50
+ ChipField,
51
+ DateField,
52
+ EmailField,
53
+ ImageField,
54
+ FileField,
55
+ NumberField,
56
+ RichTextField,
57
+ TextField,
58
+ UrlField,
59
+ SelectField,
60
+ ArrayField,
61
+ ReferenceField,
62
+ ReferenceManyField,
63
+ ReferenceArrayField,
64
+ AvatarField,
65
+ JsonField,
66
+ FKField,
67
+
68
+ // Inputs
69
+ BooleanInput,
70
+ NullableBooleanInput,
71
+ DateInput,
72
+ DateTimeInput,
73
+ ImageInput,
74
+ FileInput,
75
+ NumberInput,
76
+ PasswordInput,
77
+ TextInput,
78
+ AutocompleteInput,
79
+ RadioButtonGroupInput,
80
+ SelectInput,
81
+ ArrayInput,
82
+ AutocompleteArrayInput,
83
+ CheckboxGroupInput,
84
+ ReferenceArrayInput,
85
+ ReferenceInput,
86
+ JsonInput,
87
+ FKInput,
88
+ }
89
+
90
+ const initRAItems = (itemsProps) =>
91
+ itemsProps.map((item) => {
92
+ const Item = ui[item[0]],
93
+ props = { ...item[1] }
94
+
95
+ if (props.required) {
96
+ props.validate = required()
97
+ delete props.required
98
+ }
99
+
100
+ props.fullWidth = props.fullWidth ?? true
101
+
102
+ if (props.children) props.children = initRAItems(props.children)[0]
103
+
104
+ return <Item key={props.source} {...props} />
105
+ })
106
+
107
+ export default initRAItems
package/src/utils.js ADDED
@@ -0,0 +1,38 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import { isValidElement } from "react"
4
+ import { fetchUtils } from "ra-core"
5
+ import { stringify } from "query-string"
6
+
7
+ const parts = {}
8
+
9
+ export const setupAdmin = (part, gen) => (parts[part] = gen)
10
+
11
+ export const checkParams = (fn) => (props, res) => {
12
+ if (!props || isValidElement(props)) return props
13
+ return fn(props, res)
14
+ }
15
+
16
+ export const processAdmin = (type, props, res) => {
17
+ if (process.env.NODE_ENV == "development")
18
+ console.log(`${type}${res ? "-" + res : ""}`, props)
19
+
20
+ if (parts[`${type}-${res}`]) return parts[`${type}-${res}`](props, res)
21
+ if (parts[type]) return parts[type](props, res)
22
+ }
23
+
24
+ export const requestHeaders = {}
25
+
26
+ export const makeRequest = (url, params) => {
27
+ let { data, query, ...opts } = params || {}
28
+
29
+ if (data) opts.body = JSON.stringify(data)
30
+ if (query) url = `${url}?${stringify(query)}`
31
+
32
+ opts = {
33
+ ...opts,
34
+ headers: new Headers({ ...requestHeaders, ...opts.headers }),
35
+ }
36
+
37
+ return fetchUtils.fetchJson(url, opts)
38
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react"
2
+
3
+ import { Create, SimpleForm, TopToolbar, ListButton } from "react-admin"
4
+
5
+ import initRAItems from "../ui"
6
+ import { checkParams, processAdmin, setupAdmin } from "../utils"
7
+
8
+ // Initialize a create component
9
+ setupAdmin(
10
+ "create",
11
+ checkParams(
12
+ (inputs, res) =>
13
+ function MACreate(props) {
14
+ const Actions = (
15
+ <TopToolbar>
16
+ <ListButton />
17
+ </TopToolbar>
18
+ )
19
+
20
+ return (
21
+ <Create actions={Actions} {...props}>
22
+ <SimpleForm>
23
+ {processAdmin("create-inputs", inputs, res)}
24
+ </SimpleForm>
25
+ </Create>
26
+ )
27
+ }
28
+ )
29
+ )
30
+ setupAdmin("create-inputs", initRAItems)
@@ -0,0 +1,41 @@
1
+ import React from "react"
2
+
3
+ import {
4
+ Edit,
5
+ ListButton,
6
+ ShowButton,
7
+ SimpleForm,
8
+ TopToolbar,
9
+ } from "react-admin"
10
+
11
+ import initRAItems from "../ui"
12
+ import { checkParams, processAdmin, setupAdmin } from "../utils"
13
+ import { ActionButton } from "../buttons/ActionButton"
14
+
15
+ // Initialize an edit component
16
+ setupAdmin(
17
+ "edit",
18
+ checkParams(
19
+ ({ actions, inputs }, res) =>
20
+ function MAEdit(props) {
21
+ const Actions = ({ basePath, resource, ...baseProps }) => (
22
+ <TopToolbar>
23
+ {actions.map((props, idx) => {
24
+ let aProps = { resource, basePath, ...baseProps, ...props }
25
+ return <ActionButton key={idx} {...aProps} />
26
+ })}
27
+ <ListButton />
28
+ <ShowButton />
29
+ </TopToolbar>
30
+ )
31
+
32
+ return (
33
+ <Edit actions={<Actions />} {...props}>
34
+ <SimpleForm>{processAdmin("edit-inputs", inputs, res)}</SimpleForm>
35
+ </Edit>
36
+ )
37
+ }
38
+ )
39
+ )
40
+
41
+ setupAdmin("edit-inputs", initRAItems)
@@ -0,0 +1,86 @@
1
+ import React from "react"
2
+
3
+ import {
4
+ BulkDeleteButton,
5
+ Datagrid,
6
+ EditButton,
7
+ Filter,
8
+ List,
9
+ Pagination,
10
+ } from "react-admin"
11
+ import uniq from "lodash/uniq"
12
+ import sortBy from "lodash/sortBy"
13
+
14
+ import initRAItems from "../ui"
15
+ import { checkParams, processAdmin, setupAdmin } from "../utils"
16
+ import { BulkActionButton } from "../buttons/ActionButton"
17
+
18
+ const defaultPagination = <Pagination rowsPerPageOptions={[10, 25, 50, 100]} />
19
+
20
+ // Initiliaze a list component
21
+ setupAdmin(
22
+ "list",
23
+ checkParams((props, res) => {
24
+ let {
25
+ children,
26
+ filters,
27
+ edit,
28
+ pagination,
29
+ show,
30
+ limit,
31
+ limitMax,
32
+ actions,
33
+ ...listProps
34
+ } = props
35
+
36
+ children = processAdmin("list-fields", children, res)
37
+ if (edit) children.push(<EditButton key="edit-button" />)
38
+
39
+ pagination = pagination || (
40
+ <Pagination
41
+ rowsPerPageOptions={sortBy(uniq([10, 25, 50, 100, limit, limitMax]))}
42
+ />
43
+ )
44
+
45
+ return function MAList(props) {
46
+ let Filters = (props) => (
47
+ <Filter {...props}>{processAdmin("list-filters", filters, res)}</Filter>
48
+ )
49
+ let Actions = processAdmin("list-bulkActions", actions)
50
+
51
+ props = { ...props, ...listProps }
52
+ return (
53
+ <List
54
+ filters={<Filters />}
55
+ bulkActionButtons={<Actions />}
56
+ perPage={limit}
57
+ pagination={pagination || defaultPagination}
58
+ {...props}
59
+ >
60
+ <Datagrid rowClick={show ? "show" : null}>{children}</Datagrid>
61
+ </List>
62
+ )
63
+ }
64
+ })
65
+ )
66
+
67
+ setupAdmin(
68
+ "list-bulkActions",
69
+ (actions) =>
70
+ function MABulkActions(props) {
71
+ let buttons = actions.map((action, idx) => {
72
+ let aProps = { ...action, ...props }
73
+ return <BulkActionButton key={idx} {...aProps} />
74
+ })
75
+
76
+ return (
77
+ <>
78
+ {buttons}
79
+ <BulkDeleteButton {...props} />
80
+ </>
81
+ )
82
+ }
83
+ )
84
+
85
+ setupAdmin("list-filters", initRAItems)
86
+ setupAdmin("list-fields", initRAItems)
@@ -0,0 +1,42 @@
1
+ import React from "react"
2
+
3
+ import {
4
+ ListButton,
5
+ EditButton,
6
+ Show,
7
+ SimpleShowLayout,
8
+ TopToolbar,
9
+ } from "react-admin"
10
+
11
+ import initRAItems from "../ui"
12
+ import { checkParams, processAdmin, setupAdmin } from "../utils"
13
+ import { ActionButton } from "../buttons/ActionButton"
14
+
15
+ // Initiliaze a show component
16
+ setupAdmin(
17
+ "show",
18
+ checkParams(
19
+ ({ actions, fields }, resource) =>
20
+ function MAShow(props) {
21
+ const Actions = () => (
22
+ <TopToolbar>
23
+ {actions.map((props, idx) => {
24
+ return <ActionButton key={idx} resource={resource} {...props} />
25
+ })}
26
+ <ListButton />
27
+ <EditButton />
28
+ </TopToolbar>
29
+ )
30
+
31
+ return (
32
+ <Show actions={<Actions />} {...props}>
33
+ <SimpleShowLayout>
34
+ {processAdmin("show-fields", fields, resource)}
35
+ </SimpleShowLayout>
36
+ </Show>
37
+ )
38
+ }
39
+ )
40
+ )
41
+
42
+ setupAdmin("show-fields", initRAItems)
package/src/web.js ADDED
@@ -0,0 +1,3 @@
1
+ import { initAdmin } from "."
2
+
3
+ global.initAdmin = initAdmin
package/.eslintrc.json DELETED
@@ -1,44 +0,0 @@
1
- {
2
- "root": true,
3
- "extends": [
4
- "eslint:recommended",
5
- "plugin:react/recommended",
6
- "prettier"
7
- ],
8
- "parser": "@babel/eslint-parser",
9
- "settings": {
10
- "react": {
11
- "version": "detect"
12
- }
13
- },
14
- "parserOptions": {
15
- "ecmaFeatures": {
16
- "jsx": true,
17
- "experimentalObjectRestSpread": true
18
- },
19
- "ecmaVersion": 2020,
20
- "sourceType": "module",
21
- "babelOptions": {
22
- "presets": [
23
- "@babel/preset-react"
24
- ],
25
- "rootMode": "upward"
26
- }
27
- },
28
- "plugins": [
29
- "prettier",
30
- "react"
31
- ],
32
- "env": {
33
- "browser": true,
34
- "es6": true,
35
- "jest": true,
36
- "node": true
37
- },
38
- "rules": {
39
- "no-console": "warn",
40
- "no-debugger": "warn",
41
- "prettier/prettier": "warn",
42
- "react/prop-types": "off"
43
- }
44
- }
package/Makefile DELETED
@@ -1,18 +0,0 @@
1
- all: node_modules
2
- $(CURDIR)/node_modules/.bin/webpack
3
-
4
- node_modules: package.json
5
- npm install
6
- touch node_modules
7
-
8
- npm: node_modules
9
- $(CURDIR)/node_modules/.bin/babel src --out-dir dist
10
-
11
- pypi:
12
- $(CURDIR)/node_modules/.bin/webpack
13
-
14
- watch: node_modules
15
- $(CURDIR)/node_modules/.bin/webpack --watch
16
-
17
- dev: node_modules
18
- NODE_ENV=development $(CURDIR)/node_modules/.bin/webpack serve
package/jsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "noImplicitAny": false,
4
- "target": "ESNext",
5
- "moduleResolution": "node",
6
- "checkJs": true,
7
- "jsx": "react-jsx"
8
- }
9
- }
package/webpack.config.js DELETED
@@ -1,45 +0,0 @@
1
- const path = require("path"),
2
- webpack = require("webpack"),
3
- mode = process.env.NODE_ENV
4
-
5
- module.exports = {
6
- entry: "./src/web",
7
-
8
- output: {
9
- filename: "main.js",
10
- path: path.resolve(__dirname, "../muffin_admin"),
11
- publicPath: "/admin",
12
- },
13
-
14
- module: {
15
- rules: [
16
- {
17
- test: /\.jsx?$/,
18
- exclude: /node_modules/,
19
- use: {
20
- loader: "babel-loader",
21
- },
22
- },
23
- ],
24
- },
25
-
26
- plugins: [new webpack.EnvironmentPlugin({ NODE_ENV: "production" })],
27
-
28
- mode: mode || "production",
29
- devtool: mode == "development" && "inline-source-map",
30
-
31
- resolve: {
32
- extensions: [".jsx", ".js"],
33
- },
34
-
35
- devServer: {
36
- hot: true,
37
- open: true,
38
- proxy: [
39
- {
40
- context: ["!*.js"],
41
- target: "http://localhost:5555",
42
- },
43
- ],
44
- },
45
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes