From 7d57311290e5ff560ec8300f43881f51b98f303e Mon Sep 17 00:00:00 2001 From: akash-codemonk <67054171+akash-codemonk@users.noreply.github.com> Date: Thu, 1 Jul 2021 18:48:46 +0530 Subject: [PATCH 01/79] Fix: Remove property from dependency map when binding is removed (#5538) --- app/client/src/workers/DataTreeEvaluator.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/client/src/workers/DataTreeEvaluator.ts b/app/client/src/workers/DataTreeEvaluator.ts index ef4245c403..a637cba86f 100644 --- a/app/client/src/workers/DataTreeEvaluator.ts +++ b/app/client/src/workers/DataTreeEvaluator.ts @@ -923,6 +923,13 @@ export default class DataTreeEvaluator { } } } + // If the whole binding was removed then the value + // at this path would be "". + // In this case if the path exists in the dependency map + // remove it. + else if (fullPropertyPath in this.dependencyMap) { + delete this.dependencyMap[fullPropertyPath]; + } } break; } From cf7fe8d6a8ecdad8220e46ed52c5731b48e0e8bd Mon Sep 17 00:00:00 2001 From: Rishabh Saxena Date: Fri, 2 Jul 2021 11:34:36 +0530 Subject: [PATCH 02/79] Collab minor fixes (#5457) --- app/client/cypress/support/commands.js | 8 + app/client/cypress/support/index.js | 4 + .../assets/icons/comments/commentCursor.png | Bin 730 -> 1374 bytes .../src/comments/CommentCard/CommentCard.tsx | 8 +- .../CommentsShowcaseCarousel/index.tsx | 22 +-- .../src/components/ads/EmojiReactions.tsx | 42 +++-- .../src/constants/ThirdPartyConstants.tsx | 2 + app/client/src/constants/messages.ts | 1 + .../notifications/NotificationListItem.tsx | 87 ++++++++--- .../src/notifications/NotificationsList.tsx | 6 +- .../src/pages/AppViewer/viewer/PageTabs.tsx | 17 +- .../pages/Applications/permissionHelpers.tsx | 1 + .../src/pages/Editor/EditableAppName.tsx | 50 +++--- app/client/src/pages/Editor/EditorHeader.tsx | 42 +++-- .../src/pages/Editor/ToggleModeButton.tsx | 147 ++++++++++++------ 15 files changed, 294 insertions(+), 143 deletions(-) diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 92ba4582f5..68144e70b0 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -24,6 +24,12 @@ const viewWidgetsPage = require("../locators/ViewWidgets.json"); let pageidcopy = " "; +export const initLocalstorage = () => { + cy.window().then((window) => { + window.localStorage.setItem("ShowCommentsButtonToolTip", ""); + }); +}; + Cypress.Commands.add("createOrg", () => { cy.get(homePage.createOrg) .should("be.visible") @@ -373,6 +379,8 @@ Cypress.Commands.add("LogintoApp", (uname, pword) => { "response.body.responseMeta.status", 200, ); + + initLocalstorage(); }); Cypress.Commands.add("LoginFromAPI", (uname, pword) => { diff --git a/app/client/cypress/support/index.js b/app/client/cypress/support/index.js index 71c1be4d99..bf914f1c91 100644 --- a/app/client/cypress/support/index.js +++ b/app/client/cypress/support/index.js @@ -18,6 +18,7 @@ let appId; // Import commands.js using ES2015 syntax: import "./commands"; +import { initLocalstorage } from "./commands"; Cypress.on("uncaught:exception", (err, runnable) => { // returning false here prevents Cypress from @@ -31,11 +32,13 @@ Cypress.on("fail", (error, runnable) => { }); before(function() { + initLocalstorage(); cy.startServerAndRoutes(); // Clear indexedDB cy.window().then((window) => { window.indexedDB.deleteDatabase("Appsmith"); }); + const username = Cypress.env("USERNAME"); const password = Cypress.env("PASSWORD"); cy.LoginFromAPI(username, password); @@ -58,6 +61,7 @@ before(function() { }); beforeEach(function() { + initLocalstorage(); Cypress.Cookies.preserveOnce("SESSION", "remember_token"); cy.startServerAndRoutes(); }); diff --git a/app/client/src/assets/icons/comments/commentCursor.png b/app/client/src/assets/icons/comments/commentCursor.png index 7d68e510fb86c8d15044036ee04dedede2c45eec..25adce859fc88b80f7ac2fed2f22cd7e2c3aa800 100644 GIT binary patch delta 1362 zcmV-Y1+Dtp1>OoaiBL{Q4GJ0x0000DNk~Le0000Y0000X2nGNE0KC(AYybcN32;bR za{vGkoB#k2oB<}yIy8|sAAbb%NklB#J-}7vPgy0LQ0eb2tr7RVqq5>1NOal z`knENdod>XRjG>}>1eL!aps%zHe>%0;;cF1QcAY5u@Q@gLJt||DSynxz&R$wZa$y? zd31C%tK~}81lG~OMn*L$;IP322W;ap()MBl+6ldxPft&a?YD0qu`1*7*R(HYJO&BCJuAVMIsSKe$fiiIWi^WJY8O1?tfwLafMnS$~)8PG#|Wo z?-72d$S>L+c^2j6XP@8*t=Whdx3laVAZl}eF~eZ~*H_g9~*s%NB_K8EcqWtKg?#>(bJPxL zidhFJ9*?VR4L?+X2+JiPWEw;aqYVQC18VBK=g)tDd4Eo*W^{_`WYzi_fumN>EG{no z^=BsY%?Sz^Rn<;VE|-(~9_hWemQ)VWv5u-A?G1;+AG~?>YPFT3=Ovg~?(Xc!7zhy= zhUTeoS#^QvWJXnzfJDHQo0ACXp*OEz|3P3*@S=8ruu3-<26sWOs>t;;!u4VoI0UA~ zBLVK8ntz&l@au~g-;=QYy*(wkTrRf*B%mK%1;Xo?N13Rvug`aMbmT|%^`pV@L!R&J zXU`sdIx+DD%=VeutXsvK$h4BFyLaRL{rx3Gj}1R@Xo=}&shY!a5l3Yt6S=UVo6Tlv zlT#=ZEcl8;g65t)d5O8tCnqOw&(6+%(%aknA%6;5R633)R|~qCam!K{Se(0P16-5~ zx)|BOHG41T4DYwBG`cF*HAqX3K7jE$CUCiB&LG#Aj$L%2rjSz`f!OC_odDS2 zGPned*huxZo^!pXs47LLda0QNI53eCbb5MP#>+@p8N!KeV!hABr8Yp=G=kMx$aBzS z8-EihGeCw!;gj&kOBMJ^W8`6F8u*DpUxt~=@DKG34Vn28E+b$xmf$ls0H{6CUb$E- z+BoBwhwbn0OB6miJUrY`BW=t>Ch&s;$hfzl9SUoTrWjg4YW;dC-(XB&OqkI#M`K){ zp|e4qL&)4h%Ep>{?g2yxNkEVQu}XN8;C~1TWVENJhc1e0R^K$Qfk)sMp;{2ECSt8d z5K$e1P>GOpsTRRp%I3(z65YBBCv_0!66HXd6D(m82dwtNqz?ST#UC{iR}I-#$+VKj zWGb3)fgk+^eftg5oL)C;mS&|1N)?Tor7qE7&D;ny;Aqs}UclTlUg=Z+r}xp{852xs U*1`KbX#fBK07*qoM6N<$f-$6iQvd(} delta 713 zcmV;)0yh2L3fcuWiBL{Q4GJ0x0000DNk~Le0000M0000M2nGNE0K~LxI{*Lx32;bR za{vGf6951U69E94oEVWdAAbUGNkl47DL45L%TY3vnqNdf-`5 z)Jv;gIS~2;g~ut+fW(0ph(O{-K&Us65+^hz*x=6Rtf_GlelGo_Ph*dFzMY+!HDh~V z+cpe$N$;E;0KQAG-|wGlns!NQUxg6*dcDS{E5`X%gt6vZT_1CP(0@LTAa67pVl*6n zn~cXd1lu%C+XbHQ48#64ncUEt`o0AHqTeZSOVAH|!1J=aj@e@6lyF{#4TzF~(3NFN_SS-d4#T!jy zfk5C3Vw=t8vvR-@Ab%?3>udX1P&qJ@QK`Yb8N1>%Re~K08RNund>&EfgJoG0B0Co8 zbXs_Hm{agdrNRq^f=H!O+%Sxvzwhtav6xP$Tf#0-n=nzWR;z{aXk;8RZZ0s-6dNZ- zq}CH~DaeHAI4aq*PN!4fcR~EZ$z(EKEEbX6_6WiCWMmEk0(M6RBE-F{`umf9*stekx1m@YnKMi*7NxUWxN;+1}k!{oDcTXjqzrk zAf6N76LX59=w$OCJ#Vb^e;}sOA0#EqD%0lNITAT-+7-K*#7D^`&FK=BGKU|tXe|~C zi{dQ#ju^XKE`QtX6=3v`bts$7MrAWk&`?!X(?p!&+_f^(B#o&?R*|vKZk|(>R;z`- z`HJS+B;lp5>kE4SGoR1F2l73H!(qnG%Gz?jz#*HwwAM)vS)xzuf8Z~qW%9atCc5DO015yANkvXXu0mjf=+8`% diff --git a/app/client/src/comments/CommentCard/CommentCard.tsx b/app/client/src/comments/CommentCard/CommentCard.tsx index 9f5953912c..af58cb342f 100644 --- a/app/client/src/comments/CommentCard/CommentCard.tsx +++ b/app/client/src/comments/CommentCard/CommentCard.tsx @@ -203,16 +203,22 @@ const reduceReactions = ( reactions.reduce( (res: Record, reaction: Reaction) => { const { byUsername, emoji } = reaction; + const sameAsCurrent = byUsername === username; + const user = sameAsCurrent ? "You" : byUsername; if (res[reaction.emoji]) { res[reaction.emoji].count++; + res[reaction.emoji].users = Array.from( + new Set([...(res[reaction.emoji].users || []), user]), + ); } else { res[emoji] = { count: 1, reactionEmoji: emoji, + users: [user], } as ComponentReaction; } - if (byUsername === username) { + if (sameAsCurrent) { res[reaction.emoji].active = true; } diff --git a/app/client/src/comments/CommentsShowcaseCarousel/index.tsx b/app/client/src/comments/CommentsShowcaseCarousel/index.tsx index 0ee8294390..76d725e080 100644 --- a/app/client/src/comments/CommentsShowcaseCarousel/index.tsx +++ b/app/client/src/comments/CommentsShowcaseCarousel/index.tsx @@ -4,11 +4,6 @@ import Text, { TextType } from "components/ads/Text"; import ShowcaseCarousel, { Steps } from "components/ads/ShowcaseCarousel"; import ProfileForm, { PROFILE_FORM } from "./ProfileForm"; import CommentsCarouselModal from "./CommentsCarouselModal"; -import CommentsOnboardingStep1 from "assets/images/comments-onboarding/step-1.png"; -import CommentsOnboardingStep2 from "assets/images/comments-onboarding/step-2.png"; -import CommentsOnboardingStep3 from "assets/images/comments-onboarding/step-3.png"; -import CommentsOnboardingStep4 from "assets/images/comments-onboarding/step-4.png"; -import CommentsOnboardingStep5 from "assets/images/comments-onboarding/step-5.png"; import styled, { withTheme } from "styled-components"; import { Theme } from "constants/DefaultTheme"; @@ -26,37 +21,42 @@ import { setCommentsIntroSeen } from "utils/storage"; import { updateUserDetails } from "actions/userActions"; +import { S3_BUCKET_URL } from "constants/ThirdPartyConstants"; + +const getBanner = (step: number) => + `${S3_BUCKET_URL}/comments/step-${step}.png`; + const introSteps = [ { title: "Introducing Live Comments", content: "We are introducing live comments. From now on you will be able to comment on your apps, tag other people and exchange thoughts in threads. Click ‘Next’ to learn more about comments and start commenting.", - banner: CommentsOnboardingStep1, + banner: getBanner(1), hideBackBtn: true, }, { title: "Give feedback", content: "Comment on your co-worker’s work and share your thoughts on what works and what needs change.", - banner: CommentsOnboardingStep2, + banner: getBanner(2), }, { title: "Invite other people to your conversations", content: "When leaving a comment you can tag other people by writing ‘@’ and their name. This way the person you tagged will get a notification and an e-mail that you tagged them in a comment.", - banner: CommentsOnboardingStep3, + banner: getBanner(3), }, { title: "Tag a comment to a widget", content: "If you click on a component while in a comment mode you will tag that comment to that widget. This way if the widget is moved the comment will be moved as well. You can disconnect the comment and widget y simply moving the the comment away from the widget.", - banner: CommentsOnboardingStep4, + banner: getBanner(4), }, { title: "You are all set!", content: "By clicking on the comments icon in the top right corner you will activate the ‘collaboration mode’ and will be able to start a thread or answer to someone else’s comment.", - banner: CommentsOnboardingStep5, + banner: getBanner(5), }, ]; @@ -72,7 +72,7 @@ const StyledImg = styled.img` function IntroStep(props: { title: string; content: string; - banner: typeof CommentsOnboardingStep1; + banner: string; theme: Theme; }) { return ( diff --git a/app/client/src/components/ads/EmojiReactions.tsx b/app/client/src/components/ads/EmojiReactions.tsx index e4987861d5..1f61aac518 100644 --- a/app/client/src/components/ads/EmojiReactions.tsx +++ b/app/client/src/components/ads/EmojiReactions.tsx @@ -2,6 +2,7 @@ import React from "react"; import styled from "styled-components"; import EmojiPicker from "./EmojiPicker"; import { IconSize } from "./Icon"; +import TooltipComponent from "./Tooltip"; const Container = styled.div` display: flex; @@ -46,10 +47,28 @@ const Count = styled.div<{ active?: boolean }>` white-space: nowrap; `; +const ReactionsByContainer = styled.span` + max-width: 200px; + display: inline-block; +`; + +function ReactionsBy(props: { reaction: Reaction }) { + const { reaction } = props; + + if (!reaction?.users) return null; + const isSliced = reaction?.users.length > 5; + const users = reaction?.users.slice(0, 5); + + if (isSliced) users.push("..."); + + return {users.join(", ")}; +} + export type Reaction = { count: number; reactionEmoji: string; active?: boolean; + users?: Array; }; export type Reactions = Record; @@ -119,21 +138,26 @@ function EmojiReactions({ addOrRemove as ReactionOperation, ); }; - return ( {!hideReactions && transformReactions(reactions).map((reaction: Reaction) => ( - } key={reaction.reactionEmoji} - onClick={(e) => handleSelectReaction(e, reaction.reactionEmoji)} + modifiers={{ preventOverflow: { enabled: true } }} > - {reaction.reactionEmoji} - {reaction.count > 1 && ( - {reaction.count} - )} - + handleSelectReaction(e, reaction.reactionEmoji)} + > + {reaction.reactionEmoji} + {reaction.count > 1 && ( + {reaction.count} + )} + + ))} {!hideReactions ? ( diff --git a/app/client/src/constants/ThirdPartyConstants.tsx b/app/client/src/constants/ThirdPartyConstants.tsx index 9672955d90..c9f8112d05 100644 --- a/app/client/src/constants/ThirdPartyConstants.tsx +++ b/app/client/src/constants/ThirdPartyConstants.tsx @@ -1 +1,3 @@ export type ENVIRONMENT = "PRODUCTION" | "STAGING" | "LOCAL"; +export const S3_BUCKET_URL = + "https://s3.us-east-2.amazonaws.com/assets.appsmith.com"; diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index f8b92c1b26..90fe465b8e 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -321,6 +321,7 @@ export const MARK_ALL_AS_READ = () => "Mark all as read"; export const INVITE_A_NEW_USER = () => "Invite a new user"; export const REMOVE = () => "Remove"; export const NO_NOTIFICATIONS_TO_SHOW = () => "No notifications to show"; +export const ONE_UNREAD_MESSAGE = () => "1 unread conversation"; // Showcase Carousel export const NEXT = () => "NEXT"; diff --git a/app/client/src/notifications/NotificationListItem.tsx b/app/client/src/notifications/NotificationListItem.tsx index 65d5ca61b3..da758ef85d 100644 --- a/app/client/src/notifications/NotificationListItem.tsx +++ b/app/client/src/notifications/NotificationListItem.tsx @@ -14,10 +14,21 @@ import { useDispatch } from "react-redux"; import moment from "moment"; import styled from "styled-components"; +import { APP_MODE } from "reducers/entityReducers/appReducer"; +import OrgApi from "api/OrgApi"; + +import { + isPermitted, + PERMISSION_TYPE, +} from "pages/Applications/permissionHelpers"; + +export const NOTIFICATION_HEIGHT = 82; + const Container = styled.div` display: flex; width: 100%; padding: ${(props) => props.theme.spaces[6]}px; + height: ${NOTIFICATION_HEIGHT}px; ${Profile} { margin-right: ${(props) => props.theme.spaces[4]}px; @@ -31,6 +42,14 @@ const NotificationBodyContainer = styled.div` & b { font-weight: 500; } + + & div { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; /* number of lines to show */ + -webkit-box-orient: vertical; + } `; const FlexContainer = styled.div` @@ -57,6 +76,29 @@ const UnreadIndicator = styled.div` props.theme.colors.notifications.unreadIndicator}; `; +const getModeFromUserRole = async (orgId: string) => { + try { + const response = await OrgApi.fetchOrg({ orgId }); + const userOrgPermissions = response?.data?.data?.userPermissions || []; + const canPublish = isPermitted( + userOrgPermissions, + PERMISSION_TYPE.PUBLISH_APPLICATION, + ); + + return canPublish ? APP_MODE.EDIT : APP_MODE.PUBLISHED; + } catch (e) { + return APP_MODE.PUBLISHED; + } +}; + +const getModeFromRoleAndDomain = ( + modeFromRole: APP_MODE, + modeFromDomain: APP_MODE, +) => { + if (modeFromRole === APP_MODE.PUBLISHED) return APP_MODE.PUBLISHED; + return modeFromDomain; +}; + function CommentNotification(props: { notification: AppsmithNotification }) { const dispatch = useDispatch(); const { @@ -72,24 +114,27 @@ function CommentNotification(props: { notification: AppsmithNotification }) { applicationName, authorName, authorUsername, - mode, + mode: modeFromComment, + orgId, pageId, // resolvedState, TODO get from comment thread threadId, } = comment; - const commentThreadUrl = getCommentThreadURL({ - applicationId, - commentThreadId: threadId, - // isResolved: resolvedState?.active, - mode, - pageId, - }); - const _createdAt = createdAt || creationTime; const displayName = authorName || authorUsername; - const handleClick = () => { + const handleClick = async () => { + const modeFromRole = await getModeFromUserRole(orgId); + const mode = getModeFromRoleAndDomain(modeFromRole, modeFromComment); + + const commentThreadUrl = getCommentThreadURL({ + applicationId, + commentThreadId: threadId, + // isResolved: resolvedState?.active, + mode, + pageId, + }); history.push( `${commentThreadUrl.pathname}${commentThreadUrl.search}${commentThreadUrl.hash}`, ); @@ -137,22 +182,26 @@ function CommentThreadNotification(props: { authorName, authorUsername, id, - mode, + mode: modeFromThread, + orgId, pageId, resolvedState, } = commentThread; const commentThreadId = _id || id; - const commentThreadUrl = getCommentThreadURL({ - applicationId, - commentThreadId, - isResolved: resolvedState?.active, - mode, - pageId, - }); + const handleClick = async () => { + const modeFromRole = await getModeFromUserRole(orgId); + const mode = getModeFromRoleAndDomain(modeFromRole, modeFromThread); + + const commentThreadUrl = getCommentThreadURL({ + applicationId, + commentThreadId, + isResolved: resolvedState?.active, + mode, + pageId, + }); - const handleClick = () => { history.push( `${commentThreadUrl.pathname}${commentThreadUrl.search}${commentThreadUrl.hash}`, ); diff --git a/app/client/src/notifications/NotificationsList.tsx b/app/client/src/notifications/NotificationsList.tsx index 60c8ac6f81..4eda4426bb 100644 --- a/app/client/src/notifications/NotificationsList.tsx +++ b/app/client/src/notifications/NotificationsList.tsx @@ -9,7 +9,9 @@ import { notificationsSelector, fetchingNotificationsSelector, } from "selectors/notificationSelectors"; -import NotificationListItem from "./NotificationListItem"; +import NotificationListItem, { + NOTIFICATION_HEIGHT, +} from "./NotificationListItem"; import { AppsmithNotification } from "entities/Notification"; import { @@ -98,8 +100,6 @@ function NotificationsListHeader() { ); } -const NOTIFICATION_HEIGHT = 63; - const Footer = styled.div` display: flex; justify-content: center; diff --git a/app/client/src/pages/AppViewer/viewer/PageTabs.tsx b/app/client/src/pages/AppViewer/viewer/PageTabs.tsx index 6f36e2066c..e1a7ad5835 100644 --- a/app/client/src/pages/AppViewer/viewer/PageTabs.tsx +++ b/app/client/src/pages/AppViewer/viewer/PageTabs.tsx @@ -142,6 +142,12 @@ type Props = { export function PageTabs(props: Props) { const { appPages, currentApplicationDetails } = props; const { pathname } = useLocation(); + const location = useLocation(); + const [query, setQuery] = useState(""); + + useEffect(() => { + setQuery(window.location.search); + }, [location]); return ( @@ -161,10 +167,13 @@ export function PageTabs(props: Props) { diff --git a/app/client/src/pages/Applications/permissionHelpers.tsx b/app/client/src/pages/Applications/permissionHelpers.tsx index 821f92503f..7ad434a6fe 100644 --- a/app/client/src/pages/Applications/permissionHelpers.tsx +++ b/app/client/src/pages/Applications/permissionHelpers.tsx @@ -7,6 +7,7 @@ export enum PERMISSION_TYPE { READ_ORGANIZATION = "read:organizations", INVITE_USER_TO_ORGANIZATION = "inviteUsers:organization", MAKE_PUBLIC_APPLICATION = "makePublic:applications", + PUBLISH_APPLICATION = "publish:orgApplications", } export const isPermitted = (permissions: string[], type: string) => { diff --git a/app/client/src/pages/Editor/EditableAppName.tsx b/app/client/src/pages/Editor/EditableAppName.tsx index bde61d8ee1..5f9c14f6df 100644 --- a/app/client/src/pages/Editor/EditableAppName.tsx +++ b/app/client/src/pages/Editor/EditableAppName.tsx @@ -45,30 +45,32 @@ export default function EditableTextWrapper(props: EditableTextWrapperProps) { return ( - { - if (value.trim() === "") { - Toaster.show({ - text: "Application name can't be empty", - variant: Variant.danger, - }); - } - return false; - }} - onBlur={(value) => { - if (props.onBlur) props.onBlur(value); - setIsEditingDefault(false); - }} - placeholder={props.placeholder} - savingState={props.savingState} - /> + {props.defaultValue && ( + { + if (value.trim() === "") { + Toaster.show({ + text: "Application name can't be empty", + variant: Variant.danger, + }); + } + return false; + }} + onBlur={(value) => { + if (props.onBlur) props.onBlur(value); + setIsEditingDefault(false); + }} + placeholder={props.placeholder} + savingState={props.savingState} + /> + )} ); } diff --git a/app/client/src/pages/Editor/EditorHeader.tsx b/app/client/src/pages/Editor/EditorHeader.tsx index 4f3bf737fb..9c1bd07f1b 100644 --- a/app/client/src/pages/Editor/EditorHeader.tsx +++ b/app/client/src/pages/Editor/EditorHeader.tsx @@ -225,28 +225,26 @@ export function EditorHeader(props: EditorHeaderProps) { /> - {currentApplication && ( - el.id === applicationId) - .length > 0 - } - onBlur={(value: string) => - updateApplicationDispatch(applicationId || "", { - name: value, - currentApp: true, - }) - } - savingState={ - isSavingName ? SavingState.STARTED : SavingState.NOT_STARTED - } - /> - )} + el.id === applicationId).length > + 0 + } + onBlur={(value: string) => + updateApplicationDispatch(applicationId || "", { + name: value, + currentApp: true, + }) + } + savingState={ + isSavingName ? SavingState.STARTED : SavingState.NOT_STARTED + } + /> diff --git a/app/client/src/pages/Editor/ToggleModeButton.tsx b/app/client/src/pages/Editor/ToggleModeButton.tsx index 1998bec770..c50074e54e 100644 --- a/app/client/src/pages/Editor/ToggleModeButton.tsx +++ b/app/client/src/pages/Editor/ToggleModeButton.tsx @@ -32,6 +32,17 @@ import { APP_MODE } from "reducers/entityReducers/appReducer"; import { matchBuilderPath, matchViewerPath } from "constants/routes"; +import { createMessage, ONE_UNREAD_MESSAGE } from "constants/messages"; + +import localStorage from "utils/localStorage"; + +const getShowCommentsButtonToolTip = () => { + const flag = localStorage.getItem("ShowCommentsButtonToolTip"); + return flag === null || !!flag; +}; +const setShowCommentsButtonToolTip = (value = "") => + localStorage.setItem("ShowCommentsButtonToolTip", value); + const ModeButton = styled.div<{ active: boolean }>` position: relative; display: flex; @@ -156,11 +167,77 @@ function ViewModeReset() { ); } +const tourToolTipProps = { + hasOverlay: true, + modifiers: { + offset: { enabled: true, offset: "3, 20" }, + arrow: { + enabled: true, + fn: (data: any) => ({ + ...data, + offsets: { + ...data.offsets, + arrow: { + top: -8, + left: 80, + }, + }, + }), + }, + }, + pulseStyles: { + top: 20, + left: 28, + height: 30, + width: 30, + }, + showPulse: true, + tourIndex: 0, + tourType: TourType.COMMENTS_TOUR, +}; + +function ViewOrEditMode({ mode }: { mode?: APP_MODE }) { + return mode === APP_MODE.EDIT ? : ; +} + +function CommentModeBtn({ + handleSetCommentModeButton, + isCommentMode, + showUnreadIndicator, +}: { + handleSetCommentModeButton: () => void; + isCommentMode: boolean; + showUnreadIndicator: boolean; +}) { + const CommentModeIcon = showUnreadIndicator ? CommentModeUnread : CommentMode; + + return ( + + + Comment Mode + C + + } + hoverOpenDelay={1000} + position={Position.BOTTOM} + > + + + + ); +} + function ToggleCommentModeButton() { const commentsEnabled = useSelector(areCommentsEnabledForUserAndAppSelector); const isCommentMode = useSelector(commentModeSelector); const showUnreadIndicator = useSelector(showUnreadIndicatorSelector); const currentUser = useSelector(getCurrentUser); + const [ + showCommentButtonDiscoveryTooltip, + setShowCommentButtonDiscoveryTooltipInState, + ] = useState(getShowCommentsButtonToolTip()); useUpdateCommentMode(currentUser); const proceedToNextTourStep = useProceedToNextTourStep( @@ -170,6 +247,13 @@ function ToggleCommentModeButton() { const mode = useSelector((state: AppState) => state.entities.app.mode); + const handleSetCommentModeButton = useCallback(() => { + setCommentModeInUrl(true); + proceedToNextTourStep(); + setShowCommentButtonDiscoveryTooltipInState(false); + setShowCommentsButtonToolTip(); + }, [proceedToNextTourStep, setShowCommentButtonDiscoveryTooltipInState]); + // Show comment mode button only on the canvas editor and viewer const [shouldHide, setShouldHide] = useState(false); const location = useLocation(); @@ -182,65 +266,28 @@ function ToggleCommentModeButton() { if (!commentsEnabled) return null; - const CommentModeIcon = showUnreadIndicator ? CommentModeUnread : CommentMode; - return ( - ({ - ...data, - offsets: { - ...data.offsets, - arrow: { - top: -8, - left: 80, - }, - }, - }), - }, - }} - pulseStyles={{ - top: 20, - left: 28, - height: 30, - width: 30, - }} - showPulse - tourIndex={0} - tourType={TourType.COMMENTS_TOUR} - > +
setCommentModeInUrl(false)} > - {mode === APP_MODE.EDIT ? : } + - { - setCommentModeInUrl(true); - proceedToNextTourStep(); - }} + - - Comment Mode - C - - } - hoverOpenDelay={1000} - position={Position.BOTTOM} - > - - - + +
From 6102f5b119248348e26b1f226aacbf860e12faf0 Mon Sep 17 00:00:00 2001 From: Yash Vibhandik Date: Fri, 2 Jul 2021 12:39:17 +0530 Subject: [PATCH 03/79] Added text color JS convertible support in text widget added text color js convertible support in text widget, added COLOR_PICKER_TEXT validation and updated test case (#5409) --- app/client/src/constants/WidgetValidation.ts | 1 + app/client/src/widgets/TextWidget.tsx | 4 ++- app/client/src/workers/validations.test.ts | 34 ++++++++++++++++++++ app/client/src/workers/validations.ts | 17 ++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/app/client/src/constants/WidgetValidation.ts b/app/client/src/constants/WidgetValidation.ts index d1e0837c78..58df341dc9 100644 --- a/app/client/src/constants/WidgetValidation.ts +++ b/app/client/src/constants/WidgetValidation.ts @@ -31,6 +31,7 @@ export enum VALIDATION_TYPES { ROW_INDICES = "ROW_INDICES", IMAGE = "IMAGE", TABS_DATA = "TABS_DATA", + COLOR_PICKER_TEXT = "COLOR_PICKER_TEXT", } export type ValidationResponse = { diff --git a/app/client/src/widgets/TextWidget.tsx b/app/client/src/widgets/TextWidget.tsx index 22c291cadc..745cdd76c4 100644 --- a/app/client/src/widgets/TextWidget.tsx +++ b/app/client/src/widgets/TextWidget.tsx @@ -56,8 +56,10 @@ class TextWidget extends BaseWidget { propertyName: "textColor", label: "Text Color", controlType: "COLOR_PICKER", - isBindProperty: false, + isJSConvertible: true, + isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.COLOR_PICKER_TEXT, }, { propertyName: "fontSize", diff --git a/app/client/src/workers/validations.test.ts b/app/client/src/workers/validations.test.ts index b1053b5bd2..0bf9acaf29 100644 --- a/app/client/src/workers/validations.test.ts +++ b/app/client/src/workers/validations.test.ts @@ -508,3 +508,37 @@ describe("List data validator", () => { }); }); }); + +describe("Color Picker Text validator", () => { + const validator = VALIDATORS.COLOR_PICKER_TEXT; + const inputs = [ + "#e0e0e0", + "rgb(200,200,200)", + "{{Text2.text}}", + "

red

", + ]; + const expected = [ + { + isValid: true, + parsed: "#e0e0e0", + }, + { + isValid: true, + parsed: "rgb(200,200,200)", + }, + { + isValid: false, + parsed: "", + message: "This value does not evaluate to type: text", + }, + { + isValid: false, + parsed: "", + message: "This value does not evaluate to type: text", + }, + ]; + inputs.forEach((input, index) => { + const response = validator(input, DUMMY_WIDGET); + expect(response).toStrictEqual(expected[index]); + }); +}); diff --git a/app/client/src/workers/validations.ts b/app/client/src/workers/validations.ts index 53b8378468..b75281f7c8 100644 --- a/app/client/src/workers/validations.ts +++ b/app/client/src/workers/validations.ts @@ -15,6 +15,7 @@ import _, { isPlainObject, isString, isUndefined, + startsWith, toNumber, toString, } from "lodash"; @@ -1033,4 +1034,20 @@ export const VALIDATORS: Record = { message: `${WIDGET_TYPE_VALIDATION_ERROR}: number[]`, }; }, + [VALIDATION_TYPES.COLOR_PICKER_TEXT]: ( + value: any, + props: WidgetProps, + ): ValidationResponse => { + // check value should be string + const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.TEXT](value, props); + // check value should not html tag or unparsed js + if (startsWith(parsed, "{{") || startsWith(parsed, "<")) { + return { + isValid: false, + parsed: "", + message: `${WIDGET_TYPE_VALIDATION_ERROR}: text`, + }; + } + return { isValid, parsed }; + }, }; From 38ffe86290e13c7ecd71edbc6c4bd5df6cddc7b8 Mon Sep 17 00:00:00 2001 From: Paul Li <82799722+wmdev0808@users.noreply.github.com> Date: Fri, 2 Jul 2021 05:55:50 -0400 Subject: [PATCH 04/79] Rate widget (#4891) * FEATURE-3357 : Rate Widget -- Create the first MVP of rate widget * FEATURE-3357 : Rate Widget -- Change the widget name into rating -- Change the widget icon -- Fix the overflow issue in case max count is big -- Fix the issue in case default rate is zero -- Add validations for maxCount and defaultRate * FEATURE-3357 : Rate Widget -- Fix an issue : Stars is cut off if maxCount is greater than 20 -- Add test cases for two validation types, RATE_DEFAULT_RATE and RATE_MAX_COUNT * FEATURE-3357 : Rate Widget -- Add expected data type for tooltip field * FEATURE-3357 : Rate Widget -- Expose maxCount * FEATURE-3357 : Rate Widget -- Change contents of isAllowHalf property -- Adjust alignment of stars dynamically -- Decrease default widget width * FEATURE-3357 : Rate Widget -- Remove a unnecessary comment block --- app/client/package.json | 1 + app/client/src/assets/icons/widget/rating.svg | 3 + .../designSystems/blueprint/RateComponent.tsx | 145 +++++++++++++ .../designSystems/blueprint/TextComponent.tsx | 9 +- .../ActionConstants.tsx | 1 + app/client/src/constants/Colors.tsx | 2 + .../src/constants/FieldExpectedValue.ts | 10 + app/client/src/constants/HelpConstants.ts | 4 + app/client/src/constants/WidgetConstants.tsx | 15 ++ app/client/src/constants/WidgetValidation.ts | 3 + app/client/src/icons/WidgetIcons.tsx | 6 + .../mockResponses/WidgetConfigResponse.tsx | 13 ++ .../mockResponses/WidgetSidebarResponse.tsx | 5 + .../entityReducers/widgetConfigReducer.tsx | 2 + app/client/src/utils/WidgetRegistry.tsx | 17 ++ .../utils/autocomplete/EntityDefinitions.ts | 7 + app/client/src/widgets/RateWidget/index.tsx | 191 ++++++++++++++++++ app/client/src/workers/validations.test.ts | 184 +++++++++++++++++ app/client/src/workers/validations.ts | 83 ++++++++ app/client/yarn.lock | 27 +++ 20 files changed, 720 insertions(+), 8 deletions(-) create mode 100644 app/client/src/assets/icons/widget/rating.svg create mode 100644 app/client/src/components/designSystems/blueprint/RateComponent.tsx create mode 100644 app/client/src/widgets/RateWidget/index.tsx diff --git a/app/client/package.json b/app/client/package.json index e702010d16..07c7b910f9 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -115,6 +115,7 @@ "react-mentions": "^4.1.1", "react-paginating": "^1.4.0", "react-player": "^2.3.1", + "react-rating": "^2.0.5", "react-redux": "^7.1.3", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", diff --git a/app/client/src/assets/icons/widget/rating.svg b/app/client/src/assets/icons/widget/rating.svg new file mode 100644 index 0000000000..4b3e80c619 --- /dev/null +++ b/app/client/src/assets/icons/widget/rating.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/client/src/components/designSystems/blueprint/RateComponent.tsx b/app/client/src/components/designSystems/blueprint/RateComponent.tsx new file mode 100644 index 0000000000..207829a063 --- /dev/null +++ b/app/client/src/components/designSystems/blueprint/RateComponent.tsx @@ -0,0 +1,145 @@ +import React, { useState, useEffect } from "react"; +import { Icon, Position } from "@blueprintjs/core"; +import { IconNames } from "@blueprintjs/icons"; +import styled from "styled-components"; +import Rating from "react-rating"; +import _ from "lodash"; + +import { ComponentProps } from "components/designSystems/appsmith/BaseComponent"; +import { RateSize, RATE_SIZES } from "constants/WidgetConstants"; +import TooltipComponent from "components/ads/Tooltip"; + +/* + Note: + -webkit-line-clamp may seem like a wierd way to doing this + however, it is getting more and more useful with more browser support. + It suffices for our target browsers + More info: https://css-tricks.com/line-clampin/ +*/ + +interface RateContainerProps { + scrollable: boolean; +} + +export const RateContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + align-content: flex-start; + overflow: auto; + + > span { + align-self: ${(props) => (props.scrollable ? "flex-start" : "center")}; + } +`; + +export const Star = styled(Icon)` + padding: ${(props) => + props.iconSize === 12 ? 2.92 : props.iconSize === 16 ? 4.37 : 4.93}px; +`; + +export interface RateComponentProps extends ComponentProps { + value: number; + isLoading: boolean; + maxCount: number; + size: RateSize; + onValueChanged: (value: number) => void; + tooltips?: Array; + activeColor?: string; + inactiveColor?: string; + isAllowHalf?: boolean; + readonly?: boolean; + leftColumn?: number; + rightColumn?: number; + topRow?: number; + bottomRow?: number; +} + +function renderStarsWithTooltip(props: RateComponentProps) { + const rateTooltips = props.tooltips || []; + const rateTooltipsCount = rateTooltips.length; + const deltaCount = props.maxCount - rateTooltipsCount; + if (rateTooltipsCount === 0) { + return ( + + ); + } + const starWithTooltip = rateTooltips.map((tooltip) => ( + + + + )); + const starWithoutTooltip = _.times(deltaCount, (num: number) => ( + + )); + + return _.concat(starWithTooltip, starWithoutTooltip); +} + +function RateComponent(props: RateComponentProps) { + const rateContainerRef = React.createRef(); + + const { + bottomRow, + inactiveColor, + isAllowHalf, + leftColumn, + maxCount, + onValueChanged, + readonly, + rightColumn, + size, + topRow, + value, + } = props; + + const [scrollable, setScrollable] = useState(false); + + useEffect(() => { + const rateContainerElement = rateContainerRef.current; + if ( + rateContainerElement && + rateContainerElement.scrollHeight > rateContainerElement.clientHeight + ) { + setScrollable(true); + } else { + setScrollable(false); + } + }, [leftColumn, rightColumn, topRow, bottomRow, maxCount, size]); + + return ( + + + } + fractions={isAllowHalf ? 2 : 1} + fullSymbol={renderStarsWithTooltip(props)} + initialRating={value} + onChange={onValueChanged} + readonly={readonly} + stop={maxCount} + /> + + ); +} + +export default RateComponent; diff --git a/app/client/src/components/designSystems/blueprint/TextComponent.tsx b/app/client/src/components/designSystems/blueprint/TextComponent.tsx index 2fe019bd39..f6aef1368d 100644 --- a/app/client/src/components/designSystems/blueprint/TextComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/TextComponent.tsx @@ -16,20 +16,13 @@ type TextStyleProps = { export const BaseText = styled(Text)``; -/* - Note: - -webkit-line-clamp may seem like a wierd way to doing this - however, it is getting more and more useful with more browser support. - It suffices for our target browsers - More info: https://css-tricks.com/line-clampin/ -*/ - export const TextContainer = styled.div` && { height: 100%; width: 100%; } `; + export const StyledText = styled(Text)<{ scroll: boolean; textAlign: string; diff --git a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx index ca23da428d..73e3fa87c6 100644 --- a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx +++ b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx @@ -72,6 +72,7 @@ export enum EventType { ON_VIDEO_END = "ON_VIDEO_END", ON_VIDEO_PLAY = "ON_VIDEO_PLAY", ON_VIDEO_PAUSE = "ON_VIDEO_PAUSE", + ON_RATE_CHANGED = "ON_RATE_CHANGED", ON_IFRAME_URL_CHANGED = "ON_IFRAME_URL_CHANGED", ON_IFRAME_MESSAGE_RECEIVED = "ON_IFRAME_MESSAGE_RECEIVED", } diff --git a/app/client/src/constants/Colors.tsx b/app/client/src/constants/Colors.tsx index 3019078c18..17ff1a3972 100644 --- a/app/client/src/constants/Colors.tsx +++ b/app/client/src/constants/Colors.tsx @@ -78,6 +78,8 @@ export const Colors: Record = { ALTO2: "#E0DEDE", SEA_SHELL: "#F1F1F1", DANUBE: "#6A86CE", + RATE_ACTIVE: "#FFCB45", + RATE_INACTIVE: "#F2F2F2", }; export type Color = typeof Colors[keyof typeof Colors]; diff --git a/app/client/src/constants/FieldExpectedValue.ts b/app/client/src/constants/FieldExpectedValue.ts index 295b5682c2..68c763f877 100644 --- a/app/client/src/constants/FieldExpectedValue.ts +++ b/app/client/src/constants/FieldExpectedValue.ts @@ -169,6 +169,16 @@ const FIELD_VALUES: Record< isVisible: "boolean", gridGap: "number", }, + RATE_WIDGET: { + maxCount: "number", + defaultRate: "number", + activeColor: "string", + inactiveColor: "string", + size: "RATE_SMALL | RATE_MEDIUM | RATE_LARGE", + tooltips: "Array", + isVisible: "boolean", + isDisabled: "boolean", + }, IFRAME_WIDGET: { source: "string", title: "string", diff --git a/app/client/src/constants/HelpConstants.ts b/app/client/src/constants/HelpConstants.ts index b3bb6bb75b..357f17b519 100644 --- a/app/client/src/constants/HelpConstants.ts +++ b/app/client/src/constants/HelpConstants.ts @@ -115,6 +115,10 @@ export const HelpMap = { path: "/widget-reference/switch", searchKey: "Switch", }, + RATE_WIDGET: { + path: "/widget-reference/rate", + searchKey: "Rate", + }, IFRAME_WIDGET: { path: "/widget-reference/iframe", searchKey: "Iframe", diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index 0726eeaaef..1c91907ac9 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -27,6 +27,7 @@ export enum WidgetTypes { LIST_WIDGET = "LIST_WIDGET", SWITCH_WIDGET = "SWITCH_WIDGET", TABS_MIGRATOR_WIDGET = "TABS_MIGRATOR_WIDGET", + RATE_WIDGET = "RATE_WIDGET", IFRAME_WIDGET = "IFRAME_WIDGET", } @@ -148,3 +149,17 @@ export const TEXT_SIZES = { }; export type TextSize = keyof typeof TextSizes; + +export enum RateSizes { + SMALL = "SMALL", + MEDIUM = "MEDIUM", + LARGE = "LARGE", +} + +export const RATE_SIZES = { + SMALL: 12, + MEDIUM: 16, + LARGE: 21, +}; + +export type RateSize = keyof typeof RateSizes; diff --git a/app/client/src/constants/WidgetValidation.ts b/app/client/src/constants/WidgetValidation.ts index 58df341dc9..af3ca9e249 100644 --- a/app/client/src/constants/WidgetValidation.ts +++ b/app/client/src/constants/WidgetValidation.ts @@ -10,6 +10,7 @@ export enum VALIDATION_TYPES { BOOLEAN = "BOOLEAN", OBJECT = "OBJECT", ARRAY = "ARRAY", + ARRAY_OPTIONAL = "ARRAY_OPTIONAL", TABLE_DATA = "TABLE_DATA", OPTIONS_DATA = "OPTIONS_DATA", DATE_ISO_STRING = "DATE_ISO_STRING", @@ -31,6 +32,8 @@ export enum VALIDATION_TYPES { ROW_INDICES = "ROW_INDICES", IMAGE = "IMAGE", TABS_DATA = "TABS_DATA", + RATE_DEFAULT_RATE = "RATE_DEFAULT_RATE", + RATE_MAX_COUNT = "RATE_MAX_COUNT", COLOR_PICKER_TEXT = "COLOR_PICKER_TEXT", } diff --git a/app/client/src/icons/WidgetIcons.tsx b/app/client/src/icons/WidgetIcons.tsx index 56be404526..1dac6b81ac 100644 --- a/app/client/src/icons/WidgetIcons.tsx +++ b/app/client/src/icons/WidgetIcons.tsx @@ -22,6 +22,7 @@ import { ReactComponent as FormIcon } from "assets/icons/widget/form.svg"; import { ReactComponent as MapIcon } from "assets/icons/widget/map.svg"; import { ReactComponent as ModalIcon } from "assets/icons/widget/modal.svg"; import { ReactComponent as ListIcon } from "assets/icons/widget/list.svg"; +import { ReactComponent as RatingIcon } from "assets/icons/widget/rating.svg"; import { ReactComponent as EmbedIcon } from "assets/icons/widget/embed.svg"; /* eslint-disable react/display-name */ @@ -143,6 +144,11 @@ export const WidgetIcons: { ), + RATE_WIDGET: (props: IconProps) => ( + + + + ), IFRAME_WIDGET: (props: IconProps) => ( diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 7dca03b71b..d685885bf6 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -1091,6 +1091,19 @@ const WidgetConfigResponse: WidgetConfigReducerState = { ], }, }, + RATE_WIDGET: { + rows: 1 * GRID_DENSITY_MIGRATION_V1, + columns: 2.5 * GRID_DENSITY_MIGRATION_V1, + maxCount: 5, + defaultRate: 5, + activeColor: Colors.RATE_ACTIVE, + inactiveColor: Colors.RATE_INACTIVE, + size: "MEDIUM", + isRequired: false, + isAllowHalf: false, + isDisabled: false, + widgetName: "Rating", + }, [WidgetTypes.IFRAME_WIDGET]: { source: "https://www.wikipedia.org/", borderOpacity: 100, diff --git a/app/client/src/mockResponses/WidgetSidebarResponse.tsx b/app/client/src/mockResponses/WidgetSidebarResponse.tsx index c22bb410ab..7d98af10d7 100644 --- a/app/client/src/mockResponses/WidgetSidebarResponse.tsx +++ b/app/client/src/mockResponses/WidgetSidebarResponse.tsx @@ -105,6 +105,11 @@ const WidgetSidebarResponse: WidgetCardProps[] = [ widgetCardName: "Modal", key: generateReactKey(), }, + { + type: "RATE_WIDGET", + widgetCardName: "Rating", + key: generateReactKey(), + }, { type: "IFRAME_WIDGET", widgetCardName: "Iframe", diff --git a/app/client/src/reducers/entityReducers/widgetConfigReducer.tsx b/app/client/src/reducers/entityReducers/widgetConfigReducer.tsx index 5b70953c39..8b40f82547 100644 --- a/app/client/src/reducers/entityReducers/widgetConfigReducer.tsx +++ b/app/client/src/reducers/entityReducers/widgetConfigReducer.tsx @@ -29,6 +29,7 @@ import { VideoWidgetProps } from "widgets/VideoWidget"; import { SkeletonWidgetProps } from "../../widgets/SkeletonWidget"; import { SwitchWidgetProps } from "widgets/SwitchWidget"; import { ListWidgetProps } from "../../widgets/ListWidget/ListWidget"; +import { RateWidgetProps } from "../../widgets/RateWidget"; import { IframeWidgetProps } from "widgets/IframeWidget"; const initialState: WidgetConfigReducerState = WidgetConfigResponse; @@ -83,6 +84,7 @@ export interface WidgetConfigReducerState { ICON_WIDGET: Partial & WidgetConfigProps; SKELETON_WIDGET: Partial & WidgetConfigProps; LIST_WIDGET: Partial> & WidgetConfigProps; + RATE_WIDGET: Partial & WidgetConfigProps; IFRAME_WIDGET: Partial & WidgetConfigProps; }; configVersion: number; diff --git a/app/client/src/utils/WidgetRegistry.tsx b/app/client/src/utils/WidgetRegistry.tsx index b09af9df58..c02776fc04 100644 --- a/app/client/src/utils/WidgetRegistry.tsx +++ b/app/client/src/utils/WidgetRegistry.tsx @@ -103,6 +103,10 @@ import SwitchWidget, { import TabsMigratorWidget, { ProfiledTabsMigratorWidget, } from "widgets/Tabs/TabsMigrator"; +import RateWidget, { + RateWidgetProps, + ProfiledRateWidget, +} from "widgets/RateWidget"; import IframeWidget, { IframeWidgetProps, ProfiledIframeWidget, @@ -460,6 +464,19 @@ export default class WidgetBuilderRegistry { ModalWidget.getPropertyPaneConfig(), ); + WidgetFactory.registerWidgetBuilder( + "RATE_WIDGET", + { + buildWidget(widgetData: RateWidgetProps): JSX.Element { + return ; + }, + }, + RateWidget.getDerivedPropertiesMap(), + RateWidget.getDefaultPropertiesMap(), + RateWidget.getMetaPropertiesMap(), + RateWidget.getPropertyPaneConfig(), + ); + WidgetFactory.registerWidgetBuilder( WidgetTypes.IFRAME_WIDGET, { diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index 06a2ecc073..59ce9053f0 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -246,6 +246,13 @@ export const entityDefinitions = { items: generateTypeDef(widget.items), listData: generateTypeDef(widget.listData), }), + RATE_WIDGET: { + "!doc": "Rating widget is used to display ratings in your app.", + "!url": "https://docs.appsmith.com/widget-reference/rate", + isVisible: isVisible, + value: "number", + maxCount: "number", + }, IFRAME_WIDGET: { "!doc": "Iframe widget is used to display iframes in your app.", "!url": "https://docs.appsmith.com/widget-reference/iframe", diff --git a/app/client/src/widgets/RateWidget/index.tsx b/app/client/src/widgets/RateWidget/index.tsx new file mode 100644 index 0000000000..ffd7aab1a5 --- /dev/null +++ b/app/client/src/widgets/RateWidget/index.tsx @@ -0,0 +1,191 @@ +import React from "react"; +import BaseWidget, { WidgetProps, WidgetState } from "../BaseWidget"; +import { WidgetType, RateSize } from "constants/WidgetConstants"; +import RateComponent from "components/designSystems/blueprint/RateComponent"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; +import { DerivedPropertiesMap } from "utils/WidgetFactory"; +import * as Sentry from "@sentry/react"; +import withMeta, { WithMeta } from "widgets/MetaHOC"; +import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; + +class RateWidget extends BaseWidget { + static getPropertyPaneConfig() { + return [ + { + sectionName: "General", + children: [ + { + propertyName: "maxCount", + helpText: "Sets the maximum limit of the number of stars", + label: "Max count", + controlType: "INPUT_TEXT", + placeholderText: "Enter max count", + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.RATE_MAX_COUNT, + }, + { + propertyName: "defaultRate", + helpText: "Sets the default number of stars", + label: "Default rate", + controlType: "INPUT_TEXT", + placeholderText: "Enter default value", + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.RATE_DEFAULT_RATE, + }, + { + propertyName: "activeColor", + label: "Active color", + controlType: "COLOR_PICKER", + isBindProperty: false, + isTriggerProperty: false, + }, + { + propertyName: "inactiveColor", + label: "Inactive color", + controlType: "COLOR_PICKER", + isBindProperty: false, + isTriggerProperty: false, + }, + { + propertyName: "tooltips", + helpText: "Sets the tooltip contents of stars", + label: "Tooltips", + controlType: "INPUT_TEXT", + placeholderText: "Enter tooltips array", + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.ARRAY_OPTIONAL, + }, + { + propertyName: "size", + label: "Size", + controlType: "DROP_DOWN", + options: [ + { + label: "Small", + value: "SMALL", + }, + { + label: "Medium", + value: "MEDIUM", + }, + { + label: "Large", + value: "LARGE", + }, + ], + isBindProperty: false, + isTriggerProperty: false, + }, + { + propertyName: "isAllowHalf", + helpText: "Controls if user can submit half stars", + label: "Allow half stars", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, + }, + { + propertyName: "isVisible", + helpText: "Controls the visibility of the widget", + label: "Visible", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, + }, + { + propertyName: "isDisabled", + helpText: "Disables input to the widget", + label: "Disabled", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, + }, + ], + }, + { + sectionName: "Actions", + children: [ + { + helpText: "Triggers an action when the rate is changed", + propertyName: "onRateChanged", + label: "onChange", + controlType: "ACTION_SELECTOR", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + }, + ], + }, + ]; + } + + static getDefaultPropertiesMap(): Record { + return { + rate: "defaultRate", + }; + } + + static getDerivedPropertiesMap(): DerivedPropertiesMap { + return { + value: `{{ this.rate }}`, + }; + } + + static getMetaPropertiesMap(): Record { + return { + rate: undefined, + }; + } + + valueChangedHandler = (value: number) => { + this.props.updateWidgetMetaProperty("rate", value, { + triggerPropertyName: "onRateChanged", + dynamicString: this.props.onRateChanged, + event: { + type: EventType.ON_RATE_CHANGED, + }, + }); + }; + + getPageView() { + return ( + (this.props.rate || this.props.rate === 0) && ( + + ) + ); + } + + getWidgetType(): WidgetType { + return "RATE_WIDGET"; + } +} + +export interface RateWidgetProps extends WidgetProps, WithMeta { + maxCount: number; + size: RateSize; + defaultRate?: number; + rate?: number; + activeColor?: string; + inactiveColor?: string; + isAllowHalf?: boolean; + onRateChanged?: string; + tooltips?: Array; +} + +export default RateWidget; +export const ProfiledRateWidget = Sentry.withProfiler(withMeta(RateWidget)); diff --git a/app/client/src/workers/validations.test.ts b/app/client/src/workers/validations.test.ts index 0bf9acaf29..6185318aa2 100644 --- a/app/client/src/workers/validations.test.ts +++ b/app/client/src/workers/validations.test.ts @@ -509,6 +509,190 @@ describe("List data validator", () => { }); }); +describe("Rating widget : defaultRate", () => { + const validator = VALIDATORS.RATE_DEFAULT_RATE; + it("An input is not a number", () => { + const cases = [ + { + input: undefined, + output: { + isValid: false, + parsed: 0, + message: 'This value does not evaluate to type "number"', + }, + }, + { + input: "", + output: { + isValid: true, + parsed: 0, + }, + }, + { + input: "string", + output: { + isValid: false, + parsed: 0, + message: 'This value does not evaluate to type "number"', + }, + }, + ]; + for (const testCase of cases) { + const response = validator(testCase.input, DUMMY_WIDGET, {}); + expect(response).toStrictEqual(testCase.output); + } + }); + + it("An input is a number & maxCount", () => { + const cases = [ + { + input: 3, + output: { + isValid: true, + parsed: 3, + }, + }, + { + input: 5, + output: { + isValid: true, + parsed: 5, + }, + }, + { + input: 6, + output: { + isValid: false, + parsed: 6, + message: "This value must be less than or equal to max count", + }, + }, + ]; + for (const testCase of cases) { + const response = validator( + testCase.input, + { ...DUMMY_WIDGET, maxCount: 5 }, + {}, + ); + expect(response).toStrictEqual(testCase.output); + } + }); + + it("An input is a number & isAllowedHalf=true", () => { + const cases = [ + { + input: 3, + output: { + isValid: true, + parsed: 3, + }, + }, + { + input: 3.5, + output: { + isValid: true, + parsed: 3.5, + }, + }, + ]; + for (const testCase of cases) { + const response = validator( + testCase.input, + { ...DUMMY_WIDGET, isAllowHalf: true }, + {}, + ); + expect(response).toStrictEqual(testCase.output); + } + }); + + it("An input is a number & isAllowedHalf=false", () => { + const cases = [ + { + input: 3, + output: { + isValid: true, + parsed: 3, + }, + }, + { + input: 3.5, + output: { + isValid: false, + parsed: 3.5, + message: `This value can be a decimal onlf if 'Allow half' is true`, + }, + }, + ]; + for (const testCase of cases) { + const response = validator( + testCase.input, + { ...DUMMY_WIDGET, isAllowHalf: false }, + {}, + ); + expect(response).toStrictEqual(testCase.output); + } + }); +}); + +describe("Rating widget : maxCount", () => { + const validator = VALIDATORS.RATE_MAX_COUNT; + it("An input is not a number", () => { + const cases = [ + { + input: undefined, + output: { + isValid: false, + parsed: 0, + message: 'This value does not evaluate to type "number"', + }, + }, + { + input: "", + output: { + isValid: true, + parsed: 0, + }, + }, + { + input: "string", + output: { + isValid: false, + parsed: 0, + message: 'This value does not evaluate to type "number"', + }, + }, + ]; + for (const testCase of cases) { + const response = validator(testCase.input, DUMMY_WIDGET, {}); + expect(response).toStrictEqual(testCase.output); + } + }); + + it("An input is a number, should be an integer", () => { + const cases = [ + { + input: 3, + output: { + isValid: true, + parsed: 3, + }, + }, + { + input: 3.5, + output: { + isValid: false, + parsed: 3.5, + message: "This value must be integer", + }, + }, + ]; + for (const testCase of cases) { + const response = validator(testCase.input, DUMMY_WIDGET, {}); + expect(response).toStrictEqual(testCase.output); + } + }); +}); + describe("Color Picker Text validator", () => { const validator = VALIDATORS.COLOR_PICKER_TEXT; const inputs = [ diff --git a/app/client/src/workers/validations.ts b/app/client/src/workers/validations.ts index b75281f7c8..5716d7e8c0 100644 --- a/app/client/src/workers/validations.ts +++ b/app/client/src/workers/validations.ts @@ -227,6 +227,39 @@ export const VALIDATORS: Record = { }; } }, + [VALIDATION_TYPES.ARRAY_OPTIONAL]: (value: any): ValidationResponse => { + let parsed = value; + try { + if (!value) { + return { + isValid: true, + parsed: undefined, + transformed: undefined, + }; + } + if (isString(value)) { + parsed = JSON.parse(parsed as string); + } + + if (!Array.isArray(parsed)) { + return { + isValid: false, + parsed: [], + transformed: parsed, + message: `${WIDGET_TYPE_VALIDATION_ERROR} "Array"`, + }; + } + + return { isValid: true, parsed, transformed: parsed }; + } catch (e) { + return { + isValid: false, + parsed: [], + transformed: parsed, + message: `${WIDGET_TYPE_VALIDATION_ERROR} "Array"`, + }; + } + }, [VALIDATION_TYPES.TABS_DATA]: ( value: any, props: WidgetProps, @@ -1034,6 +1067,56 @@ export const VALIDATORS: Record = { message: `${WIDGET_TYPE_VALIDATION_ERROR}: number[]`, }; }, + [VALIDATION_TYPES.RATE_DEFAULT_RATE]: ( + value: any, + props: WidgetProps, + ): ValidationResponse => { + const { isValid, message, parsed } = VALIDATORS[VALIDATION_TYPES.NUMBER]( + value, + props, + ); + if (!isValid) { + return { isValid, parsed, message }; + } + // default rate must be less than max count + if (!isNaN(props.maxCount) && Number(value) > Number(props.maxCount)) { + return { + isValid: false, + parsed, + message: `This value must be less than or equal to max count`, + }; + } + // default rate can be a decimal onlf if Allow half property is true + if (!props.isAllowHalf && !Number.isInteger(parsed)) { + return { + isValid: false, + parsed, + message: `This value can be a decimal onlf if 'Allow half' is true`, + }; + } + return { isValid, parsed }; + }, + [VALIDATION_TYPES.RATE_MAX_COUNT]: ( + value: any, + props: WidgetProps, + ): ValidationResponse => { + const { isValid, message, parsed } = VALIDATORS[VALIDATION_TYPES.NUMBER]( + value, + props, + ); + if (!isValid) { + return { isValid, parsed, message }; + } + // max count must be integer + if (!Number.isInteger(parsed)) { + return { + isValid: false, + parsed, + message: `This value must be integer`, + }; + } + return { isValid, parsed }; + }, [VALIDATION_TYPES.COLOR_PICKER_TEXT]: ( value: any, props: WidgetProps, diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 00433b3842..13f2f6816c 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -3713,6 +3713,11 @@ version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" +"@types/lodash@^4.14.105": + version "4.14.170" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6" + integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q== + "@types/lodash@^4.14.120": version "4.14.162" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.162.tgz#65d78c397e0d883f44afbf1f7ba9867022411470" @@ -3932,6 +3937,15 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/react@^16.0.40": + version "16.14.8" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.8.tgz#4aee3ab004cb98451917c9b7ada3c7d7e52db3fe" + integrity sha512-QN0/Qhmx+l4moe7WJuTxNiTsjBwlBGHqKGvInSQCBdo7Qio0VtOqwsC0Wq7q3PbJlB0cR4Y4CVo1OOe6BOsOmA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/reactcss@*": version "1.2.3" resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834" @@ -3961,6 +3975,11 @@ dependencies: "@types/node" "*" +"@types/scheduler@*": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" + integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== + "@types/set-cookie-parser@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.0.tgz#10cc0446bad372827671a5195fbd14ebce4a9baf" @@ -14599,6 +14618,14 @@ react-popper@^2.2.4, react-popper@^2.2.5: react-fast-compare "^3.0.1" warning "^4.0.2" +react-rating@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/react-rating/-/react-rating-2.0.5.tgz#2c9d7ebe5907db0361ba28b7c19f0394e6e4dd76" + integrity sha512-uldxgLCe5bzqGX7V+7/bPgQQj2Kok6eiMgTMxjKOhfhnQkFLDlc4TjMlp7gaJFAHWdbiOnqpiShI7z8as6oWtg== + dependencies: + "@types/lodash" "^4.14.105" + "@types/react" "^16.0.40" + react-redux@^7.1.1, react-redux@^7.1.3: version "7.2.1" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.1.tgz#8dedf784901014db2feca1ab633864dee68ad985" From fb9925cdc3b157a27565903342135b02cf08bc73 Mon Sep 17 00:00:00 2001 From: Vicky Bansal <67091118+vicky-primathon@users.noreply.github.com> Date: Fri, 2 Jul 2021 16:53:38 +0530 Subject: [PATCH 05/79] Fix-Input widget with text area expands over boundaries (#5114) * Fix input widget resize issue due to textarea resizable Added cypress test to validate textarea cannot be resized * Removed cypress test and added jest test for input textarea css testing --- .../blueprint/InputComponent.test.tsx | 52 +++++++++++++++++++ .../blueprint/InputComponent.tsx | 1 + 2 files changed, 53 insertions(+) create mode 100644 app/client/src/components/designSystems/blueprint/InputComponent.test.tsx diff --git a/app/client/src/components/designSystems/blueprint/InputComponent.test.tsx b/app/client/src/components/designSystems/blueprint/InputComponent.test.tsx new file mode 100644 index 0000000000..ad89728f32 --- /dev/null +++ b/app/client/src/components/designSystems/blueprint/InputComponent.test.tsx @@ -0,0 +1,52 @@ +import store from "store"; +import React from "react"; +import { ThemeProvider, theme } from "constants/DefaultTheme"; +import InputComponent from "components/designSystems/blueprint/InputComponent"; +import { Provider } from "react-redux"; +import ReactDOM from "react-dom"; +import { act } from "react-dom/test-utils"; + +let container: HTMLDivElement | null; + +beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); +}); + +afterEach(() => { + document.body.removeChild(container); + container = null; +}); + +describe("", () => { + it("contains textarea with resize disabled", () => { + act(() => { + ReactDOM.render( + + + { + console.log(state); + }} + onValueChange={(valueAsString: string) => { + console.log(valueAsString); + }} + showError={false} + value="something" + widgetId="24234r35" + /> + + , + container, + ); + }); + const textarea = container?.querySelector("textarea"); + const styles = textarea ? getComputedStyle(textarea) : { resize: "" }; + expect(styles.resize).toEqual("none"); + }); +}); diff --git a/app/client/src/components/designSystems/blueprint/InputComponent.tsx b/app/client/src/components/designSystems/blueprint/InputComponent.tsx index 24d684b7fc..6595466ebe 100644 --- a/app/client/src/components/designSystems/blueprint/InputComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/InputComponent.tsx @@ -201,6 +201,7 @@ class InputComponent extends React.Component< onFocus={() => this.setFocusState(true)} onKeyDown={this.onKeyDownTextArea} placeholder={this.props.placeholder} + style={{ resize: "none" }} value={this.props.value} /> ); From b6e205e5b2296b1297dd8e9542294cb6be9a94c9 Mon Sep 17 00:00:00 2001 From: Vicky Bansal <67091118+vicky-primathon@users.noreply.github.com> Date: Fri, 2 Jul 2021 16:57:20 +0530 Subject: [PATCH 06/79] Fix-Rename compact mode property in Table Widget to Row Height (#5496) * Change compact mode title to Row height in property pane configurations --- .../ClientSideTests/DisplayWidgets/Table_spec.js | 8 ++++---- app/client/cypress/locators/publishWidgetspage.json | 4 ++-- .../src/widgets/TableWidget/TablePropertyPaneConfig.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js index 7bd4ae775d..d46969722f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js @@ -264,16 +264,16 @@ describe("Table Widget Functionality", function() { const tabValue = tabData; expect(tabValue).to.be.equal("Lindsay Ferguson"); cy.log("the value is" + tabValue); - cy.get(publish.compactMode).click(); - cy.get(publish.compactOpt) + cy.get(publish.rowHeight).click(); + cy.get(publish.rowHeightOpt) .contains("Tall") .click(); cy.scrollTabledataPublish("3", "3").then((tabData) => { const tabValue = tabData; expect(tabValue).to.be.equal("Byron Fields"); }); - cy.get(publish.compactMode).click(); - cy.get(publish.compactOpt) + cy.get(publish.rowHeight).click(); + cy.get(publish.rowHeightOpt) .contains("Short") .click(); cy.readTabledataPublish("4", "3").then((tabData) => { diff --git a/app/client/cypress/locators/publishWidgetspage.json b/app/client/cypress/locators/publishWidgetspage.json index 900e80d117..8e91ef0bfe 100644 --- a/app/client/cypress/locators/publishWidgetspage.json +++ b/app/client/cypress/locators/publishWidgetspage.json @@ -31,8 +31,8 @@ "inputValue": ".t--table-filter-value-input", "canvas": ".canvas", "removeFilter": ".t--table-filter-remove-btn", - "compactMode": ".t--table-compact-mode-toggle-btn", - "compactOpt": ".t--table-compact-mode-option", + "rowHeight": ".t--table-compact-mode-toggle-btn", + "rowHeightOpt": ".t--table-compact-mode-option", "visibilityMode": ".t--table-column-visibility-toggle-btn", "visibilityOpt": ".option-title", "containerWidget": ".t--widget-containerwidget", diff --git a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts index 92a6389ac3..781d45a1e2 100644 --- a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts +++ b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts @@ -769,9 +769,9 @@ export default [ isTriggerProperty: false, }, { - helpText: "Toggle visibility of the compact mode", + helpText: "Toggle visibility of the row height", propertyName: "isVisibleCompactMode", - label: "Compact Mode", + label: "Row Height", controlType: "SWITCH", isBindProperty: false, isTriggerProperty: false, From 0c4787bf8523832db004aa95205b92ce0b8134fc Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Fri, 2 Jul 2021 17:23:52 +0530 Subject: [PATCH 07/79] Fix YAML syntax in configmap for K8s --- deploy/k8s/scripts/appsmith-configmap.yaml.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/k8s/scripts/appsmith-configmap.yaml.sh b/deploy/k8s/scripts/appsmith-configmap.yaml.sh index bf7b314546..beb60f4194 100644 --- a/deploy/k8s/scripts/appsmith-configmap.yaml.sh +++ b/deploy/k8s/scripts/appsmith-configmap.yaml.sh @@ -30,7 +30,7 @@ data: APPSMITH_REDIS_URL: redis://redis-service:6379 APPSMITH_MONGODB_URI: $mongo_protocol$encoded_mongo_root_user:$encoded_mongo_root_password@$mongo_host/$mongo_db?retryWrites=true&authSource=admin APPSMITH_DISABLE_TELEMETRY: "$disable_telemetry" - APPSMITH_RECAPTCHA_SITE_KEY= "" - APPSMITH_RECAPTCHA_SECRET_KEY= "" - APPSMITH_RECAPTCHA_ENABLED= "false" + APPSMITH_RECAPTCHA_SITE_KEY: "" + APPSMITH_RECAPTCHA_SECRET_KEY: "" + APPSMITH_RECAPTCHA_ENABLED: "false" EOF From a89ec9a7812a5e4467e0ff1de486b8a107be750b Mon Sep 17 00:00:00 2001 From: Confidence Okoghenun Date: Fri, 2 Jul 2021 16:26:24 +0100 Subject: [PATCH 08/79] chore: Adds live demo #5 --- office_hours.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/office_hours.md b/office_hours.md index 9ad98f54f2..48fef782aa 100644 --- a/office_hours.md +++ b/office_hours.md @@ -21,6 +21,17 @@ You can find the archives of the calls below with a brief summary of each event. ## Archives +Appsmith Live Demo #5: Building a Tweetdeck Clone With n8n and Appsmith + +Video Link + +#### Summary + +We had our friend from n8n join us in this Live Demo session to build a Tweetdeck clone in 20 minutes. Confidence from Appsmith built the demo app +using Appsmith for the frontend and building the workflow automation with n8n. + +------------------ + Community Call Jun 24, 2021: All about Integrations Video Link From 234ba480f83510eb23c54ae7440da4a33244327f Mon Sep 17 00:00:00 2001 From: akash-codemonk <67054171+akash-codemonk@users.noreply.github.com> Date: Mon, 5 Jul 2021 10:53:57 +0530 Subject: [PATCH 09/79] Fix flaky debugger test (#5555) --- .../Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js index 50309718f1..992fe23a68 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js @@ -15,7 +15,6 @@ describe("Debugger logs", function() { }); it("Reset debugger state", function() { - cy.openPropertyPane("buttonwidget"); cy.get(".t--property-control-visible") .find(".t--js-toggle") .click(); From 54c3cfc0281bf3bfb614213e6567a262eb2f0399 Mon Sep 17 00:00:00 2001 From: Yash Vibhandik Date: Mon, 5 Jul 2021 10:56:50 +0530 Subject: [PATCH 10/79] FIX #4813 : display filter count and highlighted filter button (#4916) - add condition for display filter count and highlighted filter button - update boolean prop to color string to handled highlight filtered text --- .../designSystems/appsmith/TableComponent/TableAction.tsx | 8 ++++++-- .../appsmith/TableComponent/TableFilters.tsx | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableAction.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableAction.tsx index e3c1723f6d..54d4f4c443 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/TableAction.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableAction.tsx @@ -10,13 +10,14 @@ interface TableActionProps { title: string; children: React.ReactNode; icon?: React.ReactNode; + titleColor?: string; } export const TableIconWrapper = styled.div<{ selected?: boolean; disabled?: boolean; + titleColor?: string; }>` - background: ${(props) => (props.selected ? Colors.Gallery : "transparent")}; height: 38px; display: flex; align-items: center; @@ -26,6 +27,8 @@ export const TableIconWrapper = styled.div<{ color: ${(props) => (props.selected ? Colors.CODE_GRAY : Colors.GRAY)}; .action-title { margin-left: 4px; + white-space: nowrap; + color: ${(props) => props.titleColor || Colors.GRAY}; } position: relative; margin-left: 5px; @@ -48,9 +51,10 @@ function TableAction(props: TableActionProps) { className={props.className} onClick={handleIconClick} selected={props.selected} + titleColor={props.titleColor} > diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableFilters.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableFilters.tsx index 5347f5235f..d05dfbb753 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/TableFilters.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableFilters.tsx @@ -184,7 +184,8 @@ function TableFilters(props: TableFilterProps) { selectMenu(selected); }} selected={selected} - title="Filters" + title={`Filters${hasAnyFilters ? ` (${filters.length})` : ""}`} + titleColor={hasAnyFilters ? Colors.CODE_GRAY : Colors.GRAY} > From 02384eed369b26f06d876424d8e7f2b6c1dbf384 Mon Sep 17 00:00:00 2001 From: Yash Vibhandik Date: Mon, 5 Jul 2021 10:58:47 +0530 Subject: [PATCH 11/79] updated isLoading state when event action completed in Filepicker widget (#5407) --- app/client/src/widgets/FilepickerWidget.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/client/src/widgets/FilepickerWidget.tsx b/app/client/src/widgets/FilepickerWidget.tsx index af0b911ba5..ff7a15f43e 100644 --- a/app/client/src/widgets/FilepickerWidget.tsx +++ b/app/client/src/widgets/FilepickerWidget.tsx @@ -384,6 +384,7 @@ class FilePickerWidget extends BaseWidget< dynamicString: this.props.onFilesSelected, event: { type: EventType.ON_FILES_SELECTED, + callback: this.handleActionComplete, }, }); @@ -391,6 +392,10 @@ class FilePickerWidget extends BaseWidget< } }; + handleActionComplete = () => { + this.setState({ isLoading: false }); + }; + componentDidUpdate(prevProps: FilePickerWidgetProps) { super.componentDidUpdate(prevProps); if ( From 8ef7c62bb6537167c45eba5adeecc61512b1284f Mon Sep 17 00:00:00 2001 From: Yash Vibhandik Date: Mon, 5 Jul 2021 11:01:47 +0530 Subject: [PATCH 12/79] =?UTF-8?q?FIX=20#4376=20:=20update=20default=20conf?= =?UTF-8?q?ig=20response=20to=20render=20proper=20data=20for=20=E2=80=A6?= =?UTF-8?q?=20(#4778)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - update default config response to render proper data for table widget drop on canvas - clone data for primaryColumns --- .../mockResponses/WidgetConfigResponse.tsx | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index d685885bf6..5210a8b3d2 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -1,7 +1,7 @@ import { WidgetConfigReducerState } from "reducers/entityReducers/widgetConfigReducer"; import { WidgetProps } from "widgets/BaseWidget"; import moment from "moment-timezone"; -import { cloneDeep, get, indexOf, isString } from "lodash"; +import { cloneDeep, get, indexOf, isString, set } from "lodash"; import { generateReactKey } from "utils/generators"; import { WidgetTypes } from "constants/WidgetConstants"; import { BlueprintOperationTypes } from "sagas/WidgetBlueprintSagasEnums"; @@ -264,6 +264,32 @@ const WidgetConfigResponse: WidgetConfigReducerState = { step: 62, status: 75, }, + blueprint: { + operations: [ + { + type: BlueprintOperationTypes.MODIFY_PROPS, + fn: (widget: WidgetProps & { children?: WidgetProps[] }) => { + const primaryColumns = cloneDeep(widget.primaryColumns); + const columnIds = Object.keys(primaryColumns); + columnIds.forEach((columnId) => { + set( + primaryColumns, + `${columnId}.computedValue`, + `{{${widget.widgetName}.sanitizedTableData.map((currentRow) => { return currentRow.${columnId}})}}`, + ); + }); + const updatePropertyMap = [ + { + widgetId: widget.widgetId, + propertyName: "primaryColumns", + propertyValue: primaryColumns, + }, + ]; + return updatePropertyMap; + }, + }, + ], + }, isVisibleSearch: true, isVisibleFilters: true, isVisibleDownload: true, From c2a1c1feb4793c78984fa0e013690a31ed9ba831 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Mon, 5 Jul 2021 11:19:43 +0530 Subject: [PATCH 13/79] Don't log debug level logs in prod (#5527) --- app/client/build.sh | 2 +- app/client/package.json | 2 +- app/client/public/index.html | 10 ++--- .../CommentsCarouselModal.tsx | 2 +- .../TableComponent/CommonUtilities.test.ts | 1 - .../GlobalSearch/parseDocumentationContent.ts | 3 +- app/client/src/configs/index.ts | 2 +- .../Editor/APIEditor/ResultPagination.tsx | 2 - app/client/src/pages/Editor/Canvas.tsx | 5 ++- .../Explorer/Widgets/WidgetContextMenu.tsx | 1 - .../CustomizedDropdown/dropdownHelpers.tsx | 3 +- app/client/src/sagas/CommentSagas/index.ts | 4 +- app/client/src/sagas/GlobalSearchSagas.ts | 3 +- app/client/src/utils/AppsmithUtils.tsx | 6 ++- app/client/src/utils/storage.ts | 39 +++++++++---------- .../src/widgets/ListWidget/ListWidget.tsx | 2 +- .../TableWidget/TablePropertyPaneConfig.ts | 2 - app/client/src/widgets/TableWidget/derived.js | 8 +++- 18 files changed, 51 insertions(+), 46 deletions(-) diff --git a/app/client/build.sh b/app/client/build.sh index c668865fc0..2f625ec5c4 100755 --- a/app/client/build.sh +++ b/app/client/build.sh @@ -6,7 +6,7 @@ GIT_SHA=$(eval git rev-parse HEAD) echo $GIT_SHA echo "Sentry Auth Token: $SENTRY_AUTH_TOKEN" -REACT_APP_SENTRY_RELEASE=$GIT_SHA EXTEND_ESLINT=true craco --max-old-space-size=4096 build --config craco.build.config.js +REACT_APP_SENTRY_RELEASE=$GIT_SHA REACT_APP_CLIENT_LOG_LEVEL=ERROR EXTEND_ESLINT=true craco --max-old-space-size=4096 build --config craco.build.config.js rm ./build/static/js/*.js.map echo "build finished" diff --git a/app/client/package.json b/app/client/package.json index 07c7b910f9..dbaf77633f 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -154,7 +154,7 @@ }, "scripts": { "analyze": "source-map-explorer 'build/static/js/*.js'", - "start": "BROWSER=none EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT HOST=dev.appsmith.com craco start", + "start": "BROWSER=none EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT REACT_APP_CLIENT_LOG_LEVEL=debug HOST=dev.appsmith.com craco start", "build": "./build.sh", "build-local": "craco --max-old-space-size=4096 build --config craco.build.config.js", "build-staging": "REACT_APP_ENVIRONMENT=STAGING craco --max-old-space-size=4096 build --config craco.build.config.js", diff --git a/app/client/public/index.html b/app/client/public/index.html index df6675bb58..a022cc7b1c 100755 --- a/app/client/public/index.html +++ b/app/client/public/index.html @@ -34,10 +34,10 @@
-
+