forked from boos/BoosAPI
Repair release-branch CI after folding dev onto main
The release branch inherited a few merge-shaped regressions after the large dev snapshot was layered onto the latest main snapshot. This commit restores the main-compatible stats and TokenRoutes behavior the CI suite expects and adds the missing multi-brand helper export so the release branch reflects a coherent post-merge tree instead of a partially regressed hybrid. Constraint: Must keep the release branch mergeable to main while preserving the current publish snapshot shape Rejected: Re-open the whole release merge and hand-resolve every file again | too broad for targeted CI repair Confidence: medium Scope-risk: narrow Reversibility: clean Directive: When dev and main have drifted heavily, re-run the main-facing CI tests after any conflict normalization pass Tested: targeted vitest suite for failing CI jobs plus update-center coverage; typecheck Not-tested: Full npm test still not representative locally because of unrelated untracked tmp/sub2api suites outside the PR branch content
This commit is contained in:
+1483
-1204
File diff suppressed because it is too large
Load Diff
@@ -777,30 +777,46 @@ export function getAllBrandNames(): string[] {
|
|||||||
return BRAND_DEFINITIONS.map((brand) => brand.name);
|
return BRAND_DEFINITIONS.map((brand) => brand.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBrand(modelName: string): BrandInfo | null {
|
function toBrandInfo(brand: BrandDefinition): BrandInfo {
|
||||||
|
return {
|
||||||
|
name: brand.name,
|
||||||
|
icon: brand.icon,
|
||||||
|
color: brand.color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMatchingBrands(modelName: string): BrandInfo[] {
|
||||||
const context = buildMatchContext(modelName);
|
const context = buildMatchContext(modelName);
|
||||||
|
const matches: BrandInfo[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
|
||||||
|
const add = (brand: BrandDefinition) => {
|
||||||
|
if (seen.has(brand.name)) return;
|
||||||
|
seen.add(brand.name);
|
||||||
|
matches.push(toBrandInfo(brand));
|
||||||
|
};
|
||||||
|
|
||||||
for (const definition of BRAND_DEFINITIONS) {
|
for (const definition of BRAND_DEFINITIONS) {
|
||||||
if (definition.rules.some((rule) => matchesRule(context, rule))) {
|
if (definition.rules.some((rule) => matchesRule(context, rule))) {
|
||||||
return {
|
add(definition);
|
||||||
name: definition.name,
|
|
||||||
icon: definition.icon,
|
|
||||||
color: definition.color,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const candidate of context.candidates) {
|
for (const candidate of context.candidates) {
|
||||||
for (const rule of BRAND_FALLBACK_BOUNDARY_RULES) {
|
for (const rule of BRAND_FALLBACK_BOUNDARY_RULES) {
|
||||||
if (rule.boundaryRegex.test(candidate)) {
|
if (rule.boundaryRegex.test(candidate)) {
|
||||||
return {
|
add(rule.brand);
|
||||||
name: rule.brand.name,
|
|
||||||
icon: rule.brand.icon,
|
|
||||||
color: rule.brand.color,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMatchingBrandNames(modelName: string): string[] {
|
||||||
|
return getMatchingBrands(modelName).map((brand) => brand.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBrand(modelName: string): BrandInfo | null {
|
||||||
|
return getMatchingBrands(modelName)[0] || null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,8 +93,14 @@ const EMPTY_ROUTE_FORM: RouteEditorForm = {
|
|||||||
sourceRouteIds: [],
|
sourceRouteIds: [],
|
||||||
advancedOpen: false,
|
advancedOpen: false,
|
||||||
};
|
};
|
||||||
|
const DESKTOP_DETAIL_ENTER_MS = 260;
|
||||||
const DESKTOP_DETAIL_COLLAPSE_MS = 200;
|
const DESKTOP_DETAIL_COLLAPSE_MS = 200;
|
||||||
|
|
||||||
|
function prefersReducedMotion(): boolean {
|
||||||
|
return typeof globalThis.matchMedia === 'function'
|
||||||
|
&& globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
}
|
||||||
|
|
||||||
function getRouteRoutingStrategySuccessMessage(value: RouteRoutingStrategy): string {
|
function getRouteRoutingStrategySuccessMessage(value: RouteRoutingStrategy): string {
|
||||||
if (value === 'round_robin') return '已切换为轮询策略';
|
if (value === 'round_robin') return '已切换为轮询策略';
|
||||||
if (value === 'stable_first') return '已切换为稳定优先策略';
|
if (value === 'stable_first') return '已切换为稳定优先策略';
|
||||||
@@ -114,30 +120,44 @@ export function DesktopDetailPanelPresence({
|
|||||||
}) {
|
}) {
|
||||||
const [shouldRender, setShouldRender] = useState(open);
|
const [shouldRender, setShouldRender] = useState(open);
|
||||||
const [isOpen, setIsOpen] = useState(open);
|
const [isOpen, setIsOpen] = useState(open);
|
||||||
|
const [isEntering, setIsEntering] = useState(false);
|
||||||
const [isClosing, setIsClosing] = useState(false);
|
const [isClosing, setIsClosing] = useState(false);
|
||||||
const hasEverOpenedRef = useRef(open);
|
const hasEverOpenedRef = useRef(open);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const reduceMotion = prefersReducedMotion();
|
||||||
|
|
||||||
if (open) {
|
if (open) {
|
||||||
hasEverOpenedRef.current = true;
|
hasEverOpenedRef.current = true;
|
||||||
setShouldRender(true);
|
setShouldRender(true);
|
||||||
setIsClosing(false);
|
|
||||||
if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
|
|
||||||
const rafId = window.requestAnimationFrame(() => setIsOpen(true));
|
|
||||||
return () => window.cancelAnimationFrame(rafId);
|
|
||||||
}
|
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
return undefined;
|
setIsClosing(false);
|
||||||
|
if (reduceMotion) {
|
||||||
|
setIsEntering(false);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
setIsEntering(true);
|
||||||
|
const enterTimerId = globalThis.setTimeout(() => {
|
||||||
|
setIsEntering(false);
|
||||||
|
}, DESKTOP_DETAIL_ENTER_MS);
|
||||||
|
return () => globalThis.clearTimeout(enterTimerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasEverOpenedRef.current) {
|
if (!hasEverOpenedRef.current) {
|
||||||
setShouldRender(false);
|
setShouldRender(false);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
|
setIsEntering(false);
|
||||||
setIsClosing(false);
|
setIsClosing(false);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
|
setIsEntering(false);
|
||||||
|
if (reduceMotion) {
|
||||||
|
setShouldRender(false);
|
||||||
|
setIsClosing(false);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
setIsClosing(true);
|
setIsClosing(true);
|
||||||
const timerId = globalThis.setTimeout(() => {
|
const timerId = globalThis.setTimeout(() => {
|
||||||
setShouldRender(false);
|
setShouldRender(false);
|
||||||
@@ -150,7 +170,7 @@ export function DesktopDetailPanelPresence({
|
|||||||
if (!shouldRender) return null;
|
if (!shouldRender) return null;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`route-detail-panel-presence ${isOpen ? 'is-open' : ''} ${isClosing ? 'is-closing' : ''}`.trim()}
|
className={`route-detail-panel-presence ${isOpen ? 'is-open' : ''} ${isEntering ? 'is-entering' : ''} ${isClosing ? 'is-closing' : ''}`.trim()}
|
||||||
style={{ gridColumn: '1 / -1' }}
|
style={{ gridColumn: '1 / -1' }}
|
||||||
>
|
>
|
||||||
{children(isClosing)}
|
{children(isClosing)}
|
||||||
@@ -1280,15 +1300,20 @@ export default function TokenRoutes() {
|
|||||||
const isCurrentlyExpanded = expandedRouteIds.includes(routeId);
|
const isCurrentlyExpanded = expandedRouteIds.includes(routeId);
|
||||||
if (isCurrentlyExpanded) {
|
if (isCurrentlyExpanded) {
|
||||||
if (!isMobile) {
|
if (!isMobile) {
|
||||||
setClosingDesktopDetailRouteIds((prev) => (prev.includes(routeId) ? prev : [...prev, routeId]));
|
const reduceMotion = prefersReducedMotion();
|
||||||
const existingTimer = desktopDetailCloseTimersRef.current[routeId];
|
if (reduceMotion) {
|
||||||
if (existingTimer) {
|
|
||||||
globalThis.clearTimeout(existingTimer);
|
|
||||||
}
|
|
||||||
desktopDetailCloseTimersRef.current[routeId] = globalThis.setTimeout(() => {
|
|
||||||
setClosingDesktopDetailRouteIds((prev) => prev.filter((id) => id !== routeId));
|
setClosingDesktopDetailRouteIds((prev) => prev.filter((id) => id !== routeId));
|
||||||
delete desktopDetailCloseTimersRef.current[routeId];
|
} else {
|
||||||
}, DESKTOP_DETAIL_COLLAPSE_MS);
|
setClosingDesktopDetailRouteIds((prev) => (prev.includes(routeId) ? prev : [...prev, routeId]));
|
||||||
|
const existingTimer = desktopDetailCloseTimersRef.current[routeId];
|
||||||
|
if (existingTimer) {
|
||||||
|
globalThis.clearTimeout(existingTimer);
|
||||||
|
}
|
||||||
|
desktopDetailCloseTimersRef.current[routeId] = globalThis.setTimeout(() => {
|
||||||
|
setClosingDesktopDetailRouteIds((prev) => prev.filter((id) => id !== routeId));
|
||||||
|
delete desktopDetailCloseTimersRef.current[routeId];
|
||||||
|
}, DESKTOP_DETAIL_COLLAPSE_MS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setExpandedRouteIds((prev) => prev.filter((id) => id !== routeId));
|
setExpandedRouteIds((prev) => prev.filter((id) => id !== routeId));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user