-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathproject-list.js
1013 lines (921 loc) · 35.7 KB
/
project-list.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import { LitElement, html, css } from 'lit-element';
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-button/paper-button.js';
import '@polymer/paper-input/paper-input.js';
import '@polymer/paper-dialog/paper-dialog.js';
import '@polymer/paper-spinner/paper-spinner-lite.js';
import '@polymer/paper-dropdown-menu/paper-dropdown-menu.js';
import '@polymer/paper-item/paper-item.js';
import '@polymer/paper-listbox/paper-listbox.js';
import '@polymer/paper-tabs';
import '@polymer/iron-icon/iron-icon.js';
import '@polymer/iron-icons/social-icons.js';
import '@polymer/paper-checkbox/paper-checkbox.js';
import OnlineUserListHelper from './util/online-user-list-helper'
import Auth from './util/auth';
import GitHub from "./util/github";
/**
* The project list element provides the functionality to list existing projects and to create new ones.
* It provides several possibilities for configuration. The URL of the las2peer project service should be configured.
* It is also possible to disable the "All projects" tab, which allows hiding projects that the current user is no
* member of.
*/
export class ProjectList extends LitElement {
static get styles() {
return css`
.main {
width: 100%;
margin-top: 1em;
}
.paper-button-blue {
color: rgb(240,248,255);
background: rgb(30,144,255);
max-height: 50px;
}
.button-create-project {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.paper-button-blue:hover {
color: rgb(240,248,255);
background: rgb(65,105,225);
}
.paper-button-blue[disabled] {
background: #e1e1e1;
}
.button-danger {
height: 2.5em;
color: rgb(240,248,255);
background: rgb(255,93,84);
}
.button-danger:hover {
background: rgb(216,81,73);
}
.top-menu {
display: flex;
align-items: center;
}
.input-search-project {
border-radius: 3px;
border: thin solid #e1e1e1;
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-left: auto;
height: 2.5em;
padding-left:5px;
}
/* Set outline to none, otherwise the border color changes when clicking on input field. */
.input-search-project:focus {
outline: none;
}
.project-item-card {
display: flex;
width: 100%;
margin-top: 1em;
}
.project-item-card:hover {
background: #eeeeee;
}
.project-item-card-content {
width: 100%;
height: 100%;
align-items: center;
display: flex;
}
.project-item-name {
margin-left: 1em;
margin-top: 1em;
margin-bottom: 1em;
}
.project-item-user-list {
margin: 1em 1em 1em 0.5em;
}
.green-dot {
background-color: #c5e686;
height: 0.8em;
width: 0.8em;
border-radius: 50%;
display: inline-block;
margin-left: auto;
}
paper-tabs {
--paper-tabs-selection-bar-color: rgb(30,144,255);
}
.icon {
color: #000000;
}
.icon:hover {
color: #7c7c7c;
}
`;
}
static get properties() {
return {
// Name of the system (needs to be existing in project service)
system: {
type: String
},
// l2p groups
groups:{
type: Array
},
/**
* Array containing all the projects that were loaded from las2peer project service.
*/
projects: {
type: Array
},
/**
* Array containing the projects that are currently listed/displayed in the frontend. This is used for the
* implementation of the project search. If the user searches for projects by name, then listedProjects
* only contains the projects that match the search input. If search is ended, listedProjects gets set to
* all projects again.
*/
listedProjects: {
type: Array
},
/**
* The currently selected project, i.e. last one clicked on.
*/
selectedProject: {
type: Object
},
/**
* This property allows to disable the "All projects" tab. It can be set to "true" if only the projects where
* the user is a member of should be listed. If set to "false", then all projects that are available will be
* listed in the "All projects" tab.
*/
disableAllProjects: {
type: Boolean
},
/**
* If disableAllProjects is set to false, then we have two tabs for listing the projects - "My projects" and
* "All projects". This property stores the currently selected tab index and is either 0 (for "My projects")
* or 1 (for "All projects").
*/
tabSelected: {
type: Number
},
// TODO
projectsOnlineUser: {
type: Map
},
/**
* URL where the frontend can access the las2peer project service REST API.
*/
projectServiceURL: {
type: String
},
/**
* URL where the frontend can access the las2peer contact service REST API.
*/
contactServiceURL: {
type: String
},
/**
* Yjs address that should be used for keeping the metadata in the frontend up-to-date.
* Can also be used for the online user list.
*/
yjsAddress: {
type: String
},
/**
* Yjs resource path used for sharing the metadata and for the online user list.
*/
yjsResourcePath: {
type: String
},
/**
* Yjs instance.
*/
y: {
type: Object
},
/**
* If the user opens the project options dialog, then the project
* for which the dialog is opened gets stored in this variable.
*/
projectOptionsSelected: {
type: Object
},
connectChannelVisible: { type: Boolean }
};
}
constructor() {
super();
this.groups = [];
this.projects = [];
this.selectedProject = null;
this.listedProjects = ["sss"];
this.projectsOnlineUser = new Object();
// use a default value for project service URL for local testing
this.projectServiceURL = "http://127.0.0.1:8080";
this.contactServiceURL = "http://127.0.0.1:8080/contactservice";
window.addEventListener('metadata-change-request', this._changeMetadata.bind(this));
window.addEventListener('metadata-reload-request', this._reloadMetadata.bind(this));
window.addEventListener('projects-reload-request', (e) => this.showProjects(false));
this.disableAllProjects = false;
this.yjsAddress = "http://127.0.0.1:1234";
this.yjsResourcePath = "./socket.io";
this.connectChannelVisible = false;
}
connectedCallback() {
super.connectedCallback();
if(!GitHub.gitHubUsernameStored()) {
GitHub.hasUserGitHubAccountConnected().then(result => {
const connected = result[0];
if(connected) {
const username = result[1];
// store in localStorage
GitHub.storeGitHubUsername(username);
GitHub.sendGitHubUsernameToProjectService(this.projectServiceURL, this.system);
}
});
} else {
GitHub.sendGitHubUsernameToProjectService(this.projectServiceURL, this.system);
}
// here the properties are already set (otherwise this.system is undefinied in showProjects)
this.showProjects(false);
}
_changeMetadata(event) {
console.log(event);
let newMetadata = event.detail;
var project = this.selectedProject;
var projectName = project.name;
var oldMetadata = this.selectedProject.metadata;
console.log("Project is: ", project);
console.log("New Metadata is: ", newMetadata);
fetch(this.projectServiceURL + "/projects/" + this.system + "/changeMetadata/", {
method: "POST",
headers: Auth.getAuthHeaderWithSub(),
body: JSON.stringify({
"access_token": Auth.getAccessToken(),
"projectName": projectName,
"oldMetadata": oldMetadata,
"newMetadata": newMetadata
})
}).then( response => {
if(!response.ok) throw Error(response.status);
return response.json();
}).then(data => {
console.log(data);
if(this.y) {
// since the service successfully updated the metadata, we can also update it in the Yjs room
this.y.share.data.set("projectMetadata", newMetadata);
}
}).catch(error => {
if(error.message == "401") {
// user is not authorized
// maybe the access token has expired
Auth.removeAuthDataFromLocalStorage();
// location.reload();
} else {
console.log(error);
}
});
}
/**
* Gets called on the "metadata-reload-request" event and re-fetches the metadata of the currently selected
* project. The fetched metadata gets put into the project's Yjs room.
* This event should be used if the metadata got updated from somewhere else than the frontend, because then
* using the "metadata-change-request" event is not working anymore as the project-list contains out-of-date
* metadata and is not able to send the update metadata request to the project service successfully.
* @param event
* @private
*/
_reloadMetadata(event) {
fetch(this.projectServiceURL + "/projects/" + this.system + "/" + this.selectedProject.name, {
method: "GET",
headers: Auth.getAuthHeaderWithSub()
}).then(response => {
if(!response.ok) throw Error(response.status);
return response.json();
}).then(data => {
const metadata = data.metadata;
if(this.y) {
// update metadata in yjs room => this will also update it in this.selectedProject automatically
this.y.share.data.set("projectMetadata", metadata);
}
});
}
render() {
return html`
<div class="main">
<div class="top-menu">
<paper-button class="button-create-project paper-button-blue" @click="${this._onCreateProjectButtonClicked}">Create Project</paper-button>
<input class="input-search-project" @input="${(e) => this._onSearchInputChanged(e.target.value)}"
placeholder="Search Projects"></input>
</div>
<div>
${this.disableAllProjects ? html`` : html`
<paper-tabs id="my-and-all-projects" selected="0">
<paper-tab @click="${() => this._onTabChanged(0)}">My Projects</paper-tab>
<paper-tab @click="${() => this._onTabChanged(1)}">All Projects</paper-tab>
</paper-tabs>
`}
</div>
<!-- show spinner if projects are loading -->
${this.projectsLoading ? html`
<div style="width: 100%; display: flex">
<paper-spinner-lite style="margin-top: 4em; margin-left: auto; margin-right: auto" active></paper-spinner-lite>
</div>
` : html``}
${this.listedProjects.map(project => html`
<paper-card class="project-item-card" @click="${() => this._onProjectItemClicked(project.name)}">
<div class="project-item-card-content">
<p class="project-item-name">${project.name}</p>
<div style="margin-left: auto; display: flex">
${this.getListOfProjectOnlineUsers(project.name) ? html`<span class="green-dot" style="margin-top: auto; margin-bottom: auto"></span>` : html``}
<p class="project-item-user-list">${this.getListOfProjectOnlineUsers(project.name)}</p>
<!-- Link to GitHub -->
${project.gitHubProject ? html `
<a title="View project on GitHub" href=${project.gitHubProject.url} target="_blank"
style="margin-top: auto; margin-bottom:auto; margin-right: 0.5em">
<svg width="24px" height="24px">
<image xlink:href="https://raw.githubusercontent.com/primer/octicons/e9a9a84fb796d70c0803ab8d62eda5c03415e015/icons/mark-github-16.svg" width="24px" height="24px"/>
</svg>
</a>
` : html``}
${project.is_member ? html `
<iron-icon icon="icons:more-vert" class="icon" style="margin-top: auto; margin-bottom: auto; margin-right: 1em"
@click=${() => this.openProjectOptionsDialog(project)}></iron-icon>
` : html``}
</div>
</div>
</paper-card>
`)}
</div>
<!-- Dialog for creating new projects. -->
<paper-dialog id="dialog-create-project">
<h2>Create a Project</h2>
<paper-input id="input-project-name" @input="${(e) => this._onInputProjectNameChanged(e.target.value)}"
placeholder="Project Name"></paper-input>
<paper-dropdown-menu id="input-group-name" label="Link Group to Project:"
><paper-listbox slot="dropdown-content" class="dropdown-content">
${this.groups.map(group => html`
<paper-item value="${group.id}">${group.name}</paper-item>
`)}
</paper-listbox>
</paper-dropdown-menu>
<div>
<paper-checkbox id="cb-connect-channel" @change=${(e) => this.connectChannelVisible = e.target.checked} >Connect to chat channel</paper-checkbox>
<div ?hidden=${!this.connectChannelVisible}>
<paper-input id="input-channel-name" placeholder="Channel name"></paper-input>
or:
<paper-checkbox id="cb-create-new-channel">Create new channel</paper-checkbox>
</div>
</div>
<div class="buttons">
<paper-button @click="${this._closeCreateProjectDialogClicked}" dialog-dismiss>Cancel</paper-button>
<paper-button id="dialog-button-create" @click="${this._createProject}" dialog-confirm>Create</paper-button>
</div>
</paper-dialog>
<paper-dialog id="dialog-project-options">
<h2>Connected Group</h2>
<p>The project <span id="connected-group-project-name">Project name</span> is connected to the las2peer group:</p>
<div style="display: flex">
<p><span id="connected-group-name">Group name</span></p>
<paper-dropdown-menu style="display: none" id="input-edit-group-name" label="Link Group to Project:"
><paper-listbox slot="dropdown-content" class="dropdown-content">
${this.groups.map(group => html`
<paper-item value="${group.id}">${group.name}</paper-item>
`)}
</paper-listbox>
</paper-dropdown-menu>
<iron-icon icon="editor:mode-edit" class="icon" style="margin-top: auto; margin-bottom: auto; margin-left: 1em"
@click=${this._onEditConnectedGroupClicked}></iron-icon>
</div>
<h2>Danger Zone</h2>
<div style="display: flex; margin-top: 0">
<p>Delete this project. Please note that a project cannot be restored after deletion.</p>
<paper-button class="button-danger" @click=${this.showDeleteProjectDialog} style="margin-top: auto; margin-bottom: auto">Delete</paper-button>
</div>
<div class="buttons">
<paper-button dialog-confirm @click=${this._onGroupChanged}>OK</paper-button>
</div>
</paper-dialog>
<!-- Dialog: Are you sure to delete the project? -->
<paper-dialog id="dialog-delete-project" modal>
<h4>Delete Project</h4>
<div>
Are you sure that you want to delete the project?
</div>
<div class="buttons">
<paper-button dialog-dismiss>Cancel</paper-button>
<paper-button @click=${this._deleteProject} dialog-confirm autofocus>Yes</paper-button>
</div>
</paper-dialog>
<!-- Dialog showing a loading bar -->
<paper-dialog id="dialog-loading" modal>
<paper-spinner-lite active></paper-spinner-lite>
</paper-dialog>
<!-- Toasts -->
<!-- Toast for successful creation of project -->
<paper-toast id="toast-success" text="Project created!"></paper-toast>
<!-- Toast for successful deletion of project -->
<paper-toast id="toast-success-deletion" text="Project deleted!"></paper-toast>
<!-- Toast for creation fail because of project with same name already existing -->
<custom-style><style is="custom-style">
#toast-already-existing {
--paper-toast-background-color: red;
--paper-toast-color: white;
}
</style></custom-style>
<paper-toast id="toast-already-existing" text="A project with the given name already exists!"></paper-toast>
`;
}
/**
* Gets called by the "Create Project" button. Opens the dialog for creating a project, which then lets the user
* select a name for the project and a group that should be connected to the project.
* @private
*/
_onCreateProjectButtonClicked() {
// clear input fields of dialog
this.resetCreateProjectDialog();
// add statusbar to be able to get user infos for this step
console.log(this.contactServiceURL);
fetch(this.contactServiceURL + "/groups", {
method: "GET",
headers: Auth.getAuthHeaderWithSub()
}).then(response => {
if(!response.ok) throw Error(response.status);
console.log(typeof response)
console.log("ssssssss" + Object.keys(response));
return response.json();
}).then(data => {
// store loaded groups
// groups given by contact service as a JSONObject with key = group agent id and value = group name
// we create an array of objects with id and name attribute out of it
this.groups = [];
for(let key of Object.keys(data)) {
let group = {
"id": key,
"name": data[key]
};
this.groups.push(group);
}
console.log(this.groups);
// only open popup once group loaded
this.shadowRoot.getElementById("dialog-create-project").open();
// disable create button until user entered a project name
this.shadowRoot.getElementById("dialog-button-create").disabled = true;
}).catch(error => {
if(error.message == "401") {
// user is not authorized
// maybe the access token has expired
Auth.removeAuthDataFromLocalStorage();
// location.reload();
} else {
console.log(error);
// in case of contactservice not running, which should not happen in real deployment
this.groups = [];
// only open popup once group loaded
this.shadowRoot.getElementById("dialog-create-project").open();
// disable create button until user entered a project name
this.shadowRoot.getElementById("dialog-button-create").disabled = true;
}
});
}
/**
* Gets called when the search input gets updated by the user. Updates listedProjects array correspondingly.
* @param searchInput Input from the user entered in the input field for searching projects by name.
* @private
*/
_onSearchInputChanged(searchInput) {
if(searchInput) {
this.listedProjects = this.projects.filter(project => {
return project.name.toLowerCase().includes(searchInput.toLowerCase());
});
} else {
// no search input, show all projects that were loaded
this.listedProjects = this.projects;
}
}
/**
* Gets called when the user switches the current tab. Depending on which tab is selected, "My projects" or
* "All projects" are loaded.
* @param tabIndex 0 = My Projects, 1 = All Projects
* @private
*/
_onTabChanged(tabIndex) {
this.tabSelected = tabIndex;
if(tabIndex == 0) {
// show users projects / projects where the user is a member of
this.showProjects(false);
} else {
// show all projects
this.showProjects(true);
}
}
/**
* Loads and shows the projects that the user is a member of, or all existing projects.
* @param allProjects If all projects should be shown or only the ones where the
* current user is a member of.
*/
showProjects(allProjects) {
// set loading to true
this.projectsLoading = true;
// clear current project list
this.projects = [];
this.listedProjects = [];
if(!Auth.userInfoAvailable()) {
// user is not logged in (cannot load projects)
this.projectsLoading = false;
return;
}
fetch(this.projectServiceURL + "/projects/" + this.system, {
method: "GET",
headers: Auth.getAuthHeaderWithSub()
}).then(response => {
if(!response.ok) throw Error(response.status);
return response.json();
}).then(data => {
console.log("Projects are", data.projects);
// set loading to false, then the spinner gets hidden
this.projectsLoading = false;
// send event (containing every project, not only the ones from the user)
let event = new CustomEvent("projects-loaded", {
detail: {
projects: data.projects
},
bubbles: true
});
this.dispatchEvent(event);
// if we only want to show the projects, where the user is a member of, we need to filter out some projects
if(!allProjects) data.projects = data.projects.filter(project => project.is_member);
// store loaded projects
this.projects = data.projects;
// set projects that should be shown (currently all)
this.listedProjects = data.projects;
}).catch(error => {
if(error.message == "401") {
// user is not authorized
// maybe the access token has expired
Auth.removeAuthDataFromLocalStorage();
// location.reload();
} else {
console.log(error);
}
});
}
/**
* Gets called when the user clicks on a project in the project list. Fires an event that notifies the parent
* elements that a project got selected.
* @param projectName Name of the project that got selected in the project list.
* @private
*/
_onProjectItemClicked(projectName) {
this.selectedProject = this.getProjectByName(projectName);
let event = new CustomEvent("project-selected", {
detail: {
message: "Selected project in project list.",
project: JSON.parse(JSON.stringify(this.selectedProject))
},
bubbles: true
});
this.dispatchEvent(event);
// join Yjs room for the project metadata
if(this.y) {
this.y.connector.disconnect();
}
Y({
db: {
name: "memory" // store the shared data in memory
},
connector: {
name: "websockets-client", // use the websockets connector
room: "projects_" + this.system + "_" + projectName,
authInfo: {
accessToken: Auth.getAccessToken(),
basicAuth: Auth.getBasicAuthPart()
},
//options: { resource: this.yjsResourcePath},
url: this.yjsAddress
},
share: { // specify the shared content
data: 'Map'
}
}).then(function(y) {
this.y = y;
const currentMetadataYjs = y.share.data.get("projectMetadata");
y.share.data.observe(event => {
if(event.name == "projectMetadata") {
this.selectedProject.metadata = y.share.data.get("projectMetadata");
window.dispatchEvent(new CustomEvent("metadata-changed", {
detail: JSON.parse(JSON.stringify(this.selectedProject.metadata)),
bubbles: true
}));
}
});
if(!currentMetadataYjs) {
y.share.data.set("projectMetadata", JSON.parse(JSON.stringify(this.selectedProject.metadata)));
}
}.bind(this));
}
getProjectByName(name) {
return this.listedProjects.find(x => x.name === name);
}
/**
* Gets called when the user clicks on a project in the project list. Fires an event that notifies the parent
* elements that a project got selected.
* @param projectName Name of the project that got selected in the project list.
* @private
*/
_onGroupChangeDone(project) {
// TODO: give full information on the project and whether the user is a member of it
let event = new CustomEvent("project-selected", {
detail: {
message: "Selected project in project list.",
project: project
},
bubbles: true
});
this.dispatchEvent(event);
}
/**
* Gets called when the user clicks on the "Close" button in the create project dialog.
* @private
*/
_closeCreateProjectDialogClicked() {
this.shadowRoot.getElementById("dialog-create-project").close();
// clear input field for project name in the dialog
this.shadowRoot.getElementById("input-project-name").value = "";
}
/**
* Gets called when the user changes the input of the project name input field in the create project dialog.
* Enables/disables the creation of projects depending on whether the name input is empty or not.
* @param projectName Input
* @private
*/
_onInputProjectNameChanged(projectName) {
if(projectName) {
this.shadowRoot.getElementById("dialog-button-create").disabled = false;
} else {
this.shadowRoot.getElementById("dialog-button-create").disabled = true;
}
}
/**
* Get called when the user click on "create" in the create project dialog.
*/
_createProject() {
const projectName = this.shadowRoot.getElementById("input-project-name").value;
const linkedGroupName = this.shadowRoot.getElementById("input-group-name").value;
const linkedGroup = this.groups.find(group => group.name == linkedGroupName);
// close dialog (then also the button is not clickable and user cannot create project twice or more often)
// important: get projectName before closing dialog, because when closing the dialog the input field gets cleared
this._closeCreateProjectDialogClicked();
// show loading dialog
this.shadowRoot.getElementById("dialog-loading").open();
// currently fetches members from contact service but does not check whether project already exists (code is there but commented)
if(projectName) {
fetch(this.contactServiceURL + "/groups/" + linkedGroupName + "/member", {
method: "GET",
headers: Auth.getAuthHeaderWithSub()
}).then( response => {
if(!response.ok) throw Error(response.status);
console.log(typeof response)
console.log("ssssssss" + Object.keys(response));
return response.json();
}).then(data => {
console.log(data);
const users = Object.values(data);
const body = {
"name": projectName,
"access_token": Auth.getAccessToken(),
"linkedGroup": linkedGroup,
"users": users
};
// check if project should be linked to chat channel
const connectChannel = this.shadowRoot.getElementById("cb-connect-channel").checked;
const chatInfo = {
connectChannel,
newChannel: false
};
if(connectChannel) {
// check if new channel should be created
const newChannel = this.shadowRoot.getElementById("cb-create-new-channel").checked;
chatInfo.newChannel = newChannel;
if(!newChannel) {
chatInfo.channelName = this.shadowRoot.getElementById("input-channel-name").value;
}
}
body.chatInfo = chatInfo;
if(GitHub.gitHubUsernameStored()) {
body.gitHubUsername = GitHub.getGitHubUsername();
}
//const newProject = {"id":this.projects.length, "name":projectName, "Linked Group":linkedGroup, "Group Members":users};
fetch(this.projectServiceURL + "/projects/" + this.system, {
method: "POST",
headers: Auth.getAuthHeaderWithSub(),
body: JSON.stringify(body)
}).then(response => {
console.log(response);
// close loading dialog
this.shadowRoot.getElementById("dialog-loading").close();
if(response.status == 201) {
// project got created successfully
this.shadowRoot.getElementById("toast-success").show();
// clear input field for project name in the dialog
this.shadowRoot.getElementById("input-project-name").value = "";
// since a new project exists, reload projects from server
this.showProjects(false);
// switch to tab "My Projects"
this.tabSelected = 0;
this.shadowRoot.getElementById("my-and-all-projects").selected = 0;
} else if(response.status == 409) {
// a project with the given name already exists
this.shadowRoot.getElementById("toast-already-existing").show();
} else if(response.status == 401) {
Auth.removeAuthDataFromLocalStorage();
// location.reload();
}
// TODO: check what happens when access_token is missing in localStorage
});
});
}
}
/**
* Call this method with a map, mapping project names to lists of Yjs room names, and then these Yjs room names
* will be used for the online user list.
* @param mapProjectRooms Map, mapping project names to lists of Yjs room names, where SyncMeta is running.
*/
setOnlineUserListYjsRooms(mapProjectRooms) {
this.projectsOnlineUser = {};
for(let projectName of Object.keys(mapProjectRooms)) {
let roomNames = mapProjectRooms[projectName];
this.projectsOnlineUser[projectName] = [];
for(let roomName of roomNames) {
OnlineUserListHelper.loadListOfSyncMetaOnlineUsers(roomName, this.yjsAddress, this.yjsResourcePath).then(list => {
for(let username of list) {
if(!this.projectsOnlineUser[projectName].includes(username)) this.projectsOnlineUser[projectName].push(username);
}
this.requestUpdate();
});
}
}
}
/**
* Creates a string which contains a list of the users that are online in the
* project with the given name.
* @param projectName
* @returns {string} String containing a list of online users in the given project.
*/
getListOfProjectOnlineUsers(projectName) {
let s = "";
for(let i in this.projectsOnlineUser[projectName]) {
s += this.projectsOnlineUser[projectName][i] + ",";
}
if(s) {
s = s.substr(0,s.length-1);
}
return s;
}
/**
* Gets called when the user clicks on the edit-button for the group name in the "connected-group" dialog.
* @private
*/
_onEditConnectedGroupClicked() {
fetch(this.contactServiceURL + "/groups", {
method: "GET",
headers: Auth.getAuthHeaderWithSub()
}).then(response => {
if(!response.ok) throw Error(response.status);
return response.json();
}).then(data => {
// store loaded groups
// groups given by contact service as a JSONObject with key = group agent id and value = group name
// we create an array of objects with id and name attribute out of it
this.groups = [];
for(let key of Object.keys(data)) {
let group = {
"id": key,
"name": data[key]
};
this.groups.push(group);
}
// hide current group name paragraph element
this.shadowRoot.getElementById("connected-group-name").style.setProperty("display", "none");
// show dropdown menu to select a different group, therefore remove display: none
this.shadowRoot.getElementById("input-edit-group-name").style.removeProperty("display");
});
/*const projectName = this.shadowRoot.getElementById("connected-group-project-name").value;
const newLinkedGroupName = this.shadowRoot.getElementById("input-edit-group-name").value;
fetch(this.projectServiceURL + "/projects/changeGroup/", {
method: "POST",
headers: Auth.getAuthHeaderWithSub(),
body: JSON.stringify({
"name": projectName,
"access_token": Auth.getAccessToken(),
"newGroupNameId": newLinkedGroupName
})
}).then( response => {
if(!response.ok) throw Error(response.status);
console.log(typeof response)
console.log("ssssssss" + Object.keys(response));
return response.json();
})*/
}
/**
* Gets called when the "Group" icon of one of the displayed projects gets clicked and opens a dialog with
* information on the group which is currently connected to the project.
* @param project
*/
_onGroupChanged() {
const projectName = this.shadowRoot.getElementById("connected-group-project-name").innerText;
const newLinkedGroupName = this.shadowRoot.getElementById("input-edit-group-name").value;
if(newLinkedGroupName == undefined){
return;
}
var newLinkedGroupId = "";
for(let key of Object.keys(this.groups)) {
if(this.groups[key].name == newLinkedGroupName){
newLinkedGroupId = this.groups[key].id;
break;
}
}
fetch(this.projectServiceURL + "/projects/" + this.system + "/changeGroup/", {
method: "POST",
headers: Auth.getAuthHeaderWithSub(),
body: JSON.stringify({
"name": projectName,
"access_token": Auth.getAccessToken(),
"projectName": projectName,
"newGroupName": newLinkedGroupName,
"newGroupId": newLinkedGroupId
})
}).then( response => {
if(!response.ok) throw Error(response.status);
return response.json();
}).then(data => {
this._onGroupChangeDone(data);
}).catch(error => {
if(error.message == "401") {
// user is not authorized
// maybe the access token has expired
Auth.removeAuthDataFromLocalStorage();
// location.reload();
} else {
console.log(error);
}
});
}
/**
* Gets called when the "options" icon of one of the displayed projects gets clicked and opens a dialog with
* information on the group which is currently connected to the project and the possibility to delete the project.
* @param project
*/
openProjectOptionsDialog(project) {
this.projectOptionsSelected = project;
// reset the dialog
this.shadowRoot.getElementById("connected-group-name").style.removeProperty("display");
this.shadowRoot.getElementById("input-edit-group-name").style.setProperty("display", "none");
this.shadowRoot.getElementById("connected-group-project-name").innerText = project.name;
this.shadowRoot.getElementById("connected-group-name").innerText = project.groupName;
// open the dialog
this.shadowRoot.getElementById("dialog-project-options").open();
}
/**
* Gets called when the "delete project" button in the project option dialog is clicked.
* Opens another dialog to verify whether the project should really be deleted.
*/
showDeleteProjectDialog() {
// hide project options dialog
this.shadowRoot.getElementById("dialog-project-options").close();
// open delete dialog
this.shadowRoot.getElementById("dialog-delete-project").open();
}
/**
* Gets called if the user has verified that the project should be deleted.
*/
_deleteProject() {
let projectToDelete = this.projectOptionsSelected;
fetch(this.projectServiceURL + "/projects/" + this.system + "/" + projectToDelete.name, {
method: "DELETE",
headers: Auth.getAuthHeaderWithSub(),
body: JSON.stringify({
"access_token": Auth.getAccessToken()
})
}).then(response => {
if(response.ok) {
this.shadowRoot.getElementById("toast-success-deletion").show();
// since a project got deleted, reload projects from server
this.showProjects(false);
// switch to tab "My Projects"
this.tabSelected = 0;
this.shadowRoot.getElementById("my-and-all-projects").selected = 0;
}
else throw Error(response.status);
}).catch(error => {
});
}