Prevent duplicate server display during profile transitions
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
export const HARBOR_VERSIONS = Object.freeze({
|
||||
macClient: '0.23.5',
|
||||
gatewayClient: '0.24.5',
|
||||
macClient: '0.23.6',
|
||||
gatewayClient: '0.24.6',
|
||||
gatewayBackend: '0.24.0',
|
||||
});
|
||||
|
||||
|
||||
@@ -728,6 +728,7 @@ export function ClientOverviewPage({
|
||||
prompt={!profile.desiredServerId}
|
||||
leaving={pickerState.leaving}
|
||||
revealVersion={pickerState.revealVersion}
|
||||
anchorServerId={pickerState.anchorServerId}
|
||||
onSelect={(serverId) => selectServer(profile, serverId)}
|
||||
/>}
|
||||
/>
|
||||
|
||||
@@ -167,6 +167,7 @@ interface ServerPickerProps {
|
||||
prompt: boolean;
|
||||
leaving: boolean;
|
||||
revealVersion: number;
|
||||
anchorServerId?: string;
|
||||
onSelect: (id: string) => unknown;
|
||||
}
|
||||
|
||||
@@ -179,6 +180,7 @@ export function ServerPicker({
|
||||
prompt,
|
||||
leaving,
|
||||
revealVersion,
|
||||
anchorServerId = '',
|
||||
onSelect,
|
||||
}: ServerPickerProps) {
|
||||
const favoritesKey = `${FAVORITES_KEY}:${profileId}`;
|
||||
@@ -207,7 +209,7 @@ export function ServerPicker({
|
||||
if (view === 'recent') return recent.flatMap((id) => found.find((server) => server.id === id) || []);
|
||||
return found;
|
||||
}, [servers, query, view, favorites, recent]);
|
||||
const results = filtered.filter(({ id }) => id !== selectedServerId);
|
||||
const results = filtered.filter(({ id }) => id !== selectedServerId && id !== anchorServerId);
|
||||
const pageCount = Math.max(1, Math.ceil(results.length / SERVER_RESULT_WINDOW));
|
||||
const visible = results.slice(page * SERVER_RESULT_WINDOW, (page + 1) * SERVER_RESULT_WINDOW);
|
||||
const grouped = servers.length >= 10;
|
||||
@@ -282,7 +284,7 @@ export function ServerPicker({
|
||||
<ServerCheckButton checking={checking} onClick={checkVisible} />
|
||||
</div>
|
||||
<div className="client-server-grid">
|
||||
<ServerRow
|
||||
{servers[0].id !== anchorServerId && <ServerRow
|
||||
server={servers[0]}
|
||||
selected={servers[0].id === selectedServerId}
|
||||
favorite={false}
|
||||
@@ -290,7 +292,7 @@ export function ServerPicker({
|
||||
disabled={disabled}
|
||||
index={0}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
/>}
|
||||
</div>
|
||||
</section>;
|
||||
}
|
||||
@@ -310,8 +312,8 @@ export function ServerPicker({
|
||||
));
|
||||
|
||||
const simpleServers = [
|
||||
...(selected ? [selected] : []),
|
||||
...servers.filter(({ id }) => id !== selectedServerId),
|
||||
...(selected && selected.id !== anchorServerId ? [selected] : []),
|
||||
...servers.filter(({ id }) => id !== selectedServerId && id !== anchorServerId),
|
||||
].slice(0, SIMPLE_SERVER_LIMIT);
|
||||
|
||||
return <section className={`client-servers is-scalable${servers.length <= SIMPLE_SERVER_LIMIT ? ' is-short' : ''}`} aria-label="Выберите сервер">
|
||||
@@ -403,7 +405,7 @@ export function ServerPicker({
|
||||
fallback={autoActive ? undefined : 'Первый стабильный сервер'}
|
||||
/>
|
||||
</button>
|
||||
{selected && <ServerRow
|
||||
{selected && selected.id !== anchorServerId && <ServerRow
|
||||
server={selected}
|
||||
selected
|
||||
favorite={favorites.includes(selected.id)}
|
||||
|
||||
@@ -29,6 +29,7 @@ interface ServerPickerRenderState {
|
||||
disabled: boolean;
|
||||
leaving: boolean;
|
||||
revealVersion: number;
|
||||
anchorServerId: string;
|
||||
}
|
||||
|
||||
interface SubscriptionFeatureOptions {
|
||||
@@ -464,9 +465,11 @@ function ProfileGroup({
|
||||
|
||||
{localStatus && <div className="client-profile-local-status" role="status">{localStatus}</div>}
|
||||
|
||||
{!expanded && !closing && visibleServer && <button
|
||||
{visibleServer && <button
|
||||
className="client-profile-selected-server"
|
||||
type="button"
|
||||
aria-expanded={expanded}
|
||||
aria-controls={`client-profile-${profile.id}`}
|
||||
onClick={() => feature.toggleProfile(profile.id)}
|
||||
>
|
||||
<span aria-hidden="true" />
|
||||
@@ -486,6 +489,7 @@ function ProfileGroup({
|
||||
disabled: controlsBlocked,
|
||||
leaving: false,
|
||||
revealVersion: feature.revealVersions[profile.id] || 0,
|
||||
anchorServerId: visibleServer?.id || '',
|
||||
})}
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
min-height: 48px;
|
||||
display: grid;
|
||||
grid-template-columns: 18px minmax(0, 1fr);
|
||||
column-gap: 6px;
|
||||
align-items: center;
|
||||
padding: 4px 0 4px 8px;
|
||||
border: 0;
|
||||
@@ -278,15 +279,6 @@
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
animation: client-profile-selected-in 260ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
|
||||
@keyframes client-profile-selected-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
filter: blur(3px);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
}
|
||||
|
||||
.client-profile-selected-server > span {
|
||||
|
||||
@@ -131,7 +131,9 @@ test('server picker starts simple and reveals advanced controls on demand', () =
|
||||
assert.match(picker, /inert={!advanced \? true : undefined}/);
|
||||
assert.match(picker, /const SIMPLE_SERVER_LIMIT = 5/);
|
||||
assert.match(picker, /\.slice\(0, SIMPLE_SERVER_LIMIT\)/);
|
||||
assert.match(picker, /\{selected && <ServerRow/);
|
||||
assert.match(picker, /selected && selected\.id !== anchorServerId && <ServerRow/);
|
||||
assert.match(picker, /id !== selectedServerId && id !== anchorServerId/);
|
||||
assert.match(overview, /anchorServerId=\{pickerState\.anchorServerId\}/);
|
||||
assert.match(styles, /\.client-profile-body \.client-server-row \{[\s\S]*?grid-template-columns: minmax\(0, 1fr\) 64px;/);
|
||||
assert.match(styles, /\.client-profile-body \.client-server-row\.is-selected \{[\s\S]*?background: color-mix\(in oklch, var\(--client-accent\) 2%, transparent\);/);
|
||||
assert.match(styles, /\.client-profile-body \.client-server-check-label \{[\s\S]*?display: inline;/);
|
||||
|
||||
@@ -36,23 +36,23 @@ const expectedImports = [
|
||||
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
|
||||
const acceptedLedger = {
|
||||
counts: {
|
||||
cascadeEdges: 821,
|
||||
cascadeEdges: 820,
|
||||
customProperties: 31,
|
||||
declarations: 2986,
|
||||
declarations: 2983,
|
||||
important: 0,
|
||||
keyframes: 57,
|
||||
keyframes: 56,
|
||||
media: 13,
|
||||
rules: 921,
|
||||
rules: 920,
|
||||
variableReferences: 330,
|
||||
},
|
||||
hashes: {
|
||||
cascadeEdges: 'ac82341d26b6598c9e54a5cc2f20fd791baf0ab6a99c8bd09e838608a16567f7',
|
||||
cascadeEdges: '1590610791a755c9ec3c64da5b56da82764c9906344819fae414bf871bab1bad',
|
||||
customProperties: 'c7dd331e4bad898c450568999d8c9c6837e275a79c365c7680e143026fde4545',
|
||||
declarations: 'a7b2ecccc36118b0f77b100a48b2fdf8d8653587090a73b527b9565c7f5a88b8',
|
||||
declarations: '0bde16ab93623a2546daf42e83f037dc00d65dbf08da5851bf433d22697338b8',
|
||||
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
|
||||
duplicateSelectors: 'c0ec5c96e48cd2ceed32aa5050383bcf29c3c0b8f1b440ed6883435e3e9b964d',
|
||||
keyframes: '077e69647e0bd0761ca918e8d4109a9e8b07a1d7dbd9e0702bdca0e48011aed1',
|
||||
ruleDeclarationSequences: '36ea3201b45a89f3607e9336d30e2d0e5b88400f921847fa28263c2a2b8b75ac',
|
||||
keyframes: '39b6fc9c9ab451d63bb77afd90caf63d2d74a30cb351a8f86503de380d76a936',
|
||||
ruleDeclarationSequences: '883a3996d9d4ccd4ab6124237b6e0c00f17505dc8ff7314a8c0069fa828a2475',
|
||||
selectors: 'ec10ef7ce8f812c52c7ff6b6952ae66f5bbeebbc61e582ceddcb54c7112bbf2b',
|
||||
variableReferences: '8cb18a63664f73e6186e1730d3cd97c6e9f11bbe6b0f5cf04900948494a71f78',
|
||||
witnesses: '204a3ad7cb5b3681ea78a2e4ef1265515c33ca97ebfe560c082d51303cd3bedb',
|
||||
@@ -306,8 +306,8 @@ test('main owns one public stylesheet and the regrouped production CSS is determ
|
||||
assert.equal((main.match(/import ['"][^'"]+\.css['"]/g) || []).length, 1);
|
||||
|
||||
const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css'));
|
||||
assert.deepEqual(assets, ['index-CgoReV4i.css']);
|
||||
assert.deepEqual(assets, ['index-DgGVjCSB.css']);
|
||||
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
|
||||
assert.equal(built.byteLength, 112580);
|
||||
assert.equal(sha256(built), 'cdfeb513550fab679856b62c6ecb3d9bab8cbc464a0ffaa6c707701f7c2078ae');
|
||||
assert.equal(built.byteLength, 112427);
|
||||
assert.equal(sha256(built), '70e5b945a85ac965b31f43c25cf5766a3860453090678c44fbcdffa9951d624e');
|
||||
});
|
||||
|
||||
@@ -106,7 +106,8 @@ test('profiles render as flat accordion groups with scoped controls', () => {
|
||||
assert.match(feature, /aria-expanded=\{expanded\}/);
|
||||
assert.match(feature, /profile\.subscription\.status === 'stale'[\s\S]*profile\.subscription\.fetchedAt/);
|
||||
assert.match(feature, /feature\.operations\.profileRefresh\?\.target === profile\.id/);
|
||||
assert.match(feature, /!expanded && !closing && visibleServer && <button[\s\S]*client-profile-selected-server/);
|
||||
assert.match(feature, /\{visibleServer && <button[\s\S]*client-profile-selected-server[\s\S]*aria-expanded=\{expanded\}/);
|
||||
assert.match(feature, /anchorServerId: visibleServer\?\.id \|\| ''/);
|
||||
assert.match(feature, /const visibleServerId = applied \? feature\.selection\.appliedServerId : profile\.desiredServerId/);
|
||||
assert.match(feature, /const desired = !feature\.connected[\s\S]*&& !feature\.gatewayDirect/);
|
||||
assert.match(feature, /const profileDomain = subscriptionDomain\(profile\.subscription\.host\)/);
|
||||
|
||||
Reference in New Issue
Block a user