mirror of
https://github.com/unraid/api.git
synced 2025-12-30 21:19:49 -06:00
fix: UnraidToaster component and update dialog close button (#1657)
- Introduced a new UnraidToaster component for displaying notifications with customizable positions. - Updated the DialogClose component to use a span element for better semantic structure. - Enhanced CSS for the sonner component to ensure proper layout and styling. These changes improve user feedback through notifications and refine the dialog close button's implementation. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a toaster notifications component with configurable screen position, rich colors, and a close button; programmatic and legacy mounting helpers exposed. * **Style** * Updated toast close-button spacing and min-width behavior. * Simplified dialog close-button rendering and removed redundant style resets. * Reduced SSO provider icon size and added SSO button font-size tokens. * **Tests** * Added unit tests covering component mounting and global exports. * **Chores** * Deployment now performs broader remote cleanup before syncing. * **Chores** * Type declarations and tsconfig updated for global mount/utility typings. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -76,4 +76,21 @@ body {
|
||||
button:not(:disabled),
|
||||
[role='button']:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Font size overrides for SSO button component */
|
||||
unraid-sso-button {
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
--text-5xl: 3rem;
|
||||
--text-6xl: 3.75rem;
|
||||
--text-7xl: 4.5rem;
|
||||
--text-8xl: 6rem;
|
||||
--text-9xl: 8rem;
|
||||
}
|
||||
@@ -229,6 +229,8 @@
|
||||
top: 0;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
min-width: inherit !important;
|
||||
margin: 0 !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@@ -5,32 +5,7 @@ const props = defineProps<DialogCloseProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogClose v-bind="props">
|
||||
<DialogClose v-bind="props" as="span">
|
||||
<slot />
|
||||
</DialogClose>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* Reset webgui button styles for dialog close buttons */
|
||||
[role='dialog'] button[type='button'],
|
||||
button[aria-label*='close' i],
|
||||
button[aria-label*='dismiss' i] {
|
||||
/* Reset ALL webgui button styles using !important where needed */
|
||||
all: unset !important;
|
||||
|
||||
/* Re-apply necessary styles after reset */
|
||||
display: inline-flex !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
cursor: pointer !important;
|
||||
box-sizing: border-box !important;
|
||||
|
||||
/* Reset any webgui CSS variables */
|
||||
--button-border: none !important;
|
||||
--button-text-color: inherit !important;
|
||||
--button-background: transparent !important;
|
||||
--button-background-size: auto !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
278
web/__test__/components/standalone-mount.test.ts
Normal file
278
web/__test__/components/standalone-mount.test.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// Mock all the component imports
|
||||
vi.mock('~/components/Auth.ce.vue', () => ({
|
||||
default: { name: 'MockAuth', template: '<div>Auth</div>' }
|
||||
}));
|
||||
vi.mock('~/components/ConnectSettings/ConnectSettings.ce.vue', () => ({
|
||||
default: { name: 'MockConnectSettings', template: '<div>ConnectSettings</div>' }
|
||||
}));
|
||||
vi.mock('~/components/DownloadApiLogs.ce.vue', () => ({
|
||||
default: { name: 'MockDownloadApiLogs', template: '<div>DownloadApiLogs</div>' }
|
||||
}));
|
||||
vi.mock('~/components/HeaderOsVersion.ce.vue', () => ({
|
||||
default: { name: 'MockHeaderOsVersion', template: '<div>HeaderOsVersion</div>' }
|
||||
}));
|
||||
vi.mock('~/components/Modals.ce.vue', () => ({
|
||||
default: { name: 'MockModals', template: '<div>Modals</div>' }
|
||||
}));
|
||||
vi.mock('~/components/UserProfile.ce.vue', () => ({
|
||||
default: { name: 'MockUserProfile', template: '<div>UserProfile</div>' }
|
||||
}));
|
||||
vi.mock('~/components/UpdateOs.ce.vue', () => ({
|
||||
default: { name: 'MockUpdateOs', template: '<div>UpdateOs</div>' }
|
||||
}));
|
||||
vi.mock('~/components/DowngradeOs.ce.vue', () => ({
|
||||
default: { name: 'MockDowngradeOs', template: '<div>DowngradeOs</div>' }
|
||||
}));
|
||||
vi.mock('~/components/Registration.ce.vue', () => ({
|
||||
default: { name: 'MockRegistration', template: '<div>Registration</div>' }
|
||||
}));
|
||||
vi.mock('~/components/WanIpCheck.ce.vue', () => ({
|
||||
default: { name: 'MockWanIpCheck', template: '<div>WanIpCheck</div>' }
|
||||
}));
|
||||
vi.mock('~/components/Activation/WelcomeModal.ce.vue', () => ({
|
||||
default: { name: 'MockWelcomeModal', template: '<div>WelcomeModal</div>' }
|
||||
}));
|
||||
vi.mock('~/components/SsoButton.ce.vue', () => ({
|
||||
default: { name: 'MockSsoButton', template: '<div>SsoButton</div>' }
|
||||
}));
|
||||
vi.mock('~/components/Logs/LogViewer.ce.vue', () => ({
|
||||
default: { name: 'MockLogViewer', template: '<div>LogViewer</div>' }
|
||||
}));
|
||||
vi.mock('~/components/ThemeSwitcher.ce.vue', () => ({
|
||||
default: { name: 'MockThemeSwitcher', template: '<div>ThemeSwitcher</div>' }
|
||||
}));
|
||||
vi.mock('~/components/ApiKeyPage.ce.vue', () => ({
|
||||
default: { name: 'MockApiKeyPage', template: '<div>ApiKeyPage</div>' }
|
||||
}));
|
||||
vi.mock('~/components/DevModalTest.ce.vue', () => ({
|
||||
default: { name: 'MockDevModalTest', template: '<div>DevModalTest</div>' }
|
||||
}));
|
||||
vi.mock('~/components/ApiKeyAuthorize.ce.vue', () => ({
|
||||
default: { name: 'MockApiKeyAuthorize', template: '<div>ApiKeyAuthorize</div>' }
|
||||
}));
|
||||
vi.mock('~/components/UnraidToaster.vue', () => ({
|
||||
default: { name: 'MockUnraidToaster', template: '<div>UnraidToaster</div>' }
|
||||
}));
|
||||
|
||||
// Mock vue-mount-app module
|
||||
const mockAutoMountComponent = vi.fn();
|
||||
const mockMountVueApp = vi.fn();
|
||||
const mockGetMountedApp = vi.fn();
|
||||
|
||||
vi.mock('~/components/Wrapper/vue-mount-app', () => ({
|
||||
autoMountComponent: mockAutoMountComponent,
|
||||
mountVueApp: mockMountVueApp,
|
||||
getMountedApp: mockGetMountedApp,
|
||||
}));
|
||||
|
||||
// Mock theme store
|
||||
const mockSetTheme = vi.fn();
|
||||
const mockSetCssVars = vi.fn();
|
||||
const mockUseThemeStore = vi.fn(() => ({
|
||||
setTheme: mockSetTheme,
|
||||
setCssVars: mockSetCssVars,
|
||||
}));
|
||||
|
||||
vi.mock('~/store/theme', () => ({
|
||||
useThemeStore: mockUseThemeStore,
|
||||
}));
|
||||
|
||||
// Mock globalPinia
|
||||
vi.mock('~/store/globalPinia', () => ({
|
||||
globalPinia: { state: {} },
|
||||
}));
|
||||
|
||||
// Mock apollo client
|
||||
const mockApolloClient = {
|
||||
query: vi.fn(),
|
||||
mutate: vi.fn(),
|
||||
};
|
||||
vi.mock('~/helpers/create-apollo-client', () => ({
|
||||
client: mockApolloClient,
|
||||
}));
|
||||
|
||||
// Mock @vue/apollo-composable
|
||||
const mockProvideApolloClient = vi.fn();
|
||||
vi.mock('@vue/apollo-composable', () => ({
|
||||
provideApolloClient: mockProvideApolloClient,
|
||||
}));
|
||||
|
||||
// Mock graphql
|
||||
const mockParse = vi.fn();
|
||||
vi.mock('graphql', () => ({
|
||||
parse: mockParse,
|
||||
}));
|
||||
|
||||
// Mock @unraid/ui
|
||||
const mockEnsureTeleportContainer = vi.fn();
|
||||
vi.mock('@unraid/ui', () => ({
|
||||
ensureTeleportContainer: mockEnsureTeleportContainer,
|
||||
}));
|
||||
|
||||
describe('standalone-mount', () => {
|
||||
beforeEach(() => {
|
||||
// Reset module cache to ensure fresh imports
|
||||
vi.resetModules();
|
||||
|
||||
// Reset all mocks
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Use Vitest's unstubAllGlobals to clean up any global stubs from previous tests
|
||||
vi.unstubAllGlobals();
|
||||
|
||||
// Mock document methods
|
||||
vi.spyOn(document.head, 'appendChild').mockImplementation(() => document.createElement('style'));
|
||||
vi.spyOn(document, 'addEventListener').mockImplementation(() => {});
|
||||
|
||||
// Clear DOM
|
||||
document.head.innerHTML = '';
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.resetModules();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe('initialization', () => {
|
||||
|
||||
it('should set up Apollo client globally', async () => {
|
||||
await import('~/components/standalone-mount');
|
||||
|
||||
expect(window.apolloClient).toBe(mockApolloClient);
|
||||
expect(window.graphqlParse).toBe(mockParse);
|
||||
expect(window.gql).toBe(mockParse);
|
||||
expect(mockProvideApolloClient).toHaveBeenCalledWith(mockApolloClient);
|
||||
});
|
||||
|
||||
it('should initialize theme store', async () => {
|
||||
await import('~/components/standalone-mount');
|
||||
|
||||
expect(mockUseThemeStore).toHaveBeenCalled();
|
||||
expect(mockSetTheme).toHaveBeenCalled();
|
||||
expect(mockSetCssVars).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ensure teleport container exists', async () => {
|
||||
await import('~/components/standalone-mount');
|
||||
|
||||
expect(mockEnsureTeleportContainer).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('component auto-mounting', () => {
|
||||
it('should auto-mount all defined components', async () => {
|
||||
await import('~/components/standalone-mount');
|
||||
|
||||
// Verify that autoMountComponent was called multiple times
|
||||
expect(mockAutoMountComponent.mock.calls.length).toBeGreaterThan(0);
|
||||
|
||||
// Verify all calls have the correct structure
|
||||
mockAutoMountComponent.mock.calls.forEach(call => {
|
||||
expect(call[0]).toBeDefined(); // Component
|
||||
expect(call[1]).toBeDefined(); // Selector
|
||||
expect(call[2]).toMatchObject({
|
||||
appId: expect.any(String),
|
||||
useShadowRoot: false,
|
||||
});
|
||||
});
|
||||
|
||||
// Extract all selectors that were mounted
|
||||
const mountedSelectors = mockAutoMountComponent.mock.calls.map(call => call[1]);
|
||||
|
||||
// Verify critical components are mounted
|
||||
expect(mountedSelectors).toContain('unraid-auth');
|
||||
expect(mountedSelectors).toContain('unraid-modals');
|
||||
expect(mountedSelectors).toContain('unraid-user-profile');
|
||||
expect(mountedSelectors).toContain('uui-toaster');
|
||||
expect(mountedSelectors).toContain('#modals'); // Legacy modal selector
|
||||
|
||||
// Verify no shadow DOM is used
|
||||
const allUseShadowRoot = mockAutoMountComponent.mock.calls.every(
|
||||
call => call[2].useShadowRoot === false
|
||||
);
|
||||
expect(allUseShadowRoot).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('global exports', () => {
|
||||
it('should expose UnraidComponents globally', async () => {
|
||||
await import('~/components/standalone-mount');
|
||||
|
||||
expect(window.UnraidComponents).toBeDefined();
|
||||
expect(window.UnraidComponents).toHaveProperty('Auth');
|
||||
expect(window.UnraidComponents).toHaveProperty('ConnectSettings');
|
||||
expect(window.UnraidComponents).toHaveProperty('DownloadApiLogs');
|
||||
expect(window.UnraidComponents).toHaveProperty('HeaderOsVersion');
|
||||
expect(window.UnraidComponents).toHaveProperty('Modals');
|
||||
expect(window.UnraidComponents).toHaveProperty('UserProfile');
|
||||
expect(window.UnraidComponents).toHaveProperty('UpdateOs');
|
||||
expect(window.UnraidComponents).toHaveProperty('DowngradeOs');
|
||||
expect(window.UnraidComponents).toHaveProperty('Registration');
|
||||
expect(window.UnraidComponents).toHaveProperty('WanIpCheck');
|
||||
expect(window.UnraidComponents).toHaveProperty('WelcomeModal');
|
||||
expect(window.UnraidComponents).toHaveProperty('SsoButton');
|
||||
expect(window.UnraidComponents).toHaveProperty('LogViewer');
|
||||
expect(window.UnraidComponents).toHaveProperty('ThemeSwitcher');
|
||||
expect(window.UnraidComponents).toHaveProperty('ApiKeyPage');
|
||||
expect(window.UnraidComponents).toHaveProperty('DevModalTest');
|
||||
expect(window.UnraidComponents).toHaveProperty('ApiKeyAuthorize');
|
||||
expect(window.UnraidComponents).toHaveProperty('UnraidToaster');
|
||||
});
|
||||
|
||||
it('should expose utility functions globally', async () => {
|
||||
await import('~/components/standalone-mount');
|
||||
|
||||
expect(window.mountVueApp).toBe(mockMountVueApp);
|
||||
expect(window.getMountedApp).toBe(mockGetMountedApp);
|
||||
});
|
||||
|
||||
it('should create dynamic mount functions for each component', async () => {
|
||||
await import('~/components/standalone-mount');
|
||||
|
||||
// Check for some dynamic mount functions
|
||||
expect(typeof window.mountAuth).toBe('function');
|
||||
expect(typeof window.mountConnectSettings).toBe('function');
|
||||
expect(typeof window.mountUserProfile).toBe('function');
|
||||
expect(typeof window.mountModals).toBe('function');
|
||||
expect(typeof window.mountThemeSwitcher).toBe('function');
|
||||
|
||||
// Test calling a dynamic mount function
|
||||
const customSelector = '#custom-auth';
|
||||
window.mountAuth?.(customSelector);
|
||||
|
||||
expect(mockMountVueApp).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
selector: customSelector,
|
||||
useShadowRoot: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default selector when no custom selector provided', async () => {
|
||||
await import('~/components/standalone-mount');
|
||||
|
||||
// Call mount function without custom selector
|
||||
window.mountAuth?.();
|
||||
|
||||
expect(mockMountVueApp).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
selector: 'unraid-auth',
|
||||
useShadowRoot: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Skip SSR safety test as it's complex to test with module isolation
|
||||
describe.skip('SSR safety', () => {
|
||||
it('should not initialize when window is undefined', async () => {
|
||||
// This test is skipped because the module initialization happens at import time
|
||||
// and it's difficult to properly isolate the window object manipulation
|
||||
// The functionality is simple enough - just checking if window exists before running code
|
||||
});
|
||||
});
|
||||
});
|
||||
11
web/components/UnraidToaster.vue
Normal file
11
web/components/UnraidToaster.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { Toaster } from '@unraid/ui';
|
||||
|
||||
defineProps<{
|
||||
position: 'top-center' | 'top-right' | 'top-left' | 'bottom-center' | 'bottom-right' | 'bottom-left';
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Toaster rich-colors close-button :position="position" />
|
||||
</template>
|
||||
@@ -27,7 +27,7 @@ const handleClick = () => {
|
||||
<img
|
||||
v-if="props.provider.buttonIcon"
|
||||
:src="props.provider.buttonIcon"
|
||||
class="w-6 h-6 sso-button-icon flex-shrink-0"
|
||||
class="w-4 h-4 sso-button-icon flex-shrink-0"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Import all components
|
||||
import type { Component } from 'vue';
|
||||
import Auth from './Auth.ce.vue';
|
||||
import ConnectSettings from './ConnectSettings/ConnectSettings.ce.vue';
|
||||
import DownloadApiLogs from './DownloadApiLogs.ce.vue';
|
||||
@@ -17,7 +16,7 @@ import ThemeSwitcher from './ThemeSwitcher.ce.vue';
|
||||
import ApiKeyPage from './ApiKeyPage.ce.vue';
|
||||
import DevModalTest from './DevModalTest.ce.vue';
|
||||
import ApiKeyAuthorize from './ApiKeyAuthorize.ce.vue';
|
||||
|
||||
import UnraidToaster from './UnraidToaster.vue';
|
||||
// Import utilities
|
||||
import { autoMountComponent, mountVueApp, getMountedApp } from './Wrapper/vue-mount-app';
|
||||
import { useThemeStore } from '~/store/theme';
|
||||
@@ -27,92 +26,11 @@ import { provideApolloClient } from '@vue/apollo-composable';
|
||||
import { parse } from 'graphql';
|
||||
import { ensureTeleportContainer } from '@unraid/ui';
|
||||
|
||||
// Extend window interface for Apollo client
|
||||
declare global {
|
||||
interface Window {
|
||||
apolloClient: typeof apolloClient;
|
||||
gql: typeof parse;
|
||||
graphqlParse: typeof parse;
|
||||
}
|
||||
}
|
||||
// Window type definitions are automatically included via tsconfig.json
|
||||
|
||||
// Add pre-render CSS to hide components until they're mounted
|
||||
function injectPreRenderCSS() {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'unraid-prerender-css';
|
||||
style.textContent = `
|
||||
/* Hide unraid components during initial load to prevent FOUC */
|
||||
unraid-auth,
|
||||
unraid-connect-settings,
|
||||
unraid-download-api-logs,
|
||||
unraid-header-os-version,
|
||||
unraid-modals,
|
||||
unraid-user-profile,
|
||||
unraid-update-os,
|
||||
unraid-downgrade-os,
|
||||
unraid-registration,
|
||||
unraid-wan-ip-check,
|
||||
unraid-welcome-modal,
|
||||
unraid-sso-button,
|
||||
unraid-log-viewer,
|
||||
unraid-theme-switcher,
|
||||
unraid-api-key-manager,
|
||||
unraid-dev-modal-test,
|
||||
unraid-api-key-authorize {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* Show components once they have the unapi class (mounted) */
|
||||
unraid-auth.unapi,
|
||||
unraid-connect-settings.unapi,
|
||||
unraid-download-api-logs.unapi,
|
||||
unraid-header-os-version.unapi,
|
||||
unraid-modals.unapi,
|
||||
unraid-user-profile.unapi,
|
||||
unraid-update-os.unapi,
|
||||
unraid-downgrade-os.unapi,
|
||||
unraid-registration.unapi,
|
||||
unraid-wan-ip-check.unapi,
|
||||
unraid-welcome-modal.unapi,
|
||||
unraid-sso-button.unapi,
|
||||
unraid-log-viewer.unapi,
|
||||
unraid-theme-switcher.unapi,
|
||||
unraid-api-key-manager.unapi,
|
||||
unraid-dev-modal-test.unapi,
|
||||
unraid-api-key-authorize.unapi {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Font size overrides for SSO button component */
|
||||
unraid-sso-button {
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
--text-5xl: 3rem;
|
||||
--text-6xl: 3.75rem;
|
||||
--text-7xl: 4.5rem;
|
||||
--text-8xl: 6rem;
|
||||
--text-9xl: 8rem;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Initialize global Apollo client context
|
||||
if (typeof window !== 'undefined') {
|
||||
// Inject pre-render CSS as early as possible
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', injectPreRenderCSS);
|
||||
} else {
|
||||
injectPreRenderCSS();
|
||||
}
|
||||
|
||||
// Make Apollo client globally available
|
||||
window.apolloClient = apolloClient;
|
||||
|
||||
@@ -140,6 +58,7 @@ const componentMappings = [
|
||||
{ component: DownloadApiLogs, selector: 'unraid-download-api-logs', appId: 'download-api-logs' },
|
||||
{ component: HeaderOsVersion, selector: 'unraid-header-os-version', appId: 'header-os-version' },
|
||||
{ component: Modals, selector: 'unraid-modals', appId: 'modals' },
|
||||
{ component: Modals, selector: '#modals', appId: 'modals-legacy' }, // Legacy ID selector
|
||||
{ component: UserProfile, selector: 'unraid-user-profile', appId: 'user-profile' },
|
||||
{ component: UpdateOs, selector: 'unraid-update-os', appId: 'update-os' },
|
||||
{ component: DowngradeOs, selector: 'unraid-downgrade-os', appId: 'downgrade-os' },
|
||||
@@ -152,6 +71,8 @@ const componentMappings = [
|
||||
{ component: ApiKeyPage, selector: 'unraid-api-key-manager', appId: 'api-key-manager' },
|
||||
{ component: DevModalTest, selector: 'unraid-dev-modal-test', appId: 'dev-modal-test' },
|
||||
{ component: ApiKeyAuthorize, selector: 'unraid-api-key-authorize', appId: 'api-key-authorize' },
|
||||
{ component: UnraidToaster, selector: 'uui-toaster', appId: 'toaster' },
|
||||
{ component: UnraidToaster, selector: 'unraid-toaster', appId: 'toaster-legacy' }, // Legacy alias
|
||||
];
|
||||
|
||||
// Auto-mount all components
|
||||
@@ -162,20 +83,7 @@ componentMappings.forEach(({ component, selector, appId }) => {
|
||||
});
|
||||
});
|
||||
|
||||
// Special handling for Modals - also mount to #modals
|
||||
autoMountComponent(Modals, '#modals', {
|
||||
appId: 'modals-direct',
|
||||
useShadowRoot: false,
|
||||
});
|
||||
|
||||
// Expose functions globally for testing and dynamic mounting
|
||||
declare global {
|
||||
interface Window {
|
||||
UnraidComponents: Record<string, Component>;
|
||||
mountVueApp: typeof mountVueApp;
|
||||
getMountedApp: typeof getMountedApp;
|
||||
}
|
||||
}
|
||||
// Window interface extensions are defined in ~/types/window.d.ts
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
// Expose all components
|
||||
@@ -197,6 +105,7 @@ if (typeof window !== 'undefined') {
|
||||
ApiKeyPage,
|
||||
DevModalTest,
|
||||
ApiKeyAuthorize,
|
||||
UnraidToaster,
|
||||
};
|
||||
|
||||
// Expose utility functions
|
||||
|
||||
@@ -35,7 +35,7 @@ if [ "$has_standalone" = true ]; then
|
||||
# Ensure remote directory exists
|
||||
ssh root@"${server_name}" "mkdir -p /usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/standalone/"
|
||||
# Clear the remote standalone directory before rsyncing
|
||||
ssh root@"${server_name}" "rm -rf /usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/standalone/*"
|
||||
ssh root@"${server_name}" "rm -rf /usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/*"
|
||||
# Run rsync with proper quoting
|
||||
rsync -avz --delete -e "ssh" "$standalone_directory" "root@${server_name}:/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/standalone/"
|
||||
standalone_exit_code=$?
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
"extends": "./.nuxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["./types/window"]
|
||||
}
|
||||
}
|
||||
|
||||
52
web/types/window.d.ts
vendored
Normal file
52
web/types/window.d.ts
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { Component } from 'vue';
|
||||
import type { parse } from 'graphql';
|
||||
import type { client as apolloClient } from '~/helpers/create-apollo-client';
|
||||
import type { mountVueApp, getMountedApp } from '~/components/Wrapper/vue-mount-app';
|
||||
|
||||
/**
|
||||
* Global Window interface extensions for Unraid components
|
||||
* This file provides type definitions for properties added to the window object
|
||||
* by the standalone-mount.ts module
|
||||
*/
|
||||
declare global {
|
||||
interface Window {
|
||||
// Apollo GraphQL client and utilities
|
||||
apolloClient: typeof apolloClient;
|
||||
gql: typeof parse;
|
||||
graphqlParse: typeof parse;
|
||||
|
||||
// Vue component registry and utilities
|
||||
UnraidComponents: Record<string, Component>;
|
||||
mountVueApp: typeof mountVueApp;
|
||||
getMountedApp: typeof getMountedApp;
|
||||
|
||||
// Dynamic mount functions created at runtime
|
||||
// These are generated for each component in componentMappings
|
||||
mountAuth?: (selector?: string) => unknown;
|
||||
mountConnectSettings?: (selector?: string) => unknown;
|
||||
mountDownloadApiLogs?: (selector?: string) => unknown;
|
||||
mountHeaderOsVersion?: (selector?: string) => unknown;
|
||||
mountModals?: (selector?: string) => unknown;
|
||||
mountModalsLegacy?: (selector?: string) => unknown;
|
||||
mountUserProfile?: (selector?: string) => unknown;
|
||||
mountUpdateOs?: (selector?: string) => unknown;
|
||||
mountDowngradeOs?: (selector?: string) => unknown;
|
||||
mountRegistration?: (selector?: string) => unknown;
|
||||
mountWanIpCheck?: (selector?: string) => unknown;
|
||||
mountWelcomeModal?: (selector?: string) => unknown;
|
||||
mountSsoButton?: (selector?: string) => unknown;
|
||||
mountLogViewer?: (selector?: string) => unknown;
|
||||
mountThemeSwitcher?: (selector?: string) => unknown;
|
||||
mountApiKeyManager?: (selector?: string) => unknown;
|
||||
mountDevModalTest?: (selector?: string) => unknown;
|
||||
mountApiKeyAuthorize?: (selector?: string) => unknown;
|
||||
mountToaster?: (selector?: string) => unknown;
|
||||
mountToasterLegacy?: (selector?: string) => unknown;
|
||||
|
||||
// Index signature for any other dynamic mount functions
|
||||
[key: `mount${string}`]: ((selector?: string) => unknown) | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Export empty object to make this a module and enable augmentation
|
||||
export {};
|
||||
Reference in New Issue
Block a user