mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-12 11:48:39 -04:00
Load specific JS via isolated modules, refactor CredentialService (#142)
This commit is contained in:
@@ -55,7 +55,7 @@
|
||||
/// <inheritdoc />
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (Module != null)
|
||||
if (Module is not null)
|
||||
{
|
||||
await Module.InvokeVoidAsync("unregisterClickOutsideHandler");
|
||||
await Module.DisposeAsync();
|
||||
@@ -69,9 +69,9 @@
|
||||
/// </summary>
|
||||
private async Task LoadModuleAsync()
|
||||
{
|
||||
if (Module == null)
|
||||
if (Module is null)
|
||||
{
|
||||
Module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/clickOutsideHandler.js");
|
||||
Module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/modules/clickOutsideHandler.js");
|
||||
ObjRef = DotNetObjectReference.Create(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using AliasGenerators.Identity.Implementations
|
||||
@using AliasGenerators.Identity.Models
|
||||
@inherits AliasVault.Client.Main.Pages.MainBase
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject CredentialService CredentialService
|
||||
@implements IDisposable
|
||||
@implements IAsyncDisposable
|
||||
|
||||
<button @ref="buttonRef" @onclick="TogglePopup" id="quickIdentityButton" class="px-4 ms-5 py-2 text-sm font-medium text-white bg-gradient-to-r from-primary-500 to-primary-600 hover:from-primary-600 hover:to-primary-700 focus:outline-none dark:from-primary-400 dark:to-primary-500 dark:hover:from-primary-500 dark:hover:to-primary-600 rounded-md shadow-sm transition duration-150 ease-in-out transform hover:scale-105 active:scale-95 focus:shadow-outline">
|
||||
+ New identity
|
||||
@@ -48,17 +46,16 @@
|
||||
private CreateModel Model = new();
|
||||
private string PopupStyle { get; set; } = "";
|
||||
private ElementReference buttonRef;
|
||||
private IJSObjectReference? Module;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
async ValueTask IAsyncDisposable.DisposeAsync()
|
||||
{
|
||||
KeyboardShortcutService.UnregisterShortcutAsync("gc").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await KeyboardShortcutService.RegisterShortcutAsync("gc", ShowPopup);
|
||||
await KeyboardShortcutService.UnregisterShortcutAsync("gc");
|
||||
if (Module is not null)
|
||||
{
|
||||
await Module.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -66,36 +63,14 @@
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("eval", @"
|
||||
window.getWindowWidth = function() {
|
||||
return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
};
|
||||
window.getElementRect = function(element) {
|
||||
if (element) {
|
||||
const rect = {
|
||||
left: element.offsetLeft,
|
||||
top: element.offsetTop,
|
||||
right: element.offsetLeft + element.offsetWidth,
|
||||
bottom: element.offsetTop + element.offsetHeight,
|
||||
width: element.offsetWidth,
|
||||
height: element.offsetHeight
|
||||
};
|
||||
let parent = element.offsetParent;
|
||||
while (parent) {
|
||||
rect.left += parent.offsetLeft;
|
||||
rect.top += parent.offsetTop;
|
||||
parent = parent.offsetParent;
|
||||
}
|
||||
rect.right = rect.left + rect.width;
|
||||
rect.bottom = rect.top + rect.height;
|
||||
return rect;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
");
|
||||
await KeyboardShortcutService.RegisterShortcutAsync("gc", ShowPopup);
|
||||
Module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/modules/newIdentityWidget.js");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the URL input is focused, place cursor at the end of the default URL to allow for easy typing.
|
||||
/// </summary>
|
||||
private void OnFocusUrlInput(FocusEventArgs e)
|
||||
{
|
||||
if (Model.ServiceUrl != DefaultServiceUrl)
|
||||
@@ -172,7 +147,7 @@
|
||||
}
|
||||
|
||||
credential.Passwords = new List<Password> { new() };
|
||||
await GenerateRandomIdentity(credential);
|
||||
await CredentialService.GenerateRandomIdentity(credential);
|
||||
|
||||
var id = await CredentialService.InsertEntryAsync(credential);
|
||||
if (id == Guid.Empty)
|
||||
@@ -195,30 +170,9 @@
|
||||
ClosePopup();
|
||||
}
|
||||
|
||||
private async Task GenerateRandomIdentity(Credential credential)
|
||||
{
|
||||
// Generate a random identity using the IIdentityGenerator implementation.
|
||||
var identity = await IdentityGeneratorFactory.CreateIdentityGenerator(DbService.Settings.DefaultIdentityLanguage).GenerateRandomIdentityAsync();
|
||||
|
||||
// Generate random values for the Identity properties
|
||||
credential.Username = identity.NickName;
|
||||
credential.Alias.FirstName = identity.FirstName;
|
||||
credential.Alias.LastName = identity.LastName;
|
||||
credential.Alias.NickName = identity.NickName;
|
||||
credential.Alias.Gender = identity.Gender == Gender.Male ? "Male" : "Female";
|
||||
credential.Alias.BirthDate = identity.BirthDate;
|
||||
|
||||
// Set the email
|
||||
var emailDomain = CredentialService.GetDefaultEmailDomain();
|
||||
credential.Alias.Email = $"{identity.EmailPrefix}@{emailDomain}";
|
||||
|
||||
// Generate password
|
||||
credential.Passwords.First().Value = CredentialService.GenerateRandomPassword();
|
||||
}
|
||||
|
||||
private void OpenAdvancedMode()
|
||||
{
|
||||
NavigationManager.NavigateTo("/add-credentials");
|
||||
NavigationManager.NavigateTo("/credentials/create");
|
||||
ClosePopup();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
@page "/add-credentials"
|
||||
@page "/credentials/create"
|
||||
@page "/credentials/{id:guid}/edit"
|
||||
@inherits MainBase
|
||||
@inject CredentialService CredentialService
|
||||
@using System.Globalization
|
||||
@using AliasGenerators.Identity.Implementations
|
||||
@using AliasGenerators.Identity.Models
|
||||
|
||||
@if (EditMode)
|
||||
{
|
||||
@@ -133,10 +131,6 @@ else
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid px-4 pt-6 lg:gap-4 dark:bg-gray-900">
|
||||
|
||||
</div>
|
||||
</EditForm>
|
||||
}
|
||||
|
||||
@@ -223,9 +217,7 @@ else
|
||||
Obj = CredentialToCredentialEdit(alias);
|
||||
}
|
||||
|
||||
// Hide loading spinner
|
||||
Loading = false;
|
||||
// Force re-render invoke so the charts can be rendered
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -241,23 +233,7 @@ else
|
||||
GlobalLoadingSpinner.Show();
|
||||
StateHasChanged();
|
||||
|
||||
// Generate a random identity using the IIdentityGenerator implementation.
|
||||
var identity = await IdentityGeneratorFactory.CreateIdentityGenerator(DbService.Settings.DefaultIdentityLanguage).GenerateRandomIdentityAsync();
|
||||
|
||||
// Generate random values for the Identity properties
|
||||
Obj.Username = identity.NickName;
|
||||
Obj.Alias.FirstName = identity.FirstName;
|
||||
Obj.Alias.LastName = identity.LastName;
|
||||
Obj.Alias.NickName = identity.NickName;
|
||||
Obj.Alias.Gender = identity.Gender == Gender.Male ? "Male" : "Female";
|
||||
Obj.AliasBirthDate = identity.BirthDate.ToString("yyyy-MM-dd");
|
||||
|
||||
// Set the email
|
||||
var emailDomain = CredentialService.GetDefaultEmailDomain();
|
||||
Obj.Alias.Email = $"{identity.EmailPrefix}@{emailDomain}";
|
||||
|
||||
// Generate password
|
||||
GenerateRandomPassword();
|
||||
Obj = CredentialToCredentialEdit(await CredentialService.GenerateRandomIdentity(CredentialEditToCredential(Obj)));
|
||||
|
||||
GlobalLoadingSpinner.Hide();
|
||||
StateHasChanged();
|
||||
@@ -273,8 +249,6 @@ else
|
||||
GlobalLoadingSpinner.Show();
|
||||
StateHasChanged();
|
||||
|
||||
// Sanity check for unittest. Delete later if not needed.
|
||||
// Try to parse birthdate as datetime. if it fails, set it to empty.
|
||||
if (EditMode)
|
||||
{
|
||||
if (Id is not null)
|
||||
@@ -310,6 +284,9 @@ else
|
||||
NavigationManager.NavigateTo("/credentials/" + Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to convert a Credential object to a CredentialEdit object.
|
||||
/// </summary>
|
||||
private CredentialEdit CredentialToCredentialEdit(Credential alias)
|
||||
{
|
||||
return new CredentialEdit
|
||||
@@ -334,6 +311,9 @@ else
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to convert a CredentialEdit object to a Credential object.
|
||||
/// </summary>
|
||||
private Credential CredentialEditToCredential(CredentialEdit alias)
|
||||
{
|
||||
var credential = new Credential()
|
||||
|
||||
@@ -14,6 +14,8 @@ using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using AliasClientDb;
|
||||
using AliasGenerators.Identity.Implementations;
|
||||
using AliasGenerators.Identity.Models;
|
||||
using AliasGenerators.Password;
|
||||
using AliasGenerators.Password.Implementations;
|
||||
using AliasVault.Shared.Models;
|
||||
@@ -25,6 +27,34 @@ using Identity = AliasGenerators.Identity.Models.Identity;
|
||||
/// </summary>
|
||||
public class CredentialService(HttpClient httpClient, DbService dbService, Config config)
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a random identity for a credential.
|
||||
/// </summary>
|
||||
/// <param name="credential">The credential object to update.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task<Credential> GenerateRandomIdentity(Credential credential)
|
||||
{
|
||||
// Generate a random identity using the IIdentityGenerator implementation.
|
||||
var identity = await IdentityGeneratorFactory.CreateIdentityGenerator(dbService.Settings.DefaultIdentityLanguage).GenerateRandomIdentityAsync();
|
||||
|
||||
// Generate random values for the Identity properties
|
||||
credential.Username = identity.NickName;
|
||||
credential.Alias.FirstName = identity.FirstName;
|
||||
credential.Alias.LastName = identity.LastName;
|
||||
credential.Alias.NickName = identity.NickName;
|
||||
credential.Alias.Gender = identity.Gender == Gender.Male ? "Male" : "Female";
|
||||
credential.Alias.BirthDate = identity.BirthDate;
|
||||
|
||||
// Set the email
|
||||
var emailDomain = GetDefaultEmailDomain();
|
||||
credential.Alias.Email = $"{identity.EmailPrefix}@{emailDomain}";
|
||||
|
||||
// Generate password
|
||||
credential.Passwords.First().Value = GenerateRandomPassword();
|
||||
|
||||
return credential;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default email domain based on settings and available domains.
|
||||
/// </summary>
|
||||
|
||||
@@ -20,6 +20,7 @@ public class KeyboardShortcutService : IAsyncDisposable
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
private readonly DotNetObjectReference<CallbackWrapper> _dotNetHelper;
|
||||
private readonly NavigationManager _navigationManager;
|
||||
private readonly Lazy<Task<IJSObjectReference>> moduleTask;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="KeyboardShortcutService"/> class.
|
||||
@@ -32,6 +33,9 @@ public class KeyboardShortcutService : IAsyncDisposable
|
||||
_dotNetHelper = DotNetObjectReference.Create(new CallbackWrapper());
|
||||
_navigationManager = navigationManager;
|
||||
|
||||
moduleTask = new Lazy<Task<IJSObjectReference>>(() => jsRuntime.InvokeAsync<IJSObjectReference>(
|
||||
"import", "./js/modules/keyboardShortcuts.js").AsTask());
|
||||
|
||||
_ = RegisterStaticShortcuts();
|
||||
}
|
||||
|
||||
@@ -44,7 +48,8 @@ public class KeyboardShortcutService : IAsyncDisposable
|
||||
public async Task RegisterShortcutAsync(string keys, Func<Task> callback)
|
||||
{
|
||||
_dotNetHelper.Value.RegisterCallback(keys, callback);
|
||||
await _jsRuntime.InvokeVoidAsync("keyboardShortcuts.registerShortcut", keys, _dotNetHelper);
|
||||
var module = await moduleTask.Value;
|
||||
await module.InvokeVoidAsync("registerShortcut", keys, _dotNetHelper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -55,7 +60,8 @@ public class KeyboardShortcutService : IAsyncDisposable
|
||||
public async Task UnregisterShortcutAsync(string keys)
|
||||
{
|
||||
_dotNetHelper.Value.UnregisterCallback(keys);
|
||||
await _jsRuntime.InvokeVoidAsync("keyboardShortcuts.unregisterShortcut", keys);
|
||||
var module = await moduleTask.Value;
|
||||
await module!.InvokeVoidAsync("unregisterShortcut", keys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,7 +70,10 @@ public class KeyboardShortcutService : IAsyncDisposable
|
||||
/// <returns>ValueTask.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _jsRuntime.InvokeVoidAsync("keyboardShortcuts.unregisterAllShortcuts");
|
||||
var module = await moduleTask.Value;
|
||||
await module.InvokeVoidAsync("unregisterAllShortcuts");
|
||||
await module.DisposeAsync();
|
||||
|
||||
_dotNetHelper.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
@@ -640,6 +640,11 @@ video {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.mx-3 {
|
||||
margin-left: 0.75rem;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.mx-4 {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
@@ -719,10 +724,6 @@ video {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.ms-5 {
|
||||
margin-inline-start: 1.25rem;
|
||||
}
|
||||
|
||||
.mt-0 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
@@ -747,18 +748,18 @@ video {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.ms-3 {
|
||||
margin-inline-start: 0.75rem;
|
||||
}
|
||||
|
||||
.ms-1 {
|
||||
margin-inline-start: 0.25rem;
|
||||
}
|
||||
|
||||
.ms-2 {
|
||||
margin-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
.ms-5 {
|
||||
margin-inline-start: 1.25rem;
|
||||
}
|
||||
|
||||
.ms-3 {
|
||||
margin-inline-start: 0.75rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -47,10 +47,8 @@
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<script src="js/darkMode.js?v=@CacheBuster"></script>
|
||||
<script src="js/cryptoInterop.js?v=@CacheBuster"></script>
|
||||
<script src="js/crypto.js?v=@CacheBuster"></script>
|
||||
<script src="js/utilities.js?v=@CacheBuster"></script>
|
||||
<script src="js/keyboardShortcuts.js?v=@CacheBuster"></script>
|
||||
<script src="_framework/blazor.webassembly.js?v=@CacheBuster"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
function initDarkModeSwitcher() {
|
||||
const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
|
||||
const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
|
||||
|
||||
if (themeToggleDarkIcon === null && themeToggleLightIcon === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (localStorage.getItem('color-theme')) {
|
||||
if (localStorage.getItem('color-theme') === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
themeToggleLightIcon.classList.remove('hidden');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
themeToggleDarkIcon.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Default to light mode if not set.
|
||||
document.documentElement.classList.remove('dark');
|
||||
themeToggleLightIcon.classList.remove('hidden');
|
||||
}
|
||||
|
||||
const themeToggleBtn = document.getElementById('theme-toggle');
|
||||
|
||||
let event = new Event('dark-mode');
|
||||
|
||||
themeToggleBtn.addEventListener('click', function () {
|
||||
// toggle icons
|
||||
themeToggleDarkIcon.classList.toggle('hidden');
|
||||
themeToggleLightIcon.classList.toggle('hidden');
|
||||
|
||||
// if set via local storage previously
|
||||
if (localStorage.getItem('color-theme')) {
|
||||
if (localStorage.getItem('color-theme') === 'light') {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('color-theme', 'dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('color-theme', 'light');
|
||||
}
|
||||
// if NOT set via local storage previously
|
||||
} else if (document.documentElement.classList.contains('dark')) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('color-theme', 'light');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('color-theme', 'dark');
|
||||
}
|
||||
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
window.keyboardShortcuts = {
|
||||
shortcuts: {},
|
||||
lastKeyPressed: '',
|
||||
lastKeyPressTime: 0,
|
||||
|
||||
init: function() {
|
||||
document.addEventListener('keydown', this.handleKeyPress.bind(this));
|
||||
},
|
||||
|
||||
handleKeyPress: function(event) {
|
||||
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTime = new Date().getTime();
|
||||
const key = event.key.toLowerCase();
|
||||
|
||||
if (currentTime - this.lastKeyPressTime > 500) {
|
||||
this.lastKeyPressed = '';
|
||||
}
|
||||
|
||||
this.lastKeyPressed += key;
|
||||
this.lastKeyPressTime = currentTime;
|
||||
|
||||
const shortcut = this.shortcuts[this.lastKeyPressed];
|
||||
if (shortcut) {
|
||||
event.preventDefault();
|
||||
shortcut.dotNetHelper.invokeMethodAsync('Invoke', this.lastKeyPressed);
|
||||
this.lastKeyPressed = '';
|
||||
}
|
||||
},
|
||||
|
||||
registerShortcut: function(keys, dotNetHelper) {
|
||||
this.shortcuts[keys.toLowerCase()] = { dotNetHelper: dotNetHelper };
|
||||
},
|
||||
|
||||
unregisterShortcut: function(keys) {
|
||||
delete this.shortcuts[keys.toLowerCase()];
|
||||
}
|
||||
};
|
||||
|
||||
window.keyboardShortcuts.init();
|
||||
@@ -0,0 +1,40 @@
|
||||
let shortcuts = {};
|
||||
let lastKeyPressed = '';
|
||||
let lastKeyPressTime = 0;
|
||||
|
||||
document.addEventListener('keydown', handleKeyPress);
|
||||
|
||||
export function handleKeyPress(event) {
|
||||
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTime = new Date().getTime();
|
||||
const key = event.key.toLowerCase();
|
||||
|
||||
if (currentTime - this.lastKeyPressTime > 500) {
|
||||
lastKeyPressed = '';
|
||||
}
|
||||
|
||||
lastKeyPressed += key;
|
||||
lastKeyPressTime = currentTime;
|
||||
|
||||
const shortcut = shortcuts[lastKeyPressed];
|
||||
if (shortcut) {
|
||||
event.preventDefault();
|
||||
shortcut.dotNetHelper.invokeMethodAsync('Invoke', lastKeyPressed);
|
||||
lastKeyPressed = '';
|
||||
}
|
||||
}
|
||||
|
||||
export function registerShortcut(keys, dotNetHelper) {
|
||||
shortcuts[keys.toLowerCase()] = { dotNetHelper: dotNetHelper };
|
||||
}
|
||||
|
||||
export function unregisterShortcut(keys) {
|
||||
delete shortcuts[keys.toLowerCase()];
|
||||
}
|
||||
|
||||
export function unregisterAllShortcuts() {
|
||||
shortcuts = {};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
window.getWindowWidth = function() {
|
||||
return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
};
|
||||
window.getElementRect = function(element) {
|
||||
if (element) {
|
||||
const rect = {
|
||||
left: element.offsetLeft,
|
||||
top: element.offsetTop,
|
||||
right: element.offsetLeft + element.offsetWidth,
|
||||
bottom: element.offsetTop + element.offsetHeight,
|
||||
width: element.offsetWidth,
|
||||
height: element.offsetHeight
|
||||
};
|
||||
let parent = element.offsetParent;
|
||||
while (parent) {
|
||||
rect.left += parent.offsetLeft;
|
||||
rect.top += parent.offsetTop;
|
||||
parent = parent.offsetParent;
|
||||
}
|
||||
rect.right = rect.left + rect.width;
|
||||
rect.bottom = rect.top + rect.height;
|
||||
return rect;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -10,10 +10,6 @@ function downloadFileFromStream(fileName, contentStreamReference) {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
window.initTopMenu = function() {
|
||||
initDarkModeSwitcher();
|
||||
};
|
||||
|
||||
window.topMenuClickOutsideHandler = (dotNetHelper) => {
|
||||
document.addEventListener('click', (event) => {
|
||||
const menu = document.getElementById('userMenuDropdown');
|
||||
@@ -50,3 +46,62 @@ window.focusElement = (elementId) => {
|
||||
element.focus();
|
||||
}
|
||||
};
|
||||
|
||||
function initDarkModeSwitcher() {
|
||||
const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
|
||||
const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
|
||||
|
||||
if (themeToggleDarkIcon === null && themeToggleLightIcon === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (localStorage.getItem('color-theme')) {
|
||||
if (localStorage.getItem('color-theme') === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
themeToggleLightIcon.classList.remove('hidden');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
themeToggleDarkIcon.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Default to light mode if not set.
|
||||
document.documentElement.classList.remove('dark');
|
||||
themeToggleLightIcon.classList.remove('hidden');
|
||||
}
|
||||
|
||||
const themeToggleBtn = document.getElementById('theme-toggle');
|
||||
|
||||
let event = new Event('dark-mode');
|
||||
|
||||
themeToggleBtn.addEventListener('click', function () {
|
||||
// toggle icons
|
||||
themeToggleDarkIcon.classList.toggle('hidden');
|
||||
themeToggleLightIcon.classList.toggle('hidden');
|
||||
|
||||
// if set via local storage previously
|
||||
if (localStorage.getItem('color-theme')) {
|
||||
if (localStorage.getItem('color-theme') === 'light') {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('color-theme', 'dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('color-theme', 'light');
|
||||
}
|
||||
// if NOT set via local storage previously
|
||||
} else if (document.documentElement.classList.contains('dark')) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('color-theme', 'light');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('color-theme', 'dark');
|
||||
}
|
||||
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
window.initTopMenu = function() {
|
||||
initDarkModeSwitcher();
|
||||
};
|
||||
|
||||
|
||||
@@ -170,8 +170,8 @@ public class ClientPlaywrightTest : PlaywrightTest
|
||||
/// <returns>Async task.</returns>
|
||||
protected async Task CreateCredentialEntry(Dictionary<string, string>? formValues = null)
|
||||
{
|
||||
await NavigateUsingBlazorRouter("add-credentials");
|
||||
await WaitForUrlAsync("add-credentials", "Add credentials");
|
||||
await NavigateUsingBlazorRouter("credentials/create");
|
||||
await WaitForUrlAsync("credentials/create", "Add credentials");
|
||||
|
||||
// Check if a button with text "Generate Random Identity" appears
|
||||
var generateButton = Page.Locator("text=Generate Random Identity");
|
||||
|
||||
@@ -97,8 +97,8 @@ public class CredentialTest : ClientPlaywrightTest
|
||||
// Create a new alias with service name = "Test Service".
|
||||
var serviceName = "Test Service";
|
||||
|
||||
await NavigateUsingBlazorRouter("add-credentials");
|
||||
await WaitForUrlAsync("add-credentials", "Add credentials");
|
||||
await NavigateUsingBlazorRouter("credentials/create");
|
||||
await WaitForUrlAsync("credentials/create", "Add credentials");
|
||||
|
||||
await InputHelper.FillInputFields(
|
||||
fieldValues: new Dictionary<string, string>
|
||||
|
||||
@@ -34,8 +34,8 @@ public class GeneralSettingsTest : ClientPlaywrightTest
|
||||
await defaultEmailDomainField.SelectOptionAsync("example2.tld");
|
||||
|
||||
// Go to new credential create page and assert that the default email domain is visible on the page.
|
||||
await NavigateUsingBlazorRouter("add-credentials");
|
||||
await WaitForUrlAsync("add-credentials", "Add credentials");
|
||||
await NavigateUsingBlazorRouter("credentials/create");
|
||||
await WaitForUrlAsync("credentials/create", "Add credentials");
|
||||
|
||||
var defaultEmailDomainText = await Page.TextContentAsync("body");
|
||||
Assert.That(defaultEmailDomainText, Does.Contain("example2.tld"), "Default email domain not visible on add credentials page.");
|
||||
|
||||
Reference in New Issue
Block a user