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:
PikachuEXE 2021-10-01 15:38:33 +08:00 committed by GitHub
parent 7d0880ad05
commit 87e1093c4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 104 additions and 26 deletions

View File

@ -33,7 +33,7 @@
> >
<ft-input <ft-input
:placeholder="$t('Settings.External Player Settings.Custom External Player Executable')" :placeholder="$t('Settings.External Player Settings.Custom External Player Executable')"
:show-arrow="false" :show-action-button="false"
:show-label="true" :show-label="true"
:value="externalPlayerExecutable" :value="externalPlayerExecutable"
:tooltip="$t('Tooltips.External Player Settings.Custom External Player Executable')" :tooltip="$t('Tooltips.External Player Settings.Custom External Player Executable')"
@ -41,7 +41,7 @@
/> />
<ft-input <ft-input
:placeholder="$t('Settings.External Player Settings.Custom External Player Arguments')" :placeholder="$t('Settings.External Player Settings.Custom External Player Arguments')"
:show-arrow="false" :show-action-button="false"
:show-label="true" :show-label="true"
:value="externalPlayerCustomArgs" :value="externalPlayerCustomArgs"
:tooltip="$t('Tooltips.External Player Settings.Custom External Player Arguments')" :tooltip="$t('Tooltips.External Player Settings.Custom External Player Arguments')"

View File

