Skip to content

Commit

Permalink
WIP type-checked
Browse files Browse the repository at this point in the history
- Check if lists can be sorted by avatar (should not be sortable!)
-

Change-Id: I167a6101a0c03abc62c18098e7f370e4e4dd3b06
  • Loading branch information
awesome-manuel committed Jul 9, 2024
1 parent 4adf2c2 commit b5132e9
Show file tree
Hide file tree
Showing 16 changed files with 171 additions and 106 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@
"@emotion/styled": "^11.3.0",
"@haleos/ra-language-german": "^1.0.0",
"@haxqer/ra-language-chinese": "^4.16.2",
"@matrix-org/spec": "^1.10.1",
"@mui/icons-material": "^5.15.16",
"@mui/material": "^5.16.0",
"history": "^5.1.0",
"lodash": "^4.17.21",
"openapi-fetch": "^0.9.5",
"papaparse": "^5.4.1",
"query-string": "^7.1.1",
"ra-core": "^4.16.17",
Expand Down Expand Up @@ -91,8 +93,8 @@
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/stylistic",
"plugin:@typescript-eslint/recommended-type-checked",
"plugin:@typescript-eslint/stylistic-type-checked",
"plugin:import/typescript",
"plugin:yaml/recommended"
],
Expand Down
3 changes: 2 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin
import { Route } from "react-router-dom";

