Call recorder: skeleton loading state + port front components to twenty-ui (#22473)
## What this does
Three changes to the call-recorder app's recording/transcript front
component:
### 1. Fix loading flicker with a skeleton loader (`4dc3a167`)
The widget's loading state rendered an empty `<video controls>` element
at 16:9, which was torn down and replaced once the recording query
resolved — so every open flashed a video player, even for calendar
events with no video at all.
- New `RecordingSkeletonLoader`: three transcript-shaped pulsing rows
(avatar circle + speaker bar + text bars) held at the same 240px
min-height as the empty state, so nothing jumps when content arrives.
Visuals follow the twenty-front skeleton standard
(react-loading-skeleton theming: `background.tertiary` base,
`background.transparent.lighter` highlight sweep, 4px radius), rebuilt
with emotion since the sandboxed bundle can't import the host's global
skeleton stylesheet.
- `RecordingVideoPlayer` now only renders with a real URL (`src:
string`).
### 2. Port front components to twenty-ui (`cea0d6ee`)
`twenty-ui@1.0.0-alpha.1` is now published and consumable by apps
(CSS-injection build support + the renderer's style bridge), so the
"remove once twenty-ui can be imported safely" duplications are
resolved:
- Deleted `recording-theme-css-variables.ts` → all components use
`themeCssVariables` from `twenty-ui/theme-constants` (only non-1:1
rename: `accent.primary` → `accent.accent9`).
- Deleted `TranscriptSpeakerAvatar` / `TranscriptSpeakerChip` → replaced
with twenty-ui `Avatar` (`size="md"`, `type="rounded"`) and `Chip`
(`ChipVariant.Transparent`, `isBold`, non-clickable) in
`TranscriptEntryListItem`.
- Added `import 'twenty-ui/style.css'` to the front-component entry,
plus a root `css.d.ts` declaration. The import is load-bearing: the SDK
inlines the CSS into the component bundle and the renderer's style
bridge injects it into the host document — the host's own twenty-ui CSS
can't be relied on, since scoped class hashes are content-derived and
drift between builds.
- Bumped `twenty-sdk` / `twenty-client-sdk` to `^2.18.0` (needed for the
CSS-injection build support) and added `twenty-ui@^1.0.0-alpha.1`.
### 3. Fix manifest build + typecheck after the port (`baa6eb2f`)
- `yarn twenty dev --once` failed at "Building manifest..." — the
manifest build loads front-component modules with `twenty-ui`
proxy-mocked, so the named `themeCssVariables` import resolves to
`undefined` and any static `${themeCssVariables.x}` interpolation throws
at module scope. All static interpolations are now lazy (`${() =>
themeCssVariables.x}`), deferring the access to render time in the
browser.
- Added `css.d.ts` to `tsconfig.spec.json`'s `include` — its
`["src/**"]` include replaced the parent config's default file set, so
the `*.css` module declaration was never loaded and `yarn typecheck`
failed with TS2882.
## Notes for review / testing
- Worth exercising manually: the skeleton during load, speaker
chips/avatars rendering via twenty-ui, and hovering a long (overflowing)
speaker name — Chip's tooltip uses a react-dom portal, which is the one
path not previously exercised inside the front-component sandbox.
- App version intentionally left at `1.0.4`.
https://claude.ai/code/session_01WuHQFWiRocn82ejxsnmz28
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WuHQFWiRocn82ejxsnmz28)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22473?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -0,0 +1 @@
|
||||
declare module '*.css';
|
||||
@@ -34,8 +34,9 @@
|
||||
"oxlint": "^0.16.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"twenty-client-sdk": "^2.16.0",
|
||||
"twenty-sdk": "^2.16.0",
|
||||
"twenty-client-sdk": "^2.18.0",
|
||||
"twenty-sdk": "^2.18.0",
|
||||
"twenty-ui": "^1.0.0-alpha.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"vitest": "^4.1.9"
|
||||
|
||||
+2
@@ -1,5 +1,7 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
|
||||
import 'twenty-ui/style.css';
|
||||
|
||||
import { CALENDAR_EVENT_RECORDING_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/constants/calendar-event-recording-front-component-universal-identifier';
|
||||
import { CalendarEventRecording } from 'src/front-components/components/CalendarEventRecording';
|
||||
|
||||
|
||||
+5
-5
@@ -1,20 +1,20 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { CalendarEventRecordingContent } from 'src/front-components/components/CalendarEventRecordingContent';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
|
||||
const StyledCenteredState = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
color: ${recordingThemeCssVariables.font.colorTertiary};
|
||||
color: ${() => themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
font-family: ${recordingThemeCssVariables.font.family};
|
||||
font-size: ${recordingThemeCssVariables.font.sizeSm};
|
||||
font-family: ${() => themeCssVariables.font.family};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
padding: ${recordingThemeCssVariables.spacing[4]};
|
||||
padding: ${() => themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
export const CalendarEventRecording = () => {
|
||||
|
||||
+11
-14
@@ -1,29 +1,30 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { RecordingSkeletonLoader } from 'src/front-components/components/RecordingSkeletonLoader';
|
||||
import { RecordingTranscript } from 'src/front-components/components/RecordingTranscript';
|
||||
import { RecordingVideoPlayer } from 'src/front-components/components/RecordingVideoPlayer';
|
||||
import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type';
|
||||
|
||||
const StyledCenteredState = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
color: ${recordingThemeCssVariables.font.colorTertiary};
|
||||
color: ${() => themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
font-family: ${recordingThemeCssVariables.font.family};
|
||||
font-size: ${recordingThemeCssVariables.font.sizeSm};
|
||||
font-family: ${() => themeCssVariables.font.family};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
justify-content: center;
|
||||
min-height: 240px;
|
||||
padding: ${recordingThemeCssVariables.spacing[4]};
|
||||
padding: ${() => themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
const StyledRecordingContainer = styled.div<{
|
||||
$hasVideo?: boolean;
|
||||
}>`
|
||||
display: grid;
|
||||
gap: ${recordingThemeCssVariables.spacing[2]};
|
||||
gap: ${() => themeCssVariables.spacing[2]};
|
||||
grid-template-rows: ${({ $hasVideo }) =>
|
||||
$hasVideo ? 'auto minmax(0, 1fr)' : 'minmax(0, 1fr)'};
|
||||
min-height: 0;
|
||||
@@ -37,6 +38,7 @@ type CalendarEventRecordingBodyProps = {
|
||||
currentTimeSeconds: number;
|
||||
calendarEventParticipants: CalendarEventRecordingParticipant[];
|
||||
onVideoTimeUpdate: (videoCurrentTimeSeconds: number) => void;
|
||||
onVideoRetry: () => void;
|
||||
};
|
||||
|
||||
export const CalendarEventRecordingBody = ({
|
||||
@@ -47,6 +49,7 @@ export const CalendarEventRecordingBody = ({
|
||||
currentTimeSeconds,
|
||||
calendarEventParticipants,
|
||||
onVideoTimeUpdate,
|
||||
onVideoRetry,
|
||||
}: CalendarEventRecordingBodyProps) => {
|
||||
const hasVideo = !isUndefined(videoFileUrl);
|
||||
|
||||
@@ -60,14 +63,7 @@ export const CalendarEventRecordingBody = ({
|
||||
}
|
||||
|
||||
if (isCalendarEventRecordingQueryLoading) {
|
||||
return (
|
||||
<StyledRecordingContainer $hasVideo={false}>
|
||||
<RecordingVideoPlayer
|
||||
src={undefined}
|
||||
onTimeUpdate={onVideoTimeUpdate}
|
||||
/>
|
||||
</StyledRecordingContainer>
|
||||
);
|
||||
return <RecordingSkeletonLoader />;
|
||||
}
|
||||
|
||||
if (isUndefined(transcript) && !hasVideo) {
|
||||
@@ -84,6 +80,7 @@ export const CalendarEventRecordingBody = ({
|
||||
<RecordingVideoPlayer
|
||||
src={videoFileUrl}
|
||||
onTimeUpdate={onVideoTimeUpdate}
|
||||
onRetry={onVideoRetry}
|
||||
/>
|
||||
)}
|
||||
<RecordingTranscript
|
||||
|
||||
+35
-30
@@ -1,21 +1,21 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { CalendarEventRecordingBody } from 'src/front-components/components/CalendarEventRecordingBody';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { useCalendarEventParticipants } from 'src/front-components/hooks/use-calendar-event-participants';
|
||||
import { useCalendarEventRecording } from 'src/front-components/hooks/use-calendar-event-recording';
|
||||
|
||||
const TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS = 0.25;
|
||||
|
||||
const StyledRecordingShell = styled.div`
|
||||
background: ${recordingThemeCssVariables.background.primary};
|
||||
background: ${() => themeCssVariables.background.primary};
|
||||
border: 1px solid transparent;
|
||||
border-bottom: 1px solid transparent;
|
||||
border-radius: ${recordingThemeCssVariables.border.radiusMd};
|
||||
border-radius: ${() => themeCssVariables.border.radius.md};
|
||||
box-sizing: border-box;
|
||||
font-family: ${recordingThemeCssVariables.font.family};
|
||||
padding: ${recordingThemeCssVariables.spacing[4]};
|
||||
font-family: ${() => themeCssVariables.font.family};
|
||||
padding: ${() => themeCssVariables.spacing[4]};
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
@@ -24,31 +24,31 @@ const StyledRecordingHeader = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: ${recordingThemeCssVariables.spacing[6]};
|
||||
height: ${() => themeCssVariables.spacing[6]};
|
||||
`;
|
||||
|
||||
const StyledRecordingTitle = styled.h2`
|
||||
color: ${recordingThemeCssVariables.font.colorPrimary};
|
||||
color: ${() => themeCssVariables.font.color.primary};
|
||||
flex: 1;
|
||||
font-size: ${recordingThemeCssVariables.font.sizeMd};
|
||||
font-weight: ${recordingThemeCssVariables.font.weightMedium};
|
||||
font-size: ${() => themeCssVariables.font.size.md};
|
||||
font-weight: ${() => themeCssVariables.font.weight.medium};
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding-inline: ${recordingThemeCssVariables.spacing[1]};
|
||||
padding-inline: ${() => themeCssVariables.spacing[1]};
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
const StyledRecordingBody = styled.div`
|
||||
box-sizing: border-box;
|
||||
margin-top: ${recordingThemeCssVariables.spacing[2]};
|
||||
margin-top: ${() => themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledRecordingContentFrame = styled.div`
|
||||
background-color: ${recordingThemeCssVariables.background.secondary};
|
||||
border: 1px solid ${recordingThemeCssVariables.border.colorMedium};
|
||||
border-radius: ${recordingThemeCssVariables.border.radiusMd};
|
||||
background-color: ${() => themeCssVariables.background.secondary};
|
||||
border: 1px solid ${() => themeCssVariables.border.color.medium};
|
||||
border-radius: ${() => themeCssVariables.border.radius.md};
|
||||
box-sizing: border-box;
|
||||
padding: ${recordingThemeCssVariables.spacing[2]};
|
||||
padding: ${() => themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
type CalendarEventRecordingContentProps = {
|
||||
@@ -59,31 +59,35 @@ export const CalendarEventRecordingContent = ({
|
||||
calendarEventId,
|
||||
}: CalendarEventRecordingContentProps) => {
|
||||
const [currentTimeSeconds, setCurrentTimeSeconds] = useState(0);
|
||||
const updateCurrentTimeSeconds = useCallback(
|
||||
(videoCurrentTimeSeconds: number) => {
|
||||
const nextCurrentTimeSeconds =
|
||||
Math.floor(
|
||||
videoCurrentTimeSeconds / TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS,
|
||||
) * TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS;
|
||||
|
||||
setCurrentTimeSeconds((previousCurrentTimeSeconds) =>
|
||||
previousCurrentTimeSeconds === nextCurrentTimeSeconds
|
||||
? previousCurrentTimeSeconds
|
||||
: nextCurrentTimeSeconds,
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
const updateCurrentTimeSeconds = (videoCurrentTimeSeconds: number) => {
|
||||
const nextCurrentTimeSeconds =
|
||||
Math.floor(
|
||||
videoCurrentTimeSeconds / TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS,
|
||||
) * TRANSCRIPT_TIME_UPDATE_INTERVAL_SECONDS;
|
||||
|
||||
setCurrentTimeSeconds((previousCurrentTimeSeconds) =>
|
||||
previousCurrentTimeSeconds === nextCurrentTimeSeconds
|
||||
? previousCurrentTimeSeconds
|
||||
: nextCurrentTimeSeconds,
|
||||
);
|
||||
};
|
||||
|
||||
const {
|
||||
transcript,
|
||||
videoFile,
|
||||
isCalendarEventRecordingQueryLoading,
|
||||
errorMessage,
|
||||
refetchCalendarEventRecording,
|
||||
} = useCalendarEventRecording(calendarEventId);
|
||||
const { calendarEventParticipants } =
|
||||
useCalendarEventParticipants(calendarEventId);
|
||||
|
||||
const handleVideoRetry = () => {
|
||||
setCurrentTimeSeconds(0);
|
||||
refetchCalendarEventRecording();
|
||||
};
|
||||
|
||||
const videoFileUrl = videoFile?.url ?? undefined;
|
||||
|
||||
return (
|
||||
@@ -103,6 +107,7 @@ export const CalendarEventRecordingContent = ({
|
||||
currentTimeSeconds={currentTimeSeconds}
|
||||
calendarEventParticipants={calendarEventParticipants}
|
||||
onVideoTimeUpdate={updateCurrentTimeSeconds}
|
||||
onVideoRetry={handleVideoRetry}
|
||||
/>
|
||||
</StyledRecordingContentFrame>
|
||||
</StyledRecordingBody>
|
||||
|
||||
+5
-5
@@ -1,20 +1,20 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { CalendarEventSummaryContent } from 'src/front-components/components/CalendarEventSummaryContent';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
|
||||
const StyledCenteredState = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
color: ${recordingThemeCssVariables.font.colorTertiary};
|
||||
color: ${() => themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
font-family: ${recordingThemeCssVariables.font.family};
|
||||
font-size: ${recordingThemeCssVariables.font.sizeSm};
|
||||
font-family: ${() => themeCssVariables.font.family};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
padding: ${recordingThemeCssVariables.spacing[4]};
|
||||
padding: ${() => themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
export const CalendarEventSummary = () => {
|
||||
|
||||
+5
-5
@@ -1,20 +1,20 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { SummaryMarkdown } from 'src/front-components/components/SummaryMarkdown';
|
||||
import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
|
||||
const StyledCenteredState = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
color: ${recordingThemeCssVariables.font.colorTertiary};
|
||||
color: ${() => themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
font-family: ${recordingThemeCssVariables.font.family};
|
||||
font-size: ${recordingThemeCssVariables.font.sizeSm};
|
||||
font-family: ${() => themeCssVariables.font.family};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
justify-content: center;
|
||||
min-height: 240px;
|
||||
padding: ${recordingThemeCssVariables.spacing[4]};
|
||||
padding: ${() => themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
type CalendarEventSummaryBodyProps = {
|
||||
|
||||
+15
-15
@@ -1,16 +1,16 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { CalendarEventSummaryBody } from 'src/front-components/components/CalendarEventSummaryBody';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { useCalendarEventSummary } from 'src/front-components/hooks/use-calendar-event-summary';
|
||||
|
||||
const StyledSummaryShell = styled.div`
|
||||
background: ${recordingThemeCssVariables.background.primary};
|
||||
background: ${() => themeCssVariables.background.primary};
|
||||
border: 1px solid transparent;
|
||||
border-radius: ${recordingThemeCssVariables.border.radiusMd};
|
||||
border-radius: ${() => themeCssVariables.border.radius.md};
|
||||
box-sizing: border-box;
|
||||
font-family: ${recordingThemeCssVariables.font.family};
|
||||
padding: ${recordingThemeCssVariables.spacing[4]};
|
||||
font-family: ${() => themeCssVariables.font.family};
|
||||
padding: ${() => themeCssVariables.spacing[4]};
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
@@ -19,31 +19,31 @@ const StyledSummaryHeader = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: ${recordingThemeCssVariables.spacing[6]};
|
||||
height: ${() => themeCssVariables.spacing[6]};
|
||||
`;
|
||||
|
||||
const StyledSummaryTitle = styled.h2`
|
||||
color: ${recordingThemeCssVariables.font.colorPrimary};
|
||||
color: ${() => themeCssVariables.font.color.primary};
|
||||
flex: 1;
|
||||
font-size: ${recordingThemeCssVariables.font.sizeMd};
|
||||
font-weight: ${recordingThemeCssVariables.font.weightMedium};
|
||||
font-size: ${() => themeCssVariables.font.size.md};
|
||||
font-weight: ${() => themeCssVariables.font.weight.medium};
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding-inline: ${recordingThemeCssVariables.spacing[1]};
|
||||
padding-inline: ${() => themeCssVariables.spacing[1]};
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
const StyledSummaryBody = styled.div`
|
||||
box-sizing: border-box;
|
||||
margin-top: ${recordingThemeCssVariables.spacing[2]};
|
||||
margin-top: ${() => themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledSummaryContentFrame = styled.div`
|
||||
background-color: ${recordingThemeCssVariables.background.secondary};
|
||||
border: 1px solid ${recordingThemeCssVariables.border.colorMedium};
|
||||
border-radius: ${recordingThemeCssVariables.border.radiusMd};
|
||||
background-color: ${() => themeCssVariables.background.secondary};
|
||||
border: 1px solid ${() => themeCssVariables.border.color.medium};
|
||||
border-radius: ${() => themeCssVariables.border.radius.md};
|
||||
box-sizing: border-box;
|
||||
padding: ${recordingThemeCssVariables.spacing[3]};
|
||||
padding: ${() => themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
type CalendarEventSummaryContentProps = {
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
import { keyframes } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const SKELETON_ENTRY_COUNT = 3;
|
||||
const SKELETON_BAR_HEIGHT = '16px';
|
||||
const SKELETON_BORDER_RADIUS = '4px';
|
||||
|
||||
const skeletonHighlightSweep = keyframes`
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSkeletonBar = styled.div`
|
||||
background-color: ${() => themeCssVariables.background.tertiary};
|
||||
border-radius: ${SKELETON_BORDER_RADIUS};
|
||||
height: ${SKELETON_BAR_HEIGHT};
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
animation: ${skeletonHighlightSweep} 1.5s ease-in-out infinite;
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
${() => themeCssVariables.background.transparent.lighter},
|
||||
transparent
|
||||
);
|
||||
content: '';
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSkeletonAvatar = styled(StyledSkeletonBar)`
|
||||
border-radius: 50px;
|
||||
flex-shrink: 0;
|
||||
width: ${SKELETON_BAR_HEIGHT};
|
||||
`;
|
||||
|
||||
const StyledSkeletonSpeakerBar = styled(StyledSkeletonBar)`
|
||||
width: 96px;
|
||||
`;
|
||||
|
||||
const StyledSkeletonTextBar = styled(StyledSkeletonBar)<{ $width: string }>`
|
||||
width: ${({ $width }) => $width};
|
||||
`;
|
||||
|
||||
const StyledSkeletonEntry = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${() => themeCssVariables.spacing[2]};
|
||||
padding: ${() => themeCssVariables.spacing[2]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSkeletonEntryHeader = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${() => themeCssVariables.spacing[2]};
|
||||
min-height: ${() => themeCssVariables.spacing[6]};
|
||||
`;
|
||||
|
||||
const StyledSkeletonContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${() => themeCssVariables.spacing[2]};
|
||||
min-height: 240px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const RecordingSkeletonLoader = () => {
|
||||
const skeletonEntries = Array.from(
|
||||
{ length: SKELETON_ENTRY_COUNT },
|
||||
(_, skeletonEntryIndex) => skeletonEntryIndex,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledSkeletonContainer aria-hidden="true">
|
||||
{skeletonEntries.map((skeletonEntryIndex) => (
|
||||
<StyledSkeletonEntry key={skeletonEntryIndex}>
|
||||
<StyledSkeletonEntryHeader>
|
||||
<StyledSkeletonAvatar />
|
||||
<StyledSkeletonSpeakerBar />
|
||||
</StyledSkeletonEntryHeader>
|
||||
<StyledSkeletonTextBar $width="100%" />
|
||||
<StyledSkeletonTextBar
|
||||
$width={skeletonEntryIndex % 2 === 0 ? '75%' : '55%'}
|
||||
/>
|
||||
</StyledSkeletonEntry>
|
||||
))}
|
||||
</StyledSkeletonContainer>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { useMemo } from 'react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { TranscriptEntryList } from 'src/front-components/components/TranscriptEntryList';
|
||||
import { TranscriptErrorBox } from 'src/front-components/components/TranscriptErrorBox';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type';
|
||||
import { parseTranscriptEntries } from 'src/front-components/utils/parse-transcript-entries.util';
|
||||
import { parseTranscriptMarker } from 'src/logic-functions/domain/parse-transcript-marker.util';
|
||||
@@ -12,10 +12,10 @@ import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.
|
||||
|
||||
const StyledTranscriptCenteredState = styled.div`
|
||||
align-items: center;
|
||||
color: ${recordingThemeCssVariables.font.colorTertiary};
|
||||
color: ${() => themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
flex: 1;
|
||||
font-size: ${recordingThemeCssVariables.font.sizeSm};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
|
||||
+131
-13
@@ -1,52 +1,170 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { memo, type SyntheticEvent } from 'react';
|
||||
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { useState, type SyntheticEvent } from 'react';
|
||||
import { CircularProgressBar } from 'twenty-ui/feedback';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const DEFAULT_VIDEO_ASPECT_RATIO = '16 / 9';
|
||||
|
||||
// Forces the browser to decode the first frame under preload="metadata"
|
||||
// (Safari otherwise paints nothing until play). Fragments stay client-side,
|
||||
// so the signed file URL is unaffected.
|
||||
const FIRST_FRAME_SEEK_FRAGMENT = '#t=0.001';
|
||||
|
||||
type RecordingVideoLoadState = 'awaiting-first-frame' | 'ready' | 'errored';
|
||||
|
||||
const StyledVideoViewport = styled.div`
|
||||
aspect-ratio: ${DEFAULT_VIDEO_ASPECT_RATIO};
|
||||
background: ${recordingThemeCssVariables.background.primary};
|
||||
border-radius: ${recordingThemeCssVariables.border.radiusSm};
|
||||
background: ${() => themeCssVariables.background.primary};
|
||||
border-radius: ${() => themeCssVariables.border.radius.sm};
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledVideo = styled.video`
|
||||
accent-color: ${recordingThemeCssVariables.accent.primary};
|
||||
background: ${recordingThemeCssVariables.background.primary};
|
||||
const StyledVideo = styled.video<{ $isFirstFrameReady: boolean }>`
|
||||
accent-color: ${() => themeCssVariables.accent.accent9};
|
||||
background: ${() => themeCssVariables.background.primary};
|
||||
color-scheme: light dark;
|
||||
display: block;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
opacity: ${({ $isFirstFrameReady }) => ($isFirstFrameReady ? 1 : 0)};
|
||||
transition: opacity 200ms ease;
|
||||
width: 100%;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
transition: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledBufferingOverlay = styled.div<{ $isVisible: boolean }>`
|
||||
align-items: center;
|
||||
color: ${() => themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
inset: 0;
|
||||
justify-content: center;
|
||||
opacity: ${({ $isVisible }) => ($isVisible ? 1 : 0)};
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
transition: opacity 150ms ease
|
||||
${({ $isVisible }) => ($isVisible ? '300ms' : '0ms')};
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
transition-duration: 0ms;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPlaybackErrorState = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${() => themeCssVariables.spacing[2]};
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
padding: ${() => themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
const StyledPlaybackErrorTitle = styled.span`
|
||||
color: ${() => themeCssVariables.font.color.primary};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
font-weight: ${() => themeCssVariables.font.weight.medium};
|
||||
`;
|
||||
|
||||
const StyledPlaybackErrorDescription = styled.span`
|
||||
color: ${() => themeCssVariables.font.color.secondary};
|
||||
font-size: ${() => themeCssVariables.font.size.xs};
|
||||
`;
|
||||
|
||||
const StyledRetryButton = styled.button`
|
||||
background: ${() => themeCssVariables.background.secondary};
|
||||
border: 1px solid ${() => themeCssVariables.border.color.medium};
|
||||
border-radius: ${() => themeCssVariables.border.radius.sm};
|
||||
color: ${() => themeCssVariables.font.color.secondary};
|
||||
cursor: pointer;
|
||||
font-family: ${() => themeCssVariables.font.family};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
font-weight: ${() => themeCssVariables.font.weight.medium};
|
||||
margin-top: ${() => themeCssVariables.spacing[1]};
|
||||
padding: ${() => themeCssVariables.spacing[1]}
|
||||
${() => themeCssVariables.spacing[3]};
|
||||
|
||||
&:hover {
|
||||
background: ${() => themeCssVariables.background.tertiary};
|
||||
}
|
||||
`;
|
||||
|
||||
type RecordingVideoPlayerProps = {
|
||||
src: string | undefined;
|
||||
src: string;
|
||||
onTimeUpdate: (currentTimeSeconds: number) => void;
|
||||
onRetry: () => void;
|
||||
};
|
||||
|
||||
const RecordingVideoPlayerComponent = ({
|
||||
export const RecordingVideoPlayer = ({
|
||||
src,
|
||||
onTimeUpdate,
|
||||
onRetry,
|
||||
}: RecordingVideoPlayerProps) => {
|
||||
const [loadState, setLoadState] = useState<RecordingVideoLoadState>(
|
||||
'awaiting-first-frame',
|
||||
);
|
||||
const [isPlaybackStalled, setIsPlaybackStalled] = useState(false);
|
||||
|
||||
const markFirstFrameReady = () => {
|
||||
setLoadState((previousLoadState) =>
|
||||
previousLoadState === 'awaiting-first-frame'
|
||||
? 'ready'
|
||||
: previousLoadState,
|
||||
);
|
||||
setIsPlaybackStalled(false);
|
||||
};
|
||||
|
||||
const handleTimeUpdate = (event: SyntheticEvent<HTMLVideoElement>) => {
|
||||
markFirstFrameReady();
|
||||
onTimeUpdate(event.currentTarget.currentTime);
|
||||
};
|
||||
|
||||
const handleWaiting = () => setIsPlaybackStalled(true);
|
||||
|
||||
const handlePause = () => setIsPlaybackStalled(false);
|
||||
|
||||
const handleError = () => setLoadState('errored');
|
||||
|
||||
if (loadState === 'errored') {
|
||||
return (
|
||||
<StyledVideoViewport>
|
||||
<StyledPlaybackErrorState>
|
||||
<StyledPlaybackErrorTitle>Playback failed</StyledPlaybackErrorTitle>
|
||||
<StyledPlaybackErrorDescription>
|
||||
The recording could not be loaded.
|
||||
</StyledPlaybackErrorDescription>
|
||||
<StyledRetryButton onClick={onRetry}>Retry</StyledRetryButton>
|
||||
</StyledPlaybackErrorState>
|
||||
</StyledVideoViewport>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledVideoViewport>
|
||||
<StyledVideo
|
||||
$isFirstFrameReady={loadState === 'ready'}
|
||||
controls
|
||||
playsInline
|
||||
preload="metadata"
|
||||
src={src}
|
||||
src={`${src}${FIRST_FRAME_SEEK_FRAGMENT}`}
|
||||
onCanPlay={markFirstFrameReady}
|
||||
onError={handleError}
|
||||
onLoadedData={markFirstFrameReady}
|
||||
onPause={handlePause}
|
||||
onSeeked={markFirstFrameReady}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onWaiting={handleWaiting}
|
||||
/>
|
||||
<StyledBufferingOverlay
|
||||
$isVisible={loadState === 'awaiting-first-frame' || isPlaybackStalled}
|
||||
>
|
||||
<CircularProgressBar barWidth={3} size={24} />
|
||||
</StyledBufferingOverlay>
|
||||
</StyledVideoViewport>
|
||||
);
|
||||
};
|
||||
|
||||
export const RecordingVideoPlayer = memo(RecordingVideoPlayerComponent);
|
||||
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Fragment } from 'react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { type SummaryInlineSegment } from 'src/front-components/types/summary-inline-segment.type';
|
||||
|
||||
const StyledBold = styled.strong`
|
||||
color: ${recordingThemeCssVariables.font.colorPrimary};
|
||||
font-weight: ${recordingThemeCssVariables.font.weightMedium};
|
||||
color: ${() => themeCssVariables.font.color.primary};
|
||||
font-weight: ${() => themeCssVariables.font.weight.medium};
|
||||
`;
|
||||
|
||||
type SummaryInlineSegmentsProps = {
|
||||
|
||||
+12
-12
@@ -1,31 +1,31 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useMemo } from 'react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { SummaryInlineSegments } from 'src/front-components/components/SummaryInlineSegments';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { parseSummaryMarkdownBlocks } from 'src/front-components/utils/parse-summary-markdown-blocks.util';
|
||||
|
||||
const TOP_LEVEL_HEADING_MAX = 2;
|
||||
|
||||
const StyledSummary = styled.div`
|
||||
color: ${recordingThemeCssVariables.font.colorSecondary};
|
||||
color: ${() => themeCssVariables.font.color.secondary};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: ${recordingThemeCssVariables.font.family};
|
||||
font-size: ${recordingThemeCssVariables.font.sizeSm};
|
||||
gap: ${recordingThemeCssVariables.spacing[2]};
|
||||
font-family: ${() => themeCssVariables.font.family};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
gap: ${() => themeCssVariables.spacing[2]};
|
||||
line-height: 1.5;
|
||||
`;
|
||||
|
||||
const StyledHeading = styled.h3<{ $isTopLevel: boolean }>`
|
||||
color: ${recordingThemeCssVariables.font.colorPrimary};
|
||||
color: ${() => themeCssVariables.font.color.primary};
|
||||
font-size: ${({ $isTopLevel }) =>
|
||||
$isTopLevel
|
||||
? recordingThemeCssVariables.font.sizeMd
|
||||
: recordingThemeCssVariables.font.sizeSm};
|
||||
font-weight: ${recordingThemeCssVariables.font.weightMedium};
|
||||
? themeCssVariables.font.size.md
|
||||
: themeCssVariables.font.size.sm};
|
||||
font-weight: ${() => themeCssVariables.font.weight.medium};
|
||||
margin: 0;
|
||||
margin-top: ${recordingThemeCssVariables.spacing[1]};
|
||||
margin-top: ${() => themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledParagraph = styled.p`
|
||||
@@ -35,9 +35,9 @@ const StyledParagraph = styled.p`
|
||||
const StyledList = styled.ul`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${recordingThemeCssVariables.spacing[1]};
|
||||
gap: ${() => themeCssVariables.spacing[1]};
|
||||
margin: 0;
|
||||
padding-left: ${recordingThemeCssVariables.spacing[3]};
|
||||
padding-left: ${() => themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
type SummaryMarkdownProps = {
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useMemo } from 'react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { TranscriptEntryListItem } from 'src/front-components/components/TranscriptEntryListItem';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type';
|
||||
import { type TranscriptEntry } from 'src/front-components/types/transcript-entry.type';
|
||||
import { buildCalendarEventParticipantBySpeakerName } from 'src/front-components/utils/build-calendar-event-participant-by-speaker-name.util';
|
||||
@@ -13,7 +13,7 @@ const StyledTranscriptContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: ${recordingThemeCssVariables.spacing[2]};
|
||||
gap: ${() => themeCssVariables.spacing[2]};
|
||||
min-height: 0;
|
||||
`;
|
||||
|
||||
|
||||
+30
-19
@@ -1,8 +1,8 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { Avatar, Chip, ChipVariant } from 'twenty-ui/data-display';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { TranscriptSpeakerChip } from 'src/front-components/components/TranscriptSpeakerChip';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { type CalendarEventRecordingParticipant } from 'src/front-components/types/calendar-event-recording-participant.type';
|
||||
import {
|
||||
type TranscriptEntry,
|
||||
@@ -14,15 +14,15 @@ const StyledEntry = styled.div<{ $isActive: boolean }>`
|
||||
align-items: flex-start;
|
||||
background: ${({ $isActive }) =>
|
||||
$isActive
|
||||
? recordingThemeCssVariables.background.transparentBlue
|
||||
? themeCssVariables.background.transparent.blue
|
||||
: 'transparent'};
|
||||
border-radius: ${recordingThemeCssVariables.border.radiusSm};
|
||||
border-radius: ${() => themeCssVariables.border.radius.sm};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${recordingThemeCssVariables.spacing[2]};
|
||||
gap: ${() => themeCssVariables.spacing[2]};
|
||||
justify-content: center;
|
||||
padding: ${recordingThemeCssVariables.spacing[2]};
|
||||
padding: ${() => themeCssVariables.spacing[2]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@@ -30,21 +30,21 @@ const StyledEntryHeader = styled.div`
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
gap: ${recordingThemeCssVariables.spacing[2]};
|
||||
min-height: ${recordingThemeCssVariables.spacing[6]};
|
||||
gap: ${() => themeCssVariables.spacing[2]};
|
||||
min-height: ${() => themeCssVariables.spacing[6]};
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
const StyledTimestamp = styled.span`
|
||||
color: ${recordingThemeCssVariables.font.colorTertiary};
|
||||
font-size: ${recordingThemeCssVariables.font.sizeXs};
|
||||
color: ${() => themeCssVariables.font.color.tertiary};
|
||||
font-size: ${() => themeCssVariables.font.size.xs};
|
||||
line-height: 1.4;
|
||||
`;
|
||||
|
||||
const StyledEntryText = styled.p`
|
||||
align-self: stretch;
|
||||
color: ${recordingThemeCssVariables.font.colorSecondary};
|
||||
font-size: ${recordingThemeCssVariables.font.sizeSm};
|
||||
color: ${() => themeCssVariables.font.color.secondary};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
`;
|
||||
@@ -52,8 +52,8 @@ const StyledEntryText = styled.p`
|
||||
const StyledWord = styled.span<{ $isSpoken: boolean }>`
|
||||
color: ${({ $isSpoken }) =>
|
||||
$isSpoken
|
||||
? recordingThemeCssVariables.font.colorPrimary
|
||||
: recordingThemeCssVariables.font.colorSecondary};
|
||||
? themeCssVariables.font.color.primary
|
||||
: themeCssVariables.font.color.secondary};
|
||||
line-height: 1.4;
|
||||
transition: color 0.15s ease;
|
||||
`;
|
||||
@@ -77,11 +77,22 @@ export const TranscriptEntryListItem = ({
|
||||
return (
|
||||
<StyledEntry $isActive={isActive}>
|
||||
<StyledEntryHeader>
|
||||
<TranscriptSpeakerChip
|
||||
speakerName={speakerDisplayName}
|
||||
avatarUrl={calendarEventParticipant?.avatarUrl}
|
||||
placeholderColorSeed={
|
||||
calendarEventParticipant?.placeholderColorSeed ?? speakerDisplayName
|
||||
<Chip
|
||||
clickable={false}
|
||||
isBold
|
||||
label={speakerDisplayName}
|
||||
variant={ChipVariant.Transparent}
|
||||
leftComponent={
|
||||
<Avatar
|
||||
avatarUrl={calendarEventParticipant?.avatarUrl}
|
||||
placeholder={speakerDisplayName}
|
||||
placeholderColorSeed={
|
||||
calendarEventParticipant?.placeholderColorSeed ??
|
||||
speakerDisplayName
|
||||
}
|
||||
size="md"
|
||||
type="rounded"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{!isUndefined(entry.startSeconds) && (
|
||||
|
||||
+13
-14
@@ -1,33 +1,32 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledStateContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
font-family: ${recordingThemeCssVariables.font.family};
|
||||
font-family: ${() => themeCssVariables.font.family};
|
||||
height: 100%;
|
||||
padding: ${recordingThemeCssVariables.spacing[4]};
|
||||
padding: ${() => themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
const StyledErrorBox = styled.div`
|
||||
background: ${recordingThemeCssVariables.background.transparentDanger};
|
||||
border: 1px solid ${recordingThemeCssVariables.border.colorDanger};
|
||||
border-radius: ${recordingThemeCssVariables.border.radiusMd};
|
||||
background: ${() => themeCssVariables.background.transparent.danger};
|
||||
border: 1px solid ${() => themeCssVariables.border.color.danger};
|
||||
border-radius: ${() => themeCssVariables.border.radius.md};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${recordingThemeCssVariables.spacing[1]};
|
||||
padding: ${recordingThemeCssVariables.spacing[3]};
|
||||
gap: ${() => themeCssVariables.spacing[1]};
|
||||
padding: ${() => themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
const StyledErrorTitle = styled.span`
|
||||
color: ${recordingThemeCssVariables.font.colorDanger};
|
||||
font-size: ${recordingThemeCssVariables.font.sizeSm};
|
||||
font-weight: ${recordingThemeCssVariables.font.weightMedium};
|
||||
color: ${() => themeCssVariables.font.color.danger};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
font-weight: ${() => themeCssVariables.font.weight.medium};
|
||||
`;
|
||||
|
||||
const StyledErrorDescription = styled.span`
|
||||
color: ${recordingThemeCssVariables.font.colorSecondary};
|
||||
font-size: ${recordingThemeCssVariables.font.sizeSm};
|
||||
color: ${() => themeCssVariables.font.color.secondary};
|
||||
font-size: ${() => themeCssVariables.font.size.sm};
|
||||
`;
|
||||
|
||||
type TranscriptErrorBoxProps = {
|
||||
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
// Duplicates minimal twenty-ui Avatar logic for this app.
|
||||
// Remove once twenty-ui can be imported safely in front components.
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
|
||||
|
||||
const AVATAR_COLOR_NAMES = [
|
||||
'red',
|
||||
'ruby',
|
||||
'crimson',
|
||||
'tomato',
|
||||
'orange',
|
||||
'amber',
|
||||
'yellow',
|
||||
'lime',
|
||||
'grass',
|
||||
'green',
|
||||
'jade',
|
||||
'mint',
|
||||
'turquoise',
|
||||
'cyan',
|
||||
'sky',
|
||||
'blue',
|
||||
'iris',
|
||||
'violet',
|
||||
'purple',
|
||||
'plum',
|
||||
'pink',
|
||||
'bronze',
|
||||
'gold',
|
||||
'brown',
|
||||
'gray',
|
||||
] as const;
|
||||
|
||||
const StyledAvatar = styled.div<{
|
||||
$backgroundColor: string;
|
||||
$color: string;
|
||||
}>`
|
||||
align-items: center;
|
||||
background: ${({ $backgroundColor }) => $backgroundColor};
|
||||
border-radius: 50px;
|
||||
box-sizing: border-box;
|
||||
color: ${({ $color }) => $color};
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
font-size: ${recordingThemeCssVariables.font.sizeXs};
|
||||
font-weight: ${recordingThemeCssVariables.font.weightMedium};
|
||||
height: 16px;
|
||||
justify-content: center;
|
||||
line-height: 15px;
|
||||
overflow: hidden;
|
||||
width: 16px;
|
||||
`;
|
||||
|
||||
const StyledAvatarImage = styled.img`
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type TranscriptSpeakerAvatarProps = {
|
||||
speakerName: string;
|
||||
avatarUrl: string | undefined;
|
||||
placeholderColorSeed: string;
|
||||
};
|
||||
|
||||
const getSpeakerInitial = (speakerName: string) =>
|
||||
speakerName.trim().charAt(0).toUpperCase() || '-';
|
||||
|
||||
export const TranscriptSpeakerAvatar = ({
|
||||
speakerName,
|
||||
avatarUrl,
|
||||
placeholderColorSeed,
|
||||
}: TranscriptSpeakerAvatarProps) => {
|
||||
const [erroredAvatarUrl, setErroredAvatarUrl] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const shouldShowAvatarImage =
|
||||
isNonEmptyString(avatarUrl) && erroredAvatarUrl !== avatarUrl;
|
||||
|
||||
const handleAvatarImageError = () => {
|
||||
if (isNonEmptyString(avatarUrl)) {
|
||||
setErroredAvatarUrl(avatarUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const avatarPlaceholderColor = getAvatarPlaceholderColor({
|
||||
placeholderColorSeed,
|
||||
variant: 12,
|
||||
});
|
||||
const avatarPlaceholderBackgroundColor = getAvatarPlaceholderColor({
|
||||
placeholderColorSeed,
|
||||
variant: 4,
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledAvatar
|
||||
aria-hidden="true"
|
||||
$backgroundColor={avatarPlaceholderBackgroundColor}
|
||||
$color={avatarPlaceholderColor}
|
||||
>
|
||||
{shouldShowAvatarImage ? (
|
||||
<StyledAvatarImage
|
||||
src={avatarUrl}
|
||||
alt=""
|
||||
onError={handleAvatarImageError}
|
||||
/>
|
||||
) : (
|
||||
getSpeakerInitial(speakerName)
|
||||
)}
|
||||
</StyledAvatar>
|
||||
);
|
||||
};
|
||||
|
||||
const getAvatarPlaceholderColor = ({
|
||||
placeholderColorSeed,
|
||||
variant,
|
||||
}: {
|
||||
placeholderColorSeed: string;
|
||||
variant: 4 | 12;
|
||||
}): string => {
|
||||
const avatarColorName =
|
||||
AVATAR_COLOR_NAMES[
|
||||
Math.abs(hashString(placeholderColorSeed)) % AVATAR_COLOR_NAMES.length
|
||||
];
|
||||
|
||||
return `var(--t-color-${avatarColorName}${variant})`;
|
||||
};
|
||||
|
||||
const hashString = (value: string): number => {
|
||||
let hash = 0;
|
||||
|
||||
for (let valueIndex = 0; valueIndex < value.length; valueIndex++) {
|
||||
hash = value.charCodeAt(valueIndex) + ((hash << 5) - hash);
|
||||
}
|
||||
|
||||
return hash;
|
||||
};
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
// Duplicates minimal twenty-ui Chip logic for this app.
|
||||
// Remove once twenty-ui can be imported safely in front components.
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { TranscriptSpeakerAvatar } from 'src/front-components/components/TranscriptSpeakerAvatar';
|
||||
import { recordingThemeCssVariables } from 'src/front-components/constants/recording-theme-css-variables';
|
||||
|
||||
const StyledSpeakerChip = styled.span`
|
||||
align-items: center;
|
||||
border-radius: ${recordingThemeCssVariables.border.radiusSm};
|
||||
color: ${recordingThemeCssVariables.font.colorPrimary};
|
||||
display: inline-flex;
|
||||
font-size: ${recordingThemeCssVariables.font.sizeSm};
|
||||
font-weight: ${recordingThemeCssVariables.font.weightMedium};
|
||||
gap: ${recordingThemeCssVariables.spacing[1]};
|
||||
line-height: 1.4;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const StyledSpeakerName = styled.span`
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
type TranscriptSpeakerChipProps = {
|
||||
speakerName: string;
|
||||
avatarUrl: string | undefined;
|
||||
placeholderColorSeed: string;
|
||||
};
|
||||
|
||||
export const TranscriptSpeakerChip = ({
|
||||
speakerName,
|
||||
avatarUrl,
|
||||
placeholderColorSeed,
|
||||
}: TranscriptSpeakerChipProps) => {
|
||||
return (
|
||||
<StyledSpeakerChip>
|
||||
<TranscriptSpeakerAvatar
|
||||
speakerName={speakerName}
|
||||
avatarUrl={avatarUrl}
|
||||
placeholderColorSeed={placeholderColorSeed}
|
||||
/>
|
||||
<StyledSpeakerName>{speakerName}</StyledSpeakerName>
|
||||
</StyledSpeakerChip>
|
||||
);
|
||||
};
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
// Avoid the SDK UI entrypoint until its bundle is safe for the browser runtime.
|
||||
export const recordingThemeCssVariables = {
|
||||
accent: {
|
||||
primary: 'var(--t-accent-accent9)',
|
||||
},
|
||||
background: {
|
||||
primary: 'var(--t-background-primary)',
|
||||
secondary: 'var(--t-background-secondary)',
|
||||
transparentBlue: 'var(--t-background-transparent-blue)',
|
||||
transparentDanger: 'var(--t-background-transparent-danger)',
|
||||
},
|
||||
border: {
|
||||
colorDanger: 'var(--t-border-color-danger)',
|
||||
colorLight: 'var(--t-border-color-light)',
|
||||
colorMedium: 'var(--t-border-color-medium)',
|
||||
radiusMd: 'var(--t-border-radius-md)',
|
||||
radiusSm: 'var(--t-border-radius-sm)',
|
||||
},
|
||||
boxShadow: {
|
||||
light: 'var(--t-box-shadow-light)',
|
||||
},
|
||||
font: {
|
||||
colorDanger: 'var(--t-font-color-danger)',
|
||||
colorPrimary: 'var(--t-font-color-primary)',
|
||||
colorSecondary: 'var(--t-font-color-secondary)',
|
||||
colorTertiary: 'var(--t-font-color-tertiary)',
|
||||
family: 'var(--t-font-family)',
|
||||
sizeMd: 'var(--t-font-size-md)',
|
||||
sizeSm: 'var(--t-font-size-sm)',
|
||||
sizeXs: 'var(--t-font-size-xs)',
|
||||
weightMedium: 'var(--t-font-weight-medium)',
|
||||
},
|
||||
spacing: {
|
||||
1: 'var(--t-spacing-1)',
|
||||
2: 'var(--t-spacing-2)',
|
||||
3: 'var(--t-spacing-3)',
|
||||
4: 'var(--t-spacing-4)',
|
||||
6: 'var(--t-spacing-6)',
|
||||
},
|
||||
} as const;
|
||||
+12
-3
@@ -9,6 +9,10 @@ type CalendarEventRecordingState = {
|
||||
errorMessage: string | undefined;
|
||||
};
|
||||
|
||||
type CalendarEventRecordingResult = CalendarEventRecordingState & {
|
||||
refetchCalendarEventRecording: () => void;
|
||||
};
|
||||
|
||||
type CalendarEventRecordingVideoFile = {
|
||||
fileId: string;
|
||||
label: string | null;
|
||||
@@ -31,13 +35,18 @@ const CALENDAR_EVENT_RECORDING_ERROR_MESSAGE = 'Please try again later.';
|
||||
|
||||
export const useCalendarEventRecording = (
|
||||
calendarEventId: string | undefined,
|
||||
): CalendarEventRecordingState => {
|
||||
): CalendarEventRecordingResult => {
|
||||
const [state, setState] = useState<CalendarEventRecordingState>({
|
||||
transcript: undefined,
|
||||
videoFile: undefined,
|
||||
isCalendarEventRecordingQueryLoading: !isUndefined(calendarEventId),
|
||||
errorMessage: undefined,
|
||||
});
|
||||
const [refetchToken, setRefetchToken] = useState(0);
|
||||
|
||||
const refetchCalendarEventRecording = () => {
|
||||
setRefetchToken((previousRefetchToken) => previousRefetchToken + 1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isUndefined(calendarEventId)) {
|
||||
@@ -123,9 +132,9 @@ export const useCalendarEventRecording = (
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [calendarEventId]);
|
||||
}, [calendarEventId, refetchToken]);
|
||||
|
||||
return state;
|
||||
return { ...state, refetchCalendarEventRecording };
|
||||
};
|
||||
|
||||
const hasTranscript = (
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"composite": true,
|
||||
"types": ["vitest/globals", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"include": ["css.d.ts", "src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3":
|
||||
"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.29.2":
|
||||
version: 7.29.7
|
||||
resolution: "@babel/runtime@npm:7.29.7"
|
||||
checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e
|
||||
@@ -124,6 +124,51 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@base-ui/react@npm:^1.5.0":
|
||||
version: 1.6.0
|
||||
resolution: "@base-ui/react@npm:1.6.0"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.29.2"
|
||||
"@base-ui/utils": "npm:0.3.1"
|
||||
"@floating-ui/react-dom": "npm:^2.1.8"
|
||||
"@floating-ui/utils": "npm:^0.2.11"
|
||||
use-sync-external-store: "npm:^1.6.0"
|
||||
peerDependencies:
|
||||
"@date-fns/tz": ^1.2.0
|
||||
"@types/react": ^17 || ^18 || ^19
|
||||
date-fns: ^4.0.0
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
"@date-fns/tz":
|
||||
optional: true
|
||||
"@types/react":
|
||||
optional: true
|
||||
date-fns:
|
||||
optional: true
|
||||
checksum: 10c0/5e5994dc782710eac900d68cc0691aea70a47fabc6ef8d2ea943103d9aa96c4b44f9fd76ba65d29fa2667a5fbd7ea7a1cc02fe111c4bf639d4e3e65a1c281118
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@base-ui/utils@npm:0.3.1":
|
||||
version: 0.3.1
|
||||
resolution: "@base-ui/utils@npm:0.3.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.29.2"
|
||||
"@floating-ui/utils": "npm:^0.2.11"
|
||||
reselect: "npm:^5.2.0"
|
||||
use-sync-external-store: "npm:^1.6.0"
|
||||
peerDependencies:
|
||||
"@types/react": ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10c0/4de858c3e2c8a4486a549d51c5e887bba6921e5a7f8db54439ba558feaf1656c3592a873928aee8a1711ee9210be3a65ca43b26713e97f649695ed2fa8c3af2c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@emnapi/core@npm:1.10.0":
|
||||
version: 1.10.0
|
||||
resolution: "@emnapi/core@npm:1.10.0"
|
||||
@@ -489,6 +534,44 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/core@npm:^1.7.5":
|
||||
version: 1.7.5
|
||||
resolution: "@floating-ui/core@npm:1.7.5"
|
||||
dependencies:
|
||||
"@floating-ui/utils": "npm:^0.2.11"
|
||||
checksum: 10c0/f9c52205e198b231d63a387b09c659aab08c46a1899e0b0bbe147b8b4f048b546f15ba17cb5d2a471da9534f1883d979425e13e5c4ceee67be63e4b0abd4db5d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/dom@npm:^1.7.6":
|
||||
version: 1.7.6
|
||||
resolution: "@floating-ui/dom@npm:1.7.6"
|
||||
dependencies:
|
||||
"@floating-ui/core": "npm:^1.7.5"
|
||||
"@floating-ui/utils": "npm:^0.2.11"
|
||||
checksum: 10c0/5c098e0d7b58c9bc769f276cca1766994c2c9c70c92d091a61bba8b3e9be53c011e0a79a8457fc2fb2f3d91697a26eb52e0a4962ef936dc963b45f58613c212f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/react-dom@npm:^2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@floating-ui/react-dom@npm:2.1.8"
|
||||
dependencies:
|
||||
"@floating-ui/dom": "npm:^1.7.6"
|
||||
peerDependencies:
|
||||
react: ">=16.8.0"
|
||||
react-dom: ">=16.8.0"
|
||||
checksum: 10c0/26260ca4bb23b57c73b824062505abf977a008ce6e0463bdacca74f7e49853c4cd1d2bbf1a77c6caa17fa37dfffda2c6c4cd07a8737ebd7474aaff7818401d75
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/utils@npm:^0.2.11":
|
||||
version: 0.2.11
|
||||
resolution: "@floating-ui/utils@npm:0.2.11"
|
||||
checksum: 10c0/f4bcea1559bdbb721ecc8e8ead423ac58d6a5b6e70b602cf0810ba6ad4ed1c77211b207faa88b278a9042f0c743133de08a203ed6741c1b6443423332884d5b3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@genql/runtime@npm:^2.10.0":
|
||||
version: 2.10.0
|
||||
resolution: "@genql/runtime@npm:2.10.0"
|
||||
@@ -1020,6 +1103,28 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@monaco-editor/loader@npm:^1.5.0":
|
||||
version: 1.7.0
|
||||
resolution: "@monaco-editor/loader@npm:1.7.0"
|
||||
dependencies:
|
||||
state-local: "npm:^1.0.6"
|
||||
checksum: 10c0/1fd3579cb09b669493cf553dfc0723a93483140a44353ce9a739827edfa7b2c6541b254024d15c48176ece749ef666b6d16cce6cf0e4396c7fa1c5a48012d08a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@monaco-editor/react@npm:^4.7.0":
|
||||
version: 4.7.0
|
||||
resolution: "@monaco-editor/react@npm:4.7.0"
|
||||
dependencies:
|
||||
"@monaco-editor/loader": "npm:^1.5.0"
|
||||
peerDependencies:
|
||||
monaco-editor: ">= 0.25.0 < 1"
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
checksum: 10c0/a4e91c6eda1a71f5fd23871bd801de435490ccbc43934d23cba65cefe2be7e9d02c9a57c4ef80fc9fe99e4d4deea51e5db19cb4e0ecf3f51818dda0eb9cdbf12
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@napi-rs/wasm-runtime@npm:^1.1.4":
|
||||
version: 1.1.5
|
||||
resolution: "@napi-rs/wasm-runtime@npm:1.1.5"
|
||||
@@ -1095,6 +1200,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/colors@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@radix-ui/colors@npm:3.0.0"
|
||||
checksum: 10c0/612c6bb4efe987f2d49aaca2f785687d08150da59a248328bc2b4e187ece4ef731c1dc5c6a9430fffcac4af9e65cf2f89ceb9806f8809940c9eb238b79d2cf2f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@remix-run/router@npm:1.23.3":
|
||||
version: 1.23.3
|
||||
resolution: "@remix-run/router@npm:1.23.3"
|
||||
checksum: 10c0/73622465dd9e9a2c5a7ced7d1151ea6e9195a8a979c99b4f70a67093eeff7f339daf03a7c6304709a9c27deb2081a8fff6737663aacb33529c1a12a0a0827a17
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-android-arm64@npm:1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "@rolldown/binding-android-arm64@npm:1.0.3"
|
||||
@@ -1225,6 +1344,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tabler/icons-react@npm:^3.31.0":
|
||||
version: 3.44.0
|
||||
resolution: "@tabler/icons-react@npm:3.44.0"
|
||||
dependencies:
|
||||
"@tabler/icons": "npm:3.44.0"
|
||||
peerDependencies:
|
||||
react: ">= 16"
|
||||
checksum: 10c0/9a4bb6f1121d001049be8bb5757160ce4d5f2430671479ee94763f88ef43a69ab8d89b835d6c6451de22f3071793b779fefeed43db49d7993de1ea65c50145c9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tabler/icons@npm:3.44.0":
|
||||
version: 3.44.0
|
||||
resolution: "@tabler/icons@npm:3.44.0"
|
||||
checksum: 10c0/0d5c1f9d6e68aa04c4a661e96035190e0884e0bc0022d31922efad3cfba210a25b2f013c0b6c0ab6aeb3d5a402a9b85a168966bd11a30ba6087f614f8521ccf3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@twentyhq/call-recorder@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@twentyhq/call-recorder@workspace:."
|
||||
@@ -1239,8 +1376,9 @@ __metadata:
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
sharp: "npm:^0.34.5"
|
||||
twenty-client-sdk: "npm:^2.16.0"
|
||||
twenty-sdk: "npm:^2.16.0"
|
||||
twenty-client-sdk: "npm:^2.18.0"
|
||||
twenty-sdk: "npm:^2.18.0"
|
||||
twenty-ui: "npm:^1.0.0-alpha.1"
|
||||
typescript: "npm:^5.9.3"
|
||||
vite-tsconfig-paths: "npm:^4.2.1"
|
||||
vitest: "npm:^4.1.9"
|
||||
@@ -1691,6 +1829,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"clsx@npm:^2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "clsx@npm:2.1.1"
|
||||
checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"code-excerpt@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "code-excerpt@npm:4.0.0"
|
||||
@@ -1750,6 +1895,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-mediaquery@npm:^0.1.2":
|
||||
version: 0.1.2
|
||||
resolution: "css-mediaquery@npm:0.1.2"
|
||||
checksum: 10c0/b7825a78f52ce8a8198e004fcad0d7be1d3c9a0463ecd05ba31a0f2c94fb81468ad6f4d7bf715a6ca775696e7a17500c2a339b5216a6d0f789cbf78f9454d048
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"csstype@npm:^3.0.2, csstype@npm:^3.2.2":
|
||||
version: 3.2.3
|
||||
resolution: "csstype@npm:3.2.3"
|
||||
@@ -2239,6 +2391,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hyphenate-style-name@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "hyphenate-style-name@npm:1.1.0"
|
||||
checksum: 10c0/bfe88deac2414a41a0d08811e277c8c098f23993d6a1eb17f14a0f11b54c4d42865a63d3cfe1914668eefb9a188e2de58f38b55a179a238fd1fef606893e194f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:^0.7.2":
|
||||
version: 0.7.2
|
||||
resolution: "iconv-lite@npm:0.7.2"
|
||||
@@ -2384,7 +2543,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-tokens@npm:^4.0.0":
|
||||
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "js-tokens@npm:4.0.0"
|
||||
checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed
|
||||
@@ -2548,6 +2707,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"loose-envify@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "loose-envify@npm:1.4.0"
|
||||
dependencies:
|
||||
js-tokens: "npm:^3.0.0 || ^4.0.0"
|
||||
bin:
|
||||
loose-envify: cli.js
|
||||
checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"magic-string@npm:^0.30.21":
|
||||
version: 0.30.21
|
||||
resolution: "magic-string@npm:0.30.21"
|
||||
@@ -2557,6 +2727,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"matchmediaquery@npm:^0.3.0":
|
||||
version: 0.3.1
|
||||
resolution: "matchmediaquery@npm:0.3.1"
|
||||
dependencies:
|
||||
css-mediaquery: "npm:^0.1.2"
|
||||
checksum: 10c0/19a254e0a1eb639fcc6aa7428a6e2d36cb5646a165579897e771580653e470b43d7f4a224c522700dbf1d8f19fb7c63a9719e0a6017727ade967155a2ae89fea
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"math-intrinsics@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "math-intrinsics@npm:1.1.0"
|
||||
@@ -2671,6 +2850,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-assign@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "object-assign@npm:4.1.1"
|
||||
checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"obug@npm:^2.1.1":
|
||||
version: 2.1.3
|
||||
resolution: "obug@npm:2.1.3"
|
||||
@@ -2820,6 +3006,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prop-types@npm:^15.6.1":
|
||||
version: 15.8.1
|
||||
resolution: "prop-types@npm:15.8.1"
|
||||
dependencies:
|
||||
loose-envify: "npm:^1.4.0"
|
||||
object-assign: "npm:^4.1.1"
|
||||
react-is: "npm:^16.13.1"
|
||||
checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proxy-from-env@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "proxy-from-env@npm:2.1.0"
|
||||
@@ -2838,7 +3035,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-is@npm:^16.7.0":
|
||||
"react-is@npm:^16.13.1, react-is@npm:^16.7.0":
|
||||
version: 16.13.1
|
||||
resolution: "react-is@npm:16.13.1"
|
||||
checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1
|
||||
@@ -2856,6 +3053,44 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-responsive@npm:^9.0.2":
|
||||
version: 9.0.2
|
||||
resolution: "react-responsive@npm:9.0.2"
|
||||
dependencies:
|
||||
hyphenate-style-name: "npm:^1.0.0"
|
||||
matchmediaquery: "npm:^0.3.0"
|
||||
prop-types: "npm:^15.6.1"
|
||||
shallow-equal: "npm:^1.2.1"
|
||||
peerDependencies:
|
||||
react: ">=16.8.0"
|
||||
checksum: 10c0/c58b30d8b8ff993edbc73f7de59a2d86d8abd6380b06a9b41667c3bdd5401e5e710fa1d0fdac21368759ab9b958e8b479a111329a06e9b0241ee08f500b37a20
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-router-dom@npm:^6.4.4":
|
||||
version: 6.30.4
|
||||
resolution: "react-router-dom@npm:6.30.4"
|
||||
dependencies:
|
||||
"@remix-run/router": "npm:1.23.3"
|
||||
react-router: "npm:6.30.4"
|
||||
peerDependencies:
|
||||
react: ">=16.8"
|
||||
react-dom: ">=16.8"
|
||||
checksum: 10c0/1b25ab26a288da852f7b58eec5c14b3f37919e6773a557cd846d3a05d7a7d890c8d49bda93c0a7f497f240df1df8b0ad50635f990aafb55f2fc545ad8269a822
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-router@npm:6.30.4":
|
||||
version: 6.30.4
|
||||
resolution: "react-router@npm:6.30.4"
|
||||
dependencies:
|
||||
"@remix-run/router": "npm:1.23.3"
|
||||
peerDependencies:
|
||||
react: ">=16.8"
|
||||
checksum: 10c0/fb6de7d1002bcab9ea12c4072d93792eca494f1e4f30cfec334ccb6523756d23ce3e093c7c1241399296cebed94789a3fb89f96ee76004e0e746458a8f6bab33
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react@npm:^19.0.0, react@npm:^19.2.0":
|
||||
version: 19.2.7
|
||||
resolution: "react@npm:19.2.7"
|
||||
@@ -2870,6 +3105,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"reselect@npm:^5.2.0":
|
||||
version: 5.2.0
|
||||
resolution: "reselect@npm:5.2.0"
|
||||
checksum: 10c0/d0a8497e404b25a769fe792eacae88b9b576c30870450cd243d76ec9427d91a59c4a2cc00d824d283448b444c4c698a8f961d771c8ae39c759313afb31950e2e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve-from@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "resolve-from@npm:4.0.0"
|
||||
@@ -3021,6 +3263,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shallow-equal@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "shallow-equal@npm:1.2.1"
|
||||
checksum: 10c0/51e03abadd97c9ebe590547d92db9148446962a3f23a3a0fb1ba2fccab80af881eef0ff1f8ccefd3f066c0bc5a4c8ca53706194813b95c8835fa66448a843a26
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sharp@npm:^0.34.5":
|
||||
version: 0.34.5
|
||||
resolution: "sharp@npm:0.34.5"
|
||||
@@ -3166,6 +3415,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"state-local@npm:^1.0.6":
|
||||
version: 1.0.7
|
||||
resolution: "state-local@npm:1.0.7"
|
||||
checksum: 10c0/8dc7daeac71844452fafb514a6d6b6f40d7e2b33df398309ea1c7b3948d6110c57f112b7196500a10c54fdde40291488c52c875575670fb5c819602deca48bd9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"std-env@npm:^4.0.0-rc.1":
|
||||
version: 4.1.0
|
||||
resolution: "std-env@npm:4.1.0"
|
||||
@@ -3332,22 +3588,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-client-sdk@npm:2.16.0, twenty-client-sdk@npm:^2.16.0":
|
||||
version: 2.16.0
|
||||
resolution: "twenty-client-sdk@npm:2.16.0"
|
||||
"twenty-client-sdk@npm:2.18.0, twenty-client-sdk@npm:^2.18.0":
|
||||
version: 2.18.0
|
||||
resolution: "twenty-client-sdk@npm:2.18.0"
|
||||
dependencies:
|
||||
"@genql/runtime": "npm:^2.10.0"
|
||||
esbuild: "npm:^0.28.1"
|
||||
graphql: "npm:^16.8.1"
|
||||
lodash: "npm:^4.17.21"
|
||||
prettier: "npm:^3.8.3"
|
||||
checksum: 10c0/f990220bec103a04ca50dc8bb38c295e94a4e51b27a3188f4a0ae515c204b76bf87f5591739eb705b97e7aa5a9aa6a215d0d4da05297e50a0392801ea64a2234
|
||||
checksum: 10c0/3b7d6ca4e7b59c04cd2348af0f2ba8de153e70d565b4a38c2744b44998dfcc6c2bdb84ba6315cec208953d8c6fa7573ab33c17355f971f279299d470c57be06e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-sdk@npm:^2.16.0":
|
||||
version: 2.16.0
|
||||
resolution: "twenty-sdk@npm:2.16.0"
|
||||
"twenty-sdk@npm:^2.18.0":
|
||||
version: 2.18.0
|
||||
resolution: "twenty-sdk@npm:2.18.0"
|
||||
dependencies:
|
||||
"@sniptt/guards": "npm:^0.2.0"
|
||||
axios: "npm:^1.16.0"
|
||||
@@ -3366,12 +3622,41 @@ __metadata:
|
||||
semver: "npm:7.6.3"
|
||||
sharp: "npm:^0.34.5"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
twenty-client-sdk: "npm:2.16.0"
|
||||
twenty-client-sdk: "npm:2.18.0"
|
||||
typescript: "npm:^5.9.3"
|
||||
uuid: "npm:^13.0.2"
|
||||
bin:
|
||||
twenty: dist/cli.cjs
|
||||
checksum: 10c0/528f275f8bda022061a52c3563af514d2257f291fc5a232be90ca6011c50ff18a250984b6c68fc8e4c690644c92e1ccb0b89c511b8a19d832455c8860f2751a8
|
||||
checksum: 10c0/7e6d300894c427029e4124047614b55988671c914332613acb38f830cee11ecc480ff2a586b6153756aab1b7fa7f4520cf072c9db1428cd9ce9b9f7a62dfcd64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-ui@npm:^1.0.0-alpha.1":
|
||||
version: 1.0.0-alpha.1
|
||||
resolution: "twenty-ui@npm:1.0.0-alpha.1"
|
||||
dependencies:
|
||||
"@base-ui/react": "npm:^1.5.0"
|
||||
"@monaco-editor/react": "npm:^4.7.0"
|
||||
"@radix-ui/colors": "npm:^3.0.0"
|
||||
"@sniptt/guards": "npm:^0.2.0"
|
||||
"@tabler/icons-react": "npm:^3.31.0"
|
||||
clsx: "npm:^2.1.1"
|
||||
react-responsive: "npm:^9.0.2"
|
||||
react-router-dom: "npm:^6.4.4"
|
||||
type-fest: "npm:^4.10.1"
|
||||
zod: "npm:^4.1.11"
|
||||
peerDependencies:
|
||||
monaco-editor: ">= 0.25.0 < 1"
|
||||
react: ^19.0.0
|
||||
react-dom: ^19.0.0
|
||||
checksum: 10c0/213582e582949d6ffe90a7a8ab89a1d4e9dc724d34ed1b80bd7d50e834a8cb8539723556d41b39dc658f76f176bd8490ae0da8a3d6a713e79e6573b732084179
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-fest@npm:^4.10.1":
|
||||
version: 4.41.0
|
||||
resolution: "type-fest@npm:4.41.0"
|
||||
checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3432,6 +3717,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-sync-external-store@npm:^1.6.0":
|
||||
version: 1.6.0
|
||||
resolution: "use-sync-external-store@npm:1.6.0"
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"utility-types@npm:^3.10.0":
|
||||
version: 3.11.0
|
||||
resolution: "utility-types@npm:3.11.0"
|
||||
@@ -3725,3 +4019,10 @@ __metadata:
|
||||
checksum: 10c0/71cc2f2bbb537300c3f569e25693d37b3bc91f225cefce251a71c30bc6bb3e7f8e9420ca0eb57f2ac9e492b085b8dfa075fd1e8195c40b83c951dd59c6e4fbf8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^4.1.11":
|
||||
version: 4.4.3
|
||||
resolution: "zod@npm:4.4.3"
|
||||
checksum: 10c0/7ea31b558e88f9faf44f31dd185e2e1cbf51fed3081787fb96cc2534749b50c0acfc6da7f0922a7353ed092dd358c7d50c28ea96c94d04af64191bd33152eca3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
Reference in New Issue
Block a user