From 4213d8ba6fa04b22a5b124ccfd3b59c2d3eee9e1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 24 Jul 2024 17:22:06 +0200 Subject: [PATCH] a few polishings of UI and UX in Youtube Bar and Space Picker --- external/@worldbrain/memex-common | 2 +- .../ui/backup-pane/container.tsx | 4 +- .../ui/backup-pane/panes/overview.tsx | 4 +- .../ui/backup-pane/panes/setup-size.jsx | 4 +- src/common-ui/components/LoadingIndicator.css | 46 ------ src/common-ui/components/LoadingIndicator.tsx | 65 -------- src/common-ui/components/loading-blocker.css | 11 -- src/common-ui/components/loading-blocker.jsx | 19 --- .../ui/space-email-invites/index.tsx | 47 +++--- .../ui/space-email-invites/logic.ts | 142 ++++++++++-------- src/custom-lists/ui/space-links.tsx | 11 +- .../imports/components/EstimatesTable.jsx | 2 +- .../components/ResultListContainer.tsx | 28 ---- .../components/youtubeActionBar.tsx | 52 +++++-- 14 files changed, 155 insertions(+), 282 deletions(-) delete mode 100644 src/common-ui/components/LoadingIndicator.css delete mode 100644 src/common-ui/components/LoadingIndicator.tsx delete mode 100644 src/common-ui/components/loading-blocker.css delete mode 100644 src/common-ui/components/loading-blocker.jsx diff --git a/external/@worldbrain/memex-common b/external/@worldbrain/memex-common index 23b3dea9f0..a90bc84d72 160000 --- a/external/@worldbrain/memex-common +++ b/external/@worldbrain/memex-common @@ -1 +1 @@ -Subproject commit 23b3dea9f0561b8d23b15323a92ec243338678ee +Subproject commit a90bc84d72abd21248d674b503b44a7dd1d83e59 diff --git a/src/backup-restore/ui/backup-pane/container.tsx b/src/backup-restore/ui/backup-pane/container.tsx index 1b3a55c6f0..cfb170f29d 100644 --- a/src/backup-restore/ui/backup-pane/container.tsx +++ b/src/backup-restore/ui/backup-pane/container.tsx @@ -7,7 +7,7 @@ import { default as RunningBackup } from './panes/running-backup' import { default as OnboardingWhere } from './panes/setup-location' import OnboardingHow from './panes/setup-manual-or-automatic' import { default as OnboardingSize } from './panes/setup-size' -import LoadingBlocker from '../../../common-ui/components/loading-blocker' +import LoadingBlock from '@worldbrain/memex-common/lib/common-ui/components/loading-block' import * as logic from 'src/backup-restore/ui/backup-pane/container.logic' import RestoreWhere from 'src/backup-restore/ui/backup-pane/panes/restore-where' import RestoreRunning from 'src/backup-restore/ui/backup-pane/panes/restore-running' @@ -103,7 +103,7 @@ export default class BackupSettingsContainer extends Component { renderScreen() { const { screen } = this.state if (!screen) { - return + return } const screenConfig = SCREENS[screen] diff --git a/src/backup-restore/ui/backup-pane/panes/overview.tsx b/src/backup-restore/ui/backup-pane/panes/overview.tsx index 4602213281..dc223c767b 100644 --- a/src/backup-restore/ui/backup-pane/panes/overview.tsx +++ b/src/backup-restore/ui/backup-pane/panes/overview.tsx @@ -2,7 +2,7 @@ import React, { Component } from 'react' import classNames from 'classnames' import browser from 'webextension-polyfill' import { remoteFunction, runInBackground } from 'src/util/webextensionRPC' -import LoadingBlocker from '../../../../common-ui/components/loading-blocker' +import LoadingBlock from '@worldbrain/memex-common/lib/common-ui/components/loading-block' import RestoreConfirmation from '../components/restore-confirmation' import { withCurrentUser } from 'src/authentication/components/AuthConnector' import { WhiteSpacer10 } from 'src/common-ui/components/design-library/typography' @@ -267,7 +267,7 @@ export class OverviewContainer extends Component { render() { if (!this.state.backupTimes) { - return + return } return ( diff --git a/src/backup-restore/ui/backup-pane/panes/setup-size.jsx b/src/backup-restore/ui/backup-pane/panes/setup-size.jsx index 4c4c88b0f5..e4bdf7cc16 100644 --- a/src/backup-restore/ui/backup-pane/panes/setup-size.jsx +++ b/src/backup-restore/ui/backup-pane/panes/setup-size.jsx @@ -2,9 +2,9 @@ import React from 'react' import PropTypes from 'prop-types' import { remoteFunction } from 'src/util/webextensionRPC' import localStyles from './setup-size.css' -import LoadingBlocker from '../../../../common-ui/components/loading-blocker' import { PrimaryAction } from '@worldbrain/memex-common/lib/common-ui/components/PrimaryAction' import { WhiteSpacer20 } from 'src/common-ui/components/design-library/typography' +import LoadingBlock from '@worldbrain/memex-common/lib/common-ui/components/loading-block' const settingsStyle = require('src/options/settings/components/settings.css') @@ -40,7 +40,7 @@ export default class OnboardingSizeContainer extends React.Component { } renderLoadingIndicator() { - return + return } renderEstimationFailure() { diff --git a/src/common-ui/components/LoadingIndicator.css b/src/common-ui/components/LoadingIndicator.css deleted file mode 100644 index 25bb950c55..0000000000 --- a/src/common-ui/components/LoadingIndicator.css +++ /dev/null @@ -1,46 +0,0 @@ -.loader { - display: block; - position: relative; - font-size: 10px; - text-indent: -9999em; - width: 24px; - height: 24px; - border-radius: 50%; - background: #99879f; - background: gradient(left, #99879f 10%, rgba(60, 46, 71, 0) 42%); - background: linear-gradient(to right, #99879f 10%, rgba(60, 46, 71, 0) 42%); - animation: load 1.4s infinite linear; - transform: translateZ(0); -} - -.loader::before { - border-radius: 100% 0 0 0; - position: absolute; - top: 0; - left: 0; - content: ''; -} - -.loader::after { - background: #fff; - width: 65%; - height: 65%; - border-radius: 50%; - content: ''; - margin: auto; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; -} - -@keyframes load { - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -} diff --git a/src/common-ui/components/LoadingIndicator.tsx b/src/common-ui/components/LoadingIndicator.tsx deleted file mode 100644 index 1f873c14ae..0000000000 --- a/src/common-ui/components/LoadingIndicator.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { PureComponent } from 'react' -import { keyframes } from 'styled-components' -import styled from 'styled-components' - -const styles = require('./LoadingIndicator.css') - -export interface Props { - className?: string - size?: string -} - -class LoadingIndicator extends PureComponent { - render() { - return - } -} - -const load = () => { - return keyframes`0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - }` -} - -const Loader = styled.div<{ size: string }>` - display: block; - position: relative; - font-size: 10px; - text-indent: -9999em; - width: ${(props) => (props.size ? props.size : '24px')}; - height: ${(props) => (props.size ? props.size : '24px')}; - border-radius: 50%; - background: #99879f; - background: gradient(left, #99879f 10%, rgba(60, 46, 71, 0) 42%); - background: linear-gradient(to right, #99879f 10%, rgba(60, 46, 71, 0) 42%); - animation: load 1.4s infinite linear; - transform: translateZ(0); - animation: ${load} 1.4s infinite linear forwards; - - &::before { - border-radius: 100% 0 0 0; - position: absolute; - top: 0; - left: 0; - content: ''; - } - - &::after { - background: #fff; - width: 65%; - height: 65%; - border-radius: 50%; - content: ''; - margin: auto; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - } -` - -export default LoadingIndicator diff --git a/src/common-ui/components/loading-blocker.css b/src/common-ui/components/loading-blocker.css deleted file mode 100644 index c31da00a7b..0000000000 --- a/src/common-ui/components/loading-blocker.css +++ /dev/null @@ -1,11 +0,0 @@ -.loadingBlocker { - z-index: 2; - position: absolute; - top: 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; -} diff --git a/src/common-ui/components/loading-blocker.jsx b/src/common-ui/components/loading-blocker.jsx deleted file mode 100644 index 190828e73c..0000000000 --- a/src/common-ui/components/loading-blocker.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -// import PropTypes from 'prop-types' -// import classNames from 'classnames' -import LoadingIndicator from '@worldbrain/memex-common/lib/common-ui/components/loading-indicator' -import localStyles from './loading-blocker.css' - -export default function LoadingBlocker(props) { - return ( -
- -
- ) -} - -// PrimaryButton.propTypes = { -// children: PropTypes.node, -// disabled: PropTypes.bool, -// onClick: PropTypes.func.isRequired, -// } diff --git a/src/custom-lists/ui/space-email-invites/index.tsx b/src/custom-lists/ui/space-email-invites/index.tsx index a43069c853..74e1ca7a08 100644 --- a/src/custom-lists/ui/space-email-invites/index.tsx +++ b/src/custom-lists/ui/space-email-invites/index.tsx @@ -7,17 +7,19 @@ import { isValidEmail } from '@worldbrain/memex-common/lib/utils/email-validatio import TextField from '@worldbrain/memex-common/lib/common-ui/components/text-field' import { SharedListRoleID } from '@worldbrain/memex-common/lib/content-sharing/types' import { PrimaryAction } from '@worldbrain/memex-common/lib/common-ui/components/PrimaryAction' -import LoadingIndicator from '@worldbrain/memex-common/lib/common-ui/components/loading-indicator' import { normalizedStateToArray } from '@worldbrain/memex-common/lib/common-ui/utils/normalized-state' import { sharedListRoleIDToString } from '@worldbrain/memex-common/lib/content-sharing/ui/list-share-modal/util' import { __wrapClick } from '../utils' import { TaskState } from 'ui-logic-core/lib/types' +import LoadingBlock from '@worldbrain/memex-common/lib/common-ui/components/loading-block' export interface Props extends Dependencies { disableWriteOps?: boolean pageLinkLoadingState: TaskState } +const SpaceEmailHeight = '50px' + export default class SpaceEmailInvites extends StatefulUIElement< Props, State, @@ -40,21 +42,15 @@ export default class SpaceEmailInvites extends StatefulUIElement< } private get shouldShowInviteBtn(): boolean { - const inputValue = this.state.emailInviteInputValue.trim() - if (!inputValue.length) { - return false - } - if (!isValidEmail(inputValue)) { + const inputValues = this.state.emailInviteInputValue + .split(',') + .map((email) => email.trim()) + .filter((email) => email.length > 0) + if (inputValues.length === 0) { return false } - let alreadyInvited = false - for (const invite of normalizedStateToArray(this.state.emailInvites)) { - if (invite.email === inputValue) { - alreadyInvited = true - break - } - } - return !alreadyInvited + + return true } private handleInviteInputChange: React.ChangeEventHandler< @@ -85,16 +81,17 @@ export default class SpaceEmailInvites extends StatefulUIElement< render() { return ( <> - - Invite via Email{' '} - {this.state.emailInvitesLoadState === 'running' && ( - - )} - + Invite via Email {this.props.pageLinkLoadingState === 'running' ? ( - Available once links are finished loading + Available once links loaded + ) : this.state.emailInvitesLoadState === 'running' ? ( + ) : ( { @@ -110,7 +107,7 @@ export default class SpaceEmailInvites extends StatefulUIElement< value={this.state.emailInviteInputValue} onChange={this.handleInviteInputChange} disabled={this.props.disableWriteOps} - placeholder="Add email address" + placeholder="Add address(es) with , separator" icon="mail" onKeyDown={this.handleAddInviteInputKeyDown} /> @@ -166,8 +163,7 @@ export default class SpaceEmailInvites extends StatefulUIElement< )} - {(this.state.emailInvitesLoadState === 'success' || - this.state.emailInvitesLoadState === 'pristine') && + {this.state.emailInvitesLoadState === 'success' && !this.shouldShowInviteBtn && normalizedStateToArray(this.state.emailInvites) .length > 0 && ( @@ -307,10 +303,12 @@ const WaitingDescription = styled.div` width: fill-available; width: -moz-available; padding: 20px; + box-sizing: border-box; display: flex; border-radius: 8px; justify-content: center; align-items: center; + height: ${SpaceEmailHeight}; ` const EditableTextField = styled(TextField)` @@ -336,4 +334,5 @@ const Container = styled.div` align-items: flex-start; background-color: transparent; grid-gap: 2px; + min-height: ${SpaceEmailHeight}; ` diff --git a/src/custom-lists/ui/space-email-invites/logic.ts b/src/custom-lists/ui/space-email-invites/logic.ts index 1ecb5e5f01..7094190333 100644 --- a/src/custom-lists/ui/space-email-invites/logic.ts +++ b/src/custom-lists/ui/space-email-invites/logic.ts @@ -152,83 +152,99 @@ export default class SpaceEmailInvitesLogic extends UILogic { event, previousState, }) => { + this.emitMutation({ + emailInvitesLoadState: { $set: 'running' }, + }) const now = Date.now() - const email = event.state.emailInviteInputValue.trim() + const emails = event.state.emailInviteInputValue + .split(',') + .map((email) => email.trim()) + .filter((email) => email !== '') // Remove any empty strings const roleID = event.state.emailInviteInputRole - const prevInviteCount = event.state.emailInvites.allIds.length - const tmpId = `tmp-invite-id-${prevInviteCount}` - this.emitMutation({ - emailInviteInputValue: { $set: '' }, - emailInvites: { - allIds: { $push: [tmpId] }, - byId: { - [tmpId]: { - $set: { - email, - roleID, - id: tmpId, - createdWhen: now, - sharedListKey: null, + let prevInviteCount = event.state.emailInvites.allIds.length + + for (const email of emails) { + const tmpId = `tmp-invite-id-${prevInviteCount}` + + this.emitMutation({ + emailInvites: { + allIds: { $push: [tmpId] }, + byId: { + [tmpId]: { + $set: { + email, + roleID, + id: tmpId, + createdWhen: now, + sharedListKey: null, + }, }, }, }, - }, - }) - await executeUITask(this, 'emailInvitesCreateState', async () => { - let remoteId = this.dependencies.listData?.remoteId ?? null + }) + + await executeUITask(this, 'emailInvitesCreateState', async () => { + let remoteId = this.dependencies.listData?.remoteId ?? null - while (remoteId == null) { - remoteId = await this.dependencies.contentSharingBG.getRemoteListId( + while (remoteId == null) { + remoteId = await this.dependencies.contentSharingBG.getRemoteListId( + { + localListId: this.dependencies.listData.localId, + }, + ) + } + + const result = await this.dependencies.contentSharingBG.createListEmailInvite( { - localListId: this.dependencies.listData.localId, + now, + email, + roleID, + listId: remoteId, }, ) - } - - const result = await this.dependencies.contentSharingBG.createListEmailInvite( - { - now, - email, - roleID, - listId: remoteId, - }, - ) - if (result.status === 'success') { - // Replace temp emailInvites state with the full one - this.emitMutation({ - emailInvites: { - allIds: { - [prevInviteCount]: { $set: result.keyString }, - }, - byId: { - $unset: [tmpId], - [result.keyString]: { - $set: { - email, - roleID, - createdWhen: now, - id: result.keyString, - sharedListKey: result.keyString, + if (result.status === 'success') { + this.emitMutation({ + emailInvites: { + allIds: { + [prevInviteCount]: { $set: result.keyString }, + }, + byId: { + $unset: [tmpId], + [result.keyString]: { + $set: { + email, + roleID, + createdWhen: now, + id: result.keyString, + sharedListKey: result.keyString, + }, }, }, }, - }, - emailInvitesLoadState: { $set: 'success' }, - }) - } else if (result.status === 'permission-denied') { - this.emitMutation({ - emailInvites: { - byId: { $unset: [tmpId] }, - allIds: { - $apply: (prev) => - prev.filter((ids) => ids !== tmpId), + }) + } else if (result.status === 'permission-denied') { + this.emitMutation({ + emailInvites: { + byId: { $unset: [tmpId] }, + allIds: { + $apply: (prev) => + prev.filter((ids) => ids !== tmpId), + }, }, - }, - emailInvitesLoadState: { $set: 'pristine' }, - }) - throw new Error('Email invite encountered an error') - } + }) + throw new Error( + `Email invite for ${email} encountered an error`, + ) + } + }) + + prevInviteCount++ + } + + this.emitMutation({ + emailInviteInputValue: { $set: '' }, + emailInvitesLoadState: { $set: 'success' }, }) } diff --git a/src/custom-lists/ui/space-links.tsx b/src/custom-lists/ui/space-links.tsx index 0b1aff16ee..78aa2f0c8b 100644 --- a/src/custom-lists/ui/space-links.tsx +++ b/src/custom-lists/ui/space-links.tsx @@ -11,6 +11,7 @@ import type { InviteLink } from '@worldbrain/memex-common/lib/content-sharing/ui import type { TaskState } from 'ui-logic-core/lib/types' import type { AnalyticsCoreInterface } from '@worldbrain/memex-common/lib/analytics/types' import { trackCopyInviteLink } from '@worldbrain/memex-common/lib/analytics/events' +import LoadingBlock from '@worldbrain/memex-common/lib/common-ui/components/loading-block' export interface Props { isPageLink: boolean @@ -83,11 +84,7 @@ export default class SpaceLinks extends React.PureComponent { render() { if (this.props.loadState === 'running') { - return ( - - - - ) + return } return ( @@ -227,7 +224,7 @@ const ShareSectionContainer = styled.div` flex-direction: column; align-items: center; justify-content: center; - height: 90px; + height: 80px; ` const LinkAndRoleBox = styled.div<{ @@ -242,7 +239,7 @@ const LinkAndRoleBox = styled.div<{ // z-index: ${(props) => props['zIndex']}; height: 40px; margin: 0 -5px 5px -10px; - padding: 0px 0px 0 5px; + padding: 0px 0px 0 5px; ${(props) => diff --git a/src/options/imports/components/EstimatesTable.jsx b/src/options/imports/components/EstimatesTable.jsx index 7e75ccbcfa..aa0f59e6f6 100644 --- a/src/options/imports/components/EstimatesTable.jsx +++ b/src/options/imports/components/EstimatesTable.jsx @@ -1,7 +1,7 @@ import React from 'react' import PropTypes from 'prop-types' import Checkbox from 'src/common-ui/components/Checkbox' -import LoadingIndicator from 'src/common-ui/components/LoadingIndicator' +import LoadingIndicator from '@worldbrain/memex-common/lib/common-ui/components/loading-indicator' import { IMPORT_TYPE as TYPE, IMPORT_SERVICES as SERVICES } from '../constants' import classNames from 'classnames' import localStyles from './Import.css' diff --git a/src/overview/results/components/ResultListContainer.tsx b/src/overview/results/components/ResultListContainer.tsx index 9bf4a0ef43..27044c2c5f 100644 --- a/src/overview/results/components/ResultListContainer.tsx +++ b/src/overview/results/components/ResultListContainer.tsx @@ -1,34 +1,6 @@ import React, { PureComponent, MouseEventHandler, MouseEvent } from 'react' import { connect, MapStateToProps } from 'react-redux' -import Waypoint from 'react-waypoint' -import reduce from 'lodash/fp/reduce' - -import { selectors as opt } from 'src/options/settings' -import LoadingIndicator from 'src/common-ui/components/LoadingIndicator' -import ResultItem from 'src/common-ui/components/result-item' -import ResultList from './ResultList' -import TagHolder from 'src/common-ui/components/tag-holder' -import * as constants from '../constants' import { RootState } from 'src/options/types' -import { Result, ResultsByUrl } from '../../types' -import * as selectors from '../selectors' -import * as acts from '../actions' -import { actions as listActs } from 'src/custom-lists' -import { acts as deleteConfActs } from '../../delete-confirm-modal' -import { actions as filterActs, selectors as filters } from 'src/search-filters' -import { PageUrlsByDay } from 'src/search/background/types' -import { getLocalStorage } from 'src/util/storage' -import { TAG_SUGGESTIONS_KEY } from 'src/constants' -import niceTime from 'src/util/nice-time' -import { Annotation } from 'src/annotations/types' -import TagPicker from 'src/tags/ui/TagPicker' -import { auth, tags, collections } from 'src/util/remote-functions-background' -import { HoverBoxDashboard as HoverBox } from 'src/common-ui/components/design-library/HoverBox' -import { PageNotesCopyPaster } from 'src/copy-paster' -import { ContentSharingInterface } from 'src/content-sharing/background/types' -import { RemoteCopyPasterInterface } from 'src/copy-paster/background/types' -import { formateCalendarTime } from '@worldbrain/memex-common/lib/utils/date-time' - const styles = require('./ResultList.css') interface LocalState {} diff --git a/src/search-injection/components/youtubeActionBar.tsx b/src/search-injection/components/youtubeActionBar.tsx index fc95f3bc60..c66205330a 100644 --- a/src/search-injection/components/youtubeActionBar.tsx +++ b/src/search-injection/components/youtubeActionBar.tsx @@ -924,7 +924,7 @@ export default class YoutubeButtonMenu extends React.Component { hoverOff /> - Timestamped Note + Add Note @@ -932,20 +932,33 @@ export default class YoutubeButtonMenu extends React.Component { getPortalRoot={this.props.getRootElement} tooltipText={ - Instant note with timestamp and summary -
of the selected video range + Instant summary + note of selected + range. + + + - Click for custom prompt +
} placement="bottom" > - this.setState({ - showAINoteTooltip: true, - }) - } - onMouseUp={() => { - if (!this.state.showAINoteTooltip) { + onMouseDown={(event) => { + if (event.shiftKey) { + this.setState({ + showAINoteTooltip: true, + }) + } else if ( + this.state.showAINoteTooltip + ) { + this.handleAItimeStampButtonClick() + } else { this.handleAItimeStampButtonClick() } }} @@ -998,7 +1011,7 @@ export default class YoutubeButtonMenu extends React.Component { hoverOff /> - Summarize Video + Summarize {/* {this.state.showSummarizeTooltip ? this.renderPromptTooltip( @@ -1407,3 +1420,20 @@ const LanguageSearchBox = styled.div` justify-content: center; padding: 10px; ` + +const ToolTipTextBox = styled.div` + display: flex; + align-items: center; + justify-content: center; + grid-gap: 10px; + flex-direction: column; +` + +const TooltipSubTitle = styled.div` + font-size: 12px; + color: ${(props) => props.theme.colors.greyScale5}; + display: flex; + align-items: center; + justify-content: center; + grid-gap: 5px; +`