Skip to content

Commit

Permalink
Autofill presenter name with the info from /me.json (#1037)
Browse files Browse the repository at this point in the history
If the presenter field is empty (and no value in local storage) the
users name from info/me.json is used.
  • Loading branch information
LukasKalbertodt authored Nov 16, 2023
2 parents 539d62f + 41261fb commit 59bea17
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 17 deletions.
13 changes: 13 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ further below for information on that.
# Default: 'optional'.
#seriesField = 'optional'

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


[recording]
# A list of preferred MIME types used by the media recorder. Studio uses the
Expand Down
16 changes: 16 additions & 0 deletions src/opencast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,22 @@ 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
&& typeof this.#currentUser === "object"
&& "user" in this.#currentUser
&& this.#currentUser.user
&& typeof this.#currentUser.user === "object"
&& "name" in this.#currentUser.user
&& 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
46 changes: 29 additions & 17 deletions src/steps/finish/upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,22 @@ const UploadForm: React.FC<UploadFormProps> = ({ handleUpload }) => {
titleField = "required",
presenterField = "required",
seriesField = "optional",
autofillPresenter = [],
} = useSettings().upload ?? {};

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(),
}))
.find(Boolean)
|| "";

type FormState = "idle" | "testing";
const [state, setState] = useState<FormState>("idle");
Expand Down Expand Up @@ -205,13 +213,14 @@ 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.
// If the user has not yet changed the value of the field, but it has been prefilled
// from local storage or one of the `autofillPresenter` sources, update the state
// using that value.
useEffect(() => {
if (presenterValue !== presenter) {
dispatch({ type: "UPDATE_PRESENTER", value: presenterValue });
}
});
}, []);

const configurableServerUrl = settingsManager.isConfigurable("opencast.serverUrl");
const configurableUsername = settingsManager.isUsernameConfigurable();
Expand Down Expand Up @@ -404,20 +413,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 59bea17

Please sign in to comment.