Bump with base project last
This commit is contained in:
parent
493a76af2d
commit
4792caeabe
24 changed files with 927 additions and 538 deletions
|
|
@ -143,8 +143,11 @@ class UserController extends Controller
|
|||
*/
|
||||
public function destroy(User $user): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
/** @var array<string,string> */
|
||||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($user->deleteOrFail()) {
|
||||
return redirect()->route('bo.users.index')
|
||||
return redirect()->route('bo.users.index', $previousQueries)
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(__('models.classes.user'))
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -480,7 +480,7 @@ class ToolboxHelper
|
|||
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWidth}px");
|
||||
}
|
||||
$yMessage = trans(
|
||||
":attribute la hauteur doit être comprise entre {$sizes->minWidth}px et {$sizes->maxWidth}px"
|
||||
":attribute la hauteur doit être comprise entre {$sizes->minHeight}px et {$sizes->maxHeight}px"
|
||||
);
|
||||
if ($sizes->minHeight === $sizes->maxHeight) {
|
||||
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minHeight}px");
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use App\Enums\ActivityLogs\ActivityLogsEventEnum;
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @property string $model Target model.
|
||||
|
|
@ -98,22 +98,67 @@ class ActivityLog extends Model
|
|||
{
|
||||
$modelClassName = $activity->model_class;
|
||||
/** Get type of each fields of the target model */
|
||||
/** @var \Illuminate\Database\Eloquent\Model|null $targetModel */
|
||||
$targetModel = $activity->model_class::where(
|
||||
(new $modelClassName())->getRouteKeyName(),
|
||||
$activity->model_id
|
||||
)->first();
|
||||
if ($targetModel != null) {
|
||||
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
|
||||
if (\is_null($targetModel)) {
|
||||
return [];
|
||||
}
|
||||
$targetModelTypes = collect($targetModel->toArray())
|
||||
->map(function ($field, $fieldIndex) use ($targetModel) {
|
||||
return Schema::getColumnType($targetModel->getTable(), $fieldIndex);
|
||||
})->toArray();
|
||||
// phpcs:enable
|
||||
->map(fn ($value) => self::getValueType($value))
|
||||
->toArray();
|
||||
/** Return old, new and type of values changed */
|
||||
return ($eventEnum === ActivityLogsEventEnum::updated) ?
|
||||
array_intersect_key($targetModelTypes, $model->getChanges()) : [];
|
||||
}
|
||||
return [];
|
||||
|
||||
|
||||
/**
|
||||
* Get a value type as a string
|
||||
* ! Use this for display purpose only
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
* @throws \RuntimeException If type is unhlanded.
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.MaxExceeded
|
||||
*/
|
||||
private static function getValueType(mixed $value): string
|
||||
{
|
||||
// phpcs:enable
|
||||
$type = \gettype($value);
|
||||
switch ($type) {
|
||||
// phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
|
||||
case 'string':
|
||||
return match (true) {
|
||||
Str::length(\strip_tags($value)) !== Str::length($value) => 'html',
|
||||
Str::startsWith($value, 'storage/modelfiles') => 'file',
|
||||
Str::isUuid($value) => 'uuid',
|
||||
Str::isUlid($value) => 'ulid',
|
||||
Str::isUrl($value) => 'url',
|
||||
Str::isMatch('/^[0-9A-Za-z_-]{10}[048AEIMQUYcgkosw]$/', $value) => 'youtube_video_id',
|
||||
\is_numeric($value) => 'numeric',
|
||||
Str::isJson($value) and
|
||||
(Str::startsWith($value, '[') or Str::startsWith($value, '{')) => 'json',
|
||||
default => 'string'
|
||||
};
|
||||
case 'NULL':
|
||||
case 'object':
|
||||
case 'array':
|
||||
case 'integer':
|
||||
case 'boolean':
|
||||
return $type;
|
||||
case 'double':
|
||||
return is_float($value) ? 'float' : 'double';
|
||||
case 'resource (closed)':
|
||||
case 'resource':
|
||||
throw new \RuntimeException('Laravel model properties should not contain resources !');
|
||||
case 'unknown type':
|
||||
default:
|
||||
throw new \RuntimeException('Unhandled Type ' . \gettype($value));
|
||||
} //end switch
|
||||
}
|
||||
|
||||
// * RELATIONS
|
||||
|
|
|
|||
43
app/Rules/IsValidVimeoId.php
Normal file
43
app/Rules/IsValidVimeoId.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Validate a vimeo ID regex
|
||||
*
|
||||
* @package App\Rules
|
||||
*/
|
||||
class IsValidVimeoId implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @param \Closure(string):\Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
// phpcs:enable
|
||||
if (!self::validates($value)) {
|
||||
$fail(trans('validation.vimeo_id'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return boolean
|
||||
*/
|
||||
public static function validates(mixed $value): bool
|
||||
{
|
||||
return Str::of($value)->isMatch('/^[0-9]{8,10}$/');
|
||||
}
|
||||
}
|
||||
44
app/Rules/IsValidYoutubeId.php
Normal file
44
app/Rules/IsValidYoutubeId.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Validate a youtube ID regex
|
||||
*
|
||||
* @url https://webapps.stackexchange.com/questions/54443/format-for-id-of-youtube-video
|
||||
* @package App\Rules
|
||||
*/
|
||||
class IsValidYoutubeId implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @param \Closure(string):\Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
// phpcs:enable
|
||||
if (!self::validates($value)) {
|
||||
$fail(trans('validation.youtube_id'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return boolean
|
||||
*/
|
||||
public static function validates(mixed $value): bool
|
||||
{
|
||||
return Str::of($value)->isMatch('/^[0-9A-Za-z_-]{10}[048AEIMQUYcgkosw]$/');
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
namespace App\Traits\Controllers;
|
||||
|
||||
use App\Http\Requests\Bo\UpdateChangeOrderRequest;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
|
|
@ -14,22 +14,31 @@ trait ChangesModelOrder
|
|||
* Change order.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\UpdateChangeOrderRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param mixed ...$params
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au model utilisé.
|
||||
* @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au params utilisé.
|
||||
*/
|
||||
public function changeOrder(UpdateChangeOrderRequest $request, Model $model): \Illuminate\Http\RedirectResponse
|
||||
public function changeOrder(UpdateChangeOrderRequest $request, mixed ...$params): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$params = collect(request()->route()->parameterNames);
|
||||
$className = Str::ucfirst(Str::lower($params->get($params->count() - 2)));
|
||||
$className = "\\App\\Models\\$className";
|
||||
$paramNames = collect(request()->route()->parameterNames);
|
||||
$className = Str::of($paramNames->get($paramNames->count() - 2))->lower()->studly();
|
||||
$className = "\\App\\Models\\{$className}";
|
||||
$model = Arr::get($params, $paramNames->count() - 2);
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw new \RuntimeException("The $className class doesn't exist.");
|
||||
throw new \RuntimeException("The {$className} class doesn't exist.");
|
||||
}
|
||||
|
||||
if (!\is_object($model)) {
|
||||
throw new \RuntimeException("The second last param isn't an object.");
|
||||
}
|
||||
|
||||
if (get_class($model) !== (string)Str::of($className)->replaceStart('\\', '')) {
|
||||
throw new \RuntimeException("The {$className} doesn't match second last binded param.");
|
||||
}
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Model|null */
|
||||
$model = $className::where($model->getRouteKeyName(), $model->getRouteKey())->first();
|
||||
$model = $className::query()->where($model->getRouteKeyName(), $model->getRouteKey())->first();
|
||||
|
||||
if (!$model) {
|
||||
return back()->with('error', trans('crud.messages.order_not_changed'));
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ trait BaseEnum
|
|||
/**
|
||||
* Get enums.
|
||||
*
|
||||
* @return array
|
||||
* @return array<self>
|
||||
*/
|
||||
public static function toArray(): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -89,6 +89,9 @@ trait HasPicture
|
|||
* @param integer|null $minHeight
|
||||
* @param integer|null $maxWidth
|
||||
* @param integer|null $maxHeight
|
||||
* @param integer $limitMin
|
||||
* @param integer $limitMax
|
||||
* @param boolean $nullable
|
||||
* @return array
|
||||
*/
|
||||
public function picturesRules(
|
||||
|
|
@ -96,16 +99,19 @@ trait HasPicture
|
|||
?int $minWidth = null,
|
||||
?int $minHeight = null,
|
||||
?int $maxWidth = null,
|
||||
?int $maxHeight = null
|
||||
?int $maxHeight = null,
|
||||
int $limitMin = 1,
|
||||
int $limitMax = 5,
|
||||
bool $nullable = false
|
||||
): array {
|
||||
$sizes = (object)[
|
||||
'minWidth' => $minWidth ?? $this->minWidth,
|
||||
'minHeight' => $minHeight ?? $this->minHeight,
|
||||
'maxWidth' => $maxWidth ?? $this->maxWidth,
|
||||
'maxHeight' => $maxHeight ?? $this->maxHeight
|
||||
'maxHeight' => $maxHeight ?? $this->maxHeight,
|
||||
];
|
||||
return [
|
||||
$name => 'sometimes|array',
|
||||
$name => [!$nullable ? 'required' : 'nullable', 'array', "between:{$limitMin},{$limitMax}"],
|
||||
"{$name}Validate" => "required_with:{$name}|array",
|
||||
"{$name}Validate.*" => [
|
||||
'required',
|
||||
|
|
|
|||
30
database/migrations/2030_01_01_000001_mandatory_seeds.php
Normal file
30
database/migrations/2030_01_01_000001_mandatory_seeds.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Database\Seeders\MandatorySeeder;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Artisan::call('db:seed', [
|
||||
'class' => MandatorySeeder::class
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
};
|
||||
|
|
@ -14,8 +14,8 @@ class DatabaseSeeder extends Seeder
|
|||
public function run()
|
||||
{
|
||||
$this->call([
|
||||
UserSeeder::class,
|
||||
StaticPageSeeder::class
|
||||
MandatorySeeder::class,
|
||||
DevelopmentSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
namespace Database\Seeders\Development;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
21
database/seeders/DevelopmentSeeder.php
Normal file
21
database/seeders/DevelopmentSeeder.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Database\Seeders\Development\UserSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DevelopmentSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->call([
|
||||
UserSeeder::class
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
namespace Database\Seeders\Mandatory;
|
||||
|
||||
use App\Enums\StaticPageType;
|
||||
use App\Models\StaticPage;
|
||||
21
database/seeders/MandatorySeeder.php
Normal file
21
database/seeders/MandatorySeeder.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Database\Seeders\Mandatory\StaticPageSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class MandatorySeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->call([
|
||||
StaticPageSeeder::class
|
||||
]);
|
||||
}
|
||||
}
|
||||
938
package-lock.json
generated
938
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -27,7 +27,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.36.2",
|
||||
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
||||
"@vitejs/plugin-vue": "latest",
|
||||
"@vitejs/plugin-vue": "^4.6.2",
|
||||
"@vue/compiler-sfc": "latest",
|
||||
"@vue/eslint-config-typescript": "latest",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
|
|
|
|||
|
|
@ -214,6 +214,10 @@ return [
|
|||
'username' => 'username',
|
||||
'year' => 'year',
|
||||
],
|
||||
|
||||
'youtube_id' => ':attribute must be a valid Youtube video ID',
|
||||
'vimeo_id' => ':attribute must be a valid Vimeo video ID',
|
||||
|
||||
'custom' => [
|
||||
'of_the' => [
|
||||
'male' => 'of the ',
|
||||
|
|
@ -229,5 +233,8 @@ return [
|
|||
'static_page_type' => 'static page type',
|
||||
'seo_title' => 'SEO title',
|
||||
'seo_description' => 'SEO description',
|
||||
'video_id' => 'ID of the video',
|
||||
'youtube_video_id' => 'ID od the youtube video',
|
||||
'vimeo_video_id' => 'ID of the vimeo video',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ return [
|
|||
|
||||
'minicrud' => [
|
||||
'visibility_label' => 'Visibilité',
|
||||
'visibility_helper' => 'Vérifier qu\'il est visible.',
|
||||
'visibility_helper' => 'Cocher pour rendre visible.',
|
||||
'enum_select' => 'Sélectionnez :enum dans la liste.',
|
||||
'associated_enum' => 'Associé :enum',
|
||||
'dimensions_must_be' => 'Doit être :widthpx (largeur) par :heightpx (hauteur).',
|
||||
|
|
|
|||
|
|
@ -214,6 +214,10 @@ return [
|
|||
'username' => 'nom d\'utilisateur',
|
||||
'year' => 'année',
|
||||
],
|
||||
|
||||
'youtube_id' => ':attribute doit être un ID de vidéo Youtube valide',
|
||||
'vimeo_id' => ':attribute doit être un ID de vidéo Vimeo valide',
|
||||
|
||||
'custom' => [
|
||||
'of_the' => [
|
||||
'male' => 'du ',
|
||||
|
|
@ -229,5 +233,8 @@ return [
|
|||
'static_page_type' => 'type de page',
|
||||
'seo_title' => 'titre SEO',
|
||||
'seo_description' => 'description SEO',
|
||||
'video_id' => 'ID de la vidéo',
|
||||
'youtube_video_id' => 'ID de la vidéo youtube',
|
||||
'vimeo_video_id' => 'ID de la vidéo viméo',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<div class="container image-input-vue p-0">
|
||||
<div
|
||||
ref="imageInput"
|
||||
class="container image-input-vue p-0"
|
||||
>
|
||||
<div class="row">
|
||||
<div :class="`col-12 ${intPreview ? 'col-md-6' : ''} form-group`">
|
||||
<label
|
||||
|
|
@ -400,7 +403,7 @@
|
|||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import Cropper from "cropperjs";
|
||||
import { defineComponent } from "vue";
|
||||
import bsTooltips from "./../../modules/bsTooltips";
|
||||
import { Tooltips } from "./../../modules/bsTooltips";
|
||||
import trans from "./../../modules/trans";
|
||||
|
||||
const UNITS = [
|
||||
|
|
@ -418,7 +421,7 @@ export default defineComponent({
|
|||
components: {
|
||||
FontAwesomeIcon,
|
||||
},
|
||||
mixins: [trans, bsTooltips],
|
||||
mixins: [trans],
|
||||
emits: {
|
||||
browseFile: (intId: string) => {
|
||||
intId;
|
||||
|
|
@ -431,7 +434,11 @@ export default defineComponent({
|
|||
},
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "");
|
||||
this.initTooltips();
|
||||
this.tooltips = Tooltips.make({
|
||||
type: "dom",
|
||||
elements: (this.$refs.imageInput as HTMLDivElement)
|
||||
.querySelectorAll("[data-bs-toggle=\"tooltip\"]")
|
||||
});
|
||||
|
||||
if (json.length) {
|
||||
// * Init uppon Laravel Blade
|
||||
|
|
@ -546,6 +553,7 @@ export default defineComponent({
|
|||
};
|
||||
intOriginalFileName: string;
|
||||
intPreviewStyle: string;
|
||||
tooltips: Tooltips | null;
|
||||
} {
|
||||
return {
|
||||
intId: "",
|
||||
|
|
@ -575,6 +583,7 @@ export default defineComponent({
|
|||
},
|
||||
intOriginalFileName: "",
|
||||
intPreviewStyle: "",
|
||||
tooltips: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -618,6 +627,7 @@ export default defineComponent({
|
|||
if (this.intBrowseEvent) {
|
||||
this.intHasImage = true;
|
||||
}
|
||||
this.tooltips?.refreshTooltips();
|
||||
},
|
||||
value: {
|
||||
deep: true,
|
||||
|
|
|
|||
|
|
@ -1,26 +1,36 @@
|
|||
<template>
|
||||
<button
|
||||
ref="passwordVisibility"
|
||||
@click.prevent="toggleVisibility()"
|
||||
@keydown="keydown"
|
||||
:class="`btn btn-primary rounded-0 rounded-end password-visibility-${inputId} h-100`"
|
||||
:aria-pressed="visible"
|
||||
>
|
||||
<span
|
||||
class="text-bg-primary reveal d-flex justify-content-center align-items-center h-100"
|
||||
|
||||
:class="{
|
||||
'text-bg-primary reveal d-flex justify-content-center align-items-center h-100': true,
|
||||
'd-none': visible
|
||||
}"
|
||||
type="button"
|
||||
:title="visible ? __('vue.PasswordVisibility.hide') : __('vue.PasswordVisibility.show')"
|
||||
data-bs="tooltip"
|
||||
:title="__('vue.PasswordVisibility.hide')"
|
||||
data-bs-toggle="tooltip"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
|
||||
v-if="visible"
|
||||
icon="fas fa-eye-slash"
|
||||
:aria-hidden="true"
|
||||
/>
|
||||
|
||||
</span>
|
||||
<span
|
||||
ref="passwordVisibilitySpan"
|
||||
:class="{
|
||||
'text-bg-primary reveal d-flex justify-content-center align-items-center h-100': true,
|
||||
'd-none': !visible
|
||||
}"
|
||||
type="button"
|
||||
:title="__('vue.PasswordVisibility.show')"
|
||||
data-bs-toggle="tooltip"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
v-else
|
||||
icon="fas fa-eye"
|
||||
:aria-hidden="true"
|
||||
/>
|
||||
|
|
@ -30,8 +40,8 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { Tooltip } from "bootstrap";
|
||||
import { defineComponent } from "vue";
|
||||
import { Tooltips } from "./../../modules/bsTooltips";
|
||||
import trans from "./../../modules/trans";
|
||||
|
||||
export default defineComponent({
|
||||
|
|
@ -42,13 +52,13 @@ export default defineComponent({
|
|||
inputId: string;
|
||||
input: HTMLInputElement;
|
||||
visible: boolean;
|
||||
tooltipList: HTMLButtonElement[];
|
||||
tooltips: Tooltips|null;
|
||||
} {
|
||||
return {
|
||||
inputId: "",
|
||||
input: document.createElement("input"),
|
||||
visible: false,
|
||||
tooltipList: [],
|
||||
tooltips: null,
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
|
|
@ -68,7 +78,10 @@ export default defineComponent({
|
|||
}
|
||||
this.input = this.input as HTMLInputElement;
|
||||
this.$nextTick(() => {
|
||||
this.updateBootstrapTooltip();
|
||||
this.tooltips = Tooltips.make({
|
||||
type: "dom",
|
||||
elements: (this.$refs.passwordVisibility as HTMLDivElement).querySelectorAll("[data-bs-toggle=\"tooltip\"]")
|
||||
});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -96,34 +109,6 @@ export default defineComponent({
|
|||
this.input.type = "password";
|
||||
this.visible = false;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.updateBootstrapTooltip();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Update Bootstrap tooltips.
|
||||
*/
|
||||
updateBootstrapTooltip() {
|
||||
let newTooltipList = [].slice.call(
|
||||
document.querySelectorAll(
|
||||
".password-visibility-" + this.inputId + " [data-bs=\"tooltip\"]"
|
||||
)
|
||||
) as HTMLButtonElement[];
|
||||
let tmp = newTooltipList.filter((x) => !this.tooltipList.includes(x));
|
||||
tmp.map((tooltip) => {
|
||||
return new Tooltip(tooltip);
|
||||
});
|
||||
this.closeBootstrapTooltip();
|
||||
this.tooltipList = newTooltipList;
|
||||
},
|
||||
/**
|
||||
* Close all Bootstrap tooltips.
|
||||
*/
|
||||
closeBootstrapTooltip() {
|
||||
this.tooltipList.forEach((tooltip) => {
|
||||
tooltip.blur();
|
||||
Tooltip.getInstance(tooltip)?.hide();
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import axios from "axios";
|
||||
import * as bootstrap from "bootstrap";
|
||||
import bsTooltips from "./../../modules/bsTooltips";
|
||||
import { Tooltips } from "./../../modules/bsTooltips";
|
||||
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
|
|
@ -18,4 +18,4 @@ axios.defaults.headers.common = {
|
|||
window.axios = axios;
|
||||
window.bootstrap = bootstrap;
|
||||
|
||||
bsTooltips.methods.initTooltips();
|
||||
Tooltips.make();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import axios from "axios";
|
||||
import * as bootstrap from "bootstrap";
|
||||
import bsTooltips from "./../../modules/bsTooltips";
|
||||
import { Tooltips } from "./../../modules/bsTooltips";
|
||||
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
|
|
@ -18,4 +18,4 @@ axios.defaults.headers.common = {
|
|||
window.axios = axios;
|
||||
window.bootstrap = bootstrap;
|
||||
|
||||
bsTooltips.methods.initTooltips();
|
||||
Tooltips.make();
|
||||
|
|
|
|||
|
|
@ -1,26 +1,114 @@
|
|||
import type * as bootstrap from "bootstrap";
|
||||
import { Tooltip } from "bootstrap";
|
||||
|
||||
let tooltips: Array<bootstrap.Tooltip> = [];
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
initTooltips() {
|
||||
// Disable tooltips on reboot.
|
||||
tooltips.forEach((tooltip) => {
|
||||
tooltip.disable();
|
||||
tooltip.hide();
|
||||
});
|
||||
tooltips = [].slice
|
||||
.call(document.querySelectorAll("[data-bs-toggle=\"tooltip\"]"))
|
||||
.map(function (tooltipTriggerEl) {
|
||||
return new window.bootstrap.Tooltip(tooltipTriggerEl, {
|
||||
customClass: "large-tooltip",
|
||||
type ConstructorTypeParent = "parent";
|
||||
type ConstructorTypeDom = "dom";
|
||||
|
||||
interface ParentSelectorTooltips {
|
||||
type: ConstructorTypeParent
|
||||
/** The parent element that contains Dom elements for which tooltips shall be created. */
|
||||
parentSelector: string;
|
||||
}
|
||||
|
||||
interface DomProvidedTooltips {
|
||||
type: ConstructorTypeDom;
|
||||
/** Dom elements for which tooltips shall be created. */
|
||||
elements: HTMLElement | NodeListOf<HTMLElement>;
|
||||
}
|
||||
|
||||
interface TooltipsOptions {
|
||||
/** Tooltip options. */
|
||||
options?: Partial<Tooltip.Options>|null|undefined;
|
||||
}
|
||||
|
||||
export type TooltipsConstructor = TooltipsOptions & (ParentSelectorTooltips|DomProvidedTooltips);
|
||||
|
||||
export class Tooltips {
|
||||
/** BS tooltips instances */
|
||||
private tooltips: Array<Tooltip> = [];
|
||||
|
||||
/** The parent element that contains Dom elements for which tooltips shall be created. */
|
||||
private parentSelector: string | null = null;
|
||||
|
||||
/** Dom elements for which tooltips shall be created. */
|
||||
private elements: Array<HTMLElement> = [];
|
||||
|
||||
/** Tooltip default options. */
|
||||
private static defaultOptions: Partial<Tooltip.Options> = {
|
||||
delay: {
|
||||
show: 1250,
|
||||
hide: 0,
|
||||
},
|
||||
};
|
||||
|
||||
/** Tooltip options. */
|
||||
private options: Partial<Tooltip.Options> = {};
|
||||
|
||||
/**
|
||||
* @param parentSelector
|
||||
* @param options Tooltip options.
|
||||
*/
|
||||
constructor(params: TooltipsConstructor) {
|
||||
if (params.type === "parent") {
|
||||
this.parentSelectorConstructor(params);
|
||||
}
|
||||
if (params.type === "dom") {
|
||||
this.domPrividedTooltipsContructor(params);
|
||||
}
|
||||
}
|
||||
|
||||
public static make(params: TooltipsConstructor = { type: "parent", parentSelector: "body"}
|
||||
) {
|
||||
const fullParams = { ...{ type: "parent", options: {} }, ...params };
|
||||
return new Tooltips(fullParams);
|
||||
}
|
||||
|
||||
private parentSelectorConstructor({ parentSelector, options }: TooltipsOptions & ParentSelectorTooltips) {
|
||||
this.parentSelector = parentSelector;
|
||||
this.options = { ...Tooltips.defaultOptions, ...options };
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
private domPrividedTooltipsContructor({ elements, options }: TooltipsOptions & DomProvidedTooltips) {
|
||||
if (elements instanceof HTMLElement) {
|
||||
this.elements = [elements];
|
||||
} else {
|
||||
this.elements = Array.from(elements);
|
||||
}
|
||||
this.options = { ...Tooltips.defaultOptions, ...options };
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all Bootstrap tooltips and reinstanciate them.
|
||||
*/
|
||||
public refreshTooltips(): void {
|
||||
this.closeBootstrapTooltip();
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all Bootstrap tooltips for ever.
|
||||
*/
|
||||
public closeBootstrapTooltip(): void {
|
||||
this.tooltips.forEach((tooltip) => {
|
||||
tooltip.hide();
|
||||
// * Prevent pending tooltips beeing shown after we asked to hide them
|
||||
tooltip.disable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize tooltips
|
||||
*/
|
||||
private initTooltips() {
|
||||
this.closeBootstrapTooltip();
|
||||
/** data-bs-toggle tooltips elements */
|
||||
const elements = this.parentSelector ? [].slice
|
||||
.call(document.querySelectorAll(`${this.parentSelector} [data-bs-toggle="tooltip"]`)) :
|
||||
this.elements;
|
||||
this.tooltips = elements.map((tooltipTriggerEl) => {
|
||||
return new Tooltip(tooltipTriggerEl, this.options);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue