Skip to content

Commit

Permalink
Add the ability to autofill the presenter field in the upload step
Browse files Browse the repository at this point in the history
This patch adds a config option to specify a preference order
of potential sources to autofill the presenter field from.
Leaving it empty is equivalent to the current behavior.
The only valid value right now is `"opencast"`,
which gets the value from `/info/me.json`.
The way the feature is built, it should be easy to extend
in the future, though.
If there is a name stored in `localStorage` already,
this currently takes precedence over (any of) the suggested value(s).

Co-authored-by: naweidner <weidner@elan-ev.de>
  • Loading branch information
JulianKniephoff and narickmann committed Nov 15, 2023
1 parent 539d62f commit 9a8cdd9
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 23 deletions.
14 changes: 14 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ further below for information on that.
# Default: 'optional'.
#seriesField = 'optional'

# Whether and if so how to fill the presenter name in the upload step.
# This is a list of sources for potential presenter names to suggest,
# in order of descending preference.
# If it is empty, nothing is suggested.
# Note that right now there is only one such source,
# and that manual changes to the presenter form field
# will be persisted in the users' `localStorage`,
# and that stored value will always be preferred
# over the generated suggestion.
#
# Possible sources are:
# - `"opencast"`: Get the name from Opencast's `/info/me.json` API
#autofillPresenter = []


[recording]
# A list of preferred MIME types used by the media recorder. Studio uses the
Expand Down
26 changes: 26 additions & 0 deletions src/opencast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,32 @@ export class Opencast {
return await this.jsonRequest("info/me.json");
}

/** Returns the value from user.name from the `/info/me.json` endpoint. */
getUsername(): string | null {
if (!(this.#currentUser)) {
return null;
}
if (!(typeof this.#currentUser === "object")) {
return null;
}
if (!("user" in this.#currentUser)) {
return null;
}
if (!this.#currentUser.user) {
return null;
}
if (!(typeof this.#currentUser.user === "object")) {
return null;
}
if (!("name" in this.#currentUser.user)) {
return null;
}
if (!(typeof this.#currentUser.user.name === "string")) {
return null;
}
return this.#currentUser.user.name;
}

/** Returns the response from the `/lti` endpoint. */
async getLti(): Promise<object | null> {
return await this.jsonRequest("lti");
Expand Down
17 changes: 17 additions & 0 deletions src/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export type FormFieldState =
/** Sources that setting values can come from. */
type SettingsSource = "src-server"| "src-url" | "src-local-storage";

const PRESENTER_SOURCES = ["opencast"] as const;
type PresenterSource = typeof PRESENTER_SOURCES[number];

/** Opencast Studio runtime settings. */
export type Settings = {
opencast?: {
Expand All @@ -37,6 +40,7 @@ export type Settings = {
titleField?: FormFieldState;
presenterField?: FormFieldState;
seriesField?: FormFieldState;
autofillPresenter?: PresenterSource[];
};
recording?: {
videoBitrate?: number;
Expand Down Expand Up @@ -584,6 +588,19 @@ const SCHEMA = {
titleField: metaDataField,
presenterField: metaDataField,
seriesField: metaDataField,
autofillPresenter: (v, allowParse, src) => {
const a = types.array(v => {
const s = types.string(v);
if (!(PRESENTER_SOURCES as readonly string[]).includes(s)) {
throw new Error("invalid presenter name source");
}
return s;
})(v, allowParse, src);
if (new Set(a).size < a.length) {
throw new Error("duplicate presenter name source");
}
return a;
},
},
recording: {
videoBitrate: types.positiveInteger,
Expand Down
50 changes: 27 additions & 23 deletions src/steps/finish/upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,27 @@ type UploadFormProps = {
};

const UploadForm: React.FC<UploadFormProps> = ({ handleUpload }) => {
const uploadSettings = useSettings().upload ?? {};
const {
titleField = "required",
presenterField = "required",
seriesField = "optional",
} = useSettings().upload ?? {};
autofillPresenter = [],
} = uploadSettings;

const { t, i18n } = useTranslation();
const opencast = useOpencast();
const dispatch = useDispatch();
const settingsManager = useSettingsManager();
const { title, presenter, upload: uploadState, recordings } = useStudioState();
const presenterValue = presenter || window.localStorage.getItem(LAST_PRESENTER_KEY) || "";
const presenterValue = presenter
|| window.localStorage.getItem(LAST_PRESENTER_KEY)
|| autofillPresenter
.map(source => match(source, {
"opencast": () => opencast.getUsername(),
}))
.filter(Boolean)[0]
|| "";

type FormState = "idle" | "testing";
const [state, setState] = useState<FormState>("idle");
Expand Down Expand Up @@ -205,14 +214,6 @@ const UploadForm: React.FC<UploadFormProps> = ({ handleUpload }) => {
}
}

// If the user has not yet changed the value of the field and the last used
// presenter name is used in local storage, use that.
useEffect(() => {
if (presenterValue !== presenter) {
dispatch({ type: "UPDATE_PRESENTER", value: presenterValue });
}
});

const configurableServerUrl = settingsManager.isConfigurable("opencast.serverUrl");
const configurableUsername = settingsManager.isUsernameConfigurable();
const configurablePassword = settingsManager.isPasswordConfigurable();
Expand Down Expand Up @@ -404,20 +405,23 @@ const UploadForm: React.FC<UploadFormProps> = ({ handleUpload }) => {
};

type InputProps<I extends FieldValues, F> =
Pick<JSX.IntrinsicElements["input"], "onChange" | "autoComplete" | "defaultValue" | "onBlur"> &
Pick<
JSX.IntrinsicElements["input"],
"onChange" | "autoComplete" | "defaultValue" | "onBlur"
> &
Pick<ReturnType<typeof useForm<I>>, "register"> & {
/** Human readable string describing the field. */
label: string;
name: Path<I>;
/** Whether this field is required or may be empty. */
required: boolean;
/** Function validating the value and returning a string in the case of error. */
validate?: Validate<F, I>;
errors: Partial<Record<keyof I, FieldError>>;
/** Passed to the `<input>`. */
type?: HTMLInputTypeAttribute;
autoFocus?: boolean;
};
/** Human readable string describing the field. */
label: string;
name: Path<I>;
/** Whether this field is required or may be empty. */
required: boolean;
/** Function validating the value and returning a string in the case of error. */
validate?: Validate<F, I>;
errors: Partial<Record<keyof I, FieldError>>;
/** Passed to the `<input>`. */
type?: HTMLInputTypeAttribute;
autoFocus?: boolean;
};

/**
* A styled `<input>` element with a label. Displays errors and integrated with
Expand Down

0 comments on commit 9a8cdd9

Please sign in to comment.