diff --git a/changelog.d/652.misc b/changelog.d/652.misc new file mode 100644 index 00000000..3170fc53 --- /dev/null +++ b/changelog.d/652.misc @@ -0,0 +1 @@ +Minor improvements to widget UI styles. diff --git a/web/App.tsx b/web/App.tsx index 5dac0989..5a4d6a11 100644 --- a/web/App.tsx +++ b/web/App.tsx @@ -1,9 +1,10 @@ /* eslint-disable no-console */ import { Component } from 'preact'; import WA, { MatrixCapabilities } from 'matrix-widget-api'; -import { BridgeAPI, BridgeAPIError } from './BridgeAPI'; +import { BridgeAPI, BridgeAPIError, EmbedType, embedTypeParameter } from './BridgeAPI'; import { BridgeRoomState } from '../src/Widgets/BridgeWidgetInterface'; -import { ErrorPane } from './components/elements'; +import { LoadingSpinner } from './components/elements/LoadingSpinner'; +import { Card, ErrorPane } from './components/elements'; import AdminSettings from './components/AdminSettings'; import RoomConfigView from './components/RoomConfigView'; @@ -19,6 +20,7 @@ interface ICompleteState extends IMinimalState { [sectionName: string]: boolean; }, serviceScope?: string, + embedType: EmbedType, kind: "invite"|"admin"|"roomConfig", } @@ -55,6 +57,7 @@ export default class App extends Component { const roomId = assertParam(qs, 'roomId'); const widgetKind = qs.get('kind') as "invite"|"admin"|"roomConfig"; const serviceScope = qs.get('serviceScope'); + const embedType = qs.get(embedTypeParameter); // Fetch via config. this.widgetApi = new WA.WidgetApi(widgetId); this.widgetApi.requestCapability(MatrixCapabilities.RequiresClient); @@ -84,6 +87,7 @@ export default class App extends Component { roomId, supportedServices, serviceScope: serviceScope || undefined, + embedType: embedType === EmbedType.IntegrationManager ? EmbedType.IntegrationManager : EmbedType.Default, kind: widgetKind, busy: false, }); @@ -110,7 +114,9 @@ export default class App extends Component { if (this.state.error) { content = {this.state.error}; } else if (this.state.busy) { - content =
; + content = + + ; } if ("kind" in this.state) { @@ -123,6 +129,7 @@ export default class App extends Component { roomId={this.state.roomId} supportedServices={this.state.supportedServices} serviceScope={this.state.serviceScope} + embedType={this.state.embedType} bridgeApi={this.bridgeApi} widgetApi={this.widgetApi} />; @@ -135,7 +142,9 @@ export default class App extends Component { } return ( -
+
{content}
); diff --git a/web/BridgeAPI.ts b/web/BridgeAPI.ts index 90cd89f2..10ea73ca 100644 --- a/web/BridgeAPI.ts +++ b/web/BridgeAPI.ts @@ -113,7 +113,7 @@ export class BridgeAPI { async getServiceConfig(service: string): Promise { return this.request('GET', `/widgetapi/v1/service/${service}/config`); } - + async getConnectionsForRoom(roomId: string): Promise { return this.request('GET', `/widgetapi/v1/${encodeURIComponent(roomId)}/connections`); } @@ -140,7 +140,14 @@ export class BridgeAPI { } } +export const embedTypeParameter = 'io_element_embed_type'; +export enum EmbedType { + IntegrationManager = 'integration-manager', + Default = 'default', +} + export type BridgeConfig = FunctionComponent<{ api: BridgeAPI, roomId: string, -}>; \ No newline at end of file + showHeader: boolean, +}>; diff --git a/web/components/AdminSettings.tsx b/web/components/AdminSettings.tsx index 122f5b75..c9a7d99e 100644 --- a/web/components/AdminSettings.tsx +++ b/web/components/AdminSettings.tsx @@ -1,4 +1,5 @@ import { useEffect, useState, useCallback } from 'preact/hooks'; +import { LoadingSpinner } from "./elements/LoadingSpinner"; import { BridgeRoomState } from "../../src/Widgets/BridgeWidgetInterface"; import GeneralConfig from './configs/GeneralConfig'; import style from "./AdminSettings.module.scss"; @@ -36,7 +37,7 @@ export default function AdminSettings(props: IProps) { ); if (busy) { return
-
+
; } return
@@ -57,4 +58,4 @@ export default function AdminSettings(props: IProps) {
; -} \ No newline at end of file +} diff --git a/web/components/RoomConfigView.tsx b/web/components/RoomConfigView.tsx index 116e6e51..cfe587b4 100644 --- a/web/components/RoomConfigView.tsx +++ b/web/components/RoomConfigView.tsx @@ -1,6 +1,6 @@ import { WidgetApi } from "matrix-widget-api"; import { useState } from "preact/hooks" -import { BridgeAPI, BridgeConfig } from "../BridgeAPI"; +import { BridgeAPI, BridgeConfig, EmbedType } from "../BridgeAPI"; import style from "./RoomConfigView.module.scss"; import { ConnectionCard } from "./ConnectionCard"; import { FeedsConfig } from "./roomConfig/FeedsConfig"; @@ -21,6 +21,7 @@ interface IProps { bridgeApi: BridgeAPI, supportedServices: {[service: string]: boolean}, serviceScope?: string, + embedType: EmbedType, roomId: string, } @@ -80,7 +81,11 @@ export default function RoomConfigView(props: IProps) { if (activeConnectionType) { const ConfigComponent = connections[activeConnectionType].component; - content = ; + content = ; } else { content = <>
@@ -100,13 +105,13 @@ export default function RoomConfigView(props: IProps) { } return
-
- {!serviceScope && activeConnectionType && + {!serviceScope && activeConnectionType && +
setActiveConnectionType(null)}> Browse integrations - } -
+
+ } {content}
; } diff --git a/web/components/elements/Card.module.scss b/web/components/elements/Card.module.scss new file mode 100644 index 00000000..e93973ed --- /dev/null +++ b/web/components/elements/Card.module.scss @@ -0,0 +1,11 @@ +.card { + /* Compound/Light/Background */ + background: #FFFFFF; + + /* Compound/Light/Quinary Content */ + border: 1px solid #E3E8F0; + box-sizing: border-box; + border-radius: 8px; + + padding: 32px; +} diff --git a/web/components/elements/Card.tsx b/web/components/elements/Card.tsx new file mode 100644 index 00000000..c964f15d --- /dev/null +++ b/web/components/elements/Card.tsx @@ -0,0 +1,11 @@ +import React from 'preact'; + +import styles from './Card.module.scss'; + +const Card = (props: React.ComponentProps<'div'>) => +
; + +export { Card }; diff --git a/web/components/elements/LoadingSpinner.module.scss b/web/components/elements/LoadingSpinner.module.scss new file mode 100644 index 00000000..fa7351c2 --- /dev/null +++ b/web/components/elements/LoadingSpinner.module.scss @@ -0,0 +1,18 @@ +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.spinner { + width: 16px; + height: 16px; + + animation-name: spin; + animation-duration: 1000ms; + animation-timing-function: steps(8, end); + animation-iteration-count: infinite; +} diff --git a/web/components/elements/LoadingSpinner.tsx b/web/components/elements/LoadingSpinner.tsx new file mode 100644 index 00000000..c4a3ac34 --- /dev/null +++ b/web/components/elements/LoadingSpinner.tsx @@ -0,0 +1,71 @@ +import React from 'preact'; + +import styles from './LoadingSpinner.module.scss'; + +export const LoadingSpinner = (props: React.ComponentProps<'svg'>) => ( +
+ + + + + + + + + + +
+); diff --git a/web/components/elements/index.ts b/web/components/elements/index.ts index 11f01d71..afdc2208 100644 --- a/web/components/elements/index.ts +++ b/web/components/elements/index.ts @@ -1,6 +1,7 @@ export * from "./Button"; export * from "./ButtonSet"; +export * from "./Card"; export * from "./ErrorPane"; export * from "./InputField"; export * from "./ListItem"; -export * from "./WarningPane"; \ No newline at end of file +export * from "./WarningPane"; diff --git a/web/components/roomConfig/FeedsConfig.tsx b/web/components/roomConfig/FeedsConfig.tsx index b2feaee2..5efd1249 100644 --- a/web/components/roomConfig/FeedsConfig.tsx +++ b/web/components/roomConfig/FeedsConfig.tsx @@ -17,7 +17,7 @@ const FeedRecentResults: FunctionComponent<{item: FeedResponseItem}> = ({ item } {!item.secrets.lastResults.length && There have been no recent updates for this feed.}
    {item.secrets.lastResults.map(item =>
  • - {new Date(item.timestamp).toLocaleString()}: + {new Date(item.timestamp).toLocaleString()}: {item.ok && `✅ Successful fetch`} {!item.ok && `⚠️ ${item.error}`}
  • )} @@ -53,7 +53,7 @@ const ConnectionConfiguration: FunctionComponent - + { canEdit && } { canEdit && existingConnection && } @@ -75,9 +75,10 @@ const RoomConfigText = { const RoomConfigListItemFunc = (c: FeedResponseItem) => c.config.label || c.config.url; -export const FeedsConfig: BridgeConfig = ({ api, roomId }) => { +export const FeedsConfig: BridgeConfig = ({ api, roomId, showHeader }) => { return headerImg={FeedsIcon} + showHeader={showHeader} api={api} roomId={roomId} type="feeds" diff --git a/web/components/roomConfig/GenericWebhookConfig.tsx b/web/components/roomConfig/GenericWebhookConfig.tsx index 4c330f47..a91cfce8 100644 --- a/web/components/roomConfig/GenericWebhookConfig.tsx +++ b/web/components/roomConfig/GenericWebhookConfig.tsx @@ -2,7 +2,7 @@ import { FunctionComponent, createRef } from "preact"; import { useCallback, useState } from "preact/hooks" import CodeMirror from '@uiw/react-codemirror'; import { javascript } from '@codemirror/lang-javascript'; -import { BridgeAPI } from "../../BridgeAPI"; +import { BridgeConfig } from "../../BridgeAPI"; import { GenericHookConnectionState, GenericHookResponseItem } from "../../../src/Connections/GenericHook"; import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig"; import { InputField, ButtonSet, Button } from "../elements"; @@ -73,11 +73,6 @@ const ConnectionConfiguration: FunctionComponent; }; -interface IGenericWebhookConfigProps { - api: BridgeAPI, - roomId: string, -} - interface ServiceConfig { allowJsTransformationFunctions: boolean } @@ -91,9 +86,10 @@ const RoomConfigText = { const RoomConfigListItemFunc = (c: GenericHookResponseItem) => c.config.name; -export const GenericWebhookConfig: FunctionComponent = ({ api, roomId }) => { +export const GenericWebhookConfig: BridgeConfig = ({ api, roomId, showHeader }) => { return headerImg={WebhookIcon} + showHeader={showHeader} api={api} roomId={roomId} type="generic" @@ -102,4 +98,4 @@ export const GenericWebhookConfig: FunctionComponent listItemName={RoomConfigListItemFunc} connectionConfigComponent={ConnectionConfiguration} />; -}; \ No newline at end of file +}; diff --git a/web/components/roomConfig/GithubRepoConfig.tsx b/web/components/roomConfig/GithubRepoConfig.tsx index 536c6c26..6f446960 100644 --- a/web/components/roomConfig/GithubRepoConfig.tsx +++ b/web/components/roomConfig/GithubRepoConfig.tsx @@ -72,7 +72,7 @@ const ConnectionConfiguration: FunctionComponent setConnectionState(null), [setConnectionState]); - + return
    {!existingConnection && getRepoFullName(c.config); -export const GithubRepoConfig: BridgeConfig = ({ api, roomId }) => { +export const GithubRepoConfig: BridgeConfig = ({ api, roomId, showHeader }) => { return headerImg={GitHubIcon} + showHeader={showHeader} api={api} roomId={roomId} type="github" @@ -146,4 +147,4 @@ export const GithubRepoConfig: BridgeConfig = ({ api, roomId }) => { connectionEventType={EventType} connectionConfigComponent={ConnectionConfiguration} />; -}; \ No newline at end of file +}; diff --git a/web/components/roomConfig/GitlabRepoConfig.tsx b/web/components/roomConfig/GitlabRepoConfig.tsx index c8221b45..e2876e01 100644 --- a/web/components/roomConfig/GitlabRepoConfig.tsx +++ b/web/components/roomConfig/GitlabRepoConfig.tsx @@ -68,7 +68,7 @@ const ConnectionConfiguration: FunctionComponent setNewConnectionState(null), [setNewConnectionState]); - + return {!existingConnection && c.config.path; -export const GitlabRepoConfig: BridgeConfig = ({ api, roomId }) => { +export const GitlabRepoConfig: BridgeConfig = ({ api, roomId, showHeader }) => { return headerImg={GitLabIcon} + showHeader={showHeader} api={api} roomId={roomId} type="gitlab" @@ -133,4 +134,4 @@ export const GitlabRepoConfig: BridgeConfig = ({ api, roomId }) => { connectionEventType={EventType} connectionConfigComponent={ConnectionConfiguration} />; -}; \ No newline at end of file +}; diff --git a/web/components/roomConfig/JiraProjectConfig.tsx b/web/components/roomConfig/JiraProjectConfig.tsx index f6d3fd78..ed048c6c 100644 --- a/web/components/roomConfig/JiraProjectConfig.tsx +++ b/web/components/roomConfig/JiraProjectConfig.tsx @@ -64,7 +64,7 @@ const ConnectionConfiguration: FunctionComponent setNewConnectionState(null), [setNewConnectionState]); - + return {!existingConnection && c.config.url; -export const JiraProjectConfig: BridgeConfig = ({ api, roomId }) => { +export const JiraProjectConfig: BridgeConfig = ({ api, roomId, showHeader }) => { return headerImg={JiraIcon} + showHeader={showHeader} api={api} roomId={roomId} type="jira" @@ -119,4 +120,4 @@ export const JiraProjectConfig: BridgeConfig = ({ api, roomId }) => { connectionEventType={EventType} connectionConfigComponent={ConnectionConfiguration} />; -}; \ No newline at end of file +}; diff --git a/web/components/roomConfig/RoomConfig.tsx b/web/components/roomConfig/RoomConfig.tsx index 72d3228d..b6bcf086 100644 --- a/web/components/roomConfig/RoomConfig.tsx +++ b/web/components/roomConfig/RoomConfig.tsx @@ -1,10 +1,11 @@ import { FunctionComponent } from "preact"; import { useCallback, useEffect, useReducer, useState } from "preact/hooks" import { BridgeAPI, BridgeAPIError } from "../../BridgeAPI"; -import { ErrorPane, ListItem, WarningPane } from "../elements"; +import { ErrorPane, ListItem, WarningPane, Card } from "../elements"; import style from "./RoomConfig.module.scss"; import { GetConnectionsResponseItem } from "../../../src/provisioning/api"; import { IConnectionState } from "../../../src/Connections"; +import { LoadingSpinner } from '../elements/LoadingSpinner'; export interface ConnectionConfigurationProps { @@ -19,6 +20,7 @@ interface IRoomConfigProps(props: IRoomConfigProps) { - const { api, roomId, type, headerImg, text, listItemName, connectionEventType } = props; + const { api, roomId, type, headerImg, showHeader, text, listItemName, connectionEventType } = props; const ConnectionConfigComponent = props.connectionConfigComponent; const [ error, setError ] = useState(null); const [ connections, setConnections ] = useState(null); @@ -91,30 +93,34 @@ export const RoomConfig = function - { - error && + return +
    + { + error && (!error.isWarning - ? {error.message} - : {error.message} + ? {error.message} + : {error.message} ) - } -
    - -

    {text.header}

    -
    - { canEditRoom &&
    -

    {text.createNew}

    - {serviceConfig && } -
    } - { !!connections?.length &&
    -

    { canEditRoom ? text.listCanEdit : text.listCantEdit }

    - { serviceConfig && connections?.map(c => + } + { showHeader && +
    + +

    {text.header}

    +
    + } + { canEditRoom &&
    +

    {text.createNew}

    + {serviceConfig && } +
    } + { connections === null && } + { !!connections?.length &&
    +

    { canEditRoom ? text.listCanEdit : text.listCantEdit }

    + { serviceConfig && connections?.map(c => ) - } -
    } -
    ; -}; \ No newline at end of file + } +
} + + ; +}; diff --git a/web/styling.scss b/web/styling.scss index 4a75a590..0ecd9268 100644 --- a/web/styling.scss +++ b/web/styling.scss @@ -1,11 +1,15 @@ - - :root { --background-color: #FFFFFF; --foreground-color: #17191C; --light-color: #737D8C; --primary-color: #0DBD8B; --primary-color-disabled: #0dbd8baf; + + background-color: #F4F6FA; + color: var(--foreground-color); + min-height: 100%; + width: 100%; + font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', sans-serif, 'Noto Color Emoji'; } // @media (prefers-color-scheme: dark) { @@ -15,18 +19,6 @@ // } // } -#root { - background-color: var(--background-color); - color: var(--foreground-color); - min-height: 100vh; - width: 100vw; - font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', sans-serif, 'Noto Color Emoji'; -} - -.app { - padding: 32px; -} - body { margin: 0; } @@ -65,4 +57,4 @@ button { transform: rotate(-45deg); border-color: var(--light-color); float: right; -} \ No newline at end of file +}