mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
Improve scrape url button UX (#1903)
* change button label to icon * add URLField component
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
|||||||
StudioSelect,
|
StudioSelect,
|
||||||
Icon,
|
Icon,
|
||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
|
URLField,
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
import { useFormik } from "formik";
|
import { useFormik } from "formik";
|
||||||
@@ -384,21 +385,6 @@ export const GalleryEditPanel: React.FC<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderScrapeButton() {
|
|
||||||
if (!formik.values.url || !urlScrapable(formik.values.url)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
className="minimal scrape-url-button"
|
|
||||||
onClick={onScrapeGalleryURL}
|
|
||||||
title="Scrape"
|
|
||||||
>
|
|
||||||
<Icon className="fa-fw" icon="file-download" />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTextField(field: string, title: string, placeholder?: string) {
|
function renderTextField(field: string, title: string, placeholder?: string) {
|
||||||
return (
|
return (
|
||||||
<Form.Group controlId={title} as={Row}>
|
<Form.Group controlId={title} as={Row}>
|
||||||
@@ -461,15 +447,12 @@ export const GalleryEditPanel: React.FC<
|
|||||||
<Form.Label className="col-form-label">
|
<Form.Label className="col-form-label">
|
||||||
<FormattedMessage id="url" />
|
<FormattedMessage id="url" />
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<div className="float-right scrape-button-container">
|
|
||||||
{maybeRenderScrapeButton()}
|
|
||||||
</div>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={9}>
|
<Col xs={9}>
|
||||||
<Form.Control
|
<URLField
|
||||||
className="text-input"
|
|
||||||
placeholder={intl.formatMessage({ id: "url" })}
|
|
||||||
{...formik.getFieldProps("url")}
|
{...formik.getFieldProps("url")}
|
||||||
|
onScrapeClick={onScrapeGalleryURL}
|
||||||
|
urlScrapable={urlScrapable}
|
||||||
isInvalid={!!formik.getFieldMeta("url").error}
|
isInvalid={!!formik.getFieldMeta("url").error}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -10,19 +10,12 @@ import {
|
|||||||
import {
|
import {
|
||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
StudioSelect,
|
StudioSelect,
|
||||||
Icon,
|
|
||||||
DetailsEditNavbar,
|
DetailsEditNavbar,
|
||||||
DurationInput,
|
DurationInput,
|
||||||
|
URLField,
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
import {
|
import { Modal as BSModal, Form, Button, Col, Row } from "react-bootstrap";
|
||||||
Modal as BSModal,
|
|
||||||
Form,
|
|
||||||
Button,
|
|
||||||
Col,
|
|
||||||
Row,
|
|
||||||
InputGroup,
|
|
||||||
} from "react-bootstrap";
|
|
||||||
import { DurationUtils, FormUtils, ImageUtils } from "src/utils";
|
import { DurationUtils, FormUtils, ImageUtils } from "src/utils";
|
||||||
import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
|
import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
|
||||||
import { useFormik } from "formik";
|
import { useFormik } from "formik";
|
||||||
@@ -257,21 +250,6 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderScrapeButton() {
|
|
||||||
const { url } = formik.values;
|
|
||||||
if (!url || !urlScrapable(url)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
className="minimal scrape-url-button"
|
|
||||||
onClick={() => onScrapeMovieURL()}
|
|
||||||
>
|
|
||||||
<Icon icon="file-upload" />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeRenderScrapeDialog() {
|
function maybeRenderScrapeDialog() {
|
||||||
if (!scrapedMovie) {
|
if (!scrapedMovie) {
|
||||||
return;
|
return;
|
||||||
@@ -468,14 +446,11 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
|
|||||||
title: intl.formatMessage({ id: "url" }),
|
title: intl.formatMessage({ id: "url" }),
|
||||||
})}
|
})}
|
||||||
<Col xs={9}>
|
<Col xs={9}>
|
||||||
<InputGroup>
|
<URLField
|
||||||
<Form.Control
|
|
||||||
className="text-input"
|
|
||||||
placeholder={intl.formatMessage({ id: "url" })}
|
|
||||||
{...formik.getFieldProps("url")}
|
{...formik.getFieldProps("url")}
|
||||||
|
onScrapeClick={onScrapeMovieURL}
|
||||||
|
urlScrapable={urlScrapable}
|
||||||
/>
|
/>
|
||||||
<InputGroup.Append>{maybeRenderScrapeButton()}</InputGroup.Append>
|
|
||||||
</InputGroup>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import { Button, Form, Col, Row, Badge, Dropdown } from "react-bootstrap";
|
||||||
Button,
|
|
||||||
Form,
|
|
||||||
Col,
|
|
||||||
InputGroup,
|
|
||||||
Row,
|
|
||||||
Badge,
|
|
||||||
Dropdown,
|
|
||||||
} from "react-bootstrap";
|
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
@@ -28,6 +20,7 @@ import {
|
|||||||
CollapseButton,
|
CollapseButton,
|
||||||
Modal,
|
Modal,
|
||||||
TagSelect,
|
TagSelect,
|
||||||
|
URLField,
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { ImageUtils, getStashIDs } from "src/utils";
|
import { ImageUtils, getStashIDs } from "src/utils";
|
||||||
import { getCountryByISO } from "src/utils/country";
|
import { getCountryByISO } from "src/utils/country";
|
||||||
@@ -662,19 +655,6 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
setScraper(undefined);
|
setScraper(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderScrapeButton() {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
disabled={!urlScrapable(formik.values.url)}
|
|
||||||
className="scrape-url-button text-input"
|
|
||||||
onClick={() => onScrapePerformerURL()}
|
|
||||||
>
|
|
||||||
<Icon icon="file-upload" />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderButtons() {
|
function renderButtons() {
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
@@ -963,14 +943,11 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
<FormattedMessage id="url" />
|
<FormattedMessage id="url" />
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<Col xs={fieldXS} xl={fieldXL}>
|
<Col xs={fieldXS} xl={fieldXL}>
|
||||||
<InputGroup>
|
<URLField
|
||||||
<Form.Control
|
|
||||||
className="text-input"
|
|
||||||
placeholder="URL"
|
|
||||||
{...formik.getFieldProps("url")}
|
{...formik.getFieldProps("url")}
|
||||||
|
onScrapeClick={onScrapePerformerURL}
|
||||||
|
urlScrapable={urlScrapable}
|
||||||
/>
|
/>
|
||||||
<InputGroup.Append>{maybeRenderScrapeButton()}</InputGroup.Append>
|
|
||||||
</InputGroup>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
Icon,
|
Icon,
|
||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
ImageInput,
|
ImageInput,
|
||||||
|
URLField,
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
import { ImageUtils, FormUtils, TextUtils, getStashIDs } from "src/utils";
|
import { ImageUtils, FormUtils, TextUtils, getStashIDs } from "src/utils";
|
||||||
@@ -574,21 +575,6 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderScrapeButton() {
|
|
||||||
if (!formik.values.url || !urlScrapable(formik.values.url)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
className="minimal scrape-url-button"
|
|
||||||
onClick={onScrapeSceneURL}
|
|
||||||
title="Scrape"
|
|
||||||
>
|
|
||||||
<Icon className="fa-fw" icon="file-download" />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTextField(field: string, title: string, placeholder?: string) {
|
function renderTextField(field: string, title: string, placeholder?: string) {
|
||||||
return (
|
return (
|
||||||
<Form.Group controlId={title} as={Row}>
|
<Form.Group controlId={title} as={Row}>
|
||||||
@@ -652,15 +638,12 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
<Form.Label className="col-form-label">
|
<Form.Label className="col-form-label">
|
||||||
<FormattedMessage id="url" />
|
<FormattedMessage id="url" />
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<div className="float-right scrape-button-container">
|
|
||||||
{maybeRenderScrapeButton()}
|
|
||||||
</div>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={9}>
|
<Col xs={9}>
|
||||||
<Form.Control
|
<URLField
|
||||||
className="text-input"
|
|
||||||
placeholder={intl.formatMessage({ id: "url" })}
|
|
||||||
{...formik.getFieldProps("url")}
|
{...formik.getFieldProps("url")}
|
||||||
|
onScrapeClick={onScrapeSceneURL}
|
||||||
|
urlScrapable={urlScrapable}
|
||||||
isInvalid={!!formik.getFieldMeta("url").error}
|
isInvalid={!!formik.getFieldMeta("url").error}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -500,15 +500,6 @@ input[type="range"].blue-slider {
|
|||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
height: calc(1.5em + 0.75rem + 2px);
|
height: calc(1.5em + 0.75rem + 2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrape-button-container {
|
|
||||||
margin-right: -15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrape-url-button {
|
|
||||||
color: $text-color;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.scene-markers-panel {
|
.scene-markers-panel {
|
||||||
|
|||||||
44
ui/v2.5/src/components/Shared/URLField.tsx
Normal file
44
ui/v2.5/src/components/Shared/URLField.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
import { Button, InputGroup, Form } from "react-bootstrap";
|
||||||
|
import { Icon } from "src/components/Shared";
|
||||||
|
import { FormikHandlers } from "formik";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
value: string;
|
||||||
|
name: string;
|
||||||
|
onChange: FormikHandlers["handleChange"];
|
||||||
|
onBlur: FormikHandlers["handleBlur"];
|
||||||
|
onScrapeClick(): void;
|
||||||
|
urlScrapable(url: string): boolean;
|
||||||
|
isInvalid?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const URLField: React.FC<IProps> = (props: IProps) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputGroup className="mr-2 flex-grow-1">
|
||||||
|
<Form.Control
|
||||||
|
className="text-input"
|
||||||
|
placeholder={intl.formatMessage({ id: "url" })}
|
||||||
|
value={props.value}
|
||||||
|
name={props.name}
|
||||||
|
onChange={props.onChange}
|
||||||
|
onBlur={props.onBlur}
|
||||||
|
isInvalid={props.isInvalid}
|
||||||
|
/>
|
||||||
|
<InputGroup.Append>
|
||||||
|
<Button
|
||||||
|
className="scrape-url-button text-input"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={props.onScrapeClick}
|
||||||
|
disabled={!props.value || !props.urlScrapable(props.value)}
|
||||||
|
title={intl.formatMessage({ id: "actions.scrape" })}
|
||||||
|
>
|
||||||
|
<Icon icon="file-download" />
|
||||||
|
</Button>
|
||||||
|
</InputGroup.Append>
|
||||||
|
</InputGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -19,4 +19,5 @@ export { ExportDialog } from "./ExportDialog";
|
|||||||
export { default as DeleteEntityDialog } from "./DeleteEntityDialog";
|
export { default as DeleteEntityDialog } from "./DeleteEntityDialog";
|
||||||
export { IndeterminateCheckbox } from "./IndeterminateCheckbox";
|
export { IndeterminateCheckbox } from "./IndeterminateCheckbox";
|
||||||
export { OperationButton } from "./OperationButton";
|
export { OperationButton } from "./OperationButton";
|
||||||
|
export { URLField } from "./URLField";
|
||||||
export const TITLE_SUFFIX = " | Stash";
|
export const TITLE_SUFFIX = " | Stash";
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"save_delete_settings": "Use these options by default when deleting",
|
"save_delete_settings": "Use these options by default when deleting",
|
||||||
"save_filter": "Save filter",
|
"save_filter": "Save filter",
|
||||||
"scan": "Scan",
|
"scan": "Scan",
|
||||||
|
"scrape": "Scrape",
|
||||||
"scrape_with": "Scrape with…",
|
"scrape_with": "Scrape with…",
|
||||||
"scrape_query": "Scrape query",
|
"scrape_query": "Scrape query",
|
||||||
"scrape_scene_fragment": "Scrape by fragment",
|
"scrape_scene_fragment": "Scrape by fragment",
|
||||||
|
|||||||
Reference in New Issue
Block a user