diff --git a/app/assets/locales/de.json b/app/assets/locales/de.json index db75245b42..35285919f4 100644 --- a/app/assets/locales/de.json +++ b/app/assets/locales/de.json @@ -18,6 +18,7 @@ "close": "Schließen", "delete": "Löschen", "copy": "Einladungslink kopieren", + "copy_voice_bridge": "Telefoneinwahl kopieren", "copy_viewer_code": "Zugangscode für Zuhörerende kopieren", "copy_moderator_code": "Zugangscode für Moderation kopieren", "or": "oder", @@ -126,7 +127,14 @@ "meeting_invitation": "Du wurdest zur Teilnahme eingeladen", "meeting_not_started": "Die Konferenz hat noch nicht begonnen", "join_meeting_automatically": "Beitritt erfolgt automatisch, sobald die Konferenz beginnt", - "recording_consent": "Ich nehme zur Kenntnis, dass die Sitzung aufgezeichnet werden kann. Dies wird meine Stimme und mein Bild einschließen, wenn dies aktiviert ist." + "recording_consent": "Ich nehme zur Kenntnis, dass die Sitzung aufgezeichnet werden kann. Dies wird meine Stimme und mein Bild einschließen, wenn dies aktiviert ist.", + "invite_to_meeting": "{{ name }} lädt Sie ein, an einem Meeting teilzunehmen.", + "join_by_url": "An Besprechung per URL teilnehmen", + "alternative_options": "Alternative Optionen zur Teilnahme", + "download_ics": ".ics-Kalenderdatei herunterladen", + "share_meeting": "Raum teilen", + "join_by_phone": "Mit Telefoneinwahl beitreten", + "location": "Videokonferenz, siehe Details im Termin" }, "presentation": { "presentation": "Präsentation", @@ -418,6 +426,8 @@ "access_code_generated": "Zugangscode generiert.", "access_code_deleted": "Zugriffcode wurde gelöscht.", "copied_meeting_url": "Die URL der Konferenz wurde kopiert. Der Link kann verwendet werden, um an der Konferenz teilzunehmen.", + "download_ics": "Die .ics-Datei wurde erstellt. Öffnen Sie sie, um sie zu bearbeiten und zu versenden, um Personen zu Ihrem Meeting einzuladen.", + "copied_voice_bridge": "Die Telefonnummer und der Pin wurden kopiert. Diese können genutzt werden um an der Konferenz teilzunehmen.", "copied_viewer_code": "Zugangscode für Zuhörende wurde in die Zwischenablage kopiert.", "copied_moderator_code": "Zugangscode für Moderation wurde in die Zwischenablage kopiert." }, diff --git a/app/assets/locales/en.json b/app/assets/locales/en.json index 802f8d24fd..c4b27308b9 100644 --- a/app/assets/locales/en.json +++ b/app/assets/locales/en.json @@ -18,6 +18,7 @@ "close": "Close", "delete": "Delete", "copy": "Copy Join Link", + "copy_voice_bridge": "Copy Phone Dialup", "copy_viewer_code": "Copy Viewer Code", "copy_moderator_code": "Copy Moderator Code", "or": "Or", @@ -126,7 +127,14 @@ "meeting_invitation": "You have been invited to join", "meeting_not_started": "The meeting has not started yet", "join_meeting_automatically": "You will automatically join when the meeting starts", - "recording_consent": "I acknowledge that this session may be recorded. This may include my voice and video if enabled." + "recording_consent": "I acknowledge that this session may be recorded. This may include my voice and video if enabled.", + "invite_to_meeting": "{{ name }} invites you to join a meeting.", + "join_by_url": "Join room by URL", + "alternative_options": "Alternative options to join", + "download_ics": "Download .ics calender file", + "share_meeting": "Share Room", + "join_by_phone": "Connect via telephone dial-up", + "location": "Video conference - Further details are provided below" }, "presentation": { "presentation": "Presentation", @@ -418,6 +426,8 @@ "access_code_generated": "A new access code has been generated.", "access_code_deleted": "The access code has been deleted.", "copied_meeting_url": "The meeting URL has been copied. The link can be used to join the meeting.", + "download_ics": "The .ics file has been created. Open it to edit and send it, to invite people to your Meeting.", + "copied_voice_bridge": "The phone number and pin have been copied. They can be used to join the conference.", "copied_viewer_code": "The viewer access code has been copied.", "copied_moderator_code": "The moderator access code has been copied." }, diff --git a/app/controllers/api/v1/env_controller.rb b/app/controllers/api/v1/env_controller.rb index 4982b208e0..f4fc62c95c 100644 --- a/app/controllers/api/v1/env_controller.rb +++ b/app/controllers/api/v1/env_controller.rb @@ -29,7 +29,8 @@ def index HCAPTCHA_KEY: ENV.fetch('HCAPTCHA_SITE_KEY', nil), VERSION_TAG: ENV.fetch('VERSION_TAG', ''), CURRENT_PROVIDER: current_provider, - SMTP_ENABLED: ENV.fetch('SMTP_SERVER', nil) + SMTP_ENABLED: ENV.fetch('SMTP_SERVER', nil), + ICS_USE_HTML: ENV.fetch('ICS_USE_HTML', false) }, status: :ok end end diff --git a/app/javascript/components/rooms/RoomCard.jsx b/app/javascript/components/rooms/RoomCard.jsx index 5d62c1b4bb..33603b69e6 100644 --- a/app/javascript/components/rooms/RoomCard.jsx +++ b/app/javascript/components/rooms/RoomCard.jsx @@ -18,8 +18,7 @@ import React, { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { Button, Card, Stack } from 'react-bootstrap'; import PropTypes from 'prop-types'; -import { DocumentDuplicateIcon, LinkIcon } from '@heroicons/react/24/outline'; -import { toast } from 'react-toastify'; +import { ShareIcon, LinkIcon } from '@heroicons/react/24/outline'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../../contexts/auth/AuthProvider'; import { localizeDateTimeString } from '../../helpers/DateTimeHelper'; @@ -27,6 +26,9 @@ import Spinner from '../shared_components/utilities/Spinner'; import useStartMeeting from '../../hooks/mutations/rooms/useStartMeeting'; import MeetingBadges from './MeetingBadges'; import UserBoardIcon from './UserBoardIcon'; +import Modal from '../shared_components/modals/Modal'; +import ShareRoomForm from './room/forms/ShareRoomForm'; +import useRoomSettings from '../../hooks/queries/rooms/useRoomSettings'; export default function RoomCard({ room }) { const { t } = useTranslation(); @@ -35,47 +37,50 @@ export default function RoomCard({ room }) { const startMeeting = useStartMeeting(room.friendly_id); const currentUser = useAuth(); const localizedTime = localizeDateTimeString(room?.last_session, currentUser?.language); - - function copyInvite(friendlyId) { - navigator.clipboard.writeText(`${window.location}/${friendlyId}/join`); - toast.success(t('toast.success.room.copied_meeting_url')); - } + const roomSettings = useRoomSettings(room.friendly_id); return (
- { room?.shared_owner + {room?.shared_owner ? : }
- { room?.online + {room?.online && }
- { room.name } - { room.shared_owner && ( - { t('room.shared_by') } {' '} { room.shared_owner } + {room.name} + {room.shared_owner && ( + {t('room.shared_by')} {' '} {room.shared_owner} )} - { room.last_session ? ( - { t('room.last_session', { localizedTime }) } + {room.last_session ? ( + {t('room.last_session', { localizedTime })} ) : ( - { t('room.no_last_session') } + {t('room.no_last_session')} )}
- + + + + )} + title={t('room.meeting.share_meeting')} + body={} + /> + - - - - { (roomSettings?.data?.glModeratorAccessCode || roomSettings?.data?.glViewerAccessCode) && ( - + className="mt-1 mx-2 float-end" + type="button" + > + + {t('room.meeting.share_meeting')} + )} - - - { roomSettings?.data?.glModeratorAccessCode && ( - copyInvite('moderator')}> - { t('copy_moderator_code') } - - )} - { roomSettings?.data?.glViewerAccessCode && ( - copyInvite('viewer')}> - { t('copy_viewer_code') } - - )} - - + title={t('room.meeting.share_meeting')} + body={} + /> diff --git a/app/javascript/components/rooms/room/forms/ShareRoomForm.jsx b/app/javascript/components/rooms/room/forms/ShareRoomForm.jsx new file mode 100644 index 0000000000..2c50cfeeb8 --- /dev/null +++ b/app/javascript/components/rooms/room/forms/ShareRoomForm.jsx @@ -0,0 +1,196 @@ +// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/. +// +// Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below). +// +// This program is free software; you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the Free Software +// Foundation; either version 3.0 of the License, or (at your option) any later +// version. +// +// Greenlight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with Greenlight; if not, see . + +import React from 'react'; +import PropTypes from 'prop-types'; +import { useTranslation } from 'react-i18next'; +import { Square2StackIcon, CalendarIcon } from '@heroicons/react/24/outline'; +import { + Form, InputGroup, Button, Row, Col, +} from 'react-bootstrap'; +import { toast } from 'react-toastify'; +import { useAuth } from '../../../../contexts/auth/AuthProvider'; +import downloadICS from '../../../../helpers/ICSDownloadHelper'; +import useEnv from '../../../../hooks/queries/env/useEnv'; + +export default function ShareRoomForm({ room, roomSettings }) { + const { t } = useTranslation(); + const { isLoading, data: envData } = useEnv(); + const currentUser = useAuth(); + + function roomJoinUrl() { + if (room.friendly_id !== undefined) { + return `${window.location}/${room.friendly_id}/join`; + } + return `${window.location}/join`; + } + + function copyInvite(role) { + if (role === 'viewer') { + navigator.clipboard.writeText(roomSettings?.data?.glViewerAccessCode); + toast.success(t('toast.success.room.copied_viewer_code')); + } else if (role === 'moderator') { + navigator.clipboard.writeText(roomSettings?.data?.glModeratorAccessCode); + toast.success(t('toast.success.room.copied_moderator_code')); + } else { + navigator.clipboard.writeText(roomJoinUrl()); + toast.success(t('toast.success.room.copied_meeting_url')); + } + } + + function copyPhoneNumber() { + navigator.clipboard.writeText(`${room.voice_bridge_phone_number},,${room.voice_bridge}`); + toast.success(t('toast.success.room.copied_voice_bridge')); + } + + function downloadICSFile() { + downloadICS(currentUser.name, room.name, roomJoinUrl(), room.voice_bridge, room.voice_bridge_phone_number, t, envData.ICS_USE_HTML); + toast.success(t('toast.success.room.download_ics')); + } + + return ( +
+ + {t('copy')} + + + + + + + {typeof room.voice_bridge_phone_number !== 'undefined' && typeof room.voice_bridge !== 'undefined' && ( + + {t('copy_voice_bridge')} + + + + + + )} + + + {t('room.meeting.download_ics')} + + + + + + + {(roomSettings?.data?.glModeratorAccessCode || roomSettings?.data?.glViewerAccessCode) && ( + + {(roomSettings?.data?.glModeratorAccessCode) && ( + + {t('copy_moderator_code')} + + + + + + )} + + {(roomSettings?.data?.glViewerAccessCode) && ( + + {t('copy_viewer_code')} + + + + + + )} + + )} +
+ ); +} + +ShareRoomForm.propTypes = { + room: PropTypes.shape({ + id: PropTypes.string.isRequired, + friendly_id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + voice_bridge_phone_number: PropTypes.string, + voice_bridge: PropTypes.string, + last_session: PropTypes.string, + shared_owner: PropTypes.string, + online: PropTypes.bool, + participants: PropTypes.number, + }).isRequired, + + roomSettings: PropTypes.shape({ + data: PropTypes.shape({ + glModeratorAccessCode: PropTypes.string, + glViewerAccessCode: PropTypes.string, + }).isRequired, + }).isRequired, +}; diff --git a/app/javascript/helpers/ICSDownloadHelper.jsx b/app/javascript/helpers/ICSDownloadHelper.jsx new file mode 100644 index 0000000000..83d2dd5a3d --- /dev/null +++ b/app/javascript/helpers/ICSDownloadHelper.jsx @@ -0,0 +1,151 @@ +// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/. +// +// Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below). +// +// This program is free software; you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the Free Software +// Foundation; either version 3.0 of the License, or (at your option) any later +// version. +// +// Greenlight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with Greenlight; if not, see . + +import { createEvent } from 'ics'; +import { saveAs } from 'file-saver'; + +const createICSWithoutHTML = (name, roomName, url, voiceBridge, voiceBridgePhoneNumber, t) => { + let description = `\n\n${t('room.meeting.invite_to_meeting', { name })}\n\n${t('room.meeting.join_by_url')}:\n${url}\n`; + + if (typeof voiceBridge !== 'undefined' && typeof voiceBridgePhoneNumber !== 'undefined') { + description += `\n${t('room.meeting.join_by_phone')}: ${voiceBridgePhoneNumber},,${voiceBridge}\nPIN: ${voiceBridge}`; + } + + const date = new Date(); + + return { + start: [date.getFullYear(), date.getMonth() + 1, date.getDate(), 12, 0], + url, + description, + title: roomName, + location: t('room.meeting.location'), + }; +}; + +const createICSWithHtml = (name, roomName, url, voiceBridge, voiceBridgePhoneNumber, t) => { + let phoneData = ''; + + /* eslint-disable max-len */ + if (typeof voiceBridge !== 'undefined' && typeof voiceBridgePhoneNumber !== 'undefined') { + phoneData = `
${t('room.meeting.join_by_phone')}:
+

${voiceBridgePhoneNumber},,${voiceBridge}

`; + } + + const HTML = ` + + + + + + + + + + + + + + + + + `; + + const date = new Date(); + + return { + start: [date.getFullYear(), date.getMonth() + 1, date.getDate(), 12, 0], + url, + htmlContent: HTML, + title: roomName, + location: t('room.meeting.location'), + }; +}; + +const createICSContent = (name, roomName, url, voiceBridge, voiceBridgePhoneNumber, t, useHtml) => { + if (useHtml) { + return createICSWithHtml(name, roomName, url, voiceBridge, voiceBridgePhoneNumber, t); + } + return createICSWithoutHTML(name, roomName, url, voiceBridge, voiceBridgePhoneNumber, t); +}; + +const downloadICS = (name, room, url, voiceBridge, voiceBridgePhoneNumber, t, useHtml) => { + createEvent(createICSContent(name, room, url, voiceBridge, voiceBridgePhoneNumber, t, useHtml), (error, value) => { + if (error !== undefined && error != null) { + throw new Error(`Error creating ICS: ${error}`); + } + const blob = new Blob([value], { type: 'text/plain;charset=utf-8' }); + saveAs(blob, `bbb-meeting-${room.replace(/[/\\?%*:|"<>]/g, '')}.ics`); + }); +}; + +export default downloadICS; diff --git a/package-lock.json b/package-lock.json index 9d1c3c9131..17cec2ede0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,10 @@ "esbuild": "^0.19.9", "esbuild-plugin-import-glob": "^0.1.1", "esbuild-sass-plugin": "^2.2.6", + "file-saver": "^2.0.5", "i18next": "^21.9.1", "i18next-http-backend": "^1.4.1", + "ics": "^3.7.6", "lodash.debounce": "^4.0.8", "prop-types": "^15.8.1", "react": "^17.0.2", @@ -4727,6 +4729,11 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5166,6 +5173,38 @@ "cross-fetch": "3.1.5" } }, + "node_modules/ics": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/ics/-/ics-3.7.6.tgz", + "integrity": "sha512-Z1QIWoPzyzqKUSj2Ui9vD3ca0AS+uHyiCjkROFx9PiKtJu9vMuMo6KJ4aqFmMAqn5q3fLq/5tLTJRm4zr9jjgw==", + "dependencies": { + "nanoid": "^3.1.23", + "runes2": "^1.1.2", + "yup": "^1.2.0" + } + }, + "node_modules/ics/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ics/node_modules/yup": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", + "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -5949,6 +5988,23 @@ "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==", "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6869,6 +6925,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/runes2": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz", + "integrity": "sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==" + }, "node_modules/safe-array-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", @@ -7224,6 +7285,11 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "node_modules/tinycolor2": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", diff --git a/package.json b/package.json index a1a3718a35..187caa2c3e 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,10 @@ "esbuild": "^0.19.9", "esbuild-plugin-import-glob": "^0.1.1", "esbuild-sass-plugin": "^2.2.6", + "file-saver": "^2.0.5", "i18next": "^21.9.1", "i18next-http-backend": "^1.4.1", + "ics": "^3.7.6", "lodash.debounce": "^4.0.8", "prop-types": "^15.8.1", "react": "^17.0.2", diff --git a/sample.env b/sample.env index 8f65945afc..6e327f6f39 100644 --- a/sample.env +++ b/sample.env @@ -109,6 +109,9 @@ LOG_LEVEL=info #CLAMAV_SCANNING=true #CLAMAV_DAEMONIZE=true +# Define weather HTML should be used inside ics files generated to share meetings over e-mail clients +#ICS_USE_HTML=true + ## Support for Tagged Servers # If your Greenlight instance is connected to Scalelite or another Loadbalancer with enabled support for the 'meta_server-tag' # parameter on create calls, you can use the following variables to configure support for this feature via the Greenlight UI. diff --git a/tmp/.keep b/tmp/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/yarn.lock b/yarn.lock index 3a6b87a694..01d97ad4bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2502,6 +2502,11 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" @@ -2763,6 +2768,15 @@ i18next@^21.9.1: dependencies: "@babel/runtime" "^7.17.2" +ics@^3.7.6: + version "3.7.6" + resolved "https://registry.npmjs.org/ics/-/ics-3.7.6.tgz" + integrity sha512-Z1QIWoPzyzqKUSj2Ui9vD3ca0AS+uHyiCjkROFx9PiKtJu9vMuMo6KJ4aqFmMAqn5q3fLq/5tLTJRm4zr9jjgw== + dependencies: + nanoid "^3.1.23" + runes2 "^1.1.2" + yup "^1.2.0" + ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -3259,6 +3273,11 @@ nanoclone@^0.2.1: resolved "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz" integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== +nanoid@^3.1.23: + version "3.3.7" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -3465,7 +3484,7 @@ prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -property-expr@^2.0.4: +property-expr@^2.0.4, property-expr@^2.0.5: version "2.0.6" resolved "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz" integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== @@ -3785,6 +3804,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +runes2@^1.1.2: + version "1.1.4" + resolved "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz" + integrity sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g== + safe-array-concat@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" @@ -4065,6 +4089,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +tiny-case@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz" + integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== + tinycolor2@^1.4.2: version "1.6.0" resolved "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz" @@ -4372,3 +4401,13 @@ yup@^0.32.11: nanoclone "^0.2.1" property-expr "^2.0.4" toposort "^2.0.2" + +yup@^1.2.0: + version "1.4.0" + resolved "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz" + integrity sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg== + dependencies: + property-expr "^2.0.5" + tiny-case "^1.0.3" + toposort "^2.0.2" + type-fest "^2.19.0"