From fc8df8815e0cf2476f0f2d3d00eebd68ec3daa17 Mon Sep 17 00:00:00 2001 From: Ahmad Farhat <ahmad.af.farhat@gmail.com> Date: Wed, 12 Jul 2023 15:34:20 -0400 Subject: [PATCH] Various improvements throughout the app (#5325) * Various improvements * Invalidate right queries * Improve recording visibility dropdown * Fix min width * Fix dropdown overflow * small styling --- .../stylesheets/application.bootstrap.scss | 37 +++++++++ app/assets/stylesheets/recordings.scss | 6 +- .../components/recordings/RecordingRow.jsx | 75 ++++++++++++------- .../utilities/SimpleSelect.jsx | 47 ++++++++++++ .../useUpdateRecordingVisibility.jsx | 2 + .../public_recording_serializer.rb | 4 + lib/tasks/admin.rake | 17 +++-- 7 files changed, 153 insertions(+), 35 deletions(-) create mode 100644 app/javascript/components/shared_components/utilities/SimpleSelect.jsx diff --git a/app/assets/stylesheets/application.bootstrap.scss b/app/assets/stylesheets/application.bootstrap.scss index 746c74c8..f5fec4c7 100644 --- a/app/assets/stylesheets/application.bootstrap.scss +++ b/app/assets/stylesheets/application.bootstrap.scss @@ -441,6 +441,43 @@ input.search-bar { } } +.simple-select { + button { + background: white !important; + color: black !important; + border-color: gainsboro !important; + text-align: left; + width: 220px; + + &:hover, &:focus, &:active { + background: white !important; + color: black !important; + border-color: gainsboro !important; + } + &:focus { + box-shadow: 0 0 0 0.25rem var(--brand-color-light) !important; + } + &::after { + display: none; + } + } + + .dropdown-menu { + min-width: 220px; + } +} + +@media (max-width: 767px) { + .table-responsive .dropdown-menu { + position: static !important; + } +} +@media (min-width: 768px) { + .table-responsive { + overflow: visible; + } +} + input[type='text']:focus { border-color: whitesmoke !important; box-shadow: 0 0 0 2px var(--brand-color-light) !important; diff --git a/app/assets/stylesheets/recordings.scss b/app/assets/stylesheets/recordings.scss index f7d76ea5..9b770871 100644 --- a/app/assets/stylesheets/recordings.scss +++ b/app/assets/stylesheets/recordings.scss @@ -15,11 +15,13 @@ // with Greenlight; if not, see <http://www.gnu.org/licenses/>. #user-recordings, #room-recordings { + min-height: 400px; + table { border-top-right-radius: $border-radius-lg; border-top-left-radius: $border-radius-lg; border-spacing: 0; - overflow: hidden; + border-top-width: 0 !important; } tr { @@ -121,4 +123,4 @@ svg:hover { color: var(--brand-color) !important; } -} +} \ No newline at end of file diff --git a/app/javascript/components/recordings/RecordingRow.jsx b/app/javascript/components/recordings/RecordingRow.jsx index 8fead84a..04890601 100644 --- a/app/javascript/components/recordings/RecordingRow.jsx +++ b/app/javascript/components/recordings/RecordingRow.jsx @@ -17,7 +17,6 @@ import { VideoCameraIcon, TrashIcon, PencilSquareIcon, ClipboardDocumentIcon, EllipsisVerticalIcon, } from '@heroicons/react/24/outline'; -import Form from 'react-bootstrap/Form'; import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { @@ -32,6 +31,7 @@ import Modal from '../shared_components/modals/Modal'; import { localizeDateTimeString } from '../../helpers/DateTimeHelper'; import useRedirectRecordingUrl from '../../hooks/mutations/recordings/useRedirectRecordingUrl'; import useCopyRecordingUrl from '../../hooks/mutations/recordings/useCopyRecordingUrl'; +import SimpleSelect from '../shared_components/utilities/SimpleSelect'; // TODO: Amir - Refactor this. export default function RecordingRow({ @@ -102,29 +102,48 @@ export default function RecordingRow({ <td className="border-0"> {t('recording.length_in_minutes', { recording })} </td> <td className="border-0"> {recording.participants} </td> <td className="border-0"> - {/* TODO: Refactor this. */} - <Form.Select - className="visibility-dropdown" - onChange={(event) => { - visibilityAPI.mutate({ visibility: event.target.value, id: recording.record_id }); - }} + <SimpleSelect defaultValue={recording.visibility} - disabled={visibilityAPI.isLoading} > - <option value="Published">{t('recording.published')}</option> - <option value="Unpublished">{t('recording.unpublished')}</option> - {recording?.protectable === true - && ( - <> - <option value="Protected">{t('recording.protected')}</option> - <option value="Public/Protected">{t('recording.public_protected')}</option> - </> - )} - <option value="Public">{t('recording.public')}</option> - </Form.Select> + <Dropdown.Item + key="Public/Protected" + value="Public/Protected" + onClick={() => visibilityAPI.mutate({ visibility: 'Public/Protected', id: recording.record_id })} + > + {t('recording.public_protected')} + </Dropdown.Item> + <Dropdown.Item + key="Public" + value="Public" + onClick={() => visibilityAPI.mutate({ visibility: 'Public', id: recording.record_id })} + > + {t('recording.public')} + </Dropdown.Item> + <Dropdown.Item + key="Protected" + value="Protected" + onClick={() => visibilityAPI.mutate({ visibility: 'Protected', id: recording.record_id })} + > + {t('recording.protected')} + </Dropdown.Item> + <Dropdown.Item + key="Published" + value="Published" + onClick={() => visibilityAPI.mutate({ visibility: 'Published', id: recording.record_id })} + > + {t('recording.published')} + </Dropdown.Item> + <Dropdown.Item + key="Unpublished" + value="Unpublished" + onClick={() => visibilityAPI.mutate({ visibility: 'Unpublished', id: recording.record_id })} + > + {t('recording.unpublished')} + </Dropdown.Item> + </SimpleSelect> </td> <td className="border-0"> - {formats.map((format) => ( + {recording?.visibility !== 'Unpublished' && formats.map((format) => ( <Button onClick={() => redirectRecordingUrl.mutate({ record_id: recording.record_id, format: format.recording_type })} className={`btn-sm rounded-pill me-1 mt-1 border-0 btn-format-${format.recording_type.toLowerCase()}`} @@ -158,13 +177,15 @@ export default function RecordingRow({ ) : ( <Stack direction="horizontal" className="float-end recordings-icons"> - <Button - variant="icon" - className="mt-1 me-3" - onClick={() => copyRecordingUrl.mutate({ record_id: recording.record_id })} - > - <ClipboardDocumentIcon className="hi-s text-muted" /> - </Button> + { recording?.visibility !== 'Unpublished' && ( + <Button + variant="icon" + className="mt-1 me-3" + onClick={() => copyRecordingUrl.mutate({ record_id: recording.record_id })} + > + <ClipboardDocumentIcon className="hi-s text-muted" /> + </Button> + )} <Modal modalButton={<Dropdown.Item className="btn btn-icon"><TrashIcon className="hi-s me-2" /></Dropdown.Item>} body={( diff --git a/app/javascript/components/shared_components/utilities/SimpleSelect.jsx b/app/javascript/components/shared_components/utilities/SimpleSelect.jsx new file mode 100644 index 00000000..7ec44282 --- /dev/null +++ b/app/javascript/components/shared_components/utilities/SimpleSelect.jsx @@ -0,0 +1,47 @@ +// 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 <http://www.gnu.org/licenses/>. + +import { Dropdown } from 'react-bootstrap'; +import React from 'react'; +import PropTypes from 'prop-types'; +import { ChevronDownIcon } from '@heroicons/react/20/solid'; + +export default function SimpleSelect({ defaultValue, children }) { + // Get the currently selected option and set the dropdown toggle to that value + const defaultString = children?.filter((item) => item.props.value === defaultValue)[0]; + + return ( + <Dropdown className="simple-select"> + <Dropdown.Toggle> + { defaultString?.props?.children } + <ChevronDownIcon className="hi-s float-end" /> + </Dropdown.Toggle> + <Dropdown.Menu> + {children} + </Dropdown.Menu> + </Dropdown> + ); +} + +SimpleSelect.defaultProps = { + defaultValue: '', + children: undefined, +}; + +SimpleSelect.propTypes = { + defaultValue: PropTypes.string, + children: PropTypes.arrayOf(PropTypes.element), +}; diff --git a/app/javascript/hooks/mutations/recordings/useUpdateRecordingVisibility.jsx b/app/javascript/hooks/mutations/recordings/useUpdateRecordingVisibility.jsx index 23514102..f6488447 100644 --- a/app/javascript/hooks/mutations/recordings/useUpdateRecordingVisibility.jsx +++ b/app/javascript/hooks/mutations/recordings/useUpdateRecordingVisibility.jsx @@ -28,6 +28,8 @@ export default function useUpdateRecordingVisibility() { { onSuccess: () => { queryClient.invalidateQueries(['getRecordings']); + queryClient.invalidateQueries(['getRoomRecordings']); + queryClient.invalidateQueries(['getServerRecordings']); toast.success(t('toast.success.recording.recording_visibility_updated')); }, onError: () => { diff --git a/app/serializers/public_recording_serializer.rb b/app/serializers/public_recording_serializer.rb index a687d449..bd76270f 100644 --- a/app/serializers/public_recording_serializer.rb +++ b/app/serializers/public_recording_serializer.rb @@ -20,4 +20,8 @@ class PublicRecordingSerializer < ApplicationSerializer attributes :id, :record_id, :name, :length, :recorded_at has_many :formats + + def formats + object.formats.filter { |format| format.recording_type != 'statistics' } + end end diff --git a/lib/tasks/admin.rake b/lib/tasks/admin.rake index 775662d8..c9679a06 100644 --- a/lib/tasks/admin.rake +++ b/lib/tasks/admin.rake @@ -37,7 +37,7 @@ namespace :admin do task :super_admin, %i[name email password] => :environment do |_task, args| super_admin_email = "superadmin-#{args[:email]}" - user = User.create( + user = User.new( name: args[:name], email: super_admin_email, password: args[:password], @@ -48,11 +48,16 @@ namespace :admin do language: I18n.default_locale ) - success 'User account was created successfully!' - info " Name: #{user.name}" - info " Email: #{user.email}" - info " Password: #{user.password}" - info " Role: #{user.role.name}" + if user.save + success 'User account was created successfully!' + info " Name: #{user.name}" + info " Email: #{user.email}" + info " Password: #{user.password}" + info " Role: #{user.role.name}" + else + warning 'There was an error creating this user' + err " Error: #{user.errors.full_messages}" + end exit 0 end -- GitLab