diff --git a/README.md b/README.md
index 53aef21..9d98201 100644
--- a/README.md
+++ b/README.md
@@ -88,7 +88,7 @@ firebaseConfig specification, since it gets picked up automatically.
import React from "react";
import ReactDOM from "react-dom";
-import "typeface-roboto";
+import "typeface-rubik";
import {
Authenticator,
@@ -272,7 +272,7 @@ const myAuthenticator: Authenticator = (user?: User) => {
ReactDOM.render(
:
+ value ? :
);
}
diff --git a/example/src/index.css b/example/src/index.css
deleted file mode 100644
index 7d30ace..0000000
--- a/example/src/index.css
+++ /dev/null
@@ -1,13 +0,0 @@
-body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
diff --git a/example/src/index.tsx b/example/src/index.tsx
index 4cffe13..f0734b3 100644
--- a/example/src/index.tsx
+++ b/example/src/index.tsx
@@ -1,10 +1,9 @@
import React from "react";
import ReactDOM from "react-dom";
-import "./index.css";
import logo from "./images/test_shop_logo.png";
import algoliasearch, { SearchClient } from "algoliasearch";
-import "typeface-roboto";
+import "typeface-rubik";
import * as serviceWorker from "./serviceWorker";
@@ -376,6 +375,15 @@ export const testEntitySchema = buildSchema({
}
}
},
+ pdf: {
+ title: "Pdf",
+ dataType: "string",
+ config: {
+ storageMeta: {
+ storagePath: "test"
+ }
+ }
+ },
image_urls: {
title: "Image URLs",
dataType: "array",
@@ -550,7 +558,7 @@ const myAuthenticator: Authenticator = (user?: User) => {
ReactDOM.render(
{
ReactDOM.render(
=3",
+ "typeface-rubik": "^0.0.72",
"typeface-roboto": "^0.0.75",
"yup": "^0.29.3"
},
diff --git a/src/CMSApp.tsx b/src/CMSApp.tsx
index a49d6b6..058a881 100644
--- a/src/CMSApp.tsx
+++ b/src/CMSApp.tsx
@@ -8,7 +8,6 @@ import {
Divider,
Drawer,
Grid,
- Hidden,
IconButton,
List,
ListItem,
@@ -53,6 +52,7 @@ import {
import { useStyles } from "./styles";
import { Authenticator } from "./authenticator";
import { blue, pink, red } from "@material-ui/core/colors";
+import { AuthContext, FirebaseConfigContext } from "./contexts";
/**
* Main entry point that defines the CMS configuration
@@ -115,6 +115,13 @@ export interface CMSAppProps {
* Primary color of the theme of the CMS
*/
secondaryColor?: string
+
+ /**
+ * Font family string
+ * e.g.
+ * '"Roboto", "Helvetica", "Arial", sans-serif'
+ */
+ fontFamily?: string
}
/**
@@ -141,8 +148,6 @@ export interface AdditionalView {
const googleAuthProvider = new firebase.auth.GoogleAuthProvider();
-export const AuthContext = React.createContext(null);
-
interface HideOnScrollProps {
/**
* Injected by the documentation to work in an iframe.
@@ -167,18 +172,19 @@ function HideOnScroll(props: HideOnScrollProps) {
}
export function CMSApp({
- name,
- logo,
- navigation,
- includeMedia,
- authentication,
- allowSkipLogin,
- firebaseConfig,
- additionalViews,
- primaryColor,
- secondaryColor,
- ...props
- }: CMSAppProps) {
+ name,
+ logo,
+ navigation,
+ includeMedia,
+ authentication,
+ allowSkipLogin,
+ firebaseConfig,
+ additionalViews,
+ primaryColor,
+ secondaryColor,
+ fontFamily,
+ ...props
+ }: CMSAppProps) {
const classes = useStyles();
const theme = createMuiTheme({
palette: {
@@ -195,6 +201,9 @@ export function CMSApp({
main: red.A400
}
},
+ typography: {
+ "fontFamily": fontFamily ? fontFamily : `"Rubik", "Roboto", "Helvetica", "Arial", sans-serif`
+ },
shape: {
borderRadius: 2
},
@@ -202,15 +211,15 @@ export function CMSApp({
MuiTableRow: {
root: {
"&:last-child td": {
- borderBottom: 0,
- },
+ borderBottom: 0
+ }
}
}
- },
+ }
});
- const [mobileOpen, setMobileOpen] = React.useState(false);
+ const [drawerOpen, setDrawerOpen] = React.useState(false);
const [
firebaseConfigInitialized,
@@ -291,7 +300,7 @@ export function CMSApp({
}
}, []);
- const handleDrawerToggle = () => setMobileOpen(!mobileOpen);
+ const handleDrawerToggle = () => setDrawerOpen(!drawerOpen);
function googleSignIn() {
setAuthProviderError(null);
@@ -493,78 +502,68 @@ export function CMSApp({
);
return (
-
-
-
-
-
-
-
-
-
-
- {name}
-
-
+
+
+
+
+
+
+
+
+
+
+
+ {name}
+
+
-
- {loggedUser && loggedUser.photoURL ?
-
- :
- {loggedUser?.displayName ? loggedUser.displayName[0] : "A"}
- }
-
+
+ {loggedUser && loggedUser.photoURL ?
+
+ :
+ {loggedUser?.displayName ? loggedUser.displayName[0] : "A"}
+ }
+
-
+
-
-
+
+
-
-
-
- {getRouterSwitch(shouldIncludeMedia)}
-
-
-
-
+
+
+
+
+ {getRouterSwitch(shouldIncludeMedia)}
+
+
+
+
+
);
}
@@ -574,8 +573,11 @@ export function CMSApp({
{firebaseConfigError ?
It seems like the provided Firebase config is not
- correct. If you are using the credentials provided automatically
- by Firebase Hosting, make sure you your Firebase app to Firebase Hosting.
+ correct. If you are using the credentials provided
+ automatically
+ by Firebase Hosting, make sure you link
+ your Firebase app to
+ Firebase Hosting.
:
(
authLoading ? (
diff --git a/src/collection/CollectionTable.tsx b/src/collection/CollectionTable.tsx
index 7447989..760be16 100644
--- a/src/collection/CollectionTable.tsx
+++ b/src/collection/CollectionTable.tsx
@@ -26,6 +26,8 @@ import { TextSearchDelegate } from "../text_search_delegate";
import SearchBar from "./SearchBar";
import PreviewComponent from "../preview/PreviewComponent";
import SkeletonComponent, { renderSkeletonText } from "../preview/SkeletonComponent";
+import firebase from "firebase";
+import FieldPath = firebase.firestore.FieldPath;
interface CollectionTableProps {
/**
@@ -159,7 +161,7 @@ export default function CollectionTable(props: Collectio
setOrderBy(undefined);
};
- const handleRequestSort = (event: React.MouseEvent, property: string) => {
+ const handleRequestSort = (event: React.MouseEvent, property: any) => {
if (filter) {
const filterKeys = Object.keys(filter);
if (filterKeys.length > 1 || filterKeys[0] !== property) {
@@ -449,9 +451,9 @@ type Order = "asc" | "desc" | undefined;
interface CollectionTableHeadProps {
classes: ReturnType;
- onRequestSort: (event: React.MouseEvent, property: string) => void;
+ onRequestSort: (event: React.MouseEvent, property: any) => void;
order?: Order;
- orderBy?: string;
+ orderBy?: any;
sortable: boolean;
schema: S;
additionalColumns?: AdditionalColumnDelegate[];
@@ -477,7 +479,7 @@ function CollectionTableHead({
}: CollectionTableHeadProps) {
- const createSortHandler = (property: string) => (event: React.MouseEvent) => {
+ const createSortHandler = (property: any) => (event: React.MouseEvent) => {
onRequestSort(event, property);
};
@@ -492,6 +494,7 @@ function CollectionTableHead({
});
});
+ const sortedById = orderBy === FieldPath.documentId();
return (
@@ -499,7 +502,20 @@ function CollectionTableHead({
Id
+ padding={"default"}>
+
+ Id
+ {sortedById ?
+
+ {order === "desc" ? "Sorted descending" : (order === "asc" ? "Sorted ascending" : "")}
+
+ : null}
+
+
{headCells.map(headCell => {
const active = sortable && orderBy === headCell.id;
diff --git a/src/contexts.tsx b/src/contexts.tsx
new file mode 100644
index 0000000..baf4a63
--- /dev/null
+++ b/src/contexts.tsx
@@ -0,0 +1,5 @@
+import React from "react";
+import { User } from "firebase";
+
+export const AuthContext = React.createContext(null);
+export const FirebaseConfigContext = React.createContext(null);
diff --git a/src/form/fields/SwitchField.tsx b/src/form/fields/SwitchField.tsx
index 67ccb53..07899c5 100644
--- a/src/form/fields/SwitchField.tsx
+++ b/src/form/fields/SwitchField.tsx
@@ -1,4 +1,8 @@
-import { FormControlLabel, FormHelperText, Switch } from "@material-ui/core";
+import {
+ FormControlLabel,
+ FormHelperText,
+ Switch
+} from "@material-ui/core";
import React from "react";
import { CMSFieldProps } from "../form_props";
diff --git a/src/form/index.tsx b/src/form/index.tsx
index 78b8f8a..a881c63 100644
--- a/src/form/index.tsx
+++ b/src/form/index.tsx
@@ -184,3 +184,4 @@ export {
SwitchField,
TextField
};
+
diff --git a/src/models.ts b/src/models.ts
index 9247335..4bcb46c 100644
--- a/src/models.ts
+++ b/src/models.ts
@@ -574,7 +574,7 @@ export interface StorageMeta {
/**
* Media type of this reference, used for displaying the preview
*/
- mediaType: MediaType;
+ mediaType?: MediaType;
/**
* Absolute path in your bucket
@@ -628,7 +628,8 @@ export type StorageFileTypes =
| "audio/*"
| "application/*"
| "text/*"
- | "font/*" ;
+ | "font/*"
+ | string; // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
export interface NumberFieldConfig extends FieldConfig {
diff --git a/src/preview/EntityDetailDialog.tsx b/src/preview/EntityDetailDialog.tsx
index dfd777c..ab1b5cc 100644
--- a/src/preview/EntityDetailDialog.tsx
+++ b/src/preview/EntityDetailDialog.tsx
@@ -1,15 +1,19 @@
import { Entity, EntitySchema } from "../models";
import React, { useEffect, useState } from "react";
-import { listenEntityFromRef } from "../firebase/firestore";
-import Dialog from "@material-ui/core/Dialog";
-import DialogTitle from "@material-ui/core/DialogTitle";
-import { DialogContent } from "@material-ui/core";
+import { listenEntityFromRef } from "../firebase";
+
import EntityPreview from "../preview/EntityPreview";
-import DialogActions from "@material-ui/core/DialogActions";
-import Button from "@material-ui/core/Button";
+import {
+ Box,
+ Container,
+ Drawer,
+ IconButton,
+ Typography
+} from "@material-ui/core";
+import CloseIcon from "@material-ui/icons/Close";
export interface EntityDetailDialogProps {
- entity: Entity,
+ entity?: Entity,
schema: S
open: boolean;
onClose: () => void;
@@ -19,52 +23,53 @@ export default function EntityDetailDialog(props: Entity
const { entity, schema, onClose, open, ...other } = props;
- const [updatedEntity, setUpdatedEntity] = useState>(entity);
+ const [updatedEntity, setUpdatedEntity] = useState | undefined>(entity);
useEffect(() => {
- const cancelSubscription = listenEntityFromRef(
- entity?.reference,
- schema,
- (e) => {
- if (e) {
- setUpdatedEntity(e);
- console.log("Updated entity from Firestore", e);
- }
- });
+ const cancelSubscription =
+ entity ?
+ listenEntityFromRef(
+ entity?.reference,
+ schema,
+ (e) => {
+ if (e) {
+ setUpdatedEntity(e);
+ console.log("Updated entity from Firestore", e);
+ }
+ })
+ :
+ () => {
+ };
return () => cancelSubscription();
}, [entity]);
return (
-
+
+
+
+
+
+
+
+
+ {schema.name}
+
+
+
-
+
-
-
-
-
-
-
-
);
}
diff --git a/src/preview/EntityPreview.tsx b/src/preview/EntityPreview.tsx
index d096367..0e80be1 100644
--- a/src/preview/EntityPreview.tsx
+++ b/src/preview/EntityPreview.tsx
@@ -1,21 +1,24 @@
import * as React from "react";
import {
+ Box,
Table,
TableBody,
TableCell,
TableContainer,
- TableRow, Typography
+ TableRow,
+ Typography
} from "@material-ui/core";
import { Entity, EntitySchema } from "../models";
import PreviewComponent from "./PreviewComponent";
+import OpenInNewIcon from "@material-ui/icons/OpenInNew";
+import IconButton from "@material-ui/core/IconButton";
+import { FirebaseConfigContext } from "../contexts";
+
export interface EntityPreviewProps {
-
entity: Entity;
-
schema: S;
-
}
export default function EntityPreview(
@@ -25,37 +28,56 @@ export default function EntityPreview(
}: EntityPreviewProps) {
return (
-
-
-
-
-
-
- Id
-
-
-
- {entity.id}
-
-
- {Object.entries(schema.properties).map(([key, property]) => (
-
-
-
- {property.title}
-
-
-
-
-
-
- ))}
-
-
-
+
+ {config => (
+
+
+
+
+
+
+ Id
+
+
+
+
+ {entity.id}
+
+
+
+
+
+
+
+
+ {Object.entries(schema.properties).map(([key, property]) => (
+
+
+
+ {property.title}
+
+
+
+
+
+
+ ))}
+
+
+
+ )}
+
+
);
}
diff --git a/src/preview/PreviewComponent.tsx b/src/preview/PreviewComponent.tsx
index 2570251..d9589ba 100644
--- a/src/preview/PreviewComponent.tsx
+++ b/src/preview/PreviewComponent.tsx
@@ -14,8 +14,7 @@ import { firestore } from "firebase/app";
import {
Box,
- CardMedia,
- Checkbox,
+ CardMedia, Checkbox,
Chip,
Divider,
Grid,
@@ -27,7 +26,10 @@ import {
TableRow,
Typography
} from "@material-ui/core";
+import CheckBoxOutlineBlankIcon from "@material-ui/icons/CheckBoxOutlineBlank";
+import CheckBoxIcon from "@material-ui/icons/CheckBox";
import StorageThumbnail from "./StorageThumbnail";
+import DescriptionOutlinedIcon from "@material-ui/icons/DescriptionOutlined";
import ReferencePreview from "./ReferencePreview";
import { PreviewComponentProps } from "./PreviewComponentProps";
import { useStyles } from "../styles";
@@ -136,7 +138,7 @@ function renderMap(property: MapProperty, value: T, small: boolean) {
className={classes.tableNoBottomBorder}>
-
{property.properties[key].title}
@@ -303,18 +305,23 @@ function renderUrlAudioComponent(value: string) {
function renderUrlImageThumbnail(url: string,
small: boolean) {
return (
-
-
-
+ e.stopPropagation()}
+ >
+
+
+
+
);
}
@@ -330,6 +337,23 @@ function renderUrlVideo(url: string,
);
}
+function renderUrlFile(url: string, small: boolean) {
+ return (
+ e.stopPropagation()}>
+
+
+
+
+ );
+}
+
function renderReference(
ref: firestore.DocumentReference,
refSchema: S,
@@ -363,8 +387,9 @@ export function renderUrlComponent(property: StringProperty,
return renderUrlAudioComponent(url);
} else if (mediaType === "video") {
return renderUrlVideo(url, small);
+ } else {
+ return renderUrlFile(url, small);
}
- throw Error("URL component misconfiguration");
}
export function renderString(property: StringProperty,
@@ -374,10 +399,12 @@ export function renderString(property: StringProperty,
if (property.config?.enumValues) {
return property.config?.enumValues[value];
} else if (property.config?.multiline) {
- return
- {value}
- ;
+ return (
+
+ {value}
+
+ );
} else {
return
{value}
@@ -408,7 +435,7 @@ export function renderTimestamp(value: Date) {
-
+
{value.toLocaleString()}
diff --git a/src/routes/CollectionRoute.tsx b/src/routes/CollectionRoute.tsx
index 8760194..298728e 100644
--- a/src/routes/CollectionRoute.tsx
+++ b/src/routes/CollectionRoute.tsx
@@ -21,6 +21,7 @@ import { BreadcrumbContainer } from "../util";
import DeleteEntityDialog from "../collection/DeleteEntityDialog";
import EntityDetailDialog from "../preview/EntityDetailDialog";
+
interface CollectionRouteProps {
view: EntityCollectionView;
entityPlaceholderPath: string,
@@ -107,11 +108,10 @@ export function CollectionRoute({
filterableProperties={view.filterableProperties}
properties={view.properties}/>
- {entityClicked &&
setEntityClicked(undefined)}/>}
+ onClose={() => setEntityClicked(undefined)}/>
{deleteEntityClicked &&
marginTop: theme.spacing(3)
},
drawer: {
- [theme.breakpoints.up("md")]: {
- width: drawerWidth,
- flexShrink: 0
- }
+ // [theme.breakpoints.up("md")]: {
+ // width: drawerWidth,
+ // flexShrink: 0
+ // }
},
appBar: {
[theme.breakpoints.up("md")]: {
@@ -24,9 +24,9 @@ export const useStyles = makeStyles((theme: Theme) =>
},
menuButton: {
marginRight: theme.spacing(2),
- [theme.breakpoints.up("md")]: {
- display: "none"
- }
+ // [theme.breakpoints.up("md")]: {
+ // display: "none"
+ // }
},
grow: {
flexGrow: 1
diff --git a/yarn.lock b/yarn.lock
index f86f30b..8770f80 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -13828,6 +13828,11 @@ typeface-roboto@^0.0.75:
resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.75.tgz#98d5ba35ec234bbc7172374c8297277099cc712b"
integrity sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg==
+typeface-rubik@^0.0.72:
+ version "0.0.72"
+ resolved "https://registry.yarnpkg.com/typeface-rubik/-/typeface-rubik-0.0.72.tgz#85d028dc66e8388a941f8c7ce194fad963d1824e"
+ integrity sha512-dRJrtCEHfY3e39zq/U4JpFPVVbEogpDKCGnS70sXur253gnrwunjai26KCZbwdF3NEdMAYMTBu4CIn3hJp4xSA==
+
typescript@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"