diff --git a/.circleci/config.yml b/.circleci/config.yml index 6b70ffb3..34d9deee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -446,7 +446,7 @@ workflows: branches: only: - /^release-\d+(\.\d+){1,2}(-rc.*)?/ - - pull/1647 + - pull/1651 - store_artifacts: # for master/PR builds diff --git a/src/main/appState.ts b/src/main/appState.ts index 6f2712f2..b7a76708 100644 --- a/src/main/appState.ts +++ b/src/main/appState.ts @@ -58,6 +58,13 @@ export const updateUnreads = (serverName: string, unreads: boolean) => { emitMentions(serverName); }; +export const updateBadge = () => { + const expired = anyExpired(); + const mentions = totalMentions(); + const unreads = anyUnreads(); + emitBadge(expired, mentions, unreads); +}; + export const getUnreads = (serverName: string) => { return status.unreads.get(serverName) || false; }; diff --git a/src/main/badge.ts b/src/main/badge.ts index 7678e6a7..426a91b0 100644 --- a/src/main/badge.ts +++ b/src/main/badge.ts @@ -64,7 +64,7 @@ function showBadge(sessionExpired: boolean, mentionCount: number, showUnreadBadg export function setUnreadBadgeSetting(showUnreadBadge: boolean) { showUnreadBadgeSetting = showUnreadBadge; - AppState.emitStatus(); + AppState.updateBadge(); } export function setupBadge() { diff --git a/src/main/preload/dropdown.js b/src/main/preload/dropdown.js index c744321d..154e04ca 100644 --- a/src/main/preload/dropdown.js +++ b/src/main/preload/dropdown.js @@ -13,6 +13,7 @@ import { SWITCH_SERVER, CLOSE_TEAMS_DROPDOWN, SHOW_NEW_SERVER_MODAL, + UPDATE_TEAMS, } from 'common/communication'; console.log('preloaded for the dropdown!'); @@ -34,12 +35,15 @@ window.addEventListener('message', async (event) => { case CLOSE_TEAMS_DROPDOWN: ipcRenderer.send(CLOSE_TEAMS_DROPDOWN); break; + case UPDATE_TEAMS: + ipcRenderer.invoke(UPDATE_TEAMS, event.data.data); + break; default: console.log(`got a message: ${event}`); console.log(event); } }); -ipcRenderer.on(UPDATE_TEAMS_DROPDOWN, (event, teams, activeTeam, darkMode, expired, mentions, unreads) => { - window.postMessage({type: UPDATE_TEAMS_DROPDOWN, data: {teams, activeTeam, darkMode, expired, mentions, unreads}}, window.location.href); +ipcRenderer.on(UPDATE_TEAMS_DROPDOWN, (event, teams, activeTeam, darkMode, hasGPOTeams, expired, mentions, unreads) => { + window.postMessage({type: UPDATE_TEAMS_DROPDOWN, data: {teams, activeTeam, darkMode, hasGPOTeams, expired, mentions, unreads}}, window.location.href); }); diff --git a/src/main/views/teamDropdownView.ts b/src/main/views/teamDropdownView.ts index 39a0f836..7d344c31 100644 --- a/src/main/views/teamDropdownView.ts +++ b/src/main/views/teamDropdownView.ts @@ -25,6 +25,7 @@ export default class TeamDropdownView { teams: Team[]; activeTeam?: string; darkMode: boolean; + hasGPOTeams?: boolean; unreads?: Map; mentions?: Map; expired?: Map; @@ -57,6 +58,7 @@ export default class TeamDropdownView { updateConfig = (event: IpcMainEvent, config: CombinedConfig) => { this.teams = config.teams; this.darkMode = config.darkMode; + this.hasGPOTeams = config.registryTeams && config.registryTeams.length > 0; this.updateDropdown(); } @@ -73,7 +75,7 @@ export default class TeamDropdownView { } updateDropdown = () => { - this.view.webContents.send(UPDATE_TEAMS_DROPDOWN, this.teams, this.activeTeam, this.darkMode, this.expired, this.mentions, this.unreads); + this.view.webContents.send(UPDATE_TEAMS_DROPDOWN, this.teams, this.activeTeam, this.darkMode, this.hasGPOTeams, this.expired, this.mentions, this.unreads); } handleOpen = () => { diff --git a/src/renderer/assets/fonts/compass-icons/compass-icons.eot b/src/renderer/assets/fonts/compass-icons/compass-icons.eot index 0c82f98a..25859633 100644 Binary files a/src/renderer/assets/fonts/compass-icons/compass-icons.eot and b/src/renderer/assets/fonts/compass-icons/compass-icons.eot differ diff --git a/src/renderer/assets/fonts/compass-icons/compass-icons.svg b/src/renderer/assets/fonts/compass-icons/compass-icons.svg index 869bc909..1dbb77d6 100644 --- a/src/renderer/assets/fonts/compass-icons/compass-icons.svg +++ b/src/renderer/assets/fonts/compass-icons/compass-icons.svg @@ -368,6 +368,8 @@ + + @@ -398,6 +400,8 @@ + + @@ -422,6 +426,8 @@ + + @@ -457,4 +463,4 @@ - \ No newline at end of file + diff --git a/src/renderer/assets/fonts/compass-icons/compass-icons.ttf b/src/renderer/assets/fonts/compass-icons/compass-icons.ttf index f2a5a90a..de69e8b4 100644 Binary files a/src/renderer/assets/fonts/compass-icons/compass-icons.ttf and b/src/renderer/assets/fonts/compass-icons/compass-icons.ttf differ diff --git a/src/renderer/assets/fonts/compass-icons/compass-icons.woff b/src/renderer/assets/fonts/compass-icons/compass-icons.woff index 77cd2544..ee85c72b 100644 Binary files a/src/renderer/assets/fonts/compass-icons/compass-icons.woff and b/src/renderer/assets/fonts/compass-icons/compass-icons.woff differ diff --git a/src/renderer/assets/fonts/compass-icons/compass-icons.woff2 b/src/renderer/assets/fonts/compass-icons/compass-icons.woff2 index c466ff3a..f64efb1d 100644 Binary files a/src/renderer/assets/fonts/compass-icons/compass-icons.woff2 and b/src/renderer/assets/fonts/compass-icons/compass-icons.woff2 differ diff --git a/src/renderer/css/compass-icons.css b/src/renderer/css/compass-icons.css index 08fc4d5a..b723f98b 100755 --- a/src/renderer/css/compass-icons.css +++ b/src/renderer/css/compass-icons.css @@ -1,13 +1,12 @@ @charset "UTF-8"; - - @font-face { +@font-face { font-family: 'compass-icons'; - src: url('../assets/fonts/compass-icons/compass-icons.eot?45182295'); - src: url('../assets/fonts/compass-icons/compass-icons.eot?45182295#iefix') format('embedded-opentype'), - url('../assets/fonts/compass-icons/compass-icons.woff2?45182295') format('woff2'), - url('../assets/fonts/compass-icons/compass-icons.woff?45182295') format('woff'), - url('../assets/fonts/compass-icons/compass-icons.ttf?45182295') format('truetype'), - url('../assets/fonts/compass-icons/compass-icons.svg?45182295#compass-icons') format('svg'); + src: url('../assets/fonts/compass-icons/compass-icons.eot?81398164'); + src: url('../assets/fonts/compass-icons/compass-icons.eot?81398164#iefix') format('embedded-opentype'), + url('../assets/fonts/compass-icons/compass-icons.woff2?81398164') format('woff2'), + url('../assets/fonts/compass-icons/compass-icons.woff?81398164') format('woff'), + url('../assets/fonts/compass-icons/compass-icons.ttf?81398164') format('truetype'), + url('../assets/fonts/compass-icons/compass-icons.svg?81398164#compass-icons') format('svg'); font-weight: normal; font-style: normal; } @@ -17,11 +16,10 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'compass-icons'; - src: url('../fonts/compass-icons/compass-icons.svg?45182295#compass-icons') format('svg'); + src: url('../font/compass-icons.svg?81398164#compass-icons') format('svg'); } } */ - [class^="icon-"]:before, [class*=" icon-"]:before { font-family: "compass-icons"; font-style: normal; @@ -238,6 +236,7 @@ .icon-content-copy:before { content: '󰆏'; } /* '\f018f' */ .icon-send-outline:before { content: '󰆐'; } /* '\f0190' */ .icon-credit-card-outline:before { content: '󰆛'; } /* '\f019b' */ +.icon-drag-vertical:before { content: '󰇝'; } /* '\f01dd' */ .icon-view-grid-outline:before { content: '󰈄'; } /* '\f0204' */ .icon-exit-to-app:before { content: '󰈆'; } /* '\f0206' */ .icon-file-outline:before { content: '󰈤'; } /* '\f0224' */ @@ -253,6 +252,7 @@ .icon-message-text-outline:before { content: '󰍪'; } /* '\f036a' */ .icon-minus:before { content: '󰍴'; } /* '\f0374' */ .icon-minus-circle-outline:before { content: '󰍷'; } /* '\f0377' */ +.icon-open-in-new:before { content: '󰏌'; } /* '\f03cc' */ .icon-pause:before { content: '󰏤'; } /* '\f03e4' */ .icon-play:before { content: '󰐊'; } /* '\f040a' */ .icon-radiobox-blank:before { content: '󰐽'; } /* '\f043d' */ @@ -265,6 +265,7 @@ .icon-update:before { content: '󰚰'; } /* '\f06b0' */ .icon-eye-off-outline:before { content: '󰛑'; } /* '\f06d1' */ .icon-infinity:before { content: '󰛤'; } /* '\f06e4' */ +.icon-plus-box-outline:before { content: '󰜄'; } /* '\f0704' */ .icon-arrow-right-bold-outline:before { content: '󰧂'; } /* '\f09c2' */ .icon-trash-can-outline:before { content: '󰩺'; } /* '\f0a7a' */ .icon-account-minus-outline:before { content: '󰫬'; } /* '\f0aec' */ diff --git a/src/renderer/css/dropdown.scss b/src/renderer/css/dropdown.scss index d4a4904c..d08cb22a 100644 --- a/src/renderer/css/dropdown.scss +++ b/src/renderer/css/dropdown.scss @@ -24,6 +24,15 @@ body { min-width: 354px; } +.TeamDropdown__droppable { + width: 100%; + + > button { + border: none; + background: transparent; + } +} + .TeamDropdown__header { padding: 6px 20px; @@ -45,19 +54,23 @@ body { .TeamDropdown__button { background-color: transparent; border: none; - padding: 8px 18px; + padding: 8px 18px 8px 7px; display: flex; width: 100%; align-items: center; font-family: Open Sans; - &:hover { + &:not(.anyDragging):hover { background-color: rgba(61, 60, 64, 0.08); .TeamDropdown__button-edit, .TeamDropdown__button-remove { opacity: 1; pointer-events: all; } + + .TeamDropdown__draggable-handle > i.icon-drag-vertical { + opacity: 1; + } } &:focus, &:focus-within { @@ -68,6 +81,15 @@ body { opacity: 1; pointer-events: all; } + + .TeamDropdown__draggable-handle > i.icon-drag-vertical { + opacity: 1; + } + } + + &.dragging .TeamDropdown__draggable-handle { + opacity: 1; + pointer-events: all; } i { @@ -76,17 +98,43 @@ body { color: rgba(61, 60, 64, 0.56); } - > i.icon-check { + &.active i.icon-check { color: #166de0; } - > span { + > .TeamDropdown__draggable-handle > span, &.addServer > span { font-size: 14px; line-height: 20px; color: #3D3C40; margin-left: 12px; white-space: nowrap; } + + &.addServer { + padding-left: 24px; + + > i.icon-plus::before { + margin: 0; + } + } +} + +.TeamDropdown__draggable-handle { + cursor: pointer !important; + + &.dragging { + cursor: grabbing !important; + } + + > i.icon-drag-vertical { + opacity: 0; + + &::before { + margin: 0; + color: rgba(61, 60, 64, 0.32); + font-size: 14px; + } + } } .TeamDropdown__badge { @@ -171,7 +219,7 @@ body { } .TeamDropdown__button { - &:hover { + &:not(.anyDragging):hover { background-color: rgba(221, 221, 221, 0.08); } @@ -179,11 +227,11 @@ body { background-color: rgba(1, 119, 231, 0.08); } - > i.icon-server-variant, i.icon-plus { + &:not(.active) i, i.icon-drag-vertical { color: rgba(221, 221, 221, 0.56); } - > span { + > .TeamDropdown__draggable-handle > span, &.addServer > span { color: #DDD; } } @@ -195,4 +243,8 @@ body { .TeamDropdown__button-edit > i { color: rgba(221, 221, 221, 0.56); } + + .TeamDropdown__draggable-handle > i.icon-drag-vertical::before { + color:rgba(221, 221, 221, 0.32); + } } diff --git a/src/renderer/dropdown.tsx b/src/renderer/dropdown.tsx index 4ac39068..025c1ab7 100644 --- a/src/renderer/dropdown.tsx +++ b/src/renderer/dropdown.tsx @@ -4,10 +4,11 @@ import React from 'react'; import ReactDOM from 'react-dom'; import classNames from 'classnames'; +import {DragDropContext, Draggable, DraggingStyle, Droppable, DropResult, NotDraggingStyle} from 'react-beautiful-dnd'; import {Team} from 'types/config'; -import {CLOSE_TEAMS_DROPDOWN, REQUEST_TEAMS_DROPDOWN_INFO, SEND_DROPDOWN_MENU_SIZE, SHOW_NEW_SERVER_MODAL, SWITCH_SERVER, UPDATE_TEAMS_DROPDOWN} from 'common/communication'; +import {CLOSE_TEAMS_DROPDOWN, REQUEST_TEAMS_DROPDOWN_INFO, SEND_DROPDOWN_MENU_SIZE, SHOW_NEW_SERVER_MODAL, SWITCH_SERVER, UPDATE_TEAMS, UPDATE_TEAMS_DROPDOWN} from 'common/communication'; import './css/dropdown.scss'; import './css/compass-icons.css'; @@ -20,24 +21,39 @@ type State = { unreads?: Map; mentions?: Map; expired?: Map; + hasGPOTeams?: boolean; + isAnyDragging: boolean; } +function getStyle(style?: DraggingStyle | NotDraggingStyle) { + if (style?.transform) { + const axisLockY = `translate(0px${style.transform.slice(style.transform.indexOf(','), style.transform.length)}`; + return { + ...style, + transform: axisLockY, + }; + } + return style; +} class TeamDropdown extends React.PureComponent, State> { constructor(props: Record) { super(props); - this.state = {}; + this.state = { + isAnyDragging: false, + }; window.addEventListener('message', this.handleMessageEvent); } handleMessageEvent = (event: MessageEvent) => { if (event.data.type === UPDATE_TEAMS_DROPDOWN) { - const {teams, activeTeam, darkMode, unreads, mentions, expired} = event.data.data; + const {teams, activeTeam, darkMode, hasGPOTeams, unreads, mentions, expired} = event.data.data; this.setState({ teams, orderedTeams: teams.concat().sort((a: Team, b: Team) => a.order - b.order), activeTeam, darkMode, + hasGPOTeams, unreads, mentions, expired, @@ -53,8 +69,10 @@ class TeamDropdown extends React.PureComponent, State> { } closeMenu = () => { - (document.activeElement as HTMLElement).blur(); - window.postMessage({type: CLOSE_TEAMS_DROPDOWN}, window.location.href); + if (!this.state.isAnyDragging) { + (document.activeElement as HTMLElement).blur(); + window.postMessage({type: CLOSE_TEAMS_DROPDOWN}, window.location.href); + } } preventPropogation = (event: React.MouseEvent) => { @@ -70,6 +88,39 @@ class TeamDropdown extends React.PureComponent, State> { return team.name === this.state.activeTeam; } + onDragStart = () => { + this.setState({isAnyDragging: true}); + } + + onDragEnd = (result: DropResult) => { + const removedIndex = result.source.index; + const addedIndex = result.destination?.index; + if (addedIndex === undefined || removedIndex === addedIndex) { + this.setState({isAnyDragging: false}); + return; + } + if (!this.state.teams) { + throw new Error('No config'); + } + const teams = this.state.teams.concat(); + const tabOrder = teams.map((team, index) => { + return { + index, + order: team.order, + }; + }).sort((a, b) => (a.order - b.order)); + + const team = tabOrder.splice(removedIndex, 1); + const newOrder = addedIndex < this.state.teams.length ? addedIndex : this.state.teams.length - 1; + tabOrder.splice(newOrder, 0, team[0]); + + tabOrder.forEach((t, order) => { + teams[t.index].order = order; + }); + this.setState({teams, orderedTeams: teams.concat().sort((a: Team, b: Team) => a.order - b.order), isAnyDragging: false}); + window.postMessage({type: UPDATE_TEAMS, data: teams}, window.location.href); + } + componentDidMount() { window.postMessage({type: REQUEST_TEAMS_DROPDOWN_INFO}, window.location.href); window.addEventListener('click', this.closeMenu); @@ -83,6 +134,12 @@ class TeamDropdown extends React.PureComponent, State> { window.removeEventListener('click', this.closeMenu); } + handleClickOnDragHandle = (event: React.MouseEvent) => { + if (this.state.isAnyDragging) { + event.stopPropagation(); + } + } + render() { return (
, State> { {'Servers'}

- {this.state.orderedTeams?.map((team, index) => { - const sessionExpired = this.state.expired?.get(team.name); - const hasUnreads = this.state.unreads?.get(team.name); - const mentionCount = this.state.mentions?.get(team.name); + + + {(provided) => ( +
+ {this.state.orderedTeams?.map((team, orderedIndex) => { + const index = this.state.teams?.indexOf(team); - let badgeDiv: React.ReactNode; - if (sessionExpired) { - badgeDiv = ( -
- -
- ); - } else if (mentionCount && mentionCount > 0) { - badgeDiv = ( -
- {mentionCount > 99 ? '99+' : mentionCount} -
- ); - } else if (hasUnreads) { - badgeDiv = ( -
- ); - } + const sessionExpired = this.state.expired?.get(team.name); + const hasUnreads = this.state.unreads?.get(team.name); + const mentionCount = this.state.mentions?.get(team.name); - return ( - - - {badgeDiv &&
- {badgeDiv} -
} + let badgeDiv: React.ReactNode; + if (sessionExpired) { + badgeDiv = ( +
+ +
+ ); + } else if (mentionCount && mentionCount > 0) { + badgeDiv = ( +
+ {mentionCount > 99 ? '99+' : mentionCount} +
+ ); + } else if (hasUnreads) { + badgeDiv = ( +
+ ); + } + + return ( + + {(provided, snapshot) => ( + + + {badgeDiv &&
+ {badgeDiv} +
} +
+ + )} + + ); + })} + {provided.placeholder}
- - ); - })} + )} + +