Skip to content

Commit

Permalink
✨ Add link/unlink button for the subtitle in the chart admin
Browse files Browse the repository at this point in the history
  • Loading branch information
danyx23 committed Oct 12, 2023
1 parent e41354f commit afd1b40
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 43 deletions.
2 changes: 1 addition & 1 deletion adminSiteClient/ChartEditorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export class ChartEditorPage
// these may point to non-existent details e.g. ["not_a_real_term", "pvotery"]
@computed get currentDetailReferences() {
return {
subtitle: extractDetailsFromSyntax(this.grapher.subtitle),
subtitle: extractDetailsFromSyntax(this.grapher.currentSubtitle),
note: extractDetailsFromSyntax(this.grapher.note),
}
}
Expand Down
8 changes: 6 additions & 2 deletions adminSiteClient/EditorTextTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ChartEditor } from "./ChartEditor.js"
import {
AutoTextField,
BindAutoString,
BindAutoStringExt,
BindString,
Button,
RadioGroup,
Expand Down Expand Up @@ -155,8 +156,11 @@ export class EditorTextTab extends React.Component<{ editor: ChartEditor }> {
}
helpText="Human-friendly URL for this chart"
/>
<BindString
field="subtitle"
<BindAutoStringExt
label={"Subtitle"}
readFn={(g) => g.currentSubtitle}
writeFn={(g, newVal) => (g.subtitle = newVal)}
isAuto={grapher.subtitle === undefined}
store={grapher}
placeholder="Briefly describe the context of the data. It's best to avoid duplicating any information which can be easily inferred from other visual elements of the chart."
textarea
Expand Down
177 changes: 140 additions & 37 deletions adminSiteClient/Forms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,29 @@ export class TextAreaField extends React.Component<TextFieldProps> {
)}
</label>
)}
<textarea
className="form-control"
value={props.value}
onChange={this.onChange}
onBlur={this.onBlur}
rows={5}
{...passthroughProps}
/>
<div className="input-group">
<textarea
className="form-control custom-control"
value={props.value}
onChange={this.onChange}
onBlur={this.onBlur}
rows={5}
{...passthroughProps}
/>
{props.buttonContent && (
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
onClick={() =>
props.onButtonClick && props.onButtonClick()
}
>
{props.buttonContent}
</button>
</div>
)}
</div>
{props.helpText && (
<small className="form-text text-muted">
{props.helpText}
Expand Down Expand Up @@ -611,16 +626,11 @@ export class Section extends React.Component<{ name: string }> {
}
}

interface AutoTextFieldProps {
label?: string
value: string | undefined
placeholder?: string
type AutoTextFieldProps = TextFieldProps & {
isAuto: boolean
helpText?: string
onValue: (value: string) => void
onToggleAuto: (value: boolean) => void
softCharacterLimit?: number
onBlur?: () => void
textarea?: boolean
}

const ErrorMessage = ({ message }: { message: string }) => (
Expand Down Expand Up @@ -658,29 +668,53 @@ class SoftCharacterLimit extends React.Component<{
@observer
export class AutoTextField extends React.Component<AutoTextFieldProps> {
render() {
const { props } = this
const props = this.props
const { textarea } = props

return (
<TextField
{...props}
value={props.value}
placeholder={props.placeholder}
buttonContent={
<div
title={
props.isAuto ? "Automatic default" : "Manual input"
}
>
{props.isAuto ? (
<FontAwesomeIcon icon={faLink} />
) : (
<FontAwesomeIcon icon={faUnlink} />
)}
</div>
}
onButtonClick={() => props.onToggleAuto(!props.isAuto)}
/>
)
if (textarea)
return (
<TextAreaField
{...props}
buttonContent={
<div
title={
props.isAuto
? "Automatic default"
: "Manual input"
}
>
{props.isAuto ? (
<FontAwesomeIcon icon={faLink} />
) : (
<FontAwesomeIcon icon={faUnlink} />
)}
</div>
}
onButtonClick={() => props.onToggleAuto(!props.isAuto)}
/>
)
else
return (
<TextField
{...props}
buttonContent={
<div
title={
props.isAuto
? "Automatic default"
: "Manual input"
}
>
{props.isAuto ? (
<FontAwesomeIcon icon={faLink} />
) : (
<FontAwesomeIcon icon={faUnlink} />
)}
</div>
}
onButtonClick={() => props.onToggleAuto(!props.isAuto)}
/>
)
}
}

Expand Down Expand Up @@ -785,6 +819,75 @@ export class BindAutoString<
}
}

/** This text field is for cases where you want to have a text field or text area
with a button that be linked/unlinked to either use a default value or override it.
To use it you need to provide 4 props:
- readFn: a function that returns the current value of the field
- writeFn: a function that writes a value to the field
- store: the object that contains the field
- isAuto: a boolean that indicates whether the field is linked or not
readFn and writeFn can either read and write from the same property or different ones -
the latter is useful so that one property can provide the overridden value or the default value.
isAuto has to reliably determine if the default value is used or not.
```tsx
<BindAutoStringExt
label={"Subtitle"}
readFn={(g) => g.currentSubtitle}
writeFn={(g, newVal) => (g.subtitle = newVal)}
isAuto={grapher.subtitle === undefined}
store={grapher}
/>
```
*/
@observer
export class BindAutoStringExt<
T extends Record<string, any>
> extends React.Component<
{
readFn: (x: T) => string
writeFn: (x: T, value: string | undefined) => void
store: T
} & Omit<
AutoTextFieldProps,
"onValue" | "onToggleAuto" | "value" | "isBlur"
>
> {
@action.bound onValue(value: string | undefined = "") {
this.props.writeFn(this.props.store, value)
}

@action.bound onBlur() {
if (!this.props.isAuto) {
const trimmedValue = this.props.readFn(this.props.store).trim()
this.props.writeFn(this.props.store, trimmedValue)
}
}

@action.bound onToggleAuto(value: boolean) {
this.props.writeFn(
this.props.store,
value ? undefined : this.props.readFn(this.props.store)
)
}

render() {
const { readFn, store, ...rest } = this.props
const currentReadValue = readFn(store)
return (
<AutoTextField
value={currentReadValue || ""}
onValue={this.onValue}
onBlur={this.onBlur}
onToggleAuto={this.onToggleAuto}
{...rest}
/>
)
}
}

interface AutoFloatFieldProps {
label?: string
value: number
Expand Down
6 changes: 3 additions & 3 deletions packages/@ourworldindata/grapher/src/core/Grapher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export class Grapher
@observable.ref version = 1
@observable.ref slug?: string = undefined
@observable.ref title?: string = undefined
@observable.ref subtitle = ""
@observable.ref subtitle: string | undefined = undefined
@observable.ref sourceDesc?: string = undefined
@observable.ref note = ""
@observable hideAnnotationFieldsInTitle?: AnnotationFieldsInTitle =
Expand Down Expand Up @@ -1185,7 +1185,7 @@ export class Grapher

// Used for superscript numbers in static exports
@computed get detailsOrderedByReference(): Set<string> {
const textInOrderOfAppearance = this.subtitle + this.note
const textInOrderOfAppearance = this.currentSubtitle + this.note
const details = textInOrderOfAppearance.matchAll(
new RegExp(detailOnDemandRegex, "g")
)
Expand Down Expand Up @@ -1235,7 +1235,7 @@ export class Grapher

@computed get currentSubtitle(): string {
const subtitle = this.subtitle
if (subtitle) return subtitle
if (subtitle !== undefined) return subtitle
const yColumns = this.yColumnsFromDimensions
if (yColumns.length === 1) return yColumns[0].def.descriptionShort ?? ""
return ""
Expand Down

0 comments on commit afd1b40

Please sign in to comment.