Skip to content

Commit c026836

Browse files
kommunarrJL
authored and
JL
committed
Navigation history dropdowns (FreeTubeApp#5227)
* Supplant mousedown handler for ft-icon-button * Implement navigation history dropdowns * Update top nav to handle icon button styling * Implement long-press handling & adjust arrow logic * Prevent context menu from appearing in dev * Implement base nav history range determination logic * Solidify route.meta.title labelling & specify future steps * Improve readability & fix top-nav-with-main-color icon button styling * Update nav icon titles to reflect new behavior, & make their title text show even when disabled * Add checkmark icon to active history entry * Update checkmark styling to have uniform flex-start alignment * Fix top bar with main color navigation arrow icon bug * Update to use the NavigationHistory API * Implement workaround for updating the current page title in the dropdown list * Have document title update properly * Fix webpack build issue * Fix document.title not populating on the first page after loading the app. * Fix linting * Update app title for hashtag route * Update search page titles to include query * Remove hardcoded disabled coloring * Fix 'FreeTube' title appearing for community posts & on first search navigation
1 parent 1de687c commit c026836

File tree

18 files changed

+316
-84
lines changed

18 files changed

+316
-84
lines changed

src/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ const IpcChannels = {
55
OPEN_EXTERNAL_LINK: 'open-external-link',
66
GET_SYSTEM_LOCALE: 'get-system-locale',
77
GET_PICTURES_PATH: 'get-pictures-path',
8+
GET_NAV_HISTORY_ENTRY_TITLE_AT_INDEX: 'get-navigation-history-entry-at-index',
9+
GET_NAV_HISTORY_ACTIVE_INDEX: 'get-navigation-history-active-index',
10+
GET_NAV_HISTORY_LENGTH: 'get-navigation-history-length',
11+
GO_TO_NAV_HISTORY_OFFSET: 'go-to-navigation-history-index',
812
SHOW_OPEN_DIALOG: 'show-open-dialog',
913
SHOW_SAVE_DIALOG: 'show-save-dialog',
1014
STOP_POWER_SAVE_BLOCKER: 'stop-power-save-blocker',

src/main/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,26 @@ function runApp() {
892892
session.defaultSession.closeAllConnections()
893893
})
894894

895+
// #region navigation history
896+
897+
ipcMain.on(IpcChannels.GO_TO_NAV_HISTORY_OFFSET, ({ sender }, offset) => {
898+
sender.navigationHistory.goToOffset(offset)
899+
})
900+
901+
ipcMain.handle(IpcChannels.GET_NAV_HISTORY_ENTRY_TITLE_AT_INDEX, async ({ sender }, index) => {
902+
return sender.navigationHistory.getEntryAtIndex(index)?.title
903+
})
904+
905+
ipcMain.handle(IpcChannels.GET_NAV_HISTORY_ACTIVE_INDEX, async ({ sender }) => {
906+
return sender.navigationHistory.getActiveIndex()
907+
})
908+
909+
ipcMain.handle(IpcChannels.GET_NAV_HISTORY_LENGTH, async ({ sender }) => {
910+
return sender.navigationHistory.length()
911+
})
912+
913+
// #endregion navigation history
914+
895915
ipcMain.handle(IpcChannels.OPEN_EXTERNAL_LINK, (_, url) => {
896916
if (typeof url === 'string') {
897917
let parsedURL

src/renderer/App.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export default defineComponent({
9797
externalPlayer: function () {
9898
return this.$store.getters.getExternalPlayer
9999
},
100+
100101
defaultInvidiousInstance: function () {
101102
return this.$store.getters.getDefaultInvidiousInstance
102103
},
@@ -144,6 +145,10 @@ export default defineComponent({
144145
return this.$store.getters.getExternalLinkHandling
145146
},
146147

148+
appTitle: function () {
149+
return this.$store.getters.getAppTitle
150+
},
151+
147152
openDeepLinksInNewWindow: function () {
148153
return this.$store.getters.getOpenDeepLinksInNewWindow
149154
}
@@ -158,10 +163,11 @@ export default defineComponent({
158163
secColor: 'checkThemeSettings',
159164

160165
locale: 'setLocale',
166+
167+
appTitle: 'setDocumentTitle'
161168
},
162169
created () {
163170
this.checkThemeSettings()
164-
this.setWindowTitle()
165171
this.setLocale()
166172
},
167173
mounted: function () {
@@ -207,10 +213,16 @@ export default defineComponent({
207213
if (this.$router.currentRoute.path === '/') {
208214
this.$router.replace({ path: this.landingPage })
209215
}
216+
217+
this.setWindowTitle()
210218
})
211219
})
212220
},
213221
methods: {
222+
setDocumentTitle: function(value) {
223+
document.title = value
224+
this.$nextTick(() => this.$refs.topNav?.setActiveNavigationHistoryEntryTitle(value))
225+
},
214226
checkThemeSettings: function () {
215227
const theme = {
216228
baseTheme: this.baseTheme || 'dark',
@@ -539,7 +551,7 @@ export default defineComponent({
539551

540552
setWindowTitle: function() {
541553
if (this.windowTitle !== null) {
542-
document.title = this.windowTitle
554+
this.setAppTitle(this.windowTitle)
543555
}
544556
},
545557

@@ -562,6 +574,7 @@ export default defineComponent({
562574
'getExternalPlayerCmdArgumentsData',
563575
'fetchInvidiousInstances',
564576
'fetchInvidiousInstancesFromFile',
577+
'setAppTitle',
565578
'setRandomCurrentInvidiousInstance',
566579
'setupListenersToSyncWindows',
567580
'updateBaseTheme',

src/renderer/components/ft-icon-button/ft-icon-button.js

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { defineComponent, nextTick } from 'vue'
22
import FtPrompt from '../ft-prompt/ft-prompt.vue'
33
import { sanitizeForHtmlId } from '../../helpers/accessibility'
44

5+
const LONG_CLICK_BOUNDARY_MS = 500
6+
57
export default defineComponent({
68
name: 'FtIconButton',
79
components: {
@@ -55,21 +57,27 @@ export default defineComponent({
5557
dropdownOptions: {
5658
// Array of objects with these properties
5759
// - type: ('labelValue'|'divider', default to 'labelValue' for less typing)
58-
// - label: String (if type == 'labelValue')
59-
// - value: String (if type == 'labelValue')
60+
// - label: String (if type === 'labelValue')
61+
// - value: String (if type === 'labelValue')
62+
// - (OPTIONAL) active: Number (if type === 'labelValue')
6063
type: Array,
6164
default: () => { return [] }
6265
},
6366
dropdownModalOnMobile: {
6467
type: Boolean,
6568
default: false
69+
},
70+
openOnRightOrLongClick: {
71+
type: Boolean,
72+
default: false
6673
}
6774
},
6875
emits: ['click', 'disabled-click'],
6976
data: function () {
7077
return {
7178
dropdownShown: false,
72-
mouseDownOnIcon: false,
79+
blockLeftClick: false,
80+
longPressTimer: null,
7381
useModal: false
7482
}
7583
},
@@ -91,14 +99,24 @@ export default defineComponent({
9199
this.dropdownShown = false
92100
},
93101

94-
handleIconClick: function () {
102+
handleIconClick: function (e, isRightOrLongClick = false) {
95103
if (this.disabled) {
96104
this.$emit('disabled-click')
97105
return
98106
}
99-
if (this.forceDropdown || (this.dropdownOptions.length > 0)) {
100-
this.dropdownShown = !this.dropdownShown
101107

108+
if (this.blockLeftClick) {
109+
return
110+
}
111+
112+
if (this.longPressTimer != null) {
113+
clearTimeout(this.longPressTimer)
114+
this.longPressTimer = null
115+
}
116+
117+
if ((!this.openOnRightOrLongClick || (this.openOnRightOrLongClick && isRightOrLongClick)) &&
118+
(this.forceDropdown || this.dropdownOptions.length > 0)) {
119+
this.dropdownShown = !this.dropdownShown
102120
if (this.dropdownShown && !this.useModal) {
103121
// wait until the dropdown is visible
104122
// then focus it so we can hide it automatically when it loses focus
@@ -111,24 +129,38 @@ export default defineComponent({
111129
}
112130
},
113131

114-
handleIconMouseDown: function () {
115-
if (this.disabled) { return }
116-
if (this.dropdownShown) {
117-
this.mouseDownOnIcon = true
132+
handleIconPointerDown: function (event) {
133+
if (!this.openOnRightOrLongClick) { return }
134+
if (event.button === 2) { // right button click
135+
this.handleIconClick(null, true)
136+
} else if (event.button === 0) { // left button click
137+
this.longPressTimer = setTimeout(() => {
138+
this.handleIconClick(null, true)
139+
140+
// prevent a long press that ends on the icon button from firing the handleIconClick handler
141+
window.addEventListener('pointerup', this.preventButtonClickAfterLongPress, { once: true })
142+
window.addEventListener('pointercancel', () => {
143+
window.removeEventListener('pointerup', this.preventButtonClickAfterLongPress)
144+
}, { once: true })
145+
}, LONG_CLICK_BOUNDARY_MS)
118146
}
119147
},
120148

149+
// prevent the handleIconClick handler from firing for an instant
150+
preventButtonClickAfterLongPress: function () {
151+
this.blockLeftClick = true
152+
setTimeout(() => { this.blockLeftClick = false }, 0)
153+
},
154+
121155
handleDropdownFocusOut: function () {
122-
if (this.mouseDownOnIcon) {
123-
this.mouseDownOnIcon = false
124-
} else if (!this.$refs.dropdown.matches(':focus-within')) {
156+
if (this.dropdownShown && !this.$refs.ftIconButton.matches(':focus-within')) {
125157
this.dropdownShown = false
126158
}
127159
},
128160

129161
handleDropdownEscape: function () {
130-
this.$refs.iconButton.focus()
131-
// handleDropdownFocusOut will hide the dropdown for us
162+
this.dropdownShown = false
163+
this.$refs.ftIconButton.firstElementChild.focus()
132164
},
133165

134166
handleDropdownClick: function ({ url, index }) {

src/renderer/components/ft-icon-button/ft-icon-button.scss

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
flex-flow: row wrap;
55
justify-content: space-evenly;
66
position: relative;
7+
line-height: normal;
78
user-select: none;
89
}
910

@@ -24,12 +25,13 @@
2425

2526
&:not(.disabled) {
2627
&:hover,
27-
&:focus-visible {
28+
&:focus-visible,
29+
&.pressed {
2830
background-color: var(--side-nav-hover-color);
2931
color: var(--side-nav-hover-text-color);
3032
}
3133

32-
&:active {
34+
&.active {
3335
background-color: var(--side-nav-active-color);
3436
color: var(--side-nav-active-text-color);
3537
}
@@ -38,12 +40,13 @@
3840

3941
&.base-no-default:not(.disabled) {
4042
&:hover,
41-
&:focus-visible {
43+
&:focus-visible,
44+
&.pressed {
4245
background-color: var(--side-nav-hover-color);
4346
color: var(--side-nav-hover-text-color);
4447
}
4548

46-
&:active {
49+
&.active {
4750
background-color: var(--side-nav-active-color);
4851
color: var(--side-nav-active-text-color);
4952
}
@@ -55,11 +58,12 @@
5558

5659
&:not(.disabled) {
5760
&:hover,
58-
&:focus-visible {
61+
&:focus-visible,
62+
&.pressed {
5963
background-color: var(--primary-color-hover);
6064
}
6165

62-
&:active {
66+
&.active {
6367
background-color: var(--primary-color-active);
6468
}
6569
}
@@ -72,11 +76,12 @@
7276

7377
&:not(.disabled) {
7478
&:hover,
75-
&:focus-visible {
79+
&:focus-visible,
80+
&.pressed {
7681
background-color: var(--accent-color-hover);
7782
}
7883

79-
&:active {
84+
&.active {
8085
background-color: var(--accent-color-active);
8186
}
8287
}
@@ -88,11 +93,12 @@
8893

8994
&:not(.disabled) {
9095
&:hover,
91-
&:focus-visible {
96+
&:focus-visible,
97+
&.pressed {
9298
background-color: var(--destructive-hover-color);
9399
}
94100

95-
&:active {
101+
&.active {
96102
background-color: var(--destructive-active-color);
97103
}
98104
}
@@ -150,6 +156,17 @@
150156
padding: 0;
151157
}
152158

159+
:has(.active) {
160+
.checkmarkColumn {
161+
min-inline-size: 12px;
162+
}
163+
164+
.listItem {
165+
display: flex;
166+
gap: 6px;
167+
}
168+
}
169+
153170
.listItem {
154171
cursor: pointer;
155172
margin: 0;
@@ -159,12 +176,20 @@
159176
white-space: nowrap;
160177

161178
&:hover,
162-
&:focus-visible {
179+
&:focus-visible,
180+
&.pressed,
181+
&.active {
163182
background-color: var(--side-nav-hover-color);
164183
color: var(--side-nav-hover-text-color);
165184
transition: background 0.2s ease-in;
166185
}
167186

187+
&.active {
188+
font-weight: 600;
189+
display: flex;
190+
gap: 6px;
191+
}
192+
168193
&:active {
169194
background-color: var(--side-nav-active-color);
170195
color: var(--side-nav-active-text-color);

0 commit comments

Comments
 (0)