@ -90,10 +90,17 @@
position: absolute; position: absolute;
padding: 10px 8px; padding: 10px 8px;
top: 5px; top: 5px;
right: 0px; right: 0;
cursor: pointer;
border-radius: 200px 200px 200px 200px; border-radius: 200px 200px 200px 200px;
color: var(--primary-text-color); 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 { .search ::-webkit-calendar-picker-indicator {
@ -108,7 +115,7 @@
color: #EEEEEE; color: #EEEEEE;
} }
.ft-input-component.showArrow .ft-input { .ft-input-component.showActionButton .ft-input {
/* /*
With arrow present means With arrow present means
the text might get under the arrow with normal padding the text might get under the arrow with normal padding
@ -116,25 +123,25 @@
padding-right: 2em; padding-right: 2em;
} }
.inputAction:hover { .inputAction.enabled:hover {
background-color: var(--side-nav-hover-color); background-color: var(--side-nav-hover-color);
-moz-transition: background 0.2s ease-in; -moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in; -o-transition: background 0.2s ease-in;
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); background-color: var(--primary-color-hover);
} }
.inputAction:active { .inputAction.enabled:active {
background-color: var(--tertiary-text-color); background-color: var(--tertiary-text-color);
-moz-transition: background 0.2s ease-in; -moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in; -o-transition: background 0.2s ease-in;
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); background-color: var(--primary-color-active);
} }

View File

@ -1,5 +1,6 @@
import Vue from 'vue' import Vue from 'vue'
import FtTooltip from '../ft-tooltip/ft-tooltip.vue' import FtTooltip from '../ft-tooltip/ft-tooltip.vue'
import { mapActions } from 'vuex'
export default Vue.extend({ export default Vue.extend({
name: 'FtInput', name: 'FtInput',
@ -15,7 +16,7 @@ export default Vue.extend({
type: String, type: String,
default: '' default: ''
}, },
showArrow: { showActionButton: {
type: Boolean, type: Boolean,
default: true default: true
}, },
@ -63,7 +64,9 @@ export default Vue.extend({
}, },
// This button should be invisible on app start // This button should be invisible on app start
// As the text input box should be empty // As the text input box should be empty
clearTextButtonVisible: false clearTextButtonExisting: false,
clearTextButtonVisible: false,
actionButtonIconName: 'search'
} }
}, },
computed: { computed: {
@ -91,12 +94,19 @@ export default Vue.extend({
if (newVal) { if (newVal) {
// The button needs to be visible **immediately** // The button needs to be visible **immediately**
// To allow user to see the transition // To allow user to see the transition
this.clearTextButtonVisible = true this.clearTextButtonExisting = true
// The transition is not rendered if this property is set right after
// It's visible
setTimeout(() => {
this.clearTextButtonVisible = true
}, 0)
} else { } 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 // 0.2s in CSS = 200ms in JS
setTimeout(() => { setTimeout(() => {
this.clearTextButtonVisible = false this.clearTextButtonExisting = false
}, 200) }, 200)
} }
} }
@ -109,6 +119,9 @@ export default Vue.extend({
}, },
methods: { methods: {
handleClick: function () { handleClick: function () {
// No action if no input text
if (!this.inputDataPresent) { return }
this.searchState.showOptions = false this.searchState.showOptions = false
this.$emit('input', this.inputData) this.$emit('input', this.inputData)
this.$emit('click', this.inputData) this.$emit('click', this.inputData)
@ -118,11 +131,13 @@ export default Vue.extend({
if (this.isSearch && if (this.isSearch &&
this.searchState.selectedOption !== -1 && this.searchState.selectedOption !== -1 &&
this.inputData === this.dataList[this.searchState.selectedOption]) { return } this.inputData === this.dataList[this.searchState.selectedOption]) { return }
this.handleActionIconChange()
this.$emit('input', this.inputData) this.$emit('input', this.inputData)
}, },
handleClearTextClick: function () { handleClearTextClick: function () {
this.inputData = '' this.inputData = ''
this.handleActionIconChange()
this.$emit('input', this.inputData) this.$emit('input', this.inputData)
// Focus on input element after text is clear for better UX // Focus on input element after text is clear for better UX
@ -130,6 +145,55 @@ export default Vue.extend({
inputElement.focus() 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 () { addListener: function () {
const inputElement = document.getElementById(this.id) const inputElement = document.getElementById(this.id)
@ -178,6 +242,10 @@ export default Vue.extend({
if (this.selectOnFocus) { if (this.selectOnFocus) {
e.target.select() e.target.select()
} }
} },
...mapActions([
'getYoutubeUrlInfo'
])
} }
}) })

View File

@ -4,7 +4,7 @@
:class="{ :class="{
search: isSearch, search: isSearch,
forceTextColor: forceTextColor, forceTextColor: forceTextColor,
showArrow: showArrow, showActionButton: showActionButton,
showClearTextButton: showClearTextButton showClearTextButton: showClearTextButton
}" }"
> >
@ -21,11 +21,11 @@
/> />
</label> </label>
<font-awesome-icon <font-awesome-icon
v-if="showClearTextButton && clearTextButtonVisible" v-if="showClearTextButton && clearTextButtonExisting"
icon="times-circle" icon="times-circle"
class="clearInputTextButton" class="clearInputTextButton"
:class="{ :class="{
visible: inputDataPresent visible: clearTextButtonVisible
}" }"
tabindex="0" tabindex="0"
role="button" role="button"
@ -49,9 +49,12 @@
@keydown="e => handleKeyDown(e.keyCode)" @keydown="e => handleKeyDown(e.keyCode)"
> >
<font-awesome-icon <font-awesome-icon
v-if="showArrow" v-if="showActionButton"
icon="arrow-right" :icon="actionButtonIconName"
class="inputAction" class="inputAction"
:class="{
enabled: inputDataPresent
}"
@click="handleClick" @click="handleClick"
/> />

View File

@ -7,7 +7,7 @@
class="profileName" class="profileName"
placeholder="Profile Name" placeholder="Profile Name"
:value="profileName" :value="profileName"
:show-arrow="false" :show-action-button="false"
@input="e => profileName = e" @input="e => profileName = e"
/> />
</ft-flex-box> </ft-flex-box>
@ -40,7 +40,7 @@
class="profileName" class="profileName"
placeholder="" placeholder=""
:value="profileBgColor" :value="profileBgColor"
:show-arrow="false" :show-action-button="false"
:disabled="true" :disabled="true"
/> />
</ft-flex-box> </ft-flex-box>

View File

@ -96,7 +96,7 @@
<ft-flex-box class="generalSettingsFlexBox"> <ft-flex-box class="generalSettingsFlexBox">
<ft-input <ft-input
:placeholder="$t('Settings.General Settings.Current Invidious Instance')" :placeholder="$t('Settings.General Settings.Current Invidious Instance')"
:show-arrow="false" :show-action-button="false"
:show-label="true" :show-label="true"
:value="currentInvidiousInstance" :value="currentInvidiousInstance"
:data-list="invidiousInstancesList" :data-list="invidiousInstancesList"

View File

@ -25,14 +25,14 @@
<ft-flex-box> <ft-flex-box>
<ft-input <ft-input
:placeholder="$t('Settings.Proxy Settings.Proxy Host')" :placeholder="$t('Settings.Proxy Settings.Proxy Host')"
:show-arrow="false" :show-action-button="false"
:show-label="true" :show-label="true"
:value="proxyHostname" :value="proxyHostname"
@input="handleUpdateProxyHostname" @input="handleUpdateProxyHostname"
/> />
<ft-input <ft-input
:placeholder="$t('Settings.Proxy Settings.Proxy Port Number')" :placeholder="$t('Settings.Proxy Settings.Proxy Port Number')"
:show-arrow="false" :show-action-button="false"
:show-label="true" :show-label="true"
:value="proxyPort" :value="proxyPort"
@input="handleUpdateProxyPort" @input="handleUpdateProxyPort"

View File

@ -23,7 +23,7 @@
<ft-flex-box> <ft-flex-box>
<ft-input <ft-input
:placeholder="$t('Settings.SponsorBlock Settings[\'SponsorBlock API Url (Default is https://sponsor.ajay.app)\']')" :placeholder="$t('Settings.SponsorBlock Settings[\'SponsorBlock API Url (Default is https://sponsor.ajay.app)\']')"
:show-arrow="false" :show-action-button="false"
:show-label="true" :show-label="true"
:value="sponsorBlockUrl" :value="sponsorBlockUrl"
@input="handleUpdateSponsorBlockUrl" @input="handleUpdateSponsorBlockUrl"