diff --git a/README.md b/README.md index 36ec8f5..525d4c6 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,6 @@ firebaseConfig specification, since it gets picked up automatically. ```tsx import React from "react"; -import ReactDOM from "react-dom"; import { Authenticator, @@ -254,33 +253,38 @@ const localeSchema = buildSchema({ } }); -const navigation: EntityCollectionView[] = [ - buildCollection({ - relativePath: "products", - schema: productSchema, - name: "Products", - subcollections: [ - buildCollection({ - name: "Locales", - relativePath: "locales", - schema: localeSchema - }) - ] - }) -]; +export function SimpleApp() { -const myAuthenticator: Authenticator = (user?: firebase.User) => { - console.log("Allowing access to", user?.email); - return true; -}; + const navigation: EntityCollectionView[] = [ + buildCollection({ + relativePath: "products", + schema: productSchema, + name: "Products", + subcollections: [ + buildCollection({ + name: "Locales", + relativePath: "locales", + schema: localeSchema + }) + ] + }) + ]; -ReactDOM.render( - { + console.log("Allowing access to", user?.email); + return true; + }; + + return , + />; +} + +ReactDOM.render( + , document.getElementById("root") ); ``` diff --git a/example/package.json b/example/package.json index 0d24904..f9edc98 100644 --- a/example/package.json +++ b/example/package.json @@ -21,7 +21,10 @@ "deploy": "yarn run build && firebase deploy --only hosting" }, "eslintConfig": { - "extends": "react-app" + "extends": [ + "react-app", + "react-app/jest" + ] }, "browserslist": { "production": [ @@ -36,8 +39,12 @@ ] }, "devDependencies": { - "@types/react": "^16.9.23", - "@types/react-dom": "^16.9.5", - "@types/react-router-dom": "^5.1.3" - } + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", + "@types/react-router-dom": "^5.1.6" + }, + "include": [ + "./src/**/*", + "../dist/**/*" + ] } diff --git a/example/src/App.tsx b/example/src/App.tsx new file mode 100644 index 0000000..c76f34f --- /dev/null +++ b/example/src/App.tsx @@ -0,0 +1,701 @@ +import React from "react"; +// import './App.css'; +import { firebaseConfig } from "./firebase_config"; +import { + AdditionalColumnDelegate, + AdditionalView, + AsyncPreviewComponent, + Authenticator, + buildCollection, + buildSchema, + CMSApp, + EntityCollectionView, + EntitySaveProps, + EnumValues, + Properties +} from "@camberi/firecms"; +import PriceTextPreview from "./custom_preview/PriceTextPreview"; +import CustomColorTextField from "./custom_field/CustomColorTextField"; +import CustomBooleanPreview from "./custom_preview/CustomBooleanPreview"; +import { + blogSearchDelegate, + productsSearchDelegate, + usersSearchDelegate +} from "./algolia_utils"; +import firebase from "firebase"; +import { IconButton, Tooltip } from "@material-ui/core"; +import GitHubIcon from "@material-ui/icons/GitHub"; +import { ExampleAdditionalView } from "./ExampleAdditionalView"; +import logo from "./images/test_shop_logo.png"; + + +function App() { + + const locales: EnumValues = { + "es": "Spanish", + "de": "German", + "en": "English", + "it": "Italian", + "fr": "French" + }; + + const categories: EnumValues = { + art_and_decoration: "Art and decoration", + art_design_books: "Art and design books", + babys: "Babies and kids", + backpacks: "Backpacks and bags", + bath: "Bath", + bicycle: "Bicycle", + books: "Books", + cameras: "Cameras", + clothing_man: "Clothing man", + clothing_woman: "Clothing woman", + coffee_and_tea: "Coffee and tea", + cookbooks: "Cookbooks", + delicatessen: "Delicatessen", + desk_accessories: "Desk accessories", + exercise_equipment: "Exercise equipment", + furniture: "Furniture", + gardening: "Gardening", + headphones: "Headphones", + home_accessories: "Home accessories", + home_storage: "Home storage", + kitchen: "Kitchen", + lighting: "Lighting", + music: "Music", + outdoors: "Outdoors", + personal_care: "Personal care", + photography_books: "Photography books", + serveware: "Serveware", + smart_home: "Smart Home", + sneakers: "Sneakers", + speakers: "Speakers", + sunglasses: "Sunglasses", + toys_and_games: "Toys and games", + watches: "Watches" + }; + + const productSchema = buildSchema({ + name: "Product", + properties: { + name: { + dataType: "string", + title: "Name", + validation: { + required: true + } + }, + main_image: { + dataType: "string", + title: "Image", + config: { + storageMeta: { + mediaType: "image", + storagePath: "images", + acceptedFiles: ["image/*"], + metadata: { + cacheControl: "max-age=1000000" + } + } + }, + description: "Upload field for images", + validation: { + required: true + } + }, + category: { + dataType: "string", + title: "Category", + config: { + enumValues: categories + } + }, + price: { + dataType: "number", + title: "Price", + validation: { + requiredMessage: "You must set a price between 0 and 1000", + min: 0, + max: 1000 + }, + config: { + customPreview: PriceTextPreview + }, + description: "Price with range validation" + }, + currency: { + dataType: "string", + title: "Currency", + config: { + enumValues: { + EUR: "Euros", + DOL: "Dollars" + } + }, + validation: { + required: true + } + }, + public: { + dataType: "boolean", + title: "Public", + description: "Should this product be visible in the website", + longDescription: "Example of a long description hidden under a tooltip. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin quis bibendum turpis. Sed scelerisque ligula nec nisi pellentesque, eget viverra lorem facilisis. Praesent a lectus ac ipsum tincidunt posuere vitae non risus. In eu feugiat massa. Sed eu est non velit facilisis facilisis vitae eget ante. Nunc ut malesuada erat. Nullam sagittis bibendum porta. Maecenas vitae interdum sapien, ut aliquet risus. Donec aliquet, turpis finibus aliquet bibendum, tellus dui porttitor quam, quis pellentesque tellus libero non urna. Vestibulum maximus pharetra congue. Suspendisse aliquam congue quam, sed bibendum turpis. Aliquam eu enim ligula. Nam vel magna ut urna cursus sagittis. Suspendisse a nisi ac justo ornare tempor vel eu eros." + }, + brand: { + dataType: "string", + title: "Brand", + validation: { + required: true + } + }, + description: { + dataType: "string", + title: "Description", + description: "Example of a markdown field", + config: { + markdown: true + } + }, + available: { + dataType: "boolean", + title: "Available", + columnWidth: 100 + }, + amazon_link: { + dataType: "string", + title: "Amazon link", + config: { + url: true + } + }, + added_on: { + dataType: "timestamp", + title: "Added on", + disabled: true, + serverTimestamp: "on_create" + }, + images: { + dataType: "array", + title: "Images", + of: { + dataType: "string", + config: { + storageMeta: { + mediaType: "image", + storagePath: "images", + acceptedFiles: ["image/*"], + metadata: { + cacheControl: "max-age=1000000" + } + } + } + }, + description: "This fields allows uploading multiple images at once" + }, + related_products: { + dataType: "array", + title: "Related products", + description: "Reference to self", + of: { + dataType: "reference", + collectionPath: "products", + } + }, + publisher: { + title: "Publisher", + description: "This is an example of a map property", + dataType: "map", + properties: { + name: { + title: "Name", + dataType: "string" + }, + external_id: { + title: "External id", + dataType: "string" + } + } + }, + min_known_price: { + dataType: "number", + title: "Min known price", + disabled: true, + description: "Minimum price this product has ever had" + }, + prime_eligible: { + dataType: "boolean", + title: "Prime eligible", + disabled: true + }, + available_locales: { + title: "Available locales", + description: + "This is an example of a disabled field that gets updated trough a Cloud Function, try changing a locale 'selectable' value", + longDescription: "Example of a long description hidden under a tooltip. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin quis bibendum turpis. Sed scelerisque ligula nec nisi pellentesque, eget viverra lorem facilisis. Praesent a lectus ac ipsum tincidunt posuere vitae non risus. In eu feugiat massa. Sed eu est non velit facilisis facilisis vitae eget ante. Nunc ut malesuada erat. Nullam sagittis bibendum porta. Maecenas vitae interdum sapien, ut aliquet risus. Donec aliquet, turpis finibus aliquet bibendum, tellus dui porttitor quam, quis pellentesque tellus libero non urna. Vestibulum maximus pharetra congue. Suspendisse aliquam congue quam, sed bibendum turpis. Aliquam eu enim ligula. Nam vel magna ut urna cursus sagittis. Suspendisse a nisi ac justo ornare tempor vel eu eros.", + dataType: "array", + disabled: true, + of: { + dataType: "string", + config: { + enumValues: locales + } + } + }, + uppercase_name: { + title: "Uppercase Name", + dataType: "string", + disabled: true, + description: "This field gets updated with a preSave hook" + } + + }, + defaultValues: { + currency: "EUR", + publisher: { + name: "Default publisher" + } + } + }); + + const blogSchema = buildSchema({ + name: "Blog entry", + properties: { + name: { + title: "Name", + validation: { required: true }, + dataType: "string" + }, + gold_text: { + title: "Gold text", + description: "This field is using a custom component defined by the developer", + dataType: "string", + config: { + field: CustomColorTextField, + fieldProps: { + color: "gold" + } + } + }, + long_text: { + title: "Long text", + description: "Example of a long text", + validation: { required: true }, + dataType: "string", + config: { + multiline: true + } + }, + images: { + title: "Images", + dataType: "array", + of: { + dataType: "string", + config: { + storageMeta: { + mediaType: "image", + storagePath: "images", + acceptedFiles: ["image/*"], + metadata: { + cacheControl: "max-age=1000000" + } + } + } + }, + description: "This fields allows uploading multiple images at once and reordering" + }, + priority: { + title: "Priority", + description: "This field allows the selection of Infinity as a value", + dataType: "number", + config: { + fieldProps: { + allowInfinity: true + } + } + }, + reviewed: { + title: "Reviewed", + dataType: "boolean", + config: { + customPreview: CustomBooleanPreview + } + }, + status: { + title: "Status", + validation: { required: true }, + dataType: "string", + config: { + enumValues: { + published: "Published", + draft: "Draft" + } + } + }, + content: { + title: "Content", + validation: { required: true }, + dataType: "array", + of: { + dataType: "string" + } + }, + products: { + title: "Products", + validation: { required: true }, + dataType: "array", + of: { + dataType: "reference", + collectionPath: "products", + previewProperties: ["name", "main_image"] + } + }, + tags: { + title: "Tags", + description: "Example of generic array", + dataType: "array", + of: { + dataType: "string", + config: { + previewAsTag: true + } + } + } + }, + defaultValues: { + status: "draft", + tags: ["default tag"] + } + }); + + const usersSchema = buildSchema({ + name: "User", + properties: { + first_name: { + title: "First name", + dataType: "string" + }, + last_name: { + title: "Last name", + dataType: "string" + }, + email: { + title: "Email", + dataType: "string", + validation: { + email: true + } + }, + phone: { + title: "Phone", + dataType: "string" + }, + picture: { + title: "Picture", + dataType: "map", + properties: { + large: { + title: "Large", + dataType: "string", + config: { + url: "image" + }, + validation: { + url: true + } + }, + thumbnail: { + title: "Thumbnail", + dataType: "string", + config: { + url: "image" + }, + validation: { + url: true + } + } + }, + previewProperties: ["large"] + } + } + }); + + const productAdditionalColumn: AdditionalColumnDelegate = { + id: "spanish_title", + title: "Spanish title", + builder: (entity) => + snapshot.get("name") as string) + }/> + }; + + const localeSchema = buildSchema({ + customId: locales, + name: "Locale", + properties: { + name: { + title: "Name", + validation: { required: true }, + dataType: "string" + }, + description: { + title: "Description", + validation: { required: true }, + dataType: "string", + config: { + multiline: true + } + }, + selectable: { + title: "Selectable", + description: "Is this locale selectable", + longDescription: "Changing this value triggers a cloud function that updates the parent product", + dataType: "boolean" + }, + video: { + title: "Video", + dataType: "string", + validation: { required: false }, + config: { + storageMeta: { + mediaType: "video", + storagePath: "videos", + acceptedFiles: ["video/*"] + } + }, + columnWidth: 400 + } + } + }); + + productSchema.onPreSave = ({ + schema, + collectionPath, + id, + values, + status + }: EntitySaveProps) => { + values.uppercase_name = values.name.toUpperCase(); + return values; + }; + + productSchema.onSaveSuccess = (props) => { + console.log("onSaveSuccess", props); + }; + + productSchema.onDelete = (props) => { + console.log("onDelete", props); + }; + + const testEntitySchema = buildSchema({ + customId: true, + name: "Test entity", + properties: { + available_dates: { + dataType: "array", + title: "Available Dates", + of: { + dataType: "timestamp" + } + }, + images: { + title: "Image URLs", + dataType: "array", + of: { + dataType: "string", + config: { + storageMeta: { + mediaType: "image", + storagePath: "images", + acceptedFiles: ["image/*"] + } + } + } + }, + image: { + title: "Image", + dataType: "string", + config: { + storageMeta: { + mediaType: "image", + storagePath: "test", + acceptedFiles: ["image/*"] + } + } + }, + mark: { + title: "Mark", + dataType: "string", + config: { + markdown: true + } + }, + tags: { + title: "Tags", + dataType: "array", + of: { + dataType: "string" + } + }, + product: { + title: "Product", + dataType: "reference", + collectionPath: "products", + previewProperties: ["name", "main_image"] + }, + created_at: { + title: "Created at", + dataType: "timestamp", + autoValue: "on_create" + }, + updated_on: { + title: "Updated on", + dataType: "timestamp", + autoValue: "on_update" + }, + title: { + title: "Title", + description: "A catching title is important", + dataType: "string" + }, + description: { + title: "Description", + dataType: "string", + config: { + multiline: true + } + }, + search_adjacent: { + title: "Search adjacent", + dataType: "boolean" + }, + difficulty: { + title: "Difficulty", + dataType: "number", + }, + range: { + title: "Range", + validation: { + min: 0, + max: 3 + }, + dataType: "number" + }, + pdf: { + title: "Pdf", + dataType: "string", + config: { + storageMeta: { + storagePath: "test" + } + } + } + } + }); + + const localeCollection: EntityCollectionView = + buildCollection({ + name: "Locales", + relativePath: "locales", + deleteEnabled: true, + schema: localeSchema, + defaultSize: "l" + }) + ; + + const productsCollection = buildCollection({ + relativePath: "products", + schema: productSchema, + name: "Products", + textSearchDelegate: productsSearchDelegate, + additionalColumns: [productAdditionalColumn], + subcollections: [localeCollection], + excludedProperties: ["images", "related_products"], + filterableProperties: ["price", "available_locales"] + }); + + const usersCollection = buildCollection({ + relativePath: "users", + schema: usersSchema, + name: "Users", + textSearchDelegate: usersSearchDelegate, + properties: ["first_name", "last_name", "email", "phone", "picture"] + }); + + const blogCollection = buildCollection({ + relativePath: "blog", + schema: blogSchema, + name: "Blog", + group: "Content", + textSearchDelegate: blogSearchDelegate, + properties: ["name", "images", "status", "reviewed", "products", "long_text"], + filterableProperties: ["name", "status"], + initialFilter: { + "status": ["==", "published"] + } + }); + + const navigation: EntityCollectionView[] = [ + productsCollection, + usersCollection, + blogCollection + ]; + + if (process.env.NODE_ENV !== "production") { + navigation.push(buildCollection({ + relativePath: "test_entity", + schema: testEntitySchema, + group: "Test group", + name: "Test entity", + filterableProperties: ["difficulty", "search_adjacent", "description"], + subcollections: [{ + relativePath: "test_subcollection", + schema: testEntitySchema, + name: "Test entity", + filterableProperties: ["difficulty", "search_adjacent", "description"] + }] + })); + } + + const myAuthenticator: Authenticator = (user?: firebase.User) => { + console.log("Allowing access to", user?.email); + return true; + }; + + const githubLink = ( + + + + + + ); + + const additionalViews: AdditionalView[] = [{ + path: "additional", + name: "Additional", + group: "Content", + view: + }]; + + return ; +} + +export default App; diff --git a/example/src/index_simple.tsx b/example/src/SimpleApp.tsx similarity index 88% rename from example/src/index_simple.tsx rename to example/src/SimpleApp.tsx index 5ebea2f..bd8caf8 100644 --- a/example/src/index_simple.tsx +++ b/example/src/SimpleApp.tsx @@ -1,5 +1,4 @@ import React from "react"; -import ReactDOM from "react-dom"; import { Authenticator, @@ -33,11 +32,11 @@ const locales = { const productSchema = buildSchema({ name: "Product", properties: { - name: { - title: "Name", - validation: { required: true }, - dataType: "string" - }, + // name: { + // title: "Name", + // validation: { required: true }, + // dataType: "string" + // }, price: { title: "Price", validation: { @@ -162,33 +161,33 @@ const localeSchema = buildSchema({ } }); -const navigation: EntityCollectionView[] = [ - buildCollection({ - relativePath: "products", - schema: productSchema, - name: "Products", - subcollections: [ - buildCollection({ - name: "Locales", - relativePath: "locales", - schema: localeSchema - }) - ] - }) -]; +export function SimpleApp() { -const myAuthenticator: Authenticator = (user?: firebase.User) => { - console.log("Allowing access to", user?.email); - return true; -}; + const navigation: EntityCollectionView[] = [ + buildCollection({ + relativePath: "products", + schema: productSchema, + name: "Products", + subcollections: [ + buildCollection({ + name: "Locales", + relativePath: "locales", + schema: localeSchema + }) + ] + }) + ]; -ReactDOM.render( - { + console.log("Allowing access to", user?.email); + return true; + }; + + return , - document.getElementById("root") -); + />; +} diff --git a/example/src/index.tsx b/example/src/index.tsx index 0a9031d..5172e7d 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -1,776 +1,12 @@ import React from "react"; import ReactDOM from "react-dom"; -import firebase from "firebase"; - import "typeface-rubik"; - -import * as serviceWorker from "./serviceWorker"; - -import { - AdditionalColumnDelegate, - AdditionalView, - AsyncPreviewComponent, - Authenticator, - buildCollection, - buildSchema, - CMSApp, - EntityCollectionView, - EntitySaveProps, - EnumValues, - Properties -} from "@camberi/firecms"; - -import { IconButton, Tooltip } from "@material-ui/core"; -import GitHubIcon from "@material-ui/icons/GitHub"; - -import { firebaseConfig } from "./firebase_config"; -import CustomColorTextField from "./custom_field/CustomColorTextField"; -import CustomBooleanPreview from "./custom_preview/CustomBooleanPreview"; -import { - blogSearchDelegate, - productsSearchDelegate, - usersSearchDelegate -} from "./algolia_utils"; -import PriceTextPreview from "./custom_preview/PriceTextPreview"; -import { ExampleAdditionalView } from "./ExampleAdditionalView"; -import logo from "./images/test_shop_logo.png"; - - -const locales: EnumValues = { - "es": "Spanish", - "de": "German", - "en": "English", - "it": "Italian", - "fr": "French" -}; - -const categories: EnumValues = { - art_and_decoration: "Art and decoration", - art_design_books: "Art and design books", - babys: "Babies and kids", - backpacks: "Backpacks and bags", - bath: "Bath", - bicycle: "Bicycle", - books: "Books", - cameras: "Cameras", - clothing_man: "Clothing man", - clothing_woman: "Clothing woman", - coffee_and_tea: "Coffee and tea", - cookbooks: "Cookbooks", - delicatessen: "Delicatessen", - desk_accessories: "Desk accessories", - exercise_equipment: "Exercise equipment", - furniture: "Furniture", - gardening: "Gardening", - headphones: "Headphones", - home_accessories: "Home accessories", - home_storage: "Home storage", - kitchen: "Kitchen", - lighting: "Lighting", - music: "Music", - outdoors: "Outdoors", - personal_care: "Personal care", - photography_books: "Photography books", - serveware: "Serveware", - smart_home: "Smart Home", - sneakers: "Sneakers", - speakers: "Speakers", - sunglasses: "Sunglasses", - toys_and_games: "Toys and games", - watches: "Watches" -}; - -const productSchema = buildSchema({ - name: "Product", - properties: { - name: { - dataType: "string", - title: "Name", - validation: { - required: true - } - }, - main_image: { - dataType: "string", - title: "Image", - config: { - storageMeta: { - mediaType: "image", - storagePath: "images", - acceptedFiles: ["image/*"], - metadata: { - cacheControl: "max-age=1000000" - } - } - }, - description: "Upload field for images", - validation: { - required: true - } - }, - category: { - dataType: "string", - title: "Category", - config: { - enumValues: categories - } - }, - price: { - dataType: "number", - title: "Price", - validation: { - requiredMessage: "You must set a price between 0 and 1000", - min: 0, - max: 1000 - }, - config: { - customPreview: PriceTextPreview - }, - description: "Price with range validation" - }, - currency: { - dataType: "string", - title: "Currency", - config: { - enumValues: { - EUR: "Euros", - DOL: "Dollars" - } - }, - validation: { - required: true - } - }, - public: { - dataType: "boolean", - title: "Public", - description: "Should this product be visible in the website", - longDescription: "Example of a long description hidden under a tooltip. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin quis bibendum turpis. Sed scelerisque ligula nec nisi pellentesque, eget viverra lorem facilisis. Praesent a lectus ac ipsum tincidunt posuere vitae non risus. In eu feugiat massa. Sed eu est non velit facilisis facilisis vitae eget ante. Nunc ut malesuada erat. Nullam sagittis bibendum porta. Maecenas vitae interdum sapien, ut aliquet risus. Donec aliquet, turpis finibus aliquet bibendum, tellus dui porttitor quam, quis pellentesque tellus libero non urna. Vestibulum maximus pharetra congue. Suspendisse aliquam congue quam, sed bibendum turpis. Aliquam eu enim ligula. Nam vel magna ut urna cursus sagittis. Suspendisse a nisi ac justo ornare tempor vel eu eros." - }, - brand: { - dataType: "string", - title: "Brand", - validation: { - required: true - } - }, - description: { - dataType: "string", - title: "Description", - description: "Example of a markdown field", - config: { - markdown: true - } - }, - available: { - dataType: "boolean", - title: "Available", - columnWidth: 100 - }, - amazon_link: { - dataType: "string", - title: "Amazon link", - config: { - url: true - } - }, - added_on: { - dataType: "timestamp", - title: "Added on", - disabled: true, - serverTimestamp: "on_create" - }, - images: { - dataType: "array", - title: "Images", - of: { - dataType: "string", - config: { - storageMeta: { - mediaType: "image", - storagePath: "images", - acceptedFiles: ["image/*"], - metadata: { - cacheControl: "max-age=1000000" - } - } - } - }, - description: "This fields allows uploading multiple images at once" - }, - related_products: { - dataType: "array", - title: "Related products", - description: "Reference to self", - of: { - dataType: "reference", - collectionPath: "products", - schema: "self" - } - }, - publisher: { - title: "Publisher", - description: "This is an example of a map property", - dataType: "map", - properties: { - name: { - title: "Name", - dataType: "string" - }, - external_id: { - title: "External id", - dataType: "string" - } - } - }, - min_known_price: { - dataType: "number", - title: "Min known price", - disabled: true, - description: "Minimum price this product has ever had" - }, - prime_eligible: { - dataType: "boolean", - title: "Prime eligible", - disabled: true - }, - available_locales: { - title: "Available locales", - description: - "This is an example of a disabled field that gets updated trough a Cloud Function, try changing a locale 'selectable' value", - longDescription: "Example of a long description hidden under a tooltip. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin quis bibendum turpis. Sed scelerisque ligula nec nisi pellentesque, eget viverra lorem facilisis. Praesent a lectus ac ipsum tincidunt posuere vitae non risus. In eu feugiat massa. Sed eu est non velit facilisis facilisis vitae eget ante. Nunc ut malesuada erat. Nullam sagittis bibendum porta. Maecenas vitae interdum sapien, ut aliquet risus. Donec aliquet, turpis finibus aliquet bibendum, tellus dui porttitor quam, quis pellentesque tellus libero non urna. Vestibulum maximus pharetra congue. Suspendisse aliquam congue quam, sed bibendum turpis. Aliquam eu enim ligula. Nam vel magna ut urna cursus sagittis. Suspendisse a nisi ac justo ornare tempor vel eu eros.", - dataType: "array", - disabled: true, - of: { - dataType: "string", - config: { - enumValues: locales - } - } - }, - uppercase_name: { - title: "Uppercase Name", - dataType: "string", - disabled: true, - description: "This field gets updated with a preSave hook" - } - - }, - defaultValues: { - currency: "EUR", - publisher: { - name: "Default publisher" - } - } -}); - - -const blogSchema = buildSchema({ - name: "Blog entry", - properties: { - name: { - title: "Name", - validation: { required: true }, - dataType: "string" - }, - gold_text: { - title: "Gold text", - description: "This field is using a custom component defined by the developer", - dataType: "string", - config: { - field: CustomColorTextField, - fieldProps: { - color: "gold" - } - } - }, - long_text: { - title: "Long text", - description: "Example of a long text", - validation: { required: true }, - dataType: "string", - config: { - multiline: true - } - }, - images: { - title: "Images", - dataType: "array", - of: { - dataType: "string", - config: { - storageMeta: { - mediaType: "image", - storagePath: "images", - acceptedFiles: ["image/*"], - metadata: { - cacheControl: "max-age=1000000" - } - } - } - }, - description: "This fields allows uploading multiple images at once and reordering" - }, - priority: { - title: "Priority", - description: "This field allows the selection of Infinity as a value", - dataType: "number", - config: { - fieldProps: { - allowInfinity: true - } - } - }, - reviewed: { - title: "Reviewed", - dataType: "boolean", - config: { - customPreview: CustomBooleanPreview - } - }, - status: { - title: "Status", - validation: { required: true }, - dataType: "string", - config: { - enumValues: { - published: "Published", - draft: "Draft" - } - } - }, - content: { - title: "Content", - validation: { required: true }, - dataType: "array", - of: { - dataType: "string" - } - }, - products: { - title: "Products", - validation: { required: true }, - dataType: "array", - of: { - dataType: "reference", - collectionPath: "products", - schema: productSchema, - textSearchDelegate: productsSearchDelegate, - previewProperties: ["name", "main_image"] - } - }, - tags: { - title: "Tags", - description: "Example of generic array", - dataType: "array", - of: { - dataType: "string", - config: { - previewAsTag: true - } - } - } - }, - defaultValues: { - status: "draft", - tags: ["default tag"] - } -}); - -const usersSchema = buildSchema({ - name: "User", - properties: { - first_name: { - title: "First name", - dataType: "string" - }, - last_name: { - title: "Last name", - dataType: "string" - }, - email: { - title: "Email", - dataType: "string", - validation: { - email: true - } - }, - phone: { - title: "Phone", - dataType: "string" - }, - picture: { - title: "Picture", - dataType: "map", - properties: { - large: { - title: "Large", - dataType: "string", - config: { - url: "image" - }, - validation: { - url: true - } - }, - thumbnail: { - title: "Thumbnail", - dataType: "string", - config: { - url: "image" - }, - validation: { - url: true - } - } - }, - previewProperties: ["large"] - } - } -}); - - -const productAdditionalColumn: AdditionalColumnDelegate = { - id: "spanish_title", - title: "Spanish title", - builder: (entity) => - snapshot.get("name") as string) - }/> -}; - - -const localeSchema = buildSchema({ - customId: locales, - name: "Locale", - properties: { - name: { - title: "Name", - validation: { required: true }, - dataType: "string" - }, - description: { - title: "Description", - validation: { required: true }, - dataType: "string", - config: { - multiline: true - } - }, - selectable: { - title: "Selectable", - description: "Is this locale selectable", - longDescription: "Changing this value triggers a cloud function that updates the parent product", - dataType: "boolean" - }, - video: { - title: "Video", - dataType: "string", - validation: { required: false }, - config: { - storageMeta: { - mediaType: "video", - storagePath: "videos", - acceptedFiles: ["video/*"] - } - }, - columnWidth: 400 - } - } -}); - -productSchema.onPreSave = ({ - schema, - collectionPath, - id, - values, - status - }: EntitySaveProps) => { - values.uppercase_name = values.name.toUpperCase(); - return values; -}; - -productSchema.onSaveSuccess = (props) => { - console.log("onSaveSuccess", props); -}; - -productSchema.onDelete = (props) => { - console.log("onDelete", props); -}; - -const formQuestions: string[] = ["birth_year", - "living_situation", - "electricity_monthly", - "heating_fuels_used", - "heating_fuels_consumption_monthly"]; - -export const testEntitySchema = buildSchema({ - customId: true, - name: "Test entity", - properties: { - available_dates: { - dataType: "array", - title: "Available Dates", - validation: { - required: true - }, - of:{ - dataType: "timestamp", - } - }, - images: { - title: "Image URLs", - dataType: "array", - of: { - dataType: "string", - config: { - storageMeta: { - mediaType: "image", - storagePath: "images", - acceptedFiles: ["image/*"] - } - } - } - }, - image: { - title: "Image", - dataType: "string", - config: { - storageMeta: { - mediaType: "image", - storagePath: "test", - acceptedFiles: ["image/*"] - } - } - }, - mark: { - title: "Mark", - dataType: "string", - config: { - markdown: true - } - }, - tags: { - title: "Tags", - dataType: "array", - of: { - dataType: "string" - } - }, - product: { - title: "Product", - dataType: "reference", - collectionPath: "products", - schema: productSchema, - textSearchDelegate: productsSearchDelegate, - previewProperties: ["name", "main_image"] - }, - created_at: { - title: "Created at", - dataType: "timestamp", - autoValue: "on_create" - }, - updated_on: { - title: "Updated on", - dataType: "timestamp", - autoValue: "on_update" - }, - title: { - title: "Title", - validation: { required: true }, - description: "A catching title is important", - dataType: "string" - }, - description: { - title: "Description", - dataType: "string", - config: { - multiline: true - } - }, - search_adjacent: { - title: "Search adjacent", - dataType: "boolean" - }, - difficulty: { - title: "Difficulty", - dataType: "number", - validation: { - required: true - } - }, - range: { - title: "Range", - validation: { - min: 0, - max: 3 - }, - dataType: "number" - }, - pdf: { - title: "Pdf", - dataType: "string", - config: { - storageMeta: { - storagePath: "test" - } - } - }, - form_conditions: { - dataType: "array", - title: "Form conditions", - of: { - dataType: "map", - properties: { - type: { - dataType: "string", - title: "Type", - config: { - enumValues: { - possible_ids: "Possible ids" - } - }, - validation: { - required: true - } - }, - condition: { - dataType: "map", - title: "Condition", - config: { - pickOnlySomeKeys: true - }, - properties: formQuestions.map((questionId) => ({ - [questionId]: { - dataType: "array", - title: questionId, - of: { - dataType: "string", - validation: { - required: true - } - } - } - } as Properties)).reduce((a, b) => ({ ...a, ...b })) - } - } - } - } - } -}); - -const localeCollection: EntityCollectionView = - buildCollection({ - name: "Locales", - relativePath: "locales", - deleteEnabled: true, - schema: localeSchema, - defaultSize: "l" - }) -; - -const productsCollection = buildCollection({ - relativePath: "products", - schema: productSchema, - name: "Products", - textSearchDelegate: productsSearchDelegate, - additionalColumns: [productAdditionalColumn], - subcollections: [localeCollection], - excludedProperties: ["images", "related_products"], - filterableProperties: ["price", "available_locales"] -}); - -const usersCollection = buildCollection({ - relativePath: "users", - schema: usersSchema, - name: "Users", - textSearchDelegate: usersSearchDelegate, - properties: ["first_name", "last_name", "email", "phone", "picture"] -}); - -const blogCollection = buildCollection({ - relativePath: "blog", - schema: blogSchema, - name: "Blog", - group: "Content", - textSearchDelegate: blogSearchDelegate, - properties: ["name", "images", "status", "reviewed", "products", "long_text"], - filterableProperties: ["name", "status"], - initialFilter: { - "status": ["==", "published"] - } -}); - -const navigation: EntityCollectionView[] = [ - productsCollection, - usersCollection, - blogCollection -]; - -if (process.env.NODE_ENV !== "production") { - navigation.push(buildCollection({ - relativePath: "test_entity", - schema: testEntitySchema, - group: "Test group", - name: "Test entity", - filterableProperties: ["difficulty", "search_adjacent", "description"], - subcollections: [{ - relativePath: "test_subcollection", - schema: testEntitySchema, - name: "Test entity", - filterableProperties: ["difficulty", "search_adjacent", "description"] - }] - })); -} - -const myAuthenticator: Authenticator = (user?: firebase.User) => { - console.log("Allowing access to", user?.email); - return true; -}; - -const githubLink = ( - - - - - -); - -const additionalViews: AdditionalView[] = [{ - path: "additional", - name: "Additional", - group: "Content", - view: -}]; +import App from "./App"; ReactDOM.render( - , + + + , document.getElementById("root") ); - -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA -// serviceWorker.register(); -serviceWorker.unregister(); - - diff --git a/example/src/serviceWorker.ts b/example/src/serviceWorker.ts deleted file mode 100644 index b3793f8..0000000 --- a/example/src/serviceWorker.ts +++ /dev/null @@ -1,143 +0,0 @@ -// This optional code is used to register a service worker. -// register() is not called by default. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on subsequent visits to a page, after all the -// existing tabs open on the page have been closed, since previously cached -// resources are updated in the background. - -// To learn more about the benefits of this model and instructions on how to -// opt-in, read https://bit.ly/CRA-PWA - -const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); - -type Config = { - onSuccess?: (registration: ServiceWorkerRegistration) => void; - onUpdate?: (registration: ServiceWorkerRegistration) => void; -}; - -export function register(config?: Config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL( - (process as { env: { [key: string]: string } }).env.PUBLIC_URL, - window.location.href - ); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } - - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); - - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ); - }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } -} - -function registerValidSW(swUrl: string, config?: Config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ); - - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); - - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); -} - -function checkValidServiceWorker(swUrl: string, config?: Config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); -} - -export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); - } -} diff --git a/example/tsconfig.json b/example/tsconfig.json index a27fd20..27186b9 100644 --- a/example/tsconfig.json +++ b/example/tsconfig.json @@ -20,9 +20,6 @@ "noEmit": true, "jsx": "react-jsx" }, - "setupFiles": [ - "core-js" - ], "include": [ "src" ] diff --git a/example/yarn.lock b/example/yarn.lock index 2449f06..d281a95 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -2243,6 +2243,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash@^4.14.165": + version "4.14.165" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.165.tgz#74d55d947452e2de0742bad65270433b63a8c30f" + integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg== + "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" @@ -2300,12 +2305,12 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== -"@types/react-dom@^16.9.5": - version "16.9.10" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f" - integrity sha512-ItatOrnXDMAYpv6G8UCk2VhbYVTjZT9aorLtA/OzDN9XJ2GKcfam68jutoAcILdRjsRUO8qb7AmyObF77Q8QFw== +"@types/react-dom@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.0.tgz#b3b691eb956c4b3401777ee67b900cb28415d95a" + integrity sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g== dependencies: - "@types/react" "^16" + "@types/react" "*" "@types/react-measure@^2.0.6": version "2.0.6" @@ -2314,7 +2319,7 @@ dependencies: "@types/react" "*" -"@types/react-router-dom@^5.1.3": +"@types/react-router-dom@^5.1.6": version "5.1.6" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.6.tgz#07b14e7ab1893a837c8565634960dc398564b1fb" integrity sha512-gjrxYqxz37zWEdMVvQtWPFMFj1dRDb4TGOcgyOfSXTrEXdF92L00WE3C471O3TV/RF1oskcStkXsOU0Ete4s/g== @@ -2338,7 +2343,7 @@ dependencies: "@types/react" "*" -"@types/react@*": +"@types/react@*", "@types/react@^17.0.0": version "17.0.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8" integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw== @@ -2346,14 +2351,6 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@types/react@^16", "@types/react@^16.9.23": - version "16.14.2" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c" - integrity sha512-BzzcAlyDxXl2nANlabtT4thtvbbnhee8hMmH/CcJrISDBVcJS1iOsP1f0OAgSdGE0MsY9tqcrb9YoZcOFv9dbQ== - dependencies: - "@types/prop-types" "*" - csstype "^3.0.2" - "@types/resolve@0.0.8": version "0.0.8" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" @@ -5793,11 +5790,6 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -fn-name@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c" - integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA== - follow-redirects@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" @@ -8459,6 +8451,11 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== + nanoid@^3.1.20: version "3.1.20" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" @@ -9967,7 +9964,7 @@ prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" -property-expr@^2.0.2: +property-expr@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910" integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg== @@ -11598,11 +11595,6 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -synchronous-promise@^2.0.13: - version "2.0.15" - resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.15.tgz#07ca1822b9de0001f5ff73595f3d08c4f720eb8e" - integrity sha512-k8uzYIkIVwmT+TcglpdN50pS2y1BDcUnBPK9iJeGu0Pl1lOI8pD6wtzgw91Pjpe+RxtTncw32tLxs/R0yNL2Mg== - table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -11968,15 +11960,15 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeface-roboto@^0.0.75: - version "0.0.75" - resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.75.tgz#98d5ba35ec234bbc7172374c8297277099cc712b" - integrity sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg== +typeface-roboto@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-1.1.13.tgz#9c4517cb91e311706c74823e857b4bac9a764ae5" + integrity sha512-YXvbd3a1QTREoD+FJoEkl0VQNJoEjewR2H11IjVv4bp6ahuIcw0yyw/3udC4vJkHw3T3cUh85FTg8eWef3pSaw== -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== +typeface-rubik@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/typeface-rubik/-/typeface-rubik-1.1.13.tgz#c01d40f05718590bc05d08ea404666a8ef56377a" + integrity sha512-mMWgn3bYfT6V4WWA3yvdEM3AkTA7iK1XwhlOX3+vWfqHc6O9I2RUsi9krEXzNdQ1UE/eoEFBRigMtu30PsdEDg== typeface-space-mono@^1.1.13: version "1.1.13" @@ -12883,15 +12875,15 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -yup@^0.29.3: - version "0.29.3" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea" - integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ== +yup@^0.32.8: + version "0.32.8" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.8.tgz#16e4a949a86a69505abf99fd0941305ac9adfc39" + integrity sha512-SZulv5FIZ9d5H99EN5tRCRPXL0eyoYxWIP1AacCrjC9d4DfP13J1dROdKGfpfRHT3eQB6/ikBl5jG21smAfCkA== dependencies: "@babel/runtime" "^7.10.5" - fn-name "~3.0.0" - lodash "^4.17.15" + "@types/lodash" "^4.14.165" + lodash "^4.17.20" lodash-es "^4.17.11" - property-expr "^2.0.2" - synchronous-promise "^2.0.13" + nanoclone "^0.2.1" + property-expr "^2.0.4" toposort "^2.0.2" diff --git a/package.json b/package.json index 625d75a..713c094 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,12 @@ "react-dom": "^17.0.1", "react-scripts": "^4.0.1" }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, "devDependencies": { "@types/jest": "^26.0.14", "@types/node": "^14.14.14", diff --git a/src/CMSApp.tsx b/src/CMSApp.tsx index 71b5985..331da7d 100644 --- a/src/CMSApp.tsx +++ b/src/CMSApp.tsx @@ -183,19 +183,24 @@ export function CMSApp(props: CMSAppProps) { = authentication instanceof Function ? authentication : undefined; function initFirebase(config: Object) { - try { - firebase.initializeApp(config); - firebase.analytics(); - setUsedFirebaseConfig(config); - setFirebaseConfigError(false); - setFirebaseConfigInitialized(true); - } catch (e) { - console.error(e); - setFirebaseConfigError(true); - } + if (firebase.apps.length === 0) + try { + firebase.initializeApp(config); + firebase.analytics(); + setUsedFirebaseConfig(config); + setFirebaseConfigError(false); + setFirebaseConfigInitialized(true); + } catch (e) { + console.error(e); + setFirebaseConfigError(true); + } } useEffect(() => { + + if (firebase.apps.length > 0) + return; + if (firebaseConfig) { initFirebase(firebaseConfig); } else if (process.env.NODE_ENV === "production") { diff --git a/src/form/EntityForm.tsx b/src/form/EntityForm.tsx index 6a65e52..6244a82 100644 --- a/src/form/EntityForm.tsx +++ b/src/form/EntityForm.tsx @@ -194,6 +194,7 @@ function EntityForm({ ; } + console.log("baseValues", baseValues); return ( } @@ -204,6 +205,8 @@ function EntityForm({ > {({ values, touched, dirty, setFieldValue, setFieldTouched, handleSubmit, isSubmitting }) => { + // console.log("touched", touched, dirty); + useEffect(() => { onModified(dirty); }, [dirty]); diff --git a/src/form/fields/ArrayDefaultField.tsx b/src/form/fields/ArrayDefaultField.tsx index 2407eaf..0be254d 100644 --- a/src/form/fields/ArrayDefaultField.tsx +++ b/src/form/fields/ArrayDefaultField.tsx @@ -53,7 +53,9 @@ export default function ArrayDefaultField({ const fieldError = getIn(errors, field.name); - const showError = getIn(touched, field.name) && fieldError && !!fieldError.filter((e: any) => !!e).length; + const showError = getIn(touched, field.name) + && fieldError + && (!Array.isArray(fieldError) || !!fieldError.filter((e: any) => !!e).length); const ofProperty: Property = property.of as Property; const classes = formStyles(); @@ -139,6 +141,10 @@ export default function ArrayDefaultField({ {includeDescription && } + {showError + && typeof fieldError === "string" + && {fieldError}} + ); }} diff --git a/src/form/fields/StorageUploadField.tsx b/src/form/fields/StorageUploadField.tsx index 199e822..2972163 100644 --- a/src/form/fields/StorageUploadField.tsx +++ b/src/form/fields/StorageUploadField.tsx @@ -9,12 +9,11 @@ import { LinearProgress, makeStyles, Paper, - RootRef, Typography } from "@material-ui/core"; import { getDownloadURL, uploadFile } from "../../firebase"; -import firebase from 'firebase/app'; +import firebase from "firebase/app"; import { @@ -345,89 +344,85 @@ export function StorageUpload({ } ); - const { ref, ...rootProps } = getRootProps(); + const { ...rootProps } = getRootProps(); const helpText = multipleFilesSupported ? "Drag 'n' drop some files here, or click to select files" : "Drag 'n' drop a file here, or click to select one"; return ( +
- + -
+ - - - - - {internalValue.map((entry, index) => { - let child; - if (entry.storagePathOrDownloadUrl) { - const renderProperty = multipleFilesSupported - ? (property as ArrayProperty).of as Property - : property; - child = ( - - ); - } else if (entry.file) { - child = ( - - ); - } - - return - {child} - ; - }) + {internalValue.map((entry, index) => { + let child; + if (entry.storagePathOrDownloadUrl) { + const renderProperty = multipleFilesSupported + ? (property as ArrayProperty).of as Property + : property; + child = ( + + ); + } else if (entry.file) { + child = ( + + ); } - - - {helpText} - - + return + {child} + ; + }) + } + + + {helpText} + -
-
+ + +
); } diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5..0000000 --- a/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/src/routes/EntityFormRoute.tsx b/src/routes/EntityFormRoute.tsx index 6188fda..c31abda 100644 --- a/src/routes/EntityFormRoute.tsx +++ b/src/routes/EntityFormRoute.tsx @@ -334,15 +334,17 @@ function EntityFormRoute({ const containerRef = React.useRef(null); - const form = ; + const form = ( + + ); const subCollectionsView = view.subcollections && view.subcollections.map( (subcollectionView, colIndex) => { diff --git a/tsconfig.json b/tsconfig.json index ee107fa..5fa49e3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "module": "esnext", "lib": [ "dom", + "dom.iterable", "esnext" ], "moduleResolution": "node", @@ -28,7 +29,8 @@ "noEmit": true }, "include": [ - "src" + "src", + "./src/**/*.ts" ], "exclude": [ "node_modules",