diff --git a/app/lib/frontend/templates/_utils.dart b/app/lib/frontend/templates/_utils.dart index d36e71d8d..d8aa88207 100644 --- a/app/lib/frontend/templates/_utils.dart +++ b/app/lib/frontend/templates/_utils.dart @@ -5,6 +5,7 @@ import '../../package/models.dart' show PackageVersionAsset; import '../../shared/markdown.dart'; import '../dom/dom.dart' as d; +import '../static_files.dart'; /// Renders a file content (e.g. markdown, dart source file) into HTML. d.Node renderFile( @@ -44,3 +45,35 @@ d.Node _renderDartCode(String text) => d.Node _renderPlainText(String text) => d.div(classes: ['highlight'], child: d.pre(text: text)); + +d.Node foldableSection({ + required d.Node title, + required Iterable children, + Iterable? buttonDivClasses, +}) { + return d.div( + classes: ['foldable-section', 'foldable'], + children: [ + d.div( + classes: ['foldable-button', ...?buttonDivClasses], + children: [ + d.img( + classes: ['foldable-icon'], + image: d.Image( + src: staticUrls + .getAssetUrl('/static/img/foldable-section-icon.svg'), + alt: 'trigger folding of the section', + width: 13, + height: 6, + ), + ), + title, + ], + ), + d.div( + classes: ['foldable-content'], + children: children, + ), + ], + ); +} diff --git a/app/lib/frontend/templates/report.dart b/app/lib/frontend/templates/report.dart index 5470f459d..0578c52ba 100644 --- a/app/lib/frontend/templates/report.dart +++ b/app/lib/frontend/templates/report.dart @@ -2,12 +2,11 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:pub_dev/frontend/static_files.dart'; - import '../../account/models.dart'; import '../../admin/models.dart'; import '../dom/dom.dart' as d; import '../dom/material.dart' as material; +import '_utils.dart'; import 'layout.dart'; const _subjectKindLabels = { @@ -93,7 +92,7 @@ Iterable _report( d.p(text: ''), // illegal content if (subject.isPackage) - _foldableSection( + foldableSection( title: d.text('I believe the package contains illegal content.'), children: [ d.markdown('Please report illegal content through the ' @@ -101,7 +100,7 @@ Iterable _report( ], ) else if (subject.isPublisher) - _foldableSection( + foldableSection( title: d.text('I believe the publisher contains illegal content.'), children: [ d.markdown('Please report illegal content through the ' @@ -111,7 +110,7 @@ Iterable _report( // contact if (subject.isPackage) - _foldableSection( + foldableSection( title: d.text( 'I have found a bug in the package / I need help using the package.'), children: [ @@ -131,7 +130,7 @@ Iterable _report( ], ) else if (subject.isPublisher) - _foldableSection( + foldableSection( title: d.text('I want to contact the publisher.'), children: [ d.markdown('Please consult the publisher page: ' @@ -143,7 +142,7 @@ Iterable _report( ), // direct report - _foldableSection( + foldableSection( buttonDivClasses: ['report-page-direct-report'], title: d.text('I believe the $kindLabel violates pub.dev/policy.'), children: [ @@ -176,7 +175,7 @@ Iterable _report( ), // problem with pub.dev - _foldableSection( + foldableSection( title: d.text('I have a problem with the pub.dev website.'), children: [ d.markdown('Security vulnerabilities may be reported through ' @@ -234,35 +233,3 @@ Iterable _appeal( ), ]; } - -d.Node _foldableSection({ - required d.Node title, - required Iterable children, - Iterable? buttonDivClasses, -}) { - return d.div( - classes: ['foldable-section', 'foldable'], - children: [ - d.div( - classes: ['foldable-button', ...?buttonDivClasses], - children: [ - d.img( - classes: ['foldable-icon'], - image: d.Image( - src: staticUrls - .getAssetUrl('/static/img/foldable-section-icon.svg'), - alt: 'trigger folding of the section', - width: 13, - height: 6, - ), - ), - title, - ], - ), - d.div( - classes: ['foldable-content'], - children: children, - ), - ], - ); -} diff --git a/app/lib/frontend/templates/views/pkg/admin_page.dart b/app/lib/frontend/templates/views/pkg/admin_page.dart index bc332a450..b505c704f 100644 --- a/app/lib/frontend/templates/views/pkg/admin_page.dart +++ b/app/lib/frontend/templates/views/pkg/admin_page.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:_pub_shared/data/package_api.dart'; +import 'package:pub_dev/frontend/templates/_utils.dart'; import '../../../../account/models.dart'; import '../../../../package/models.dart'; @@ -20,220 +21,233 @@ d.Node packageAdminPageNode({ }) { final pkgHasPublisher = package.publisherId != null; return d.fragment([ - d.h2(text: 'Package ownership'), - d.div(children: [ - if (!pkgHasPublisher) ...[ - d.markdown( - 'You can transfer this package to a verified publisher if you are a member of the publisher. ' - 'Transferring the package removes the current uploaders, so that only the members of the publisher can upload new versions.'), - d.markdown( - '**Upgrading to verified publishers is an irreversible operation.** ' - 'Packages can be transferred between publishers, but they can\'t be converted back to legacy uploader ownership.'), - ], - if (pkgHasPublisher) - d.markdown( - 'You can transfer your package to a **different publisher** if you’re also a member of the publisher.'), - if (userPublishers.isNotEmpty) ...[ - material.dropdown( - id: '-admin-set-publisher-input', - label: 'Select a publisher', - classes: ['-admin-dropdown'], - options: [ - if (!pkgHasPublisher) - material.option( - value: '', text: '', disabled: true, selected: true), - ...userPublishers.map( - (p) => material.option( - value: p, text: p, selected: p == package.publisherId), - ), + foldableSection( + title: d.h2(text: 'Package ownership'), + children: [ + d.div(children: [ + if (!pkgHasPublisher) ...[ + d.markdown( + 'You can transfer this package to a verified publisher if you are a member of the publisher. ' + 'Transferring the package removes the current uploaders, so that only the members of the publisher can upload new versions.'), + d.markdown( + '**Upgrading to verified publishers is an irreversible operation.** ' + 'Packages can be transferred between publishers, but they can\'t be converted back to legacy uploader ownership.'), ], - ), - d.p( - child: material.button( - id: '-admin-set-publisher-button', - classes: ['pub-button-danger'], - raised: true, - label: 'Transfer to publisher', - ), - ), - ], - if (userPublishers.isEmpty) - d.markdown('You have no verified publisher. ' - 'Use the [create publisher](${urls.createPublisherUrl()}) page to create one.'), - if (!pkgHasPublisher) ...[ - d.h3(text: 'Uploaders'), - if (uploaderUsers.length == 1) - d.p( - text: - 'There is only a single uploader. Consider adding more uploaders to protect against losing control of the package.', - classes: ['warning'], - ), - material.dataTable( - id: '-pkg-admin-uploaders-table', - ariaLabel: 'Uploaders of package', - columns: [ - material.DataTableColumn( - headerContent: d.text('Email'), - headerClasses: ['email-header'], - renderCell: (u) => d.text(u.email!), + if (pkgHasPublisher) + d.markdown( + 'You can transfer your package to a **different publisher** if you’re also a member of the publisher.'), + if (userPublishers.isNotEmpty) ...[ + material.dropdown( + id: '-admin-set-publisher-input', + label: 'Select a publisher', + classes: ['-admin-dropdown'], + options: [ + if (!pkgHasPublisher) + material.option( + value: '', text: '', disabled: true, selected: true), + ...userPublishers.map( + (p) => material.option( + value: p, text: p, selected: p == package.publisherId), + ), + ], ), - material.DataTableColumn( - headerContent: d.text(''), - headerClasses: ['icons-header'], - renderCell: (u) => d.a( - classes: ['-pub-remove-uploader-button'], - title: 'Remove uploader', - attributes: {'data-email': u.email!}, - text: '×', + d.p( + child: material.button( + id: '-admin-set-publisher-button', + classes: ['pub-button-danger'], + raised: true, + label: 'Transfer to publisher', ), ), ], - entries: uploaderUsers, - ), - d.p( - child: material.button( - id: '-pkg-admin-invite-uploader-button', - label: 'Invite uploader', - raised: true, - ), - ), - d.div( - id: '-pkg-admin-invite-uploader-content', - classes: ['modal-content-hidden'], - children: [ + if (userPublishers.isEmpty) + d.markdown('You have no verified publisher. ' + 'Use the [create publisher](${urls.createPublisherUrl()}) page to create one.'), + if (!pkgHasPublisher) ...[ + d.h3(text: 'Uploaders'), + if (uploaderUsers.length == 1) + d.p( + text: + 'There is only a single uploader. Consider adding more uploaders to protect against losing control of the package.', + classes: ['warning'], + ), + material.dataTable( + id: '-pkg-admin-uploaders-table', + ariaLabel: 'Uploaders of package', + columns: [ + material.DataTableColumn( + headerContent: d.text('Email'), + headerClasses: ['email-header'], + renderCell: (u) => d.text(u.email!), + ), + material.DataTableColumn( + headerContent: d.text(''), + headerClasses: ['icons-header'], + renderCell: (u) => d.a( + classes: ['-pub-remove-uploader-button'], + title: 'Remove uploader', + attributes: {'data-email': u.email!}, + text: '×', + ), + ), + ], + entries: uploaderUsers, + ), d.p( - text: 'You can invite new uploader to this package. ' - 'Once new uploaders accept the invitation, they have full administrative rights, ' - 'with the following abilities:'), - d.ul(children: [ - d.li(text: 'Transfer this package to a publisher'), - d.li(text: 'Upload new versions of this package'), - d.li(text: 'Invite and remove uploaders of this package'), - ]), - d.div( - classes: ['-pub-form-textfield-row'], - child: material.textField( - id: '-pkg-admin-invite-uploader-input', - label: 'Email address', + child: material.button( + id: '-pkg-admin-invite-uploader-button', + label: 'Invite uploader', + raised: true, ), ), + d.div( + id: '-pkg-admin-invite-uploader-content', + classes: ['modal-content-hidden'], + children: [ + d.p( + text: 'You can invite new uploader to this package. ' + 'Once new uploaders accept the invitation, they have full administrative rights, ' + 'with the following abilities:'), + d.ul(children: [ + d.li(text: 'Transfer this package to a publisher'), + d.li(text: 'Upload new versions of this package'), + d.li(text: 'Invite and remove uploaders of this package'), + ]), + d.div( + classes: ['-pub-form-textfield-row'], + child: material.textField( + id: '-pkg-admin-invite-uploader-input', + label: 'Email address', + ), + ), + ], + ), ], - ), + ]), ], - ]), - d.h2(text: 'Package Options'), - d.h3(text: 'Discontinued'), - d.markdown( - 'A package can be marked as [discontinued](https://dart.dev/tools/pub/publishing#discontinue) ' - 'to inform users that the package is no longer maintained. ' - '*Discontinued packages* remain available to package users, but they don\'t appear ' - 'in search results on pub.dev unless the user specifies advanced search options.'), - d.div( - classes: ['-pub-form-checkbox-row'], - child: material.checkbox( - id: '-admin-is-discontinued-checkbox', - label: 'Mark "discontinued"', - checked: package.isDiscontinued, - ), ), - if (package.isDiscontinued) ...[ - d.h3(text: 'Suggested replacement'), - d.markdown('When a package is *discontinued* the author may designate a ' - '*suggested replacement package*. Package users will be suggested ' - 'to consider using the designated replacement package.'), - d.p( - text: 'Designating a replacement package is optional, ' - 'and only serves to guide existing package users.'), - d.div( - classes: ['-pub-form-textfield-row'], - children: [ - material.textField( - id: '-package-replaced-by', - label: null, - value: package.replacedBy, + foldableSection( + title: d.h2(text: 'Package Options'), + children: [ + d.h3(text: 'Discontinued'), + d.markdown( + 'A package can be marked as [discontinued](https://dart.dev/tools/pub/publishing#discontinue) ' + 'to inform users that the package is no longer maintained. ' + '*Discontinued packages* remain available to package users, but they don\'t appear ' + 'in search results on pub.dev unless the user specifies advanced search options.'), + d.div( + classes: ['-pub-form-checkbox-row'], + child: material.checkbox( + id: '-admin-is-discontinued-checkbox', + label: 'Mark "discontinued"', + checked: package.isDiscontinued, ), - material.button( - id: '-package-replaced-by-button', - label: 'Update "suggested replacement"', - raised: true, - ) - ], - ), - ], - if (!package.isDiscontinued) ...[ - d.h3(text: 'Unlisted'), - d.markdown( - 'A package that\'s marked as *unlisted* doesn\'t normally appear in search results on pub.dev. ' - 'Unlisted packages remain publicly available, and users can search for them using advanced search options.'), - d.div( - classes: ['-pub-form-checkbox-row'], - child: material.checkbox( - id: '-admin-is-unlisted-checkbox', - label: 'Mark "unlisted"', - checked: package.isUnlisted, ), - ), - ], - _automatedPublishing(package), - d.h2(text: 'Package Version Retraction'), - d.div(children: [ - d.markdown( - 'You can [retract](https://dart.dev/go/package-retraction) a package version up to 7 days after publication.'), - d.markdown( - 'This will not remove the package version, but warn developers using' - ' it and stop new applications from taking dependency on it without a dependency override.'), - d.markdown( - 'You can restore a retracted package version if the version was retracted within the last 7 days.'), - d.h3(text: 'Retract Package Version'), - if (retractableVersions.isNotEmpty) ...[ - material.dropdown( - id: '-admin-retract-package-version-input', - label: 'Select a version', - classes: ['-admin-dropdown'], - options: [ - ...retractableVersions.map( - (v) => material.option(value: v, text: v), + if (package.isDiscontinued) ...[ + d.h3(text: 'Suggested replacement'), + d.markdown( + 'When a package is *discontinued* the author may designate a ' + '*suggested replacement package*. Package users will be suggested ' + 'to consider using the designated replacement package.'), + d.p( + text: 'Designating a replacement package is optional, ' + 'and only serves to guide existing package users.'), + d.div( + classes: ['-pub-form-textfield-row'], + children: [ + material.textField( + id: '-package-replaced-by', + label: null, + value: package.replacedBy, + ), + material.button( + id: '-package-replaced-by-button', + label: 'Update "suggested replacement"', + raised: true, + ) + ], + ), + ], + if (!package.isDiscontinued) ...[ + d.h3(text: 'Unlisted'), + d.markdown( + 'A package that\'s marked as *unlisted* doesn\'t normally appear in search results on pub.dev. ' + 'Unlisted packages remain publicly available, and users can search for them using advanced search options.'), + d.div( + classes: ['-pub-form-checkbox-row'], + child: material.checkbox( + id: '-admin-is-unlisted-checkbox', + label: 'Mark "unlisted"', + checked: package.isUnlisted, ), - ], - ), - d.p( - child: material.button( - id: '-admin-retract-package-version-button', - classes: ['pub-button-danger'], - raised: true, - label: 'Retract Package Version', ), - ) + ], ], - if (retractableVersions.isEmpty) - d.markdown('This package has no retractable versions.'), - ]), - d.h3(text: 'Restore Retracted Package Version'), - d.div(children: [ - if (retractedVersions.isNotEmpty) ...[ - material.dropdown( - id: '-admin-restore-retract-package-version-input', - label: 'Select a version', - classes: ['-admin-dropdown'], - options: [ - ...retractedVersions.map( - (v) => material.option(value: v, text: v), + ), + _automatedPublishing(package), + foldableSection( + title: d.h2(text: 'Package Version Retraction'), + children: [ + d.div(children: [ + d.markdown( + 'You can [retract](https://dart.dev/go/package-retraction) a package version up to 7 days after publication.'), + d.markdown( + 'This will not remove the package version, but warn developers using' + ' it and stop new applications from taking dependency on it without a dependency override.'), + d.markdown( + 'You can restore a retracted package version if the version was retracted within the last 7 days.'), + d.h3(text: 'Retract Package Version'), + if (retractableVersions.isNotEmpty) ...[ + material.dropdown( + id: '-admin-retract-package-version-input', + label: 'Select a version', + classes: ['-admin-dropdown'], + options: [ + ...retractableVersions.map( + (v) => material.option(value: v, text: v), + ), + ], ), + d.p( + child: material.button( + id: '-admin-retract-package-version-button', + classes: ['pub-button-danger'], + raised: true, + label: 'Retract Package Version', + ), + ) ], - ), - d.p( - child: material.button( - id: '-admin-restore-retract-package-version-button', - classes: ['pub-button-danger'], - raised: true, - label: 'Restore Retraced Package Version', - ), - ), + if (retractableVersions.isEmpty) + d.markdown('This package has no retractable versions.'), + ]), + d.h3(text: 'Restore Retracted Package Version'), + d.div(children: [ + if (retractedVersions.isNotEmpty) ...[ + material.dropdown( + id: '-admin-restore-retract-package-version-input', + label: 'Select a version', + classes: ['-admin-dropdown'], + options: [ + ...retractedVersions.map( + (v) => material.option(value: v, text: v), + ), + ], + ), + d.p( + child: material.button( + id: '-admin-restore-retract-package-version-button', + classes: ['pub-button-danger'], + raised: true, + label: 'Restore Retraced Package Version', + ), + ), + ], + if (retractedVersions.isEmpty) + d.markdown( + 'This package has no retracted versions that can be restored.'), + ]), ], - if (retractedVersions.isEmpty) - d.markdown( - 'This package has no retracted versions that can be restored.'), - ]), + ), ]); } @@ -241,148 +255,150 @@ d.Node _automatedPublishing(Package package) { final github = package.automatedPublishing?.githubConfig; final gcp = package.automatedPublishing?.gcpConfig; final isGithubEnabled = github?.isEnabled ?? false; - return d.fragment([ - d.h2(text: 'Automated publishing'), - d.h3(text: 'Publishing from GitHub Actions'), - d.div( - classes: [ - '-pub-form-checkbox-row', - '-pub-form-checkbox-toggle-next-sibling', - ], - child: material.checkbox( - id: '-pkg-admin-automated-github-enabled', - label: 'Enable publishing from GitHub Actions', - checked: isGithubEnabled, + return foldableSection( + title: d.h2(text: 'Automated publishing'), + children: [ + d.h3(text: 'Publishing from GitHub Actions'), + d.div( + classes: [ + '-pub-form-checkbox-row', + '-pub-form-checkbox-toggle-next-sibling', + ], + child: material.checkbox( + id: '-pkg-admin-automated-github-enabled', + label: 'Enable publishing from GitHub Actions', + checked: isGithubEnabled, + ), ), - ), - d.div( - classes: [ - '-pub-form-checkbox-indent', - if (!isGithubEnabled) '-pub-form-block-hidden', - ], - children: [ - d.div( - classes: ['-pub-form-textfield-row'], - child: material.textField( - id: '-pkg-admin-automated-github-repository', - label: 'Repository (/)', - value: github?.repository, + d.div( + classes: [ + '-pub-form-checkbox-indent', + if (!isGithubEnabled) '-pub-form-block-hidden', + ], + children: [ + d.div( + classes: ['-pub-form-textfield-row'], + child: material.textField( + id: '-pkg-admin-automated-github-repository', + label: 'Repository (/)', + value: github?.repository, + ), ), - ), - d.div( - classes: ['-pub-form-textfield-row'], - children: [ - material.textField( - id: '-pkg-admin-automated-github-tagpattern', - label: 'Tag pattern', - value: github?.tagPattern ?? 'v{{version}}', + d.div( + classes: ['-pub-form-textfield-row'], + children: [ + material.textField( + id: '-pkg-admin-automated-github-tagpattern', + label: 'Tag pattern', + value: github?.tagPattern ?? 'v{{version}}', + ), + d.markdown( + '`{{version}}` will be substituted for the version number of the package. ' + 'For example, for tags like `v1.2.3` use `v{{version}}`, ' + 'and for `mypackage-1.2.3` use `mypackage-{{version}}`.'), + ], + ), + // events + d.div( + classes: [ + '-pub-form-checkbox-row', + ], + child: material.checkbox( + id: '-pkg-admin-automated-github-push-events', + label: 'Enable publishing from `push` events', + checked: github?.isPushEventEnabled ?? true, + labelNodeContent: (_) => d.fragment([ + d.text('Enable publishing from '), + d.code(text: 'push'), + d.text(' events'), + ]), ), - d.markdown( - '`{{version}}` will be substituted for the version number of the package. ' - 'For example, for tags like `v1.2.3` use `v{{version}}`, ' - 'and for `mypackage-1.2.3` use `mypackage-{{version}}`.'), - ], - ), - // events - d.div( - classes: [ - '-pub-form-checkbox-row', - ], - child: material.checkbox( - id: '-pkg-admin-automated-github-push-events', - label: 'Enable publishing from `push` events', - checked: github?.isPushEventEnabled ?? true, - labelNodeContent: (_) => d.fragment([ - d.text('Enable publishing from '), - d.code(text: 'push'), - d.text(' events'), - ]), ), - ), - d.div( - classes: [ - '-pub-form-checkbox-row', - ], - child: material.checkbox( - id: '-pkg-admin-automated-github-workflowdispatch-events', - label: 'Enable publishing from `workflow_dispatch` events', - checked: github?.isWorkflowDispatchEventEnabled ?? false, - labelNodeContent: (_) => d.fragment([ - d.text('Enable publishing from '), - d.code(text: 'workflow_dispatch'), - d.text(' events'), - ]), + d.div( + classes: [ + '-pub-form-checkbox-row', + ], + child: material.checkbox( + id: '-pkg-admin-automated-github-workflowdispatch-events', + label: 'Enable publishing from `workflow_dispatch` events', + checked: github?.isWorkflowDispatchEventEnabled ?? false, + labelNodeContent: (_) => d.fragment([ + d.text('Enable publishing from '), + d.code(text: 'workflow_dispatch'), + d.text(' events'), + ]), + ), ), - ), - // enviroment - d.div( - classes: [ - '-pub-form-checkbox-row', - '-pub-form-checkbox-toggle-next-sibling', - ], - child: material.checkbox( - id: '-pkg-admin-automated-github-requireenv', - label: 'Require GitHub Actions environment', - checked: github?.requireEnvironment ?? false, + // enviroment + d.div( + classes: [ + '-pub-form-checkbox-row', + '-pub-form-checkbox-toggle-next-sibling', + ], + child: material.checkbox( + id: '-pkg-admin-automated-github-requireenv', + label: 'Require GitHub Actions environment', + checked: github?.requireEnvironment ?? false, + ), ), - ), - d.div( - classes: [ - '-pub-form-checkbox-indent', - '-pub-form-textfield-row', - if (!(github?.requireEnvironment ?? false)) - '-pub-form-block-hidden', - ], - child: material.textField( - id: '-pkg-admin-automated-github-environment', - label: 'Environment', - value: github?.environment, + d.div( + classes: [ + '-pub-form-checkbox-indent', + '-pub-form-textfield-row', + if (!(github?.requireEnvironment ?? false)) + '-pub-form-block-hidden', + ], + child: material.textField( + id: '-pkg-admin-automated-github-environment', + label: 'Environment', + value: github?.environment, + ), ), + if (isGithubEnabled) _exampleGithubWorkflow(github!), + ], + ), + d.h3(text: 'Publishing with Google Cloud Service account'), + d.markdown( + 'When publishing with a GCP _service account_ is enabled, the service account configured here ' + 'will be able to create temporary tokens that allows publishing of this package. ' + 'To learn more about publishing using a _service account_, see ' + '[dart.dev/go/automated-publishing](https://dart.dev/go/automated-publishing)'), + d.div( + classes: [ + '-pub-form-checkbox-row', + '-pub-form-checkbox-toggle-next-sibling', + ], + child: material.checkbox( + id: '-pkg-admin-automated-gcp-enabled', + label: 'Enable publishing with Google Cloud Service account', + checked: gcp?.isEnabled ?? false, ), - if (isGithubEnabled) _exampleGithubWorkflow(github!), - ], - ), - d.h3(text: 'Publishing with Google Cloud Service account'), - d.markdown( - 'When publishing with a GCP _service account_ is enabled, the service account configured here ' - 'will be able to create temporary tokens that allows publishing of this package. ' - 'To learn more about publishing using a _service account_, see ' - '[dart.dev/go/automated-publishing](https://dart.dev/go/automated-publishing)'), - d.div( - classes: [ - '-pub-form-checkbox-row', - '-pub-form-checkbox-toggle-next-sibling', - ], - child: material.checkbox( - id: '-pkg-admin-automated-gcp-enabled', - label: 'Enable publishing with Google Cloud Service account', - checked: gcp?.isEnabled ?? false, ), - ), - d.div( - classes: [ - '-pub-form-checkbox-indent', - if (!(gcp?.isEnabled ?? false)) '-pub-form-block-hidden', - ], - children: [ - d.div( - classes: ['-pub-form-textfield-row'], - child: material.textField( - id: '-pkg-admin-automated-gcp-serviceaccountemail', - label: 'Service account email', - value: gcp?.serviceAccountEmail, + d.div( + classes: [ + '-pub-form-checkbox-indent', + if (!(gcp?.isEnabled ?? false)) '-pub-form-block-hidden', + ], + children: [ + d.div( + classes: ['-pub-form-textfield-row'], + child: material.textField( + id: '-pkg-admin-automated-gcp-serviceaccountemail', + label: 'Service account email', + value: gcp?.serviceAccountEmail, + ), ), + ], + ), + d.p( + child: material.button( + id: '-pkg-admin-automated-button', + label: 'Update', + raised: true, ), - ], - ), - d.p( - child: material.button( - id: '-pkg-admin-automated-button', - label: 'Update', - raised: true, ), - ), - ]); + ], + ); } d.Node _exampleGithubWorkflow(GithubPublishingConfig github) { diff --git a/app/lib/frontend/templates/views/publisher/admin_page.dart b/app/lib/frontend/templates/views/publisher/admin_page.dart index d8a9b2ff7..112e2d31d 100644 --- a/app/lib/frontend/templates/views/publisher/admin_page.dart +++ b/app/lib/frontend/templates/views/publisher/admin_page.dart @@ -7,6 +7,7 @@ import 'package:_pub_shared/data/publisher_api.dart' as api; import '../../../../publisher/models.dart'; import '../../../dom/dom.dart' as d; import '../../../dom/material.dart' as material; +import '../../_utils.dart'; /// Creates the publisher admin page content. d.Node publisherAdminPageNode({ @@ -14,110 +15,118 @@ d.Node publisherAdminPageNode({ required List members, }) { return d.fragment([ - d.h2(text: 'Publisher information'), - d.div( - classes: ['-pub-form-textfield-row'], - child: material.textArea( - id: '-publisher-description', - label: 'Description', - rows: 5, - cols: 60, - value: publisher.description, - ), - ), - d.div( - classes: ['-pub-form-textfield-row'], - child: material.textField( - id: '-publisher-website-url', - label: 'Website', - value: publisher.websiteUrl, - ), - ), - d.div( - classes: ['-pub-form-textfield-row'], - child: material.textField( - id: '-publisher-contact-email', - label: 'Contact email', - value: publisher.contactEmail, - ), - ), - d.div( - classes: ['-pub-form-right-aligned'], - child: material.button( - id: '-publisher-update-button', - label: 'Update', - raised: true, - ), - ), - d.h2(text: 'Members'), - if (members.length == 1) - d.p( - text: 'This publisher only has a single member. ' - 'Consider adding more members to protect against losing control of the publisher.', - classes: ['warning'], - ), - material.dataTable( - id: '-pub-publisher-admin-members-table', - ariaLabel: 'Members of publisher', - columns: [ - material.DataTableColumn( - headerContent: d.text('Email'), - headerClasses: ['email-header'], - renderCell: (api.PublisherMember m) => d.text(m.email), + foldableSection( + title: d.h2(text: 'Publisher information'), + children: [ + d.div( + classes: ['-pub-form-textfield-row'], + child: material.textArea( + id: '-publisher-description', + label: 'Description', + rows: 5, + cols: 60, + value: publisher.description, + ), ), - material.DataTableColumn( - headerContent: d.text('Role'), - headerClasses: ['role-header'], - renderCell: (api.PublisherMember m) => d.text(m.role), + d.div( + classes: ['-pub-form-textfield-row'], + child: material.textField( + id: '-publisher-website-url', + label: 'Website', + value: publisher.websiteUrl, + ), + ), + d.div( + classes: ['-pub-form-textfield-row'], + child: material.textField( + id: '-publisher-contact-email', + label: 'Contact email', + value: publisher.contactEmail, + ), ), - material.DataTableColumn( - headerContent: d.text(''), - headerClasses: ['icons-header'], - renderCell: (api.PublisherMember m) => d.a( - classes: ['-pub-remove-user-button'], - attributes: { - 'data-user-id': m.userId, - 'data-email': m.email, - }, - title: 'Remove member', - text: '×', + d.div( + classes: ['-pub-form-right-aligned'], + child: material.button( + id: '-publisher-update-button', + label: 'Update', + raised: true, ), ), ], - entries: members, ), - d.div( - classes: ['-pub-form-right-aligned'], - child: material.button( - id: '-admin-add-member-button', - label: 'Add member', - raised: true, - ), - ), - d.div( - id: '-admin-add-member-content', - classes: ['modal-content-hidden'], + foldableSection( + title: d.h2(text: 'Members'), children: [ - d.p( - text: 'You can invite new members to this verified publisher. ' - 'Once new members accept the invitation, they have full administrative rights, with the following abilities:', - ), - d.ul( - children: [ - d.li(text: 'Transfer packages to and from this publisher'), - d.li( - text: - 'Upload new versions of packages owned by this publisher'), - d.li(text: 'Add and remove members of this publisher'), + if (members.length == 1) + d.p( + text: 'This publisher only has a single member. ' + 'Consider adding more members to protect against losing control of the publisher.', + classes: ['warning'], + ), + material.dataTable( + id: '-pub-publisher-admin-members-table', + ariaLabel: 'Members of publisher', + columns: [ + material.DataTableColumn( + headerContent: d.text('Email'), + headerClasses: ['email-header'], + renderCell: (api.PublisherMember m) => d.text(m.email), + ), + material.DataTableColumn( + headerContent: d.text('Role'), + headerClasses: ['role-header'], + renderCell: (api.PublisherMember m) => d.text(m.role), + ), + material.DataTableColumn( + headerContent: d.text(''), + headerClasses: ['icons-header'], + renderCell: (api.PublisherMember m) => d.a( + classes: ['-pub-remove-user-button'], + attributes: { + 'data-user-id': m.userId, + 'data-email': m.email, + }, + title: 'Remove member', + text: '×', + ), + ), ], + entries: members, ), d.div( - classes: ['-pub-form-textfield-row'], - child: material.textField( - id: '-admin-invite-member-input', - label: 'Email address', + classes: ['-pub-form-right-aligned'], + child: material.button( + id: '-admin-add-member-button', + label: 'Add member', + raised: true, ), ), + d.div( + id: '-admin-add-member-content', + classes: ['modal-content-hidden'], + children: [ + d.p( + text: 'You can invite new members to this verified publisher. ' + 'Once new members accept the invitation, they have full administrative rights, with the following abilities:', + ), + d.ul( + children: [ + d.li(text: 'Transfer packages to and from this publisher'), + d.li( + text: + 'Upload new versions of packages owned by this publisher'), + d.li(text: 'Add and remove members of this publisher'), + ], + ), + d.div( + classes: ['-pub-form-textfield-row'], + child: material.textField( + id: '-admin-invite-member-input', + label: 'Email address', + ), + ), + ], + ), ], ), ]); diff --git a/app/test/frontend/golden/pkg_admin_page.html b/app/test/frontend/golden/pkg_admin_page.html index e1a473f50..6e5f74dae 100644 --- a/app/test/frontend/golden/pkg_admin_page.html +++ b/app/test/frontend/golden/pkg_admin_page.html @@ -241,355 +241,383 @@

