mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-06-24 22:12:06 +02:00
Add variant support to the logo fetching feature
This commit is contained in:
parent
7ddb4276a6
commit
6909be3318
@ -57,7 +57,11 @@ class IconController extends Controller
|
|||||||
? $validated['iconCollection']
|
? $validated['iconCollection']
|
||||||
: $request->user()->preferences['iconCollection'];
|
: $request->user()->preferences['iconCollection'];
|
||||||
|
|
||||||
$icon = LogoLib::driver($iconCollection)->getIcon($validated['service']);
|
$variant = Arr::has($validated, 'variant') && $validated['variant']
|
||||||
|
? $validated['variant']
|
||||||
|
: 'regular';
|
||||||
|
|
||||||
|
$icon = LogoLib::driver($iconCollection)->getIcon($validated['service'], $variant);
|
||||||
|
|
||||||
return $icon
|
return $icon
|
||||||
? response()->json(['filename' => $icon], 201)
|
? response()->json(['filename' => $icon], 201)
|
||||||
|
@ -24,10 +24,29 @@ class IconFetchRequest extends FormRequest
|
|||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
return [
|
$rules = [
|
||||||
'service' => 'string',
|
'service' => 'string',
|
||||||
'iconCollection' => 'nullable|string|in:tfa,selfh,dashboardicons',
|
'iconCollection' => 'sometimes|required|string|in:tfa,selfh,dashboardicons',
|
||||||
|
'variant' => [
|
||||||
|
'sometimes',
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($this->input('iconCollection', null) === 'selfh') {
|
||||||
|
$rules['variant'][] = 'in:regular,light,dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->input('iconCollection', null) === 'dashboardicons') {
|
||||||
|
$rules['variant'][] = 'in:regular,light,dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->input('iconCollection', null) === 'tfa') {
|
||||||
|
$rules['variant'][] = 'in:regular';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,7 +59,7 @@ class IconFetchRequest extends FormRequest
|
|||||||
protected function prepareForValidation()
|
protected function prepareForValidation()
|
||||||
{
|
{
|
||||||
$this->merge([
|
$this->merge([
|
||||||
'service' => strip_tags(strval($this->service)),
|
'service' => strip_tags(strval($this->input('service'))),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ namespace App\Services\LogoLib;
|
|||||||
|
|
||||||
use App\Facades\IconStore;
|
use App\Facades\IconStore;
|
||||||
use App\Services\LogoLib\LogoLibInterface;
|
use App\Services\LogoLib\LogoLibInterface;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
@ -25,14 +26,21 @@ abstract class AbstractLogoLib implements LogoLibInterface
|
|||||||
*/
|
*/
|
||||||
protected string $format = 'svg';
|
protected string $format = 'svg';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suffix to append to the queried resource to get a specific variant
|
||||||
|
*/
|
||||||
|
protected string $variant = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a logo for the given service and save it as an icon
|
* Fetch a logo for the given service and save it as an icon
|
||||||
*
|
*
|
||||||
* @param string|null $serviceName Name of the service to fetch a logo for
|
* @param string|null $serviceName Name of the service to fetch a logo for
|
||||||
|
* @param string|null $variant The theme variant to fetch (light, dark, etc...)
|
||||||
* @return string|null The icon filename or null if no logo has been found
|
* @return string|null The icon filename or null if no logo has been found
|
||||||
*/
|
*/
|
||||||
public function getIcon(?string $serviceName) : string|null
|
public function getIcon(?string $serviceName, string $variant = null) : string|null
|
||||||
{
|
{
|
||||||
|
$this->setVariant($variant);
|
||||||
$logoFilename = $this->getLogo(strval($serviceName));
|
$logoFilename = $this->getLogo(strval($serviceName));
|
||||||
|
|
||||||
if (!$logoFilename) {
|
if (!$logoFilename) {
|
||||||
@ -50,6 +58,20 @@ abstract class AbstractLogoLib implements LogoLibInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the variant using passed parameter or default
|
||||||
|
*/
|
||||||
|
protected function setVariant(?string $variant) : void
|
||||||
|
{
|
||||||
|
if (! $variant || ! in_array($variant, ['regular', 'dark', 'light'])) {
|
||||||
|
$this->variant = Auth::user()
|
||||||
|
? Auth::user()->preferences['iconVariant']
|
||||||
|
: 'regular';
|
||||||
|
} else {
|
||||||
|
$this->variant = $variant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the logo's filename for a given service
|
* Return the logo's filename for a given service
|
||||||
*
|
*
|
||||||
@ -59,7 +81,7 @@ abstract class AbstractLogoLib implements LogoLibInterface
|
|||||||
protected function getLogo(string $serviceName)
|
protected function getLogo(string $serviceName)
|
||||||
{
|
{
|
||||||
$referenceName = $this->sanitizeServiceName(strval($serviceName));
|
$referenceName = $this->sanitizeServiceName(strval($serviceName));
|
||||||
$logoFilename = $referenceName . '.' . $this->format;
|
$logoFilename = $referenceName . $this->suffix() . '.' . $this->format;
|
||||||
$cachedFilename = $this->cachePrefix . $logoFilename;
|
$cachedFilename = $this->cachePrefix . $logoFilename;
|
||||||
|
|
||||||
if ($referenceName && ! Storage::disk('logos')->exists($cachedFilename)) {
|
if ($referenceName && ! Storage::disk('logos')->exists($cachedFilename)) {
|
||||||
@ -69,6 +91,28 @@ abstract class AbstractLogoLib implements LogoLibInterface
|
|||||||
return Storage::disk('logos')->exists($cachedFilename) ? $cachedFilename : null;
|
return Storage::disk('logos')->exists($cachedFilename) ? $cachedFilename : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suffix to append to the reference name to get a specific variant
|
||||||
|
*/
|
||||||
|
protected function suffix() : string
|
||||||
|
{
|
||||||
|
switch ($this->variant) {
|
||||||
|
case 'light':
|
||||||
|
$suffix = '-light';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'dark':
|
||||||
|
$suffix = '-dark';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$suffix = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $suffix;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Url to use in http request to get a specific logo from the logo lib
|
* Url to use in http request to get a specific logo from the logo lib
|
||||||
*/
|
*/
|
||||||
@ -92,10 +136,13 @@ abstract class AbstractLogoLib implements LogoLibInterface
|
|||||||
*/
|
*/
|
||||||
protected function fetchLogo(string $logoFilename) : void
|
protected function fetchLogo(string $logoFilename) : void
|
||||||
{
|
{
|
||||||
|
$url = $this->logoUrl($logoFilename);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$response = Http::withOptions([
|
// $response = Http::withOptions([
|
||||||
'proxy' => config('2fauth.config.outgoingProxy'),
|
// 'proxy' => config('2fauth.config.outgoingProxy'),
|
||||||
])->retry(3, 100)->get($this->logoUrl($logoFilename));
|
// ])->retry(3, 100)->get($url);
|
||||||
|
$response = Http::get($url);
|
||||||
|
|
||||||
if ($response->successful()) {
|
if ($response->successful()) {
|
||||||
$filename = $this->cachePrefix . $logoFilename;
|
$filename = $this->cachePrefix . $logoFilename;
|
||||||
|
@ -4,5 +4,5 @@ namespace App\Services\LogoLib;
|
|||||||
|
|
||||||
interface LogoLibInterface
|
interface LogoLibInterface
|
||||||
{
|
{
|
||||||
public function getIcon(?string $serviceName): string|null;
|
public function getIcon(?string $serviceName, string $variant = null): string|null;
|
||||||
}
|
}
|
@ -24,7 +24,7 @@ class TfaLogoLib extends AbstractLogoLib implements LogoLibInterface
|
|||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
const TFA_URL = 'https://2fa.directory/api/v3/tfa.json';
|
const TFA_JSON_URL = 'https://2fa.directory/api/v3/tfa.json';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
@ -45,7 +45,7 @@ class TfaLogoLib extends AbstractLogoLib implements LogoLibInterface
|
|||||||
* @param string|null $serviceName Name of the service to fetch a logo for
|
* @param string|null $serviceName Name of the service to fetch a logo for
|
||||||
* @return string|null The icon filename or null if no logo has been found
|
* @return string|null The icon filename or null if no logo has been found
|
||||||
*/
|
*/
|
||||||
public function getIcon(?string $serviceName) : string|null
|
public function getIcon(?string $serviceName, string $variant = null) : string|null
|
||||||
{
|
{
|
||||||
$logoFilename = $this->getLogo(strval($serviceName));
|
$logoFilename = $this->getLogo(strval($serviceName));
|
||||||
|
|
||||||
@ -58,6 +58,14 @@ class TfaLogoLib extends AbstractLogoLib implements LogoLibInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suffix to append to the reference name to get a specific variant
|
||||||
|
*/
|
||||||
|
protected function suffix() : string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the logo's filename for a given service
|
* Return the logo's filename for a given service
|
||||||
*
|
*
|
||||||
@ -104,7 +112,7 @@ class TfaLogoLib extends AbstractLogoLib implements LogoLibInterface
|
|||||||
try {
|
try {
|
||||||
$response = Http::withOptions([
|
$response = Http::withOptions([
|
||||||
'proxy' => config('2fauth.config.outgoingProxy'),
|
'proxy' => config('2fauth.config.outgoingProxy'),
|
||||||
])->retry(3, 100)->get(self::TFA_URL);
|
])->retry(3, 100)->get(self::TFA_JSON_URL);
|
||||||
|
|
||||||
$coll = collect(json_decode(htmlspecialchars_decode($response->body()), true)) /* @phpstan-ignore-line */
|
$coll = collect(json_decode(htmlspecialchars_decode($response->body()), true)) /* @phpstan-ignore-line */
|
||||||
->mapWithKeys(function ($item, $key) {
|
->mapWithKeys(function ($item, $key) {
|
||||||
|
@ -14,6 +14,7 @@ $preferences = [
|
|||||||
'displayMode' => envUnlessEmpty('USERPREF_DEFAULT__DISPLAY_MODE', 'list'),
|
'displayMode' => envUnlessEmpty('USERPREF_DEFAULT__DISPLAY_MODE', 'list'),
|
||||||
'showAccountsIcons' => envUnlessEmpty('USERPREF_DEFAULT__SHOW_ACCOUNTS_ICONS', true),
|
'showAccountsIcons' => envUnlessEmpty('USERPREF_DEFAULT__SHOW_ACCOUNTS_ICONS', true),
|
||||||
'iconCollection' => envUnlessEmpty('USERPREF_DEFAULT__ICON_COLLECTION', 'selfh'),
|
'iconCollection' => envUnlessEmpty('USERPREF_DEFAULT__ICON_COLLECTION', 'selfh'),
|
||||||
|
'iconVariant' => envUnlessEmpty('USERPREF_DEFAULT__ICON_VARIANT', 'regular'),
|
||||||
'kickUserAfter' => envUnlessEmpty('USERPREF_DEFAULT__KICK_USER_AFTER', 15),
|
'kickUserAfter' => envUnlessEmpty('USERPREF_DEFAULT__KICK_USER_AFTER', 15),
|
||||||
'activeGroup' => 0,
|
'activeGroup' => 0,
|
||||||
'rememberActiveGroup' => envUnlessEmpty('USERPREF_DEFAULT__REMEMBER_ACTIVE_GROUP', true),
|
'rememberActiveGroup' => envUnlessEmpty('USERPREF_DEFAULT__REMEMBER_ACTIVE_GROUP', true),
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useIdGenerator, useValidationErrorIdGenerator } from '@/composables/helpers'
|
import { useIdGenerator, useValidationErrorIdGenerator } from '@/composables/helpers'
|
||||||
|
|
||||||
|
const model = defineModel()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: [String, Number, Boolean],
|
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
@ -30,7 +30,6 @@
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const selected = ref(props.modelValue)
|
|
||||||
const { inputId } = useIdGenerator('select', props.fieldName + props.idSuffix)
|
const { inputId } = useIdGenerator('select', props.fieldName + props.idSuffix)
|
||||||
const { valErrorId } = useValidationErrorIdGenerator(props.fieldName)
|
const { valErrorId } = useValidationErrorIdGenerator(props.fieldName)
|
||||||
const legendId = useIdGenerator('legend', props.fieldName + props.idSuffix).inputId
|
const legendId = useIdGenerator('legend', props.fieldName + props.idSuffix).inputId
|
||||||
@ -49,8 +48,7 @@
|
|||||||
<div class="select">
|
<div class="select">
|
||||||
<select
|
<select
|
||||||
:id="inputId"
|
:id="inputId"
|
||||||
v-model="selected"
|
v-model="model"
|
||||||
v-on:change="$emit('update:modelValue', $event.target.value)"
|
|
||||||
:disabled="isDisabled || isLocked"
|
:disabled="isDisabled || isLocked"
|
||||||
:aria-describedby="help ? legendId : undefined"
|
:aria-describedby="help ? legendId : undefined"
|
||||||
:aria-invalid="fieldError != undefined"
|
:aria-invalid="fieldError != undefined"
|
||||||
|
4
resources/js/services/twofaccountService.js
vendored
4
resources/js/services/twofaccountService.js
vendored
@ -23,8 +23,8 @@ export default {
|
|||||||
return apiClient.post('/twofaccounts', { uri: uri }, { ...config })
|
return apiClient.post('/twofaccounts', { uri: uri }, { ...config })
|
||||||
},
|
},
|
||||||
|
|
||||||
getLogo(service, iconCollection, config = {}) {
|
getLogo(service, iconCollection, variant, config = {}) {
|
||||||
return apiClient.post('/icons/default', { service: service, iconCollection: iconCollection }, { ...config })
|
return apiClient.post('/icons/default', { service: service, iconCollection: iconCollection, variant: variant }, { ...config })
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteIcon(icon, config = {}) {
|
deleteIcon(icon, config = {}) {
|
||||||
|
@ -24,10 +24,22 @@
|
|||||||
{ text: 'settings.forms.automatic', value: 'system', icon: 'desktop' },
|
{ text: 'settings.forms.automatic', value: 'system', icon: 'desktop' },
|
||||||
]
|
]
|
||||||
const iconCollections = [
|
const iconCollections = [
|
||||||
{ text: 'selfh.st', value: 'selfh', url: 'https://selfh.st/icons/' },
|
{ text: 'selfh.st', value: 'selfh', url: 'https://selfh.st/icons/', defaultVariant: 'regular' },
|
||||||
{ text: 'dashboardicons.com', value: 'dashboardicons', url: 'https://dashboardicons.com/' },
|
{ text: 'dashboardicons.com', value: 'dashboardicons', url: 'https://dashboardicons.com/', defaultVariant: 'regular' },
|
||||||
{ text: '2fa.directory', value: 'tfa', url: 'https://2fa.directory/' },
|
{ text: '2fa.directory', value: 'tfa', url: 'https://2fa.directory/', defaultVariant: 'regular' },
|
||||||
]
|
]
|
||||||
|
const iconCollectionVariants = {
|
||||||
|
selfh: [
|
||||||
|
{ text: 'commons.regular', value: 'regular' },
|
||||||
|
{ text: 'settings.forms.light', value: 'light' },
|
||||||
|
{ text: 'settings.forms.dark', value: 'dark' },
|
||||||
|
],
|
||||||
|
dashboardicons: [
|
||||||
|
{ text: 'commons.regular', value: 'regular' },
|
||||||
|
{ text: 'settings.forms.light', value: 'light' },
|
||||||
|
{ text: 'settings.forms.dark', value: 'dark' },
|
||||||
|
]
|
||||||
|
}
|
||||||
const passwordFormats = [
|
const passwordFormats = [
|
||||||
{ text: '12 34 56', value: 2, legend: 'settings.forms.pair', title: 'settings.forms.pair_legend' },
|
{ text: '12 34 56', value: 2, legend: 'settings.forms.pair', title: 'settings.forms.pair_legend' },
|
||||||
{ text: '123 456', value: 3, legend: 'settings.forms.trio', title: 'settings.forms.trio_legend' },
|
{ text: '123 456', value: 3, legend: 'settings.forms.trio', title: 'settings.forms.trio_legend' },
|
||||||
@ -118,6 +130,28 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the iconCollection preference on the backend
|
||||||
|
* @param {string} preference
|
||||||
|
* @param {any} value
|
||||||
|
*/
|
||||||
|
function saveIconCollection(value) {
|
||||||
|
savePreference('iconCollection', value)
|
||||||
|
|
||||||
|
if (! Object.prototype.hasOwnProperty.call(iconCollectionVariants, value)) {
|
||||||
|
if (user.preferences.iconVariant != 'regular') {
|
||||||
|
user.preferences.iconVariant = 'regular'
|
||||||
|
userService.updatePreference('iconVariant', user.preferences.iconVariant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (iconCollectionVariants[value].find((variant) => variant.value == user.preferences.iconVariant) == undefined) {
|
||||||
|
user.preferences.iconVariant = iconCollections.find((collection) => collection.value == value).defaultVariant
|
||||||
|
userService.updatePreference('iconVariant', user.preferences.iconVariant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeRouteLeave((to) => {
|
onBeforeRouteLeave((to) => {
|
||||||
if (! to.name.startsWith('settings.')) {
|
if (! to.name.startsWith('settings.')) {
|
||||||
notify.clear()
|
notify.clear()
|
||||||
@ -159,11 +193,13 @@
|
|||||||
<!-- Official icons -->
|
<!-- Official icons -->
|
||||||
<FormCheckbox v-model="user.preferences.getOfficialIcons" @update:model-value="val => savePreference('getOfficialIcons', val)" fieldName="getOfficialIcons" :isLocked="appSettings.lockedPreferences.includes('getOfficialIcons')" label="settings.forms.get_official_icons.label" help="settings.forms.get_official_icons.help" />
|
<FormCheckbox v-model="user.preferences.getOfficialIcons" @update:model-value="val => savePreference('getOfficialIcons', val)" fieldName="getOfficialIcons" :isLocked="appSettings.lockedPreferences.includes('getOfficialIcons')" label="settings.forms.get_official_icons.label" help="settings.forms.get_official_icons.help" />
|
||||||
<!-- icon collections -->
|
<!-- icon collections -->
|
||||||
<FormSelect v-model="user.preferences.iconCollection" @update:model-value="val => savePreference('iconCollection', val)" :options="iconCollections" fieldName="iconCollection" :isLocked="appSettings.lockedPreferences.includes('iconCollection')" :isDisabled="!user.preferences.getOfficialIcons" label="settings.forms.icon_collection.label" help="settings.forms.icon_collection.help" :isIndented="true">
|
<FormSelect v-model="user.preferences.iconCollection" @update:model-value="val => saveIconCollection(val)" :options="iconCollections" fieldName="iconCollection" :isLocked="appSettings.lockedPreferences.includes('iconCollection')" :isDisabled="!user.preferences.getOfficialIcons" label="settings.forms.icon_collection.label" help="settings.forms.icon_collection.help" :isIndented="true">
|
||||||
<a class="button is-ghost" :href="iconCollectionUrl" target="_blank" :title="$t('commons.visit_x', { website: iconCollectionDomain})">
|
<a class="button is-ghost" :href="iconCollectionUrl" target="_blank" :title="$t('commons.visit_x', { website: iconCollectionDomain})">
|
||||||
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
||||||
</a>
|
</a>
|
||||||
</FormSelect>
|
</FormSelect>
|
||||||
|
<!-- icon variant -->
|
||||||
|
<FormSelect v-if="iconCollectionVariants[user.preferences.iconCollection]" v-model="user.preferences.iconVariant" @update:model-value="val => savePreference('iconVariant', val)" :options="iconCollectionVariants[user.preferences.iconCollection]" fieldName="iconVariant" :isLocked="appSettings.lockedPreferences.includes('iconVariant')" :isDisabled="!user.preferences.getOfficialIcons" label="settings.forms.icon_variant.label" help="settings.forms.icon_variant.help" :isIndented="true" />
|
||||||
<!-- password format -->
|
<!-- password format -->
|
||||||
<FormCheckbox v-model="user.preferences.formatPassword" @update:model-value="val => savePreference('formatPassword', val)" fieldName="formatPassword" :isLocked="appSettings.lockedPreferences.includes('formatPassword')" label="settings.forms.password_format.label" help="settings.forms.password_format.help" />
|
<FormCheckbox v-model="user.preferences.formatPassword" @update:model-value="val => savePreference('formatPassword', val)" fieldName="formatPassword" :isLocked="appSettings.lockedPreferences.includes('formatPassword')" label="settings.forms.password_format.label" help="settings.forms.password_format.help" />
|
||||||
<FormToggle v-model="user.preferences.formatPasswordBy" @update:model-value="val => savePreference('formatPasswordBy', val)" :choices="passwordFormats" fieldName="formatPasswordBy" :isLocked="appSettings.lockedPreferences.includes('formatPasswordBy')" :isDisabled="!user.preferences.formatPassword" />
|
<FormToggle v-model="user.preferences.formatPasswordBy" @update:model-value="val => savePreference('formatPasswordBy', val)" :choices="passwordFormats" fieldName="formatPasswordBy" :isLocked="appSettings.lockedPreferences.includes('formatPasswordBy')" :isDisabled="!user.preferences.formatPassword" />
|
||||||
|
@ -38,10 +38,22 @@
|
|||||||
icon: null
|
icon: null
|
||||||
}))
|
}))
|
||||||
const iconCollections = [
|
const iconCollections = [
|
||||||
{ text: 'selfh.st', value: 'selfh' },
|
{ text: 'selfh.st', value: 'selfh', asVariant: true },
|
||||||
{ text: 'dashboardicons.com', value: 'dashboardicons' },
|
{ text: 'dashboardicons.com', value: 'dashboardicons', asVariant: true },
|
||||||
{ text: '2fa.directory', value: 'tfa' },
|
{ text: '2fa.directory', value: 'tfa', asVariant: false },
|
||||||
]
|
]
|
||||||
|
const iconCollectionVariants = {
|
||||||
|
selfh: [
|
||||||
|
{ text: 'commons.regular', value: 'regular' },
|
||||||
|
{ text: 'settings.forms.light', value: 'light' },
|
||||||
|
{ text: 'settings.forms.dark', value: 'dark' },
|
||||||
|
],
|
||||||
|
dashboardicons: [
|
||||||
|
{ text: 'commons.regular', value: 'regular' },
|
||||||
|
{ text: 'settings.forms.light', value: 'light' },
|
||||||
|
{ text: 'settings.forms.dark', value: 'dark' },
|
||||||
|
]
|
||||||
|
}
|
||||||
const otpDisplayProps = ref({
|
const otpDisplayProps = ref({
|
||||||
otp_type: '',
|
otp_type: '',
|
||||||
account : '',
|
account : '',
|
||||||
@ -75,6 +87,8 @@
|
|||||||
const ShowTwofaccountInModal = ref(false)
|
const ShowTwofaccountInModal = ref(false)
|
||||||
const fetchingLogo = ref(false)
|
const fetchingLogo = ref(false)
|
||||||
const iconCollection = ref(user.preferences.iconCollection)
|
const iconCollection = ref(user.preferences.iconCollection)
|
||||||
|
const iconCollectionVariant = ref(user.preferences.iconVariant)
|
||||||
|
|
||||||
|
|
||||||
// $refs
|
// $refs
|
||||||
const iconInput = ref(null)
|
const iconInput = ref(null)
|
||||||
@ -152,6 +166,12 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(iconCollection, (val) => {
|
||||||
|
iconCollectionVariant.value = Object.prototype.hasOwnProperty.call(iconCollectionVariants, val)
|
||||||
|
? iconCollectionVariants[val][0].value
|
||||||
|
: ''
|
||||||
|
})
|
||||||
|
|
||||||
watch(tempIcon, (val) => {
|
watch(tempIcon, (val) => {
|
||||||
if( showQuickForm.value ) {
|
if( showQuickForm.value ) {
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
@ -385,17 +405,17 @@
|
|||||||
if (user.preferences.getOfficialIcons) {
|
if (user.preferences.getOfficialIcons) {
|
||||||
fetchingLogo.value = true
|
fetchingLogo.value = true
|
||||||
|
|
||||||
twofaccountService.getLogo(form.service, iconCollection.value, { returnError: true })
|
twofaccountService.getLogo(form.service, iconCollection.value, iconCollectionVariant.value, { returnError: true })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.status === 201) {
|
if (response.status === 201) {
|
||||||
// clean possible already uploaded temp icon
|
// clean possible already uploaded temp icon
|
||||||
deleteTempIcon()
|
deleteTempIcon()
|
||||||
tempIcon.value = response.data.filename;
|
tempIcon.value = response.data.filename;
|
||||||
}
|
}
|
||||||
else notify.warn( {text: trans('errors.no_logo_found_for_x', {service: strip_tags(form.service)}) })
|
else notify.warn( {text: trans('errors.no_icon_for_this_variant') })
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
notify.warn({ text: trans('errors.no_logo_found_for_x', {service: strip_tags(form.service)}) })
|
notify.warn({ text: trans('errors.no_icon_for_this_variant') })
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
fetchingLogo.value = false
|
fetchingLogo.value = false
|
||||||
@ -492,52 +512,56 @@
|
|||||||
<FormField v-model="form.account" fieldName="account" :fieldError="form.errors.get('account')" label="twofaccounts.account" :placeholder="$t('twofaccounts.forms.account.placeholder')" />
|
<FormField v-model="form.account" fieldName="account" :fieldError="form.errors.get('account')" label="twofaccounts.account" :placeholder="$t('twofaccounts.forms.account.placeholder')" />
|
||||||
<!-- icon upload -->
|
<!-- icon upload -->
|
||||||
<label for="filUploadIcon" class="label">{{ $t('twofaccounts.icon') }}</label>
|
<label for="filUploadIcon" class="label">{{ $t('twofaccounts.icon') }}</label>
|
||||||
<div class="columns is-mobile mb-0">
|
<!-- try my luck -->
|
||||||
<div class="column pt-0">
|
<!-- <fieldset v-if="user.preferences.getOfficialIcons" :disabled="!form.service"> -->
|
||||||
<!-- try my luck -->
|
<div class="field has-addons">
|
||||||
<fieldset v-if="user.preferences.getOfficialIcons" :disabled="!form.service">
|
<div class="control">
|
||||||
<div class="field is-grouped">
|
<div class="select">
|
||||||
<div class="control">
|
<select :disabled="!form.service" name="icon-collection" v-model="iconCollection">
|
||||||
<VueButton @click="fetchLogo" :color="mode == 'dark' ? 'is-dark' : ''" :nativeType="'button'" :is-loading="fetchingLogo" aria-describedby="lgdTryMyLuck">
|
<option v-for="collection in iconCollections" :key="collection.text" :value="collection.value">
|
||||||
<span class="icon is-small">
|
{{ collection.text }}
|
||||||
<FontAwesomeIcon :icon="['fas', 'globe']" />
|
</option>
|
||||||
</span>
|
</select>
|
||||||
<span>{{ $t('twofaccounts.forms.i_m_lucky') }}</span>
|
|
||||||
</VueButton>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<div class="select">
|
|
||||||
<select name="icon-collection" v-model="iconCollection">
|
|
||||||
<option v-for="collection in iconCollections" :key="collection.text" :value="collection.value">
|
|
||||||
{{ collection.text }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</div>
|
||||||
<div class="field is-grouped">
|
<div v-if="iconCollectionVariants[iconCollection]" class="control">
|
||||||
<!-- upload icon button -->
|
<div class="select">
|
||||||
<div class="control is-flex">
|
<select :disabled="!form.service" name="icon-collection-variant" v-model="iconCollectionVariant">
|
||||||
<div role="button" tabindex="0" class="file mr-3" :class="mode == 'dark' ? 'is-dark' : 'is-white'" @keyup.enter="iconInputLabel.click()">
|
<option v-for="variant in iconCollectionVariants[iconCollection]" :key="variant.value" :value="variant.value">
|
||||||
<label for="filUploadIcon" class="file-label" ref="iconInputLabel">
|
{{ $t(variant.text) }}
|
||||||
<input id="filUploadIcon" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
|
</option>
|
||||||
<span class="file-cta">
|
</select>
|
||||||
<span class="file-icon">
|
|
||||||
<FontAwesomeIcon :icon="['fas', 'upload']" />
|
|
||||||
</span>
|
|
||||||
<span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<span class="tag is-large" :class="mode =='dark' ? 'is-dark' : 'is-white'" v-if="tempIcon">
|
|
||||||
<img class="icon-preview" :src="$2fauth.config.subdirectory + '/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
|
|
||||||
<button type="button" class="clear-selection delete is-small" @click.prevent="deleteTempIcon" :aria-label="$t('twofaccounts.remove_icon')"></button>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow" style="width: 200px;">
|
<!-- </fieldset> -->
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<!-- try my luck button -->
|
||||||
|
<div class="control">
|
||||||
|
<VueButton @click="fetchLogo" :color="mode == 'dark' ? 'is-dark' : ''" :nativeType="'button'" :is-loading="fetchingLogo" :disabled="!form.service" aria-describedby="lgdTryMyLuck">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<FontAwesomeIcon :icon="['fas', 'globe']" />
|
||||||
|
</span>
|
||||||
|
<span>{{ $t('twofaccounts.forms.i_m_lucky') }}</span>
|
||||||
|
</VueButton>
|
||||||
|
</div>
|
||||||
|
<!-- upload icon button -->
|
||||||
|
<div class="control is-flex">
|
||||||
|
<div role="button" tabindex="0" class="file mr-3" :class="mode == 'dark' ? 'is-dark' : 'is-white'" @keyup.enter="iconInputLabel.click()">
|
||||||
|
<label for="filUploadIcon" class="file-label" ref="iconInputLabel">
|
||||||
|
<input id="filUploadIcon" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
|
||||||
|
<span class="file-cta">
|
||||||
|
<span class="file-icon">
|
||||||
|
<FontAwesomeIcon :icon="['fas', 'upload']" />
|
||||||
|
</span>
|
||||||
|
<span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<span class="tag is-large" :class="mode =='dark' ? 'is-dark' : 'is-white'" v-if="tempIcon">
|
||||||
|
<img class="icon-preview" :src="$2fauth.config.subdirectory + '/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
|
||||||
|
<button type="button" class="clear-selection delete is-small" @click.prevent="deleteTempIcon" :aria-label="$t('twofaccounts.remove_icon')"></button>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -92,5 +92,6 @@ return [
|
|||||||
'x_month' => ':x mos.',
|
'x_month' => ':x mos.',
|
||||||
'one_year' => '1 yr.',
|
'one_year' => '1 yr.',
|
||||||
'copy_next_password' => 'Copy next password to clipboard',
|
'copy_next_password' => 'Copy next password to clipboard',
|
||||||
'visit_x' => 'Visit :website'
|
'visit_x' => 'Visit :website',
|
||||||
|
'regular' => 'Regular',
|
||||||
];
|
];
|
||||||
|
@ -53,7 +53,7 @@ return [
|
|||||||
'unsupported_migration' => 'Data do not match any supported format',
|
'unsupported_migration' => 'Data do not match any supported format',
|
||||||
'unsupported_otp_type' => 'Unsupported OTP type',
|
'unsupported_otp_type' => 'Unsupported OTP type',
|
||||||
'encrypted_migration' => 'Unreadable, the data seem encrypted',
|
'encrypted_migration' => 'Unreadable, the data seem encrypted',
|
||||||
'no_logo_found_for_x' => 'No logo available for :service',
|
'no_icon_for_this_variant' => 'No icon available in this variant',
|
||||||
'file_upload_failed' => 'File upload failed',
|
'file_upload_failed' => 'File upload failed',
|
||||||
'unauthorized' => 'Unauthorized',
|
'unauthorized' => 'Unauthorized',
|
||||||
'unauthorized_legend' => 'You do not have permissions to view this resource or to perform this action',
|
'unauthorized_legend' => 'You do not have permissions to view this resource or to perform this action',
|
||||||
|
@ -126,8 +126,12 @@ return [
|
|||||||
'help' => '(Try to) Get the official icon of the 2FA issuer when adding an account'
|
'help' => '(Try to) Get the official icon of the 2FA issuer when adding an account'
|
||||||
],
|
],
|
||||||
'icon_collection' => [
|
'icon_collection' => [
|
||||||
'label' => 'Preferred icons source',
|
'label' => 'Favorite icon source',
|
||||||
'help' => 'The icons collection to be queried when an official icon is required. Changing this setting does not refresh icons that have already been fetched.'
|
'help' => 'The icons collection to be queried at first when an official icon is required. Changing this setting does not refresh icons that have already been fetched.'
|
||||||
|
],
|
||||||
|
'icon_variant' => [
|
||||||
|
'label' => 'Icon variant',
|
||||||
|
'help' => 'Some icons may be available in several flavors to best suit dark or light UIs. Set the one you want to look for first.'
|
||||||
],
|
],
|
||||||
'auto_lock' => [
|
'auto_lock' => [
|
||||||
'label' => 'Auto lock',
|
'label' => 'Auto lock',
|
||||||
|
@ -66,7 +66,7 @@ return [
|
|||||||
],
|
],
|
||||||
'choose_image' => 'Upload',
|
'choose_image' => 'Upload',
|
||||||
'i_m_lucky' => 'Try my luck',
|
'i_m_lucky' => 'Try my luck',
|
||||||
'i_m_lucky_legend' => 'The "Try my luck" button tries to get a standard icon from the selected icon collection. The simpler the service value, the more likely you are to get the correct icon: Do not append any extension (like ".com"), use the exact name of the service, avoid special chars.',
|
'i_m_lucky_legend' => 'The "Try my luck" button tries to get a standard icon from the selected icon collection. The simpler the Service field value, the more likely you are to get the expected icon: Do not append any extension (like ".com"), use the exact name of the service, avoid special chars.',
|
||||||
'test' => 'Test',
|
'test' => 'Test',
|
||||||
'group' => [
|
'group' => [
|
||||||
'label' => 'Group',
|
'label' => 'Group',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user