diff --git a/lib/gui/app/components/settings/settings.tsx b/lib/gui/app/components/settings/settings.tsx index 34d2465b..e895cad0 100644 --- a/lib/gui/app/components/settings/settings.tsx +++ b/lib/gui/app/components/settings/settings.tsx @@ -120,10 +120,6 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) { } done={() => toggleModal(false)} - style={{ - width: 780, - height: 420, - }} > {_.map(settingsList, (setting: Setting, i: number) => { diff --git a/lib/gui/app/components/source-selector/source-selector.tsx b/lib/gui/app/components/source-selector/source-selector.tsx index e8468310..4cd1c0d2 100644 --- a/lib/gui/app/components/source-selector/source-selector.tsx +++ b/lib/gui/app/components/source-selector/source-selector.tsx @@ -52,6 +52,7 @@ import { Modal, StepButton, StepNameButton, + ScrollableFlex, } from '../../styled-components'; import { colors } from '../../theme'; import { middleEllipsis } from '../../utils/middle-ellipsis'; @@ -61,19 +62,24 @@ import ImageSvg from '../../../assets/image.svg'; const recentUrlImagesKey = 'recentUrlImages'; -function normalizeRecentUrlImages(urls: any): string[] { +function normalizeRecentUrlImages(urls: any[]): URL[] { if (!Array.isArray(urls)) { urls = []; } - return _.chain(urls) - .filter(_.isString) - .reject(_.isEmpty) - .uniq() - .takeRight(5) - .value(); + urls = urls + .map((url) => { + try { + return new URL(url); + } catch (error) { + // Invalid URL, skip + } + }) + .filter((url) => url !== undefined); + urls = _.uniqBy(urls, (url) => url.href); + return urls.slice(urls.length - 5); } -function getRecentUrlImages(): string[] { +function getRecentUrlImages(): URL[] { let urls = []; try { urls = JSON.parse(localStorage.getItem(recentUrlImagesKey) || '[]'); @@ -83,11 +89,9 @@ function getRecentUrlImages(): string[] { return normalizeRecentUrlImages(urls); } -function setRecentUrlImages(urls: string[]) { - localStorage.setItem( - recentUrlImagesKey, - JSON.stringify(normalizeRecentUrlImages(urls)), - ); +function setRecentUrlImages(urls: URL[]) { + const normalized = normalizeRecentUrlImages(urls.map((url: URL) => url.href)); + localStorage.setItem(recentUrlImagesKey, JSON.stringify(normalized)); } const Card = styled(BaseCard)` @@ -124,13 +128,13 @@ const URLSelector = ({ }) => { const [imageURL, setImageURL] = React.useState(''); const [recentImages, setRecentImages]: [ - string[], - (value: React.SetStateAction) => void, + URL[], + (value: React.SetStateAction) => void, ] = React.useState([]); const [loading, setLoading] = React.useState(false); React.useEffect(() => { const fetchRecentUrlImages = async () => { - const recentUrlImages: string[] = await getRecentUrlImages(); + const recentUrlImages: URL[] = await getRecentUrlImages(); setRecentImages(recentUrlImages); }; fetchRecentUrlImages(); @@ -139,15 +143,16 @@ const URLSelector = ({ { setLoading(true); - const sanitizedRecentUrls = normalizeRecentUrlImages([ - ...recentImages, + const urlStrings = recentImages.map((url: URL) => url.href); + const normalizedRecentUrls = normalizeRecentUrlImages([ + ...urlStrings, imageURL, ]); - setRecentUrlImages(sanitizedRecentUrls); + setRecentUrlImages(normalizedRecentUrls); await done(imageURL); }} > @@ -164,24 +169,29 @@ const URLSelector = ({ } /> - {!_.isEmpty(recentImages) && ( - + {recentImages.length > 0 && ( + Recent - ( - { - setImageURL(recent); - }} - > - - {_.last(_.split(recent, '/'))} - {recent} - - - ))} - /> + + ( + { + setImageURL(recent.href); + }} + style={{ + overflowWrap: 'break-word', + }} + > + {recent.pathname.split('/').pop()} - {recent.href} + + )) + .reverse()} + /> + )} @@ -280,7 +290,7 @@ export class SourceSelector extends React.Component< private async onSelectImage(_event: IpcRendererEvent, imagePath: string) { const isURL = - _.startsWith(imagePath, 'https://') || _.startsWith(imagePath, 'http://'); + imagePath.startsWith('https://') || imagePath.startsWith('http://'); await this.selectImageByPath({ imagePath, SourceType: isURL ? sourceDestination.Http : sourceDestination.File, @@ -354,8 +364,8 @@ export class SourceSelector extends React.Component< }); } else { if ( - !_.startsWith(imagePath, 'https://') && - !_.startsWith(imagePath, 'http://') + !imagePath.startsWith('https://') && + !imagePath.startsWith('http://') ) { const invalidImageError = errors.createUserError({ title: 'Unsupported protocol', diff --git a/lib/gui/app/components/target-selector/target-selector-modal.tsx b/lib/gui/app/components/target-selector/target-selector-modal.tsx index ccff228c..f4da1c64 100644 --- a/lib/gui/app/components/target-selector/target-selector-modal.tsx +++ b/lib/gui/app/components/target-selector/target-selector-modal.tsx @@ -50,7 +50,7 @@ import { import { store } from '../../models/store'; import { logEvent, logException } from '../../modules/analytics'; import { open as openExternal } from '../../os/open-external/services/open-external'; -import { Modal } from '../../styled-components'; +import { Modal, ScrollableFlex } from '../../styled-components'; import TargetSVGIcon from '../../../assets/tgt.svg'; @@ -83,19 +83,6 @@ function isDrivelistDrive( return typeof (drive as scanner.adapters.DrivelistDrive).size === 'number'; } -const ScrollableFlex = styled(Flex)` - overflow: auto; - - ::-webkit-scrollbar { - display: none; - } - - > div > div { - /* This is required for the sticky table header in TargetsTable */ - overflow-x: visible; - } -`; - const TargetsTable = styled(({ refFn, ...props }) => { return (
@@ -376,10 +363,6 @@ export class TargetSelectorModal extends React.Component< cancel={cancel} done={() => done(selectedList)} action={`Select (${selectedList.length})`} - style={{ - width: '780px', - height: '420px', - }} primaryButtonProps={{ primary: !hasStatus, warning: hasStatus, @@ -387,7 +370,7 @@ export class TargetSelectorModal extends React.Component< }} {...props} > - + {!hasAvailableDrives() ? ( Plug a target drive ) : ( - + ) => { if (t !== null) { diff --git a/lib/gui/app/css/main.css b/lib/gui/app/css/main.css index aacf3d32..03ab634d 100644 --- a/lib/gui/app/css/main.css +++ b/lib/gui/app/css/main.css @@ -63,5 +63,5 @@ button:focus, } .disabled { - opacity: 0.2; + opacity: 0.4; } diff --git a/lib/gui/app/styled-components.tsx b/lib/gui/app/styled-components.tsx index 10d245ff..19ba5ea7 100644 --- a/lib/gui/app/styled-components.tsx +++ b/lib/gui/app/styled-components.tsx @@ -121,8 +121,7 @@ export const DetailsText = (props: FlexProps) => ( /> ); -export const Modal = styled((props) => { - const { style = { height: 420 } } = props; +export const Modal = styled(({ style, ...props }) => { return ( { > ); })` > div { - padding: 30px; + padding: 24px 30px; height: calc(100% - 80px); - overflow-y: auto; + + ::-webkit-scrollbar { + display: none; + } > h3 { margin: 0; @@ -178,3 +183,16 @@ export const Modal = styled((props) => { } } `; + +export const ScrollableFlex = styled(Flex)` + overflow: auto; + + ::-webkit-scrollbar { + display: none; + } + + > div > div { + /* This is required for the sticky table header in TargetsTable */ + overflow-x: visible; + } +`;