Move in progress apps temporarily
This commit is contained in:
parent
ba04aa108c
commit
6eaaf8e9be
360 changed files with 6171 additions and 55 deletions
|
|
@ -1,43 +0,0 @@
|
|||
.voiceWaveWrapper {
|
||||
width: 100%;
|
||||
max-height: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.visible {
|
||||
display: block;
|
||||
}
|
||||
.buttonWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.playerWrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.recordTime {
|
||||
align-self: center;
|
||||
width: 66px;
|
||||
height: 18px;
|
||||
margin-top: 10px;
|
||||
font-family: "sans";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
color: #000;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
import { useInput } from "react-admin";
|
||||
import React, { useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import MicIcon from "@material-ui/icons/Mic";
|
||||
import StopIcon from "@material-ui/icons/Stop";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import { makeStyles, useTheme } from "@material-ui/core/styles";
|
||||
import AudioPlayer from "material-ui-audio-player";
|
||||
import { useStopwatch } from "react-timer-hook";
|
||||
import style from "./MicInput.module.css";
|
||||
import type { ReactMicProps } from "react-mic";
|
||||
|
||||
const ReactMic = dynamic<ReactMicProps>(
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
() => import("react-mic").then((mod) => mod.ReactMic),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const blobToDataUri = (blob) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(blob);
|
||||
return new Promise((resolve) => {
|
||||
reader.onloadend = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const dataUriToObj = (dataUri) => {
|
||||
const [prefix, base64] = dataUri.split(",");
|
||||
const mime = prefix.slice(5, prefix.indexOf(";"));
|
||||
|
||||
const result = {};
|
||||
result[mime] = base64;
|
||||
return result;
|
||||
};
|
||||
|
||||
const blobToResult = async (blob) => {
|
||||
const result = dataUriToObj(await blobToDataUri(blob));
|
||||
return result;
|
||||
};
|
||||
|
||||
const resultToDataUri = (result): string => {
|
||||
if (!result || !result["audio/webm"]) return "";
|
||||
const base64 = result["audio/webm"];
|
||||
const r = `data:audio/webm;base64,${base64}`;
|
||||
return r;
|
||||
};
|
||||
|
||||
const MicInput = (props) => {
|
||||
const { seconds, minutes, hours, start, reset, pause } = useStopwatch();
|
||||
const theme = useTheme();
|
||||
const {
|
||||
input: { value, onChange },
|
||||
} = useInput(props);
|
||||
|
||||
let [record, setRecorder] = useState({ record: false });
|
||||
const decodedValue = resultToDataUri(value);
|
||||
const startRecording = () => {
|
||||
setRecorder({ record: true });
|
||||
reset();
|
||||
start();
|
||||
};
|
||||
|
||||
const stopRecording = () => {
|
||||
setRecorder({ record: false });
|
||||
pause();
|
||||
};
|
||||
|
||||
async function onData(recordedBlob) {}
|
||||
|
||||
async function onStop(recordedBlob) {
|
||||
const result = await blobToResult(recordedBlob.blob);
|
||||
onChange(result);
|
||||
}
|
||||
|
||||
const isRecording = record.record;
|
||||
const canPlay = !isRecording && decodedValue;
|
||||
const duration = `${hours
|
||||
.toString()
|
||||
.padStart(2, "0")}:${minutes
|
||||
.toString()
|
||||
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
||||
|
||||
const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
volumeIcon: {
|
||||
display: "none",
|
||||
},
|
||||
mainSlider: {
|
||||
display: "none",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="MuiFormControl-marginDense RaFormInput-input-40">
|
||||
<div className={style.content}>
|
||||
<div className={style.voiceWaveWrapper}>
|
||||
<ReactMic
|
||||
record={record.record}
|
||||
className={isRecording ? style.visible : style.hidden}
|
||||
onStop={onStop}
|
||||
onData={onData}
|
||||
strokeColor={theme.palette.primary.main}
|
||||
backgroundColor="white"
|
||||
mimeType="audio/webm"
|
||||
visualSetting="frequencyBars"
|
||||
/>
|
||||
</div>
|
||||
<div>{isRecording ? <p>Recording... {duration}</p> : ""}</div>
|
||||
|
||||
<div className={style.buttonWrapper}>
|
||||
{isRecording ? (
|
||||
<Button variant="contained" color="primary" onClick={stopRecording}>
|
||||
<StopIcon />
|
||||
Stop
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={startRecording}
|
||||
style={{ marginRight: "20px" }}
|
||||
>
|
||||
<MicIcon />
|
||||
Record
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className={style.playerWrapper}>
|
||||
{canPlay && (
|
||||
<AudioPlayer
|
||||
elevation={0}
|
||||
src={decodedValue}
|
||||
variation="secondary"
|
||||
volume={false}
|
||||
useStyles={useStyles}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MicInput;
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import {
|
||||
SimpleForm,
|
||||
Create,
|
||||
FormDataConsumer,
|
||||
SelectInput,
|
||||
BooleanInput,
|
||||
ReferenceInput,
|
||||
required,
|
||||
CreateProps,
|
||||
} from "react-admin";
|
||||
import TwilioLanguages from "./twilio-languages";
|
||||
import {
|
||||
PromptInput,
|
||||
VoiceInput,
|
||||
AvailableNumbersInput,
|
||||
populateNumber,
|
||||
} from "./shared";
|
||||
import MicInput from "./MicInput";
|
||||
|
||||
const VoiceLineCreate = (props: CreateProps) => {
|
||||
return (
|
||||
<Create {...props} title="Create Voice Line" transform={populateNumber}>
|
||||
<SimpleForm>
|
||||
<ReferenceInput
|
||||
label="Provider"
|
||||
source="providerId"
|
||||
reference="voiceProviders"
|
||||
validate={[required()]}
|
||||
>
|
||||
<SelectInput optionText={(p) => `${p.kind}: ${p.name}`} />
|
||||
</ReferenceInput>
|
||||
<FormDataConsumer subscription={{ values: true }}>
|
||||
{AvailableNumbersInput}
|
||||
</FormDataConsumer>
|
||||
<SelectInput
|
||||
source="language"
|
||||
choices={TwilioLanguages.languages}
|
||||
validate={[required()]}
|
||||
/>
|
||||
<FormDataConsumer subscription={{ values: true }}>
|
||||
{VoiceInput}
|
||||
</FormDataConsumer>
|
||||
|
||||
<FormDataConsumer subscription={{ values: true }}>
|
||||
{PromptInput}
|
||||
</FormDataConsumer>
|
||||
<BooleanInput source="audioPromptEnabled" />
|
||||
<MicInput source="promptAudio" />
|
||||
</SimpleForm>
|
||||
</Create>
|
||||
);
|
||||
};
|
||||
|
||||
export default VoiceLineCreate;
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import {
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
Edit,
|
||||
FormDataConsumer,
|
||||
SelectInput,
|
||||
BooleanInput,
|
||||
ReferenceInput,
|
||||
required,
|
||||
EditProps,
|
||||
} from "react-admin";
|
||||
import TwilioLanguages from "./twilio-languages";
|
||||
import { VoiceInput, PromptInput } from "./shared";
|
||||
import MicInput from "./MicInput";
|
||||
|
||||
const VoiceLineTitle = ({ record }: { record?: any }) => {
|
||||
let title = "";
|
||||
if (record) title = record.name ? record.name : record.email;
|
||||
return <span>VoiceLine {title}</span>;
|
||||
};
|
||||
|
||||
const VoiceLineEdit = (props: EditProps) => {
|
||||
return (
|
||||
<Edit title={<VoiceLineTitle />} {...props}>
|
||||
<SimpleForm>
|
||||
<ReferenceInput
|
||||
disabled
|
||||
label="Provider"
|
||||
source="providerId"
|
||||
reference="providers"
|
||||
validate={[required()]}
|
||||
>
|
||||
<SelectInput optionText={(p) => `${p.kind}: ${p.name}`} />
|
||||
</ReferenceInput>
|
||||
<TextInput disabled source="providerLineSid" />
|
||||
<TextInput disabled source="number" />
|
||||
<SelectInput source="language" choices={TwilioLanguages.languages} />
|
||||
<FormDataConsumer subscription={{ values: true }}>
|
||||
{VoiceInput}
|
||||
</FormDataConsumer>
|
||||
<FormDataConsumer subscription={{ values: true }}>
|
||||
{PromptInput}
|
||||
</FormDataConsumer>
|
||||
<BooleanInput source="audioPromptEnabled" />
|
||||
<MicInput source="promptAudio" />
|
||||
</SimpleForm>
|
||||
</Edit>
|
||||
);
|
||||
};
|
||||
|
||||
export default VoiceLineEdit;
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import {
|
||||
List,
|
||||
ListProps,
|
||||
Datagrid,
|
||||
DateField,
|
||||
FunctionField,
|
||||
TextField,
|
||||
ReferenceField,
|
||||
} from "react-admin";
|
||||
|
||||
const VoiceLineList = (props: ListProps) => (
|
||||
<List {...props} exporter={false}>
|
||||
<Datagrid rowClick="edit">
|
||||
<ReferenceField
|
||||
label="Provider"
|
||||
source="providerId"
|
||||
reference="providers"
|
||||
>
|
||||
<FunctionField render={(p) => `${p.kind}: ${p.name}`} />
|
||||
</ReferenceField>
|
||||
<TextField source="number" />
|
||||
<TextField source="language" />
|
||||
<TextField source="voice" />
|
||||
<DateField source="createdAt" />
|
||||
<DateField source="updatedAt" />
|
||||
</Datagrid>
|
||||
</List>
|
||||
);
|
||||
|
||||
export default VoiceLineList;
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import VoiceLineIcon from "@material-ui/icons/PhoneCallback";
|
||||
import VoiceLineList from "./VoiceLineList";
|
||||
import VoiceLineEdit from "./VoiceLineEdit";
|
||||
import VoiceLineCreate from "./VoiceLineCreate";
|
||||
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default {
|
||||
list: VoiceLineList,
|
||||
create: VoiceLineCreate,
|
||||
edit: VoiceLineEdit,
|
||||
icon: VoiceLineIcon,
|
||||
};
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
/* add css module styles here (optional) */
|
||||
@import url("https://fonts.googleapis.com/css?family=Lato:400,700&display=swap");
|
||||
.recorder_library_box,
|
||||
.recorder_library_box * {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Lato", sans-serif;
|
||||
}
|
||||
.recorder_library_box .recorder_box {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 30px 0;
|
||||
}
|
||||
.recorder_library_box .recorder_box_inner {
|
||||
min-height: 400px;
|
||||
background: #212121;
|
||||
border-radius: 0 0 3px 3px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.recorder_library_box .mic_icon {
|
||||
width: 60px;
|
||||
display: flex;
|
||||
height: 60px;
|
||||
position: fixed;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgb(245, 0, 87);
|
||||
border-radius: 50%;
|
||||
bottom: 65px;
|
||||
right: 20%;
|
||||
color: #fff;
|
||||
font-size: 25px;
|
||||
}
|
||||
.recorder_library_box .reco_header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: #bd9f61;
|
||||
align-items: center;
|
||||
padding: 20px 20px;
|
||||
color: #fff;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
.recorder_library_box .reco_header .h2 {
|
||||
font-weight: 400;
|
||||
}
|
||||
.recorder_library_box .reco_header .close_icons {
|
||||
font-size: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
transition: 0.5s ease all;
|
||||
}
|
||||
.recorder_library_box .reco_header .close_icons:hover {
|
||||
background: rgba(123, 118, 106, 0.21);
|
||||
}
|
||||
|
||||
.recorder_library_box .record_section {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
.recorder_library_box .record_section .mic_icon {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 20px;
|
||||
}
|
||||
.recorder_library_box .record_section .duration_section {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
bottom: 100px;
|
||||
}
|
||||
|
||||
.recorder_library_box .btn_wrapper {
|
||||
margin: 20px 30px;
|
||||
}
|
||||
.recorder_library_box .btn_wrapper .btn {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
background: #185fec;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
border: 1px solid #185fec;
|
||||
transition: 0.3s ease all;
|
||||
}
|
||||
.recorder_library_box .btn_wrapper .btn:hover {
|
||||
background: #fff;
|
||||
color: #185fec;
|
||||
}
|
||||
.recorder_library_box .btn_wrapper .clear_btn {
|
||||
background: #fff;
|
||||
color: #185fec;
|
||||
margin-left: 15px;
|
||||
}
|
||||
.recorder_library_box .btn_wrapper .clear_btn:hover {
|
||||
background: #185fec;
|
||||
color: #fff;
|
||||
}
|
||||
.recorder_library_box .duration {
|
||||
text-align: center;
|
||||
}
|
||||
.recorder_library_box .recorder_page_box {
|
||||
min-height: calc(100vh - 128px);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.recorder_library_box .duration * {
|
||||
color: #fff;
|
||||
font-size: 60px;
|
||||
}
|
||||
.recorder_library_box .duration_section .help {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.recorder_library_box .record_controller {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
bottom: 0px;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.recorder_library_box .record_controller .icons {
|
||||
width: 50px;
|
||||
display: flex;
|
||||
height: 50px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
margin-right: 15px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.recorder_library_box .record_controller .stop {
|
||||
background: #940505;
|
||||
}
|
||||
.recorder_library_box .record_controller .pause {
|
||||
background: #9c6702;
|
||||
}
|
||||
|
|
@ -1,296 +0,0 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import PlayIcon from "@material-ui/icons/PlayCircleFilled";
|
||||
import {
|
||||
TextInput,
|
||||
SelectInput,
|
||||
required,
|
||||
useTranslate,
|
||||
useNotify,
|
||||
ReferenceInput,
|
||||
ReferenceField,
|
||||
TextField,
|
||||
} from "react-admin";
|
||||
import { IconButton, CircularProgress } from "@material-ui/core";
|
||||
import absoluteUrl from "../../../lib/absolute-url";
|
||||
import TwilioLanguages from "./twilio-languages";
|
||||
|
||||
type TTSProvider = (voice: any, language: any, prompt: any) => Promise<void>;
|
||||
|
||||
const tts = async (providerId): Promise<TTSProvider> => {
|
||||
const r = await fetch(
|
||||
`/api/v1/voice/twilio/text-to-speech-token/${providerId}`
|
||||
);
|
||||
const { token } = await r.json();
|
||||
const twilioClient = await import("twilio-client");
|
||||
return (voice, language, prompt): Promise<void> =>
|
||||
new Promise((resolve) => {
|
||||
if (!voice || !language || !prompt) resolve();
|
||||
const Device = twilioClient.Device;
|
||||
const device = new Device();
|
||||
const silence = `${absoluteUrl().origin}/static/silence.mp3`;
|
||||
device.setup(token, {
|
||||
codecPrefences: ["opus", "pcmu"],
|
||||
enableRingingState: false,
|
||||
fakeLocalDTMF: true,
|
||||
disableAudioContextSounds: true,
|
||||
sounds: {
|
||||
disconnect: silence,
|
||||
incoming: silence,
|
||||
outgoing: silence,
|
||||
},
|
||||
});
|
||||
device.on("ready", function (device) {
|
||||
device.connect({ language, voice, prompt });
|
||||
});
|
||||
device.on("disconnect", () => resolve());
|
||||
device.on("error", () => resolve());
|
||||
});
|
||||
};
|
||||
|
||||
export const TextToSpeechButton = ({ form }) => {
|
||||
const { providerId, language, voice, promptText: prompt } = form.formData;
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [ttsProvider, setTTSProvider] = useState<
|
||||
undefined | { provider: TTSProvider }
|
||||
>(undefined);
|
||||
const [playText, setPlayText] = useState<
|
||||
undefined | { func: () => Promise<void> }
|
||||
>(undefined);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (providerId) {
|
||||
setLoading(true);
|
||||
setTTSProvider({ provider: await tts(providerId) });
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [providerId]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setPlayText({
|
||||
func: async () => {
|
||||
setLoading(true);
|
||||
if (ttsProvider) await ttsProvider.provider(voice, language, prompt);
|
||||
setLoading(false);
|
||||
},
|
||||
});
|
||||
})();
|
||||
}, [prompt, language, voice, ttsProvider?.provider]);
|
||||
|
||||
const disabled = !(providerId && prompt?.length >= 2 && voice && language);
|
||||
/* TODO add this back to IconButtonwhen we know how to extend MUI theme and appease typescript
|
||||
variant="contained"
|
||||
*/
|
||||
return (
|
||||
<IconButton onClick={playText?.func} disabled={disabled} color="primary">
|
||||
{!loading && <PlayIcon />}
|
||||
{loading && <CircularProgress size={20} />}
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const PromptInput = (form, ...rest) => {
|
||||
return (
|
||||
<TextInput
|
||||
source="promptText"
|
||||
multiline
|
||||
options={{ fullWidth: true }}
|
||||
InputProps={{ endAdornment: <TextToSpeechButton form={form} /> }}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const validateVoice = (args, values) => {
|
||||
if (!values.language) return "validation.language";
|
||||
if (!values.voice) return "validation.voice";
|
||||
|
||||
const availableVoices = TwilioLanguages.voices[values.language];
|
||||
const found =
|
||||
availableVoices.filter((v) => v.id === values.voice).length === 1;
|
||||
if (!found) return "validation.voice";
|
||||
};
|
||||
|
||||
export const VoiceInput = (form, ...rest) => {
|
||||
const voice = TwilioLanguages.voices[form.formData.language] || [];
|
||||
return (
|
||||
<SelectInput
|
||||
source="voice"
|
||||
choices={voice}
|
||||
validate={[required(), validateVoice]}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
let noAvailableNumbers = false;
|
||||
let availableNumbers = [];
|
||||
|
||||
const getAvailableNumbers = async (providerId) => {
|
||||
try {
|
||||
const r = await fetch(`/api/v1/voice/providers/${providerId}/freeNumbers`);
|
||||
availableNumbers = await r.json();
|
||||
noAvailableNumbers = availableNumbers.length === 0;
|
||||
return availableNumbers;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Could not fetch available numbers for provider ${providerId} - ${error}`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const sidToNumber = (sid) => {
|
||||
return availableNumbers
|
||||
.filter(({ id }) => id === sid)
|
||||
.map(({ name }) => name)[0];
|
||||
};
|
||||
|
||||
export const populateNumber = (data) => {
|
||||
return {
|
||||
...data,
|
||||
number: sidToNumber(data.providerLineSid),
|
||||
};
|
||||
};
|
||||
|
||||
const hasNumbers = (args, value, values, translate, ...props) => {
|
||||
if (noAvailableNumbers) return "validation.noAvailableNumbers";
|
||||
};
|
||||
|
||||
export const AvailableNumbersInput = (form, ...rest) => {
|
||||
const {
|
||||
// @ts-expect-error: non-existent property
|
||||
meta: { touched, error } = {},
|
||||
// @ts-expect-error: non-existent property
|
||||
input: { ...inputProps },
|
||||
...props
|
||||
} = rest;
|
||||
const translate = useTranslate();
|
||||
const notify = useNotify();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [choices, setChoices] = useState({});
|
||||
// @ts-expect-error: Invalid return type
|
||||
useEffect(async () => {
|
||||
if (form && form.formData && form.formData.providerId) {
|
||||
setLoading(true);
|
||||
const choices = await getAvailableNumbers(form.formData.providerId);
|
||||
setChoices({
|
||||
choices,
|
||||
helperText: noAvailableNumbers
|
||||
? translate("validation.noAvailableNumbers")
|
||||
: "",
|
||||
});
|
||||
if (noAvailableNumbers) notify("validation.noAvailableNumbers", "error");
|
||||
setLoading(false);
|
||||
}
|
||||
}, [form && form.formData ? form.formData.providerId : undefined]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SelectInput
|
||||
label="Number"
|
||||
source="providerLineSid"
|
||||
// @ts-expect-error: non-existent property
|
||||
choices={choices.choices}
|
||||
disabled={loading}
|
||||
validate={[hasNumbers, required()]}
|
||||
// @ts-expect-error: non-existent property
|
||||
error={Boolean(touched && error) || Boolean(choices.helperText)}
|
||||
// @ts-expect-error: non-existent property
|
||||
helperText={choices.helperText}
|
||||
{...inputProps}
|
||||
{...props}
|
||||
/>
|
||||
{loading && <CircularProgress />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
const voiceLineName = voiceLine => {
|
||||
return voiceLine.number
|
||||
}
|
||||
const getVoiceLineChoices = async ():Promise<any[]> => {
|
||||
try {
|
||||
const r = await fetch(`/api/v1/voice/voice-line`);
|
||||
const lines = await r.json();
|
||||
if(lines.data?.length > 0) {
|
||||
return lines.data.map(voiceLine => ({"id": voiceLine.id, "name": voiceLineName(voiceLine)}))
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Could not fetch voice lines error: ${error}`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export const AsyncSelectInput = (choiceLoader: () => Promise<any[]>, label, source, translationEmpty,) => (form, ...rest) => {
|
||||
const {
|
||||
meta: { touched, error } = {},
|
||||
input: { ...inputProps },
|
||||
...props
|
||||
} = rest;
|
||||
|
||||
const translate = useTranslate();
|
||||
const notify = useNotify();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [choices, setChoices] = useState({choices: []});
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setLoading(true);
|
||||
//const items = await choiceLoader()
|
||||
const items = [{"id": "testing", "name": "OMG"}]
|
||||
setChoices({
|
||||
choices: items,
|
||||
helperText: items.length === 0
|
||||
? translate(translationEmpty)
|
||||
: "",
|
||||
});
|
||||
if (items.length === 0) notify(translationEmpty, "error");
|
||||
setLoading(false);
|
||||
})()}, [form && form.formData ? form.formData.providerId : undefined]);
|
||||
|
||||
const isNotEmpty = () => {
|
||||
if (choices.choices.length === 0) return translationEmpty;
|
||||
return undefined;
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{choices.choices.length > 0 &&
|
||||
<SelectInput
|
||||
label={label}
|
||||
source={source}
|
||||
choices={choices.choices}
|
||||
disabled={loading}
|
||||
validate={[isNotEmpty, required()]}
|
||||
error={Boolean(touched && error) || Boolean(choices.helperText)}
|
||||
helperText={choices.helperText}
|
||||
{...inputProps}
|
||||
{...props}
|
||||
/>}
|
||||
{loading && <CircularProgress />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export const VoiceLineSelectInput = AsyncSelectInput(getVoiceLineChoices, "Voice Line", "backendId", "validation.noVoiceLines" )
|
||||
*/
|
||||
|
||||
export const VoiceLineSelectInput = (source: string) => () => (
|
||||
<ReferenceInput
|
||||
label="Voice Line"
|
||||
source={source}
|
||||
reference="voiceLines"
|
||||
validate={[required()]}
|
||||
>
|
||||
<SelectInput optionText="number" />
|
||||
</ReferenceInput>
|
||||
);
|
||||
|
||||
export const VoiceLineField = (source: string) => () => (
|
||||
<ReferenceField label="Voice Line" source={source} reference="voiceLines">
|
||||
<TextField source="number" />
|
||||
</ReferenceField>
|
||||
);
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
const languages = {
|
||||
languages: [
|
||||
{ id: "arb", name: "Arabic" },
|
||||
{ id: "cy-GB", name: "Welsh" },
|
||||
{ id: "da-DK", name: "Danish" },
|
||||
{ id: "de-DE", name: "German" },
|
||||
{ id: "en-US", name: "English (US)" },
|
||||
{ id: "en-AU", name: "English (Australian)" },
|
||||
{ id: "en-GB", name: "English (British)" },
|
||||
{ id: "en-GB-WLS", name: "English (Welsh)" },
|
||||
{ id: "en-IN", name: "English (Indian)" },
|
||||
{ id: "es-ES", name: "Spanish (Castilian)" },
|
||||
{ id: "es-MX", name: "Spanish (Mexico)" },
|
||||
{ id: "es-US", name: "Spanish (Latin American)" },
|
||||
{ id: "fr-CA", name: "French (Canadian)" },
|
||||
{ id: "fr-FR", name: "French" },
|
||||
{ id: "hi-IN", name: "Hindi" },
|
||||
{ id: "is-IS", name: "Icelandic" },
|
||||
{ id: "it-IT", name: "Italian" },
|
||||
{ id: "ja-JP", name: "Japanese" },
|
||||
{ id: "ko-KR", name: "Korean" },
|
||||
{ id: "nb-NO", name: "Norwegian" },
|
||||
{ id: "nl-NL", name: "Dutch" },
|
||||
{ id: "pl-PL", name: "Polish" },
|
||||
{ id: "pt-BR", name: "Portuguese (Brazilian)" },
|
||||
{ id: "pt-PT", name: "Portuguese (European)" },
|
||||
{ id: "ro-RO", name: "Romanian" },
|
||||
{ id: "ru-RU", name: "Russian" },
|
||||
{ id: "sv-SE", name: "Swedish" },
|
||||
{ id: "tr-TR", name: "Turkish" },
|
||||
{ id: "zh-CN", name: "Chinese (Mandarin)" },
|
||||
],
|
||||
voices: {
|
||||
arb: [{ id: "Polly.Zeina", name: "Zeina" }],
|
||||
"cy-GB": [{ id: "Polly.Gwyneth", name: "Gwyneth" }],
|
||||
"da-DK": [{ id: "Polly.Naja", name: "Naja" }],
|
||||
"de-DE": [{ id: "Polly.Marlene", name: "Marlene" }],
|
||||
"en-US": [{ id: "Polly.Salli", name: "Salli" }],
|
||||
"en-AU": [{ id: "Polly.Nicole", name: "Nicole" }],
|
||||
"en-GB": [{ id: "Polly.Amy", name: "Amy" }],
|
||||
"en-GB-WLS": [{ id: "Polly.Geraint", name: "Geraint" }],
|
||||
"en-IN": [{ id: "Polly.Aditi", name: "Aditi" }],
|
||||
"es-ES": [{ id: "Polly.Conchita", name: "Conchita" }],
|
||||
"es-MX": [{ id: "Polly.Mia", name: "Mia" }],
|
||||
"es-US": [{ id: "Polly.Penelope", name: "Penelope" }],
|
||||
"fr-CA": [{ id: "Polly.Chantal", name: "Chantal" }],
|
||||
"fr-FR": [{ id: "Polly.Celine", name: "Celine" }],
|
||||
"hi-IN": [{ id: "Polly.Aditi", name: "Aditi" }],
|
||||
"is-IS": [{ id: "Polly.Dora", name: "Dora" }],
|
||||
"it-IT": [{ id: "Polly.Carla", name: "Carla" }],
|
||||
"ja-JP": [{ id: "Polly.Mizuki", name: "Mizuki" }],
|
||||
"ko-KR": [{ id: "Polly.Seoyeon", name: "Seoyeon" }],
|
||||
"nb-NO": [{ id: "Polly.Liv", name: "Liv" }],
|
||||
"nl-NL": [{ id: "Polly.Lotte", name: "Lotte" }],
|
||||
"pl-PL": [{ id: "Polly.Ewa", name: "Ewa" }],
|
||||
"pt-BR": [{ id: "Polly.Vitoria", name: "Vitoria" }],
|
||||
"pt-PT": [{ id: "Polly.Ines", name: "Ines" }],
|
||||
"ro-RO": [{ id: "Polly.Carmen", name: "Carmen" }],
|
||||
"ru-RU": [{ id: "Polly.Tatyana", name: "Tatyana" }],
|
||||
"sv-SE": [{ id: "Polly.Astrid", name: "Astrid" }],
|
||||
"tr-TR": [{ id: "Polly.Filiz", name: "Filiz" }],
|
||||
"zh-CN": [{ id: "Polly.Zhiyu", name: "Zhiyu" }],
|
||||
},
|
||||
};
|
||||
export default languages;
|
||||
Loading…
Add table
Add a link
Reference in a new issue