Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
package org.eclipse.openvsx.storage.log;
package org.eclipse.openvsx.analytics.ingestion;

import java.time.LocalDateTime;
import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
package org.eclipse.openvsx.storage.log;
package org.eclipse.openvsx.analytics.ingestion;

import java.time.ZoneId;

Expand All @@ -18,6 +18,8 @@
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

import org.eclipse.openvsx.analytics.ingestion.aws.AwsDownloadCountHandler;
import org.eclipse.openvsx.analytics.ingestion.azure.AzureDownloadCountHandler;
import org.eclipse.openvsx.entities.FileResource;
import org.eclipse.openvsx.migration.HandlerJobRequest;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
package org.eclipse.openvsx.storage.log;
package org.eclipse.openvsx.analytics.ingestion.aws;

import java.io.*;
import java.nio.charset.StandardCharsets;
Expand All @@ -34,6 +34,7 @@
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;

import org.eclipse.openvsx.analytics.ingestion.DownloadCountProcessor;
import org.eclipse.openvsx.entities.Extension;
import org.eclipse.openvsx.entities.FileResource;
import org.eclipse.openvsx.migration.HandlerJobRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*****************************************************************************/
package org.eclipse.openvsx.storage.log;
package org.eclipse.openvsx.analytics.ingestion.aws;

