diff --git a/independent/webiny-cli/src/init/template/packages/admin/src/config/development.js b/independent/webiny-cli/src/init/template/packages/admin/src/config/development.js index bb5ac1d1ae1..daceef5a53a 100644 --- a/independent/webiny-cli/src/init/template/packages/admin/src/config/development.js +++ b/independent/webiny-cli/src/init/template/packages/admin/src/config/development.js @@ -33,16 +33,5 @@ export default { errorPolicy: "all" } } - }), - components: { - Image: { - presets: { - avatar: { width: 128 } - }, - plugin: "image-component" - }, - withFileUpload: { - plugin: ["with-file-upload", { uri: "/files" }] - } - } + }) }; diff --git a/independent/webiny-cli/src/init/template/packages/admin/src/config/production.js b/independent/webiny-cli/src/init/template/packages/admin/src/config/production.js index f3a7e8e871e..72e65458024 100644 --- a/independent/webiny-cli/src/init/template/packages/admin/src/config/production.js +++ b/independent/webiny-cli/src/init/template/packages/admin/src/config/production.js @@ -23,16 +23,5 @@ export default { addTypename: true, dataIdFromObject: obj => obj.id || null }) - }), - components: { - Image: { - presets: { - avatar: { width: 128 } - }, - plugin: "image-component" - }, - withFileUpload: { - plugin: ["with-file-upload", { uri: "/files" }] - } - } + }) }; diff --git a/independent/webiny-integration-cookie-policy/src/plugins/admin/components/CookiePolicySettings.js b/independent/webiny-integration-cookie-policy/src/plugins/admin/components/CookiePolicySettings.js index 1c639fce3f6..fe7183ac6e5 100644 --- a/independent/webiny-integration-cookie-policy/src/plugins/admin/components/CookiePolicySettings.js +++ b/independent/webiny-integration-cookie-policy/src/plugins/admin/components/CookiePolicySettings.js @@ -11,6 +11,7 @@ import { withSnackbar } from "webiny-admin/components"; import { RadioGroup, Radio } from "webiny-ui/Radio"; import graphql from "./graphql"; import showCookiePolicy from "./../../utils/showCookiePolicy"; +import { CircularProgress } from "webiny-ui/Progress"; import { SimpleForm, @@ -29,9 +30,9 @@ const positionOptions = [ const CookiePolicySettings = ({ showSnackbar }) => { return ( - {({ data }) => ( + {({ data, loading: queryInProgress }) => ( - {update => ( + {(update, { loading: mutationInProgress }) => (
{ @@ -45,6 +46,9 @@ const CookiePolicySettings = ({ showSnackbar }) => { > {({ Bind, form, data }) => ( + {(queryInProgress || mutationInProgress) && ( + + )} { return ( - {({ data }) => ( + {({ data, loading: queryInProgress }) => ( - {update => ( + {(update, { loading: mutationInProgress }) => ( { @@ -35,6 +36,9 @@ const GoogleTagManagerSettings = ({ showSnackbar }) => { > {({ Bind, form, data }) => ( + {(queryInProgress || mutationInProgress) && ( + + )} { return ( - {({ data }) => ( + {({ data, loading: queryInProgress }) => ( - {update => ( + {(update, { loading: mutationInProgress }) => ( { @@ -35,6 +36,9 @@ const MailchimpSettings = ({ showSnackbar }) => { > {({ Bind, form, data }) => ( + {(queryInProgress || mutationInProgress) && ( + + )} void, data: Object, error: Object | null @@ -70,12 +71,14 @@ const withSaveHandler = ({ create, update, response, variables, snackbar }): Fun return compose( setDisplayName("saveHandler"), withState("invalidFields", "setInvalidFields", {}), + withState("mutationInProgress", "setMutationInProgress", false), graphql(create, { name: "createRecord" }), graphql(update, { name: "updateRecord" }), withHandlers({ saveRecord: ({ createRecord, updateRecord, + setMutationInProgress, setInvalidFields, showSnackbar, showDialog, @@ -83,6 +86,7 @@ const withSaveHandler = ({ create, update, response, variables, snackbar }): Fun dataList, id }: Object) => { + setMutationInProgress(true); return async (formData: Object) => { // Reset errors setInvalidFields(null); @@ -91,24 +95,29 @@ const withSaveHandler = ({ create, update, response, variables, snackbar }): Fun const operation = id ? updateRecord({ variables: { id, ...gqlVariables } }) : createRecord({ variables: gqlVariables }); - return operation.then(res => { - const { data, error } = response(res.data); - if (error) { - if (error.code === "INVALID_ATTRIBUTES") { - showSnackbar("Some of your form input is incorrect!"); - setInvalidFields(error.data.invalidAttributes); - return; - } else { - showDialog(error.message, { - title: "Something unexpected happened" - }); - return; + return operation + .then(res => { + const { data, error } = response(res.data); + if (error) { + if (error.code === "INVALID_ATTRIBUTES") { + showSnackbar("Some of your form input is incorrect!"); + setInvalidFields(error.data.invalidAttributes); + return; + } else { + showDialog(error.message, { + title: "Something unexpected happened" + }); + return; + } } - } - showSnackbar(snackbar(data)); - router.goToRoute({ params: { id: data.id }, merge: true }); - !id && dataList.refresh(); - }); + showSnackbar(snackbar(data)); + router.goToRoute({ params: { id: data.id }, merge: true }); + !id && dataList.refresh(); + }) + .then(res => { + setMutationInProgress(false); + return res; + }); }; } }) @@ -163,6 +172,7 @@ export const withCrud = ({ list, form }: Object): Function => { dataList, saveRecord, formData, + mutationInProgress, invalidFields, showSnackbar, showDialog, @@ -184,6 +194,7 @@ export const withCrud = ({ list, form }: Object): Function => { ...formData, invalidFields, onSubmit: saveRecord, + loading: formData && formData.loading || mutationInProgress, router, showSnackbar, showDialog diff --git a/packages/webiny-admin/src/components/withCrud/getFormData.js b/packages/webiny-admin/src/components/withCrud/getFormData.js index 48e90d787d1..35940f6e7a0 100644 --- a/packages/webiny-admin/src/components/withCrud/getFormData.js +++ b/packages/webiny-admin/src/components/withCrud/getFormData.js @@ -31,6 +31,7 @@ const process = ({ data, form }: Object) => { const getFormData = ({ data, form }: Object) => { const formData = process({ data, form }); return { + loading: get(data, "loading") || false, data: get(formData, "data") || {}, error: get(formData, "error") || null }; diff --git a/packages/webiny-api-cms/src/entities/CmsSettings.entity.js b/packages/webiny-api-cms/src/entities/CmsSettings.entity.js index 287b53dadb7..e15fd56c261 100644 --- a/packages/webiny-api-cms/src/entities/CmsSettings.entity.js +++ b/packages/webiny-api-cms/src/entities/CmsSettings.entity.js @@ -3,12 +3,13 @@ import { settingsFactory } from "webiny-api/entities"; import { Model } from "webiny-model"; import FileModel from "./File.model"; -class SocialMedia extends Model { +class SocialMediaModel extends Model { constructor() { super(); this.attr("facebook").char(); this.attr("twitter").char(); this.attr("instagram").char(); + this.attr("image").model(FileModel); } } @@ -31,7 +32,7 @@ const cmsSettingsModelFactory = () => { this.attr("domain").char(); this.attr("favicon").model(FileModel); this.attr("logo").model(FileModel); - this.attr("social").model(SocialMedia); + this.attr("social").model(SocialMediaModel); } }; }; diff --git a/packages/webiny-api-cms/src/install/plugins/importData.js b/packages/webiny-api-cms/src/install/plugins/importData.js index 03d3e72b990..037dc1ce3fd 100644 --- a/packages/webiny-api-cms/src/install/plugins/importData.js +++ b/packages/webiny-api-cms/src/install/plugins/importData.js @@ -3,7 +3,7 @@ import setupEntities from "./setupEntities"; import createDefaultPages from "./importData/createDefaultPages"; import createDefaultBlocks from "./importData/createDefaultBlocks"; import * as data from "./data"; - +import {get} from "lodash"; export default async (context: Object) => { setupEntities(context); const { Category, Menu, CmsSettings } = context.cms.entities; @@ -65,7 +65,7 @@ export default async (context: Object) => { // Settings init. const cmsSettings = new CmsSettings(); await createDefaultPages(context, { categories, cmsSettings }); - cmsSettings.data.domain = "http://localhost:3002"; + cmsSettings.data.domain = get(context, "cms.siteUrl"); await cmsSettings.save(); }; diff --git a/packages/webiny-api-cms/src/plugins/settings/index.js b/packages/webiny-api-cms/src/plugins/settings/index.js index 784fb04e3d6..993caeca128 100644 --- a/packages/webiny-api-cms/src/plugins/settings/index.js +++ b/packages/webiny-api-cms/src/plugins/settings/index.js @@ -11,6 +11,7 @@ export default [ facebook: String twitter: String instagram: String + image: File } type CmsSettings { @@ -38,6 +39,7 @@ export default [ facebook: String twitter: String instagram: String + image: FileInput } input CmsDefaultPageInput { diff --git a/packages/webiny-app-cms/src/admin/plugins/routes.js b/packages/webiny-app-cms/src/admin/plugins/routes.js index 3e7010233d0..3cb7916fee6 100644 --- a/packages/webiny-app-cms/src/admin/plugins/routes.js +++ b/packages/webiny-app-cms/src/admin/plugins/routes.js @@ -2,7 +2,7 @@ import React from "react"; import AdminLayout from "webiny-admin/components/Layouts/AdminLayout"; import Categories from "webiny-app-cms/admin/views/Categories/Categories"; -// import Menus from "webiny-app-cms/admin/views/Menus/Menus"; +import Menus from "webiny-app-cms/admin/views/Menus/Menus"; import Pages from "webiny-app-cms/admin/views/Pages/Pages"; import Editor from "webiny-app-cms/admin/views/Pages/Editor"; import { SecureRoute } from "webiny-app-security/components"; @@ -29,7 +29,27 @@ export default [ } } }, - + { + name: "route-cms-menus", + type: "route", + route: { + name: "Cms.Menus", + path: "/cms/menus", + exact: true, + render() { + return ( + + + CMS - Menus + + + + + + ); + } + } + }, { name: "route-cms-pages", type: "route", diff --git a/packages/webiny-app-cms/src/admin/plugins/settings/components/CmsSettings.js b/packages/webiny-app-cms/src/admin/plugins/settings/components/CmsSettings.js index 538986391db..52459fd0337 100644 --- a/packages/webiny-app-cms/src/admin/plugins/settings/components/CmsSettings.js +++ b/packages/webiny-app-cms/src/admin/plugins/settings/components/CmsSettings.js @@ -7,6 +7,8 @@ import { Query, Mutation } from "react-apollo"; import { withSnackbar } from "webiny-admin/components"; import graphql from "./graphql"; import PagesAutoComplete from "./PagesAutoComplete"; +import { CircularProgress } from "webiny-ui/Progress"; +import Image from "./Image"; import { SimpleForm, @@ -15,71 +17,103 @@ import { SimpleFormHeader } from "webiny-admin/components/Views/SimpleForm"; -const CmsSettings = ({ showSnackbar }) => { - return ( - - {({ data }) => ( - - {update => ( - { - await update({ - variables: { - data: data.cms - } - }); - showSnackbar("Settings updated successfully."); - }} - > - {({ Bind, form }) => ( - - - - - - - - - - - - - - - - - - - - - - - - Save - - - - )} - - )} - - )} - - ); -}; +class CmsSettings extends React.Component { + render() { + const { showSnackbar } = this.props; + return ( + + {({ data, loading: queryInProgress }) => ( + + {(update, { loading: mutationInProgress }) => ( +
{ + this.setState({ loading: true }); + await update({ + variables: { + data: data.cms + } + }); + this.setState({ loading: false }); + + showSnackbar("Settings updated successfully."); + }} + > + {({ Bind, form }) => ( + + {(queryInProgress || mutationInProgress) && ( + + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Save + + + + )} +
+ )} +
+ )} +
+ ); + } +} export default withSnackbar()(CmsSettings); diff --git a/packages/webiny-app-cms/src/admin/plugins/settings/components/Image.js b/packages/webiny-app-cms/src/admin/plugins/settings/components/Image.js new file mode 100644 index 00000000000..3dae65f3209 --- /dev/null +++ b/packages/webiny-app-cms/src/admin/plugins/settings/components/Image.js @@ -0,0 +1,9 @@ +// @flow +import * as React from "react"; +import { withImageUpload, SingleImageUpload } from "webiny-app/components"; + +const Image = props => { + return ; +}; + +export default withImageUpload()(Image); diff --git a/packages/webiny-app-cms/src/admin/plugins/settings/components/generalSettings/GeneralSettings.js b/packages/webiny-app-cms/src/admin/plugins/settings/components/generalSettings/GeneralSettings.js index 5723a808a22..3bd46447120 100644 --- a/packages/webiny-app-cms/src/admin/plugins/settings/components/generalSettings/GeneralSettings.js +++ b/packages/webiny-app-cms/src/admin/plugins/settings/components/generalSettings/GeneralSettings.js @@ -8,6 +8,7 @@ import Image from "./Image"; import { Query, Mutation } from "react-apollo"; import { withSnackbar } from "webiny-admin/components"; import graphql from "./graphql"; +import { CircularProgress } from "webiny-ui/Progress"; import { SimpleForm, @@ -19,9 +20,9 @@ import { const GeneralSettings = ({ showSnackbar }) => { return ( - {({ data }) => ( + {({ data, loading: queryInProgress }) => ( - {update => ( + {(update, { loading: mutationInProgress }) => (
{ @@ -35,6 +36,9 @@ const GeneralSettings = ({ showSnackbar }) => { > {({ Bind, form }) => ( + {(queryInProgress || mutationInProgress) && ( + + )} diff --git a/packages/webiny-app-cms/src/admin/plugins/settings/components/graphql.js b/packages/webiny-app-cms/src/admin/plugins/settings/components/graphql.js index a57b59e03e3..dc880caa5a7 100644 --- a/packages/webiny-app-cms/src/admin/plugins/settings/components/graphql.js +++ b/packages/webiny-app-cms/src/admin/plugins/settings/components/graphql.js @@ -8,6 +8,11 @@ const fields = /* GraphQL */ ` notFound error } + social { + image { + src + } + } } `; diff --git a/packages/webiny-app-cms/src/admin/views/Categories/CategoriesForm.js b/packages/webiny-app-cms/src/admin/views/Categories/CategoriesForm.js index 5e560e95e45..7de2646041f 100644 --- a/packages/webiny-app-cms/src/admin/views/Categories/CategoriesForm.js +++ b/packages/webiny-app-cms/src/admin/views/Categories/CategoriesForm.js @@ -14,6 +14,7 @@ import { SimpleFormContent } from "webiny-admin/components/Views/SimpleForm"; import { categoryUrlValidator } from "./validators"; +import { CircularProgress } from "webiny-ui/Progress"; const t = i18n.namespace("Cms.CategoriesForm"); @@ -21,12 +22,14 @@ const CategoriesForm = ({ data, invalidFields, onSubmit, + loading, cms: { theme } }: WithCrudFormProps & { cms: WithCmsPropsType }) => { return ( {({ data, form, Bind }) => ( + {loading && } diff --git a/packages/webiny-app-cms/src/admin/views/Menus/MenusForm.js b/packages/webiny-app-cms/src/admin/views/Menus/MenusForm.js index 5bea05d181d..ed096321ed0 100644 --- a/packages/webiny-app-cms/src/admin/views/Menus/MenusForm.js +++ b/packages/webiny-app-cms/src/admin/views/Menus/MenusForm.js @@ -6,6 +6,8 @@ import { Input } from "webiny-ui/Input"; import { ButtonPrimary } from "webiny-ui/Button"; import MenuItems from "./MenusForm/MenuItems"; import type { WithCrudFormProps } from "webiny-admin/components"; +import { CircularProgress } from "webiny-ui/Progress"; + import { SimpleForm, SimpleFormFooter, @@ -19,7 +21,7 @@ type State = { class MenusForm extends React.Component { render() { - const { data, invalidFields, onSubmit } = this.props; + const { data, invalidFields, onSubmit, loading } = this.props; // TODO: onSubmit - remove attributes added by the Tree plugin @@ -27,6 +29,7 @@ class MenusForm extends React.Component { {({ data, form, Bind }) => ( + {loading && } diff --git a/packages/webiny-app-cms/src/editor/plugins/pageSettings/components/SocialSettings.js b/packages/webiny-app-cms/src/editor/plugins/pageSettings/components/SocialSettings.js index 60fb415679c..3f7be396e56 100644 --- a/packages/webiny-app-cms/src/editor/plugins/pageSettings/components/SocialSettings.js +++ b/packages/webiny-app-cms/src/editor/plugins/pageSettings/components/SocialSettings.js @@ -37,7 +37,15 @@ const SocialSettings = ({ Bind }: Object) => { - + diff --git a/packages/webiny-app-cms/src/site/components/Page.js b/packages/webiny-app-cms/src/site/components/Page.js index f543c096b43..6689c3353df 100644 --- a/packages/webiny-app-cms/src/site/components/Page.js +++ b/packages/webiny-app-cms/src/site/components/Page.js @@ -2,7 +2,7 @@ import * as React from "react"; import { Query } from "react-apollo"; import Loader from "./Loader"; -import { Content, buildQueryProps } from "./Page/index"; +import { Content, buildQueryProps, getSettings } from "./Page/index"; import { withCms } from "webiny-app-cms/context"; import type { WithCmsPropsType } from "webiny-app-cms/context"; import { get } from "lodash"; @@ -22,52 +22,66 @@ const NO_ERROR_PAGE_DEFAULT = const Page = ({ cms, match: { url, query } }: Props) => { return ( - - {({ data, error: gqlError, loading }) => { - if (loading) { - return ; - } + + {({ data: settings }) => ( + + {({ data, error: gqlError, loading }) => { + if (loading) { + return ; + } - if (gqlError) { - const Component = get(cms, "defaults.pages.error"); - invariant(Component, NO_ERROR_PAGE_DEFAULT); + if (gqlError) { + const Component = get(cms, "defaults.pages.error"); + invariant(Component, NO_ERROR_PAGE_DEFAULT); - return ; - } + return ; + } - // Not pretty, but "onComplete" callback executed too late. Will be executed only once. - if (!defaultPages.error) { - defaultPages.error = data.cms.errorPage; - } + // Not pretty, but "onComplete" callback executed too late. Will be executed only once. + if (!defaultPages.error) { + defaultPages.error = data.cms.errorPage; + } - if (!defaultPages.notFound) { - defaultPages.notFound = data.cms.notFoundPage; - } + if (!defaultPages.notFound) { + defaultPages.notFound = data.cms.notFoundPage; + } - const { data: page, error: pageError } = data.cms.page; + const { data: page, error: pageError } = data.cms.page; - if (page) { - return ; - } + if (page) { + return ; + } - if (pageError.code === "NOT_FOUND") { - if (defaultPages.notFound) { - return ; - } + if (pageError.code === "NOT_FOUND") { + if (defaultPages.notFound) { + return ( + + ); + } - const Component = get(cms, "defaults.pages.notFound"); - invariant(Component, NO_404_PAGE_DEFAULT); - return ; - } + const Component = get(cms, "defaults.pages.notFound"); + invariant(Component, NO_404_PAGE_DEFAULT); + return ; + } - if (defaultPages.error) { - return ; - } + if (defaultPages.error) { + return ( + + ); + } - const Component = get(cms, "defaults.pages.error"); - invariant(Component, NO_ERROR_PAGE_DEFAULT); - return ; - }} + const Component = get(cms, "defaults.pages.error"); + invariant(Component, NO_ERROR_PAGE_DEFAULT); + return ; + }} + + )} ); }; diff --git a/packages/webiny-app-cms/src/site/components/Page/Content.js b/packages/webiny-app-cms/src/site/components/Page/Content.js index 9ef56c3e9ce..806f7919f0d 100644 --- a/packages/webiny-app-cms/src/site/components/Page/Content.js +++ b/packages/webiny-app-cms/src/site/components/Page/Content.js @@ -1,54 +1,79 @@ // @flow import React from "react"; - import Element from "webiny-app-cms/render/components/Element"; import Layout from "./../Layout"; import { Helmet } from "react-helmet"; import { get } from "lodash"; -const Content = ({ page }: { page: Object }) => { - const seo = { - title: "", - description: "", - meta: [], - ...get(page, "settings.seo") - }; - - const social = { - title: "", - description: "", - image: null, - ...get(page, "settings.social") - }; - - return ( -
- - - {page.title && {page.title}} - {seo.title && } - {seo.description && } - {seo.meta.map(({ name, content }, index) => ( - - ))} - - {social.image && social.image.src && ( - - )} - - {social.title && } - {social.description && ( - - )} - {social.meta.map(({ property, content }, index) => ( - - ))} - - - - -
- ); -}; +type Props = { settings: Object, page: Object }; + +class Content extends React.Component { + renderOgImageMeta({ page, settings }: Object) { + let src = get(page, "social.image.src"); + if (!src) { + src = get(settings, "social.image.src"); + } + + return src ? : null; + } + + render() { + const { page, settings } = this.props; + + const meta = { + page: { + seo: { + title: "", + description: "", + meta: [], + ...get(page, "settings.seo") + }, + social: { + title: "", + description: "", + image: null, + ...get(page, "settings.social") + } + }, + settings: { + social: { + image: null, + ...get(settings, "cms.social") + } + } + }; + + return ( +
+ + + {page.title && {page.title}} + {meta.page.seo.title && } + {meta.page.seo.description && ( + + )} + {meta.page.seo.meta.map(({ name, content }, index) => ( + + ))} + + {this.renderOgImageMeta(meta)} + {meta.page.social.title && ( + + )} + + {meta.page.social.description && ( + + )} + {meta.page.social.meta.map(({ property, content }, index) => ( + + ))} + + + + +
+ ); + } +} export default Content; diff --git a/packages/webiny-app-cms/src/site/components/Page/getSettings.js b/packages/webiny-app-cms/src/site/components/Page/getSettings.js new file mode 100644 index 00000000000..64b37e0b62a --- /dev/null +++ b/packages/webiny-app-cms/src/site/components/Page/getSettings.js @@ -0,0 +1,16 @@ +// @flow +import gql from "graphql-tag"; + +export default gql` + { + settings { + cms { + social { + image { + src + } + } + } + } + } +`; diff --git a/packages/webiny-app-cms/src/site/components/Page/graphql/getDataFields.js b/packages/webiny-app-cms/src/site/components/Page/graphql/getDataFields.js index c421bbe0c2e..fa789d7c183 100644 --- a/packages/webiny-app-cms/src/site/components/Page/graphql/getDataFields.js +++ b/packages/webiny-app-cms/src/site/components/Page/graphql/getDataFields.js @@ -1,5 +1,5 @@ // @flow -import getSettingsFields from "./getSettingsFields"; +import getPageSettingsFields from "./getPageSettingsFields"; const getDataFields = () => { return /* GraphQL */ ` @@ -17,7 +17,7 @@ const getDataFields = () => { } settings { _empty - ${getSettingsFields()} + ${getPageSettingsFields()} } category { id diff --git a/packages/webiny-app-cms/src/site/components/Page/graphql/getSettingsFields.js b/packages/webiny-app-cms/src/site/components/Page/graphql/getPageSettingsFields.js similarity index 100% rename from packages/webiny-app-cms/src/site/components/Page/graphql/getSettingsFields.js rename to packages/webiny-app-cms/src/site/components/Page/graphql/getPageSettingsFields.js diff --git a/packages/webiny-app-cms/src/site/components/Page/graphql/index.js b/packages/webiny-app-cms/src/site/components/Page/graphql/index.js index c88a6decec6..c91b97d3588 100644 --- a/packages/webiny-app-cms/src/site/components/Page/graphql/index.js +++ b/packages/webiny-app-cms/src/site/components/Page/graphql/index.js @@ -2,4 +2,4 @@ export { default as getDataFields } from "./getDataFields"; export { default as getErrorPageFields } from "./getErrorPageFields"; export { default as getNotFoundPageFields } from "./getNotFoundPageFields"; -export { default as getSettingsFields } from "./getSettingsFields"; +export { default as getPageSettingsFields } from "./getPageSettingsFields"; diff --git a/packages/webiny-app-cms/src/site/components/Page/index.js b/packages/webiny-app-cms/src/site/components/Page/index.js index bd570a4b057..daa7586129c 100644 --- a/packages/webiny-app-cms/src/site/components/Page/index.js +++ b/packages/webiny-app-cms/src/site/components/Page/index.js @@ -1,3 +1,4 @@ // @flow export { default as buildQueryProps } from "./buildQueryProps"; +export { default as getSettings } from "./getSettings"; export { default as Content } from "./Content"; diff --git a/packages/webiny-app-security/src/admin/views/Account.js b/packages/webiny-app-security/src/admin/views/Account.js index ea6b6c74572..ffcfaf001cf 100644 --- a/packages/webiny-app-security/src/admin/views/Account.js +++ b/packages/webiny-app-security/src/admin/views/Account.js @@ -11,8 +11,9 @@ import { Grid, Cell } from "webiny-ui/Grid"; import { Input } from "webiny-ui/Input"; import { ButtonPrimary } from "webiny-ui/Button"; import { withSnackbar } from "webiny-admin/components"; -import { compose, withHandlers } from "recompose"; +import { compose, withHandlers, withState } from "recompose"; import AvatarImage from "./Users/AvatarImage"; +import { CircularProgress } from "webiny-ui/Progress"; import { SimpleForm, @@ -23,10 +24,11 @@ import { const t = i18n.namespace("Security.UsersForm"); -const UsersForm = ({ onSubmit, user }: Object) => ( +const UsersForm = ({ onSubmit, user, loading }: Object) => ( {({ data, form, Bind }) => ( + {loading && } @@ -116,6 +118,7 @@ const updateCurrentUser = gql` export default compose( withSnackbar(), withSecurity(), + withState("loading", "setLoading", null), graphql(getCurrentUser, { props: ({ data }) => ({ user: get(data, "security.getCurrentUser") || { data: {} } @@ -123,11 +126,13 @@ export default compose( }), graphql(updateCurrentUser, { name: "updateCurrentUser" }), withHandlers({ - onSubmit: ({ updateCurrentUser, showSnackbar }) => async formData => { + onSubmit: ({ setLoading, updateCurrentUser, showSnackbar }) => async formData => { + setLoading(true); const { data: response } = await updateCurrentUser({ variables: { data: pick(formData, ["email", "firstName", "lastName", "avatar"]) } }); const { error } = response.security.updateCurrentUser; + setLoading(false); if (error) { return showSnackbar(error.message, { actionText: "Close" diff --git a/packages/webiny-app-security/src/admin/views/Groups/GroupsForm.js b/packages/webiny-app-security/src/admin/views/Groups/GroupsForm.js index 7fedc9ea270..49189e89680 100644 --- a/packages/webiny-app-security/src/admin/views/Groups/GroupsForm.js +++ b/packages/webiny-app-security/src/admin/views/Groups/GroupsForm.js @@ -7,6 +7,7 @@ import { Input } from "webiny-ui/Input"; import { ButtonPrimary } from "webiny-ui/Button"; import RolesAutoComplete from "./../Components/RolesAutoComplete"; import type { WithCrudFormProps } from "webiny-admin/components"; +import { CircularProgress } from "webiny-ui/Progress"; import { SimpleForm, @@ -19,6 +20,7 @@ const t = i18n.namespace("Security.GroupsForm"); const GroupForm = ({ onSubmit, + loading, data, invalidFields }: WithCrudFormProps & { scopes: Array }) => { @@ -26,6 +28,7 @@ const GroupForm = ({ {({ data, form, Bind }) => ( + {loading && } diff --git a/packages/webiny-app-security/src/admin/views/Login.js b/packages/webiny-app-security/src/admin/views/Login.js index 0cb9c7764fe..23fb4abfe4e 100644 --- a/packages/webiny-app-security/src/admin/views/Login.js +++ b/packages/webiny-app-security/src/admin/views/Login.js @@ -21,10 +21,12 @@ import { } from "./Login/StyledComponents"; import logoOrange from "./../assets/images/logo_orange.png"; import { loginMutation } from "./Login/graphql"; +import { CircularProgress } from "webiny-ui/Progress"; const t = i18n.namespace("Webiny.Admin.Login"); -const Login = ({ login, error }: { login: Function, error?: Object }) => { +const Login = (props: Object) => { + const { login, error, loading }: { loading: boolean, login: Function, error?: Object } = props; return ( @@ -34,6 +36,7 @@ const Login = ({ login, error }: { login: Function, error?: Object }) => { + {loading && } <h1> <Typography use="headline4">{t`Sign In`}</Typography> @@ -103,14 +106,17 @@ const Login = ({ login, error }: { login: Function, error?: Object }) => { export default compose( graphql(loginMutation, { name: "doLogin" }), withState("error", "setError", null), + withState("loading", "setLoading", null), withHandlers({ - login: ({ doLogin, setError, onToken }) => { + login: ({ doLogin, setError, onToken, setLoading }) => { + setLoading(true); return async formData => { // Reset error setError(null); // Perform login const res = await doLogin({ variables: formData }); const { data, error } = res.data.security.loginUser; + setLoading(false); if (error) { return setError(error); } diff --git a/packages/webiny-app-security/src/admin/views/Login/StyledComponents.js b/packages/webiny-app-security/src/admin/views/Login/StyledComponents.js index 03e478c4ba8..e165cafcf3f 100644 --- a/packages/webiny-app-security/src/admin/views/Login/StyledComponents.js +++ b/packages/webiny-app-security/src/admin/views/Login/StyledComponents.js @@ -26,7 +26,8 @@ export const LoginContent = styled("div")({ }); export const InnerContent = styled("div")({ - padding: 25 + padding: 25, + position: "relative" }); export const Footer = styled("div")({ diff --git a/packages/webiny-app-security/src/admin/views/Roles/RolesForm.js b/packages/webiny-app-security/src/admin/views/Roles/RolesForm.js index f2e6d157c4c..258cb2fb33d 100644 --- a/packages/webiny-app-security/src/admin/views/Roles/RolesForm.js +++ b/packages/webiny-app-security/src/admin/views/Roles/RolesForm.js @@ -7,6 +7,7 @@ import { Input } from "webiny-ui/Input"; import { ButtonPrimary } from "webiny-ui/Button"; import { MultiAutoComplete } from "webiny-ui/AutoComplete"; import type { WithCrudFormProps } from "webiny-admin/components"; +import { CircularProgress } from "webiny-ui/Progress"; import { SimpleForm, @@ -19,6 +20,7 @@ const t = i18n.namespace("Security.RolesForm"); const RoleForm = ({ onSubmit, + loading, data, invalidFields, scopes @@ -27,6 +29,7 @@ const RoleForm = ({ <Form invalidFields={invalidFields} data={data} onSubmit={onSubmit}> {({ data, form, Bind }) => ( <SimpleForm> + {loading && <CircularProgress />} <SimpleFormHeader title={data.name ? data.name : "Untitled"} /> <SimpleFormContent> <Grid> diff --git a/packages/webiny-app-security/src/admin/views/Users/UsersForm.js b/packages/webiny-app-security/src/admin/views/Users/UsersForm.js index 678a9df7bda..0f186b37e71 100644 --- a/packages/webiny-app-security/src/admin/views/Users/UsersForm.js +++ b/packages/webiny-app-security/src/admin/views/Users/UsersForm.js @@ -4,6 +4,7 @@ import { i18n } from "webiny-app/i18n"; import { Form } from "webiny-form"; import { Grid, Cell } from "webiny-ui/Grid"; import { Input } from "webiny-ui/Input"; +import { CircularProgress } from "webiny-ui/Progress"; import { ButtonPrimary } from "webiny-ui/Button"; import GroupsAutoComplete from "./../Components/GroupsAutoComplete"; import RolesAutoComplete from "./../Components/RolesAutoComplete"; @@ -20,11 +21,12 @@ import type { WithCrudFormProps } from "webiny-admin/components"; const t = i18n.namespace("Security.UsersForm"); -const UsersForm = ({ onSubmit, data, invalidFields }: WithCrudFormProps) => { +const UsersForm = ({ onSubmit, data, invalidFields, loading }: WithCrudFormProps) => { return ( <Form invalidFields={invalidFields} data={data} onSubmit={onSubmit}> {({ data, form, Bind }) => ( <SimpleForm> + {loading && <CircularProgress />} <SimpleFormHeader title={data.fullName || "N/A"} /> <SimpleFormContent> <Grid> diff --git a/packages/webiny-app/src/components/Addons.js b/packages/webiny-app/src/components/Addons.js index 2bb9a502877..0c0e7dd6bda 100644 --- a/packages/webiny-app/src/components/Addons.js +++ b/packages/webiny-app/src/components/Addons.js @@ -13,13 +13,21 @@ export default class Addons extends React.Component<Props, State> { ready: false }; - constructor() { - super(); + componentMounted = false; + + componentDidMount() { + this.componentMounted = true; this.init().then(() => { - this.setState({ ready: true }); + if (this.componentMounted) { + this.setState({ ready: true }); + } }); } + componentWillUnmount() { + this.componentMounted = false; + } + async init() { const plugins = getPlugins("addon-render"); for (let i = 0; i < plugins.length; i++) { diff --git a/packages/webiny-entity-memory/package.json b/packages/webiny-entity-memory/package.json index 5b7515f00a4..be2f99eef8c 100644 --- a/packages/webiny-entity-memory/package.json +++ b/packages/webiny-entity-memory/package.json @@ -19,6 +19,8 @@ "webiny-entity": "0.0.0" }, "devDependencies": { + "chai": "^3.5.0", + "webiny-model": "0.0.0", "@babel/cli": "^7.0.0", "@babel/core": "^7.0.0", "@babel/preset-env": "^7.0.0", diff --git a/packages/webiny-entity-mongodb/package.json b/packages/webiny-entity-mongodb/package.json index 8f5e4631264..752adca640e 100644 --- a/packages/webiny-entity-mongodb/package.json +++ b/packages/webiny-entity-mongodb/package.json @@ -15,13 +15,16 @@ "dependencies": { "@babel/runtime": "^7.0.0", "lodash": "^4.17.4", - "mdbid": "^1.0.0" + "mdbid": "^1.0.0", + "webiny-entity": "0.0.0" }, "peerDependencies": { "mongodb": "^3.0.6" }, "devDependencies": { - "sinon": "^4.2.0" + "chai": "^3.5.0", + "sinon": "^4.2.0", + "webiny-model": "0.0.0" }, "scripts": { "build": "babel src -d ${DEST:-build} --source-maps --copy-files", diff --git a/packages/webiny-entity/package.json b/packages/webiny-entity/package.json index f93cb1d7741..d4c43f177d7 100644 --- a/packages/webiny-entity/package.json +++ b/packages/webiny-entity/package.json @@ -18,6 +18,7 @@ "webiny-model": "0.0.0" }, "devDependencies": { + "chai": "^3.5.0", "@babel/cli": "^7.0.0", "@babel/core": "^7.0.0", "@babel/preset-env": "^7.0.0", diff --git a/packages/webiny-ui/package.json b/packages/webiny-ui/package.json index ced2cd048a5..4a3f1ee0b9f 100644 --- a/packages/webiny-ui/package.json +++ b/packages/webiny-ui/package.json @@ -56,6 +56,7 @@ "react-custom-scrollbars": "^4.2.1", "react-emotion": "^9.2.6", "react-loading-skeleton": "^0.5.0", + "react-spinner-material": "^1.0.19", "react-transition-group": "^2.5.1", "react-visibility-sensor": "^5.0.2", "webiny-form": "0.0.0", diff --git a/packages/webiny-ui/src/ImageCropper/ImageCropper.js b/packages/webiny-ui/src/ImageCropper/ImageCropper.js deleted file mode 100644 index 0a373e2e123..00000000000 --- a/packages/webiny-ui/src/ImageCropper/ImageCropper.js +++ /dev/null @@ -1,47 +0,0 @@ -// @flow -import * as React from "react"; -import Cropper from "cropperjs"; -import "cropperjs/dist/cropper.css"; - -export type RenderPropParams = { - getImgProps: (props: ?Object) => Object, - getDataURL: () => ?string, - cropper: Cropper -}; - -type Props = { - children: RenderPropParams => React.Node -}; - -class ImageCropper extends React.Component<Props> { - imageRef = React.createRef(); - cropper: ?Cropper = null; - - componentDidMount() { - this.cropper = new Cropper(this.imageRef, this.props); - } - - componentWillUnmount() { - this.cropper && this.cropper.destroy(); - } - - render() { - return this.props.children({ - cropper: this.cropper, - getImgProps: (props: ?Object) => { - return { - ...props, - ref: ref => (this.imageRef = ref) - }; - }, - getDataURL: () => { - if (this.cropper) { - return this.cropper.getCroppedCanvas().toDataURL(); - } - return null; - } - }); - } -} - -export { ImageCropper }; diff --git a/packages/webiny-ui/src/ImageCropper/index.js b/packages/webiny-ui/src/ImageCropper/index.js deleted file mode 100644 index d1dbb406a59..00000000000 --- a/packages/webiny-ui/src/ImageCropper/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// @flow -export * from "./ImageCropper"; diff --git a/packages/webiny-ui/src/ImageEditor/ImageEditor.js b/packages/webiny-ui/src/ImageEditor/ImageEditor.js index 586dbdbeeb6..d117a48fb7e 100644 --- a/packages/webiny-ui/src/ImageEditor/ImageEditor.js +++ b/packages/webiny-ui/src/ImageEditor/ImageEditor.js @@ -20,6 +20,7 @@ type RenderPropArgs = { type Props = { src: string, tools: Array<ToolbarTool>, + options: ?Object, onToolActivate?: Function, onToolDeactivate?: Function, children?: RenderPropArgs => React.Node @@ -85,21 +86,23 @@ class ImageEditor extends React.Component<Props, State> { image = null; componentDidMount() { - initScripts().then(this.updateCanvas); - } - - componentDidUpdate(previousProps: Object) { - if (previousProps.src !== this.props.src) { - this.deactivateTool(); + initScripts().then(() => { this.updateCanvas(); - } + setTimeout(() => { + const { options } = this.props; + if (typeof options === "object" && options) { + for (let key in options) { + if (options[key].autoEnable === true) { + const tool: ?ImageEditorTool = toolbar[key]; + tool && this.activateTool(tool); + break; + } + } + } + }, 250); + }); } - resetCanvas = () => { - this.deactivateTool(); - this.updateCanvas(); - }; - updateCanvas = () => { const { src } = this.props; this.image = new window.Image(); @@ -118,8 +121,15 @@ class ImageEditor extends React.Component<Props, State> { } }; - activateTool = (tool: ImageEditorTool) => { - this.setState({ tool }); + activateTool = (tool: string | ImageEditorTool) => { + if (typeof tool === "string") { + tool = toolbar[tool]; + } + + this.setState({ tool }, () => { + typeof tool.onActivate === "function" && + tool.onActivate({ canvas: this.canvas, options: this.getToolOptions(tool) }); + }); }; deactivateTool = () => { @@ -166,8 +176,17 @@ class ImageEditor extends React.Component<Props, State> { this.deactivateTool(); }; + getToolOptions = (tool: ImageEditorTool) => { + const { options } = this.props; + if (!options || typeof options !== "object") { + return {}; + } + + return options[tool.name] || {}; + }; + render() { - const { src, tools, children } = this.props; + const { src, tools, children, options } = this.props; const { tool } = this.state; const editor = ( <React.Fragment> @@ -181,7 +200,6 @@ class ImageEditor extends React.Component<Props, State> { return ( <div key={key} className={classNames({ disabled: this.state.tool })}> {tool.icon({ - canvas: this.canvas, activateTool: () => this.activateTool(tool) })} </div> @@ -194,6 +212,7 @@ class ImageEditor extends React.Component<Props, State> { <> {typeof tool.renderForm === "function" && tool.renderForm({ + options: this.getToolOptions({ options, tool }), image: this.image, canvas: this.canvas })} diff --git a/packages/webiny-ui/src/ImageEditor/toolbar/crop.js b/packages/webiny-ui/src/ImageEditor/toolbar/crop.js index 7b6b6c4f437..feac9a1fb8a 100644 --- a/packages/webiny-ui/src/ImageEditor/toolbar/crop.js +++ b/packages/webiny-ui/src/ImageEditor/toolbar/crop.js @@ -19,20 +19,17 @@ const renderForm = () => { const tool: ImageEditorTool = { name: "crop", - icon(props) { + icon({ activateTool }) { return ( <Tooltip placement={"bottom"} content={"Crop"}> - <IconButton - icon={<CropIcon />} - onClick={() => { - cropper = new Cropper(props.canvas.current); - props.activateTool(); - }} - /> + <IconButton icon={<CropIcon />} onClick={activateTool} /> </Tooltip> ); }, renderForm, + onActivate: ({ canvas, options }) => { + cropper = new Cropper(canvas.current, options); + }, cancel: () => cropper && cropper.destroy(), apply: ({ canvas }) => { return new Promise(resolve => { @@ -58,6 +55,7 @@ const tool: ImageEditorTool = { } cropper.destroy(); + cropper = null; }); } }; diff --git a/packages/webiny-ui/src/ImageEditor/toolbar/flip.js b/packages/webiny-ui/src/ImageEditor/toolbar/flip.js index 7f57ce8cdb8..df911dc9c9e 100644 --- a/packages/webiny-ui/src/ImageEditor/toolbar/flip.js +++ b/packages/webiny-ui/src/ImageEditor/toolbar/flip.js @@ -45,28 +45,25 @@ const renderForm = () => { const tool: ImageEditorTool = { name: "flip", - icon({ canvas, activateTool }) { + icon({ activateTool }) { return ( <Tooltip placement={"bottom"} content={"Flip"}> - <IconButton - icon={<FlipIcon />} - onClick={() => { - cropper = new Cropper(canvas.current, { - background: false, - modal: false, - guides: false, - dragMode: "none", - highlight: false, - autoCrop: false - }); - activateTool(); - }} - /> + <IconButton icon={<FlipIcon />} onClick={activateTool} /> </Tooltip> ); }, renderForm, cancel: () => cropper && cropper.destroy(), + onActivate: ({ canvas }) => { + cropper = new Cropper(canvas.current, { + background: false, + modal: false, + guides: false, + dragMode: "none", + highlight: false, + autoCrop: false + }); + }, apply: ({ canvas }) => { return new Promise(resolve => { if (!cropper) { diff --git a/packages/webiny-ui/src/ImageEditor/toolbar/rotate.js b/packages/webiny-ui/src/ImageEditor/toolbar/rotate.js index 19bd54d8bca..bc72f96cc9d 100644 --- a/packages/webiny-ui/src/ImageEditor/toolbar/rotate.js +++ b/packages/webiny-ui/src/ImageEditor/toolbar/rotate.js @@ -42,22 +42,12 @@ class RenderForm extends React.Component<*, { rangeInput: 0 }> { const tool: ImageEditorTool = { name: "rotate", - icon({ canvas, activateTool }) { + icon({ activateTool }) { return ( <Tooltip placement={"bottom"} content={"Rotate"}> <IconButton icon={<RotateRight />} - onClick={() => { - cropper = new Cropper(canvas.current, { - background: false, - modal: false, - guides: false, - dragMode: "none", - highlight: false, - autoCrop: false - }); - activateTool(); - }} + onClick={activateTool} /> </Tooltip> ); @@ -65,6 +55,16 @@ const tool: ImageEditorTool = { renderForm(props) { return <RenderForm {...props} />; }, + onActivate: ({ canvas }) => { + cropper = new Cropper(canvas.current, { + background: false, + modal: false, + guides: false, + dragMode: "none", + highlight: false, + autoCrop: false + }); + }, cancel: () => cropper && cropper.destroy(), apply: ({ canvas }) => { return new Promise(resolve => { diff --git a/packages/webiny-ui/src/ImageEditor/toolbar/types.js b/packages/webiny-ui/src/ImageEditor/toolbar/types.js index c86b69f17cd..824178a0b09 100644 --- a/packages/webiny-ui/src/ImageEditor/toolbar/types.js +++ b/packages/webiny-ui/src/ImageEditor/toolbar/types.js @@ -5,12 +5,13 @@ export type ImageEditorTool = { name: string, apply?: Function, cancel?: Function, + onActivate?: { options: Object, canvas: any }, icon: ({ - canvas: any, activateTool: Function }) => React.Element<any>, renderForm?: ({ canvas: any, - renderApplyCancel: Function + renderApplyCancel: Function, + options?: Object }) => React.Node }; diff --git a/packages/webiny-ui/src/ImageUpload/ImageEditorDialog.js b/packages/webiny-ui/src/ImageUpload/ImageEditorDialog.js index b098e3151a8..cb7210077a7 100644 --- a/packages/webiny-ui/src/ImageUpload/ImageEditorDialog.js +++ b/packages/webiny-ui/src/ImageUpload/ImageEditorDialog.js @@ -1,44 +1,44 @@ // @flow import * as React from "react"; import { ImageEditor } from "webiny-ui/ImageEditor"; +import { Tooltip } from "webiny-ui/Tooltip"; import { Dialog, DialogAccept, DialogCancel, DialogFooter, DialogBody } from "webiny-ui/Dialog"; -type Props = Object & { src: ?string }; +type Props = Object & { options?: Object, src: ?string }; -class ImageEditorDialog extends React.Component<Props> { +class ImageEditorDialog extends React.Component<Props, { imageProcessing: boolean }> { imageEditor = React.createRef(); - componentDidUpdate(previousProps) { - // Reset form when closing the dialog. - const open = { previous: previousProps.open, current: this.props.open }; - if (open.previous && !open.current) { - // Added timeout to avoid minor text flicker when closing the dialog. - this.imageEditor.current.resetCanvas(); - } - } - render() { - const { src, onAccept, ...dialogProps } = this.props; + const { src, options, onAccept, open, ...dialogProps } = this.props; return ( - <Dialog {...dialogProps}> - <ImageEditor src={src} ref={this.imageEditor}> - {({ render, getCanvasDataUrl, activeTool, applyActiveTool }) => ( - <> - <DialogBody>{render()}</DialogBody> - <DialogFooter> - <DialogCancel>Cancel</DialogCancel> - <DialogAccept - onClick={async () => { - activeTool && (await applyActiveTool()); - onAccept(getCanvasDataUrl()); - }} - > - Save - </DialogAccept> - </DialogFooter> - </> - )} - </ImageEditor> + <Dialog + open={open} + onAccept={() => onAccept(this.imageEditor.current.getCanvasDataUrl())} + {...dialogProps} + > + {open && ( + <ImageEditor ref={this.imageEditor} src={src} options={options}> + {({ render, activeTool }) => ( + <> + <DialogBody>{render()}</DialogBody> + <DialogFooter> + <DialogCancel>Cancel</DialogCancel> + {activeTool ? ( + <Tooltip + content={"Please close currently active tool."} + placement={"top"} + > + <DialogAccept disabled>Save</DialogAccept> + </Tooltip> + ) : ( + <DialogAccept>Save</DialogAccept> + )} + </DialogFooter> + </> + )} + </ImageEditor> + )} </Dialog> ); } diff --git a/packages/webiny-ui/src/ImageUpload/MultiImageUpload.js b/packages/webiny-ui/src/ImageUpload/MultiImageUpload.js index 81b71d7f26b..447d3a882c6 100644 --- a/packages/webiny-ui/src/ImageUpload/MultiImageUpload.js +++ b/packages/webiny-ui/src/ImageUpload/MultiImageUpload.js @@ -53,11 +53,9 @@ type Props = FormComponentProps & { // Uses "bytes" (https://www.npmjs.com/package/bytes) library to convert string notation to actual number. maxSize: string, - // By default, the editor tool will be shown when an image is selected. - // Set to false if there is no need for editor to be shown. Otherwise, set true (default value) or alternatively - // an object containing all of the image editor related options (eg. "filter"). + // Image editor options. // Please check the docs of ImageEditor component for the list of all available options. - editor?: boolean | Object, + imageEditor?: Object, // Use these to customize error messages (eg. if i18n supported is needed). errorMessages: { @@ -82,7 +80,7 @@ class MultiImageUpload extends React.Component<Props, State> { static defaultProps = { accept: ["image/jpeg", "image/png", "image/gif", "image/svg+xml"], maxSize: "5mb", - editor: false, + imageEditor: {}, errorMessages: { maxSizeExceeded: "Max size exceeded.", unsupportedFileType: "Unsupported file type.", @@ -158,6 +156,7 @@ class MultiImageUpload extends React.Component<Props, State> { label, description, disabled, + imageEditor, accept, maxSize, className @@ -177,6 +176,7 @@ class MultiImageUpload extends React.Component<Props, State> { )} <ImageEditorDialog + options={imageEditor} open={this.state.imageEditor.open} src={imageEditorImageSrc} onClose={() => { diff --git a/packages/webiny-ui/src/ImageUpload/SingleImageUpload.js b/packages/webiny-ui/src/ImageUpload/SingleImageUpload.js index edd4136349f..299576be70e 100644 --- a/packages/webiny-ui/src/ImageUpload/SingleImageUpload.js +++ b/packages/webiny-ui/src/ImageUpload/SingleImageUpload.js @@ -45,11 +45,9 @@ type Props = FormComponentProps & { // Uses "bytes" (https://www.npmjs.com/package/bytes) library to convert string notation to actual number. maxSize: string, - // By default, the editor tool will be shown when an image is selected. - // Set to false if there is no need for editor to be shown. Otherwise, set true (default value) or alternatively - // an object containing all of the image editor related options (eg. "filter"). + // Image editor options. // Please check the docs of ImageEditor component for the list of all available options. - imageEditor?: boolean | Object, + imageEditor?: Object, // Custom image preview renderer. By default images are rendered via simple <img> element. renderImagePreview?: () => React.Element<any>, @@ -87,8 +85,8 @@ const noImageEditingTypes = ["image/svg+xml", "image/gif"]; export class SingleImageUpload extends React.Component<Props, State> { static defaultProps = { maxSize: "10mb", + imageEditor: {}, accept: ["image/jpeg", "image/png", "image/gif", "image/svg+xml"], - imageEditor: true, showRemoveImageButton: true, errorMessages: { maxSizeExceeded: "Max size exceeded.", @@ -132,6 +130,7 @@ export class SingleImageUpload extends React.Component<Props, State> { }; render() { + const { className, value, @@ -141,6 +140,7 @@ export class SingleImageUpload extends React.Component<Props, State> { accept, maxSize, onChange, + imageEditor, showRemoveImageButton, renderImagePreview } = this.props; @@ -161,6 +161,7 @@ export class SingleImageUpload extends React.Component<Props, State> { )} <ImageEditorDialog + options={imageEditor} open={this.state.imageEditor.open} src={imageEditorImageSrc} onClose={() => { diff --git a/packages/webiny-ui/src/Progress/CircularProgres.js b/packages/webiny-ui/src/Progress/CircularProgres.js new file mode 100644 index 00000000000..46a46bea6bd --- /dev/null +++ b/packages/webiny-ui/src/Progress/CircularProgres.js @@ -0,0 +1,48 @@ +// This is just to center the spinner +import * as React from "react"; +import styled from "react-emotion"; + +import Spinner from "react-spinner-material"; + +type Props = { + size: number, + spinnerColor: string, + spinnerWidth: number, + visible: boolean +}; + +const defaultProps = { + size: 45, + spinnerColor: "#fa5723", + spinnerWidth: 4, + visible: true +}; + +const SpinnerWrapper = styled("div")({ + width: "100%", + height: "100%", + position: "absolute", + background: "white", + opacity: 0.92, + top: 0, + left: 0, + zIndex: 10, + "> div": { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)" + } +}); + +const CircularProgress = (props: Props) => { + return ( + <SpinnerWrapper> + <div> + <Spinner {...defaultProps} {...props} /> + </div> + </SpinnerWrapper> + ); +}; + +export default CircularProgress; diff --git a/packages/webiny-ui/src/Progress/README.md b/packages/webiny-ui/src/Progress/README.md new file mode 100644 index 00000000000..efb13c69a82 --- /dev/null +++ b/packages/webiny-ui/src/Progress/README.md @@ -0,0 +1,16 @@ +# Progress + +### Design +<a href="https://material.io/design/components/progress-indicators.html#circular-progress-indicators" target="_blank"> + https://material.io/design/components/progress-indicators.html#circular-progress-indicators +</a> + +### Description +Progress components are used in cases where user has to wait for a background process to finish. + +Currently only the `CircularProgress` component is available, which is ideal for forms. + +### Import +```js +import { CircularProgress } from "webiny-ui/Progress"; +``` \ No newline at end of file diff --git a/packages/webiny-ui/src/Progress/index.js b/packages/webiny-ui/src/Progress/index.js new file mode 100644 index 00000000000..b2ac5d97c04 --- /dev/null +++ b/packages/webiny-ui/src/Progress/index.js @@ -0,0 +1,2 @@ +// @flow +export { default as CircularProgress } from "./CircularProgres"; diff --git a/packages/webiny-ui/src/Progress/svg/circularProgress.svg b/packages/webiny-ui/src/Progress/svg/circularProgress.svg new file mode 100644 index 00000000000..d248754eb7e --- /dev/null +++ b/packages/webiny-ui/src/Progress/svg/circularProgress.svg @@ -0,0 +1,3 @@ +<svg class="spinner" width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg"> + <circle class="path" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle> +</svg> \ No newline at end of file diff --git a/scripts/checkdep/config.base.js b/scripts/checkdep/config.base.js index 9a4a1ab2b36..a2028046c2a 100644 --- a/scripts/checkdep/config.base.js +++ b/scripts/checkdep/config.base.js @@ -10,12 +10,14 @@ module.exports = { "independent/webiny-integration-google-tag-manager", "independent/webiny-integration-mailchimp", "independent/webiny-integration-typeform", - "packages/demo-admin", "packages/demo-api", "packages/demo-site", "packages/webiny-data-extractor", - "packages/webiny-validation" + "packages/webiny-validation", + "packages/webiny-entity", + "packages/webiny-entity-memory", + "packages/webiny-entity-mongodb" ], ignoredDirs: ["/node_modules/"] }; diff --git a/scripts/checkdep/extractSrcDeps.js b/scripts/checkdep/extractSrcDeps.js index 0495ecc5a73..14f3b8dfb6d 100644 --- a/scripts/checkdep/extractSrcDeps.js +++ b/scripts/checkdep/extractSrcDeps.js @@ -2,27 +2,23 @@ const glob = require("glob"); const fs = require("fs"); const parseImports = source => { - let regex = /import.*from.*['"]([a-zA-Z0-9-_@\.]*).*['"]/g; - let m; + const regexes = [ + /import.+from[ ]+['"]([a-zA-Z0-9-_@\.]*).*['"]/g, + / require\(['"]([a-zA-Z0-9-_@\.]*)['"]/g, + /import[ ]+['"]([a-zA-Z0-9-_@\.]*)['"]/g + ]; const results = []; + regexes.forEach(regex => { + let m; + while ((m = regex.exec(source)) !== null) { + if (m.index === regex.lastIndex) { + regex.lastIndex++; + } - while ((m = regex.exec(source)) !== null) { - if (m.index === regex.lastIndex) { - regex.lastIndex++; - } - - results.push(m[1]); - } - - regex = / require\(['"]([a-zA-Z0-9-_@\.]*)['"]/g; - while ((m = regex.exec(source)) !== null) { - if (m.index === regex.lastIndex) { - regex.lastIndex++; + results.push(m[1]); } - - results.push(m[1]); - } + }); return results; }; diff --git a/yarn.lock b/yarn.lock index 1a2a9a84940..1e51319146b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17299,6 +17299,13 @@ react-sortable-tree@^2.3.0, react-sortable-tree@^2.6.0: react-lifecycles-compat "^3.0.4" react-virtualized "^9.19.1" +react-spinner-material@^1.0.19: + version "1.0.19" + resolved "https://registry.yarnpkg.com/react-spinner-material/-/react-spinner-material-1.0.19.tgz#312a7e2bfc3f8b07f00fce35f083ec514c9a9da5" + integrity sha512-NYV6sD54E1xIeEKig7zlHlLdTxv5OERB+/YEuGM49RXYVt6HjpERcfkdUIJLopFW88+XlgyrYW+wK7I3m79pfA== + dependencies: + prop-types "^15.5.10" + react-split-pane@^0.1.84: version "0.1.85" resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.85.tgz#64819946a99b617ffa2d20f6f45a0056b6ee4faa"