Browse Source

Add macOS style titlebar (#23)

- Add macOS titlebar icons
- Add config in miscSlice
- Fix title line height
master
jeffvli 3 years ago
committed by Jeff
parent
commit
c8e7f559db
  1. 1
      src/__tests__/App.test.tsx
  2. 163
      src/components/layout/Titlebar.tsx
  3. 25
      src/components/layout/styled.tsx
  4. 27
      src/components/settings/ConfigPanels/LookAndFeelConfig.tsx
  5. 4
      src/components/shared/setDefaultSettings.ts
  6. BIN
      src/img/icons/close-mac-hover.png
  7. BIN
      src/img/icons/close-mac.png
  8. BIN
      src/img/icons/max-mac-hover.png
  9. BIN
      src/img/icons/max-mac.png
  10. BIN
      src/img/icons/min-mac-hover.png
  11. BIN
      src/img/icons/min-mac.png
  12. 13
      src/redux/miscSlice.ts
  13. 19
      src/styles/App.global.css

1
src/__tests__/App.test.tsx

@ -77,6 +77,7 @@ const miscState: General = {
modalPages: [], modalPages: [],
isProcessingPlaylist: [], isProcessingPlaylist: [],
dynamicBackground: false, dynamicBackground: false,
titleBar: 'windows',
}; };
const mockInitialState = { const mockInitialState = {

163
src/components/layout/Titlebar.tsx

@ -1,5 +1,12 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { TitleHeader, DragRegion, WindowControl, WindowControlButton } from './styled'; import {
TitleHeader,
DragRegion,
WindowControl,
WindowControlButton,
MacControl,
MacControlButton,
} from './styled';
import { useAppSelector } from '../../redux/hooks'; import { useAppSelector } from '../../redux/hooks';
import { getCurrentEntryList } from '../../shared/utils'; import { getCurrentEntryList } from '../../shared/utils';
import logo from '../../../assets/icon.png'; import logo from '../../../assets/icon.png';
@ -7,7 +14,11 @@ import logo from '../../../assets/icon.png';
const Titlebar = ({ font }: any) => { const Titlebar = ({ font }: any) => {
const playQueue = useAppSelector((state) => state.playQueue); const playQueue = useAppSelector((state) => state.playQueue);
const player = useAppSelector((state) => state.player); const player = useAppSelector((state) => state.player);
const misc = useAppSelector((state) => state.misc);
const [title, setTitle] = useState(document.title); const [title, setTitle] = useState(document.title);
const [hoverMin, setHoverMin] = useState(false);
const [hoverMax, setHoverMax] = useState(false);
const [hoverClose, setHoverClose] = useState(false);
useEffect(() => { useEffect(() => {
const currentEntryList = getCurrentEntryList(playQueue); const currentEntryList = getCurrentEntryList(playQueue);
@ -27,46 +38,116 @@ const Titlebar = ({ font }: any) => {
return ( return (
<TitleHeader id="titlebar" font={font}> <TitleHeader id="titlebar" font={font}>
<DragRegion id="drag-region"> <DragRegion id="drag-region">
<div id="window-title-wrapper"> {misc.titleBar === 'mac' && (
<span id="window-title"> <>
<img src={logo} height="20px" width="20px" alt="" style={{ marginRight: '5px' }} /> <div id="window-title-wrapper-mac">
{title} <span id="window-title-mac">{title}</span>
</span> </div>
</div>
<WindowControl id="window-controls"> <MacControl id="window-controls">
<WindowControlButton minButton className="button" id="min-button"> <MacControlButton
<img minButton
className="icon" className="button"
srcSet="img/icons/min-w-10.png 1x, img/icons/min-w-12.png 1.25x, img/icons/min-w-15.png 1.5x, img/icons/min-w-15.png 1.75x, img/icons/min-w-20.png 2x, img/icons/min-w-20.png 2.25x, img/icons/min-w-24.png 2.5x, img/icons/min-w-30.png 3x, img/icons/min-w-30.png 3.5x" id="min-button"
draggable="false" onMouseOver={() => setHoverMin(true)}
alt="" onMouseLeave={() => setHoverMin(false)}
/> >
</WindowControlButton> <img
<WindowControlButton maxButton className="button" id="max-button"> className="icon"
<img draggable="false"
className="icon" alt=""
srcSet="img/icons/max-w-10.png 1x, img/icons/max-w-12.png 1.25x, img/icons/max-w-15.png 1.5x, img/icons/max-w-15.png 1.75x, img/icons/max-w-20.png 2x, img/icons/max-w-20.png 2.25x, img/icons/max-w-24.png 2.5x, img/icons/max-w-30.png 3x, img/icons/max-w-30.png 3.5x" src={`img/icons/min-mac${hoverMin ? '-hover' : ''}.png`}
draggable="false" />
alt="" </MacControlButton>
/> <MacControlButton
</WindowControlButton> maxButton
<WindowControlButton restoreButton className="button" id="restore-button"> className="button"
<img id="max-button"
className="icon" onMouseOver={() => setHoverMax(true)}
srcSet="img/icons/restore-w-10.png 1x, img/icons/restore-w-12.png 1.25x, img/icons/restore-w-15.png 1.5x, img/icons/restore-w-15.png 1.75x, img/icons/restore-w-20.png 2x, img/icons/restore-w-20.png 2.25x, img/icons/restore-w-24.png 2.5x, img/icons/restore-w-30.png 3x, img/icons/restore-w-30.png 3.5x" onMouseLeave={() => setHoverMax(false)}
draggable="false" >
alt="" <img
/> className="icon"
</WindowControlButton> draggable="false"
<WindowControlButton className="button" id="close-button"> alt=""
<img src={`img/icons/max-mac${hoverMax ? '-hover' : ''}.png`}
className="icon" />
srcSet="img/icons/close-w-10.png 1x, img/icons/close-w-12.png 1.25x, img/icons/close-w-15.png 1.5x, img/icons/close-w-15.png 1.75x, img/icons/close-w-20.png 2x, img/icons/close-w-20.png 2.25x, img/icons/close-w-24.png 2.5x, img/icons/close-w-30.png 3x, img/icons/close-w-30.png 3.5x" </MacControlButton>
draggable="false" <MacControlButton
alt="" restoreButton
/> className="button"
</WindowControlButton> id="restore-button"
</WindowControl> onMouseOver={() => setHoverMax(true)}
onMouseLeave={() => setHoverMax(false)}
>
<img
className="icon"
src={`img/icons/max-mac${hoverMax ? '-hover' : ''}.png`}
draggable="false"
alt=""
/>
</MacControlButton>
<MacControlButton
className="button"
id="close-button"
onMouseOver={() => setHoverClose(true)}
onMouseLeave={() => setHoverClose(false)}
>
<img
className="icon"
src={`img/icons/close-mac${hoverClose ? '-hover' : ''}.png`}
draggable="false"
alt=""
/>
</MacControlButton>
</MacControl>
</>
)}
{misc.titleBar === 'windows' && (
<>
<div id="window-title-wrapper">
<span id="window-title">
<img src={logo} height="20px" width="20px" alt="" style={{ marginRight: '5px' }} />
{title}
</span>
</div>
<WindowControl id="window-controls">
<WindowControlButton minButton className="button" id="min-button">
<img
className="icon"
srcSet="img/icons/min-w-10.png 1x, img/icons/min-w-12.png 1.25x, img/icons/min-w-15.png 1.5x, img/icons/min-w-15.png 1.75x, img/icons/min-w-20.png 2x, img/icons/min-w-20.png 2.25x, img/icons/min-w-24.png 2.5x, img/icons/min-w-30.png 3x, img/icons/min-w-30.png 3.5x"
draggable="false"
alt=""
/>
</WindowControlButton>
<WindowControlButton maxButton className="button" id="max-button">
<img
className="icon"
srcSet="img/icons/max-w-10.png 1x, img/icons/max-w-12.png 1.25x, img/icons/max-w-15.png 1.5x, img/icons/max-w-15.png 1.75x, img/icons/max-w-20.png 2x, img/icons/max-w-20.png 2.25x, img/icons/max-w-24.png 2.5x, img/icons/max-w-30.png 3x, img/icons/max-w-30.png 3.5x"
draggable="false"
alt=""
/>
</WindowControlButton>
<WindowControlButton restoreButton className="button" id="restore-button">
<img
className="icon"
srcSet="img/icons/restore-w-10.png 1x, img/icons/restore-w-12.png 1.25x, img/icons/restore-w-15.png 1.5x, img/icons/restore-w-15.png 1.75x, img/icons/restore-w-20.png 2x, img/icons/restore-w-20.png 2.25x, img/icons/restore-w-24.png 2.5x, img/icons/restore-w-30.png 3x, img/icons/restore-w-30.png 3.5x"
draggable="false"
alt=""
/>
</WindowControlButton>
<WindowControlButton className="button" id="close-button">
<img
className="icon"
srcSet="img/icons/close-w-10.png 1x, img/icons/close-w-12.png 1.25x, img/icons/close-w-15.png 1.5x, img/icons/close-w-15.png 1.75x, img/icons/close-w-20.png 2x, img/icons/close-w-20.png 2.25x, img/icons/close-w-24.png 2.5x, img/icons/close-w-30.png 3x, img/icons/close-w-30.png 3.5x"
draggable="false"
alt=""
/>
</WindowControlButton>
</WindowControl>
</>
)}
</DragRegion> </DragRegion>
</TitleHeader> </TitleHeader>
); );

25
src/components/layout/styled.tsx

@ -71,6 +71,31 @@ export const WindowControl = styled.div`
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
`; `;
export const MacControl = styled.div`
display: grid;
grid-template-columns: repeat(3, 30px);
position: absolute;
top: 0;
left: 0;
height: 100%;
-webkit-app-region: no-drag;
`;
export const MacControlButton = styled.div<{
minButton?: boolean;
maxButton?: boolean;
restoreButton?: boolean;
}>`
user-select: none;
grid-row: 1 / span 1;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
grid-column: ${(props) => (props.minButton ? 2 : props.maxButton || props.restoreButton ? 3 : 1)};
`;
export const WindowControlButton = styled.div<{ export const WindowControlButton = styled.div<{
minButton?: boolean; minButton?: boolean;
maxButton?: boolean; maxButton?: boolean;

27
src/components/settings/ConfigPanels/LookAndFeelConfig.tsx

@ -12,7 +12,7 @@ import {
import ListViewConfig from './ListViewConfig'; import ListViewConfig from './ListViewConfig';
import { Fonts } from '../Fonts'; import { Fonts } from '../Fonts';
import { useAppDispatch } from '../../../redux/hooks'; import { useAppDispatch } from '../../../redux/hooks';
import { setTheme, setFont, setDynamicBackground } from '../../../redux/miscSlice'; import { setTheme, setFont, setDynamicBackground, setMiscSetting } from '../../../redux/miscSlice';
import { import {
songColumnPicker, songColumnPicker,
songColumnList, songColumnList,
@ -83,6 +83,7 @@ const LookAndFeelConfig = () => {
<StyledInputPicker <StyledInputPicker
data={Fonts} data={Fonts}
groupBy="role" groupBy="role"
cleanable={false}
defaultValue={String(settings.getSync('font'))} defaultValue={String(settings.getSync('font'))}
onChange={(e: string) => { onChange={(e: string) => {
settings.setSync('font', e); settings.setSync('font', e);
@ -91,6 +92,30 @@ const LookAndFeelConfig = () => {
/> />
</div> </div>
<br /> <br />
<div>
<ControlLabel>Titlebar style (requires app restart)</ControlLabel>
<br />
<StyledInputPicker
data={[
{
label: 'macOS',
value: 'mac',
},
{
label: 'Windows',
value: 'windows',
},
]}
cleanable={false}
defaultValue={String(settings.getSync('titleBarStyle'))}
onChange={(e: string) => {
settings.setSync('titleBarStyle', e);
dispatch(setMiscSetting({ setting: 'titleBar', value: e }));
}}
/>
</div>
<br />
<ConfigPanel header="List-View" bordered> <ConfigPanel header="List-View" bordered>
<p>Select the columns you want displayed on pages with a list-view.</p> <p>Select the columns you want displayed on pages with a list-view.</p>
<StyledCheckbox <StyledCheckbox

4
src/components/shared/setDefaultSettings.ts

@ -26,6 +26,10 @@ const setDefaultSettings = (force: boolean) => {
settings.setSync('cachePath', path.join(path.dirname(settings.file()))); settings.setSync('cachePath', path.join(path.dirname(settings.file())));
} }
if (force || !settings.hasSync('titleBarStyle')) {
settings.setSync('titleBarStyle', 'windows');
}
if (force || !settings.hasSync('scrobble')) { if (force || !settings.hasSync('scrobble')) {
settings.setSync('scrobble', false); settings.setSync('scrobble', false);
} }

BIN
src/img/icons/close-mac-hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

BIN
src/img/icons/close-mac.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

BIN
src/img/icons/max-mac-hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

BIN
src/img/icons/max-mac.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

BIN
src/img/icons/min-mac-hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

BIN
src/img/icons/min-mac.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

13
src/redux/miscSlice.ts

@ -42,6 +42,7 @@ export interface General {
isProcessingPlaylist: string[]; isProcessingPlaylist: string[];
contextMenu: ContextMenu; contextMenu: ContextMenu;
dynamicBackground: boolean; dynamicBackground: boolean;
titleBar: 'windows' | 'mac' | string;
} }
const initialState: General = { const initialState: General = {
@ -58,6 +59,7 @@ const initialState: General = {
show: false, show: false,
}, },
dynamicBackground: Boolean(parsedSettings.dynamicBackground), dynamicBackground: Boolean(parsedSettings.dynamicBackground),
titleBar: String(parsedSettings.titleBarStyle),
}; };
const miscSlice = createSlice({ const miscSlice = createSlice({
@ -72,6 +74,16 @@ const miscSlice = createSlice({
state.expandSidebar = action.payload; state.expandSidebar = action.payload;
}, },
setMiscSetting: (state, action: PayloadAction<{ setting: string; value: any }>) => {
switch (action.payload.setting) {
case 'titleBar':
state.titleBar = action.payload.value;
break;
default:
break;
}
},
setContextMenu: (state, action: PayloadAction<ContextMenu>) => { setContextMenu: (state, action: PayloadAction<ContextMenu>) => {
state.contextMenu.show = action.payload.show; state.contextMenu.show = action.payload.show;
state.contextMenu.xPos = action.payload.xPos; state.contextMenu.xPos = action.payload.xPos;
@ -156,5 +168,6 @@ export const {
setContextMenu, setContextMenu,
setExpandSidebar, setExpandSidebar,
setDynamicBackground, setDynamicBackground,
setMiscSetting,
} = miscSlice.actions; } = miscSlice.actions;
export default miscSlice.reducer; export default miscSlice.reducer;

19
src/styles/App.global.css

@ -106,6 +106,25 @@ body {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
user-select: none;
}
#window-title-wrapper-mac {
white-space: nowrap;
height: 100%;
padding: 2px;
text-align: center;
}
#window-title-mac {
max-width: 60%;
width: 100%;
display: inline-block;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
user-select: none;
} }
#restore-button { #restore-button {

Loading…
Cancel
Save