Skip to content

Commit 3d2a02f

Browse files
authored
Copy/paste functionality established (#2257)
Closes #2254
1 parent 437759d commit 3d2a02f

File tree

8 files changed

+176
-1
lines changed

8 files changed

+176
-1
lines changed

app/assets/images/checkbox.svg

Lines changed: 4 additions & 0 deletions
Loading

app/assets/images/copy.svg

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

app/assets/stylesheets/_project.scss

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,81 @@
162162
line-height: 1.5rem; /* 150% */
163163
}
164164

165+
#copy-project-path-button {
166+
width: 1.5rem;
167+
height: 1.5rem;
168+
background-repeat: no-repeat;
169+
background-position: center;
170+
}
171+
172+
.copy-paste-frames {
173+
background-image: asset_url("copy.svg");
174+
}
175+
176+
.copy-paste-check {
177+
background-image: asset_url("green-checkmark.svg");
178+
}
179+
180+
copy-paste-error {
181+
background-image: asset_url("custom_error.svg");
182+
}
183+
184+
.pul-copy-paste-tooltip {
185+
position: relative;
186+
display: inline-block;
187+
cursor: pointer;
188+
width: 1.5rem;
189+
height: 1.5rem;
190+
aspect-ratio: 1/1;
191+
font-weight: normal;
192+
}
193+
194+
/* Tooltip text */
195+
.pul-copy-paste-tooltiptext::after {
196+
content: " ";
197+
position: absolute;
198+
top: 50%;
199+
right: 100%; /* To the left of the tooltip */
200+
margin-top: -5px;
201+
border-width: 5px;
202+
border-style: solid;
203+
border-color: transparent #333 transparent transparent;
204+
}
205+
206+
.pul-copy-paste-tooltiptext {
207+
visibility: hidden; /* Hidden by default */
208+
background-color: #333;
209+
color: #fff;
210+
font-size: 12px;
211+
padding: 8px 12px;
212+
border-radius: 4px;
213+
white-space: nowrap;
214+
text-overflow: ellipsis;
215+
position: absolute;
216+
z-index: 1; /* Ensure tooltip is displayed above content */
217+
top: -50%;
218+
left: 95%;
219+
@supports (-webkit-line-clamp: 4) {
220+
white-space: initial;
221+
display: -webkit-box;
222+
-webkit-box-orient: vertical;
223+
-webkit-line-clamp: 4;
224+
}
225+
}
226+
227+
copy-project-path-label-normal {
228+
width: 3rem;
229+
}
230+
231+
.copy-project-path-label-copied {
232+
width: 5rem;
233+
}
234+
235+
/* Show the tooltip text on hover */
236+
.pul-copy-paste-tooltip:hover .pul-copy-paste-tooltiptext {
237+
visibility: visible;
238+
}
239+
165240
.basic-details-fields {
166241
padding-top: 30px;
167242
list-style-type: none;

app/javascript/entrypoints/application.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { userRolesAutocomplete } from './userRolesAutocomplete.js';
3232
import { storageInputs } from './storageInputs.js';
3333
import { validationClear } from './validation.js';
3434
import { titleCopySaveExit } from './titleCopySaveExit.js';
35+
import { copyPastePath } from './copyPastePath.js';
3536

3637
const app = createApp({});
3738

@@ -247,6 +248,7 @@ function initPage() {
247248
validationClear();
248249
titleCopySaveExit();
249250
userRolesAutocomplete();
251+
copyPastePath();
250252
}
251253

252254
window.addEventListener('load', () => initPage());
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// eslint-disable-next-line import/prefer-default-export
2+
export function copyPastePath() {
3+
// Sets the elements to the proper CSS classes once a value has been copied to the clipboard.
4+
function setCopiedToClipboard(iconEl, labelEl, normalClass, copiedClass) {
5+
$(iconEl).removeClass('copy-paste-frames');
6+
$(iconEl).addClass('copy-paste-check');
7+
$(labelEl).text('Copied!');
8+
$(labelEl).removeClass(normalClass);
9+
$(labelEl).addClass(copiedClass);
10+
}
11+
12+
// Resets the elements to the proper CSS classes (e.g. displays as if the copy has not happened)
13+
function resetCopyToClipboard(iconEl, labelEl, normalClass, copiedClass) {
14+
$(labelEl).text('Copy');
15+
$(labelEl).removeClass(copiedClass);
16+
$(labelEl).addClass(normalClass);
17+
$(iconEl).addClass('copy-paste-frames');
18+
$(iconEl).removeClass('copy-paste-check');
19+
}
20+
21+
// Sets icon and label to indicate that an error happened when copying a value to the clipboard
22+
function errorCopyToClipboard(iconEl, errorMsg) {
23+
$(iconEl).removeClass('copy-paste-frames');
24+
$(iconEl).addClass('copy-paste-error');
25+
console.error(errorMsg);
26+
}
27+
28+
function copyPath(value, iconEl, labelEl, normalClass, copiedClass) {
29+
// Copy value to the clipboard....
30+
navigator.clipboard.writeText(value).then(
31+
() => {
32+
// ...and notify the user
33+
setCopiedToClipboard(iconEl, labelEl, normalClass, copiedClass);
34+
setTimeout(() => {
35+
resetCopyToClipboard(iconEl, labelEl, normalClass, copiedClass);
36+
}, 20000);
37+
},
38+
() => {
39+
errorCopyToClipboard(iconEl, 'Copy to clipboard failed');
40+
},
41+
);
42+
}
43+
44+
$('#copy-project-path-button').on('click', () => {
45+
const projectPath = $('#copy-project-path-button').data('url');
46+
copyPath(
47+
projectPath,
48+
'#copy-project-path-button',
49+
'#copy-project-path-label',
50+
'copy-project-path-label-normal',
51+
'copy-project-path-label-copied',
52+
);
53+
});
54+
55+
$(() => {
56+
$('#copy-project-path-button').addClass('copy-paste-frames');
57+
});
58+
}

app/views/projects/_project_details_heading.html.erb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@
2626
<div class="col-12">
2727
<div class="date-and-data-info-single">
2828
<ul>
29-
<li><span class="label" id="project-path-copy">Project Directory:</span> <%= @presenter.project_directory %></li>
29+
<li><span class="label" id="project-path-copy">Project Directory:</span> <%= @presenter.project_directory %>
30+
<button id="copy-project-path-button" class="btn" data-url="<%= @presenter.project_directory %>" title="Copy project path to the clipboard">
31+
<div class="pul-copy-paste-tooltip">
32+
<span id="copy-project-path-label" class="copy-project-path-label-normal pul-copy-paste-tooltiptext">Copy</span>
33+
</div>
34+
</button>
35+
</li>
3036
</ul>
3137
</div>
3238
</div>

spec/system/project_details_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,30 @@
8585
expect(page).to have_content("RDSS-Research Data and Scholarship Services")
8686
expect(page).to have_content("Research")
8787
end
88+
it "copies DOI to the clipboard" do
89+
sign_in sponsor_user
90+
project_in_mediaflux.metadata_model.status = Project::APPROVED_STATUS
91+
project_in_mediaflux.metadata_model.storage_capacity["size"]["approved"] = 1
92+
project_in_mediaflux.metadata_model.storage_capacity["unit"]["approved"] = "TB"
93+
project_in_mediaflux.metadata_model.storage_performance_expectations["approved"] = "slow"
94+
project_in_mediaflux.save!
95+
visit "/projects/#{project_in_mediaflux.id}/details"
96+
97+
expect(page.html.include?('<button id="copy-project-path-button"')).to be true
98+
99+
# A test as follows would be preferrable
100+
#
101+
# ```
102+
# expect(page).to have_content "COPY"
103+
# click_on "#copy-project-path-button"
104+
# expect(page).to have_css ".copy-paste-check"
105+
# ```
106+
#
107+
# but unfortunately this kind of test only works when we run RSpec like this:
108+
#
109+
# RUN_IN_BROWSER=true bundle exec rspec spec/system/project_details_spec.rb
110+
#
111+
end
88112
end
89113

90114
context "Empty Project Purpose field" do

0 commit comments

Comments
 (0)