Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions frontend-web/webclient/app/DefaultObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import {SidebarStateProps} from "./Applications/Redux/Reducer";
import {getUserThemePreference} from "./UtilityFunctions";
import {defaultAvatar} from "./AvataaarLib";
import {HookStore} from "./Utilities/ReduxHooks";
import {ProviderBrandingResponse} from "./UCloud/ProviderBrandingApi";
import {initProviderBranding} from "./ProviderBrandings/AutomaticProviderBranding";
import {BrandingResponse} from "./UCloud/BrandingApi";
import {initBranding} from "./Applications/Branding/AutomaticBranding";

Expand All @@ -26,7 +24,6 @@ export interface LegacyReduxObject {
avatar: AvatarReduxObject;
project: ProjectRedux.State;
terminal: TerminalState;
providerBrandings: ProviderBrandingResponse;
branding: BrandingResponse
popinChild: PopInArgs | null;
loading: boolean;
Expand Down Expand Up @@ -59,7 +56,6 @@ export function initObject(): ReduxObject {
avatar: initAvatar(),
project: ProjectRedux.initialState,
terminal: initTerminalState(),
providerBrandings: initProviderBranding(),
branding: initBranding(),
popinChild: null,
loading: false,
Expand Down
4 changes: 2 additions & 2 deletions frontend-web/webclient/app/Grants/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {ChangeOrganizationDetails, OptionalInfo, optionalInfoRequest, optionalIn
import {ProviderBranding, ProviderBrandingProductDescription, ProviderBrandingResponse} from "@/UCloud/ProviderBrandingApi";
import {useSelector} from "react-redux";
import {sendFailureNotification, sendSuccessNotification} from "@/Notifications";
import {providerBrandingStore} from "@/ProviderBrandings/AutomaticProviderBranding";

// State model
// =====================================================================================================================
Expand Down Expand Up @@ -1371,7 +1372,6 @@ export function Editor(): React.ReactNode {
const isForSubAllocator = getQueryParam(location.search, "subAllocator") == "true";
useProjectId(); // FIXME(Jonas): Is this some refresh-thing that breaks stuff if you remove it?

const providerBrandingData = useSelector((it: ReduxObject) => it.providerBrandings);
const [missingUserInfo, setMissingUserInfo] = React.useState(false);
React.useEffect(() => {
(async () => {
Expand Down Expand Up @@ -2139,7 +2139,7 @@ export function Editor(): React.ReactNode {

if (hideZeroFields && !anyNonZeroValues) return null;

const currentProvider = providerBrandingData.providers[providerId];
const currentProvider = providerBrandingStore.getSnapshot().providers[providerId];
const productDescription = currentProvider?.productDescription?.find(it => it.category === category.category.name);
const showDescriptions = productDescription != undefined;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,39 @@
import * as React from "react";
import {useCloudAPI} from "@/Authentication/DataHook";
import {providerBrandingApi, ProviderBrandingResponse } from "@/UCloud/ProviderBrandingApi";
import {useDispatch} from "react-redux";
import {PayloadAction} from "@reduxjs/toolkit";
import {callAPI} from "@/Authentication/DataHook";
import {ProviderBranding, providerBrandingApi, ProviderBrandingResponse} from "@/UCloud/ProviderBrandingApi";
import {ExternalStoreBase} from "@/Utilities/ReduxUtilities";
import ProviderInfo from "@/Assets/provider_info.json";

export const AutomaticProviderBranding: React.FunctionComponent = () => {
const [providerBrandings, fetchBranding] = useCloudAPI<ProviderBrandingResponse>(
providerBrandingApi.browse(),
{ providers: {} }
);

React.useEffect(() => {
const intervalId = setInterval(() => {
fetchBranding(providerBrandingApi.browse());
}, 1000 * 60 * 60);
return () => {
clearInterval(intervalId);
}
}, []);

const dispatch = useDispatch();
export const providerBrandingStore = new class extends ExternalStoreBase {
private branding: ProviderBrandingResponse = {providers: {}};

React.useEffect(() => {
dispatch({type: ADD_PROVIDER_BRANDING, payload: providerBrandings.data});
}, [providerBrandings.data]);

return null;
};
async onInit(): Promise<void> {
this.fetch();
window.setInterval(() => {
this.fetch();
}, 3 * 600_000);
}

const ADD_PROVIDER_BRANDING = "ADD_PROVIDER_BRANDING";
type SetProviderBranding = PayloadAction<ProviderBrandingResponse, typeof ADD_PROVIDER_BRANDING>
async fetch() {
try {
const response = await callAPI(providerBrandingApi.browse());
this.branding = response;
} catch (e: any) {
console.warn(e);
}
}

type ProviderBrandingAction = SetProviderBranding;
public getSnapshot(): Readonly<ProviderBrandingResponse> {
return this.branding;
}

export function initProviderBranding(): ProviderBrandingResponse {
return {
providers: {},
public getProviderProperty<Property extends keyof ProviderBranding>(providerId: string, providerProperty: Property): ProviderBranding[Property] | undefined {
const property = this.branding.providers[providerId]?.[providerProperty];
const useFallback = providerProperty === "logo" && (property as string)?.includes("/");
if (!property) console.warn(`Property '${providerProperty}' missing for ${providerId}`, this.branding);
if (useFallback) console.warn(`Using fallback logo for ${providerId}. Actual value: ${property}`)
return property && !useFallback ? property : ProviderInfo.providers.find(it => it.id === providerId)?.[providerProperty as string];
}
}

export function providerBrandingReducer(state: ProviderBrandingResponse = initProviderBranding(), action: ProviderBrandingAction): ProviderBrandingResponse {
switch (action.type) {
case ADD_PROVIDER_BRANDING: {
return action.payload;
}
default:
return state;
}
}
providerBrandingStore.onInit();
32 changes: 16 additions & 16 deletions frontend-web/webclient/app/Providers/Detailed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {ProviderLogo} from "./ProviderLogo";
import {ProviderTitle} from "./ProviderTitle";
import TitledCard from "@/ui-components/HighlightedCard";
import {SidebarTabId} from "@/ui-components/SidebarComponents";
import {useSelector} from "react-redux";
import {ProviderBranding} from "@/UCloud/ProviderBrandingApi";
import {providerBrandingStore} from "@/ProviderBrandings/AutomaticProviderBranding";

function useProviderBranding(id?: string): ProviderBranding | undefined {
const data = useSelector((it: ReduxObject) => it.providerBrandings);
if (!id) return undefined;
return data.providers[id];
const providers = providerBrandingStore.getSnapshot();
return providers.providers[id];
}

export default function DetailedProvider() {
Expand Down Expand Up @@ -50,7 +50,7 @@ export default function DetailedProvider() {
<Flex>
{section.image !== "" ? <Flex flexDirection="column" mr="24px" my="8px">
<Box flexGrow={1} />
<img alt={`Provider detail image`} style={{height: "150px", objectFit: "scale-down"}} src={section.image} />
<img alt={`Provider detail image`} style={{height: "150px", objectFit: "scale-down"}} src={section.image} />
<Box flexGrow={1} />
</Flex> : <Box />}
<div>
Expand All @@ -62,27 +62,27 @@ export default function DetailedProvider() {
</TitledCard>
</Box>
)}
{entry.productDescription.map((prod, index) =>
{entry.productDescription.map((prod, index) =>
<Box key={index} my="32px">
<TitledCard>
<Flex>
<h2>{prod.category}</h2>
<Box flexGrow={1}/>
<Box flexGrow={1} />
<div>
{prod.shortDescription}
</div>
</Flex>
<Flex>
<div>
{prod.section.image ? <img alt={`Product Section Image`} style={{height: "150px", objectFit: "scale-down"}} src={prod.section.image} /> : <div />}
</div>
<div>
<br/>
<br/>
<Markdown>
{prod.section.description}
</Markdown>
</div>
<div>
{prod.section.image ? <img alt={`Product Section Image`} style={{height: "150px", objectFit: "scale-down"}} src={prod.section.image} /> : <div />}
</div>
<div>
<br />
<br />
<Markdown>
{prod.section.description}
</Markdown>
</div>
</Flex>
</TitledCard>
</Box>
Expand Down
19 changes: 10 additions & 9 deletions frontend-web/webclient/app/Providers/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@ import {CardClass} from "@/ui-components/Card";
import {SidebarTabId} from "@/ui-components/SidebarComponents";
import {ProviderBranding} from "@/UCloud/ProviderBrandingApi";
import {useSelector} from "react-redux";
import {providerBrandingStore} from "@/ProviderBrandings/AutomaticProviderBranding";

export function ProviderEntry(props: { provider: ProviderBranding }): React.ReactNode {
export function ProviderEntry(props: {provider: ProviderBranding}): React.ReactNode {
if (!props.provider.id || !props.provider.title) return null;

return (
<Link to={`/providers/detailed/${props.provider.id}`}>
<div className={classConcat(CardClass, ProviderCard)}>
<Flex mt="12px">
<Flex mx="auto">
<ProviderLogo providerId={props.provider.id} size={150}/>
<ProviderLogo providerId={props.provider.id} size={150} />
</Flex>
</Flex>
<h3 style={{textAlign: "center", marginTop: "8px", height: "50px"}}>
<ProviderTitle providerId={props.provider.id}/>
<ProviderTitle providerId={props.provider.id} />
</h3>

<div style={{textAlign: "start"}}>
Expand All @@ -37,8 +38,8 @@ export function ProviderEntry(props: { provider: ProviderBranding }): React.Reac
}

function useProviderBrandings(): Record<string, ProviderBranding> | undefined {
const data = useSelector((it: ReduxObject) => it.providerBrandings);
return data.providers;
const providers = React.useSyncExternalStore(sub => providerBrandingStore.subscribe(sub), providerBrandingStore.getSnapshot);
return providers.providers;
}

export default function ProviderOverview() {
Expand All @@ -51,20 +52,20 @@ export default function ProviderOverview() {
const main = <Box m="12px 24px">
<GridCardGroup minmax={250}>
{Object.values(providers).map(provider =>
<ProviderEntry key={provider.title} provider={provider}/>
<ProviderEntry key={provider.title} provider={provider} />
)}
</GridCardGroup>
</Box>

if (!Client.isLoggedIn) return (<>
<NonAuthenticatedHeader/>
<Box mb="72px"/>
<NonAuthenticatedHeader />
<Box mb="72px" />
<div>
{main}
</div>
</>);

return (<MainContainer main={main}/>);
return (<MainContainer main={main} />);
}

const ProviderCard = injectStyle("provider-card", k => `
Expand Down
13 changes: 9 additions & 4 deletions frontend-web/webclient/app/Providers/ProviderLogo.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import * as React from "react";
import {Image} from "@/ui-components";
import ProviderInfo from "@/Assets/provider_info.json";
import {classConcat} from "@/Unstyled";
import {injectStyle} from "@/Unstyled";
import {TooltipV2} from "@/ui-components/Tooltip";
import {getProviderTitle} from "@/Providers/ProviderTitle";
import {providerBrandingStore} from "@/ProviderBrandings/AutomaticProviderBranding";

export function providerLogoPath(providerId: string): string {
const logo = ProviderInfo.providers.find(p => p.id === providerId)?.logo ?? "";
const logo = providerBrandingStore.getProviderProperty(providerId, "logo");
if (logo) return `/Images/${logo}`;
return "";
}

export const ProviderLogo: React.FunctionComponent<{providerId: string; size: number; className?: string;}> = ({providerId, size, className}) => {
const myInfo = ProviderInfo.providers.find(p => p.id === providerId);
const [logo, title] = [
providerBrandingStore.getProviderProperty(providerId, "logo"),
providerBrandingStore.getProviderProperty(providerId, "title")
];


return <ProviderLogoWrapper size={size} className={className} tooltip={getProviderTitle(providerId)}>
{!myInfo ? (providerId[0] ?? "?").toUpperCase() : <Image src={`/Images/${myInfo.logo}`} alt={`Logo for ${myInfo.title}`} />}
{!logo ? (providerId[0] ?? "?").toUpperCase() : <Image src={logo.includes("/") ? logo : `/Images/${logo}`} alt={`Logo for ${title}`} />}
</ProviderLogoWrapper>
};

Expand Down
19 changes: 5 additions & 14 deletions frontend-web/webclient/app/Providers/ProviderTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
import * as React from "react";
import ProviderInfo from "@/Assets/provider_info.json";
import {capitalized} from "@/UtilityFunctions";

interface ProviderInfo {
id: string;
title: string;
logo: string | null;
shortTitle: string;
}
import {providerBrandingStore} from "@/ProviderBrandings/AutomaticProviderBranding";

export const ProviderTitle: React.FunctionComponent<{providerId: string}> = ({providerId}) => {
return <>{getProviderTitle(providerId)}</>;
};

export function getProviderTitle(providerId: string): string {
const providers: ProviderInfo[] = ProviderInfo.providers;
const myInfo = providers.find(p => p.id === providerId);
return myInfo?.title ?? capitalized(providerId.replace("_", " ").replace("-", " "));
const title = providerBrandingStore.getProviderProperty(providerId, "title");
return title ?? capitalized(providerId.replace("_", " ").replace("-", " "));
}
export function getShortProviderTitle(providerId: string): string {
const providers: ProviderInfo[] = ProviderInfo.providers;
const myInfo = providers.find(p => p.id === providerId);
return myInfo?.shortTitle ?? capitalized(providerId.replace("_", " ").replace("-", " "));
const shortTitle = providerBrandingStore.getProviderProperty(providerId, "shortTitle");
return shortTitle ?? capitalized(providerId.replace("_", " ").replace("-", " "));
}
2 changes: 1 addition & 1 deletion frontend-web/webclient/app/Terminal/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {BulkResponse} from "@/UCloud";
import JobsApi, {InteractiveSession} from "@/UCloud/JobsApi";
import {bulkRequestOf, bulkResponseOf} from "@/UtilityFunctions";
import {ShellWithSession} from "@/Applications/Jobs/Shell";
import {Terminal} from "xterm";
import {Terminal} from "@xterm/xterm";
import {getCssPropertyValue} from "@/Utilities/StylingUtilities";
import {CSSVarCurrentSidebarStickyWidth} from "@/ui-components/List";
import {Tab} from "@/Editor/Editor";
Expand Down
3 changes: 1 addition & 2 deletions frontend-web/webclient/app/UCloud/ProviderBrandingApi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,4 @@ export class ProviderBrandingApi {
}
}

const providerBrandingApi = new ProviderBrandingApi();
export { providerBrandingApi };
export const providerBrandingApi = new ProviderBrandingApi();
2 changes: 0 additions & 2 deletions frontend-web/webclient/app/Utilities/ReduxUtilities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {popInReducer} from "@/ui-components/PopIn";
import sidebar from "@/Applications/Redux/Reducer";
import {EnhancedStore, ReducersMapObject, configureStore} from "@reduxjs/toolkit";
import {noopCall} from "@/Authentication/DataHook";
import {providerBrandingReducer} from "@/ProviderBrandings/AutomaticProviderBranding";
import {brandingReducer} from "@/Applications/Branding/AutomaticBranding";

export const CONTEXT_SWITCH = "CONTEXT_SWITCH";
Expand Down Expand Up @@ -42,7 +41,6 @@ export const store = confStore(initObject(), {
sidebar,
avatar: avatarReducer,
terminal: terminalReducer,
providerBrandings: providerBrandingReducer,
branding: brandingReducer,
loading,
project: ProjectRedux.reducer,
Expand Down
10 changes: 0 additions & 10 deletions frontend-web/webclient/app/UtilityFunctions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,6 @@ export type ExtensionType =
| "markdown"
| "application";

export const commonFileExtensions = [
"app", "application", "md", "markdown", "markdown", "zig", "swift", "kt", "kts", "js", "jsx", "ts", "tsx",
"java", "py", "python", "tex", "r", "c", "h", "cc", "hh", "c++", "h++", "hpp", "cpp", "cxx", "jai", "hxx",
"html", "htm", "lhs", "hs", "sql", "sh", "iol", "ol", "col", "bib", "toc", "jar", "exe", "xml", "json",
"yml", "ini", "sbatch", "code", "png", "gif", "tiff", "eps", "ppm", "svg", "jpg", "jpeg", "image", "txt",
"doc", "docx", "log", "csv", "tsv", "plist", "out", "err", "text", "pdf", "pdf", "wav", "mp3", "ogg", "aac",
"pcm", "audio", "mpg", "mp4", "avi", "mov", "wmv", "video", "gz", "zip", "tar", "tgz", "tbz", "bz2", "archive",
"dat", "binary", "rs",
];

const languages = {
"md": "markdown",
"kt": "kotlin",
Expand Down
Loading