import { ImportFeature } from "./components/ImportFeature";
import { SynapseTranslationMessages } from "./i18n";
import germanMessages from "./i18n/de";
import englishMessages from "./i18n/en";
import frenchMessages from "./i18n/fr";
Expand All @@ -30,7 +31,7 @@ const messages = {
zh: chineseMessages,
};
const i18nProvider = polyglotI18nProvider(
locale => (messages[locale] ? merge({}, messages.en, messages[locale]) : messages.en),
locale => (messages[locale] ? (merge({}, messages.en, messages[locale]) as SynapseTranslationMessages) : messages.en),
resolveBrowserLocale(),
[
{ locale: "en", name: "English" },
Expand Down
6 changes: 3 additions & 3 deletions src/components/AvatarField.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { get } from "lodash";

import { Avatar } from "@mui/material";
import { Avatar, AvatarProps } from "@mui/material";
import { useRecordContext } from "react-admin";

const AvatarField = ({ source, ...rest }) => {
const AvatarField = ({ source, ...rest }: AvatarProps & { source: string }) => {
const record = useRecordContext(rest);
const src = get(record, source)?.toString();
const src = get(record, source, "") as string;
const { alt, classes, sizes, sx, variant } = rest;
return <Avatar alt={alt} classes={classes} sizes={sizes} src={src} sx={sx} variant={variant} />;
};
Expand Down
47 changes: 23 additions & 24 deletions src/components/ImportFeature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const LOGGING = true;

const expectedFields = ["id", "displayname"].sort();

function TranslatableOption({ value, text }) {
function TranslatableOption({ value, text }: { value: string; text: string }) {
const translate = useTranslate();
return <option value={value}>{translate(text)}</option>;
}
Expand Down Expand Up @@ -81,7 +81,7 @@ const FilePicker = () => {

const dataProvider = useDataProvider();

const onFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
const onFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (progress !== null) return;

setValues([]);
Expand All @@ -106,11 +106,11 @@ const FilePicker = () => {
skipEmptyLines: true /* especially for a final EOL in the csv file */,
complete: result => {
if (result.errors) {
setError(result.errors.map(e => e.toString()));
setError(result.errors.map(e => String(e)));
}
/* Papaparse is very lenient, we may be able to salvage
* the data in the file. */
verifyCsv(result, { setValues, setStats, setError });
verifyCsv(result, setValues, setStats, setError);
},
});
} catch {
Expand All @@ -119,7 +119,12 @@ const FilePicker = () => {
}
};

const verifyCsv = ({ data, meta, errors }: ParseResult<ImportLine>, { setValues, setStats, setError }) => {
const verifyCsv = (
{ data, meta, errors }: ParseResult<ImportLine>,
setValues: (values: ImportLine[]) => void,
setStats: (stats: ChangeStats | null) => void,
setError: (error: string | string[] | null) => void
) => {
/* First, verify the presence of required fields */
const missingFields = expectedFields.filter(eF => meta.fields?.find(mF => eF === mF));

Expand Down Expand Up @@ -206,29 +211,23 @@ const FilePicker = () => {
return true;
};

const runImport = async () => {
const runImport = () => {
if (progress !== null) {
notify("import_users.errors.already_in_progress");
return;
}

const results = await doImport(
dataProvider,
values,
conflictMode,
passwordMode,
useridMode,
dryRun,
setProgress,
setError
void doImport(dataProvider, values, conflictMode, passwordMode, useridMode, dryRun, setProgress, setError).then(
results => {
setImportResults(results);
// offer CSV download of skipped or errored records
// (so that the user doesn't have to filter out successful
// records manually when fixing stuff in the CSV)
setSkippedRecords(unparseCsv(results.skippedRecords));
if (LOGGING) console.log("Skipped records:");
if (LOGGING) console.log(skippedRecords);
}
);
setImportResults(results);
// offer CSV download of skipped or errored records
// (so that the user doesn't have to filter out successful
// records manually when fixing stuff in the CSV)
setSkippedRecords(unparseCsv(results.skippedRecords));
if (LOGGING) console.log("Skipped records:");
if (LOGGING) console.log(skippedRecords);
};

// XXX every single one of the requests will restart the activity indicator
Expand Down Expand Up @@ -370,7 +369,7 @@ const FilePicker = () => {
element.click();
};

const onConflictModeChanged = async (e: ChangeEvent<HTMLSelectElement>) => {
const onConflictModeChanged = (e: ChangeEvent<HTMLSelectElement>) => {
if (progress !== null) {
return;
}
Expand All @@ -387,7 +386,7 @@ const FilePicker = () => {
setPasswordMode(e.target.checked);
};

const onUseridModeChanged = async (e: ChangeEvent<HTMLSelectElement>) => {
const onUseridModeChanged = (e: ChangeEvent<HTMLSelectElement>) => {
if (progress !== null) {
return;
}
Expand Down
1 change: 1 addition & 0 deletions src/components/ServerNotices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const ServerNoticeDialog = ({ open, onClose, onSubmit }) => {
<DialogContent>
<DialogContentText>{translate("resources.servernotices.helper.send")}</DialogContentText>
<SimpleForm toolbar={<ServerNoticeToolbar />} onSubmit={onSubmit}>
{/* TODO: Use MUI form (does not require a record) */}
<TextInput
source="body"
label="resources.servernotices.fields.body"
Expand Down
2 changes: 1 addition & 1 deletion src/components/devices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const DeviceRemoveButton = (props: DeleteWithConfirmButtonProps) => {
redirect={false}
translateOptions={{
id: record.id,
name: record.display_name ? record.display_name : record.id,
name: String(record.display_name ?? record.id),
}}
/>
);
Expand Down
9 changes: 8 additions & 1 deletion src/components/media.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ import { dateParser } from "./date";
import { DeleteMediaParams, SynapseDataProvider } from "../synapse/dataProvider";
import { getMediaUrl } from "../synapse/synapse";

const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
interface DeleteMediaDialogProps {
open: boolean;
onClose: () => void;
onSubmit: (params: DeleteMediaParams) => void;
}

const DeleteMediaDialog = ({ open, onClose, onSubmit }: DeleteMediaDialogProps) => {
const translate = useTranslate();

const DeleteMediaToolbar = (props: ToolbarProps) => (
Expand All @@ -53,6 +59,7 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
<DialogContent>
<DialogContentText>{translate("delete_media.helper.send")}</DialogContentText>
<SimpleForm toolbar={<DeleteMediaToolbar />} onSubmit={onSubmit}>
{/* TODO: Use MUI form (does not require a record) */}
<DateTimeInput
fullWidth
source="before_ts"
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/it.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import italianMessages from "ra-language-italian";

import { TranslationMessages } from "ra-core";

import { SynapseTranslationMessages } from ".";

const it: SynapseTranslationMessages = {
...italianMessages,
...(italianMessages as TranslationMessages),
synapseadmin: {
auth: {
base_url: "URL dell'homeserver",
Expand Down
59 changes: 25 additions & 34 deletions src/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@ import {
import { useFormContext } from "react-hook-form";

import { useAppContext } from "../AppContext";
import {
getServerVersion,
getSupportedFeatures,
getSupportedLoginFlows,
getWellKnownUrl,
isValidBaseUrl,
splitMxid,
} from "../synapse/synapse";
import { getServerVersion, getWellKnownUrl, isValidBaseUrl, splitMxid, useSynapse } from "../synapse/synapse";

interface UserDataFields {
base_url: string;
username: string;
password: string;
}

const FormBox = styled(Box)(({ theme }) => ({
display: "flex",
Expand Down Expand Up @@ -115,20 +114,14 @@ const LoginPage = () => {
console.log("Base URL is:", baseUrl);
console.log("SSO Token is:", ssoToken);
console.log("Let's try token login...");
login(auth).catch(error => {
alert(
typeof error === "string"
? error
: typeof error === "undefined" || !error.message
? "ra.auth.sign_in_error"
: error.message
);
login(auth).catch((error: Error | string | undefined) => {
alert(typeof error === "string" ? error : !error?.message ? "ra.auth.sign_in_error" : error.message);
console.error(error);
});
}
}

const validateBaseUrl = value => {
const validateBaseUrl = (value: string) => {
if (!value.match(/^(http|https):\/\//)) {
return translate("synapseadmin.auth.protocol_error");
} else if (!value.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/)) {
Expand All @@ -140,16 +133,11 @@ const LoginPage = () => {

const handleSubmit = auth => {
setLoading(true);
login(auth).catch(error => {
login(auth).catch((error: Error | string | undefined) => {
setLoading(false);
notify(
typeof error === "string"
? error
: typeof error === "undefined" || !error.message
? "ra.auth.sign_in_error"
: error.message,
{ type: "warning" }
);
notify(typeof error === "string" ? error : !error?.message ? "ra.auth.sign_in_error" : error.message, {
type: "warning",
});
});
};

Expand All @@ -161,7 +149,7 @@ const LoginPage = () => {
window.location.href = ssoFullUrl;
};

const UserData = ({ formData }) => {
const UserData = ({ formData }: { formData: UserDataFields }) => {
const form = useFormContext();
const [serverVersion, setServerVersion] = useState("");
const [matrixVersions, setMatrixVersions] = useState("");
Expand All @@ -171,7 +159,7 @@ const LoginPage = () => {
// check if username is a full qualified userId then set base_url accordingly
const domain = splitMxid(formData.username)?.domain;
if (domain) {
getWellKnownUrl(domain).then(url => {
void getWellKnownUrl(domain).then(url => {
if (allowAnyBaseUrl || (allowMultipleBaseUrls && restrictBaseUrl.includes(url)))
form.setValue("base_url", url);
});
Expand All @@ -183,22 +171,25 @@ const LoginPage = () => {
form.setValue("base_url", restrictBaseUrl[0]);
}
if (!isValidBaseUrl(formData.base_url)) return;
const synapseClient = useSynapse(formData.base_url);

getServerVersion(formData.base_url)
.then(serverVersion => setServerVersion(`${translate("synapseadmin.auth.server_version")} ${serverVersion}`))
.catch(() => setServerVersion(""));

getSupportedFeatures(formData.base_url)
synapseClient
.getSupportedFeatures()
.then(features =>
setMatrixVersions(`${translate("synapseadmin.auth.supports_specs")} ${features.versions.join(", ")}`)
setMatrixVersions(`${translate("synapseadmin.auth.supports_specs")} ${features?.versions.join(", ")}`)
)
.catch(() => setMatrixVersions(""));

// Set SSO Url
getSupportedLoginFlows(formData.base_url)
synapseClient
.getSupportedLoginFlows()
.then(loginFlows => {
const supportPass = loginFlows.find(f => f.type === "m.login.password") !== undefined;
const supportSSO = loginFlows.find(f => f.type === "m.login.sso") !== undefined;
const supportPass = loginFlows?.find(f => f.type === "m.login.password") !== undefined;
const supportSSO = loginFlows?.find(f => f.type === "m.login.sso") !== undefined;
setSupportPassAuth(supportPass);
setSSOBaseUrl(supportSSO ? formData.base_url : "");
})
Expand Down Expand Up @@ -286,7 +277,7 @@ const LoginPage = () => {
</MenuItem>
))}
</Select>
<FormDataConsumer>{formDataProps => <UserData {...formDataProps} />}</FormDataConsumer>
<FormDataConsumer<UserDataFields>>{formDataProps => <UserData {...formDataProps} />}</FormDataConsumer>
<CardActions className="actions">
<Button
variant="contained"
Expand Down
4 changes: 2 additions & 2 deletions src/resources/destinations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ export const DestinationReconnectButton = () => {
const [handleReconnect, { isLoading }] = useDelete();

// Reconnect is not required if no error has occurred. (`failure_ts`)
if (!record || !record.failure_ts) return null;
if (!record?.failure_ts) return null;

const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
// Prevents redirection to the detail page when clicking in the list
e.stopPropagation();

handleReconnect(
void handleReconnect(
"destinations",
{ id: record.id },
{
Expand Down
11 changes: 3 additions & 8 deletions src/resources/room_directory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const RoomDirectoryBulkPublishButton = (props: ButtonProps) => {
const { mutate, isLoading } = useMutation(
() =>
dataProvider.createMany("room_directory", {
ids: selectedIds,
/* TODO: Use custom dataProvider function */ ids: selectedIds,
data: {},
}),
{
Expand Down Expand Up @@ -103,7 +103,7 @@ export const RoomDirectoryPublishButton = (props: ButtonProps) => {
const [create, { isLoading }] = useCreate();

const handleSend = () => {
create(
void create(
"room_directory",
{ data: { id: record.id } },
{
Expand Down Expand Up @@ -140,12 +140,7 @@ export const RoomDirectoryList = () => (
bulkActionButtons={<RoomDirectoryBulkUnpublishButton />}
omit={["room_id", "canonical_alias", "topic"]}
>
<AvatarField
source="avatar_src"
sortable={false}
sx={{ height: "40px", width: "40px" }}
label="resources.rooms.fields.avatar"
/>
<AvatarField source="avatar_src" sx={{ height: "40px", width: "40px" }} />
<TextField source="name" sortable={false} label="resources.rooms.fields.name" />
<TextField source="room_id" sortable={false} label="resources.rooms.fields.room_id" />
<TextField source="canonical_alias" sortable={false} label="resources.rooms.fields.canonical_alias" />
Expand Down
Loading

0 comments on commit b5132e9

Please sign in to comment.