Merge pull request #22 from element-hq/t3chguy/banner
@@ -0,0 +1,45 @@
|
||||
# @element-hq/element-web-module-banner
|
||||
|
||||
Banner module for Element Web.
|
||||
Allows rendering a top bar with slide out left panel menu.
|
||||
|
||||
Supports the following configuration options:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- | ------ | ------------------------------------------------------------ |
|
||||
| logo_url | string | URL to the logo to render in the banner |
|
||||
| logo_link_url | string | URL to send the user to when clicking the logo in the banner |
|
||||
| menu | `Menu` | Data to render in the banner menu |
|
||||
|
||||
The `Menu` type is fulfilled by the following discriminated union:
|
||||
|
||||
### Univention menu
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------- | ------------ | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| type | "univention" | The type for this menu config |
|
||||
| ics_url | string | URL to the UCS Intercom Service, https://docs.software-univention.de/intercom-service/latest/architecture.html#endpoints |
|
||||
|
||||
### Static menu
|
||||
|
||||
| Key | Type | Description |
|
||||
| ---------- | ---------------- | ------------------------------------------------------------------------- |
|
||||
| type | "static" | The type for this menu config |
|
||||
| logo_url | string, optional | URL to the logo to render in the menu, defaults to banner logo if omitted |
|
||||
| categories | `[]Category` | Categories to render in the menu |
|
||||
|
||||
The `Category` type is fulfilled by the following interface:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ----- | -------- | -------------------------------- |
|
||||
| name | string | The name of this category |
|
||||
| links | `[]Link` | Links to render in this category |
|
||||
|
||||
The `Link` type is fulfilled by the following interface:
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------- | ---------------- | --------------------------------------- |
|
||||
| icon_uri | string | URL to the icon to render for this link |
|
||||
| name | string | The name to render for this link |
|
||||
| link_url | string | The URL to link to |
|
||||
| target | string, optional | The `target` to use for this link |
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@element-hq/element-web-module-banner",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"prepare": "vite build",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"lint:codestyle": "echo 'handled by lint:eslint'",
|
||||
"test": "echo no tests yet"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@element-hq/element-web-module-api": "^1.0.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/react": "^19",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"react": "^19",
|
||||
"rollup-plugin-external-globals": "^0.13.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-node-polyfills": "^0.23.0",
|
||||
"vite-plugin-svgr": "^4.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@vector-im/compound-design-tokens": "^4.0.3",
|
||||
"@vector-im/compound-web": "^7.11.0",
|
||||
"framer-motion": "^12.4.10",
|
||||
"styled-components": "^6.1.18",
|
||||
"zod": "^3.24.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import { type Api } from "@element-hq/element-web-module-api";
|
||||
import { Heading } from "@vector-im/compound-web";
|
||||
|
||||
import { type ModuleConfig } from "./config";
|
||||
import UniventionMenu from "./Univention/Menu";
|
||||
import Menu from "./Menu";
|
||||
import Logo from "./Logo.tsx";
|
||||
|
||||
const Root = styled.nav`
|
||||
height: ${({ theme }): string => theme.bannerHeight};
|
||||
background-color: ${({ theme }): string => theme.bannerBackgroundColor};
|
||||
border-bottom: "var(--cpd-border-width-1) solid var(--cpd-color-bg-subtle-primary)";
|
||||
display: flex;
|
||||
gap: var(--cpd-space-3x);
|
||||
`;
|
||||
|
||||
const LogoContainer = styled.div`
|
||||
display: flex;
|
||||
padding: var(--cpd-space-3x) 0;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
api: Api;
|
||||
logoUrl: string;
|
||||
href: string;
|
||||
menu: ModuleConfig["menu"];
|
||||
}
|
||||
|
||||
const Banner: FC<Props> = ({ api, logoUrl, href, menu }) => {
|
||||
let menuJsx;
|
||||
switch (menu.type) {
|
||||
case "static": {
|
||||
menuJsx = <Menu api={api} config={menu} fallbackLogoUrl={logoUrl} />;
|
||||
break;
|
||||
}
|
||||
case "univention": {
|
||||
menuJsx = <UniventionMenu api={api} config={menu} fallbackLogoUrl={logoUrl} />;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Root>
|
||||
{menuJsx}
|
||||
<LogoContainer>
|
||||
<Logo api={api} src={logoUrl} href={href} height="100%" />
|
||||
</LogoContainer>
|
||||
<Heading size="sm" weight="medium" as="h1">
|
||||
{api.config.get("brand")}
|
||||
</Heading>
|
||||
</Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default Banner;
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import { type Api } from "@element-hq/element-web-module-api";
|
||||
|
||||
const Anchor = styled.a`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const Image = styled.img<{
|
||||
height?: string;
|
||||
}>`
|
||||
align-self: center;
|
||||
height: ${({ height }): string => height ?? "32px"};
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
api: Api;
|
||||
src: string;
|
||||
height?: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
const Logo: FC<Props> = ({ api, src, href, height }) => {
|
||||
const img = <Image alt={api.i18n.translate("logo_alt")} src={src} height={height} />;
|
||||
|
||||
if (!href) return img;
|
||||
|
||||
return (
|
||||
<Anchor aria-label={api.i18n.translate("logo_link_label")} href={href}>
|
||||
{img}
|
||||
</Anchor>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type FC, type JSX, useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import * as Dialog from "@radix-ui/react-dialog";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import { InlineSpinner } from "@vector-im/compound-web";
|
||||
|
||||
import { StaticConfig } from "./config";
|
||||
import type { Api } from "@element-hq/element-web-module-api";
|
||||
import Logo from "./Logo.tsx";
|
||||
import TriggerIcon from "./trigger.svg?react";
|
||||
|
||||
const Sidebar = styled(motion.div)`
|
||||
padding: 0 var(--cpd-space-3x) var(--cpd-space-4x);
|
||||
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1);
|
||||
overflow: auto;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: ${({ theme }): string => theme.menuWidth};
|
||||
background: ${({ theme }): string => theme.menuBackgroundColor};
|
||||
border-radius: 0 16px 16px 0;
|
||||
`;
|
||||
|
||||
const SidebarHeading = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--cpd-space-5x);
|
||||
`;
|
||||
|
||||
const Trigger = styled.button`
|
||||
align-items: center;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
background-color: ${({ theme }): string => theme.triggerBackgroundColor};
|
||||
color: ${({ theme }): string => theme.triggerColor};
|
||||
width: ${({ theme }): string => theme.triggerWidth};
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: ${({ theme }): string => theme.triggerBackgroundColorHover};
|
||||
color: ${({ theme }): string => theme.triggerColorContrast};
|
||||
}
|
||||
|
||||
&:active,
|
||||
&[data-expanded="true"] {
|
||||
background-color: ${({ theme }): string => theme.triggerBackgroundColorPressed};
|
||||
color: ${({ theme }): string => theme.triggerColorContrast};
|
||||
}
|
||||
|
||||
svg {
|
||||
margin: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const CloseButton = styled.button`
|
||||
/* Reset button styles */
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: ${({ theme }): string => theme.menuButtonBackgroundColorHover};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: ${({ theme }): string => theme.menuButtonBackgroundColorPressed};
|
||||
}
|
||||
`;
|
||||
|
||||
const CategoryHeading = styled.h2`
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
color: ${({ theme }): string => theme.subheadingColor};
|
||||
margin-top: var(--cpd-space-4x);
|
||||
margin-bottom: var(--cpd-space-2x);
|
||||
`;
|
||||
|
||||
const LinkButton = styled.a`
|
||||
font-size: 14px;
|
||||
color: var(--cpd-color-text-action-primary);
|
||||
font-weight: var(--cpd-font-weight-medium);
|
||||
display: flex;
|
||||
border-radius: 8px;
|
||||
padding: var(--cpd-space-2x);
|
||||
align-items: center;
|
||||
|
||||
&:link {
|
||||
color: var(--cpd-color-text-action-primary);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: ${({ theme }): string => theme.menuButtonBackgroundColorHover};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: ${({ theme }): string => theme.menuButtonBackgroundColorPressed};
|
||||
}
|
||||
`;
|
||||
|
||||
const LinkLogo = styled.img`
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
border-radius: 4px;
|
||||
border: ${({ theme }): string => `var(--cpd-border-width-1) solid ${theme.menuButtonBackgroundColorPressed}`};
|
||||
background-color: ${({ theme }): string => theme.menuBackgroundColor};
|
||||
margin-right: var(--cpd-space-2x);
|
||||
`;
|
||||
|
||||
const CentredContainer = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
|
||||
svg {
|
||||
margin: 0 auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const Overlay = styled(motion.div)`
|
||||
background-color: rgba(238, 239, 242, 0.5);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
api: Api;
|
||||
config: StaticConfig | Error | null; // null for loading
|
||||
fallbackLogoUrl: string;
|
||||
}
|
||||
|
||||
const Category: FC<{
|
||||
data: StaticConfig["categories"][number];
|
||||
}> = ({ data }) => {
|
||||
return (
|
||||
<div>
|
||||
<CategoryHeading>{data.name}</CategoryHeading>
|
||||
{data.links.map((link) => (
|
||||
<LinkButton key={link.link_url} href={link.link_url} target={link.target ?? "_blank"}>
|
||||
<LinkLogo src={link.icon_uri} role="presentation" /> {link.name}
|
||||
</LinkButton>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Menu: FC<Props> = ({ api, config, fallbackLogoUrl }) => {
|
||||
const theme = useTheme();
|
||||
const width = parseInt(theme.menuWidth.slice(0, -2), 10);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
let content: JSX.Element;
|
||||
let logoUrl = fallbackLogoUrl;
|
||||
if (config instanceof Error) {
|
||||
content = <CentredContainer>{api.i18n.translate("univention_error")}</CentredContainer>;
|
||||
} else if (config) {
|
||||
content = (
|
||||
<>
|
||||
{config.categories.map((category) => (
|
||||
<Category key={category.name} data={category} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
if (config.logo_url) {
|
||||
logoUrl = config.logo_url;
|
||||
}
|
||||
} else {
|
||||
content = (
|
||||
<CentredContainer>
|
||||
<InlineSpinner size={32} />
|
||||
</CentredContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Trigger asChild>
|
||||
<Trigger aria-haspopup={true} aria-expanded={open} aria-label={api.i18n.translate("trigger_label")}>
|
||||
<TriggerIcon />
|
||||
</Trigger>
|
||||
</Dialog.Trigger>
|
||||
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<Dialog.Portal forceMount>
|
||||
<Dialog.Overlay asChild>
|
||||
<Overlay
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
data-testid="dialog-overlay"
|
||||
/>
|
||||
</Dialog.Overlay>
|
||||
<Dialog.Content asChild>
|
||||
<Sidebar
|
||||
initial={{ x: -width }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: -width }}
|
||||
transition={{ type: "tween", ease: "easeInOut", duration: 0.3 }}
|
||||
aria-label={api.i18n.translate("menu_label")}
|
||||
>
|
||||
<Dialog.Title>
|
||||
<SidebarHeading>
|
||||
<Logo api={api} src={logoUrl} />
|
||||
<Dialog.Close asChild>
|
||||
<CloseButton
|
||||
aria-label={api.i18n.translate("close_label")}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.04167 13.9999L6 12.9583L8.9375 9.99992L6 7.06242L7.04167 6.02075L10 8.95825L12.9375 6.02075L13.9792 7.06242L11.0417 9.99992L13.9792 12.9583L12.9375 13.9999L10 11.0624L7.04167 13.9999Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</CloseButton>
|
||||
</Dialog.Close>
|
||||
</SidebarHeading>
|
||||
</Dialog.Title>
|
||||
{content}
|
||||
</Sidebar>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Dialog.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default Menu;
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { FC, useEffect, useState } from "react";
|
||||
|
||||
import type { Api } from "@element-hq/element-web-module-api";
|
||||
import { StaticConfig, UniventionConfig } from "../config";
|
||||
import StaticMenu from "../Menu";
|
||||
import { fetchNavigation } from "./navigation";
|
||||
import SilentLogin from "./SilentLogin";
|
||||
|
||||
interface Props {
|
||||
api: Api;
|
||||
config: UniventionConfig;
|
||||
fallbackLogoUrl: string;
|
||||
}
|
||||
|
||||
const Menu: FC<Props> = ({ api, config, fallbackLogoUrl }) => {
|
||||
const [loggedIn, setLoggedIn] = useState(false);
|
||||
const [data, setData] = useState<StaticConfig | Error>();
|
||||
const language = api.i18n.language.toLowerCase().startsWith("de") ? "de-DE" : "en";
|
||||
|
||||
useEffect(() => {
|
||||
let discard = false;
|
||||
|
||||
setData(undefined);
|
||||
fetchNavigation(config.ics_url, language)
|
||||
.then((data) => {
|
||||
if (discard) return;
|
||||
setData(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (discard) return;
|
||||
setData(error);
|
||||
});
|
||||
|
||||
return (): void => {
|
||||
discard = true;
|
||||
};
|
||||
}, [config, language, loggedIn]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!loggedIn && <SilentLogin onLoggedIn={setLoggedIn} icsUrl={config.ics_url} />}
|
||||
<StaticMenu api={api} config={data ?? null} fallbackLogoUrl={config.logo_url ?? fallbackLogoUrl} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Menu;
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { FC, useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const HiddenIFrame = styled.iframe`
|
||||
display: none;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
icsUrl: string;
|
||||
onLoggedIn(success: boolean): void;
|
||||
}
|
||||
|
||||
const SilentLogin: FC<Props> = ({ onLoggedIn, icsUrl }) => {
|
||||
const url = new URL("silent", icsUrl);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (event: MessageEvent): void => {
|
||||
if (event.origin === url.origin && typeof event.data === "object" && event.data["loggedIn"] === true) {
|
||||
onLoggedIn(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", listener);
|
||||
|
||||
return (): void => {
|
||||
window.removeEventListener("message", listener);
|
||||
};
|
||||
}, [onLoggedIn, url.origin]);
|
||||
|
||||
return <HiddenIFrame src={url.href} />;
|
||||
};
|
||||
|
||||
export default SilentLogin;
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { StaticConfig } from "../config";
|
||||
|
||||
/**
|
||||
* Univention Central Navigation API
|
||||
* https://docs.software-univention.de/nubus-kubernetes-customization/1.x/en/api/central-navigation.html
|
||||
*/
|
||||
const UniventionCentralNavigation = z.object({
|
||||
categories: z.array(
|
||||
z.object({
|
||||
identifier: z.string(),
|
||||
display_name: z.string(),
|
||||
entries: z.array(
|
||||
z.object({
|
||||
/**
|
||||
* A unique identifier for the navigation item.
|
||||
*/
|
||||
identifier: z.string(),
|
||||
/**
|
||||
* The URL to the icon in SVG format.
|
||||
*/
|
||||
icon_url: z.string(),
|
||||
/**
|
||||
* The label of the link.
|
||||
*/
|
||||
display_name: z.string(),
|
||||
/**
|
||||
* The destination URL of the link.
|
||||
*/
|
||||
link: z.string(),
|
||||
/**
|
||||
* The browsing context in which the browser opens the link.
|
||||
* Corresponds to the `target` property of `<a>` tags in HTML.
|
||||
*/
|
||||
target: z.string(),
|
||||
/**
|
||||
* It’s usually empty.
|
||||
*/
|
||||
keywords: z.object({}).optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
type UniventionCentralNavigation = z.infer<typeof UniventionCentralNavigation>;
|
||||
|
||||
function navigationToConfig(navigation: UniventionCentralNavigation): StaticConfig {
|
||||
return {
|
||||
type: "static",
|
||||
categories: navigation.categories.map((category) => ({
|
||||
name: category.display_name,
|
||||
links: category.entries.map((entry) => ({
|
||||
icon_uri: entry.icon_url,
|
||||
name: entry.display_name,
|
||||
link_url: entry.link,
|
||||
target: entry.target,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchNavigation(icsUrl: string, language: string): Promise<StaticConfig> {
|
||||
const url = new URL("navigation.json", icsUrl);
|
||||
url.search = `?language=${language}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch navigation: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const config = await UniventionCentralNavigation.parseAsync(data);
|
||||
return navigationToConfig(config);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { z, ZodSchema, ZodTypeDef } from "zod";
|
||||
|
||||
import { Theme } from "./theme.ts";
|
||||
|
||||
const StaticConfig = z.object({
|
||||
type: z.literal("static"),
|
||||
|
||||
/**
|
||||
* Alternative logo URL to display in the popover menu.
|
||||
* Will use the main logo url if omitted.
|
||||
*/
|
||||
logo_url: z.string().url().optional(),
|
||||
|
||||
/**
|
||||
* Categories of links to display in the menu.
|
||||
*/
|
||||
categories: z.array(
|
||||
z.object({
|
||||
/**
|
||||
* The category display name
|
||||
*/
|
||||
name: z.string(),
|
||||
/**
|
||||
* List of links to display in the category
|
||||
*/
|
||||
links: z.array(
|
||||
z.object({
|
||||
/**
|
||||
* The URL to the icon.
|
||||
*/
|
||||
icon_uri: z.string().url(),
|
||||
/**
|
||||
* The label of the link.
|
||||
*/
|
||||
name: z.string(),
|
||||
/**
|
||||
* The destination URL of the link.
|
||||
*/
|
||||
link_url: z.string().url(),
|
||||
/**
|
||||
* The browsing context in which the browser opens the link.
|
||||
*/
|
||||
target: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export type StaticConfig = z.infer<typeof StaticConfig>;
|
||||
|
||||
const UniventionConfig = z.object({
|
||||
type: z.literal("univention"),
|
||||
|
||||
/**
|
||||
* Alternative logo URL to display in the popover menu.
|
||||
* Will use the main logo url if omitted.
|
||||
*/
|
||||
logo_url: z.string().url().optional(),
|
||||
|
||||
/**
|
||||
* Base URL to an Intercom Service
|
||||
* https://docs.software-univention.de/intercom-service/latest/architecture.html#endpoints
|
||||
*/
|
||||
ics_url: z.string().url(),
|
||||
});
|
||||
|
||||
export type UniventionConfig = z.infer<typeof UniventionConfig>;
|
||||
|
||||
export const ModuleConfig = z.object({
|
||||
/**
|
||||
* The URL of the portal logo.svg file.
|
||||
* @example `https://example.com/logo.svg`
|
||||
*/
|
||||
logo_url: z.string().url(),
|
||||
|
||||
/**
|
||||
* The URL of the portal.
|
||||
* @example `https://example.com`
|
||||
*/
|
||||
logo_link_url: z.string().url(),
|
||||
|
||||
/**
|
||||
* Configuration for the menu.
|
||||
*/
|
||||
menu: z.discriminatedUnion("type", [StaticConfig, UniventionConfig]),
|
||||
|
||||
/**
|
||||
* Theme variable overrides, optional.
|
||||
*/
|
||||
theme: Theme.default({}),
|
||||
});
|
||||
|
||||
export type ModuleConfig = z.infer<typeof ModuleConfig>;
|
||||
|
||||
export type ConfigSchema = ZodSchema<z.output<typeof ModuleConfig>, ZodTypeDef, z.input<typeof ModuleConfig>>;
|
||||
|
||||
export const CONFIG_KEY = "io.element.element-web-modules.banner";
|
||||
|
||||
declare module "@element-hq/element-web-module-api" {
|
||||
export interface Config {
|
||||
[CONFIG_KEY]: ConfigSchema["_input"];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { ThemeProvider } from "styled-components";
|
||||
|
||||
import type { Module, Api, ModuleFactory } from "@element-hq/element-web-module-api";
|
||||
import Translations from "./translations.json";
|
||||
import { ModuleConfig, CONFIG_KEY } from "./config";
|
||||
import Banner from "./Banner";
|
||||
import { name as ModuleName } from "../package.json";
|
||||
|
||||
class BannerModule implements Module {
|
||||
public static readonly moduleApiVersion = "^1.0.0";
|
||||
|
||||
private config?: ModuleConfig;
|
||||
|
||||
public constructor(private api: Api) {}
|
||||
|
||||
public async load(): Promise<void> {
|
||||
this.api.i18n.register(Translations);
|
||||
|
||||
try {
|
||||
this.config = ModuleConfig.parse(this.api.config.get(CONFIG_KEY));
|
||||
} catch (e) {
|
||||
console.error("Failed to init module", e);
|
||||
throw new Error(`Errors in module configuration for "${ModuleName}"`);
|
||||
}
|
||||
|
||||
const div = document.createElement("div");
|
||||
this.api.rootNode.before(div);
|
||||
|
||||
const root = this.api.createRoot(div);
|
||||
root.render(
|
||||
<ThemeProvider theme={this.config.theme}>
|
||||
<Banner
|
||||
api={this.api}
|
||||
logoUrl={this.config.logo_url}
|
||||
href={this.config.logo_link_url}
|
||||
menu={this.config.menu}
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BannerModule satisfies ModuleFactory;
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
export const Theme = z.object({
|
||||
textColor: z.string().default("var(--cpd-color-text-primary)"),
|
||||
subheadingColor: z.string().default("var(--cpd-color-text-secondary)"),
|
||||
bannerBackgroundColor: z.string().default("var(--cpd-color-bg-canvas-default)"),
|
||||
bannerHeight: z.string().default("60px"),
|
||||
triggerWidth: z.string().default("68px"),
|
||||
triggerBackgroundColor: z.string().default("var(--cpd-color-bg-subtle-secondary)"),
|
||||
triggerBackgroundColorHover: z.string().default("var(--cpd-color-bg-accent-hovered)"),
|
||||
triggerBackgroundColorPressed: z.string().default("var(--cpd-color-bg-accent-pressed)"),
|
||||
triggerColor: z.string().default("var(--cpd-color-icon-primary)"),
|
||||
triggerColorContrast: z.string().default("var(--cpd-color-icon-on-solid-primary)"),
|
||||
menuWidth: z.string().default("320px"),
|
||||
menuBackgroundColor: z.string().default("var(--cpd-color-bg-canvas-default)"),
|
||||
menuButtonBackgroundColorHover: z.string().default("var(--cpd-color-bg-action-secondary-hovered)"),
|
||||
menuButtonBackgroundColorPressed: z.string().default("var(--cpd-color-bg-action-secondary-pressed)"),
|
||||
});
|
||||
|
||||
export type Theme = z.infer<typeof Theme>;
|
||||
|
||||
declare module "styled-components" {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface DefaultTheme extends Theme {}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"logo_alt": {
|
||||
"en": "Portal logo",
|
||||
"de": "Portal Logo"
|
||||
},
|
||||
"menu_label": {
|
||||
"en": "Menu",
|
||||
"de": "Menü"
|
||||
},
|
||||
"trigger_label": {
|
||||
"en": "Show menu",
|
||||
"de": "Menü anzeigen"
|
||||
},
|
||||
"close_label": {
|
||||
"en": "Close menu",
|
||||
"de": "Menü schließen"
|
||||
},
|
||||
"logo_link_label": {
|
||||
"en": "Show portal",
|
||||
"de": "Portal anzeigen"
|
||||
},
|
||||
"univention_error": {
|
||||
"en": "Failed to load data from Univention",
|
||||
"de": "Daten konnten nicht von Univention geladen werden"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.99992 16.6667C4.54159 16.6667 4.14922 16.5035 3.82284 16.1771C3.49645 15.8507 3.33325 15.4584 3.33325 15C3.33325 14.5417 3.49645 14.1493 3.82284 13.823C4.14922 13.4966 4.54159 13.3334 4.99992 13.3334C5.45825 13.3334 5.85061 13.4966 6.177 13.823C6.50339 14.1493 6.66659 14.5417 6.66659 15C6.66659 15.4584 6.50339 15.8507 6.177 16.1771C5.85061 16.5035 5.45825 16.6667 4.99992 16.6667ZM9.99992 16.6667C9.54158 16.6667 9.14922 16.5035 8.82284 16.1771C8.49645 15.8507 8.33325 15.4584 8.33325 15C8.33325 14.5417 8.49645 14.1493 8.82284 13.823C9.14922 13.4966 9.54158 13.3334 9.99992 13.3334C10.4583 13.3334 10.8506 13.4966 11.177 13.823C11.5034 14.1493 11.6666 14.5417 11.6666 15C11.6666 15.4584 11.5034 15.8507 11.177 16.1771C10.8506 16.5035 10.4583 16.6667 9.99992 16.6667ZM14.9999 16.6667C14.5416 16.6667 14.1492 16.5035 13.8228 16.1771C13.4964 15.8507 13.3333 15.4584 13.3333 15C13.3333 14.5417 13.4964 14.1493 13.8228 13.823C14.1492 13.4966 14.5416 13.3334 14.9999 13.3334C15.4583 13.3334 15.8506 13.4966 16.177 13.823C16.5034 14.1493 16.6666 14.5417 16.6666 15C16.6666 15.4584 16.5034 15.8507 16.177 16.1771C15.8506 16.5035 15.4583 16.6667 14.9999 16.6667ZM4.99992 11.6667C4.54159 11.6667 4.14922 11.5035 3.82284 11.1771C3.49645 10.8507 3.33325 10.4584 3.33325 10C3.33325 9.54171 3.49645 9.14935 3.82284 8.82296C4.14922 8.49657 4.54159 8.33337 4.99992 8.33337C5.45825 8.33337 5.85061 8.49657 6.177 8.82296C6.50339 9.14935 6.66659 9.54171 6.66659 10C6.66659 10.4584 6.50339 10.8507 6.177 11.1771C5.85061 11.5035 5.45825 11.6667 4.99992 11.6667ZM9.99992 11.6667C9.54158 11.6667 9.14922 11.5035 8.82284 11.1771C8.49645 10.8507 8.33325 10.4584 8.33325 10C8.33325 9.54171 8.49645 9.14935 8.82284 8.82296C9.14922 8.49657 9.54158 8.33337 9.99992 8.33337C10.4583 8.33337 10.8506 8.49657 11.177 8.82296C11.5034 9.14935 11.6666 9.54171 11.6666 10C11.6666 10.4584 11.5034 10.8507 11.177 11.1771C10.8506 11.5035 10.4583 11.6667 9.99992 11.6667ZM14.9999 11.6667C14.5416 11.6667 14.1492 11.5035 13.8228 11.1771C13.4964 10.8507 13.3333 10.4584 13.3333 10C13.3333 9.54171 13.4964 9.14935 13.8228 8.82296C14.1492 8.49657 14.5416 8.33337 14.9999 8.33337C15.4583 8.33337 15.8506 8.49657 16.177 8.82296C16.5034 9.14935 16.6666 9.54171 16.6666 10C16.6666 10.4584 16.5034 10.8507 16.177 11.1771C15.8506 11.5035 15.4583 11.6667 14.9999 11.6667ZM4.99992 6.66671C4.54159 6.66671 4.14922 6.50351 3.82284 6.17712C3.49645 5.85074 3.33325 5.45837 3.33325 5.00004C3.33325 4.54171 3.49645 4.14935 3.82284 3.82296C4.14922 3.49657 4.54159 3.33337 4.99992 3.33337C5.45825 3.33337 5.85061 3.49657 6.177 3.82296C6.50339 4.14935 6.66659 4.54171 6.66659 5.00004C6.66659 5.45837 6.50339 5.85074 6.177 6.17712C5.85061 6.50351 5.45825 6.66671 4.99992 6.66671ZM9.99992 6.66671C9.54158 6.66671 9.14922 6.50351 8.82284 6.17712C8.49645 5.85074 8.33325 5.45837 8.33325 5.00004C8.33325 4.54171 8.49645 4.14935 8.82284 3.82296C9.14922 3.49657 9.54158 3.33337 9.99992 3.33337C10.4583 3.33337 10.8506 3.49657 11.177 3.82296C11.5034 4.14935 11.6666 4.54171 11.6666 5.00004C11.6666 5.45837 11.5034 5.85074 11.177 6.17712C10.8506 6.50351 10.4583 6.66671 9.99992 6.66671ZM14.9999 6.66671C14.5416 6.66671 14.1492 6.50351 13.8228 6.17712C13.4964 5.85074 13.3333 5.45837 13.3333 5.00004C13.3333 4.54171 13.4964 4.14935 13.8228 3.82296C14.1492 3.49657 14.5416 3.33337 14.9999 3.33337C15.4583 3.33337 15.8506 3.49657 16.177 3.82296C16.5034 4.14935 16.6666 4.54171 16.6666 5.00004C16.6666 5.45837 16.5034 5.85074 16.177 6.17712C15.8506 6.50351 15.4583 6.66671 14.9999 6.66671Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { test as base, expect } from "../../../../playwright/element-web-test.ts";
|
||||
import { type ConfigSchema } from "../src/config.ts";
|
||||
|
||||
const test = base.extend<{
|
||||
// Resolver for when to respond to the navigation.json request
|
||||
navigationJsonResolver: PromiseWithResolvers<void>;
|
||||
}>({
|
||||
navigationJsonResolver: async ({}, use) => {
|
||||
await use(Promise.withResolvers<void>());
|
||||
},
|
||||
});
|
||||
|
||||
test.describe("Banner", () => {
|
||||
test.use({
|
||||
displayName: "Timmy",
|
||||
page: async ({ context, page, moduleDir }, use) => {
|
||||
for (const path of ["logo.svg", "app1.png", "app2.png", "opendesk/"]) {
|
||||
await context.route(`/${path}*`, async (route) => {
|
||||
const file = new URL(route.request().url()).pathname;
|
||||
await route.fulfill({ path: `${moduleDir}/tests/fixture/${file}` });
|
||||
});
|
||||
}
|
||||
|
||||
await page.goto("/");
|
||||
await use(page);
|
||||
},
|
||||
});
|
||||
|
||||
test("should error if config is missing", { tag: ["@screenshot"] }, async ({ page }) => {
|
||||
await expect(page.getByText("Your Element is misconfigured")).toBeVisible();
|
||||
await expect(page.getByText("Errors in module configuration")).toBeVisible();
|
||||
// We don't take a screenshot as we don't want to assert Element's styling, only our own
|
||||
});
|
||||
|
||||
const configs: ConfigSchema["_input"][] = [
|
||||
{
|
||||
logo_url: "http://localhost:8080/logo.svg",
|
||||
logo_link_url: "https://example.com/portal",
|
||||
menu: {
|
||||
type: "static",
|
||||
categories: [
|
||||
{
|
||||
name: "Applications",
|
||||
links: [
|
||||
{
|
||||
icon_uri: "http://localhost:8080/app1.png",
|
||||
name: "E-Mail",
|
||||
link_url: "https://example.com/email",
|
||||
target: "app1",
|
||||
},
|
||||
{
|
||||
icon_uri: "http://localhost:8080/app2.png",
|
||||
name: "Riot",
|
||||
link_url: "https://riot.im/app",
|
||||
target: "riot-im",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Links",
|
||||
links: [
|
||||
{
|
||||
icon_uri: "http://localhost:8080/app1.png",
|
||||
name: "Link",
|
||||
link_url: "https://example.com/link1",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
logo_url: "http://localhost:8080/opendesk/logomark.svg",
|
||||
logo_link_url: "https://example.com/portal",
|
||||
menu: {
|
||||
type: "univention",
|
||||
logo_url: "http://localhost:8080/opendesk/logofull.svg",
|
||||
ics_url: "http://localhost:8080/ics/",
|
||||
},
|
||||
theme: {
|
||||
triggerBackgroundColorHover: "#571EFA",
|
||||
triggerBackgroundColorPressed: "#4519C2",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const config of configs) {
|
||||
const type = config.menu.type;
|
||||
|
||||
test.describe(`${type} config`, () => {
|
||||
test.use({
|
||||
config: {
|
||||
"io.element.element-web-modules.banner": config,
|
||||
},
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ context, moduleDir, navigationJsonResolver }) => {
|
||||
await context.route("http://localhost:8080/ics/navigation.json*", async (route) => {
|
||||
await navigationJsonResolver.promise;
|
||||
await route.fulfill({
|
||||
path: `${moduleDir}/tests/fixture/navigation.json`,
|
||||
contentType: "application/json",
|
||||
});
|
||||
});
|
||||
await context.route("http://localhost:8080/ics/silent", async (route) => {
|
||||
await route.fulfill({
|
||||
path: `${moduleDir}/tests/fixture/silent/index.html`,
|
||||
contentType: "text/html",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("should render", { tag: ["@screenshot"] }, async ({ page, axe, navigationJsonResolver }) => {
|
||||
await expect(page.getByRole("heading", { name: "Welcome to Element!" })).toBeVisible();
|
||||
await expect(page.getByLabel("Show portal")).toHaveAttribute("href", "https://example.com/portal");
|
||||
|
||||
const nav = page.locator("nav");
|
||||
|
||||
const trigger = page.getByLabel("Show menu");
|
||||
await expect(trigger).toBeVisible();
|
||||
|
||||
// Assert the banner looks as we expect
|
||||
await expect(nav).toMatchAriaSnapshot();
|
||||
await expect(nav).toMatchScreenshot(`${type}_nav.png`);
|
||||
|
||||
// Check hover styles
|
||||
await trigger.hover();
|
||||
await expect(nav).toMatchScreenshot(`${type}_nav_hover.png`);
|
||||
|
||||
await test.step("open menu", async () => {
|
||||
await trigger.click();
|
||||
const sidebar = page.getByRole("dialog");
|
||||
|
||||
if (type === "univention") {
|
||||
await expect(sidebar).toMatchScreenshot(`${type}_menu_loading.png`);
|
||||
navigationJsonResolver.resolve();
|
||||
}
|
||||
await page.pause();
|
||||
|
||||
const emailApp = page.getByText("E-Mail");
|
||||
await expect(emailApp).toHaveAttribute("href", "https://example.com/email");
|
||||
await emailApp.hover();
|
||||
|
||||
// Assert the sidebar looks as we expect
|
||||
await expect(axe).toHaveNoViolations();
|
||||
await expect(sidebar).toMatchAriaSnapshot();
|
||||
await expect(page).toMatchScreenshot(`${type}_menu.png`, {
|
||||
// We exclude this as we don't want to assert Element's styling, only our own
|
||||
css: `
|
||||
#matrixchat {
|
||||
opacity: 0;
|
||||
background: orchid;
|
||||
}
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
||||
await test.step("close menu", async () => {
|
||||
await expect(axe).toHaveNoViolations();
|
||||
|
||||
// Assert it closes by clicking the overlay
|
||||
await page.getByTestId("dialog-overlay").click();
|
||||
await expect(page.getByRole("dialog")).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("univention config", () => {
|
||||
test.use({
|
||||
config: {
|
||||
"io.element.element-web-modules.banner": {
|
||||
logo_url: "http://localhost:8080/opendesk/logomark.svg",
|
||||
logo_link_url: "https://example.com/portal",
|
||||
menu: {
|
||||
type: "univention",
|
||||
logo_url: "http://localhost:8080/opendesk/logofull.svg",
|
||||
ics_url: "http://localhost:8080/ics/",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ context }) => {
|
||||
await context.route("http://localhost:8080/ics/silent", async (route) => {
|
||||
await route.fulfill({ status: 500 });
|
||||
});
|
||||
await context.route("http://localhost:8080/ics/navigation.json*", async (route) => {
|
||||
await route.fulfill({ status: 500 });
|
||||
});
|
||||
});
|
||||
|
||||
test("should render error", { tag: ["@screenshot"] }, async ({ page, axe }) => {
|
||||
await expect(page.getByRole("heading", { name: "Welcome to Element!" })).toBeVisible();
|
||||
await expect(page.getByLabel("Show portal")).toHaveAttribute("href", "https://example.com/portal");
|
||||
|
||||
const trigger = page.getByLabel("Show menu");
|
||||
|
||||
await trigger.click();
|
||||
const sidebar = page.getByRole("dialog");
|
||||
await expect(sidebar.getByText("Failed to load")).toBeVisible();
|
||||
await expect(sidebar).toMatchScreenshot("univention_error.png");
|
||||
await expect(axe).toHaveNoViolations();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px"
|
||||
width="789.322px" height="336.807px" viewBox="0 0 789.322 336.807" enable-background="new 0 0 789.322 336.807"
|
||||
xml:space="preserve">
|
||||
<path d="M8.876,7.71v321.386h23.13v7.711H0V0h32.006v7.71H8.876z"/>
|
||||
<path d="M97.989,109.594v16.264h0.463c4.338-6.191,9.563-10.998,15.684-14.406c6.117-3.402,13.129-5.11,21.027-5.11
|
||||
c7.588,0,14.521,1.475,20.793,4.415c6.274,2.945,11.038,8.131,14.291,15.567c3.56-5.265,8.4-9.913,14.521-13.94
|
||||
c6.117-4.025,13.358-6.042,21.724-6.042c6.351,0,12.234,0.776,17.66,2.325c5.418,1.549,10.065,4.027,13.938,7.434
|
||||
c3.869,3.41,6.889,7.863,9.062,13.357c2.167,5.504,3.253,12.122,3.253,19.869v80.385h-32.993v-68.074
|
||||
c0-4.025-0.154-7.82-0.465-11.385c-0.313-3.56-1.161-6.656-2.555-9.293c-1.395-2.631-3.45-4.724-6.157-6.274
|
||||
c-2.711-1.543-6.391-2.322-11.037-2.322s-8.403,0.896-11.269,2.671c-2.868,1.784-5.112,4.109-6.737,6.971
|
||||
c-1.626,2.869-2.711,6.12-3.252,9.762c-0.545,3.638-0.814,7.318-0.814,11.035v66.91h-32.991v-67.375c0-3.562-0.081-7.087-0.23-10.57
|
||||
c-0.158-3.487-0.814-6.7-1.978-9.645c-1.162-2.94-3.099-5.304-5.809-7.088c-2.711-1.775-6.699-2.671-11.965-2.671
|
||||
c-1.551,0-3.603,0.349-6.156,1.048c-2.556,0.697-5.036,2.016-7.435,3.949c-2.404,1.938-4.454,4.726-6.158,8.363
|
||||
c-1.705,3.642-2.556,8.402-2.556,14.287v69.701h-32.99V109.594H97.989z"/>
|
||||
<path d="M271.545,127.254c3.405-5.113,7.744-9.215,13.012-12.316c5.264-3.097,11.186-5.303,17.771-6.621
|
||||
c6.582-1.315,13.205-1.976,19.865-1.976c6.042,0,12.158,0.428,18.354,1.277c6.195,0.855,11.85,2.522,16.962,4.997
|
||||
c5.111,2.477,9.292,5.926,12.546,10.338c3.253,4.414,4.879,10.262,4.879,17.543v62.494c0,5.428,0.31,10.611,0.931,15.567
|
||||
c0.615,4.959,1.701,8.676,3.251,11.153h-33.455c-0.621-1.86-1.126-3.755-1.511-5.693c-0.39-1.933-0.661-3.908-0.813-5.923
|
||||
c-5.267,5.422-11.465,9.217-18.585,11.386c-7.127,2.163-14.407,3.251-21.842,3.251c-5.733,0-11.077-0.698-16.033-2.09
|
||||
c-4.958-1.395-9.293-3.562-13.01-6.51c-3.718-2.938-6.622-6.656-8.713-11.147s-3.138-9.84-3.138-16.033
|
||||
c0-6.813,1.199-12.43,3.604-16.84c2.399-4.417,5.495-7.939,9.295-10.575c3.793-2.632,8.129-4.607,13.01-5.923
|
||||
c4.878-1.315,9.795-2.358,14.752-3.137c4.957-0.772,9.835-1.393,14.638-1.857c4.801-0.466,9.062-1.164,12.779-2.093
|
||||
c3.718-0.929,6.658-2.282,8.829-4.065c2.165-1.781,3.172-4.375,3.02-7.785c0-3.56-0.58-6.389-1.742-8.479
|
||||
c-1.161-2.09-2.711-3.719-4.646-4.88c-1.937-1.161-4.183-1.936-6.737-2.325c-2.557-0.382-5.309-0.58-8.248-0.58
|
||||
c-6.506,0-11.617,1.395-15.335,4.183c-3.716,2.788-5.889,7.437-6.506,13.94h-32.991C266.2,138.793,268.133,132.362,271.545,127.254z
|
||||
M336.714,173.837c-2.09,0.696-4.337,1.275-6.736,1.741c-2.402,0.465-4.918,0.853-7.551,1.161c-2.635,0.313-5.268,0.698-7.899,1.163
|
||||
c-2.48,0.461-4.919,1.086-7.317,1.857c-2.404,0.779-4.495,1.822-6.274,3.138c-1.784,1.317-3.216,2.985-4.3,4.994
|
||||
c-1.085,2.014-1.626,4.571-1.626,7.668c0,2.94,0.541,5.422,1.626,7.431c1.084,2.017,2.558,3.604,4.416,4.765
|
||||
s4.025,1.976,6.507,2.438c2.475,0.466,5.031,0.698,7.665,0.698c6.505,0,11.537-1.082,15.103-3.253
|
||||
c3.561-2.166,6.192-4.762,7.899-7.785c1.702-3.019,2.749-6.072,3.137-9.174c0.384-3.097,0.58-5.576,0.58-7.434V170.93
|
||||
C340.548,172.172,338.806,173.139,336.714,173.837z"/>
|
||||
<path d="M461.826,109.594v22.072h-24.161v59.479c0,5.573,0.928,9.292,2.788,11.149c1.856,1.859,5.576,2.788,11.152,2.788
|
||||
c1.859,0,3.638-0.076,5.343-0.232c1.703-0.152,3.33-0.388,4.878-0.696v25.557c-2.788,0.465-5.887,0.773-9.293,0.931
|
||||
c-3.407,0.149-6.737,0.23-9.99,0.23c-5.111,0-9.953-0.35-14.521-1.048c-4.571-0.695-8.597-2.047-12.081-4.063
|
||||
c-3.486-2.011-6.236-4.88-8.248-8.597c-2.016-3.714-3.021-8.595-3.021-14.639v-70.859h-19.98v-22.072h19.98V73.582h32.992v36.012
|
||||
H461.826z"/>
|
||||
<path d="M508.989,109.594v22.306h0.465c1.546-3.72,3.636-7.163,6.272-10.341c2.634-3.172,5.652-5.885,9.06-8.131
|
||||
c3.405-2.242,7.047-3.985,10.923-5.228c3.868-1.237,7.898-1.859,12.081-1.859c2.168,0,4.566,0.39,7.202,1.163v30.67
|
||||
c-1.551-0.312-3.41-0.584-5.576-0.814c-2.17-0.233-4.26-0.35-6.274-0.35c-6.041,0-11.152,1.01-15.332,3.021
|
||||
c-4.182,2.014-7.55,4.761-10.107,8.247c-2.555,3.487-4.379,7.55-5.462,12.198c-1.083,4.645-1.625,9.682-1.625,15.102v54.133h-32.991
|
||||
V109.594H508.989z"/>
|
||||
<path d="M568.931,91.006V63.823h32.994v27.183H568.931z M601.925,109.594v120.117h-32.994V109.594H601.925z"/>
|
||||
<path d="M619.116,109.594h37.637l21.144,31.365l20.911-31.365h36.476l-39.496,56.226l44.377,63.892h-37.64l-25.093-37.87
|
||||
l-25.094,37.87H615.4l43.213-63.193L619.116,109.594z"/>
|
||||
<path d="M780.444,329.096V7.71h-23.13V0h32.008v336.807h-32.008v-7.711H780.444z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"identifier": "applications",
|
||||
"display_name": "Anwendungen",
|
||||
"entries": [
|
||||
{
|
||||
"identifier": "app1",
|
||||
"display_name": "Portal",
|
||||
"icon_url": "http://localhost:8080/opendesk/app1.svg",
|
||||
"link": "https://example.com/app1",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"identifier": "app2",
|
||||
"display_name": "Aufgaben",
|
||||
"icon_url": "http://localhost:8080/opendesk/app2.svg",
|
||||
"link": "https://example.com/app2",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"identifier": "app3",
|
||||
"display_name": "Chat",
|
||||
"icon_url": "http://localhost:8080/opendesk/app3.svg",
|
||||
"link": "https://example.com/app3",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"identifier": "app4",
|
||||
"display_name": "Dateien",
|
||||
"icon_url": "http://localhost:8080/opendesk/app4.svg",
|
||||
"link": "https://example.com/ap4",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"identifier": "app5",
|
||||
"display_name": "E-Mail",
|
||||
"icon_url": "http://localhost:8080/opendesk/app5.svg",
|
||||
"link": "https://example.com/email",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"identifier": "app6",
|
||||
"display_name": "Kelender",
|
||||
"icon_url": "http://localhost:8080/opendesk/app6.svg",
|
||||
"link": "https://example.com/app6",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"identifier": "app7",
|
||||
"display_name": "Kontakte",
|
||||
"icon_url": "http://localhost:8080/opendesk/app7.svg",
|
||||
"link": "https://example.com/app7",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"identifier": "app8",
|
||||
"display_name": "Notizen",
|
||||
"icon_url": "http://localhost:8080/opendesk/app8.svg",
|
||||
"link": "https://example.com/app8",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"identifier": "app9",
|
||||
"display_name": "Projekte",
|
||||
"icon_url": "http://localhost:8080/opendesk/app9.svg",
|
||||
"link": "https://example.com/app9",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"identifier": "app10",
|
||||
"display_name": "Videokonferenz",
|
||||
"icon_url": "http://localhost:8080/opendesk/app10.svg",
|
||||
"link": "https://example.com/app10",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"identifier": "app11",
|
||||
"display_name": "Wiki",
|
||||
"icon_url": "http://localhost:8080/opendesk/app11.svg",
|
||||
"link": "https://example.com/app11",
|
||||
"target": "_blank"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.0001 5.96045L6.48535 10.9613V18.4604H10.1625V13.7727H13.8397V18.4604H17.5148V10.9613L12.0001 5.96045Z" fill="#571EFA"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 236 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.8718 7.93982H7.49831C6.61769 7.93982 5.90381 8.6537 5.90381 9.53432V15.9078C5.90381 16.7884 6.61769 17.5023 7.49831 17.5023H13.8718C14.7524 17.5023 15.4663 16.7884 15.4663 15.9078V9.53432C15.4663 8.6537 14.7524 7.93982 13.8718 7.93982Z" fill="#571EFA"/>
|
||||
<path d="M20.5666 16.2273C18.6303 16.2273 17.0605 14.6575 17.0605 12.7212C17.0605 10.7848 18.6303 9.21509 20.5666 9.21509V16.2273Z" fill="#341291"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 519 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.8906 16.7064C14.8906 18.0265 13.8201 19.097 12.5 19.097C11.1799 19.097 10.1094 18.0265 10.1094 16.7064V15.9103H14.8906V16.7064Z" fill="#341291"/>
|
||||
<path d="M12.5 6.3457C9.85974 6.3457 7.71875 8.4867 7.71875 11.127C7.71875 12.3526 8.18428 13.4681 8.94442 14.3137H16.0556C16.8157 13.4681 17.2812 12.3526 17.2812 11.127C17.2812 8.4867 15.1403 6.3457 12.5 6.3457Z" fill="#571EFA"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 493 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.60352 12.0848C7.92382 12.0848 8.99414 11.0145 8.99414 9.69421C8.99414 8.37391 7.92382 7.30359 6.60352 7.30359C5.28321 7.30359 4.21289 8.37391 4.21289 9.69421C4.21289 11.0145 5.28321 12.0848 6.60352 12.0848Z" fill="#571EFA"/>
|
||||
<path d="M20.1509 8.89624H10.5884V10.4907H20.1509V8.89624Z" fill="#341291"/>
|
||||
<path d="M6.60352 18.1395C7.92382 18.1395 8.99414 17.0692 8.99414 15.7489C8.99414 14.4286 7.92382 13.3583 6.60352 13.3583C5.28321 13.3583 4.21289 14.4286 4.21289 15.7489C4.21289 17.0692 5.28321 18.1395 6.60352 18.1395Z" fill="#571EFA"/>
|
||||
<path d="M20.1509 14.9509H10.5884V16.5454H20.1509V14.9509Z" fill="#341291"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 731 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.1368 11.1263C14.0161 11.1263 14.7313 10.4134 14.7313 9.53177V7.93727C14.7313 7.05794 14.0183 6.34277 13.1368 6.34277H6.761C5.88167 6.34277 5.1665 7.05569 5.1665 7.93727V14.313L8.35325 11.1263H13.1345H13.1368Z" fill="#341291"/>
|
||||
<path d="M18.2373 12.7206H11.8616C10.9823 12.7206 10.2671 13.4335 10.2671 14.3151V15.9096C10.2671 16.7889 10.98 17.5041 11.8616 17.5041H16.6428L19.8296 20.6908V14.3151C19.8296 13.4357 19.1167 12.7206 18.2351 12.7206H18.2373Z" fill="#571EFA"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 586 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.06592 10.2686H17.2224C18.1017 10.2686 18.8168 10.9837 18.8168 11.863V16.644C18.8168 17.5233 18.1017 18.2384 17.2224 18.2384H7.66034C6.78105 18.2384 6.06592 17.5233 6.06592 16.644V10.2686Z" fill="#571EFA"/>
|
||||
<path d="M11.4834 8.67372H6.06592V7.71796C6.06592 6.83642 6.7788 6.12354 7.66034 6.12354H10.2105L11.4856 8.67372H11.4834Z" fill="#341291"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 461 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5388 12.7107L18.9609 7.89411C18.6516 7.61951 18.2459 7.45129 17.7982 7.45129H7.2794C6.83411 7.45129 6.42593 7.61951 6.1167 7.89411L12.5388 12.7107Z" fill="#571EFA"/>
|
||||
<path d="M14.0008 13.8069L12.5387 14.9028L11.0767 13.8069L5.52539 9.64343V16.2164C5.52539 17.1837 6.3096 17.9704 7.27934 17.9704H17.7981C18.7654 17.9704 19.552 17.1862 19.552 16.2164V9.64343L14.0008 13.8069Z" fill="#341291"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 508 B |
@@ -0,0 +1,7 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.67479 13.6771H4.21289V18.4584H8.67479V13.6771Z" fill="#571EFA"/>
|
||||
<path d="M14.7309 13.6771H10.269V18.4584H14.7309V13.6771Z" fill="#571EFA"/>
|
||||
<path d="M8.67479 7.30359H4.21289V12.0848H8.67479V7.30359Z" fill="#341291"/>
|
||||
<path d="M14.7309 7.30359H10.269V12.0848H14.7309V7.30359Z" fill="#571EFA"/>
|
||||
<path d="M20.7876 7.30359H16.3257V12.0848H20.7876V7.30359Z" fill="#571EFA"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 486 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.03809 12.4022C9.35839 12.4022 10.4287 11.3319 10.4287 10.0116C10.4287 8.69129 9.35839 7.62097 8.03809 7.62097C6.71778 7.62097 5.64746 8.69129 5.64746 10.0116C5.64746 11.3319 6.71778 12.4022 8.03809 12.4022Z" fill="#571EFA"/>
|
||||
<path d="M16.9619 12.4022C18.2822 12.4022 19.3525 11.3319 19.3525 10.0116C19.3525 8.69129 18.2822 7.62097 16.9619 7.62097C15.6416 7.62097 14.5713 8.69129 14.5713 10.0116C14.5713 11.3319 15.6416 12.4022 16.9619 12.4022Z" fill="#341291"/>
|
||||
<path d="M4.21289 17.8214C4.21289 15.7097 5.92434 13.996 8.03834 13.996C10.1523 13.996 11.8638 15.7074 11.8638 17.8214H4.21289Z" fill="#571EFA"/>
|
||||
<path d="M13.1367 17.8214C13.1367 15.7097 14.8482 13.996 16.9622 13.996C19.0762 13.996 20.7876 15.7074 20.7876 17.8214H13.1367Z" fill="#341291"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 869 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.2369 17.5029H6.79199V19.0974H18.2369V17.5029Z" fill="#341291"/>
|
||||
<path d="M14.7599 6.78423L15.8867 7.91095C16.5096 8.53391 16.5096 9.54144 15.8867 10.1644L14.6902 11.3608L10.1429 15.9082H6.7627V12.528L11.3101 7.98067L12.5065 6.78423C13.1294 6.16128 14.137 6.16128 14.7599 6.78423Z" fill="#571EFA"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 414 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.12451 19.095L6.12451 6.3457H4.53001L4.53001 19.095H6.12451Z" fill="#571EFA"/>
|
||||
<path d="M18.9089 13.5161H12.5354C11.6548 13.5161 10.9409 14.23 10.9409 15.1106V16.7029C10.9409 17.5835 11.6548 18.2974 12.5354 18.2974H18.9089C19.7895 18.2974 20.5034 17.5835 20.5034 16.7029V15.1106C20.5034 14.23 19.7895 13.5161 18.9089 13.5161Z" fill="#571EFA"/>
|
||||
<path d="M14.1277 7.1427H9.31276C8.43214 7.1427 7.71826 7.85658 7.71826 8.7372V10.3295C7.71826 11.2101 8.43214 11.924 9.31276 11.924H14.1277C15.0084 11.924 15.7222 11.2101 15.7222 10.3295V8.7372C15.7222 7.85658 15.0084 7.1427 14.1277 7.1427Z" fill="#341291"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 718 B |
@@ -0,0 +1,13 @@
|
||||
<svg width="113" height="31" viewBox="0 0 113 31" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="101" height="31" fill="white"/>
|
||||
<path d="M20.7634 21.3042V21.6881C20.7634 23.3086 19.4467 24.6263 17.8274 24.6263H10.936C9.31669 24.6263 8 23.3086 8 21.6881V14.7914C8 13.1708 9.31669 11.8531 10.936 11.8531H11.3196V21.306H20.7652L20.7634 21.3042Z" fill="#927AFA"/>
|
||||
<path d="M20.8913 19.0041H13.6162V10.9555C13.6162 8.20836 15.8497 5.97314 18.5948 5.97314H20.8931C24.0597 5.97314 26.6372 8.55266 26.6372 11.7216V13.2538C26.6372 16.4228 24.0597 19.0023 20.8931 19.0023L20.8913 19.0041ZM16.934 15.6819H20.8913C22.2278 15.6819 23.3157 14.5932 23.3157 13.2556V11.7234C23.3157 10.3859 22.2278 9.29713 20.8913 9.29713H18.593C17.6779 9.29713 16.934 10.0416 16.934 10.9573V15.6837V15.6819Z" fill="#571EFA"/>
|
||||
<path d="M36.0164 12.3705C38.6912 12.3705 40.4006 14.3353 40.4006 16.7418C40.4006 19.1482 38.6912 21.1131 36.0164 21.1131C33.3416 21.1131 31.6143 19.1482 31.6143 16.7418C31.6143 14.3353 33.3236 12.3705 36.0164 12.3705ZM36.0164 20.0459C38.0644 20.0459 39.1487 18.5209 39.1487 16.7418C39.1487 14.9626 38.0644 13.4376 36.0164 13.4376C33.9684 13.4376 32.8679 14.9626 32.8679 16.7418C32.8679 18.5209 33.9504 20.0459 36.0164 20.0459Z" fill="black"/>
|
||||
<path d="M42.0273 12.5399H43.281V14.6742C43.8736 13.2682 45.1272 12.3705 46.9555 12.3705C49.3096 12.3705 50.9001 14.1496 50.9001 16.758C50.9001 19.3664 49.3096 21.1131 46.9555 21.1131C45.093 21.1131 43.8574 20.1811 43.281 18.8094V24.0279H42.0273V12.5399ZM46.5646 20.0459C48.2577 20.0459 49.6465 18.995 49.6465 16.758C49.6465 14.521 48.2577 13.4376 46.5646 13.4376C44.8715 13.4376 43.3314 14.539 43.3314 16.758C43.3314 18.977 44.8895 20.0459 46.5646 20.0459Z" fill="black"/>
|
||||
<path d="M56.2153 12.3706C58.6199 12.3706 60.2284 13.8793 60.2284 16.4535V16.9618H53.4739C53.5243 18.7247 54.4213 20.0785 56.2838 20.0785C57.8238 20.0785 58.7226 19.2655 59.027 18.0109H60.2626C59.9078 19.5016 58.8919 21.1114 56.318 21.1114C53.5927 21.1114 52.2725 19.078 52.2725 16.6211C52.2725 13.91 53.8467 12.3688 56.2171 12.3688L56.2153 12.3706ZM58.991 15.9794C58.8721 14.3354 57.7716 13.4035 56.1973 13.4035C54.7239 13.4035 53.6234 14.3516 53.4883 15.9794H58.991Z" fill="black"/>
|
||||
<path d="M61.9883 12.5399H63.2419V14.8779C63.6994 13.3186 64.8846 12.3705 66.7291 12.3705C68.5735 12.3705 69.7425 13.5908 69.7425 15.7936V20.9436H68.4727V15.8946C68.4727 14.1496 67.7954 13.4376 66.3058 13.4376C64.4091 13.4376 63.2419 15.0311 63.2419 17.2844V20.9436H61.9883V12.5399Z" fill="black"/>
|
||||
<path d="M71.8589 8.9978H76.2448C77.4859 8.9978 78.581 9.15102 79.5303 9.45566C80.4777 9.7603 81.254 10.3534 81.8574 11.2348C82.4608 12.1163 82.7634 13.3583 82.7634 14.9626C82.7634 16.5669 82.4608 17.8269 81.8574 18.7066C81.254 19.588 80.4777 20.1811 79.5303 20.4857C78.581 20.7904 77.4877 20.9436 76.2448 20.9436H71.8589V8.9978ZM76.2448 18.5552C77.1022 18.5552 77.7885 18.4867 78.3018 18.3515C78.8152 18.2163 79.2331 17.8918 79.5555 17.3763C79.8779 16.8625 80.0382 16.0568 80.0382 14.9626C80.0382 13.8684 79.8743 13.0771 79.5483 12.5561C79.2204 12.037 78.8026 11.7089 78.2946 11.5737C77.7867 11.4385 77.104 11.37 76.2466 11.37H74.5697V18.5552H76.2466H76.2448Z" fill="black"/>
|
||||
<path d="M84.5322 14.2759C84.9105 13.6143 85.4346 13.1096 86.1065 12.7599C86.7783 12.4102 87.5493 12.2354 88.4174 12.2354C89.8404 12.2354 90.9518 12.6139 91.7533 13.371C92.5548 14.1281 92.9547 15.1736 92.9547 16.5057V17.2177H86.555C86.6 17.827 86.7928 18.3047 87.1314 18.649C87.47 18.9933 87.9329 19.1663 88.5201 19.1663C89.0281 19.1663 89.4477 19.06 89.781 18.8437C90.1142 18.6292 90.3249 18.3245 90.4168 17.9279H92.9727C92.8376 18.8761 92.3819 19.6458 91.6092 20.2317C90.8365 20.8193 89.8116 21.1131 88.5363 21.1131C87.5997 21.1131 86.7873 20.9239 86.0975 20.5453C85.4094 20.1668 84.8817 19.6386 84.5142 18.9608C84.1468 18.2831 83.9648 17.5043 83.9648 16.6229C83.9648 15.7414 84.154 14.9374 84.5322 14.2759ZM90.4492 15.8261C90.4042 15.2835 90.2042 14.8653 89.8476 14.5715C89.4928 14.2777 89.0317 14.1317 88.4679 14.1317C87.9041 14.1317 87.4664 14.2795 87.1224 14.5715C86.7783 14.8653 86.5838 15.2835 86.5388 15.8261H90.4492Z" fill="black"/>
|
||||
<path d="M95.2406 20.2821C94.4949 19.7287 94.1221 18.9157 94.1221 17.8414H96.696C96.696 18.3281 96.8491 18.6868 97.1535 18.9175C97.4579 19.15 97.9262 19.2654 98.5585 19.2654C99.0214 19.2654 99.3564 19.2059 99.5653 19.087C99.7743 18.968 99.8788 18.7787 99.8788 18.5191C99.8788 18.3389 99.8175 18.1911 99.6932 18.0793C99.569 17.9657 99.3708 17.8702 99.1006 17.7909L96.4925 17.0789C95.9161 16.9329 95.4207 16.6697 95.0029 16.2911C94.585 15.9126 94.376 15.3898 94.376 14.7229C94.376 13.9207 94.7057 13.306 95.3667 12.8752C96.0278 12.4462 96.95 12.2317 98.1352 12.2317C99.4447 12.2317 100.466 12.4949 101.199 13.0194C101.932 13.544 102.3 14.3047 102.3 15.2979H99.7257C99.7257 14.4741 99.2015 14.0613 98.1514 14.0613C97.7785 14.0613 97.4849 14.1244 97.2706 14.247C97.0563 14.3714 96.9482 14.5354 96.9482 14.7391C96.9482 15.0888 97.258 15.343 97.8794 15.5016L99.877 15.9937C100.655 16.1866 101.28 16.4822 101.748 16.8842C102.217 17.2844 102.451 17.8468 102.451 18.5696C102.451 19.3952 102.121 20.0243 101.46 20.4587C100.799 20.8932 99.8211 21.1113 98.5224 21.1113C97.0779 21.1113 95.9827 20.8337 95.237 20.2803L95.2406 20.2821Z" fill="black"/>
|
||||
<path d="M103.875 8.4895H106.449V15.7071L109.852 12.4029H113L108.751 16.5543L112.984 20.9436H109.852L106.449 17.3511V20.9436H103.875V8.4895Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="35" height="36" viewBox="0 0 35 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.6929 25.8326V26.3232C21.6929 28.394 20.0294 30.0778 17.9837 30.0778H9.27732C7.23157 30.0778 5.56812 28.394 5.56812 26.3232V17.5102C5.56812 15.4394 7.23157 13.7556 9.27732 13.7556H9.76201V25.8349H21.6952L21.6929 25.8326Z" fill="#AA98FB"/>
|
||||
<path d="M21.8544 22.8933H12.6633V12.6084C12.6633 9.09797 15.4851 6.2417 18.953 6.2417H21.8567C25.8572 6.2417 29.1135 9.53793 29.1135 13.5874V15.5453C29.1135 19.5948 25.8572 22.891 21.8567 22.891L21.8544 22.8933ZM16.855 18.6481H21.8544C23.5429 18.6481 24.9173 17.2568 24.9173 15.5476V13.5897C24.9173 11.8805 23.5429 10.4893 21.8544 10.4893H18.9508C17.7948 10.4893 16.855 11.4406 16.855 12.6107V18.6504V18.6481Z" fill="#571EFA"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 783 B |
@@ -0,0 +1,9 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Univention Silent Login</title>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
- navigation:
|
||||
- button "Show menu":
|
||||
- img
|
||||
- link "Show portal":
|
||||
- /url: https://example.com/portal
|
||||
- img "Portal logo"
|
||||
@@ -0,0 +1,13 @@
|
||||
- dialog "Portal logo Close menu":
|
||||
- heading "Portal logo Close menu" [level=2]:
|
||||
- img "Portal logo"
|
||||
- button "Close menu":
|
||||
- img
|
||||
- heading "Applications" [level=2]
|
||||
- link "E-Mail":
|
||||
- /url: https://example.com/email
|
||||
- link "Riot":
|
||||
- /url: https://riot.im/app
|
||||
- heading "Links" [level=2]
|
||||
- link "Link":
|
||||
- /url: https://example.com/link1
|
||||
@@ -0,0 +1,6 @@
|
||||
- navigation:
|
||||
- button "Show menu":
|
||||
- img
|
||||
- link "Show portal":
|
||||
- /url: https://example.com/portal
|
||||
- img "Portal logo"
|
||||
@@ -0,0 +1,28 @@
|
||||
- dialog "Portal logo Close menu":
|
||||
- heading "Portal logo Close menu" [level=2]:
|
||||
- img "Portal logo"
|
||||
- button "Close menu":
|
||||
- img
|
||||
- heading "Anwendungen" [level=2]
|
||||
- link "Portal":
|
||||
- /url: https://example.com/app1
|
||||
- link "Aufgaben":
|
||||
- /url: https://example.com/app2
|
||||
- link "Chat":
|
||||
- /url: https://example.com/app3
|
||||
- link "Dateien":
|
||||
- /url: https://example.com/ap4
|
||||
- link "E-Mail":
|
||||
- /url: https://example.com/email
|
||||
- link "Kelender":
|
||||
- /url: https://example.com/app6
|
||||
- link "Kontakte":
|
||||
- /url: https://example.com/app7
|
||||
- link "Notizen":
|
||||
- /url: https://example.com/app8
|
||||
- link "Projekte":
|
||||
- /url: https://example.com/app9
|
||||
- link "Videokonferenz":
|
||||
- /url: https://example.com/app10
|
||||
- link "Wiki":
|
||||
- /url: https://example.com/app11
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { nodePolyfills } from "vite-plugin-node-polyfills";
|
||||
import externalGlobals from "rollup-plugin-external-globals";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, "src/index.tsx"),
|
||||
name: "element-web-module-banner",
|
||||
fileName: "index",
|
||||
formats: ["es"],
|
||||
},
|
||||
outDir: "lib",
|
||||
target: "esnext",
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
external: ["react"],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
svgr(),
|
||||
nodePolyfills({
|
||||
include: ["events"],
|
||||
}),
|
||||
externalGlobals({
|
||||
// Reuse React from the host app
|
||||
react: "window.React",
|
||||
}),
|
||||
],
|
||||
define: {
|
||||
// Use production mode for the build as it is tested against production builds of Element Web,
|
||||
// this is required for React JSX versions to be compatible.
|
||||
process: { env: { NODE_ENV: "production" } },
|
||||
},
|
||||
});
|
||||