global changes and test
This commit is contained in:
parent
44cdba6b7d
commit
b75f2e3b49
3
public/robots.txt
Normal file
3
public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
25
src/components/ModalEditor.jsx
Normal file
25
src/components/ModalEditor.jsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Modal, Text } from '@mantine/core';
|
||||||
|
|
||||||
|
const ModalEditor = ({ title, opened, handlerClose }) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal.Root opened={opened} onClose={handlerClose}>
|
||||||
|
<Modal.Overlay />
|
||||||
|
<Modal.Content>
|
||||||
|
<Modal.Header>
|
||||||
|
<Modal.Title>
|
||||||
|
<Text fw={700} fz="lg">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</Modal.Title>
|
||||||
|
<Modal.CloseButton />
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
{content}
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalEditor;
|
@ -1,8 +1,8 @@
|
|||||||
import { Image } from '@mantine/core';
|
import { Image } from '@mantine/core';
|
||||||
|
import { isImage } from '../tools/fileUtil';
|
||||||
|
|
||||||
const MediaPlayer = ({ file, fileId, shouldContain }) => {
|
const MediaPlayer = ({ file, fileId, shouldContain }) => {
|
||||||
const isImage = () => file.type.split('/')[0] === 'image';
|
return isImage(file.type) ? (
|
||||||
return isImage() ? (
|
|
||||||
<Image
|
<Image
|
||||||
src={'/api/files/' + (fileId ?? file.id)}
|
src={'/api/files/' + (fileId ?? file.id)}
|
||||||
alt={file?.name ?? ''}
|
alt={file?.name ?? ''}
|
||||||
|
@ -25,7 +25,6 @@ const ModalAddFile = ({ opened, handler, addFiles }) => {
|
|||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
const CHUNK_SIZE = 1 * 1024 * 1024; // 1MB chunks
|
|
||||||
|
|
||||||
files.forEach((file) => formData.append(`${file.name}`, file, file.name));
|
files.forEach((file) => formData.append(`${file.name}`, file, file.name));
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ import { Perm, checkPerm } from '../../tools/grant-access';
|
|||||||
import { useAuth } from '../../tools/auth-provider';
|
import { useAuth } from '../../tools/auth-provider';
|
||||||
import { parseTime } from '../../tools/timeUtil';
|
import { parseTime } from '../../tools/timeUtil';
|
||||||
import MediaPlayer from '../../components/media-player';
|
import MediaPlayer from '../../components/media-player';
|
||||||
|
import { isVideo } from '../../tools/fileUtil';
|
||||||
|
|
||||||
|
const DEFAULT_FILE_TIME = 2;
|
||||||
|
|
||||||
const Content = ({ form, playlistId, playlist }) => {
|
const Content = ({ form, playlistId, playlist }) => {
|
||||||
const [fileSelector, setFileSelector] = useState(false);
|
const [fileSelector, setFileSelector] = useState(false);
|
||||||
@ -36,7 +39,7 @@ const Content = ({ form, playlistId, playlist }) => {
|
|||||||
max_position++;
|
max_position++;
|
||||||
|
|
||||||
file.position = max_position;
|
file.position = max_position;
|
||||||
file.seconds = 10;
|
file.seconds = DEFAULT_FILE_TIME;
|
||||||
const index = form.values.files.length;
|
const index = form.values.files.length;
|
||||||
form.insertListItem('files', file);
|
form.insertListItem('files', file);
|
||||||
API.playlists
|
API.playlists
|
||||||
@ -154,10 +157,18 @@ const Content = ({ form, playlistId, playlist }) => {
|
|||||||
<MediaPlayer file={form.getInputProps(`files.${index}`).value} />
|
<MediaPlayer file={form.getInputProps(`files.${index}`).value} />
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
|
{...(isVideo(form.getInputProps(`files.${index}.type`).value)
|
||||||
|
? {
|
||||||
|
disabled: true,
|
||||||
|
value: 0,
|
||||||
|
description: 'Default to video duration',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
value: form.getInputProps(`files.${index}.seconds`).value,
|
||||||
|
onChange: (secs) => handleChangeSeconds(secs, index),
|
||||||
|
})}
|
||||||
hideControls
|
hideControls
|
||||||
description="Seconds to display"
|
label="Seconds to display"
|
||||||
value={form.getInputProps(`files.${index}.seconds`).value}
|
|
||||||
onChange={(secs) => handleChangeSeconds(secs, index)}
|
|
||||||
error={
|
error={
|
||||||
form.getInputProps(`files.${index}.seconds`).errors && 'This field is required'
|
form.getInputProps(`files.${index}.seconds`).errors && 'This field is required'
|
||||||
}
|
}
|
||||||
|
38
src/pages/roles/create.jsx
Normal file
38
src/pages/roles/create.jsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Modal, Text } from '@mantine/core';
|
||||||
|
import API from '../../services/api';
|
||||||
|
import RoleViewEditor from './role-view-editor';
|
||||||
|
|
||||||
|
const ModalCreateRole = ({ opened, handler, addRole, item }) => {
|
||||||
|
const validate = (role) => {
|
||||||
|
if (role) {
|
||||||
|
addRole(role);
|
||||||
|
}
|
||||||
|
handler();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal.Root opened={opened} onClose={handler}>
|
||||||
|
<Modal.Overlay />
|
||||||
|
<Modal.Content>
|
||||||
|
<Modal.Header>
|
||||||
|
<Modal.Title>
|
||||||
|
<Text fw={700} fz="lg">
|
||||||
|
Create Role
|
||||||
|
</Text>
|
||||||
|
</Modal.Title>
|
||||||
|
<Modal.CloseButton />
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<RoleViewEditor
|
||||||
|
buttonText="Create"
|
||||||
|
item={item}
|
||||||
|
APICall={API.roles.create}
|
||||||
|
handler={(role) => validate(role)}
|
||||||
|
/>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalCreateRole;
|
82
src/pages/roles/role-selector.jsx
Normal file
82
src/pages/roles/role-selector.jsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { MultiSelect } from '@mantine/core';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import setNotification from '../errors/error-notification';
|
||||||
|
import API from '../../services/api';
|
||||||
|
import { Perm, checkPerm } from '../../tools/grant-access';
|
||||||
|
import { useAuth } from '../../tools/auth-provider';
|
||||||
|
import ModalCreateRole from './create';
|
||||||
|
|
||||||
|
const RoleSelector = ({ defaultRoles, label, value, setValue }) => {
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
const [search, setSearch] = useState();
|
||||||
|
const [showCreateRole, setShowCreateRole] = useState(false);
|
||||||
|
const toggleCreateRole = () => setShowCreateRole(!showCreateRole);
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
|
||||||
|
const { user } = useAuth();
|
||||||
|
const canCreateRole = checkPerm(Perm.CREATE_ROLE, user);
|
||||||
|
|
||||||
|
const addRoles = (roles) => {
|
||||||
|
if (!roles) return;
|
||||||
|
for (let i = 0; i < roles.length; i++) {
|
||||||
|
const role = roles[i];
|
||||||
|
if (!data.find((r) => r.id === role.id)) {
|
||||||
|
role.label = role.name;
|
||||||
|
role.value = role.id.toString();
|
||||||
|
setData((prev) => [...prev, role]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
API.roles
|
||||||
|
.search(search)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
addRoles(res.data);
|
||||||
|
} else {
|
||||||
|
setNotification(true, res);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setNotification(true, err);
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [search]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addRoles(defaultRoles);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [defaultRoles]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MultiSelect
|
||||||
|
label={label}
|
||||||
|
data={data}
|
||||||
|
searchable
|
||||||
|
searchValue={search}
|
||||||
|
onSearchChange={setSearch}
|
||||||
|
value={value}
|
||||||
|
onChange={setValue}
|
||||||
|
maxDropdownHeight={160}
|
||||||
|
creatable={canCreateRole}
|
||||||
|
getCreateLabel={(query) => `+ Create ${query}`}
|
||||||
|
onCreate={(query) => {
|
||||||
|
setQuery(query);
|
||||||
|
setShowCreateRole(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{canCreateRole && (
|
||||||
|
<ModalCreateRole
|
||||||
|
opened={showCreateRole}
|
||||||
|
item={{ name: query }}
|
||||||
|
addRole={(role) => addRoles([role])}
|
||||||
|
handler={toggleCreateRole}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RoleSelector;
|
64
src/pages/roles/role-view-editor.jsx
Normal file
64
src/pages/roles/role-view-editor.jsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { Button, TextInput, Group, Stack } from '@mantine/core';
|
||||||
|
import { useForm, isNotEmpty } from '@mantine/form';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import setNotification from '../errors/error-notification';
|
||||||
|
|
||||||
|
const RoleViewEditor = ({ item, handler, buttonText, APICall }) => {
|
||||||
|
const handleClose = (role) => {
|
||||||
|
form.reset();
|
||||||
|
handler(role);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
name: item?.name ?? '',
|
||||||
|
},
|
||||||
|
validate: {
|
||||||
|
name: isNotEmpty('Name is required'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setFieldValue('name', item?.name);
|
||||||
|
return () => {};
|
||||||
|
}, [item]);
|
||||||
|
|
||||||
|
const handleSubmit = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (form.validate().hasErrors) return;
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
if (item?.id) {
|
||||||
|
const res = await APICall(item?.id, { name: form.values.name });
|
||||||
|
handleClose(res.data);
|
||||||
|
} else {
|
||||||
|
const res = await APICall({ name: form.values.name });
|
||||||
|
handleClose(res.data);
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
} catch (err) {
|
||||||
|
setIsLoading(false);
|
||||||
|
setNotification(true, err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<TextInput label="Name" placeholder="Name" withAsterisk {...form.getInputProps('name')} mb="sm" />
|
||||||
|
todo parent id
|
||||||
|
users
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button variant="light" color="red" onClick={() => handler()}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" variant="light" color="green" loading={isLoading}>
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RoleViewEditor;
|
10
src/tools/fileUtil.js
Normal file
10
src/tools/fileUtil.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export const isImage = (type) => {
|
||||||
|
if (!type) return false;
|
||||||
|
return type.split('/')[0] === 'image';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isVideo = (type) => {
|
||||||
|
if (!type) return false;
|
||||||
|
return type.split('/')[0] === 'video';
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user