From c0f8eb5a54349eeb9a28471592c94ff1160cc40e Mon Sep 17 00:00:00 2001 From: Iacopo Leardini Date: Thu, 30 May 2024 12:09:47 +0200 Subject: [PATCH 01/11] refactor(chat): improve error handling in thumbnail upload --- src/stories/chat/context/chatContext.tsx | 44 ++++++------------- src/stories/chat/index.stories.tsx | 8 +++- .../ThumbnailContainer/DeleteThumbnailX.tsx | 1 + .../parts/ThumbnailContainer/Thumbnail.tsx | 30 ++++++++----- .../chat/parts/ThumbnailContainer/index.tsx | 3 ++ 5 files changed, 42 insertions(+), 44 deletions(-) diff --git a/src/stories/chat/context/chatContext.tsx b/src/stories/chat/context/chatContext.tsx index 0e6654f6..e33eda80 100644 --- a/src/stories/chat/context/chatContext.tsx +++ b/src/stories/chat/context/chatContext.tsx @@ -66,39 +66,23 @@ export const ChatContextProvider = ({ setEditor, thumbnails, setThumbnails, - afterUploadCallback: (failed: string[]) => { - setThumbnails( - thumbnails.map((file) => { - if (failed.includes(file.id)) { - file.isLoadingMedia = false; - //file.isError = true; - } else { - file.isLoadingMedia = false; - //file.isError = false - } - return file; - }) - ); - }, + afterUploadCallback: (failed: string[]) => {}, - addThumbnails: ({ files }: { files: (File & CommentMedia)[] }) => { + addThumbnails: async ({ files }: { files: (File & CommentMedia)[] }) => { setThumbnails((prev) => [...prev, ...files]); - - if (onFileUpload) { - onFileUpload(files).then((data: Data) => { - const failed = data.failed?.map((f) => f.name); - setThumbnails((prev) => { - return prev.map((file) => { - file.isLoadingMedia = false; - if (failed?.length && failed.includes(file.id)) { - file.isError = true; - } else { - file.isError = false; - } - return file; - }); - }); + if (!onFileUpload) return; + try { + const data = await onFileUpload(files); + const failed = data.failed?.map((f) => f.name); + setThumbnails((prev) => { + return prev.map(file => { + file.isLoadingMedia = false; + file.isError = typeof file.name !== "undefined" && failed?.includes(file.name); + return file; + }) }); + } catch (e) { + console.log("Error uploading files", e); } }, clearInput: () => { diff --git a/src/stories/chat/index.stories.tsx b/src/stories/chat/index.stories.tsx index 3660ab85..47e957f3 100644 --- a/src/stories/chat/index.stories.tsx +++ b/src/stories/chat/index.stories.tsx @@ -260,9 +260,13 @@ Menus.args = { hasButtonsMenu: true, onFileUpload: async (files) => { return new Promise((resolve, reject) => { + // simulate a 2.5 seconds delay then fail the first file and succeed the others setTimeout(() => { - resolve({ failed: [], uploaded_ids: [] }); - }, 3000); + resolve({ + failed: [{errorCode: "GENERIC_ERROR", name: files[0].name}], + uploaded_ids: files.slice(1).map(file => ({id: parseInt(file.id)})) + }); + }, 2500); }); }, i18n: { diff --git a/src/stories/chat/parts/ThumbnailContainer/DeleteThumbnailX.tsx b/src/stories/chat/parts/ThumbnailContainer/DeleteThumbnailX.tsx index 76ef6bf0..9eeed5ea 100644 --- a/src/stories/chat/parts/ThumbnailContainer/DeleteThumbnailX.tsx +++ b/src/stories/chat/parts/ThumbnailContainer/DeleteThumbnailX.tsx @@ -12,6 +12,7 @@ const StyledDeleteThumbnailX = styled.div` opacity: 0; transition: opacity 0.2s; z-index: 2; + color: ${({ theme }) => theme.palette.grey[800]}; `; interface Props { diff --git a/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx b/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx index 089c49e2..e7e59ef5 100644 --- a/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx +++ b/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx @@ -4,23 +4,23 @@ import { Spinner } from "@zendeskgarden/react-loaders"; import { SpecialCard } from "../../../special-cards"; import { ReactComponent as VideoPlayIcon } from "../../../../assets/icons/video-play-icon.svg"; -const ImageCard = styled(SpecialCard)` +const ImageCard = styled(SpecialCard)<{isError?: boolean}>` padding: 0; position: relative; overflow: hidden; - min-width: 90px; + width: 90px; &:before { content: ""; + font-size: ${({ theme }) => theme.fontSizes.xs}; position: absolute; + padding: ${({ theme }) => theme.space.xs}; top: 0; left: 0; width: 100%; height: 100%; - background-color: ${({ theme }) => theme.palette.grey[800]}; - opacity: 0; + background-color: ${({ theme }) => theme.palette.grey[800]}00; // 0% opacity transition: opacity 0.2s; - z-index: 1; } &:hover { @@ -28,10 +28,18 @@ const ImageCard = styled(SpecialCard)` opacity: 1; } &:before { - opacity: 0.3; + background-color: ${({ theme }) => theme.palette.grey[800]}4d; // 30% opacity } } - + ${(p) => + p.isError && + ` + &:before{ + content: "Error Uploading Media"; + color: ${p.theme.palette.white}; + background-color: ${p.theme.palette.grey[800]}b3; // 0.7 opacity + } + `} &.video { svg { position: absolute; @@ -53,6 +61,7 @@ const Preview = styled.div<{ align-items: center; height: 100px; width: 100%; + color: ${({ theme }) => theme.palette.white}; ${(p) => p.url && @@ -98,13 +107,10 @@ const Thumbnail = ({ - {isError && ( - // todo: add error icon - error uploading media - )} {isLoadingMedia ? ( - + { previewUrl: file.url, id: file.id, isLoadingMedia: file.isLoadingMedia, + isError: file.isError, })); }, [thumbnails]); @@ -48,6 +50,7 @@ const ThumbnailContainer = ({ openLightbox }: Props) => { showX type={file.fileType} isLoadingMedia={file.isLoadingMedia} + isError={file.isError} removeThumbnail={() => { removeThumbnail(index); onDeleteThumbnail(file.id); From 565e8fc34b717050da443f9a770d95317e9e6cd0 Mon Sep 17 00:00:00 2001 From: Iacopo Leardini Date: Thu, 30 May 2024 16:34:08 +0200 Subject: [PATCH 02/11] refactor(chat): update CommentMedia interface to use 'error' string --- src/stories/chat/_types.tsx | 2 +- src/stories/chat/context/chatContext.tsx | 3 +- .../parts/ThumbnailContainer/Thumbnail.tsx | 31 ++++++++++++------- .../chat/parts/ThumbnailContainer/index.tsx | 14 ++------- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/stories/chat/_types.tsx b/src/stories/chat/_types.tsx index c96d2810..e072b0d6 100644 --- a/src/stories/chat/_types.tsx +++ b/src/stories/chat/_types.tsx @@ -45,7 +45,7 @@ export interface CommentMedia { type: string; name?: string; isLoadingMedia?: boolean; - isError?: boolean; + error?: string; url?: string; } diff --git a/src/stories/chat/context/chatContext.tsx b/src/stories/chat/context/chatContext.tsx index e33eda80..28946d9a 100644 --- a/src/stories/chat/context/chatContext.tsx +++ b/src/stories/chat/context/chatContext.tsx @@ -73,11 +73,10 @@ export const ChatContextProvider = ({ if (!onFileUpload) return; try { const data = await onFileUpload(files); - const failed = data.failed?.map((f) => f.name); setThumbnails((prev) => { return prev.map(file => { file.isLoadingMedia = false; - file.isError = typeof file.name !== "undefined" && failed?.includes(file.name); + file.error = data.failed?.find(f => f.name === file.name)?.errorCode; return file; }) }); diff --git a/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx b/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx index e7e59ef5..93576951 100644 --- a/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx +++ b/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx @@ -4,7 +4,7 @@ import { Spinner } from "@zendeskgarden/react-loaders"; import { SpecialCard } from "../../../special-cards"; import { ReactComponent as VideoPlayIcon } from "../../../../assets/icons/video-play-icon.svg"; -const ImageCard = styled(SpecialCard)<{isError?: boolean}>` +const ImageCard = styled(SpecialCard)<{error?: string, isLoading?: boolean}>` padding: 0; position: relative; overflow: hidden; @@ -32,14 +32,21 @@ const ImageCard = styled(SpecialCard)<{isError?: boolean}>` } } ${(p) => - p.isError && + p.error && ` &:before{ - content: "Error Uploading Media"; + content: "Error: ${p.error}"; color: ${p.theme.palette.white}; background-color: ${p.theme.palette.grey[800]}b3; // 0.7 opacity } `} + ${(p) => + p.isLoading && + ` + &:before{ + background-color: ${p.theme.palette.grey[800]}b3; // 0.7 opacity + } + `} &.video { svg { position: absolute; @@ -53,17 +60,15 @@ const ImageCard = styled(SpecialCard)<{isError?: boolean}>` } `; -const Preview = styled.div<{ - url?: string; -}>` +const Preview = styled.div<{url?: string;}>` display: flex; justify-content: center; align-items: center; height: 100px; width: 100%; color: ${({ theme }) => theme.palette.white}; - - ${(p) => + + ${p => p.url && ` background-image: url(${p.url}); @@ -86,7 +91,7 @@ interface Props { isLoadingMedia?: boolean; removeThumbnail?: () => void; showX?: boolean; - isError?: boolean; + error?: string; } const Thumbnail = ({ @@ -95,8 +100,8 @@ const Thumbnail = ({ removeThumbnail, clickThumbnail, showX, - isLoadingMedia = true, - isError = false, + isLoadingMedia, + error = "", }: Props) => { const handleCancel = (e: any) => { e.stopPropagation(); @@ -107,13 +112,15 @@ const Thumbnail = ({ {isLoadingMedia ? ( void; } @@ -33,7 +23,7 @@ const ThumbnailContainer = ({ openLightbox }: Props) => { previewUrl: file.url, id: file.id, isLoadingMedia: file.isLoadingMedia, - isError: file.isError, + error: file.error, })); }, [thumbnails]); @@ -50,7 +40,7 @@ const ThumbnailContainer = ({ openLightbox }: Props) => { showX type={file.fileType} isLoadingMedia={file.isLoadingMedia} - isError={file.isError} + error={file.error} removeThumbnail={() => { removeThumbnail(index); onDeleteThumbnail(file.id); From 16278fe1d2ca5cd5b8e9b8c025b161f810701014 Mon Sep 17 00:00:00 2001 From: Iacopo Leardini Date: Thu, 30 May 2024 16:56:05 +0200 Subject: [PATCH 03/11] refactor(chat): update Thumbnail component styles --- src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx b/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx index 93576951..9e8af36b 100644 --- a/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx +++ b/src/stories/chat/parts/ThumbnailContainer/Thumbnail.tsx @@ -49,6 +49,7 @@ const ImageCard = styled(SpecialCard)<{error?: string, isLoading?: boolean}>` `} &.video { svg { + color: ${({ theme }) => theme.palette.grey[800]}; position: absolute; top: 50%; left: 50%; @@ -121,6 +122,7 @@ const Thumbnail = ({ style={{ display: "flex", position: "absolute", + color: "white", alignItems: "center", justifyContent: "center", }} @@ -137,7 +139,7 @@ const Thumbnail = ({ - + )} From a821d1fc97670a6c8252a8eedbc7c367a8eb0477 Mon Sep 17 00:00:00 2001 From: "Luca Cannarozzo (@cannarocks)" Date: Fri, 31 May 2024 09:24:37 +0200 Subject: [PATCH 04/11] refactor(highlight): update transcript-sentiment component to show sentiment only for non-zero speakers --- src/stories/highlight/demo-parts/data.ts | 10 +++++----- .../highlight/demo-parts/transcript-sentiment.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/stories/highlight/demo-parts/data.ts b/src/stories/highlight/demo-parts/data.ts index 4f349007..b3c38cf8 100644 --- a/src/stories/highlight/demo-parts/data.ts +++ b/src/stories/highlight/demo-parts/data.ts @@ -21,7 +21,7 @@ export const DemoTranscript = { alternatives: [ { transcript: - "Bene? Sì la vedo la sento bene bene perfetto allora direi che possiamo cominciare bene, innanzitutto grazie per aver accettato di partecipare a quest'incontro. Io come vi ho accennato su Miriam e lavoro in società del gruppo Unipol e oggi vorrei testare con con lei il prototipo di un nuovo servizio Il nostro compito quindi è sapere che cosa ne pensa e se necessario se valuta eventuali miglioramenti pronto? Pronto non mi vede. Okay io ho la webcam accesa proviamo a continuare così. L'importante è che mi senta. Sì, io la sento. Perfetto. Allora appunto dicevo che oggi con lei vorrei testare il prototipo di un nuovo servizio a cui stiamo lavorando e quindi vorremmo capire insieme da lei quali sono le sue considerazioni e se ci sono cose che vorrebbe che fossero diverse da come effettivamente le vede. E come già saprà tutto quello che diremo e che faremo sarà registrato sia l'audio che il video e I risultati comunque tra di noi ci serviranno solamente ai fini del del progetto stesso. Tuttavia si senta assolutamente libero di fare tutte le considerazioni che più opportune sia positive che negative che siano quindi non si faccia assolutamente nessun problema anche perché non è lei il l'oggetto di valutazione ma appunto il il prototipo che andremo a vedere insieme. Durante il test e e perché già il fatto che abbia delle domande da sottopormi per noi è un'evidenza molto importante. Le le chiederò di svolgere alcuni compiti durante I quali le chiederò di parlare sempre a voce alta e di raccontarmi che cosa sta facendo passo dopo passo nel mentre che lo sta facendo ed eventualmente di dirmi come si aspetterebbe che fosse una determinata cosa che vede. Sì. Okay. E niente okay allora se è tutto chiaro possiamo possiamo cominciare. Partiamo okay allora innanzitutto le volevo chiedere quale fosse la sua occupazione? Io sono impiegato all'USL sanitaria qual è il il telefono che usa generalmente e da cui adesso sto facendo il la chiamata? È il mio telefono cioè il mio il mio cellulare personale il mio sì che che modello è? È un Motorola Moto G sette okay perfetto e le volevo chiedere indicativamente quanto tempo nell'arco della giornata utilizzando internet sia per ragioni lavorative che durante il suo tempo libero beh lo uso lo uso parecchio sia sul lavoro ma soprattutto per il mio tempo libero diciamo che sul lavoro uso spesso il il computer uso diciamo la rete la rete aziendale più che internet non so diciamo un paio d'ore al giorno sul lavoro e sì un'ora anche due un paio d'ore anche come nel tempo libero okay e utilizza l'app di Unipolsai? Un po' poco cioè non non la uso perché mhmm non l'ho scaricata ma mhmm non ho non ho capito bene il funzionamento non l'ho usata adesso okay e generalmente per quali spostamenti utilizza l'automobile? La uso principalmente casa lavoro. Okay e se dovesse farmi una stima mh quanto tempo", + "Bene? Sì la vedo la sento bene bene perfetto allora direi che possiamo cominciare bene, innanzitutto grazie per aver accettato di partecipare a quest'incontro. Io come vi ho accennato su Miriam e lavoro in società del gruppo e oggi vorrei testare con con lei il prototipo di un nuovo servizio Il nostro compito quindi è sapere che cosa ne pensa e se necessario se valuta eventuali miglioramenti pronto? Pronto non mi vede. Okay io ho la webcam accesa proviamo a continuare così. L'importante è che mi senta. Sì, io la sento. Perfetto. Allora appunto dicevo che oggi con lei vorrei testare il prototipo di un nuovo servizio a cui stiamo lavorando e quindi vorremmo capire insieme da lei quali sono le sue considerazioni e se ci sono cose che vorrebbe che fossero diverse da come effettivamente le vede. E come già saprà tutto quello che diremo e che faremo sarà registrato sia l'audio che il video e I risultati comunque tra di noi ci serviranno solamente ai fini del del progetto stesso. Tuttavia si senta assolutamente libero di fare tutte le considerazioni che più opportune sia positive che negative che siano quindi non si faccia assolutamente nessun problema anche perché non è lei il l'oggetto di valutazione ma appunto il il prototipo che andremo a vedere insieme. Durante il test e e perché già il fatto che abbia delle domande da sottopormi per noi è un'evidenza molto importante. Le le chiederò di svolgere alcuni compiti durante I quali le chiederò di parlare sempre a voce alta e di raccontarmi che cosa sta facendo passo dopo passo nel mentre che lo sta facendo ed eventualmente di dirmi come si aspetterebbe che fosse una determinata cosa che vede. Sì. Okay. E niente okay allora se è tutto chiaro possiamo possiamo cominciare. Partiamo okay allora innanzitutto le volevo chiedere quale fosse la sua occupazione? Io sono impiegato all'USL sanitaria qual è il il telefono che usa generalmente e da cui adesso sto facendo il la chiamata? È il mio telefono cioè il mio il mio cellulare personale il mio sì che che modello è? È un Motorola Moto G sette okay perfetto e le volevo chiedere indicativamente quanto tempo nell'arco della giornata utilizzando internet sia per ragioni lavorative che durante il suo tempo libero beh lo uso lo uso parecchio sia sul lavoro ma soprattutto per il mio tempo libero diciamo che sul lavoro uso spesso il il computer uso diciamo la rete la rete aziendale più che internet non so diciamo un paio d'ore al giorno sul lavoro e sì un'ora anche due un paio d'ore anche come nel tempo libero okay e utilizza l'app di ? Un po' poco cioè non non la uso perché mhmm non l'ho scaricata ma mhmm non ho non ho capito bene il funzionamento non l'ho usata adesso okay e generalmente per quali spostamenti utilizza l'automobile? La uso principalmente casa lavoro. Okay e se dovesse farmi una stima mh quanto tempo", confidence: 0.9921875, words: [ { @@ -3157,13 +3157,13 @@ export const DemoTranscript = { punctuated_word: "di", }, { - word: "unipolsai", + word: "", start: 251.7, end: 252.2, confidence: 0.7298584, speaker: 0, speaker_confidence: 0.3623047, - punctuated_word: "Unipolsai?", + punctuated_word: "?", }, { word: "un", @@ -3618,7 +3618,7 @@ export const DemoTranscript = { ], paragraphs: { transcript: - "\nSpeaker 0: Bene?\n\nSpeaker 1: Sì la vedo la sento bene\n\nSpeaker 0: bene perfetto allora direi che possiamo cominciare bene, innanzitutto grazie per aver accettato di partecipare a quest'incontro. Io come vi ho accennato su Miriam e lavoro in società del gruppo Unipol e oggi vorrei testare con con lei il prototipo di un nuovo servizio Il nostro compito quindi è sapere che cosa ne pensa e se necessario se valuta eventuali miglioramenti\n\nSpeaker 1: pronto? Pronto\n\nSpeaker 0: non mi vede. Okay io ho la webcam accesa proviamo a continuare così. L'importante è che mi senta.\n\nSpeaker 1: Sì, io la sento.\n\nSpeaker 0: Perfetto. Allora appunto dicevo che oggi con lei vorrei testare il prototipo di un nuovo servizio a cui stiamo lavorando e quindi vorremmo capire insieme da lei quali sono le sue considerazioni e se ci sono cose che vorrebbe che fossero diverse da come effettivamente le vede. E come già saprà tutto quello che diremo e che faremo sarà registrato sia l'audio che il video e I risultati comunque tra di noi ci serviranno solamente ai fini del del progetto stesso. Tuttavia si senta assolutamente libero di fare tutte le considerazioni che più opportune sia positive che negative che siano quindi non si faccia assolutamente nessun problema anche perché non è lei il l'oggetto di valutazione ma appunto il il prototipo che andremo a vedere insieme. Durante il test e e perché già il fatto che abbia delle domande da sottopormi per noi è un'evidenza molto importante.\n\nLe le chiederò di svolgere alcuni compiti durante I quali le chiederò di parlare sempre a voce alta e di raccontarmi che cosa sta facendo passo dopo passo nel mentre che lo sta facendo ed eventualmente di dirmi come si aspetterebbe che fosse una determinata cosa che vede.\n\nSpeaker 1: Sì.\n\nSpeaker 0: Okay. E niente okay allora se è tutto chiaro possiamo possiamo cominciare. Partiamo okay allora innanzitutto le volevo chiedere quale fosse la sua occupazione?\n\nSpeaker 1: Io sono impiegato all'USL sanitaria\n\nSpeaker 0: qual è il il telefono che usa generalmente e da cui adesso sto facendo il la chiamata?\n\nSpeaker 1: È il mio telefono cioè il mio il mio cellulare personale il mio\n\nSpeaker 0: sì che che modello\n\nSpeaker 1: è? È un Motorola Moto G sette\n\nSpeaker 0: okay perfetto e le volevo chiedere indicativamente quanto tempo nell'arco della giornata utilizzando internet sia per ragioni lavorative che durante il suo tempo libero\n\nSpeaker 1: beh lo uso lo uso parecchio sia sul lavoro ma soprattutto per il mio tempo libero diciamo che sul lavoro uso spesso il il computer uso diciamo la rete la rete aziendale più che internet non so diciamo un paio d'ore al giorno sul lavoro e sì un'ora anche due un paio d'ore anche come nel tempo libero\n\nSpeaker 0: okay e utilizza l'app di Unipolsai?\n\nSpeaker 1: Un po' poco cioè non non la uso perché mhmm non l'ho scaricata ma mhmm non ho non ho capito bene il funzionamento non l'ho usata adesso\n\nSpeaker 0: okay e generalmente per quali spostamenti utilizza l'automobile?\n\nSpeaker 1: La uso principalmente casa lavoro.\n\nSpeaker 0: Okay e se dovesse farmi una stima mh quanto tempo", + "\nSpeaker 0: Bene?\n\nSpeaker 1: Sì la vedo la sento bene\n\nSpeaker 0: bene perfetto allora direi che possiamo cominciare bene, innanzitutto grazie per aver accettato di partecipare a quest'incontro. Io come vi ho accennato su Miriam e lavoro in società del gruppo e oggi vorrei testare con con lei il prototipo di un nuovo servizio Il nostro compito quindi è sapere che cosa ne pensa e se necessario se valuta eventuali miglioramenti\n\nSpeaker 1: pronto? Pronto\n\nSpeaker 0: non mi vede. Okay io ho la webcam accesa proviamo a continuare così. L'importante è che mi senta.\n\nSpeaker 1: Sì, io la sento.\n\nSpeaker 0: Perfetto. Allora appunto dicevo che oggi con lei vorrei testare il prototipo di un nuovo servizio a cui stiamo lavorando e quindi vorremmo capire insieme da lei quali sono le sue considerazioni e se ci sono cose che vorrebbe che fossero diverse da come effettivamente le vede. E come già saprà tutto quello che diremo e che faremo sarà registrato sia l'audio che il video e I risultati comunque tra di noi ci serviranno solamente ai fini del del progetto stesso. Tuttavia si senta assolutamente libero di fare tutte le considerazioni che più opportune sia positive che negative che siano quindi non si faccia assolutamente nessun problema anche perché non è lei il l'oggetto di valutazione ma appunto il il prototipo che andremo a vedere insieme. Durante il test e e perché già il fatto che abbia delle domande da sottopormi per noi è un'evidenza molto importante.\n\nLe le chiederò di svolgere alcuni compiti durante I quali le chiederò di parlare sempre a voce alta e di raccontarmi che cosa sta facendo passo dopo passo nel mentre che lo sta facendo ed eventualmente di dirmi come si aspetterebbe che fosse una determinata cosa che vede.\n\nSpeaker 1: Sì.\n\nSpeaker 0: Okay. E niente okay allora se è tutto chiaro possiamo possiamo cominciare. Partiamo okay allora innanzitutto le volevo chiedere quale fosse la sua occupazione?\n\nSpeaker 1: Io sono impiegato all'USL sanitaria\n\nSpeaker 0: qual è il il telefono che usa generalmente e da cui adesso sto facendo il la chiamata?\n\nSpeaker 1: È il mio telefono cioè il mio il mio cellulare personale il mio\n\nSpeaker 0: sì che che modello\n\nSpeaker 1: è? È un Motorola Moto G sette\n\nSpeaker 0: okay perfetto e le volevo chiedere indicativamente quanto tempo nell'arco della giornata utilizzando internet sia per ragioni lavorative che durante il suo tempo libero\n\nSpeaker 1: beh lo uso lo uso parecchio sia sul lavoro ma soprattutto per il mio tempo libero diciamo che sul lavoro uso spesso il il computer uso diciamo la rete la rete aziendale più che internet non so diciamo un paio d'ore al giorno sul lavoro e sì un'ora anche due un paio d'ore anche come nel tempo libero\n\nSpeaker 0: okay e utilizza l'app di ?\n\nSpeaker 1: Un po' poco cioè non non la uso perché mhmm non l'ho scaricata ma mhmm non ho non ho capito bene il funzionamento non l'ho usata adesso\n\nSpeaker 0: okay e generalmente per quali spostamenti utilizza l'automobile?\n\nSpeaker 1: La uso principalmente casa lavoro.\n\nSpeaker 0: Okay e se dovesse farmi una stima mh quanto tempo", paragraphs: [ { @@ -3814,7 +3814,7 @@ export const DemoTranscript = { { sentences: [ { - text: "okay e utilizza l'app di Unipolsai?", + text: "okay e utilizza l'app di ?", start: 249.7, end: 252.2, }, diff --git a/src/stories/highlight/demo-parts/transcript-sentiment.tsx b/src/stories/highlight/demo-parts/transcript-sentiment.tsx index 7a37a8f3..a328bec0 100644 --- a/src/stories/highlight/demo-parts/transcript-sentiment.tsx +++ b/src/stories/highlight/demo-parts/transcript-sentiment.tsx @@ -53,7 +53,7 @@ export const TSentiment = ( {formatDuration(p.start - args.offset)} -{" "} {formatDuration(p.end - args.offset)}) - {p.sentiment && getSentiment(p.sentiment).text} + {p.speaker !== 0 && p.sentiment && getSentiment(p.sentiment).text} {p.words.map((w) => ( Date: Fri, 31 May 2024 11:11:31 +0200 Subject: [PATCH 05/11] =?UTF-8?q?=F0=9F=9A=80=20feat(player):=20add=20show?= =?UTF-8?q?Controls=20prop=20to=20PlayerArgs=20and=20WrapperProps=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(player):=20update=20Container=20and=20Contro?= =?UTF-8?q?lsWrapper=20styles=20based=20on=20showControls=20prop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/player/_types.tsx | 2 ++ src/stories/player/index.stories.tsx | 7 +++++++ src/stories/player/index.tsx | 2 ++ src/stories/player/parts/container.tsx | 8 +++++--- src/stories/player/parts/controls.tsx | 9 +++++++-- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/stories/player/_types.tsx b/src/stories/player/_types.tsx index 5749144d..3228723a 100644 --- a/src/stories/player/_types.tsx +++ b/src/stories/player/_types.tsx @@ -10,6 +10,7 @@ export interface PlayerArgs extends HTMLAttributes { bookmarks?: IBookmark[]; handleBookmarkUpdate?: (bookmark: IBookmark) => void; i18n?: PlayerI18n; + showControls?: boolean; } export interface PlayerI18n { @@ -31,6 +32,7 @@ export interface IBookmark { export interface WrapperProps { isPlaying?: boolean; isLoaded?: boolean; + showControls?: boolean; } type VideoTag = { diff --git a/src/stories/player/index.stories.tsx b/src/stories/player/index.stories.tsx index bfbe6097..63363c65 100644 --- a/src/stories/player/index.stories.tsx +++ b/src/stories/player/index.stories.tsx @@ -79,6 +79,13 @@ Basic.args = { ...defaultArgs, }; +export const ShowControls = Template.bind({}); +ShowControls.args = { + ...defaultArgs, + url: "https://mediaconvert-test-output-bk.s3.eu-west-1.amazonaws.com/db00e97cfb85971e3fa71b7735142e07ab2d1ebf_1605195177.m3u8", + showControls: true, +}; + export const Streaming = Template.bind({}); Streaming.args = { ...defaultArgs, diff --git a/src/stories/player/index.tsx b/src/stories/player/index.tsx index bc5f0d4a..fc2fff87 100644 --- a/src/stories/player/index.tsx +++ b/src/stories/player/index.tsx @@ -50,6 +50,7 @@ const PlayerCore = forwardRef( isLoaded={isLoaded} isPlaying={context.isPlaying} ref={containerRef} + showControls={props.showControls} > {!isLoaded ? ( @@ -68,6 +69,7 @@ const PlayerCore = forwardRef( isCutting={isCutting} onBookMarkUpdated={props.handleBookmarkUpdate} i18n={props.i18n} + showControls={props.showControls} /> diff --git a/src/stories/player/parts/container.tsx b/src/stories/player/parts/container.tsx index 0803498d..f4e6101d 100644 --- a/src/stories/player/parts/container.tsx +++ b/src/stories/player/parts/container.tsx @@ -6,9 +6,11 @@ import { VideoStyle } from "./video"; export const Container = styled.div` position: relative; - display: flex; - flex-direction: column; - justify-content: center; + ${({ showControls }) => !showControls && ` + display: flex; + flex-direction: column; + justify-content: center; + `} height: 100%; width: 100%; video { diff --git a/src/stories/player/parts/controls.tsx b/src/stories/player/parts/controls.tsx index 2f650b27..1730baad 100644 --- a/src/stories/player/parts/controls.tsx +++ b/src/stories/player/parts/controls.tsx @@ -15,7 +15,7 @@ import { formatDuration } from "../utils"; import useDebounce from "../../../hooks/useDebounce"; export const ControlsWrapper = styled.div` - position: absolute; + ${({ showControls }) => showControls ? "position: relative;" : "position: absolute;"} bottom: 0; left: 0; right: 0; @@ -66,6 +66,7 @@ export const Controls = ({ isCutting, onBookMarkUpdated, i18n, + showControls = false, }: { container: HTMLDivElement | null; onCutHandler?: (time: number) => void; @@ -73,6 +74,7 @@ export const Controls = ({ isCutting?: boolean; onBookMarkUpdated?: (bookmark: IBookmark) => void; i18n?: PlayerI18n; + showControls?: boolean; }) => { const [progress, setProgress] = useState(0); const [tooltipMargin, setTooltipMargin] = useState(0); @@ -195,7 +197,10 @@ export const Controls = ({ }, [debouncedMark, onBookMarkUpdated]); return ( - + Date: Fri, 31 May 2024 11:20:44 +0200 Subject: [PATCH 06/11] =?UTF-8?q?=F0=9F=9A=80=20feat(cutterButton.tsx):=20?= =?UTF-8?q?add=20styled=20component=20for=20Cutter=20button=20to=20prevent?= =?UTF-8?q?=20breaking=20on=20smaller=20screens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/player/parts/cutterButton.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/stories/player/parts/cutterButton.tsx b/src/stories/player/parts/cutterButton.tsx index b4ae301a..2624520e 100644 --- a/src/stories/player/parts/cutterButton.tsx +++ b/src/stories/player/parts/cutterButton.tsx @@ -3,6 +3,13 @@ import { ReactComponent as TagIcon } from "../../../assets/icons/tag-stroke.svg" import { ReactComponent as PlusIcon } from "../assets/plus.svg"; import { Button } from "../../buttons/button"; import { PlayerI18n } from "../_types"; +import { Span } from "../../typography/span"; +import { styled } from "styled-components"; + +// Prevent button from breaking on smaller screens +const StyledButton = styled(Button)` + overflow: visible; +`; export const Cutter = ({ onCutHandler, @@ -20,7 +27,7 @@ export const Cutter = ({ if (!onCutHandler) return null; return ( - + ); }; From 2ff0e43387656c051b209868eba13efa270f0f28 Mon Sep 17 00:00:00 2001 From: Kariamos Date: Thu, 6 Jun 2024 18:06:37 +0200 Subject: [PATCH 07/11] feat: added new component for internal avatar --- src/stories/avatar/InternalAvatar.tsx | 19 +++++++++++++++++++ src/stories/avatar/index.tsx | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 src/stories/avatar/InternalAvatar.tsx diff --git a/src/stories/avatar/InternalAvatar.tsx b/src/stories/avatar/InternalAvatar.tsx new file mode 100644 index 00000000..51e4abd6 --- /dev/null +++ b/src/stories/avatar/InternalAvatar.tsx @@ -0,0 +1,19 @@ +import styled from "styled-components"; +import LogoIcon from "../../assets/logo-icon.svg"; + +const StyledInternalAvatar = styled.div` + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +`; + +const InternalAvatar = () => { + return ( + + avatar + + ); +}; + +export default InternalAvatar; diff --git a/src/stories/avatar/index.tsx b/src/stories/avatar/index.tsx index c81d0605..d0ba14ee 100644 --- a/src/stories/avatar/index.tsx +++ b/src/stories/avatar/index.tsx @@ -4,6 +4,7 @@ import styled from "styled-components"; import { AvatarArgs } from "./_types"; import LogoIcon from "../../assets/logo-icon.svg"; import { getColor } from "../theme/utils"; +import InternalAvatar from "./InternalAvatar"; const UgAvatar = styled(ZendeskAvatar)` text-transform: uppercase; @@ -35,7 +36,7 @@ const Avatar = ({ isSystem, badge, ...props }: AvatarArgs) => { if (type === "image") return avatar; if (type === "text") return {props.children}; - if (type === "system") return avatar; + if (type === "system") return ; }; return ( From e694598870fe1a6df2008868b1f5d13533b20a94 Mon Sep 17 00:00:00 2001 From: Kariamos Date: Thu, 6 Jun 2024 18:09:12 +0200 Subject: [PATCH 08/11] chore: removed unused import --- src/stories/avatar/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stories/avatar/index.tsx b/src/stories/avatar/index.tsx index d0ba14ee..797b466a 100644 --- a/src/stories/avatar/index.tsx +++ b/src/stories/avatar/index.tsx @@ -2,7 +2,6 @@ import { Avatar as ZendeskAvatar } from "@zendeskgarden/react-avatars"; import { theme } from "../theme"; import styled from "styled-components"; import { AvatarArgs } from "./_types"; -import LogoIcon from "../../assets/logo-icon.svg"; import { getColor } from "../theme/utils"; import InternalAvatar from "./InternalAvatar"; From fb2add3ddbc5d27515eb7d014f4ebe1e0652b027 Mon Sep 17 00:00:00 2001 From: Kariamos Date: Mon, 10 Jun 2024 11:25:42 +0200 Subject: [PATCH 09/11] removed useless style --- src/stories/avatar/InternalAvatar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stories/avatar/InternalAvatar.tsx b/src/stories/avatar/InternalAvatar.tsx index 51e4abd6..1af1cc9d 100644 --- a/src/stories/avatar/InternalAvatar.tsx +++ b/src/stories/avatar/InternalAvatar.tsx @@ -5,7 +5,6 @@ const StyledInternalAvatar = styled.div` display: flex; align-items: center; justify-content: center; - cursor: pointer; `; const InternalAvatar = () => { From 1bcd6ac2fee57bc43c97fe74d129394db62b889c Mon Sep 17 00:00:00 2001 From: Kariamos Date: Mon, 10 Jun 2024 12:21:09 +0200 Subject: [PATCH 10/11] feat: centered tooltip attachment text --- src/stories/chat/parts/bar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stories/chat/parts/bar.tsx b/src/stories/chat/parts/bar.tsx index 8462c92b..542b093d 100644 --- a/src/stories/chat/parts/bar.tsx +++ b/src/stories/chat/parts/bar.tsx @@ -126,6 +126,7 @@ const CommentBar = ({ From 3d1905fb83042265852e4d203e5f26059072ba1a Mon Sep 17 00:00:00 2001 From: Marco Bonomo Date: Mon, 10 Jun 2024 17:14:57 +0200 Subject: [PATCH 11/11] =?UTF-8?q?=F0=9F=9A=80=20feat(highlight):=20add=20C?= =?UTF-8?q?reateObservationButton=20component=20=F0=9F=94=A7=20chore(highl?= =?UTF-8?q?ight):=20refactor=20Highlight=20component=20to=20handle=20selec?= =?UTF-8?q?tion=20button=20click?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../highlight/CreateObservationButton.tsx | 15 ++ src/stories/highlight/_types.tsx | 7 +- src/stories/highlight/index.stories.tsx | 11 ++ src/stories/highlight/index.tsx | 129 +++++++++++++----- 4 files changed, 124 insertions(+), 38 deletions(-) create mode 100644 src/stories/highlight/CreateObservationButton.tsx diff --git a/src/stories/highlight/CreateObservationButton.tsx b/src/stories/highlight/CreateObservationButton.tsx new file mode 100644 index 00000000..132fdca4 --- /dev/null +++ b/src/stories/highlight/CreateObservationButton.tsx @@ -0,0 +1,15 @@ +import { styled } from "styled-components"; +import { Button } from "../buttons/button"; + +const CreateObservationButton = styled(Button) <{ + position: { x: number; y: number }; +}>` + user-select: none; + position: absolute; + left: ${({ position: { x } }) => x}px; + top: ${({ position: { y } }) => y}px; + transform: translate(-50%, 0); + z-index: ${({ theme }) => theme.levels.front}; +`; + +export { CreateObservationButton }; \ No newline at end of file diff --git a/src/stories/highlight/_types.tsx b/src/stories/highlight/_types.tsx index 56f04522..2d067a50 100644 --- a/src/stories/highlight/_types.tsx +++ b/src/stories/highlight/_types.tsx @@ -14,13 +14,14 @@ export interface HighlightArgs { isBold?: boolean; /** Renders with monospace font */ isMonospace?: boolean; - /** Adjusts the font size. By default font size is medium */ size?: "xs" | "sm" | "md" | "lg" | "xl" | "xxl" | "xxxl"; - handleSelection?: (part: { from: number; to: number; text: string }) => void; - search?: string; + onSelectionButtonClick?: (part: { from: number; to: number; text: string }) => void; + i18n?: { + selectionButtonLabel: string; + }; } export interface Observation extends Omit { diff --git a/src/stories/highlight/index.stories.tsx b/src/stories/highlight/index.stories.tsx index f68d10b1..f187d4da 100644 --- a/src/stories/highlight/index.stories.tsx +++ b/src/stories/highlight/index.stories.tsx @@ -578,6 +578,17 @@ const defaultArgs: StoryArgs = { export const Default = Template.bind({}); Default.args = defaultArgs; +export const WithSelectionButton = Template.bind({}); +WithSelectionButton.args = { + ...defaultArgs, + onSelectionButtonClick: (part) => { + console.log("CreateObservation: " + JSON.stringify(part)); + }, + i18n: { + selectionButtonLabel: "Add observation", + }, +}; + export const VideoSync = VideoTemplate.bind({}); VideoSync.args = { ...defaultArgs, diff --git a/src/stories/highlight/index.tsx b/src/stories/highlight/index.tsx index 4f5d95cc..9d1405f0 100644 --- a/src/stories/highlight/index.tsx +++ b/src/stories/highlight/index.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren, useCallback, useEffect, useMemo, useRef } from "react"; +import { PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from "react"; import styled from "styled-components"; import { getColor } from "../theme/utils"; import { HighlightArgs, Observation, WordProps } from "./_types"; @@ -6,6 +6,8 @@ import { HighlightContextProvider } from "./highlightContext"; import { Searchable } from "./searchable"; import { Tooltip } from "../tooltip"; import { theme } from "../theme"; +import { ReactComponent as TagIcon } from "../../assets/icons/tag-stroke.svg"; +import { CreateObservationButton } from "./CreateObservationButton"; const StyledWord = styled.div< WordProps & { observations?: Observation[] } @@ -60,10 +62,22 @@ const Layer = styled.div<{ */ const Highlight = (props: PropsWithChildren) => { + const { onSelectionButtonClick, search, i18n } = props; const ref = useRef(null); + const [isSelecting, setIsSelecting] = useState(false); + const [position, setPosition] = useState<{ + x: number; + y: number; + }>(); + const [selection, setSelection] = useState<{ + from: number; + to: number; + text: string; + }>(); + const activeSelection = document.getSelection(); const extractText = (selection: Selection) => { - if(selection.anchorNode === null || selection.focusNode === null) return ""; + if (selection.anchorNode === null || selection.focusNode === null) return ""; var range = selection.getRangeAt(0); var tempDiv = document.createElement("div"); @@ -81,39 +95,68 @@ const Highlight = (props: PropsWithChildren) => { }; const handleSelectionChange = useCallback(() => { - const activeSelection = document.getSelection(); - - if (!activeSelection) { - return; - } - - // Extract the text from the selection cleaning unselectable items - const text = extractText(activeSelection); - if (!text) return; - - const anchorNode = activeSelection?.anchorNode?.parentElement; - const focusNode = activeSelection?.focusNode?.parentElement; - - if ( - anchorNode && - focusNode && - ref.current?.contains(anchorNode) && // Selection starts inside the ref - ref.current?.contains(focusNode) // Selection ends inside the ref - ) { - const selectionPart = { - from: Math.min( - Number.parseFloat(anchorNode.getAttribute("data-start") ?? "0"), - Number.parseFloat(focusNode.getAttribute("data-start") ?? "0") - ), - to: Math.max( - Number.parseFloat(anchorNode.getAttribute("data-end") ?? "0"), - Number.parseFloat(focusNode.getAttribute("data-end") ?? "0") - ), - }; - - props?.handleSelection?.({ ...selectionPart, text }); + if (activeSelection && activeSelection.toString().length > 0) { + // Extract the text from the selection cleaning unselectable items + const text = extractText(activeSelection); + if (!text) return; + + const anchorNode = activeSelection?.anchorNode?.parentElement; + const focusNode = activeSelection?.focusNode?.parentElement; + + if ( + anchorNode && + focusNode && + ref.current?.contains(anchorNode) && // Selection starts inside the ref + ref.current?.contains(focusNode) // Selection ends inside the ref + ) { + if (props?.onSelectionButtonClick) { + setIsSelecting(true); + + const range = activeSelection.getRangeAt(0); + const rects = range.getClientRects(); + const lastRect = rects[rects.length - 1]; + const containerRect = + ref && ref.current + ? ref.current.getBoundingClientRect() + : null; + + if (!lastRect || !containerRect) return; + + const relativeY = + lastRect.bottom - containerRect.top + ref.current.scrollTop; + const relativeX = + lastRect.right - containerRect.left + ref.current.scrollLeft; + + if (relativeY > 0 || relativeX > 0) + // Fix to avoid the button to be placed sometimes at the top left corner of the screen (X: 0, Y: 0) + setPosition({ + x: relativeX, + y: relativeY + 15, + }); + } else { + setIsSelecting(false); + } + + const selectionPart = { + from: Math.min( + Number.parseFloat(anchorNode.getAttribute("data-start") ?? "0"), + Number.parseFloat(focusNode.getAttribute("data-start") ?? "0") + ), + to: Math.max( + Number.parseFloat(anchorNode.getAttribute("data-end") ?? "0"), + Number.parseFloat(focusNode.getAttribute("data-end") ?? "0") + ), + }; + + props?.handleSelection?.({ ...selectionPart, text }); + setSelection({ ...selectionPart, text }); + } else { + setIsSelecting(false); + } + } else { + setIsSelecting(false); } - }, [props]); + }, [props, activeSelection]); useEffect(() => { if (ref.current === null) return; @@ -125,8 +168,24 @@ const Highlight = (props: PropsWithChildren) => { }, [ref, props, handleSelectionChange]); return ( - + {props.children} + {isSelecting && ( + onSelectionButtonClick(selection) })} + > + + + + {i18n?.selectionButtonLabel ?? "Create observation"} + + )} ); };