Metadata

-

Package ownership

-
-

You can transfer this package to a verified publisher if you are a member of the publisher. Transferring the package removes the current uploaders, so that only the members of the publisher can upload new versions.

-

- Upgrading to verified publishers is an irreversible operation. - Packages can be transferred between publishers, but they can't be converted back to legacy uploader ownership. -

-
-
- - - - - - - - - Select a publisher - -
-
-
    -
  • - - -
  • -
  • - - example.com -
  • -
-
-
-

- -

-

Uploaders

-

There is only a single uploader. Consider adding more uploaders to protect against losing control of the package.

-
- - - - - - - - - - - - - -
admin@pub.dev - × -
+
+
+ trigger folding of the section +

Package ownership

-

- -

-
diff --git a/app/test/frontend/golden/publisher_admin_page.html b/app/test/frontend/golden/publisher_admin_page.html index 61890c04f..0759de1cf 100644 --- a/app/test/frontend/golden/publisher_admin_page.html +++ b/app/test/frontend/golden/publisher_admin_page.html @@ -158,85 +158,99 @@

example.com

-

Publisher information

-
- -
-
0 / 4096
- -
-
-
-
+
+
+ trigger folding of the section +

Publisher information

-
-
-
- -
- -
-
-
+
+
+ +
+
0 / 4096
+ +
+
+
+
-
-
-
-
- -
- -
-
-
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
-
- -
-

Members

-

This publisher only has a single member. Consider adding more members to protect against losing control of the publisher.

-
- - - - - - - - - - - - - - - -
Role
admin@pub.devadmin - × -
-
-
- -
-