Compare commits

..

1 Commits

Author SHA1 Message Date
Jacques ROUSSEL 1280e806bf 💚(github) remove secret fetch
The secrets are not managed in the folder anymore.
2025-02-05 11:24:40 +01:00
117 changed files with 129 additions and 37584 deletions
+5 -4
View File
@@ -117,7 +117,8 @@ jobs:
- build-and-push-backend
- build-and-push-summary
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
if: |
github.event_name != 'pull_request'
steps:
-
name: Checkout repository
@@ -125,6 +126,6 @@ jobs:
-
name: Call argocd github webhook
run: |
data='{"ref": "'$GITHUB_REF'","repository": {"html_url":"'$GITHUB_SERVER_URL'/numerique-gouv/lasuite-deploiement"}}'
sig=$(echo -n ${data} | openssl dgst -sha1 -hmac "${{ secrets.ARGOCD_PREPROD_WEBHOOK_SECRET }}" | awk '{print "X-Hub-Signature: sha1="$2}')
curl -X POST -H 'X-GitHub-Event:push' -H "Content-Type: application/json" -H "${sig}" --data "${data}" ${{ vars.ARGOCD_PREPROD_WEBHOOK_URL }}
data='{"ref": "'$GITHUB_REF'","repository": {"html_url":"'$GITHUB_SERVER_URL'/'$GITHUB_REPOSITORY'"}}'
sig=$(echo -n ${data} | openssl dgst -sha1 -hmac ''${{ secrets.ARGOCD_WEBHOOK_SECRET }}'' | awk '{print "X-Hub-Signature: sha1="$2}')
curl -X POST -H 'X-GitHub-Event:push' -H "Content-Type: application/json" -H "${sig}" --data "${data}" ${{ vars.ARGOCD_WEBHOOK_URL }}
+6 -37
View File
@@ -175,56 +175,25 @@ jobs:
- name: Check format
run: cd src/frontend/ && npm run check
lint-sdk:
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/sdk/library
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Check linting
run: npm run lint
- name: Check format
run: npm run check
build-sdk:
runs-on: ubuntu-latest
needs: lint-sdk
defaults:
run:
working-directory: src/sdk/library
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Build SDK
run: npm run build
i18n-crowdin:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v1
-
uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: "infrastructure,secrets"
- name: Checkout repository
-
name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ steps.app-token.outputs.token }}
- name: Load sops secrets
-
name: Load sops secrets
uses: rouja/actions-sops@main
with:
secret-file: secrets/numerique-gouv/meet/secrets.enc.env
+3 -1
View File
@@ -3,8 +3,10 @@ run-name: Release Chart
on:
push:
branches:
- 'main'
paths:
- src/helm/meet/**
- ./src/helm/meet/**
jobs:
release:
-1
View File
@@ -1,4 +1,3 @@
# Changelog
All notable changes to this project will be documented in this file.
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "meet"
version = "0.1.13"
version = "0.1.12"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
-5
View File
@@ -34,11 +34,6 @@ RUN npm run build
# ---- Front-end image ----
FROM nginxinc/nginx-unprivileged:1.26-alpine AS frontend-production
USER root
RUN apk update && apk upgrade libssl3 libcrypto3
USER nginx
# Un-privileged user running the application
ARG DOCKER_USER
USER ${DOCKER_USER}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "meet",
"version": "0.1.13",
"version": "0.1.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meet",
"version": "0.1.13",
"version": "0.1.12",
"dependencies": {
"@livekit/components-react": "2.8.1",
"@livekit/components-styles": "1.1.4",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "0.1.13",
"version": "0.1.12",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
-26
View File
@@ -121,32 +121,6 @@ const config: Config = {
'50%': { opacity: '0.65' },
'100%': { opacity: '1' },
},
rotate: {
'0%': {
transform: 'rotate(0deg)',
},
'100%': {
transform: 'rotate(360deg)',
},
},
prixClipFix: {
'0%': {
clipPath: 'polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0)',
},
'25%': {
clipPath: 'polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0)',
},
'50%': {
clipPath:
'polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%)',
},
'75%': {
clipPath: 'polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%)',
},
'100%': {
clipPath: 'polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0)',
},
},
},
tokens: defineTokens({
/* we take a few things from the panda preset but for now we clear out some stuff.
+10 -27
View File
@@ -13,41 +13,24 @@ import { routes } from './routes'
import './i18n/init'
import { queryClient } from '@/api/queryClient'
import { AppInitialization } from '@/components/AppInitialization'
import { SdkCreateButton } from './features/sdk/routes/CreateButton'
function App() {
const { i18n } = useTranslation()
useLang(i18n.language)
return (
<QueryClientProvider client={queryClient}>
<AppInitialization />
<Suspense fallback={null}>
<I18nProvider locale={i18n.language}>
<Switch>
<Route path="/sdk" nest>
<Route path="/create-button">
<SdkCreateButton />
</Route>
</Route>
{/* We only want support and ReactQueryDevTools in non /sdk routes */}
<Route path="*">
<AppInitialization />
<Layout>
{Object.entries(routes).map(([, route], i) => (
<Route
key={i}
path={route.path}
component={route.Component}
/>
))}
</Layout>
<ReactQueryDevtools
initialIsOpen={false}
buttonPosition="top-left"
/>
</Route>
<Route component={NotFoundScreen} />
</Switch>
<Layout>
<Switch>
{Object.entries(routes).map(([, route], i) => (
<Route key={i} path={route.path} component={route.Component} />
))}
<Route component={NotFoundScreen} />
</Switch>
</Layout>
<ReactQueryDevtools initialIsOpen={false} buttonPosition="top-left" />
</I18nProvider>
</Suspense>
</QueryClientProvider>
-24
View File
@@ -1,24 +0,0 @@
export const VisioIcon = () => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.841702 8.95427C0.75 9.42479 0.75 10.0042 0.75 10.9748V13.3096C0.75 14.5138 0.75 15.1159 0.925134 15.6549C1.08009 16.1319 1.33356 16.5709 1.6691 16.9435C2.04833 17.3647 2.56977 17.6658 3.61264 18.2679L5.6346 19.4353C6.55753 19.9681 7.07208 20.2652 7.56247 20.4081V13.2043C7.56247 12.8442 7.36578 12.5129 7.04965 12.3404L0.841702 8.95427Z"
fill="currentColor"
/>
<path
d="M17.747 10.0123V13.7685C17.747 14.2878 17.9727 14.7815 18.3654 15.1214L21.0688 17.4609C21.227 17.5947 21.3912 17.7042 21.5616 17.7894C21.738 17.8685 21.9084 17.9081 22.0726 17.9081C22.4255 17.9081 22.7084 17.7925 22.9213 17.5613C23.1404 17.324 23.2499 17.0168 23.2499 16.6396V7.14866C23.2499 6.77146 23.1404 6.46726 22.9213 6.23607C22.7084 5.9988 22.4255 5.88017 22.0726 5.88017C21.9084 5.88017 21.738 5.91971 21.5616 5.9988C21.3912 6.07789 21.227 6.1874 21.0688 6.32733L18.3675 8.65759C17.9735 8.99746 17.747 9.49201 17.747 10.0123Z"
fill="currentColor"
/>
<path
d="M1.74517 7.61282C1.67 7.57182 1.59117 7.54405 1.51135 7.52872C1.56171 7.46443 1.61433 7.40178 1.66914 7.34091C2.04837 6.91973 2.56981 6.61868 3.61268 6.01657L5.63464 4.84919C6.67751 4.24708 7.19894 3.94603 7.7533 3.82819C8.2438 3.72394 8.75074 3.72394 9.24124 3.82819C9.7956 3.94603 10.317 4.24708 11.3599 4.84919L13.358 6.0028C13.366 6.00738 13.374 6.01197 13.3819 6.01658C14.4248 6.61868 14.9462 6.91973 15.3255 7.34091C15.661 7.71357 15.9145 8.15259 16.0694 8.62951C16.2446 9.16852 16.2446 9.77063 16.2446 10.9748V13.3096C16.2446 14.5138 16.2446 15.1159 16.0694 15.6549C15.9145 16.1319 15.661 16.5709 15.3255 16.9435C14.9462 17.3647 14.4248 17.6657 13.382 18.2678C13.373 18.273 13.364 18.2782 13.3551 18.2833L11.3599 19.4353C10.317 20.0374 9.7956 20.3384 9.24124 20.4562C9.21846 20.4611 9.19564 20.4657 9.17279 20.4701L9.17278 13.2043C9.17278 12.255 8.65422 11.3814 7.82079 10.9268L1.74517 7.61282Z"
fill="currentColor"
/>
</svg>
)
}
@@ -10,13 +10,7 @@ import { attemptSilentLogin, canAttemptSilentLogin } from '../utils/silentLogin'
* Here our wrapper just returns false in that case, without triggering an error:
* this is done to prevent unnecessary query retries with react query
*/
export const fetchUser = (
opts: {
attemptSilent?: boolean
} = {
attemptSilent: true,
}
): Promise<ApiUser | false> => {
export const fetchUser = (): Promise<ApiUser | false> => {
return new Promise((resolve, reject) => {
fetchApi<ApiUser>('/users/me')
.then(resolve)
@@ -25,7 +19,7 @@ export const fetchUser = (
if (error instanceof ApiError && error.statusCode === 401) {
// make sure to not resolve the promise while trying to silent login
// so that consumers of fetchUser don't think the work already ended
if (opts.attemptSilent && canAttemptSilentLogin()) {
if (canAttemptSilentLogin()) {
attemptSilentLogin(300)
} else {
resolve(false)
@@ -11,15 +11,10 @@ import { initializeSupportSession } from '@/features/support/hooks/useSupport'
*
* `isLoggedIn` is undefined while query is loading and true/false when it's done
*/
export const useUser = (
opts: {
fetchUserOptions?: Parameters<typeof fetchUser>[0]
} = {}
) => {
export const useUser = () => {
const query = useQuery({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: [keys.user],
queryFn: () => fetchUser(opts.fetchUserOptions),
queryFn: fetchUser,
staleTime: Infinity,
})
+1 -14
View File
@@ -13,12 +13,11 @@ import { RiAddLine, RiLink } from '@remixicon/react'
import { LaterMeetingDialog } from '@/features/home/components/LaterMeetingDialog'
import { IntroSlider } from '@/features/home/components/IntroSlider'
import { MoreLink } from '@/features/home/components/MoreLink'
import { ReactNode, useEffect, useState } from 'react'
import { ReactNode, useState } from 'react'
import { css } from '@/styled-system/css'
import { menuRecipe } from '@/primitives/menuRecipe.ts'
import { usePersistentUserChoices } from '@/features/rooms/livekit/hooks/usePersistentUserChoices'
import { SdkReverseClient } from '@/features/sdk/SdkReverseClient'
const Columns = ({ children }: { children?: ReactNode }) => {
return (
@@ -157,18 +156,6 @@ export const Home = () => {
const { mutateAsync: createRoom } = useCreateRoom()
const [laterRoomId, setLaterRoomId] = useState<null | string>(null)
const { user } = useUser()
/**
* Used for SDK popup to close automatically.
*/
useEffect(() => {
if (!user) {
return
}
SdkReverseClient.broadcastAuthentication()
}, [user])
return (
<UserAware>
<Screen>
@@ -1,69 +1,16 @@
import { useEffect } from 'react'
import { useRoomContext } from '@livekit/components-react'
import { Participant, RemoteParticipant, RoomEvent } from 'livekit-client'
import { Participant, RoomEvent } from 'livekit-client'
import { ToastProvider, toastQueue } from './components/ToastProvider'
import { NotificationType } from './NotificationType'
import { NotificationDuration } from './NotificationDuration'
import { Div } from '@/primitives'
import { ChatMessage, isMobileBrowser } from '@livekit/components-core'
import { isMobileBrowser } from '@livekit/components-core'
import { useNotificationSound } from '@/features/notifications/hooks/useSoundNotification'
export const MainNotificationToast = () => {
const room = useRoomContext()
const { triggerNotificationSound } = useNotificationSound()
useEffect(() => {
const handleChatMessage = (
chatMessage: ChatMessage,
participant?: Participant | undefined
) => {
if (!participant || participant.isLocal) return
triggerNotificationSound(NotificationType.MessageReceived)
toastQueue.add(
{
participant: participant,
message: chatMessage.message,
type: NotificationType.MessageReceived,
},
{ timeout: NotificationDuration.MESSAGE }
)
}
room.on(RoomEvent.ChatMessage, handleChatMessage)
return () => {
room.off(RoomEvent.ChatMessage, handleChatMessage)
}
}, [room, triggerNotificationSound])
useEffect(() => {
const handleDataReceived = (
payload: Uint8Array,
participant?: RemoteParticipant
) => {
const decoder = new TextDecoder()
const notificationType = decoder.decode(payload)
if (!participant) return
switch (notificationType) {
case NotificationType.ParticipantMuted:
toastQueue.add(
{
participant,
type: NotificationType.ParticipantMuted,
},
{ timeout: NotificationDuration.ALERT }
)
break
default:
return
}
}
room.on(RoomEvent.DataReceived, handleDataReceived)
return () => {
room.off(RoomEvent.DataReceived, handleDataReceived)
}
}, [room])
useEffect(() => {
const showJoinNotification = (participant: Participant) => {
if (isMobileBrowser()) {
@@ -76,7 +23,7 @@ export const MainNotificationToast = () => {
type: NotificationType.ParticipantJoined,
},
{
timeout: NotificationDuration.PARTICIPANT_JOINED,
timeout: 5000,
}
)
}
@@ -135,7 +82,7 @@ export const MainNotificationToast = () => {
participant,
type: NotificationType.HandRaised,
},
{ timeout: NotificationDuration.HAND_RAISED }
{ timeout: 5000 }
)
}
}
@@ -1,14 +0,0 @@
export enum ToastDuration {
SHORT = 3000,
MEDIUM = 4000,
LONG = 5000,
EXTRA_LONG = 7000,
}
export const NotificationDuration = {
ALERT: ToastDuration.SHORT,
MESSAGE: ToastDuration.LONG,
PARTICIPANT_JOINED: ToastDuration.LONG,
HAND_RAISED: ToastDuration.LONG,
LOWER_HAND: ToastDuration.EXTRA_LONG,
} as const
@@ -1,7 +1,5 @@
export enum NotificationType {
ParticipantJoined = 'participantJoined',
HandRaised = 'handRaised',
ParticipantMuted = 'participantMuted',
MessageReceived = 'messageReceived',
LowerHand = 'lowerHand',
// todo - implement message received notification
}
@@ -1,45 +0,0 @@
import { useToast } from '@react-aria/toast'
import { useRef } from 'react'
import { StyledToastContainer, ToastProps } from './Toast'
import { HStack } from '@/styled-system/jsx'
import { useTranslation } from 'react-i18next'
import { Button } from '@/primitives'
export function ToastLowerHand({ state, ...props }: ToastProps) {
const { t } = useTranslation('notifications', { keyPrefix: 'lowerHand' })
const ref = useRef(null)
const { toastProps, contentProps } = useToast(props, state, ref)
const toast = props.toast
const handleDismiss = () => {
// Clear onClose handler to prevent lowering the hand when user dismisses
toast.onClose = undefined
state.close(toast.key)
}
return (
<StyledToastContainer {...toastProps} ref={ref}>
<HStack
justify="center"
alignItems="center"
{...contentProps}
padding={14}
gap={0}
>
<p>{t('auto')}</p>
<Button
size="sm"
variant="text"
style={{
color: '#60a5fa',
marginLeft: '0.5rem',
}}
onPress={() => handleDismiss()}
>
{t('dismiss')}
</Button>
</HStack>
</StyledToastContainer>
)
}
@@ -1,74 +0,0 @@
import { useToast } from '@react-aria/toast'
import { useEffect, useRef } from 'react'
import { StyledToastContainer, ToastProps } from './Toast'
import { Text } from '@/primitives'
import { RiMessage2Line } from '@remixicon/react'
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
import { Button as RACButton } from 'react-aria-components'
import { css } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
export function ToastMessageReceived({ state, ...props }: ToastProps) {
const { t } = useTranslation('notifications')
const ref = useRef(null)
const { toastProps } = useToast(props, state, ref)
const toast = props.toast
const participant = toast.content.participant
const message = toast.content.message
const { isChatOpen, toggleChat } = useSidePanel()
useEffect(() => {
if (isChatOpen) {
state.close(toast.key)
}
}, [isChatOpen, toast, state])
if (isChatOpen) return null
return (
<StyledToastContainer {...toastProps} ref={ref}>
<RACButton onPress={() => toggleChat()} aria-label={t('openChat')}>
<div
className={css({
display: 'flex',
flexDirection: 'column',
alignItems: 'start',
padding: '14px',
gap: '0.75rem',
textAlign: 'start',
width: '150px',
md: {
width: '260px',
},
})}
>
<div
className={css({
display: 'flex',
flexDirection: 'row',
justifyContent: 'start',
gap: '0.5rem',
})}
>
<RiMessage2Line
size={20}
className={css({
color: 'primary.300',
marginTop: '3px',
})}
aria-hidden="true"
/>
<span>{participant.name}</span>
</div>
<Text margin={false} centered={false} wrap={'pretty'} fullWidth>
{message}
</Text>
</div>
</RACButton>
</StyledToastContainer>
)
}
@@ -1,26 +0,0 @@
import { useToast } from '@react-aria/toast'
import { useRef } from 'react'
import { StyledToastContainer, ToastProps } from './Toast'
import { HStack } from '@/styled-system/jsx'
import { useTranslation } from 'react-i18next'
export function ToastMuted({ state, ...props }: ToastProps) {
const { t } = useTranslation('notifications')
const ref = useRef(null)
const { toastProps, contentProps } = useToast(props, state, ref)
const participant = props.toast.content.participant
return (
<StyledToastContainer {...toastProps} ref={ref}>
<HStack
justify="center"
alignItems="center"
{...contentProps}
padding={14}
gap={0}
>
{t('muted', { name: participant.name })}
</HStack>
</StyledToastContainer>
)
}
@@ -1,52 +1,30 @@
import { AriaToastRegionProps, useToastRegion } from '@react-aria/toast'
import type { QueuedToast, ToastState } from '@react-stately/toast'
import type { ToastState } from '@react-stately/toast'
import { Toast } from './Toast'
import { useRef } from 'react'
import { NotificationType } from '../NotificationType'
import { ToastJoined } from './ToastJoined'
import { ToastData } from './ToastProvider'
import { ToastRaised } from './ToastRaised'
import { ToastMuted } from './ToastMuted'
import { ToastMessageReceived } from './ToastMessageReceived'
import { ToastLowerHand } from './ToastLowerHand'
import { ToastRaised } from '@/features/notifications/components/ToastRaised.tsx'
interface ToastRegionProps extends AriaToastRegionProps {
state: ToastState<ToastData>
}
const renderToast = (
toast: QueuedToast<ToastData>,
state: ToastState<ToastData>
) => {
switch (toast.content?.type) {
case NotificationType.ParticipantJoined:
return <ToastJoined key={toast.key} toast={toast} state={state} />
case NotificationType.HandRaised:
return <ToastRaised key={toast.key} toast={toast} state={state} />
case NotificationType.ParticipantMuted:
return <ToastMuted key={toast.key} toast={toast} state={state} />
case NotificationType.MessageReceived:
return (
<ToastMessageReceived key={toast.key} toast={toast} state={state} />
)
case NotificationType.LowerHand:
return <ToastLowerHand key={toast.key} toast={toast} state={state} />
default:
return <Toast key={toast.key} toast={toast} state={state} />
}
}
export function ToastRegion({ state, ...props }: ToastRegionProps) {
const ref = useRef(null)
const { regionProps } = useToastRegion(props, state, ref)
return (
<div {...regionProps} ref={ref} className="toast-region">
{state.visibleToasts.map((toast) => renderToast(toast, state))}
{state.visibleToasts.map((toast) => {
if (toast.content?.type === NotificationType.ParticipantJoined) {
return <ToastJoined key={toast.key} toast={toast} state={state} />
}
if (toast.content?.type === NotificationType.HandRaised) {
return <ToastRaised key={toast.key} toast={toast} state={state} />
}
return <Toast key={toast.key} toast={toast} state={state} />
})}
</div>
)
}
@@ -1,28 +0,0 @@
import { toastQueue } from './components/ToastProvider'
import { NotificationType } from './NotificationType'
import { NotificationDuration } from './NotificationDuration'
import { Participant } from 'livekit-client'
export const showLowerHandToast = (
participant: Participant,
onClose: () => void
) => {
toastQueue.add(
{
participant,
type: NotificationType.LowerHand,
},
{
timeout: NotificationDuration.LOWER_HAND,
onClose,
}
)
}
export const closeLowerHandToasts = () => {
toastQueue.visibleToasts.forEach((toast) => {
if (toast.content.type === NotificationType.LowerHand) {
toastQueue.close(toast.key)
}
})
}
@@ -32,7 +32,7 @@ export const Conference = ({
useEffect(() => {
posthog.capture('visit-room', { slug: roomId })
}, [roomId])
const fetchKey = [keys.room, roomId]
const fetchKey = [keys.room, roomId, userConfig.username]
const {
mutateAsync: createRoom,
@@ -49,7 +49,6 @@ export const Conference = ({
isError: isFetchError,
data,
} = useQuery({
/* eslint-disable @tanstack/query/exhaustive-deps */
queryKey: fetchKey,
staleTime: 6 * 60 * 60 * 1000, // By default, LiveKit access tokens expire 6 hours after generation
initialData: initialRoomData,
@@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next'
import { styled, VStack } from '@/styled-system/jsx'
import { usePostHog } from 'posthog-js/react'
import { PostHog } from 'posthog-js'
import { Button as RACButton } from 'react-aria-components'
const Card = styled('div', {
base: {
@@ -171,7 +170,7 @@ const RateQuality = ({
<H lvl={3}>{t('question')}</H>
<Bar>
{[...Array(maxRating)].map((_, index) => (
<RACButton
<Button
key={index}
onPress={() => handleRatingClick(index + 1)}
className={ratingButtonRecipe({
@@ -180,7 +179,7 @@ const RateQuality = ({
})}
>
{index + 1}
</RACButton>
</Button>
))}
</Bar>
<div
@@ -3,23 +3,11 @@ import Source = Track.Source
import { fetchServerApi } from './fetchServerApi'
import { buildServerApiUrl } from './buildServerApiUrl'
import { useRoomData } from '../hooks/useRoomData'
import { useRoomContext } from '@livekit/components-react'
import { NotificationType } from '@/features/notifications/NotificationType'
export const useMuteParticipant = () => {
const data = useRoomData()
const room = useRoomContext()
const notifyParticipant = async (participant: Participant) => {
const encoder = new TextEncoder()
const data = encoder.encode(NotificationType.ParticipantMuted)
await room.localParticipant.publishData(data, {
reliable: true,
destinationIdentities: [participant.identity],
})
}
const muteParticipant = async (participant: Participant) => {
const muteParticipant = (participant: Participant) => {
if (!data || !data?.livekit) {
throw new Error('Room data is not available')
}
@@ -30,33 +18,22 @@ export const useMuteParticipant = () => {
if (!trackSid) {
throw new Error('Missing audio track')
}
try {
const response = await fetchServerApi(
buildServerApiUrl(
data.livekit.url,
'twirp/livekit.RoomService/MutePublishedTrack'
),
data.livekit.token,
{
method: 'POST',
body: JSON.stringify({
room: data.livekit.room,
identity: participant.identity,
muted: true,
track_sid: trackSid,
}),
}
)
await notifyParticipant(participant)
return response
} catch (error) {
console.error(
`Failed to mute participant ${participant.identity}: ${error instanceof Error ? error.message : 'Unknown error'}`
)
}
return fetchServerApi(
buildServerApiUrl(
data.livekit.url,
'twirp/livekit.RoomService/MutePublishedTrack'
),
data.livekit.token,
{
method: 'POST',
body: JSON.stringify({
room: data.livekit.room,
identity: participant.identity,
muted: true,
track_sid: trackSid,
}),
}
)
}
return { muteParticipant }
}
@@ -1,121 +0,0 @@
import { css } from '@/styled-system/css'
import { Button, Text } from '@/primitives'
import { useMemo, useRef } from 'react'
import { ScreenSharePreferenceStore } from '@/stores/ScreenSharePreferences'
import { useSnapshot } from 'valtio'
import { useLocalParticipant } from '@livekit/components-react'
import { useSize } from '../hooks/useResizeObserver'
import { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { useTranslation } from 'react-i18next'
export const FullScreenShareWarning = ({
trackReference,
}: {
trackReference: TrackReferenceOrPlaceholder
}) => {
const { t } = useTranslation('rooms', { keyPrefix: 'fullScreenWarning' })
const warningContainerRef = useRef<HTMLDivElement>(null)
const { width: containerWidth } = useSize(warningContainerRef)
const { localParticipant } = useLocalParticipant()
const screenSharePreferences = useSnapshot(ScreenSharePreferenceStore)
const isFullScreenCapture = useMemo(() => {
const trackLabel =
trackReference.publication?.track?.mediaStreamTrack?.label
return trackLabel?.includes('screen')
}, [trackReference])
const shouldShowWarning =
screenSharePreferences.enabled && isFullScreenCapture
const handleStopScreenShare = async () => {
if (!localParticipant.isScreenShareEnabled) return
await localParticipant.setScreenShareEnabled(false, {}, {})
}
const handleDismissWarning = () => {
ScreenSharePreferenceStore.enabled = false
}
if (!shouldShowWarning) return null
return (
<div
className={css({
position: 'absolute',
zIndex: '1000',
height: '100%',
width: '100%',
})}
ref={warningContainerRef}
>
{(!containerWidth || containerWidth >= 300) && (
<div
className={css({
position: 'absolute',
zIndex: '1000',
height: '100%',
width: '100%',
backgroundColor: 'rgba(22, 22, 34, 0.9)',
padding: '2rem',
})}
>
<div
className={css({
display: 'flex',
justifyContent: 'space-between',
flexDirection: 'column',
gap: '1rem',
md: {
flexDirection: 'row',
},
})}
>
<Text
style={{
color: 'white',
flexBasis: '55%',
fontWeight: '500',
}}
margin={false}
wrap={'pretty'}
>
{t('message')}
</Text>
<div
className={css({
display: 'flex',
flexDirection: 'row',
gap: '1rem',
})}
>
<Button
variant="tertiary"
size="sm"
style={{
height: 'fit-content',
}}
onPress={async () => {
await handleStopScreenShare()
}}
>
{t('stop')}
</Button>
<Button
variant="primaryTextDark"
size="sm"
style={{
height: 'fit-content',
}}
onPress={() => handleDismissWarning()}
>
{t('ignore')}
</Button>
</div>
</div>
</div>
)}
</div>
)
}
@@ -1,36 +0,0 @@
import { useTranslation } from 'react-i18next'
import { Button, Dialog, P } from '@/primitives'
import { HStack } from '@/styled-system/jsx'
export const MuteAlertDialog = ({
isOpen,
onClose,
onSubmit,
name,
}: {
isOpen: boolean
onClose: () => void
onSubmit: () => void
name: string
}) => {
const { t } = useTranslation('rooms', {
keyPrefix: 'participants.muteParticipantAlert',
})
return (
<Dialog
isOpen={isOpen}
role="alertdialog"
aria-label={t('heading', { name })}
>
<P>{t('description', { name })}</P>
<HStack gap={1}>
<Button variant="text" size="sm" onPress={onClose}>
{t('cancel')}
</Button>
<Button variant="text" size="sm" onPress={onSubmit}>
{t('confirm')}
</Button>
</HStack>
</Dialog>
)
}
@@ -1,6 +1,7 @@
import {
AudioTrack,
ConnectionQualityIndicator,
FocusToggle,
LockLockedIcon,
ParticipantName,
ParticipantTileProps,
@@ -22,13 +23,11 @@ import {
TrackReferenceOrPlaceholder,
} from '@livekit/components-core'
import { Track } from 'livekit-client'
import { ParticipantPlaceholder } from '@/features/rooms/livekit/components/ParticipantPlaceholder'
import { RiHand } from '@remixicon/react'
import { useRaisedHand } from '../hooks/useRaisedHand'
import { useRaisedHand } from '@/features/rooms/livekit/hooks/useRaisedHand'
import { HStack } from '@/styled-system/jsx'
import { MutedMicIndicator } from './MutedMicIndicator'
import { ParticipantPlaceholder } from './ParticipantPlaceholder'
import { ParticipantTileFocus } from './ParticipantTileFocus'
import { FullScreenShareWarning } from './FullScreenShareWarning'
import { MutedMicIndicator } from '@/features/rooms/livekit/components/MutedMicIndicator'
export function TrackRefContextIfNeeded(
props: React.PropsWithChildren<{
@@ -101,7 +100,6 @@ export const ParticipantTile: (
<div ref={ref} style={{ position: 'relative' }} {...elementProps}>
<TrackRefContextIfNeeded trackRef={trackReference}>
<ParticipantContextIfNeeded participant={trackReference.participant}>
<FullScreenShareWarning trackReference={trackReference} />
{children ?? (
<>
{isTrackReference(trackReference) &&
@@ -175,9 +173,7 @@ export const ParticipantTile: (
)}
</>
)}
{!disableMetadata && (
<ParticipantTileFocus trackRef={trackReference} />
)}
{!disableMetadata && <FocusToggle trackRef={trackReference} />}
</ParticipantContextIfNeeded>
</TrackRefContextIfNeeded>
</div>
@@ -1,224 +0,0 @@
import { css } from '@/styled-system/css'
import { HStack } from '@/styled-system/jsx'
import { Button } from '@/primitives'
import {
RiFullscreenLine,
RiImageCircleAiFill,
RiMicLine,
RiMicOffLine,
RiPushpin2Line,
RiUnpinLine,
} from '@remixicon/react'
import {
useFocusToggle,
useTrackMutedIndicator,
} from '@livekit/components-react'
import { useTranslation } from 'react-i18next'
import { TrackReferenceOrPlaceholder } from '@livekit/components-core'
import { useEffect, useRef, useState } from 'react'
import { useSidePanel } from '../hooks/useSidePanel'
import { useFullScreen } from '../hooks/useFullScreen'
import { Participant, Track } from 'livekit-client'
import { MuteAlertDialog } from './MuteAlertDialog'
import { useMuteParticipant } from '../api/muteParticipant'
const ZoomButton = ({
trackRef,
}: {
trackRef: TrackReferenceOrPlaceholder
}) => {
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
const { toggleFullScreen, isFullscreenAvailable } = useFullScreen({
trackRef,
})
if (!isFullscreenAvailable) {
return
}
return (
<Button
size="sm"
variant="primaryTextDark"
square
tooltip={t('fullScreen')}
onPress={() => toggleFullScreen()}
>
<RiFullscreenLine />
</Button>
)
}
const FocusButton = ({
trackRef,
}: {
trackRef: TrackReferenceOrPlaceholder
}) => {
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
const { mergedProps, inFocus } = useFocusToggle({
trackRef,
props: {},
})
return (
<Button
size="sm"
variant="primaryTextDark"
square
tooltip={inFocus ? t('pin.disable') : t('pin.enable')}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onPress={(event) => mergedProps?.onClick?.(event as any)}
>
{inFocus ? <RiUnpinLine /> : <RiPushpin2Line />}
</Button>
)
}
const EffectsButton = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
const { isEffectsOpen, toggleEffects } = useSidePanel()
return (
<Button
size={'sm'}
variant={'primaryTextDark'}
square
tooltip={t('effects')}
onPress={() => !isEffectsOpen && toggleEffects()}
>
<RiImageCircleAiFill />
</Button>
)
}
const MuteButton = ({ participant }: { participant: Participant }) => {
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
const { isMuted } = useTrackMutedIndicator({
participant: participant,
source: Track.Source.Microphone,
})
const { muteParticipant } = useMuteParticipant()
const [isAlertOpen, setIsAlertOpen] = useState(false)
const name = participant.name || participant.identity
return (
<>
<Button
isDisabled={isMuted}
size={'sm'}
variant={'primaryTextDark'}
square
onPress={() => setIsAlertOpen(true)}
tooltip={t('muteParticipant', { name })}
>
{!isMuted ? <RiMicLine /> : <RiMicOffLine />}
</Button>
<MuteAlertDialog
isOpen={isAlertOpen}
onSubmit={() =>
muteParticipant(participant).then(() => setIsAlertOpen(false))
}
onClose={() => setIsAlertOpen(false)}
name={name}
/>
</>
)
}
const MOUSE_IDLE_TIME = 3000
export const ParticipantTileFocus = ({
trackRef,
}: {
trackRef: TrackReferenceOrPlaceholder
}) => {
const [hovered, setHovered] = useState(false)
const [opacity, setOpacity] = useState(0)
const idleTimerRef = useRef<number | null>(null)
const [isIdleRef, setIsIdleRef] = useState(false)
useEffect(() => {
if (hovered && !isIdleRef) {
// Wait for next frame to ensure element is mounted
requestAnimationFrame(() => {
setOpacity(0.6)
})
} else {
setOpacity(0)
}
}, [hovered, isIdleRef])
const handleMouseMove = () => {
if (idleTimerRef.current) {
window.clearTimeout(idleTimerRef.current)
}
idleTimerRef.current = window.setTimeout(() => {
setIsIdleRef(true)
}, MOUSE_IDLE_TIME)
setIsIdleRef(false)
}
const participant = trackRef.participant
const isScreenShare = trackRef.source == Track.Source.ScreenShare
const isLocal = trackRef.participant.isLocal
return (
<div
className={css({
position: 'absolute',
left: '0',
top: '0',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100%',
})}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
onMouseMove={handleMouseMove}
>
{hovered && (
<div
className={css({
backgroundColor: 'primaryDark.50',
transition: 'opacity 200ms linear',
zIndex: 1,
borderRadius: '0.25rem',
display: 'flex',
_hover: {
opacity: '0.95 !important',
},
})}
style={{ opacity }}
>
<HStack
gap={0.5}
className={css({
padding: '0.5rem',
_hover: {
opacity: '1 !important',
},
})}
>
<FocusButton trackRef={trackRef} />
{!isScreenShare ? (
<>
{participant.isLocal ? (
<EffectsButton />
) : (
<MuteButton participant={participant} />
)}
</>
) : (
!isLocal && <ZoomButton trackRef={trackRef} />
)}
</HStack>
</div>
)}
</div>
)
}
@@ -4,13 +4,6 @@ import { ToggleButton } from '@/primitives'
import { css } from '@/styled-system/css'
import { useRoomContext } from '@livekit/components-react'
import { useRaisedHand } from '@/features/rooms/livekit/hooks/useRaisedHand'
import { useEffect, useRef, useState } from 'react'
import {
closeLowerHandToasts,
showLowerHandToast,
} from '@/features/notifications/utils'
const SPEAKING_DETECTION_DELAY = 3000
export const HandToggle = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.hand' })
@@ -20,39 +13,6 @@ export const HandToggle = () => {
participant: room.localParticipant,
})
const isSpeaking = room.localParticipant.isSpeaking
const speakingTimerRef = useRef<NodeJS.Timeout | null>(null)
const [hasShownToast, setHasShownToast] = useState(false)
const resetToastState = () => {
setHasShownToast(false)
}
useEffect(() => {
if (isHandRaised) return
closeLowerHandToasts()
}, [isHandRaised])
useEffect(() => {
const shouldShowToast = isSpeaking && isHandRaised && !hasShownToast
if (shouldShowToast && !speakingTimerRef.current) {
speakingTimerRef.current = setTimeout(() => {
setHasShownToast(true)
const onClose = () => {
if (isHandRaised) toggleRaisedHand()
resetToastState()
}
showLowerHandToast(room.localParticipant, onClose)
}, SPEAKING_DETECTION_DELAY)
}
if ((!isSpeaking || !isHandRaised) && speakingTimerRef.current) {
clearTimeout(speakingTimerRef.current)
speakingTimerRef.current = null
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSpeaking, isHandRaised, hasShownToast, toggleRaisedHand])
const tooltipLabel = isHandRaised ? 'lower' : 'raise'
return (
@@ -68,10 +28,7 @@ export const HandToggle = () => {
aria-label={t(tooltipLabel)}
tooltip={t(tooltipLabel)}
isSelected={isHandRaised}
onPress={() => {
toggleRaisedHand()
resetToastState()
}}
onPress={() => toggleRaisedHand()}
data-attr={`controls-hand-${tooltipLabel}`}
>
<RiHand />
@@ -7,7 +7,7 @@ import { useFullScreen } from '../../../hooks/useFullScreen'
export const FullScreenMenuItem = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'options.items' })
const { toggleFullScreen, isCurrentlyFullscreen, isFullscreenAvailable } =
useFullScreen({})
useFullScreen()
if (!isFullscreenAvailable) {
return null
@@ -13,10 +13,36 @@ import {
} from '@livekit/components-react'
import Source = Track.Source
import { RiMicFill, RiMicOffFill } from '@remixicon/react'
import { Button } from '@/primitives'
import { Button, Dialog, P } from '@/primitives'
import { useState } from 'react'
import { useMuteParticipant } from '@/features/rooms/livekit/api/muteParticipant'
import { MuteAlertDialog } from '../../MuteAlertDialog'
const MuteAlertDialog = ({
isOpen,
onClose,
onSubmit,
name,
}: {
isOpen: boolean
onClose: () => void
onSubmit: () => void
name: string
}) => {
const { t } = useTranslation('rooms')
return (
<Dialog isOpen={isOpen} role="alertdialog">
<P>{t('participants.muteParticipantAlert.description', { name })}</P>
<HStack gap={1}>
<Button variant="text" size="sm" onPress={onClose}>
{t('participants.muteParticipantAlert.cancel')}
</Button>
<Button variant="text" size="sm" onPress={onSubmit}>
{t('participants.muteParticipantAlert.confirm')}
</Button>
</HStack>
</Dialog>
)
}
type MicIndicatorProps = {
participant: Participant
@@ -1,4 +1,4 @@
import { LocalVideoTrack, Track } from 'livekit-client'
import { LocalVideoTrack } from 'livekit-client'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
@@ -12,9 +12,6 @@ import { styled } from '@/styled-system/jsx'
import { BackgroundOptions } from '@livekit/track-processors'
import { BlurOn } from '@/components/icons/BlurOn'
import { BlurOnStrong } from '@/components/icons/BlurOnStrong'
import { useTrackToggle } from '@livekit/components-react'
import { Loader } from '@/primitives/Loader'
import { useSyncAfterDelay } from '@/hooks/useSyncAfterDelay'
enum BlurRadius {
NONE = 0,
@@ -46,9 +43,7 @@ export const EffectsConfiguration = ({
}: EffectsConfigurationProps) => {
const videoRef = useRef<HTMLVideoElement>(null)
const { t } = useTranslation('rooms', { keyPrefix: 'effects' })
const { toggle, enabled } = useTrackToggle({ source: Track.Source.Camera })
const [processorPending, setProcessorPending] = useState(false)
const processorPendingReveal = useSyncAfterDelay(processorPending)
useEffect(() => {
const videoElement = videoRef.current
@@ -67,30 +62,8 @@ export const EffectsConfiguration = ({
type: ProcessorType,
options: BackgroundOptions
) => {
if (!videoTrack) return
setProcessorPending(true)
if (!videoTrack) {
/**
* Special case: if no video track is available, then we must pass directly the processor into the
* toggle call. Otherwise, the rest of the function below would not have a videoTrack to call
* setProccesor on.
*
* We arrive in this condition when we enter the room with the camera already off.
*/
const newProcessorTmp = BackgroundProcessorFactory.getProcessor(
type,
options
)!
await toggle(true, {
processor: newProcessorTmp,
})
setTimeout(() => setProcessorPending(false))
return
}
if (!enabled) {
await toggle(true)
}
const processor = getProcessor()
try {
if (isSelected(type, options)) {
@@ -112,7 +85,7 @@ export const EffectsConfiguration = ({
onSubmit?.(processor)
}
} catch (error) {
console.error('Error applying effect:', error)
console.error('Error applying blur:', error)
} finally {
// Without setTimeout the DOM is not refreshing when updating the options.
setTimeout(() => setProcessorPending(false))
@@ -161,7 +134,6 @@ export const EffectsConfiguration = ({
className={css({
width: '100%',
aspectRatio: 16 / 9,
position: 'relative',
})}
>
{videoTrack && !videoTrack.isMuted ? (
@@ -198,17 +170,6 @@ export const EffectsConfiguration = ({
</P>
</div>
)}
{processorPendingReveal && (
<div
className={css({
position: 'absolute',
right: '8px',
bottom: '8px',
})}
>
<Loader />
</div>
)}
</div>
<div
className={css(
@@ -251,7 +212,7 @@ export const EffectsConfiguration = ({
tooltip={tooltipLabel(ProcessorType.BLUR, {
blurRadius: BlurRadius.LIGHT,
})}
isDisabled={processorPendingReveal}
isDisabled={processorPending}
onChange={async () =>
await toggleEffect(ProcessorType.BLUR, {
blurRadius: BlurRadius.LIGHT,
@@ -271,7 +232,7 @@ export const EffectsConfiguration = ({
tooltip={tooltipLabel(ProcessorType.BLUR, {
blurRadius: BlurRadius.NORMAL,
})}
isDisabled={processorPendingReveal}
isDisabled={processorPending}
onChange={async () =>
await toggleEffect(ProcessorType.BLUR, {
blurRadius: BlurRadius.NORMAL,
@@ -319,7 +280,7 @@ export const EffectsConfiguration = ({
tooltip={tooltipLabel(ProcessorType.VIRTUAL, {
imagePath,
})}
isDisabled={processorPendingReveal}
isDisabled={processorPending}
onChange={async () =>
await toggleEffect(ProcessorType.VIRTUAL, {
imagePath,
@@ -2,32 +2,9 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { useMemo, useState } from 'react'
import { TrackReferenceOrPlaceholder } from '@livekit/components-core'
export function useFullScreen({
trackRef,
}: {
trackRef?: TrackReferenceOrPlaceholder
}) {
const videoElement = useMemo(() => {
const elements = trackRef?.publication?.track?.attachedElements
if (!elements) return
// Find the visible video element
const likeKitElement = elements.find((el) =>
el.classList.contains('lk-participant-media-video')
)
if (!likeKitElement) {
console.warn('Could not find LiveKit-managed video element')
return elements[0] || null
}
return likeKitElement
}, [trackRef])
import { useState } from 'react'
export function useFullScreen() {
const getIsFullscreen = () => {
return !!(
document.fullscreenElement ||
@@ -47,7 +24,7 @@ export function useFullScreen({
const enterFullscreen = async () => {
try {
const docEl = videoElement || document.documentElement
const docEl = document.documentElement
if (docEl.requestFullscreen) {
await docEl.requestFullscreen()
} else if (docEl.webkitRequestFullscreen) {
@@ -19,5 +19,5 @@ export function useRaisedHand({ participant }: useRaisedHandProps) {
}
}
return { isHandRaised: parsedMetadata.raised || false, toggleRaisedHand }
return { isHandRaised: parsedMetadata.raised, toggleRaisedHand }
}
@@ -1,10 +1,12 @@
import { ApiRoom } from '@/features/rooms/api/ApiRoom'
import { useRoomContext } from '@livekit/components-react'
import { useParams } from 'wouter'
import { keys } from '@/api/queryKeys'
import { queryClient } from '@/api/queryClient'
export const useRoomData = (): ApiRoom | undefined => {
const room = useRoomContext()
const { roomId } = useParams()
const queryKey = [keys.room, roomId]
const queryKey = [keys.room, roomId, room.localParticipant.name]
return queryClient.getQueryData<ApiRoom>(queryKey)
}
@@ -1,84 +0,0 @@
import { authUrl, useUser } from '../auth'
export enum ClientMessageType {
ROOM_CREATED = 'ROOM_CREATED',
}
export class SdkReverseClient {
/**
* IDEA: Use API Key. Must be based on some sort of credentials? No needs for now as there are no security
* plausible at the moment.
*/
static getAllowTargetOrigin() {
return '*'
}
static post(type: ClientMessageType, data: unknown = {}) {
window.parent.postMessage(
{
type,
data,
},
SdkReverseClient.getAllowTargetOrigin()
)
}
static broadcastAuthentication() {
const bc = new BroadcastChannel('APP_CHANNEL')
bc.postMessage({ type: 'AUTHENTICATED' })
/**
* This means the parent window has authenticated has successfully refetched user, then we can close the popup.
*/
bc.onmessage = (event) => {
if (event.data.type === 'AUTHENTICATED_ACK') {
window.close()
}
}
}
static waitForAuthenticationAck() {
return new Promise<void>((resolve) => {
const bc = new BroadcastChannel('APP_CHANNEL')
bc.onmessage = async (event) => {
if (event.data.type === 'AUTHENTICATED') {
resolve()
bc.postMessage({ type: 'AUTHENTICATED_ACK' })
}
}
})
}
}
/**
* Returns a function to be awaited in order to make sure the user is logged in.
* If not logged-in it opens a popup with the connection flow, the promise returned is resolved
* once logged-in.
*
* To be used in SDK scope.
*/
export function useEnsureAuth() {
const { isLoggedIn, ...other } = useUser({
fetchUserOptions: { attemptSilent: false },
})
const startSSO = () => {
return new Promise<void>((resolve) => {
SdkReverseClient.waitForAuthenticationAck().then(async () => {
await other.refetch()
resolve()
})
const params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
width=400,height=900,left=100,top=100`
window.open(new URL('authenticate/', authUrl()).href, '', params)
})
}
const ensureAuth = async () => {
if (!isLoggedIn) {
await startSSO()
}
}
return { ensureAuth }
}
@@ -1,103 +0,0 @@
import { Button } from '@/primitives/Button'
import { useTranslation } from 'react-i18next'
import { usePersistentUserChoices } from '@livekit/components-react'
import { useState } from 'react'
import { getRouteUrl } from '@/navigation/getRouteUrl'
import { css } from '@/styled-system/css'
import { RiCheckLine, RiFileCopyLine } from '@remixicon/react'
import { VisioIcon } from '@/assets/VisioIcon'
import { generateRoomId, useCreateRoom } from '../../rooms'
import {
ClientMessageType,
SdkReverseClient,
useEnsureAuth,
} from '../SdkReverseClient'
export const SdkCreateButton = () => {
const { t } = useTranslation('sdk', { keyPrefix: 'createButton' })
const [roomUrl, setRoomUrl] = useState<string>()
const [isLoading, setIsLoading] = useState(false)
const {
userChoices: { username },
} = usePersistentUserChoices()
const { mutateAsync: createRoom } = useCreateRoom()
const { ensureAuth } = useEnsureAuth()
const submitCreateRoom = async () => {
setIsLoading(true)
const slug = generateRoomId()
const data = await createRoom({ slug, username })
const roomUrlTmp = getRouteUrl('room', data.slug)
setRoomUrl(roomUrlTmp)
setIsLoading(false)
SdkReverseClient.post(ClientMessageType.ROOM_CREATED, {
url: roomUrlTmp,
})
}
const submit = async () => {
await ensureAuth()
submitCreateRoom()
}
return (
<div
className={css({
paddingTop: '3px',
paddingLeft: '3px',
})}
>
{roomUrl ? (
<RoomUrl roomUrl={roomUrl} />
) : (
<Button
variant="primaryDark"
aria-label={t('label')}
onPress={submit}
data-attr="sdk-create"
loading={isLoading}
icon={<VisioIcon />}
>
{t('label')}
</Button>
)}
</div>
)
}
const RoomUrl = ({ roomUrl }: { roomUrl: string }) => {
const [isCopied, setIsCopied] = useState(false)
const copy = () => {
navigator.clipboard.writeText(roomUrl!)
setIsCopied(true)
setTimeout(() => setIsCopied(false), 1000)
}
return (
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
})}
>
<span
className={css({
color: 'greyscale.600',
})}
>
{roomUrl}
</span>
<Button
variant={isCopied ? 'success' : 'quaternaryText'}
data-attr="sdk-create-copy"
onPress={copy}
square
>
{isCopied ? <RiCheckLine /> : <RiFileCopyLine />}
</Button>
</div>
)
}
@@ -1,30 +0,0 @@
import { useEffect, useRef, useState } from 'react'
/**
* If value stays truthy for more than waitFor ms, syncValue takes the value of value.
* @param value
* @param waitFor
* @returns
*/
export function useSyncAfterDelay<T>(value: T, waitFor: number = 300) {
const valueRef = useRef(value)
const timeoutRef = useRef<NodeJS.Timeout>()
const [syncValue, setSyncValue] = useState<T>()
useEffect(() => {
valueRef.current = value
if (value) {
if (!timeoutRef.current) {
timeoutRef.current = setTimeout(() => {
setSyncValue(valueRef.current)
timeoutRef.current = undefined
}, waitFor)
}
} else {
setSyncValue(value)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value])
return syncValue
}
@@ -6,11 +6,5 @@
"raised": {
"description": "",
"cta": ""
},
"muted": "",
"openChat": "",
"lowerHand": {
"auto": "",
"dismiss": ""
}
}
-15
View File
@@ -179,7 +179,6 @@
"muteYourself": "",
"muteParticipant": "",
"muteParticipantAlert": {
"heading": "",
"description": "",
"confirm": "",
"cancel": ""
@@ -190,19 +189,5 @@
},
"recording": {
"label": ""
},
"participantTileFocus": {
"pin": {
"enable": "",
"disable": ""
},
"effects": "",
"muteParticipant": "",
"fullScreen": ""
},
"fullScreenWarning": {
"message": "",
"stop": "",
"ignore": ""
}
}
-5
View File
@@ -1,5 +0,0 @@
{
"createButton": {
"label": ""
}
}
@@ -6,11 +6,5 @@
"raised": {
"description": "{{name}} has raised their hand.",
"cta": "Open waiting list"
},
"muted": "{{name}} has muted your microphone. No participant can hear you.",
"openChat": "Open chat",
"lowerHand": {
"auto": "It seems you have started speaking, so your hand will be lowered.",
"dismiss": "Keep hand raised"
}
}
-15
View File
@@ -178,7 +178,6 @@
"muteYourself": "Close your mic",
"muteParticipant": "Close the mic of {{name}}",
"muteParticipantAlert": {
"heading": "Mute {{name}}",
"description": "Mute {{name}} for all participants? {{name}} will be the only one who can unmute themselves.",
"confirm": "Mute",
"cancel": "Cancel"
@@ -189,19 +188,5 @@
},
"recording": {
"label": "Recording"
},
"participantTileFocus": {
"pin": {
"enable": "Pin",
"disable": "Unpin"
},
"effects": "Apply visual effects",
"muteParticipant": "Mute {{name}}",
"fullScreen": "Full screen"
},
"fullScreenWarning": {
"message": "To avoid infinite loop display, do not share your entire screen or browser window. Instead, share a tab or another window.",
"stop": "Stop presenting",
"ignore": "Ignore"
}
}
-5
View File
@@ -1,5 +0,0 @@
{
"createButton": {
"label": "Create a Visio link"
}
}
@@ -6,11 +6,5 @@
"raised": {
"description": "{{name}} a levé la main.",
"cta": "Ouvrir la file d'attente"
},
"muted": "{{name}} a coupé votre micro. Aucun participant ne peut l'entendre.",
"openChat": "Ouvrir le chat",
"lowerHand": {
"auto": "Il semblerait que vous ayez pris la parole, donc la main va être baissée.",
"dismiss": "Laisser la main levée"
}
}
-15
View File
@@ -178,7 +178,6 @@
"muteYourself": "Couper votre micro",
"muteParticipant": "Couper le micro de {{name}}",
"muteParticipantAlert": {
"heading": "Couper le micro de {{name}}",
"description": "Couper le micro de {{name}} pour tous les participants ? {{name}} est la seule personne habilitée à réactiver son micro",
"confirm": "Couper le micro",
"cancel": "Annuler"
@@ -189,19 +188,5 @@
},
"recording": {
"label": "Enregistrement"
},
"participantTileFocus": {
"pin": {
"enable": "Épingler",
"disable": "Annuler l'épinglage"
},
"effects": "Appliquer des effets",
"muteParticipant": "Couper le micro de {{name}}",
"fullScreen": "Plein écran"
},
"fullScreenWarning": {
"message": "Pour éviter l'affichage en boucle infinie, ne partagez pas l'intégralité de votre écran ni de votre fenêtre de navigateur. Partagez plutôt un onglet ou une autre fenêtre.",
"stop": "Arrêter la présentation",
"ignore": "Ignorer"
}
}
-5
View File
@@ -1,5 +0,0 @@
{
"createButton": {
"label": "Créer un lien Visio"
}
}
+1 -1
View File
@@ -41,7 +41,7 @@
},
"settingsButtonLabel": "Paramètres",
"tabs": {
"account": "Profil",
"account": "Profile",
"audio": "Audio",
"general": "Général",
"notifications": "Notifications"
+2 -8
View File
@@ -6,15 +6,12 @@ import { type RecipeVariantProps } from '@/styled-system/css'
import { buttonRecipe, type ButtonRecipe } from './buttonRecipe'
import { TooltipWrapper, type TooltipWrapperProps } from './TooltipWrapper'
import { ReactNode } from 'react'
import { Loader } from './Loader'
export type ButtonProps = RecipeVariantProps<ButtonRecipe> &
RACButtonsProps &
TooltipWrapperProps & {
// Use tooltip as description below the button.
description?: boolean
} & {
icon?: ReactNode
}
export const Button = ({
@@ -23,16 +20,13 @@ export const Button = ({
...props
}: ButtonProps) => {
const [variantProps, componentProps] = buttonRecipe.splitVariantProps(props)
const { className, ...remainingComponentProps } = componentProps
return (
<TooltipWrapper tooltip={tooltip} tooltipType={tooltipType}>
<RACButton
className={[buttonRecipe(variantProps), className].join(' ')}
{...(remainingComponentProps as RACButtonsProps)}
className={buttonRecipe(variantProps)}
{...(componentProps as RACButtonsProps)}
>
{!props.loading && props.icon}
{props.loading && <Loader />}
{componentProps.children as ReactNode}
{props.description && <span>{tooltip}</span>}
</RACButton>
-45
View File
@@ -1,45 +0,0 @@
import { cva } from '@/styled-system/css'
const loader = cva({
base: {
borderRadius: '50%',
position: 'relative',
animation: 'rotate 1s linear infinite',
'&:before, &:after': {
content: '""',
boxSizing: 'border-box',
position: 'absolute',
inset: '0',
borderRadius: '50%',
borderStyle: 'solid',
borderColor: 'white',
},
_before: {
animation: 'prixClipFix 2s linear infinite',
},
_after: {
borderColor: 'white',
animation:
'prixClipFix 2s linear infinite, rotate 0.5s linear infinite reverse',
inset: 6,
},
},
variants: {
size: {
small: {
width: '24px',
height: '24px',
'&:before, &:after': {
borderWidth: '2px',
},
},
},
},
defaultVariants: {
size: 'small',
},
})
export const Loader = () => {
return <div className={loader()}></div>
}
-5
View File
@@ -92,11 +92,6 @@ export const text = cva({
marginBottom: 0.5,
},
},
fullWidth: {
true: {
width: '100%',
},
},
last: {
true: {
marginBottom: '0!',
@@ -8,10 +8,6 @@ export const buttonRecipe = cva({
transition: 'background 200ms, outline 200ms, border-color 200ms',
cursor: 'pointer',
border: '1px solid transparent',
'&[data-disabled]': {
cursor: 'default',
},
gap: '0.5rem',
},
variants: {
size: {
@@ -105,11 +101,6 @@ export const buttonRecipe = cva({
'&[data-selected]': {
boxShadow: 'token(colors.primary.400) 0px 0px 0px 3px inset',
},
'&[data-disabled]': {
backgroundColor: 'greyscale.100',
color: 'greyscale.400',
opacity: '0.7',
},
},
tertiary: {
backgroundColor: 'primary.100',
@@ -177,18 +168,6 @@ export const buttonRecipe = cva({
color: 'primaryDark.100',
},
},
quaternaryText: {
backgroundColor: 'transparent',
color: 'greyscale.600',
'&[data-hovered]': {
backgroundColor: 'greyscale.100',
color: 'greyscale.700',
},
'&[data-pressed]': {
backgroundColor: 'greyscale.100',
color: 'greyscale.700',
},
},
greyscale: {
backgroundColor: 'transparent',
color: 'greyscale.400',
@@ -279,9 +258,6 @@ export const buttonRecipe = cva({
width: 'full',
},
},
loading: {
true: {},
},
// some toggle buttons make more sense without a "pushed button" style when selected because their content changes to mark the state
shySelected: {
true: {},
@@ -1,9 +0,0 @@
import { proxy } from 'valtio'
type State = {
enabled: boolean
}
export const ScreenSharePreferenceStore = proxy<State>({
enabled: true,
})
+1 -16
View File
@@ -14,7 +14,6 @@ const DEFAULT_STATE: State = {
new Map([
[NotificationType.ParticipantJoined, true],
[NotificationType.HandRaised, true],
[NotificationType.MessageReceived, true],
])
),
soundNotificationVolume: 0.1,
@@ -25,21 +24,7 @@ function getNotificationsState(): State {
const stored = localStorage.getItem(STORAGE_KEYS.NOTIFICATIONS)
if (!stored) return DEFAULT_STATE
const parsed = JSON.parse(stored, deserializeToProxyMap)
// Ensure all default notification types exist in the recovered state
return {
...DEFAULT_STATE,
...parsed,
soundNotifications: proxyMap(
new Map(
Array.from(DEFAULT_STATE.soundNotifications.keys()).map((key) => [
key,
parsed.soundNotifications.has(key)
? parsed.soundNotifications.get(key)
: DEFAULT_STATE.soundNotifications.get(key),
])
)
),
}
return parsed || DEFAULT_STATE
} catch (error: unknown) {
console.error(
'[NotificationsStore] Failed to parse stored settings:',
@@ -1,4 +1,3 @@
{{ if .Values.secrets }}
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
@@ -32,4 +31,3 @@ spec:
webhook:
url: "http://bitwarden-cli-{{ $.Release.Namespace }}.{{ $.Release.Namespace }}.svc.cluster.local:8087/object/attachment/{{`{{ .remoteRef.property }}`}}?itemid={{`{{ .remoteRef.key }}`}}"
result: {}
{{ end }}
@@ -1,4 +1,3 @@
{{ if .Values.secrets }}
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
@@ -27,4 +26,3 @@ spec:
key: {{ .itemId }}
property: {{ .field }}
{{- end }}
{{ end }}
@@ -1,4 +1,3 @@
{{ if .Values.secrets }}
---
apiVersion: apps/v1
kind: Deployment
@@ -91,4 +90,3 @@ spec:
selector:
app.kubernetes.io/name: bitwarden-cli
app.kubernetes.io/instance: bitwarden-cli
{{ end }}
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: v2
type: application
name: meet
version: 0.0.3
version: 0.0.1
-4
View File
@@ -82,7 +82,6 @@
| `backend.persistence.volume-name.mountPath` | Path where the volume should be mounted to | |
| `backend.extraVolumeMounts` | Additional volumes to mount on the backend. | `[]` |
| `backend.extraVolumes` | Additional volumes to mount on the backend. | `[]` |
| `backend.pdb.enabled` | Enable pdb on backend | `true` |
### frontend
@@ -132,7 +131,6 @@
| `frontend.persistence.volume-name.mountPath` | Path where the volume should be mounted to | |
| `frontend.extraVolumeMounts` | Additional volumes to mount on the frontend. | `[]` |
| `frontend.extraVolumes` | Additional volumes to mount on the frontend. | `[]` |
| `frontend.pdb.enabled` | Enable pdb on frontend | `true` |
### posthog
@@ -210,7 +208,6 @@
| `summary.persistence.volume-name.mountPath` | Path where the volume should be mounted to | |
| `summary.extraVolumeMounts` | Additional volumes to mount on the summary. | `[]` |
| `summary.extraVolumes` | Additional volumes to mount on the summary. | `[]` |
| `summary.pdb.enabled` | Enable pdb on summary | `true` |
### celery
@@ -258,4 +255,3 @@
| `celery.persistence.volume-name.mountPath` | Path where the volume should be mounted to | |
| `celery.extraVolumeMounts` | Additional volumes to mount on the celery. | `[]` |
| `celery.extraVolumes` | Additional volumes to mount on the celery. | `[]` |
| `celery.pdb.enabled` | Enable pdb on celery | `false` |
@@ -138,16 +138,3 @@ spec:
emptyDir: {}
{{- end }}
{{- end }}
---
{{ if .Values.backend.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
@@ -138,16 +138,3 @@ spec:
emptyDir: {}
{{- end }}
{{- end }}
---
{{ if .Values.celery.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
@@ -138,16 +138,3 @@ spec:
emptyDir: {}
{{- end }}
{{- end }}
---
{{ if .Values.frontend.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
@@ -138,16 +138,3 @@ spec:
emptyDir: {}
{{- end }}
{{- end }}
---
{{ if .Values.summary.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
+4 -17
View File
@@ -120,8 +120,8 @@ backend:
targetPort: 8000
annotations: {}
## @param backend.migrate.command backend migrate command
## @param backend.migrate.restartPolicy backend migrate job restart policy
## @param backend.migrate.command backend migrate command
## @param backend.migrate.restartPolicy backend migrate job restart policy
migrate:
command:
- "python"
@@ -130,8 +130,8 @@ backend:
- "--no-input"
restartPolicy: Never
## @param backend.createsuperuser.command backend migrate command
## @param backend.createsuperuser.restartPolicy backend migrate job restart policy
## @param backend.createsuperuser.command backend migrate command
## @param backend.createsuperuser.restartPolicy backend migrate job restart policy
createsuperuser:
command:
- "/bin/sh"
@@ -184,9 +184,6 @@ backend:
## @param backend.extraVolumes Additional volumes to mount on the backend.
extraVolumes: []
## @param backend.pdb.enabled Enable pdb on backend
pdb:
enabled: true
## @section frontend
@@ -282,10 +279,6 @@ frontend:
## @param frontend.extraVolumes Additional volumes to mount on the frontend.
extraVolumes: []
## @param frontend.pdb.enabled Enable pdb on frontend
pdb:
enabled: true
## @section posthog
posthog:
@@ -450,9 +443,6 @@ summary:
## @param summary.extraVolumes Additional volumes to mount on the summary.
extraVolumes: []
## @param summary.pdb.enabled Enable pdb on summary
pdb:
enabled: true
## @section celery
@@ -543,6 +533,3 @@ celery:
## @param celery.extraVolumes Additional volumes to mount on the celery.
extraVolumes: []
## @param celery.pdb.enabled Enable pdb on celery
pdb:
enabled: false
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mail_mjml",
"version": "0.1.13",
"version": "0.1.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mail_mjml",
"version": "0.1.13",
"version": "0.1.12",
"license": "MIT",
"dependencies": {
"@html-to/text-cli": "0.5.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "0.1.13",
"version": "0.1.12",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
-24
View File
@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
@@ -1,31 +0,0 @@
{
"hash": "d05ec1ac",
"configHash": "da92b5ff",
"lockfileHash": "f357bfbf",
"browserHash": "63541641",
"optimized": {
"react/jsx-dev-runtime": {
"src": "../../../node_modules/react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js",
"fileHash": "b16b7fd8",
"needsInterop": true
},
"react": {
"src": "../../../node_modules/react/index.js",
"file": "react.js",
"fileHash": "a954da71",
"needsInterop": true
},
"react-dom/client": {
"src": "../../../node_modules/react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "cfb7ce0f",
"needsInterop": true
}
},
"chunks": {
"chunk-DEZHIYJM": {
"file": "chunk-DEZHIYJM.js"
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-3
View File
@@ -1,3 +0,0 @@
{
"type": "module"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-4
View File
@@ -1,4 +0,0 @@
import {
require_react
} from "./chunk-DEZHIYJM.js";
export default require_react();
-7
View File
@@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}
@@ -1,911 +0,0 @@
import {
__commonJS,
require_react
} from "./chunk-DEZHIYJM.js";
// ../node_modules/react/cjs/react-jsx-dev-runtime.development.js
var require_react_jsx_dev_runtime_development = __commonJS({
"../node_modules/react/cjs/react-jsx-dev-runtime.development.js"(exports) {
"use strict";
if (true) {
(function() {
"use strict";
var React = require_react();
var REACT_ELEMENT_TYPE = Symbol.for("react.element");
var REACT_PORTAL_TYPE = Symbol.for("react.portal");
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_PROVIDER_TYPE = Symbol.for("react.provider");
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
var REACT_MEMO_TYPE = Symbol.for("react.memo");
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen");
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
var FAUX_ITERATOR_SYMBOL = "@@iterator";
function getIteratorFn(maybeIterable) {
if (maybeIterable === null || typeof maybeIterable !== "object") {
return null;
}
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
if (typeof maybeIterator === "function") {
return maybeIterator;
}
return null;
}
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
function error(format) {
{
{
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
printWarning("error", format, args);
}
}
}
function printWarning(level, format, args) {
{
var ReactDebugCurrentFrame2 = ReactSharedInternals.ReactDebugCurrentFrame;
var stack = ReactDebugCurrentFrame2.getStackAddendum();
if (stack !== "") {
format += "%s";
args = args.concat([stack]);
}
var argsWithFormat = args.map(function(item) {
return String(item);
});
argsWithFormat.unshift("Warning: " + format);
Function.prototype.apply.call(console[level], console, argsWithFormat);
}
}
var enableScopeAPI = false;
var enableCacheElement = false;
var enableTransitionTracing = false;
var enableLegacyHidden = false;
var enableDebugTracing = false;
var REACT_MODULE_REFERENCE;
{
REACT_MODULE_REFERENCE = Symbol.for("react.module.reference");
}
function isValidElementType(type) {
if (typeof type === "string" || typeof type === "function") {
return true;
}
if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing) {
return true;
}
if (typeof type === "object" && type !== null) {
if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
// with.
type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== void 0) {
return true;
}
}
return false;
}
function getWrappedName(outerType, innerType, wrapperName) {
var displayName = outerType.displayName;
if (displayName) {
return displayName;
}
var functionName = innerType.displayName || innerType.name || "";
return functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName;
}
function getContextName(type) {
return type.displayName || "Context";
}
function getComponentNameFromType(type) {
if (type == null) {
return null;
}
{
if (typeof type.tag === "number") {
error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.");
}
}
if (typeof type === "function") {
return type.displayName || type.name || null;
}
if (typeof type === "string") {
return type;
}
switch (type) {
case REACT_FRAGMENT_TYPE:
return "Fragment";
case REACT_PORTAL_TYPE:
return "Portal";
case REACT_PROFILER_TYPE:
return "Profiler";
case REACT_STRICT_MODE_TYPE:
return "StrictMode";
case REACT_SUSPENSE_TYPE:
return "Suspense";
case REACT_SUSPENSE_LIST_TYPE:
return "SuspenseList";
}
if (typeof type === "object") {
switch (type.$$typeof) {
case REACT_CONTEXT_TYPE:
var context = type;
return getContextName(context) + ".Consumer";
case REACT_PROVIDER_TYPE:
var provider = type;
return getContextName(provider._context) + ".Provider";
case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, "ForwardRef");
case REACT_MEMO_TYPE:
var outerName = type.displayName || null;
if (outerName !== null) {
return outerName;
}
return getComponentNameFromType(type.type) || "Memo";
case REACT_LAZY_TYPE: {
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return getComponentNameFromType(init(payload));
} catch (x) {
return null;
}
}
}
}
return null;
}
var assign = Object.assign;
var disabledDepth = 0;
var prevLog;
var prevInfo;
var prevWarn;
var prevError;
var prevGroup;
var prevGroupCollapsed;
var prevGroupEnd;
function disabledLog() {
}
disabledLog.__reactDisabledLog = true;
function disableLogs() {
{
if (disabledDepth === 0) {
prevLog = console.log;
prevInfo = console.info;
prevWarn = console.warn;
prevError = console.error;
prevGroup = console.group;
prevGroupCollapsed = console.groupCollapsed;
prevGroupEnd = console.groupEnd;
var props = {
configurable: true,
enumerable: true,
value: disabledLog,
writable: true
};
Object.defineProperties(console, {
info: props,
log: props,
warn: props,
error: props,
group: props,
groupCollapsed: props,
groupEnd: props
});
}
disabledDepth++;
}
}
function reenableLogs() {
{
disabledDepth--;
if (disabledDepth === 0) {
var props = {
configurable: true,
enumerable: true,
writable: true
};
Object.defineProperties(console, {
log: assign({}, props, {
value: prevLog
}),
info: assign({}, props, {
value: prevInfo
}),
warn: assign({}, props, {
value: prevWarn
}),
error: assign({}, props, {
value: prevError
}),
group: assign({}, props, {
value: prevGroup
}),
groupCollapsed: assign({}, props, {
value: prevGroupCollapsed
}),
groupEnd: assign({}, props, {
value: prevGroupEnd
})
});
}
if (disabledDepth < 0) {
error("disabledDepth fell below zero. This is a bug in React. Please file an issue.");
}
}
}
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
var prefix;
function describeBuiltInComponentFrame(name, source, ownerFn) {
{
if (prefix === void 0) {
try {
throw Error();
} catch (x) {
var match = x.stack.trim().match(/\n( *(at )?)/);
prefix = match && match[1] || "";
}
}
return "\n" + prefix + name;
}
}
var reentry = false;
var componentFrameCache;
{
var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map;
componentFrameCache = new PossiblyWeakMap();
}
function describeNativeComponentFrame(fn, construct) {
if (!fn || reentry) {
return "";
}
{
var frame = componentFrameCache.get(fn);
if (frame !== void 0) {
return frame;
}
}
var control;
reentry = true;
var previousPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = void 0;
var previousDispatcher;
{
previousDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = null;
disableLogs();
}
try {
if (construct) {
var Fake = function() {
throw Error();
};
Object.defineProperty(Fake.prototype, "props", {
set: function() {
throw Error();
}
});
if (typeof Reflect === "object" && Reflect.construct) {
try {
Reflect.construct(Fake, []);
} catch (x) {
control = x;
}
Reflect.construct(fn, [], Fake);
} else {
try {
Fake.call();
} catch (x) {
control = x;
}
fn.call(Fake.prototype);
}
} else {
try {
throw Error();
} catch (x) {
control = x;
}
fn();
}
} catch (sample) {
if (sample && control && typeof sample.stack === "string") {
var sampleLines = sample.stack.split("\n");
var controlLines = control.stack.split("\n");
var s = sampleLines.length - 1;
var c = controlLines.length - 1;
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
c--;
}
for (; s >= 1 && c >= 0; s--, c--) {
if (sampleLines[s] !== controlLines[c]) {
if (s !== 1 || c !== 1) {
do {
s--;
c--;
if (c < 0 || sampleLines[s] !== controlLines[c]) {
var _frame = "\n" + sampleLines[s].replace(" at new ", " at ");
if (fn.displayName && _frame.includes("<anonymous>")) {
_frame = _frame.replace("<anonymous>", fn.displayName);
}
{
if (typeof fn === "function") {
componentFrameCache.set(fn, _frame);
}
}
return _frame;
}
} while (s >= 1 && c >= 0);
}
break;
}
}
}
} finally {
reentry = false;
{
ReactCurrentDispatcher.current = previousDispatcher;
reenableLogs();
}
Error.prepareStackTrace = previousPrepareStackTrace;
}
var name = fn ? fn.displayName || fn.name : "";
var syntheticFrame = name ? describeBuiltInComponentFrame(name) : "";
{
if (typeof fn === "function") {
componentFrameCache.set(fn, syntheticFrame);
}
}
return syntheticFrame;
}
function describeFunctionComponentFrame(fn, source, ownerFn) {
{
return describeNativeComponentFrame(fn, false);
}
}
function shouldConstruct(Component) {
var prototype = Component.prototype;
return !!(prototype && prototype.isReactComponent);
}
function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) {
if (type == null) {
return "";
}
if (typeof type === "function") {
{
return describeNativeComponentFrame(type, shouldConstruct(type));
}
}
if (typeof type === "string") {
return describeBuiltInComponentFrame(type);
}
switch (type) {
case REACT_SUSPENSE_TYPE:
return describeBuiltInComponentFrame("Suspense");
case REACT_SUSPENSE_LIST_TYPE:
return describeBuiltInComponentFrame("SuspenseList");
}
if (typeof type === "object") {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE:
return describeFunctionComponentFrame(type.render);
case REACT_MEMO_TYPE:
return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
case REACT_LAZY_TYPE: {
var lazyComponent = type;
var payload = lazyComponent._payload;
var init = lazyComponent._init;
try {
return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn);
} catch (x) {
}
}
}
}
return "";
}
var hasOwnProperty = Object.prototype.hasOwnProperty;
var loggedTypeFailures = {};
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame.setExtraStackFrame(null);
}
}
}
function checkPropTypes(typeSpecs, values, location, componentName, element) {
{
var has = Function.call.bind(hasOwnProperty);
for (var typeSpecName in typeSpecs) {
if (has(typeSpecs, typeSpecName)) {
var error$1 = void 0;
try {
if (typeof typeSpecs[typeSpecName] !== "function") {
var err = Error((componentName || "React class") + ": " + location + " type `" + typeSpecName + "` is invalid; it must be a function, usually from the `prop-types` package, but received `" + typeof typeSpecs[typeSpecName] + "`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");
err.name = "Invariant Violation";
throw err;
}
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
} catch (ex) {
error$1 = ex;
}
if (error$1 && !(error$1 instanceof Error)) {
setCurrentlyValidatingElement(element);
error("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).", componentName || "React class", location, typeSpecName, typeof error$1);
setCurrentlyValidatingElement(null);
}
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
loggedTypeFailures[error$1.message] = true;
setCurrentlyValidatingElement(element);
error("Failed %s type: %s", location, error$1.message);
setCurrentlyValidatingElement(null);
}
}
}
}
}
var isArrayImpl = Array.isArray;
function isArray(a) {
return isArrayImpl(a);
}
function typeName(value) {
{
var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag;
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object";
return type;
}
}
function willCoercionThrow(value) {
{
try {
testStringCoercion(value);
return false;
} catch (e) {
return true;
}
}
}
function testStringCoercion(value) {
return "" + value;
}
function checkKeyStringCoercion(value) {
{
if (willCoercionThrow(value)) {
error("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value));
return testStringCoercion(value);
}
}
}
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
var RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true
};
var specialPropKeyWarningShown;
var specialPropRefWarningShown;
var didWarnAboutStringRefs;
{
didWarnAboutStringRefs = {};
}
function hasValidRef(config) {
{
if (hasOwnProperty.call(config, "ref")) {
var getter = Object.getOwnPropertyDescriptor(config, "ref").get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== void 0;
}
function hasValidKey(config) {
{
if (hasOwnProperty.call(config, "key")) {
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.key !== void 0;
}
function warnIfStringRefCannotBeAutoConverted(config, self) {
{
if (typeof config.ref === "string" && ReactCurrentOwner.current && self && ReactCurrentOwner.current.stateNode !== self) {
var componentName = getComponentNameFromType(ReactCurrentOwner.current.type);
if (!didWarnAboutStringRefs[componentName]) {
error('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', getComponentNameFromType(ReactCurrentOwner.current.type), config.ref);
didWarnAboutStringRefs[componentName] = true;
}
}
}
}
function defineKeyPropWarningGetter(props, displayName) {
{
var warnAboutAccessingKey = function() {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, "key", {
get: warnAboutAccessingKey,
configurable: true
});
}
}
function defineRefPropWarningGetter(props, displayName) {
{
var warnAboutAccessingRef = function() {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
error("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
}
}
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type,
key,
ref,
props,
// Record the component responsible for creating this element.
_owner: owner
};
{
element._store = {};
Object.defineProperty(element._store, "validated", {
configurable: false,
enumerable: false,
writable: true,
value: false
});
Object.defineProperty(element, "_self", {
configurable: false,
enumerable: false,
writable: false,
value: self
});
Object.defineProperty(element, "_source", {
configurable: false,
enumerable: false,
writable: false,
value: source
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
function jsxDEV(type, config, maybeKey, source, self) {
{
var propName;
var props = {};
var key = null;
var ref = null;
if (maybeKey !== void 0) {
{
checkKeyStringCoercion(maybeKey);
}
key = "" + maybeKey;
}
if (hasValidKey(config)) {
{
checkKeyStringCoercion(config.key);
}
key = "" + config.key;
}
if (hasValidRef(config)) {
ref = config.ref;
warnIfStringRefCannotBeAutoConverted(config, self);
}
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === void 0) {
props[propName] = defaultProps[propName];
}
}
}
if (key || ref) {
var displayName = typeof type === "function" ? type.displayName || type.name || "Unknown" : type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
}
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;
function setCurrentlyValidatingElement$1(element) {
{
if (element) {
var owner = element._owner;
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
ReactDebugCurrentFrame$1.setExtraStackFrame(stack);
} else {
ReactDebugCurrentFrame$1.setExtraStackFrame(null);
}
}
}
var propTypesMisspellWarningShown;
{
propTypesMisspellWarningShown = false;
}
function isValidElement(object) {
{
return typeof object === "object" && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
}
}
function getDeclarationErrorAddendum() {
{
if (ReactCurrentOwner$1.current) {
var name = getComponentNameFromType(ReactCurrentOwner$1.current.type);
if (name) {
return "\n\nCheck the render method of `" + name + "`.";
}
}
return "";
}
}
function getSourceInfoErrorAddendum(source) {
{
if (source !== void 0) {
var fileName = source.fileName.replace(/^.*[\\\/]/, "");
var lineNumber = source.lineNumber;
return "\n\nCheck your code at " + fileName + ":" + lineNumber + ".";
}
return "";
}
}
var ownerHasKeyUseWarning = {};
function getCurrentComponentErrorInfo(parentType) {
{
var info = getDeclarationErrorAddendum();
if (!info) {
var parentName = typeof parentType === "string" ? parentType : parentType.displayName || parentType.name;
if (parentName) {
info = "\n\nCheck the top-level render call using <" + parentName + ">.";
}
}
return info;
}
}
function validateExplicitKey(element, parentType) {
{
if (!element._store || element._store.validated || element.key != null) {
return;
}
element._store.validated = true;
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
return;
}
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
var childOwner = "";
if (element && element._owner && element._owner !== ReactCurrentOwner$1.current) {
childOwner = " It was passed a child from " + getComponentNameFromType(element._owner.type) + ".";
}
setCurrentlyValidatingElement$1(element);
error('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);
setCurrentlyValidatingElement$1(null);
}
}
function validateChildKeys(node, parentType) {
{
if (typeof node !== "object") {
return;
}
if (isArray(node)) {
for (var i = 0; i < node.length; i++) {
var child = node[i];
if (isValidElement(child)) {
validateExplicitKey(child, parentType);
}
}
} else if (isValidElement(node)) {
if (node._store) {
node._store.validated = true;
}
} else if (node) {
var iteratorFn = getIteratorFn(node);
if (typeof iteratorFn === "function") {
if (iteratorFn !== node.entries) {
var iterator = iteratorFn.call(node);
var step;
while (!(step = iterator.next()).done) {
if (isValidElement(step.value)) {
validateExplicitKey(step.value, parentType);
}
}
}
}
}
}
}
function validatePropTypes(element) {
{
var type = element.type;
if (type === null || type === void 0 || typeof type === "string") {
return;
}
var propTypes;
if (typeof type === "function") {
propTypes = type.propTypes;
} else if (typeof type === "object" && (type.$$typeof === REACT_FORWARD_REF_TYPE || // Note: Memo only checks outer props here.
// Inner props are checked in the reconciler.
type.$$typeof === REACT_MEMO_TYPE)) {
propTypes = type.propTypes;
} else {
return;
}
if (propTypes) {
var name = getComponentNameFromType(type);
checkPropTypes(propTypes, element.props, "prop", name, element);
} else if (type.PropTypes !== void 0 && !propTypesMisspellWarningShown) {
propTypesMisspellWarningShown = true;
var _name = getComponentNameFromType(type);
error("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?", _name || "Unknown");
}
if (typeof type.getDefaultProps === "function" && !type.getDefaultProps.isReactClassApproved) {
error("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.");
}
}
}
function validateFragmentProps(fragment) {
{
var keys = Object.keys(fragment.props);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key !== "children" && key !== "key") {
setCurrentlyValidatingElement$1(fragment);
error("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.", key);
setCurrentlyValidatingElement$1(null);
break;
}
}
if (fragment.ref !== null) {
setCurrentlyValidatingElement$1(fragment);
error("Invalid attribute `ref` supplied to `React.Fragment`.");
setCurrentlyValidatingElement$1(null);
}
}
}
var didWarnAboutKeySpread = {};
function jsxWithValidation(type, props, key, isStaticChildren, source, self) {
{
var validType = isValidElementType(type);
if (!validType) {
var info = "";
if (type === void 0 || typeof type === "object" && type !== null && Object.keys(type).length === 0) {
info += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.";
}
var sourceInfo = getSourceInfoErrorAddendum(source);
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}
var typeString;
if (type === null) {
typeString = "null";
} else if (isArray(type)) {
typeString = "array";
} else if (type !== void 0 && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = "<" + (getComponentNameFromType(type.type) || "Unknown") + " />";
info = " Did you accidentally export a JSX literal instead of a component?";
} else {
typeString = typeof type;
}
error("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s", typeString, info);
}
var element = jsxDEV(type, props, key, source, self);
if (element == null) {
return element;
}
if (validType) {
var children = props.children;
if (children !== void 0) {
if (isStaticChildren) {
if (isArray(children)) {
for (var i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
if (Object.freeze) {
Object.freeze(children);
}
} else {
error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");
}
} else {
validateChildKeys(children, type);
}
}
}
{
if (hasOwnProperty.call(props, "key")) {
var componentName = getComponentNameFromType(type);
var keys = Object.keys(props).filter(function(k) {
return k !== "key";
});
var beforeExample = keys.length > 0 ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}";
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
var afterExample = keys.length > 0 ? "{" + keys.join(": ..., ") + ": ...}" : "{}";
error('A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />', beforeExample, componentName, afterExample, componentName);
didWarnAboutKeySpread[componentName + beforeExample] = true;
}
}
}
if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}
return element;
}
}
var jsxDEV$1 = jsxWithValidation;
exports.Fragment = REACT_FRAGMENT_TYPE;
exports.jsxDEV = jsxDEV$1;
})();
}
}
});
// ../node_modules/react/jsx-dev-runtime.js
var require_jsx_dev_runtime = __commonJS({
"../node_modules/react/jsx-dev-runtime.js"(exports, module) {
if (false) {
module.exports = null;
} else {
module.exports = require_react_jsx_dev_runtime_development();
}
}
});
export default require_jsx_dev_runtime();
/*! Bundled license information:
react/cjs/react-jsx-dev-runtime.development.js:
(**
* @license React
* react-jsx-dev-runtime.development.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=react_jsx-dev-runtime.js.map
File diff suppressed because one or more lines are too long
-50
View File
@@ -1,50 +0,0 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
```js
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```
-28
View File
@@ -1,28 +0,0 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Visio SDK Demo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
-3231
View File
File diff suppressed because it is too large Load Diff
-29
View File
@@ -1,29 +0,0 @@
{
"name": "consumer",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"@eslint/js": "9.17.0",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"@vitejs/plugin-react": "4.3.4",
"eslint": "8.57.0",
"eslint-plugin-react-hooks": "5.1.0",
"eslint-plugin-react-refresh": "0.4.18",
"globals": "15.14.0",
"typescript": "5.7.3",
"typescript-eslint": "8.18.2",
"vite": "6.0.11"
}
}
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-5
View File
@@ -1,5 +0,0 @@
#root {
margin: 0 auto;
text-align: center;
}
-42
View File
@@ -1,42 +0,0 @@
import { VisioCreateButton } from "@gouvfr-lasuite/visio-sdk";
import "./App.css";
import { useState } from "react";
function App() {
const [roomUrl, setRoomUrl] = useState("");
return (
<form className="form">
<div className="header">
<h2>Create event</h2>
<span>Visio demo app</span>
</div>
<div className="group">
<label>Subject</label>
<input type="text" />
</div>
<div className="group">
<label>Place</label>
<input type="text" />
</div>
<div className="group">
<label>Visioconference</label>
<VisioCreateButton onRoomCreated={setRoomUrl} />
</div>
<div className="group">
<label>Description</label>
<textarea />
</div>
<button
type="button"
onClick={() => {
alert("Room url: " + roomUrl);
}}
>
Create
</button>
</form>
);
}
export default App;
-74
View File
@@ -1,74 +0,0 @@
* {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
box-sizing: border-box;
color: rgb(9, 9, 11);
}
body {
background-color: oklch(0.985 0 0);
display: flex;
justify-content: center;
padding-top: 2rem;
}
h2 {
margin: 0;
}
span {
font-size: 14px;
color: oklch(0.372 0.044 257.287);
}
.form {
display: flex;
flex-direction: column;
width: 500px;
gap: 1rem;
border: 1px solid #e5e7eb;
background-color: white;
border-radius: 1rem;
padding: 3rem;
}
.header {
margin-bottom: 2rem;
}
.group {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
label {
color: rgb(9, 9, 11);
font-weight: 500;
font-size: 14px;
}
input,
textarea {
border: 1px rgb(228, 228, 231) solid;
border-radius: 6px;
height: 36px;
width: 100%;
padding: 0.75rem;
}
textarea {
height: 200px;
}
button {
background-color: rgb(45, 45, 70);
color: white;
height: 46px;
border-radius: 4px;
font-size: 16px;
font-weight: 300;
cursor: pointer;
}
-10
View File
@@ -1,10 +0,0 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
-1
View File
@@ -1 +0,0 @@
/// <reference types="vite/client" />
-26
View File
@@ -1,26 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
-7
View File
@@ -1,7 +0,0 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
-24
View File
@@ -1,24 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
-7
View File
@@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})
-8
View File
@@ -1,8 +0,0 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
-11
View File
@@ -1,11 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
-1
View File
@@ -1 +0,0 @@
VITE_VISIO_SDK_URL=https://visio.numerique.gouv.fr/sdk
-21
View File
@@ -1,21 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"plugin:@tanstack/eslint-plugin-query/recommended",
"plugin:jsx-a11y/recommended",
"prettier",
],
ignorePatterns: ["dist", ".eslintrc.cjs", "styled-system"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
};
-24
View File
@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
-5
View File
@@ -1,5 +0,0 @@
{
"semi": false,
"trailingComma": "es5",
"singleQuote": true
}
-7
View File
@@ -1,7 +0,0 @@
# @gouvfr-lasuite/visio-sdk
## 0.0.1
### Major Changes
- Create library with VisioCreateButton

Some files were not shown because too many files have changed in this diff Show More