Update top nav input box action button icon depends on input (#1738)
* Rename `showArrow` to `showActionButton` * * Display different icon for action button when input text look like a Youtube URL * ! Fix transition for button appearing absent * * Update to use new icon for all FT supported URLs * Update src/renderer/components/ft-input/ft-input.js Co-authored-by: Jason <84899178+jasonhenriquez@users.noreply.github.com> * * Update action button to look disabled when input text is empty * * Disable button hover/active visual effect when "disabled" * * Make action button only respond to cursor when enabled Co-authored-by: Jason <84899178+jasonhenriquez@users.noreply.github.com>
This commit is contained in:
parent
7d0880ad05
commit
87e1093c4d
|
@ -33,7 +33,7 @@
|
|||
>
|
||||
<ft-input
|
||||
:placeholder="$t('Settings.External Player Settings.Custom External Player Executable')"
|
||||
:show-arrow="false"
|
||||
:show-action-button="false"
|
||||
:show-label="true"
|
||||
:value="externalPlayerExecutable"
|
||||
:tooltip="$t('Tooltips.External Player Settings.Custom External Player Executable')"
|
||||
|
@ -41,7 +41,7 @@
|
|||
/>
|
||||
<ft-input
|
||||
:placeholder="$t('Settings.External Player Settings.Custom External Player Arguments')"
|
||||
:show-arrow="false"
|
||||
:show-action-button="false"
|
||||
:show-label="true"
|
||||
:value="externalPlayerCustomArgs"
|
||||
:tooltip="$t('Tooltips.External Player Settings.Custom External Player Arguments')"
|
||||
|
|
|
@ -90,10 +90,17 @@
|
|||
position: absolute;
|
||||
padding: 10px 8px;
|
||||
top: 5px;
|
||||
right: 0px;
|
||||
cursor: pointer;
|
||||
right: 0;
|
||||
border-radius: 200px 200px 200px 200px;
|
||||
color: var(--primary-text-color);
|
||||
/* this should look disabled by default */
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.inputAction.enabled {
|
||||
opacity: 100%;
|
||||
/* Only look respond to cursor when enabled */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search ::-webkit-calendar-picker-indicator {
|
||||
|
@ -108,7 +115,7 @@
|
|||
color: #EEEEEE;
|
||||
}
|
||||
|
||||
.ft-input-component.showArrow .ft-input {
|
||||
.ft-input-component.showActionButton .ft-input {
|
||||
/*
|
||||
With arrow present means
|
||||
the text might get under the arrow with normal padding
|
||||
|
@ -116,25 +123,25 @@
|
|||
padding-right: 2em;
|
||||
}
|
||||
|
||||
.inputAction:hover {
|
||||
.inputAction.enabled:hover {
|
||||
background-color: var(--side-nav-hover-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
.forceTextColor .inputAction:hover {
|
||||
.forceTextColor .inputAction.enabled:hover {
|
||||
background-color: var(--primary-color-hover);
|
||||
}
|
||||
|
||||
.inputAction:active {
|
||||
.inputAction.enabled:active {
|
||||
background-color: var(--tertiary-text-color);
|
||||
-moz-transition: background 0.2s ease-in;
|
||||
-o-transition: background 0.2s ease-in;
|
||||
transition: background 0.2s ease-in;
|
||||
}
|
||||
|
||||
.forceTextColor .inputAction:active {
|
||||
.forceTextColor .inputAction.enabled:active {
|
||||
background-color: var(--primary-color-active);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import FtTooltip from '../ft-tooltip/ft-tooltip.vue'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FtInput',
|
||||
|
@ -15,7 +16,7 @@ export default Vue.extend({
|
|||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showArrow: {
|
||||
showActionButton: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
@ -63,7 +64,9 @@ export default Vue.extend({
|
|||
},
|
||||
// This button should be invisible on app start
|
||||
// As the text input box should be empty
|
||||
clearTextButtonVisible: false
|
||||
clearTextButtonExisting: false,
|
||||
clearTextButtonVisible: false,
|
||||
actionButtonIconName: 'search'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -91,12 +94,19 @@ export default Vue.extend({
|
|||
if (newVal) {
|
||||
// The button needs to be visible **immediately**
|
||||
// To allow user to see the transition
|
||||
this.clearTextButtonExisting = true
|
||||
// The transition is not rendered if this property is set right after
|
||||
// It's visible
|
||||
setTimeout(() => {
|
||||
this.clearTextButtonVisible = true
|
||||
}, 0)
|
||||
} else {
|
||||
// Hide the button after the transition
|
||||
// Hide the button with transition
|
||||
this.clearTextButtonVisible = false
|
||||
// Remove the button after the transition
|
||||
// 0.2s in CSS = 200ms in JS
|
||||
setTimeout(() => {
|
||||
this.clearTextButtonVisible = false
|
||||
this.clearTextButtonExisting = false
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +119,9 @@ export default Vue.extend({
|
|||
},
|
||||
methods: {
|
||||
handleClick: function () {
|
||||
// No action if no input text
|
||||
if (!this.inputDataPresent) { return }
|
||||
|
||||
this.searchState.showOptions = false
|
||||
this.$emit('input', this.inputData)
|
||||
this.$emit('click', this.inputData)
|
||||
|
@ -118,11 +131,13 @@ export default Vue.extend({
|
|||
if (this.isSearch &&
|
||||
this.searchState.selectedOption !== -1 &&
|
||||
this.inputData === this.dataList[this.searchState.selectedOption]) { return }
|
||||
this.handleActionIconChange()
|
||||
this.$emit('input', this.inputData)
|
||||
},
|
||||
|
||||
handleClearTextClick: function () {
|
||||
this.inputData = ''
|
||||
this.handleActionIconChange()
|
||||
this.$emit('input', this.inputData)
|
||||
|
||||
// Focus on input element after text is clear for better UX
|
||||
|
@ -130,6 +145,55 @@ export default Vue.extend({
|
|||
inputElement.focus()
|
||||
},
|
||||
|
||||
handleActionIconChange: function() {
|
||||
// Only need to update icon if visible
|
||||
if (!this.showActionButton) { return }
|
||||
|
||||
if (!this.inputDataPresent) {
|
||||
// Change back to default icon if text is blank
|
||||
this.actionButtonIconName = 'search'
|
||||
return
|
||||
}
|
||||
|
||||
// Update action button icon according to input
|
||||
try {
|
||||
this.getYoutubeUrlInfo(this.inputData).then((result) => {
|
||||
let isYoutubeLink = false
|
||||
|
||||
switch (result.urlType) {
|
||||
case 'video':
|
||||
case 'playlist':
|
||||
case 'search':
|
||||
case 'channel':
|
||||
isYoutubeLink = true
|
||||
break
|
||||
case 'hashtag':
|
||||
// TODO: Implement a hashtag related view
|
||||
// isYoutubeLink is already `false`
|
||||
break
|
||||
|
||||
case 'invalid_url':
|
||||
default: {
|
||||
// isYoutubeLink is already `false`
|
||||
}
|
||||
}
|
||||
|
||||
if (isYoutubeLink) {
|
||||
// Go to URL (i.e. Video/Playlist/Channel
|
||||
this.actionButtonIconName = 'arrow-right'
|
||||
} else {
|
||||
// Search with text
|
||||
this.actionButtonIconName = 'search'
|
||||
}
|
||||
})
|
||||
} catch (ex) {
|
||||
// On exception, consider text as invalid URL
|
||||
this.actionButtonIconName = 'search'
|
||||
// Rethrow exception
|
||||
throw ex
|
||||
}
|
||||
},
|
||||
|
||||
addListener: function () {
|
||||
const inputElement = document.getElementById(this.id)
|
||||
|
||||
|
@ -178,6 +242,10 @@ export default Vue.extend({
|
|||
if (this.selectOnFocus) {
|
||||
e.target.select()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'getYoutubeUrlInfo'
|
||||
])
|
||||
}
|
||||
})
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
:class="{
|
||||
search: isSearch,
|
||||
forceTextColor: forceTextColor,
|
||||
showArrow: showArrow,
|
||||
showActionButton: showActionButton,
|
||||
showClearTextButton: showClearTextButton
|
||||
}"
|
||||
>
|
||||
|
@ -21,11 +21,11 @@
|
|||
/>
|
||||
</label>
|
||||
<font-awesome-icon
|
||||
v-if="showClearTextButton && clearTextButtonVisible"
|
||||
v-if="showClearTextButton && clearTextButtonExisting"
|
||||
icon="times-circle"
|
||||
class="clearInputTextButton"
|
||||
:class="{
|
||||
visible: inputDataPresent
|
||||
visible: clearTextButtonVisible
|
||||
}"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
|
@ -49,9 +49,12 @@
|
|||
@keydown="e => handleKeyDown(e.keyCode)"
|
||||
>
|
||||
<font-awesome-icon
|
||||
v-if="showArrow"
|
||||
icon="arrow-right"
|
||||
v-if="showActionButton"
|
||||
:icon="actionButtonIconName"
|
||||
class="inputAction"
|
||||
:class="{
|
||||
enabled: inputDataPresent
|
||||
}"
|
||||
@click="handleClick"
|
||||
/>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
class="profileName"
|
||||
placeholder="Profile Name"
|
||||
:value="profileName"
|
||||
:show-arrow="false"
|
||||
:show-action-button="false"
|
||||
@input="e => profileName = e"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
|
@ -40,7 +40,7 @@
|
|||
class="profileName"
|
||||
placeholder=""
|
||||
:value="profileBgColor"
|
||||
:show-arrow="false"
|
||||
:show-action-button="false"
|
||||
:disabled="true"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
<ft-flex-box class="generalSettingsFlexBox">
|
||||
<ft-input
|
||||
:placeholder="$t('Settings.General Settings.Current Invidious Instance')"
|
||||
:show-arrow="false"
|
||||
:show-action-button="false"
|
||||
:show-label="true"
|
||||
:value="currentInvidiousInstance"
|
||||
:data-list="invidiousInstancesList"
|
||||
|
|
|
@ -25,14 +25,14 @@
|
|||
<ft-flex-box>
|
||||
<ft-input
|
||||
:placeholder="$t('Settings.Proxy Settings.Proxy Host')"
|
||||
:show-arrow="false"
|
||||
:show-action-button="false"
|
||||
:show-label="true"
|
||||
:value="proxyHostname"
|
||||
@input="handleUpdateProxyHostname"
|
||||
/>
|
||||
<ft-input
|
||||
:placeholder="$t('Settings.Proxy Settings.Proxy Port Number')"
|
||||
:show-arrow="false"
|
||||
:show-action-button="false"
|
||||
:show-label="true"
|
||||
:value="proxyPort"
|
||||
@input="handleUpdateProxyPort"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<ft-flex-box>
|
||||
<ft-input
|
||||
:placeholder="$t('Settings.SponsorBlock Settings[\'SponsorBlock API Url (Default is https://sponsor.ajay.app)\']')"
|
||||
:show-arrow="false"
|
||||
:show-action-button="false"
|
||||
:show-label="true"
|
||||
:value="sponsorBlockUrl"
|
||||
@input="handleUpdateSponsorBlockUrl"
|
||||
|
|
Loading…
Reference in New Issue