diff --git a/.gitignore b/.gitignore index e8793163da..9aa4a57259 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ build/ key.json node_modules/ + +# test profile files are also not needed + +/app/dev-data-file.jsonl +test-profile.yml \ No newline at end of file diff --git a/app/lib/frontend/templates/views/pkg/info_box.dart b/app/lib/frontend/templates/views/pkg/info_box.dart index e6a303e588..06b9edc8ec 100644 --- a/app/lib/frontend/templates/views/pkg/info_box.dart +++ b/app/lib/frontend/templates/views/pkg/info_box.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:pana/pana.dart'; +import 'package:pub_dev/frontend/templates/views/pkg/one_click_install.dart'; import 'package:pubspec_parse/pubspec_parse.dart' as pubspek; import '../../../../package/models.dart'; @@ -69,6 +70,7 @@ d.Node packageInfoBoxNode({ return d.fragment([ imageCarousel(), labeledScores, + _oneClickInstall(package.name!), if (thumbnailUrl != null) d.div(classes: [ 'detail-screenshot-thumbnail' @@ -105,6 +107,10 @@ d.Node packageInfoBoxNode({ ]); } +d.Node _oneClickInstall(String packageName) { + return oneClickInstallNode(packageName: packageName); +} + d.Node _publisher(String? publisherId) { return _block( 'Publisher', diff --git a/app/lib/frontend/templates/views/pkg/one_click_install.dart b/app/lib/frontend/templates/views/pkg/one_click_install.dart new file mode 100644 index 0000000000..21f8caba7a --- /dev/null +++ b/app/lib/frontend/templates/views/pkg/one_click_install.dart @@ -0,0 +1,53 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// 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 '../../../dom/dom.dart' as d; +import '../../../static_files.dart'; + +d.Node oneClickInstallNode({required String packageName}) { + return d.fragment([ + d.div( + classes: ['pkg-page-install-wrapper'], + children: [ + d.h3(classes: ['title'], child: d.text('Install command')), + d.div(classes: [ + 'pkg-page-install' + ], children: [ + d.span(classes: [ + 'pkg-page-install-code' + ], children: [ + d.pre(children: [d.text('dart pub add $packageName')]) + ]), + d.span( + classes: ['pkg-page-install-copy'], + children: [ + d.img( + classes: ['pkg-page-install-copy-icon'], + attributes: { + 'data-copy-content': 'dart pub add $packageName', + 'data-ga-click-event': 'copy-package-version', + }, + image: d.Image( + src: staticUrls + .getAssetUrl('/static/img/content-copy-icon.svg'), + alt: 'copy "dart pub add $packageName" to clipboard', + width: 18, + height: 18, + ), + title: 'Copy "dart pub add $packageName" to clipboard', + ), + d.div( + classes: ['pkg-page-install-copy-feedback'], + children: [ + d.span(classes: ['code'], text: '$packageName'), + d.text(' copied to clipboard'), + ], + ), + ], + ), + ]), + ], + ), + ]); +} diff --git a/pkg/web_app/lib/src/hoverable.dart b/pkg/web_app/lib/src/hoverable.dart index 62f25e371a..0b92df2202 100644 --- a/pkg/web_app/lib/src/hoverable.dart +++ b/pkg/web_app/lib/src/hoverable.dart @@ -10,6 +10,7 @@ import 'package:_pub_shared/format/x_ago_format.dart'; void setupHoverable() { _setEventForHoverable(); _setEventForPackageTitleCopyToClipboard(); + _setEventForPackageInstallCopyToClipboard(); _setEventForPreCodeCopyToClipboard(); _updateXAgoLabels(); _setEventForXAgo(); @@ -72,6 +73,21 @@ void _setEventForPackageTitleCopyToClipboard() { }); } +void _setEventForPackageInstallCopyToClipboard() { + final root = document.querySelector('.pkg-page-install-copy'); + final icon = root?.querySelector('.pkg-page-install-copy-icon'); + final feedback = root?.querySelector('.pkg-page-install-copy-feedback'); + if (root == null || icon == null || feedback == null) return; + + final copyContent = icon.dataset['copy-content']; + if (copyContent == null || copyContent.isEmpty) return; + + icon.onClick.listen((_) async { + _copyToClipboard(copyContent); + await _animateCopyFeedback(feedback); + }); +} + Future _animateCopyFeedback(Element feedback) async { feedback.classes.add('visible'); await window.animationFrame; diff --git a/pkg/web_css/lib/src/_pkg.scss b/pkg/web_css/lib/src/_pkg.scss index 990247df6a..67575154a5 100644 --- a/pkg/web_css/lib/src/_pkg.scss +++ b/pkg/web_css/lib/src/_pkg.scss @@ -49,7 +49,8 @@ width: 100%; border-spacing: 0; - td, th { + td, + th { border-bottom: 1px solid #c8c8ca; padding: 8px 4px; text-align: left; @@ -199,7 +200,7 @@ min-width: 40px; max-width: 40px; - >.pkg-report-icon { + > .pkg-report-icon { width: 18px; } } @@ -225,7 +226,7 @@ } &.-is-yellow { - color: #FFA500; + color: #ffa500; } .foldable-icon { @@ -233,7 +234,7 @@ width: 12px; transform: rotate(180deg); - transition: transform .3s linear; + transition: transform 0.3s linear; } } @@ -243,7 +244,6 @@ } } - .pkg-report-content { padding-left: 40px; @@ -278,14 +278,48 @@ } } -.pkg-page-title-copy { +.pkg-page-install-wrapper { + h3.title { + font-size: 16px; + } + .pkg-page-install { + background: $color-grey-bg; + border-radius: 5px; + padding: 10px 5px; + display: flex; + align-items: center; + + .pkg-page-install-code { + font-size: 13px; + pre { + background: inherit; + line-height: inherit; + padding: 0px; + overflow-x: inherit; + white-space: pre-wrap; + word-wrap: break-word; + color: #d14; + } + } + + &:hover { + .pkg-page-install-copy-icon { + opacity: 0.5; + } + } + } +} + +.pkg-page-title-copy, +.pkg-page-install-copy { position: relative; display: inline-block; height: 20px; width: 20px; margin-left: 12px; - .pkg-page-title-copy-icon { + .pkg-page-title-copy-icon, + .pkg-page-install-copy-icon { display: block; width: 20px; height: 20px; @@ -298,7 +332,8 @@ } } - .pkg-page-title-copy-feedback { + .pkg-page-title-copy-feedback, + .pkg-page-install-copy-feedback { position: absolute; top: -12px; left: 32px; @@ -312,7 +347,7 @@ display: none; transition: opacity $copy-feedback-transition-opacity-delay; - >.code { + > .code { font-family: $font-family-google-sans-mono; display: block; } @@ -346,7 +381,7 @@ // // The scaling breaks down on smaller screens, keeping it for // desktop only. - font-size: 1.0vw; + font-size: 1vw; } } } diff --git a/pkg/web_css/lib/src/_variables.scss b/pkg/web_css/lib/src/_variables.scss index b7c8e9df06..5eaefbcd64 100644 --- a/pkg/web_css/lib/src/_variables.scss +++ b/pkg/web_css/lib/src/_variables.scss @@ -33,5 +33,7 @@ $color-input-primary: #0175C2; $color-input-danger: #ff4242; $color-link: $color-input-primary; +$color-grey-bg: #f5f5f7; + // NOTE: keep in sync with hoverable.dart 900ms delay $copy-feedback-transition-opacity-delay: 0.9s;