Skip to content

Commit 59bea17

Browse files
Autofill presenter name with the info from /me.json (#1037)
If the presenter field is empty (and no value in local storage) the users name from info/me.json is used.
2 parents 539d62f + 41261fb commit 59bea17

File tree

4 files changed

+75
-17
lines changed

4 files changed

+75
-17
lines changed

CONFIGURATION.md

+13
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@ further below for information on that.
106106
# Default: 'optional'.
107107
#seriesField = 'optional'
108108

109+
# Whether to fill the presenter name in the upload step, and if so, how.
110+
# This is a list of sources for potential presenter names to fill in,
111+
# in order of descending preference. If it is empty, nothing is suggested.
112+
#
113+
# Note that right now there is only one such source. Also, manual changes
114+
# to the presenter form field will be persisted in the users' `localStorage`,
115+
# and that stored value will always be preferred over the other sources.
116+
#
117+
# Possible sources are:
118+
# - `"opencast"`: Get the name from Opencast's `/info/me.json` API,
119+
# specifically the field `user.name`
120+
#autofillPresenter = []
121+
109122

110123
[recording]
111124
# A list of preferred MIME types used by the media recorder. Studio uses the

src/opencast.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,22 @@ export class Opencast {
288288
return await this.jsonRequest("info/me.json");
289289
}
290290

291+
/** Returns the value from user.name from the `/info/me.json` endpoint. */
292+
getUsername(): string | null {
293+
if (!(
294+
this.#currentUser
295+
&& typeof this.#currentUser === "object"
296+
&& "user" in this.#currentUser
297+
&& this.#currentUser.user
298+
&& typeof this.#currentUser.user === "object"
299+
&& "name" in this.#currentUser.user
300+
&& typeof this.#currentUser.user.name === "string"
301+
)) {
302+
return null;
303+
}
304+
return this.#currentUser.user.name;
305+
}
306+
291307
/** Returns the response from the `/lti` endpoint. */
292308
async getLti(): Promise<object | null> {
293309
return await this.jsonRequest("lti");

src/settings.tsx

+17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export type FormFieldState =
2121
/** Sources that setting values can come from. */
2222
type SettingsSource = "src-server"| "src-url" | "src-local-storage";
2323

24+
const PRESENTER_SOURCES = ["opencast"] as const;
25+
type PresenterSource = typeof PRESENTER_SOURCES[number];
26+
2427
/** Opencast Studio runtime settings. */
2528
export type Settings = {
2629
opencast?: {
@@ -37,6 +40,7 @@ export type Settings = {
3740
titleField?: FormFieldState;
3841
presenterField?: FormFieldState;
3942
seriesField?: FormFieldState;
43+
autofillPresenter?: PresenterSource[];
4044
};
4145
recording?: {
4246
videoBitrate?: number;
@@ -584,6 +588,19 @@ const SCHEMA = {
584588
titleField: metaDataField,
585589
presenterField: metaDataField,
586590
seriesField: metaDataField,
591+
autofillPresenter: (v, allowParse, src) => {
592+
const a = types.array(v => {
593+
const s = types.string(v);
594+
if (!(PRESENTER_SOURCES as readonly string[]).includes(s)) {
595+
throw new Error("invalid presenter name source");
596+
}
597+
return s;
598+
})(v, allowParse, src);
599+
if (new Set(a).size < a.length) {
600+
throw new Error("duplicate presenter name source");
601+
}
602+
return a;
603+
},
587604
},
588605
recording: {
589606
videoBitrate: types.positiveInteger,

src/steps/finish/upload.tsx

+29-17
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,22 @@ const UploadForm: React.FC<UploadFormProps> = ({ handleUpload }) => {
163163
titleField = "required",
164164
presenterField = "required",
165165
seriesField = "optional",
166+
autofillPresenter = [],
166167
} = useSettings().upload ?? {};
167168

168169
const { t, i18n } = useTranslation();
169170
const opencast = useOpencast();
170171
const dispatch = useDispatch();
171172
const settingsManager = useSettingsManager();
172173
const { title, presenter, upload: uploadState, recordings } = useStudioState();
173-
const presenterValue = presenter || window.localStorage.getItem(LAST_PRESENTER_KEY) || "";
174+
const presenterValue = presenter
175+
|| window.localStorage.getItem(LAST_PRESENTER_KEY)
176+
|| autofillPresenter
177+
.map(source => match(source, {
178+
"opencast": () => opencast.getUsername(),
179+
}))
180+
.find(Boolean)
181+
|| "";
174182

175183
type FormState = "idle" | "testing";
176184
const [state, setState] = useState<FormState>("idle");
@@ -205,13 +213,14 @@ const UploadForm: React.FC<UploadFormProps> = ({ handleUpload }) => {
205213
}
206214
}
207215

208-
// If the user has not yet changed the value of the field and the last used
209-
// presenter name is used in local storage, use that.
216+
// If the user has not yet changed the value of the field, but it has been prefilled
217+
// from local storage or one of the `autofillPresenter` sources, update the state
218+
// using that value.
210219
useEffect(() => {
211220
if (presenterValue !== presenter) {
212221
dispatch({ type: "UPDATE_PRESENTER", value: presenterValue });
213222
}
214-
});
223+
}, []);
215224

216225
const configurableServerUrl = settingsManager.isConfigurable("opencast.serverUrl");
217226
const configurableUsername = settingsManager.isUsernameConfigurable();
@@ -404,20 +413,23 @@ const UploadForm: React.FC<UploadFormProps> = ({ handleUpload }) => {
404413
};
405414

406415
type InputProps<I extends FieldValues, F> =
407-
Pick<JSX.IntrinsicElements["input"], "onChange" | "autoComplete" | "defaultValue" | "onBlur"> &
416+
Pick<
417+
JSX.IntrinsicElements["input"],
418+
"onChange" | "autoComplete" | "defaultValue" | "onBlur"
419+
> &
408420
Pick<ReturnType<typeof useForm<I>>, "register"> & {
409-
/** Human readable string describing the field. */
410-
label: string;
411-
name: Path<I>;
412-
/** Whether this field is required or may be empty. */
413-
required: boolean;
414-
/** Function validating the value and returning a string in the case of error. */
415-
validate?: Validate<F, I>;
416-
errors: Partial<Record<keyof I, FieldError>>;
417-
/** Passed to the `<input>`. */
418-
type?: HTMLInputTypeAttribute;
419-
autoFocus?: boolean;
420-
};
421+
/** Human readable string describing the field. */
422+
label: string;
423+
name: Path<I>;
424+
/** Whether this field is required or may be empty. */
425+
required: boolean;
426+
/** Function validating the value and returning a string in the case of error. */
427+
validate?: Validate<F, I>;
428+
errors: Partial<Record<keyof I, FieldError>>;
429+
/** Passed to the `<input>`. */
430+
type?: HTMLInputTypeAttribute;
431+
autoFocus?: boolean;
432+
};
421433

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

0 commit comments

Comments
 (0)