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
|
<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')"
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.clearTextButtonExisting = true
|
||||||
|
// The transition is not rendered if this property is set right after
|
||||||
|
// It's visible
|
||||||
|
setTimeout(() => {
|
||||||
this.clearTextButtonVisible = true
|
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'
|
||||||
|
])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue