diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/MeetingDAO.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/MeetingDAO.scala index dde6879235..7b20dc3e14 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/MeetingDAO.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/MeetingDAO.scala @@ -8,6 +8,7 @@ case class MeetingSystemColumnsDbModel( loginUrl: Option[String], logoutUrl: Option[String], customLogoUrl: Option[String], + customDarkLogoUrl: Option[String], bannerText: Option[String], bannerColor: Option[String], ) @@ -70,9 +71,10 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet val loginUrl = column[Option[String]]("loginUrl") val logoutUrl = column[Option[String]]("logoutUrl") val customLogoUrl = column[Option[String]]("customLogoUrl") + val customDarkLogoUrl = column[Option[String]]("customDarkLogoUrl") val bannerText = column[Option[String]]("bannerText") val bannerColor = column[Option[String]]("bannerColor") - val systemColumns = (loginUrl, logoutUrl, customLogoUrl, bannerText, bannerColor) <> (MeetingSystemColumnsDbModel.tupled, MeetingSystemColumnsDbModel.unapply) + val systemColumns = (loginUrl, logoutUrl, customLogoUrl, customDarkLogoUrl, bannerText, bannerColor) <> (MeetingSystemColumnsDbModel.tupled, MeetingSystemColumnsDbModel.unapply) val createdTime = column[Long]("createdTime") val durationInSeconds = column[Int]("durationInSeconds") val endWhenNoModerator = column[Boolean]("endWhenNoModerator") @@ -111,6 +113,10 @@ object MeetingDAO { case "" => None case logoUrl => Some(logoUrl) }, + customDarkLogoUrl = meetingProps.systemProps.customDarkLogoURL match { + case "" => None + case darkLogoUrl => Some(darkLogoUrl) + }, bannerText = meetingProps.systemProps.bannerText match { case "" => None case bannerText => Some(bannerText) diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala index 2f90d1af10..67728e95ab 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala @@ -73,6 +73,7 @@ case class SystemProps( loginUrl: String, logoutUrl: String, customLogoURL: String, + customDarkLogoURL: String, bannerText: String, bannerColor: String, ) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java index 0c51f71846..2de1966241 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java @@ -37,6 +37,7 @@ public class ApiParams { public static final String MEETING_LAYOUT = "meetingLayout"; public static final String IS_BREAKOUT = "isBreakout"; public static final String LOGO = "logo"; + public static final String DARK_LOGO = "darklogo"; public static final String LOGOUT_TIMER = "logoutTimer"; public static final String LOGIN_URL = "loginURL"; public static final String LOGOUT_URL = "logoutURL"; diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java index 16a57e2c9b..b20f253837 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java @@ -429,7 +429,7 @@ public class MeetingService implements MessageListener { m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(), m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(), m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(), - m.breakoutRoomsParams, m.lockSettingsParams, m.getLoginUrl(), m.getLogoutUrl(), m.getCustomLogoURL(), + m.breakoutRoomsParams, m.lockSettingsParams, m.getLoginUrl(), m.getLogoutUrl(), m.getCustomLogoURL(), m.getCustomDarkLogoURL(), m.getBannerText(), m.getBannerColor(), m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(), m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(), m.getOverrideClientSettings()); diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java index 15d8cf1779..a7e1a3ea2b 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java @@ -105,7 +105,9 @@ public class ParamsProcessorUtil { private boolean defaultNotifyRecordingIsOn = false; private boolean defaultKeepEvents = false; private Boolean useDefaultLogo; + private Boolean useDefaultDarkLogo; private String defaultLogoURL; + private String defaultDarkLogoURL; private String defaultPresentationUploadExternalDescription = ""; private String defaultPresentationUploadExternalUrl = ""; @@ -830,6 +832,16 @@ public class ParamsProcessorUtil { meeting.setCustomLogoURL(this.getDefaultLogoURL()); } + if (!StringUtils.isEmpty(params.get(ApiParams.DARK_LOGO))) { + meeting.setCustomDarkLogoURL(params.get(ApiParams.DARK_LOGO)); + } else if (!StringUtils.isEmpty(params.get(ApiParams.LOGO))) { + meeting.setCustomDarkLogoURL(params.get(ApiParams.LOGO)); + } else if (this.getUseDefaultDarkLogo()) { + meeting.setCustomDarkLogoURL(this.getDefaultDarkLogoURL()); + } else if (!this.getUseDefaultDarkLogo() && this.getUseDefaultLogo()) { + meeting.setCustomDarkLogoURL(this.getDefaultLogoURL()); + } + if (!StringUtils.isEmpty(params.get(ApiParams.COPYRIGHT))) { meeting.setCustomCopyright(params.get(ApiParams.COPYRIGHT)); } @@ -895,10 +907,18 @@ public class ParamsProcessorUtil { return useDefaultLogo; } + public Boolean getUseDefaultDarkLogo() { + return useDefaultDarkLogo; + } + public String getDefaultLogoURL() { return defaultLogoURL; } + public String getDefaultDarkLogoURL() { + return defaultDarkLogoURL; + } + public Boolean getAllowRequestsWithoutSession() { return allowRequestsWithoutSession; } @@ -1262,10 +1282,19 @@ public class ParamsProcessorUtil { this.useDefaultLogo = value; } + public void setUseDefaultDarkLogo(Boolean value) { + this.useDefaultDarkLogo = value; + } + + public void setDefaultLogoURL(String url) { this.defaultLogoURL = url; } + public void setDefaultDarkLogoURL(String url) { + this.defaultDarkLogoURL = url; + } + public void setAllowRequestsWithoutSession(Boolean allowRequestsWithoutSession) { this.allowRequestsWithoutSession = allowRequestsWithoutSession; } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java index ef6ef73175..417aa1537b 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java @@ -95,6 +95,7 @@ public class Meeting { private final List breakoutRooms = new ArrayList<>(); private ArrayList groups = new ArrayList(); private String customLogoURL = ""; + private String customDarkLogoURL = ""; private String customCopyright = ""; private Boolean muteOnStart = false; private Boolean allowModsToUnmuteUsers = false; @@ -643,10 +644,18 @@ public class Meeting { return customLogoURL; } + public String getCustomDarkLogoURL() { + return customDarkLogoURL; + } + public void setCustomLogoURL(String url) { customLogoURL = url; } + public void setCustomDarkLogoURL(String url) { + customDarkLogoURL = url; + } + public void setCustomCopyright(String copyright) { customCopyright = copyright; } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java b/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java index e82dccf8ea..7eeb3d52d2 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java @@ -61,6 +61,7 @@ public interface IBbbWebApiGWApp { String loginUrl, String logoutUrl, String customLogoURL, + String customDarkLogoURL, String bannerText, String bannerColor, ArrayList groups, diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala index 08e07dc65f..e0f91d4e92 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala @@ -157,6 +157,7 @@ class BbbWebApiGWApp( loginUrl: String, logoutUrl: String, customLogoURL: String, + customDarkLogoURL: String, bannerText: String, bannerColor: String, groups: java.util.ArrayList[Group], @@ -247,6 +248,7 @@ class BbbWebApiGWApp( }, logoutUrl, customLogoURL, + customDarkLogoURL, bannerText match { case t: String => t case _ => "" diff --git a/bbb-graphql-server/bbb_schema.sql b/bbb-graphql-server/bbb_schema.sql index 845855514a..9fdec5f069 100644 --- a/bbb-graphql-server/bbb_schema.sql +++ b/bbb-graphql-server/bbb_schema.sql @@ -33,6 +33,7 @@ create table "meeting" ( "loginUrl" varchar(500), "logoutUrl" varchar(500), "customLogoUrl" varchar(500), + "customDarkLogoUrl" varchar(500), "bannerText" text, "bannerColor" varchar(50), "createdTime" bigint, diff --git a/bbb-graphql-server/metadata/databases/BigBlueButton/tables/public_v_meeting.yaml b/bbb-graphql-server/metadata/databases/BigBlueButton/tables/public_v_meeting.yaml index e7868c3508..074295d1e6 100644 --- a/bbb-graphql-server/metadata/databases/BigBlueButton/tables/public_v_meeting.yaml +++ b/bbb-graphql-server/metadata/databases/BigBlueButton/tables/public_v_meeting.yaml @@ -161,6 +161,7 @@ select_permissions: - createdAt - createdTime - customLogoUrl + - customDarkLogoUrl - disabledFeatures - durationInSeconds - endWhenNoModerator @@ -190,6 +191,7 @@ select_permissions: - bannerColor - bannerText - customLogoUrl + - customDarkLogoUrl - ended - endedAt - endedBy diff --git a/bigbluebutton-html5/imports/ui/components/app/service.js b/bigbluebutton-html5/imports/ui/components/app/service.js index 0b31dbb41e..7f238feca3 100644 --- a/bigbluebutton-html5/imports/ui/components/app/service.js +++ b/bigbluebutton-html5/imports/ui/components/app/service.js @@ -2,6 +2,15 @@ import * as DarkReader from 'darkreader'; import Styled from './styles'; import logger from '/imports/startup/client/logger'; import useMeeting from '../../core/hooks/useMeeting'; +import Storage from '/imports/ui/services/storage/session'; + +const CUSTOM_LOGO_URL_KEY = 'CustomLogoUrl'; + +const CUSTOM_DARK_LOGO_URL_KEY = 'CustomDarkLogoUrl'; + +const equalURLs = () => ( + Storage.getItem(CUSTOM_LOGO_URL_KEY) === Storage.getItem(CUSTOM_DARK_LOGO_URL_KEY) +); export function useMeetingIsBreakout() { const { data: meeting } = useMeeting((m) => ({ @@ -12,11 +21,17 @@ export function useMeetingIsBreakout() { } export const setDarkTheme = (value) => { + let invert = [Styled.DtfInvert]; + + if(equalURLs()) { + invert = [Styled.DtfBrandingInvert]; + } + if (value && !DarkReader.isEnabled()) { DarkReader.enable( { brightness: 100, contrast: 90 }, { - invert: [Styled.DtfInvert], + invert, ignoreInlineStyle: [Styled.DtfCss], ignoreImageAnalysis: [Styled.DtfImages], }, diff --git a/bigbluebutton-html5/imports/ui/components/app/styles.js b/bigbluebutton-html5/imports/ui/components/app/styles.js index 7c1caf39e7..a81d49622f 100644 --- a/bigbluebutton-html5/imports/ui/components/app/styles.js +++ b/bigbluebutton-html5/imports/ui/components/app/styles.js @@ -23,6 +23,41 @@ const ActionsBar = styled.section` const Layout = styled(FlexColumn)``; const DtfInvert = ` + body { + background-color: var(--darkreader-neutral-background) !important; + } + header[id="Navbar"] { + background-color: var(--darkreader-neutral-background) !important; + } + section[id="ActionsBar"] { + background-color: var(--darkreader-neutral-background) !important; + } + select { + border: 0.1rem solid #FFFFFF !important; + } + select[data-test="skipSlide"] { + border: unset !important; + } + div[data-test="presentationContainer"] { + background-color: var(--darkreader-neutral-background) !important; + } + select { + border-top: unset !important; + border-right: unset !important; + border-left: unset !important; + } + .tl-container { + background-color: var(--tl-background) !important; + } + #TD-Tools button, #TD-TopPanel-Undo, #TD-TopPanel-Redo, #TD-Styles { + border-color: transparent !important; + } + [id="TD-StylesMenu"], + [id="TD-Styles-Color-Container"], + #connectionBars > div +`; + +const DtfBrandingInvert = ` body { background-color: var(--darkreader-neutral-background) !important; } @@ -73,6 +108,7 @@ export default { ActionsBar, Layout, DtfInvert, + DtfBrandingInvert, DtfCss, DtfImages, }; diff --git a/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/component.tsx b/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/component.tsx index 8550d3b86a..0cac341509 100644 --- a/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/component.tsx @@ -42,6 +42,7 @@ interface PresenceManagerProps extends PresenceManagerContainerProps { bannerColor: string; bannerText: string; customLogoUrl: string; + customDarkLogoUrl: string; loggedOut: boolean; guestStatus: string; guestLobbyMessage: string | null; @@ -67,6 +68,7 @@ const PresenceManager: React.FC = ({ bannerColor, bannerText, customLogoUrl, + customDarkLogoUrl, loggedOut, guestLobbyMessage, guestStatus, @@ -112,6 +114,7 @@ const PresenceManager: React.FC = ({ extId, meetingName, customLogoUrl, + customDarkLogoUrl, }); }, []); @@ -227,6 +230,7 @@ const PresenceManagerContainer: React.FC = ({ chi bannerColor, bannerText, customLogoUrl, + customDarkLogoUrl, } = userInfoData.meeting[0]; const { extId, name: userName, userId } = userInfoData.user_current[0]; @@ -250,6 +254,7 @@ const PresenceManagerContainer: React.FC = ({ chi bannerText={bannerText} loggedOut={loggedOut} customLogoUrl={customLogoUrl} + customDarkLogoUrl={customDarkLogoUrl} guestLobbyMessage={guestStatusDetails?.guestLobbyMessage ?? null} positionInWaitingQueue={guestStatusDetails?.positionInWaitingQueue ?? null} guestStatus={guestStatus} diff --git a/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/queries.ts b/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/queries.ts index 7b2d7b807e..577f35e32f 100644 --- a/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/queries.ts +++ b/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/queries.ts @@ -32,6 +32,7 @@ export interface GetUserInfoResponse { bannerColor: string; bannerText: string; customLogoUrl: string; + customDarkLogoUrl: string; }>; user_current: Array<{ extId: string; @@ -49,6 +50,7 @@ query getUserInfo { bannerColor bannerText customLogoUrl + customDarkLogoUrl } user_current { extId diff --git a/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/service.ts b/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/service.ts index 93764c1c9e..d6f27df973 100644 --- a/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/service.ts +++ b/bigbluebutton-html5/imports/ui/components/join-handler/presenceManager/service.ts @@ -23,6 +23,7 @@ export const setUserDataToSessionStorage = (userData: { extId: string, meetingName: string, customLogoUrl: string, + customDarkLogoUrl: string, }) => { sessionStorage.setItem('meetingId', userData.meetingId); sessionStorage.setItem('userId', userData.userId); @@ -32,6 +33,7 @@ export const setUserDataToSessionStorage = (userData: { sessionStorage.setItem('extId', userData.extId); sessionStorage.setItem('meetingName', userData.meetingName); Storage.setItem('CustomLogoUrl', userData.customLogoUrl); + Storage.setItem('CustomDarkLogoUrl', userData.customDarkLogoUrl); }; export default { diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx index f3468f8372..994ffd68ac 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx @@ -8,12 +8,15 @@ import UserContentContainer from './user-list-content/container'; const propTypes = { compact: PropTypes.bool, CustomLogoUrl: PropTypes.string, + CustomDarkLogoUrl: PropTypes.string, + DarkModeIsEnabled: PropTypes.bool, showBranding: PropTypes.bool.isRequired, }; const defaultProps = { compact: false, CustomLogoUrl: null, + CustomDarkLogoUrl: null, }; class UserList extends PureComponent { @@ -21,16 +24,19 @@ class UserList extends PureComponent { const { compact, CustomLogoUrl, + CustomDarkLogoUrl, + DarkModeIsEnabled, showBranding, } = this.props; + const logoUrl = DarkModeIsEnabled ? CustomDarkLogoUrl : CustomLogoUrl; return ( { showBranding && !compact - && CustomLogoUrl - ? : null + && logoUrl + ? : null } diff --git a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx index 3e8384a3f6..c8334dd9c2 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/container.jsx @@ -1,13 +1,19 @@ import React from 'react'; import getFromUserSettings from '/imports/ui/services/users-settings'; +import { isDarkThemeEnabled } from '/imports/ui/components/app/service'; import UserList from './component'; import { useStorageKey } from '../../services/storage/hooks'; const UserListContainer = (props) => { const CustomLogoUrl = useStorageKey('CustomLogoUrl', 'session'); + const CustomDarkLogoUrl = useStorageKey('CustomDarkLogoUrl', 'session'); + const DarkModeIsEnabled = isDarkThemeEnabled(); + return ( diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js index 9a474448c2..c3e5eeb89e 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/service.js +++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js @@ -20,12 +20,18 @@ const STARTED_CHAT_LIST_KEY = 'startedChatList'; const CUSTOM_LOGO_URL_KEY = 'CustomLogoUrl'; +const CUSTOM_DARK_LOGO_URL_KEY = 'CustomDarkLogoUrl'; + export const setCustomLogoUrl = (path) => Storage.setItem(CUSTOM_LOGO_URL_KEY, path); +export const setCustomDarkLogoUrl = (path) => Storage.setItem(CUSTOM_DARK_LOGO_URL_KEY, path); + export const setModeratorOnlyMessage = (msg) => Storage.setItem('ModeratorOnlyMessage', msg); const getCustomLogoUrl = () => Storage.getItem(CUSTOM_LOGO_URL_KEY); +const getCustomDarkLogoUrl = () => Storage.getItem(CUSTOM_DARK_LOGO_URL_KEY); + const sortByWhiteboardAccess = (a, b) => { const _a = a.whiteboardAccess; const _b = b.whiteboardAccess; @@ -453,6 +459,7 @@ export default { isPublicChat, roving, getCustomLogoUrl, + getCustomDarkLogoUrl, focusFirstDropDownItem, sortUsersByCurrent, UserJoinedMeetingAlert, diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index 89845f4f97..0e0f8d788c 100644 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -317,7 +317,9 @@ graphqlApiUrl=${bigbluebutton.web.serverURL}/api/rest sessionsCleanupDelayInMinutes=60 useDefaultLogo=false +useDefaultDarkLogo=false defaultLogoURL=${bigbluebutton.web.serverURL}/images/logo.png +defaultDarkLogoURL=${bigbluebutton.web.serverURL}/images/darklogo.png # Allow requests without JSESSIONID to be handled (default = false) allowRequestsWithoutSession=false diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml index 8625110e32..d1568c6b63 100755 --- a/bigbluebutton-web/grails-app/conf/spring/resources.xml +++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml @@ -144,7 +144,9 @@ with BigBlueButton; if not, see . + +