class CloudFrontLogFileParser implements LogFileParser {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*****************************************************************************/
package org.eclipse.openvsx.storage.log;
package org.eclipse.openvsx.analytics.ingestion.aws;

import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*****************************************************************************/
package org.eclipse.openvsx.storage.log;
package org.eclipse.openvsx.analytics.ingestion.aws;

import org.jspecify.annotations.Nullable;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*****************************************************************************/
package org.eclipse.openvsx.storage.log;
package org.eclipse.openvsx.analytics.ingestion.aws;

import org.jspecify.annotations.NonNull;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
package org.eclipse.openvsx.storage.log;
package org.eclipse.openvsx.analytics.ingestion.azure;

import java.io.IOException;
import java.net.URI;
Expand Down Expand Up @@ -42,6 +42,7 @@
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;

import org.eclipse.openvsx.analytics.ingestion.DownloadCountProcessor;
import org.eclipse.openvsx.entities.FileResource;
import org.eclipse.openvsx.migration.HandlerJobRequest;
import org.eclipse.openvsx.settings.SettingsService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
@ConditionalOnProperty(value = "ovsx.data.mirror.enabled", havingValue = "true")
public class DownloadCountServiceAspect {

@Around("execution(* org.eclipse.openvsx.storage.log.*DownloadCountService.isEnabled(..))")
@Around("execution(* org.eclipse.openvsx.analytics.ingestion.*DownloadCountService.isEnabled(..))")
public Object isEnabled(ProceedingJoinPoint ignoredPjp) throws Throwable {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import org.eclipse.openvsx.metrics.ExtensionDownloadMetrics;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.search.SearchUtilService;
import org.eclipse.openvsx.storage.log.DownloadCountService;
import org.eclipse.openvsx.analytics.ingestion.DownloadCountService;
import org.eclipse.openvsx.util.HttpHeadersUtil;
import org.eclipse.openvsx.util.TempFile;
import org.eclipse.openvsx.util.UrlUtil;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
import org.eclipse.openvsx.security.OAuth2UserServices;
import org.eclipse.openvsx.security.SecurityConfig;
import org.eclipse.openvsx.storage.*;
import org.eclipse.openvsx.storage.log.DownloadCountService;
import org.eclipse.openvsx.analytics.ingestion.DownloadCountService;
import org.eclipse.openvsx.trustedpublishing.TrustedPublishingConfig;
import org.eclipse.openvsx.util.ChangesCursor;
import org.eclipse.openvsx.util.LogService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
import org.eclipse.openvsx.security.OAuth2UserServices;
import org.eclipse.openvsx.security.SecurityConfig;
import org.eclipse.openvsx.storage.*;
import org.eclipse.openvsx.storage.log.DownloadCountService;
import org.eclipse.openvsx.analytics.ingestion.DownloadCountService;
import org.eclipse.openvsx.util.TargetPlatform;
import org.eclipse.openvsx.util.VersionService;
import org.eclipse.openvsx.web.JacksonConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
import org.eclipse.openvsx.storage.GoogleCloudStorageService;
import org.eclipse.openvsx.storage.LocalStorageService;
import org.eclipse.openvsx.storage.StorageUtilService;
import org.eclipse.openvsx.storage.log.DownloadCountService;
import org.eclipse.openvsx.analytics.ingestion.DownloadCountService;
import org.eclipse.openvsx.trustedpublishing.TrustedPublishingConfig;
import org.eclipse.openvsx.util.LogService;
import org.eclipse.openvsx.util.TargetPlatform;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*****************************************************************************/
package org.eclipse.openvsx.storage.log;
package org.eclipse.openvsx.analytics.ingestion.aws;

import java.io.BufferedReader;
import java.io.IOException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*****************************************************************************/
package org.eclipse.openvsx.storage.log;
package org.eclipse.openvsx.analytics.ingestion.aws;

import java.io.BufferedReader;
import java.io.IOException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
import org.eclipse.openvsx.scanning.ExtensionScanService;
import org.eclipse.openvsx.search.SearchUtilService;
import org.eclipse.openvsx.storage.*;
import org.eclipse.openvsx.storage.log.DownloadCountService;
import org.eclipse.openvsx.analytics.ingestion.DownloadCountService;
import org.eclipse.openvsx.util.ErrorResultException;
import org.eclipse.openvsx.util.LogService;
import org.eclipse.openvsx.util.TargetPlatform;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import org.eclipse.openvsx.metrics.ExtensionDownloadMetrics;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.search.SearchUtilService;
import org.eclipse.openvsx.storage.log.DownloadCountService;
import org.eclipse.openvsx.analytics.ingestion.DownloadCountService;

import static org.eclipse.openvsx.entities.FileResource.README;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import org.eclipse.openvsx.metrics.ExtensionDownloadMetrics;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.search.SearchUtilService;
import org.eclipse.openvsx.storage.log.DownloadCountService;
import org.eclipse.openvsx.analytics.ingestion.DownloadCountService;
import org.eclipse.openvsx.util.TempFile;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down
14 changes: 14 additions & 0 deletions webui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ This change log covers only the frontend library (webui) of Open VSX.
- Publishing goes through TanStack Query: `publishExtension` and `createNamespace` are mutation hooks (`usePublishExtension`, `useCreateNamespace`), and both service methods lose their `AbortController` parameter — writes are no longer aborted, and retries are the query client's to own. The user's extension list is a query too (`useUserExtensions`), read by the settings tab and by the publish queue as it follows a package, so a card appears in the list as soon as the registry has the package
- `ExtensionCard` accepts an `Extension` as well as a `SearchEntry`, and takes optional `to`, `linkState`, `overlay`, `footerStart`, `dimmed`, `tone` and `iconPending` props so other surfaces can reuse it instead of copying it

### Added

- Add a `Pill` component — the clickable glass pill the category pills are built on, now usable on its own — and extract the `MonoSlash`, `glassSurface` and `compactControl` page primitives out of the search field, the pills and the search header
- Add `userLoading` to `MainContext`, so custom pages can tell "not logged in" from "still resolving the user"
- Add a `userMenuContent` slot to `PageSettings.elements`: extra entries for the logged-in account menu, rendered above the admin entry. The slot receives a `MenuEntry` component to build entries with, so each entry is styled by the menu it appears in — the desktop and mobile menus style theirs differently, and a consumer cannot match both on its own
- Add an `adminPages` slot to `PageSettings.elements`: extra admin dashboard pages, each declaring a name, icon, optional description and optional category, and each appearing in the side panel, as a card on the dashboard overview and as a route. Contributions are additive — a category name matching a built-in group appends to it, and a page whose path would shadow a built-in one is ignored
- Widen the published API for consumers building their own pages: the request layer (`sendRequest`, `sendNonRetriableRequest`, `ErrorResponse`, `controllerFromSignal`), `MainContext`, `AppProviders`, `NotFound`, `createDefaultTheme` with the `MONO_FONT`/`NAVBAR_HEIGHT` tokens, the `createRoute`/`createAbsoluteURL`/`addQuery`/`formatCompactNumber`/`toRelativeTime` utils, the `useDebouncedCallback` and `useGridCursor` hooks, the navbar-chrome, search-focus and page-search-bar hooks, the category icon helpers, `ExtensionDetailRoutes`, and the `itemIcon`/`MenuItemText` building blocks for `userMenuContent` entries

### Changed

- Rename `ScrollToTop` to `ScrollRestoration`, matching what it does on back/forward navigation
- Rename the extension tint context to `navbar-chrome-context` and add a second channel to it: a page with sections pinned under the navbar can extend the navbar's blur fan down to back them (`useExtendNavbarBlur`)
- Give Popover and Autocomplete popups the same floating-paper treatment as the other menus, and stop Popovers locking body scroll — the lock jumps the scroll position on mobile and shifts the pinned chrome

### Fixed

- Fix a React warning ("Received `true` for a non-boolean attribute `notched`") from the admin dashboard's publisher role filter, whose custom `InputBase` doesn't consume the `notched` prop MUI's `Select` injects for the (unused) outlined variant
Expand Down
2 changes: 1 addition & 1 deletion webui/src/app-providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface AppProvidersProps {
* bottom in one place. Ordered outer→inner; keyboard shortcuts and search wrap
* every route (admin included). Router, theme, and Helmet stay at the app entry —
* they're supplied by whoever mounts the library. Lower-tier, feature-scoped
* providers (e.g. extension tint) stay with their feature.
* providers (e.g. the navbar chrome) stay with their feature.
*/
export const AppProviders: FunctionComponent<AppProvidersProps> = ({
mainContext,
Expand Down
33 changes: 5 additions & 28 deletions webui/src/components/category-pill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,8 @@
********************************************************************************/

import { FunctionComponent, useEffect, useRef } from 'react';
import { ButtonBase, SvgIconProps } from '@mui/material';
import { styled } from '@mui/material/styles';
import { accentHover, focusOutline } from './page-primitives';

const Root = styled(ButtonBase, {
shouldForwardProp: prop => prop !== 'isSelected'
})<{ isSelected?: boolean }>(({ theme, isSelected }) => ({
display: 'inline-flex',
alignItems: 'center',
gap: '0.4375rem',
flexShrink: 0,
overflow: 'hidden',
backgroundColor: isSelected ? theme.palette.accentSoft : theme.palette.surface2,
border: `1px solid ${isSelected ? theme.palette.secondary.main : theme.palette.divider}`,
color: isSelected ? theme.palette.secondary.light : theme.palette.text.secondary,
fontSize: '0.8125rem',
fontWeight: isSelected ? 600 : 500,
padding: '0.4375rem 0.8125rem',
borderRadius: theme.shape.borderRadiusPill,
whiteSpace: 'nowrap',
fontFamily: 'inherit',
transition: 'border-color 0.14s, color 0.14s',
...(isSelected ? {} : accentHover(theme)),
...focusOutline(theme)
}));
import { SvgIconProps } from '@mui/material';
import { Pill } from './pill';

export interface CategoryPillProps {
label: string;
Expand All @@ -56,9 +33,9 @@ export const CategoryPill: FunctionComponent<CategoryPillProps> = ({ label, icon
}, [isSelected]);

return (
<Root ref={ref} isSelected={isSelected} aria-pressed={!!isSelected} onClick={onClick}>
<Icon sx={{ fontSize: '1rem', flexShrink: 0, color: 'secondary.main' }} />
<Pill ref={ref} isSelected={isSelected} aria-pressed={!!isSelected} onClick={onClick}>
<Icon sx={{ fontSize: '1rem', flexShrink: 0, color: isSelected ? 'inherit' : 'secondary.main' }} />
{label}
</Root>
</Pill>
);
};
11 changes: 1 addition & 10 deletions webui/src/components/extension-searchfield.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { alpha, styled } from '@mui/material/styles';
import { MONO_FONT } from '../default/theme';
import SearchIcon from '@mui/icons-material/Search';
import ClearIcon from '@mui/icons-material/Close';
import { focusRing } from './page-primitives';
import { focusRing, MonoSlash } from './page-primitives';

interface ExtensionSearchfieldProps {
onSearchChanged: (s: string) => void;
Expand Down Expand Up @@ -49,15 +49,6 @@ const SearchWrap = styled(Box, {
'&:focus-within': focusRing(theme)
}));

const MonoSlash = styled('span')(({ theme }) => ({
fontFamily: MONO_FONT,
color: theme.palette.secondary.light,
fontSize: '1.0625rem',
lineHeight: 1,
flexShrink: 0,
userSelect: 'none'
}));

const SearchInput = styled(InputBase)(({ theme }) => ({
flex: 1,
fontFamily: MONO_FONT,
Expand Down
37 changes: 37 additions & 0 deletions webui/src/components/page-primitives.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import { Box, Typography } from '@mui/material';
import { alpha, CSSObject, styled, Theme } from '@mui/material/styles';
import { MONO_FONT } from '../default/theme';

/** Normalized gap between stacked sections; the owl selector skips the first (and any null) child. */
export const SectionStack = styled(Box)(({ theme }) => ({
Expand Down Expand Up @@ -42,6 +43,16 @@ export const ExtensionGrid = styled(Box)({
}
});

/** Leading '/' glyph of the search fields — the field's mark and the shortcut hint in one. */
export const MonoSlash = styled('span')(({ theme }) => ({
fontFamily: MONO_FONT,
color: theme.palette.secondary.light,
fontSize: '1.0625rem',
lineHeight: 1,
flexShrink: 0,
userSelect: 'none'
}));

/** Small uppercase label used to head sections, columns and sidebars. */
export const Eyebrow = styled(Typography)(({ theme }) => ({
fontSize: '0.75rem',
Expand Down Expand Up @@ -107,6 +118,32 @@ export const TagChip = styled('span', { shouldForwardProp: prop => prop !== 'acc
})
);

/**
* Translucent glass fill for controls floating over scrolled content (pills, pinned toolbars).
* Backdrop filters re-blur on every scrolled frame, so keep the frosted surfaces small.
*/
export const glassSurface = (theme: Theme, opacity = 0.7) => ({
backgroundColor: alpha(theme.palette.surface2, opacity),
backdropFilter: 'blur(2px) saturate(1.8)'
});

/**
* Compact toolbar control (the "Sort by" select, filter autocompletes): one 30px text row on a
* paper fill. Selects take it directly via sx; composite inputs spread it into their outlined
* root. It carries nested selectors for the select's inner slots, so it belongs in sx (or another
* emotion context) and will not work in a plain style prop.
*/
export const compactControl = (theme: Theme) => ({
fontSize: '0.8125rem',
fontWeight: 500,
color: theme.palette.text.primary,
height: '1.875rem',
backgroundColor: theme.palette.background.paper,
borderRadius: `${theme.shape.borderRadius}px`,
'& .MuiSelect-select': { padding: '0.25rem 2rem 0.25rem 0.625rem' },
'& .MuiSelect-icon': { color: theme.palette.text.disabled, fontSize: '1.125rem' }
});

/** Hover treatment for chips and pills: accent border and text color. Suppressed on touch devices. */
export const accentHover = (theme: Theme) => ({
'@media (hover: hover)': {
Expand Down
43 changes: 43 additions & 0 deletions webui/src/components/pill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/********************************************************************************
* Copyright (c) 2026 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/

import { ButtonBase } from '@mui/material';
import { alpha, styled } from '@mui/material/styles';
import { accentHover, focusOutline, glassSurface } from './page-primitives';

/**
* Clickable pill primitive: a translucent glass surface flipping to an accent fill when
* selected — the same treatment as the extension detail page's sticky tabs.
*/
export const Pill = styled(ButtonBase, {
shouldForwardProp: prop => prop !== 'isSelected'
})<{ isSelected?: boolean }>(({ theme, isSelected }) => ({
display: 'inline-flex',
alignItems: 'center',
gap: '0.4375rem',
flexShrink: 0,
overflow: 'hidden',
...glassSurface(theme),
...(isSelected ? { backgroundColor: alpha(theme.palette.secondary.main, 0.7) } : {}),
border: `1px solid ${isSelected ? theme.palette.secondary.main : theme.palette.divider}`,
color: isSelected ? theme.palette.secondary.contrastText : theme.palette.text.secondary,
fontSize: '0.8125rem',
fontWeight: isSelected ? 600 : 500,
padding: '0.4375rem 0.8125rem',
borderRadius: theme.shape.borderRadiusPill,
whiteSpace: 'nowrap',
fontFamily: 'inherit',
transition: 'border-color 0.14s, color 0.14s, background 0.14s',
...(isSelected ? {} : accentHover(theme)),
...focusOutline(theme)
}));
2 changes: 2 additions & 0 deletions webui/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface MainContext {
pageSettings: PageSettings;
handleError: (err: unknown, options?: { onClose?: () => void }) => void;
user?: UserData;
/** The initial user fetch is still in flight, so `user` being undefined doesn't yet mean logged out. */
userLoading: boolean;
updateUser: () => void;
loginProviders?: Record<string, string>;
version?: RegistryVersion;
Expand Down
Loading
Loading