Skip to content

Commit 166d7e0

Browse files
committed
New /my-liked-packages page (behind experimental).
1 parent 5cd7efd commit 166d7e0

File tree

7 files changed

+104
-17
lines changed

7 files changed

+104
-17
lines changed

app/lib/frontend/handlers/account.dart

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:_pub_shared/data/account_api.dart';
6+
import 'package:_pub_shared/search/search_form.dart';
7+
import 'package:_pub_shared/search/tags.dart';
68
import 'package:clock/clock.dart';
79
import 'package:pub_dev/frontend/handlers/cache_control.dart';
10+
import 'package:pub_dev/package/search_adapter.dart';
811
import 'package:shelf/shelf.dart' as shelf;
912

1013
import '../../account/backend.dart';
@@ -311,7 +314,7 @@ Future<shelf.Response> accountPackagesPageHandler(shelf.Request request) async {
311314
return htmlResponse(html);
312315
}
313316

314-
/// Handles requests for GET my-liked-packages
317+
/// Handles requests for GET /my-liked-packages
315318
Future<shelf.Response> accountMyLikedPackagesPageHandler(
316319
shelf.Request request,
317320
) async {
@@ -323,11 +326,49 @@ Future<shelf.Response> accountMyLikedPackagesPageHandler(
323326
final user = (await accountBackend.lookupUserById(
324327
requestContext.authenticatedUserId!,
325328
))!;
329+
330+
// Redirect in case of empty search query.
331+
if (request.requestedUri.query == 'q=') {
332+
return redirectResponse(request.requestedUri.path);
333+
}
334+
335+
if (requestContext.experimentalFlags.useMyLikedSearch) {
336+
// redirect to the search page when any search or pagination is present
337+
final searchForm = SearchForm.parse(request.requestedUri.queryParameters);
338+
if (searchForm.isNotEmpty) {
339+
final redirectForm = searchForm.addRequiredTagIfAbsent(
340+
AccountTag.isLikedByMe,
341+
);
342+
return redirectResponse(
343+
redirectForm.toSearchLink(page: searchForm.currentPage),
344+
);
345+
}
346+
347+
final appliedSearchForm = SearchForm().toggleRequiredTag(
348+
AccountTag.isLikedByMe,
349+
);
350+
final searchResult = await searchAdapter.search(
351+
appliedSearchForm,
352+
// Do not apply rate limit here.
353+
rateLimitKey: null,
354+
);
355+
final html = renderMyLikedPackagesPage(
356+
user: user,
357+
userSessionData: requestContext.sessionData!,
358+
likes: null,
359+
searchForm: appliedSearchForm,
360+
searchResult: searchResult,
361+
);
362+
return htmlResponse(html);
363+
}
364+
326365
final likes = await likeBackend.listPackageLikes(user.userId);
327366
final html = renderMyLikedPackagesPage(
328367
user: user,
329368
userSessionData: requestContext.sessionData!,
330369
likes: likes,
370+
searchForm: null,
371+
searchResult: null,
331372
);
332373
return htmlResponse(html);
333374
}

app/lib/frontend/handlers/experimental.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ typedef PublicFlag = ({String name, String description});
1010

1111
const _publicFlags = <PublicFlag>{
1212
(name: 'example', description: 'Short description'),
13+
(
14+
name: 'my-liked-search',
15+
description: 'New "My liked packages" page and search.',
16+
),
1317
};
1418

1519
final _allFlags = <String>{
1620
'dark-as-default',
17-
'my-liked-search',
1821
..._publicFlags.map((x) => x.name),
1922
};
2023

app/lib/frontend/templates/admin.dart

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:_pub_shared/data/page_data.dart';
6+
import 'package:_pub_shared/search/search_form.dart';
7+
import 'package:pub_dev/frontend/templates/listing.dart';
8+
import 'package:pub_dev/package/search_adapter.dart';
69

710
import '../../account/models.dart' show LikeData, User, SessionData;
811
import '../../audit/models.dart';
@@ -97,16 +100,38 @@ String renderAccountPackagesPage({
97100
String renderMyLikedPackagesPage({
98101
required User user,
99102
required SessionData userSessionData,
100-
required List<LikeData> likes,
103+
required List<LikeData>? likes,
104+
required SearchForm? searchForm,
105+
required SearchResultPage? searchResult,
101106
}) {
102-
final resultCount = likes.isNotEmpty
103-
? d.p(
104-
text:
105-
'You like ${likes.length} ${likes.length == 1 ? 'package' : 'packages'}.',
106-
)
107-
: d.p(text: 'You have not liked any packages yet.');
108-
109-
final tabContent = d.fragment([resultCount, likedPackageListNode(likes)]);
107+
late d.Node tabContent;
108+
if (likes != null) {
109+
final resultCount = likes.isNotEmpty
110+
? d.p(
111+
text:
112+
'You like ${likes.length} ${likes.length == 1 ? 'package' : 'packages'}.',
113+
)
114+
: d.p(text: 'You have not liked any packages yet.');
115+
116+
tabContent = d.fragment([resultCount, likedPackageListNode(likes)]);
117+
} else {
118+
final infoNode = listingInfo(
119+
searchForm: searchForm!,
120+
totalCount: searchResult!.totalCount,
121+
title: 'My liked packages',
122+
messageFromBackend: searchResult.errorMessage,
123+
);
124+
final listNode = packageList(searchResult);
125+
final pagination = searchResult.hasHit
126+
? paginationNode(PageLinks(searchForm, searchResult.totalCount))
127+
: null;
128+
tabContent = d.fragment([
129+
infoNode,
130+
listNode,
131+
if (pagination != null) pagination,
132+
]);
133+
}
134+
110135
final content = renderDetailPage(
111136
headerNode: _accountDetailHeader(user, userSessionData),
112137
tabs: [
@@ -129,6 +154,7 @@ String renderMyLikedPackagesPage({
129154
noIndex: true,
130155
mainClasses: [wideHeaderDetailPageClassName],
131156
pageData: PageData(sessionAware: true),
157+
searchForm: searchForm,
132158
);
133159
}
134160

app/lib/frontend/templates/layout.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ enum PageType {
3535

3636
/// Whether to show a wide/tall search banner at the top of the page,
3737
/// otherwise only show a top-navigation search input.
38-
bool showSearchBanner(PageType type) =>
39-
type != PageType.account &&
38+
bool showSearchBanner(PageType type, SearchForm? searchForm) =>
39+
(type != PageType.account || searchForm != null) &&
4040
type != PageType.package &&
4141
type != PageType.standalone;
4242

@@ -99,7 +99,11 @@ String renderLayoutPage(
9999
? null
100100
: pageDataJsonCodec.encode(pageData.toJson()),
101101
bodyClasses: bodyClasses,
102-
siteHeader: siteHeaderNode(pageType: type, userSession: session),
102+
siteHeader: siteHeaderNode(
103+
pageType: type,
104+
userSession: session,
105+
searchForm: searchForm,
106+
),
103107
announcementBanner: announcementBannerHtml == null
104108
? null
105109
: d.unsafeRawHtml(announcementBannerHtml),
@@ -108,7 +112,7 @@ String renderLayoutPage(
108112
: hex
109113
.encode(sha1.convert(utf8.encode(announcementBannerHtml)).bytes)
110114
.substring(0, 16),
111-
searchBanner: showSearchBanner(type)
115+
searchBanner: showSearchBanner(type, searchForm)
112116
? _renderSearchBanner(type: type, searchForm: searchForm)
113117
: null,
114118
isLanding: type == PageType.landing,

app/lib/frontend/templates/views/shared/site_header.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:_pub_shared/search/search_form.dart';
6+
57
import '../../../../account/models.dart' show SessionData;
68
import '../../../../shared/urls.dart' as urls;
79
import '../../../dom/dom.dart' as d;
@@ -10,7 +12,11 @@ import '../../_consts.dart';
1012
import '../../layout.dart' show PageType, showSearchBanner;
1113

1214
/// Creates the site header and navigation node.
13-
d.Node siteHeaderNode({required PageType pageType, SessionData? userSession}) {
15+
d.Node siteHeaderNode({
16+
required PageType pageType,
17+
required SessionData? userSession,
18+
required SearchForm? searchForm,
19+
}) {
1420
return d.div(
1521
classes: ['site-header'],
1622
children: [
@@ -31,7 +37,7 @@ d.Node siteHeaderNode({required PageType pageType, SessionData? userSession}) {
3137
),
3238
d.div(classes: ['site-header-space']),
3339
d.div(classes: ['site-header-mask']),
34-
if (!showSearchBanner(pageType))
40+
if (!showSearchBanner(pageType, searchForm))
3541
d.div(
3642
classes: ['site-header-search'],
3743
child: d.form(

app/test/frontend/templates_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,8 @@ void main() {
880880
LikeData(package: 'super_package', created: liked1),
881881
LikeData(package: 'another_package', created: liked2),
882882
],
883+
searchForm: null,
884+
searchResult: null,
883885
);
884886
expectGoldenFile(
885887
html,

pkg/pub_integration/test/like_test.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ void main() {
8888
await Future.delayed(Duration(milliseconds: 200));
8989
expect(await getCountLabels(), ['1', '1', '']);
9090

91+
// checking /my-liked-packages - with the one liked package
92+
await page.gotoOrigin('/my-liked-packages');
93+
final info = await listingPageInfo(page);
94+
expect(info.packageNames.toSet(), {'test_pkg'});
95+
9196
// checking search with my-liked packages - with the one liked package
9297
await page.gotoOrigin('/packages?q=pkg+is:liked-by-me');
9398
final info2 = await listingPageInfo(page);

0 commit comments

Comments
 (0)