Mattthew commited on
Commit
35e8599
1 Parent(s): cc13651

adding tag editing feature + many tag updates

Browse files

many tags hand checked for accuracy and updated. tag editing allow tags to be edited for offline use only

Files changed (4) hide show
  1. artists_and_tags.js +0 -0
  2. index.css +75 -6
  3. index.html +22 -5
  4. index.js +416 -29
artists_and_tags.js CHANGED
The diff for this file is too large to render. See raw diff
 
index.css CHANGED
@@ -434,12 +434,13 @@ footer.special #close_footer strong {
434
  #export textarea {
435
  resize: vertical;
436
  width: 100%;
437
- height: 200px;
438
  }
439
 
440
  #export .buttons {
441
  display: flex;
442
  flex-direction: row;
 
443
  }
444
 
445
  #export .buttons div {
@@ -452,6 +453,16 @@ footer.special #close_footer strong {
452
  opacity: 1;
453
  }
454
 
 
 
 
 
 
 
 
 
 
 
455
  #filtersHidingAll {
456
  display: none;
457
  font-size: 24px;
@@ -528,6 +539,47 @@ footer.special #close_footer strong {
528
  color: #aaa;
529
  }
530
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
531
  .image-item .firstN {
532
  margin-right: 8px;
533
  white-space: nowrap;
@@ -547,9 +599,7 @@ footer.special #close_footer strong {
547
  }
548
 
549
  .image-item .imgTools {
550
- display: flex;
551
- flex-direction: row;
552
- align-items: end;
553
  height: 100%;
554
  background-color: #666;
555
  opacity: 0;
@@ -561,7 +611,7 @@ footer.special #close_footer strong {
561
  }
562
 
563
  .image-item .imgTools > div {
564
- position: relative;
565
  opacity: 0.7;
566
  cursor: pointer;
567
  }
@@ -585,6 +635,8 @@ footer.special #close_footer strong {
585
  }
586
 
587
  .image-item .art_prev {
 
 
588
  width: 50px;
589
  height: 50px;
590
  background-color: #333;
@@ -592,14 +644,26 @@ footer.special #close_footer strong {
592
  }
593
 
594
  .image-item .art_next {
 
 
595
  width: 50px;
596
  height: 50px;
597
  background-color: #333;
598
  border-radius: 4px 0px 0px 0px;
599
  }
600
 
 
 
 
 
 
 
 
 
 
601
  .image-item .art_star {
602
- flex-grow: 1;
 
603
  width: 128px;
604
  height: 100%;
605
  }
@@ -711,6 +775,11 @@ footer.special #close_footer strong {
711
  filter: grayscale(0%);
712
  }
713
 
 
 
 
 
 
714
  #layout.edit_mode #toggles {
715
  width: calc(100% - 40px);
716
  transition: width 200ms ease-out;
 
434
  #export textarea {
435
  resize: vertical;
436
  width: 100%;
437
+ height: 150px;
438
  }
439
 
440
  #export .buttons {
441
  display: flex;
442
  flex-direction: row;
443
+ margin-bottom: 20px;
444
  }
445
 
446
  #export .buttons div {
 
453
  opacity: 1;
454
  }
455
 
456
+ #export #import_favorites_button, #export #delete_edits_button {
457
+ opacity: 1;
458
+ color: #f77;
459
+ }
460
+
461
+ #export #import_favorites_button:hover, #export #delete_edits_button:hover {
462
+ opacity: 1;
463
+ color: #fff;
464
+ }
465
+
466
  #filtersHidingAll {
467
  display: none;
468
  font-size: 24px;
 
539
  color: #aaa;
540
  }
541
 
542
+ .image-item h4.edit_mode {
543
+ z-index: 1;
544
+ top: 40px;
545
+ width: 206px;
546
+ height: 304px;
547
+ box-sizing: border-box;
548
+ padding: 6px 4px;
549
+ display: flex;
550
+ flex-direction: column;
551
+ overflow-y: scroll;
552
+ overflow-x: hidden;
553
+ background-color: #fbc1c1;
554
+ border-radius: 4px;
555
+ border: 2px solid #b30000;
556
+ box-shadow: inset 0px 3px 4px #00000096;
557
+ text-align: left;
558
+ font-size: 14px;
559
+ color: #000;
560
+ opacity: 1;
561
+ cursor: default;
562
+ }
563
+
564
+ .image-item h4.edit_mode label {
565
+ cursor: pointer;
566
+ }
567
+
568
+ #edit_mode_helper {
569
+ position: relative;
570
+ z-index: 2;
571
+ display: flex;
572
+ flex-direction: column;
573
+ padding: 10px 0 0 20px;
574
+ background-color: white;
575
+ color: #777;
576
+ }
577
+
578
+ #edit_mode_helper span {
579
+ white-space: nowrap;
580
+ cursor: pointer;
581
+ }
582
+
583
  .image-item .firstN {
584
  margin-right: 8px;
585
  white-space: nowrap;
 
599
  }
600
 
601
  .image-item .imgTools {
602
+ position: relative;
 
 
603
  height: 100%;
604
  background-color: #666;
605
  opacity: 0;
 
611
  }
612
 
613
  .image-item .imgTools > div {
614
+ position: absolute;
615
  opacity: 0.7;
616
  cursor: pointer;
617
  }
 
635
  }
636
 
637
  .image-item .art_prev {
638
+ bottom: 0;
639
+ left: 0;
640
  width: 50px;
641
  height: 50px;
642
  background-color: #333;
 
644
  }
645
 
646
  .image-item .art_next {
647
+ bottom: 0;
648
+ right: 0;
649
  width: 50px;
650
  height: 50px;
651
  background-color: #333;
652
  border-radius: 4px 0px 0px 0px;
653
  }
654
 
655
+ .image-item .art_edit {
656
+ top: 0;
657
+ right: 0;
658
+ width: 50px;
659
+ height: 50px;
660
+ background-color: #333;
661
+ border-radius: 0px 0px 0px 4px;
662
+ }
663
+
664
  .image-item .art_star {
665
+ top: 0;
666
+ left: 64px;
667
  width: 128px;
668
  height: 100%;
669
  }
 
775
  filter: grayscale(0%);
776
  }
777
 
778
+ .image-item.edit_mode {
779
+ border: 1px solid #f00;
780
+ box-shadow: 0 0px 15px #ff0000b8;
781
+ }
782
+
783
  #layout.edit_mode #toggles {
784
  width: calc(100% - 40px);
785
  transition: width 200ms ease-out;
index.html CHANGED
@@ -22,6 +22,7 @@
22
  <li><strong>tags</strong> toggles those checkmarks</li>
23
  <li><strong>star</strong> marks them as favorite</li>
24
  <li>🎨, 🧑, and 🏞️ switches the prompt/image</li>
 
25
  </ul>
26
  <h3>Permissive filter</h3>
27
  <ul>
@@ -42,10 +43,14 @@
42
  </ul>
43
  <h3>When using Stable Diffusion</h3>
44
  <ul>
45
- <li>Reproduce these styles with the prompt with "by {Artist full name}, ...."
46
  <li>Adding the style tags to the prompt can also help</li>
47
  <li>You can combine the styles of two are more artists</li>
48
  </ul>
 
 
 
 
49
  </div>
50
  </div>
51
  <div id="about" class="information">
@@ -104,11 +109,22 @@
104
  </div>
105
  <div id="export" class="information">
106
  <div>
107
- <h2>Export/Import favorited artists</h2>
108
- <textarea placeholder="You haven't favorited any artists yet."></textarea>
 
 
 
 
 
 
 
 
 
 
 
 
109
  <div class="buttons">
110
- <div id="export_to_clipboard">copy to clipboard</div>
111
- <div id="export_import">import</div>
112
  </div>
113
  </div>
114
  </div>
@@ -186,6 +202,7 @@
186
  <div class="art_prev"></div>
187
  <div class="art_star"></div>
188
  <div class="art_next"></div>
 
189
  </div>
190
  <div class="imgBox">
191
  <img alt="First Last - art" src="images/SDXL_1_0/First_Last-artwork.jpg">
 
22
  <li><strong>tags</strong> toggles those checkmarks</li>
23
  <li><strong>star</strong> marks them as favorite</li>
24
  <li>🎨, 🧑, and 🏞️ switches the prompt/image</li>
25
+ <li>✍️ enters tag-editing mode, see below</li>
26
  </ul>
27
  <h3>Permissive filter</h3>
28
  <ul>
 
43
  </ul>
44
  <h3>When using Stable Diffusion</h3>
45
  <ul>
46
+ <li>Reproduce these styles with the prompt with "by {Artist full name}, ...."</li>
47
  <li>Adding the style tags to the prompt can also help</li>
48
  <li>You can combine the styles of two are more artists</li>
49
  </ul>
50
+ <h3>✍️ Tag-editing is only for offline use!</h3>
51
+ <ul>
52
+ <li>If you are viewing this from Hugging Face, your tag edits won't be applied. They'll be saved and applied if you've downloaded the repository and are viewing your local file. In both cases, your edits will be available for export 📥 as text. If you want to suggestions for tag improvements for everyone's benefit, then edit tags, copy the text from 📥, and post it as a comment to Hugging Face. I will incorporate accurate edits ASAP. Thank you for contributing!</li>
53
+ </ul>
54
  </div>
55
  </div>
56
  <div id="about" class="information">
 
109
  </div>
110
  <div id="export" class="information">
111
  <div>
112
+ <h3>Export/Import favorited artists</h2>
113
+ <textarea id="export_favorites_list" placeholder="You haven't favorited any artists yet."></textarea>
114
+ <div class="buttons">
115
+ <div id="export_favorites_button">copy to clipboard</div>
116
+ <div id="import_favorites_button">import</div>
117
+ </div>
118
+ <h3>Export tag-edits to send to author</h2>
119
+ <textarea id="export_edits_list" placeholder="You haven't edited any tags yet."></textarea>
120
+ <div class="buttons">
121
+ <div id="export_edits_button">copy to clipboard</div>
122
+ <div id="delete_edits_button">restore official database</div>
123
+ </div>
124
+ <h3>Export database</h2>
125
+ <textarea id="export_artists_list"></textarea>
126
  <div class="buttons">
127
+ <div id="export_artists_button">copy to clipboard</div>
 
128
  </div>
129
  </div>
130
  </div>
 
202
  <div class="art_prev"></div>
203
  <div class="art_star"></div>
204
  <div class="art_next"></div>
205
+ <div class="art_edit"></div>
206
  </div>
207
  <div class="imgBox">
208
  <img alt="First Last - art" src="images/SDXL_1_0/First_Last-artwork.jpg">
index.js CHANGED
@@ -7,17 +7,20 @@ var timer;
7
  var artTypes = ['🎨','🧑','🏞️'];
8
  var imgTypeShown = 0;
9
  var log = '';
10
- var editMode = false;
11
  var windowWidth = 0;
12
  var gutterStartPosX, mouseStartPosX, gutterEndPercentX
13
  var style, stylesheet, imgHoverRule;
14
-
 
15
  //
16
  //
17
  //
18
  // functions
19
  function startUp() {
 
20
  updateFooter();
 
21
  insertArtists();
22
  insertCheckboxesFromArtistsData();
23
  insertCheckboxesFromCategories();
@@ -37,6 +40,22 @@ function startUp() {
37
  getStyleRuleForDrag();
38
  }
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  function updateFooter() {
41
  let proto = window.location.protocol;
42
  if (proto.startsWith('http')) {
@@ -48,6 +67,39 @@ function updateFooter() {
48
  }
49
  }
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  function insertArtists() {
52
  // artistsData is defined in the artists_and_tags.js file
53
  let missingFiles = '';
@@ -56,7 +108,7 @@ function insertArtists() {
56
  var last = artist[0];
57
  var first = artist[1];
58
  var tags1 = artist[2].replaceAll('|', ' ').toLowerCase(); // for classes
59
- var tags2 = artist[2].replaceAll('|', ', ').toLowerCase(); // for display
60
  // class names can't start with a number, but some tags do
61
  // in these cases we prepend the class with 'qqqq-'
62
  tags1 = tags1.replace(/(^|\s)(\d)/g, '$1qqqq-$2');
@@ -82,7 +134,7 @@ function insertArtists() {
82
  h3.title = 'copy to clipboard';
83
  var h4 = document.createElement('h4');
84
  h4.textContent = tags2;
85
- h4.title = 'check/uncheck these tags';
86
  itemHeader.appendChild(h4);
87
  itemDiv.appendChild(itemHeader);
88
  var box = document.createElement('div');
@@ -106,6 +158,12 @@ function insertArtists() {
106
  artNextSpan.textContent = '🏞️';
107
  artNext.appendChild(artNextSpan);
108
  imgTools.appendChild(artNext);
 
 
 
 
 
 
109
  box.appendChild(imgTools);
110
  var imgBox = document.createElement('div');
111
  imgBox.className = 'imgBox';
@@ -438,11 +496,16 @@ function updateArtistsCountPerTag(whoCalled) {
438
  if(deprecatedCheckbox.checked) {
439
  count = filteredDivs.length;
440
  } else {
441
- count = matchingDivs.length;
442
  }
443
  checkbox.parentNode.classList.remove('no_matches');
444
  checkbox.parentNode.querySelector('input').disabled = false;
445
- checkbox.parentNode.querySelector('.count').textContent = ' - ' + count.toLocaleString();
 
 
 
 
 
446
  }
447
  });
448
  updateArtistsCountPerCategory();
@@ -463,7 +526,7 @@ function updateArtistsCountPerTag(whoCalled) {
463
  if(deprecatedCheckbox.checked) {
464
  count = filteredDivs.length;
465
  } else {
466
- count = matchingDivs.length;
467
  }
468
  if(count == 0) {
469
  checkbox.parentNode.classList.add('no_matches');
@@ -705,9 +768,10 @@ function showAbout() {
705
  }
706
 
707
  function showExport() {
708
- document.getElementById('export').classList.add('shown');
709
  hideToggles();
710
- var textarea = document.getElementById('export').getElementsByTagName('textarea')[0];
 
 
711
  var favorites = localStorage.getItem('favoritedArtists');
712
  var value = '';
713
  if(favorites) {
@@ -720,21 +784,54 @@ function showExport() {
720
  }
721
  }
722
  value += '\r\n\r\nTo import these favorites later, click "copy to clipboard" and save to any file. Then paste the text from that file into this text box, and click "import". The imported text must contain the JSON string below (the curly brackets and what\'s between them). It must not contain any other more than one set of curly brackets.\r\n\r\n' + favorites;
723
- textarea.value = value;
724
  } else {
725
  value += 'You haven\'t favorited any artists yet.\r\n\r\n';
726
  value += 'To import favorites that you exported earlier, paste the text into this text box, and click "import".';
727
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728
  }
729
 
730
- function copyExportToClipboard() {
731
- var favorites = document.getElementById('export').getElementsByTagName('textarea')[0].value;
732
- navigator.clipboard.writeText(favorites);
733
- doAlert('Favorites copied to clipboard!',1);
 
 
 
 
 
 
 
 
 
 
 
 
734
  }
735
 
736
  function importFavorites() {
737
- let el = document.getElementById('export').getElementsByTagName('textarea')[0];
738
  let favorites = el.value;
739
  let startCount = (favorites.match(/{/g) || []).length;
740
  let endCount = (favorites.match(/}/g) || []).length;
@@ -999,9 +1096,9 @@ function storeMostUsedState(label) {
999
 
1000
  function enterExitEditMostUsedMode(doExit) {
1001
  let inputs = Array.from(document.querySelectorAll('input'));
1002
- if(editMode || doExit) {
1003
  // exit edit mode
1004
- editMode = false;
1005
  document.getElementById('edit_most_used').textContent = 'edit';
1006
  document.getElementById('layout').classList.remove('edit_mode');
1007
  inputs.forEach(function(input) {
@@ -1018,7 +1115,7 @@ function enterExitEditMostUsedMode(doExit) {
1018
  updateArtistsCountPerCategory();
1019
  } else {
1020
  // enter edit mode
1021
- editMode = true;
1022
  document.getElementById('edit_most_used').textContent = 'exit editing';
1023
  document.getElementById('layout').classList.add('edit_mode');
1024
  inputs.forEach(function(input) {
@@ -1166,12 +1263,23 @@ function copyStuffToClipboard(item,stuff) {
1166
  if(stuff == 'name') {
1167
  var str = item.closest('.image-item').getElementsByClassName('firstN')[0].textContent +
1168
  ' ' + item.closest('.image-item').getElementsByClassName('lastN')[0].textContent;
1169
- doAlert('Copied to name clipboard!',1);
 
 
 
 
 
 
1170
  } else if(stuff == 'tags') {
1171
  var str = item.textContent;
1172
- doAlert('Copied to tags clipboard!',1);
 
 
 
 
 
 
1173
  }
1174
- navigator.clipboard.writeText(str);
1175
  }
1176
 
1177
  function toggleThisArtistsTags(tagsStr) {
@@ -1345,6 +1453,267 @@ function movePartition(e) {
1345
  }
1346
  }
1347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1348
  //
1349
  //
1350
  //
@@ -1452,14 +1821,26 @@ document.addEventListener("DOMContentLoaded", function() {
1452
  storeOptionsState();
1453
  });
1454
  // add information event listeners
1455
- var export_to_clipboard = document.getElementById('export_to_clipboard');
1456
- export_to_clipboard.addEventListener('click', function(e) {
1457
- copyExportToClipboard();
1458
  });
1459
- var export_import = document.getElementById('export_import');
1460
- export_import.addEventListener('click', function(e) {
1461
  importFavorites();
1462
  });
 
 
 
 
 
 
 
 
 
 
 
 
1463
  var information = document.querySelectorAll('.information');
1464
  information.forEach(function(element) {
1465
  element.addEventListener('mouseleave', function(e) {
@@ -1502,13 +1883,14 @@ document.addEventListener("DOMContentLoaded", function() {
1502
  if (event.key === 'Escape' || event.keyCode === 27) {
1503
  // event.key for modern browsers, event.keyCode for older ones
1504
  enterExitEditMostUsedMode('exit');
 
1505
  hideInformation();
1506
  }
1507
  });
1508
  var labels = document.querySelectorAll('label');
1509
  Array.from(labels).forEach(function(label) {
1510
  label.addEventListener('click', function(e) {
1511
- if(editMode) {
1512
  addRemoveIsMostUsed(this);
1513
  storeMostUsedState(this);
1514
  }
@@ -1540,12 +1922,17 @@ document.addEventListener("DOMContentLoaded", function() {
1540
  rotatePromptsImages();
1541
  storeOptionsState();
1542
  });
 
 
 
1543
  imageItem.getElementsByTagName('h3')[0].addEventListener('click', function(e) {
1544
  copyStuffToClipboard(this,'name');
1545
  });
1546
  imageItem.getElementsByTagName('h4')[0].addEventListener('click', function(e) {
1547
- copyStuffToClipboard(this, 'tags')
1548
- // toggleThisArtistsTags(this.textContent);
 
 
1549
  });
1550
  });
1551
  // add gutter event listener
 
7
  var artTypes = ['🎨','🧑','🏞️'];
8
  var imgTypeShown = 0;
9
  var log = '';
10
+ var editMostUsedMode = false;
11
  var windowWidth = 0;
12
  var gutterStartPosX, mouseStartPosX, gutterEndPercentX
13
  var style, stylesheet, imgHoverRule;
14
+ var tagsConcatenated = new Set();
15
+ var editedArtists = new Set();
16
  //
17
  //
18
  //
19
  // functions
20
  function startUp() {
21
+ updateTagsConcatenated();
22
  updateFooter();
23
+ loadEditedArtists();
24
  insertArtists();
25
  insertCheckboxesFromArtistsData();
26
  insertCheckboxesFromCategories();
 
40
  getStyleRuleForDrag();
41
  }
42
 
43
+ function updateTagsConcatenated() {
44
+ // this set is used for tag editing mode
45
+ for (var i=0, il=tagCategories.length; i<il; i++) {
46
+ for (var j=1, jl=tagCategories[i].length; j<jl; j++) {
47
+ // j=1 because we don't want to include the category name
48
+ // we also exclude tags that have the in the format of "added-YYYY-MM-DD"
49
+ let tag = tagCategories[i][j].replace(/, added-(\d|-)*/g,'');
50
+ tagsConcatenated.add(tag);
51
+ }
52
+ }
53
+ let tagsConcatenatedArray = Array.from(tagsConcatenated).sort(function (a, b) {
54
+ return a.toLowerCase().localeCompare(b.toLowerCase());
55
+ });
56
+ tagsConcatenated = new Set(tagsConcatenatedArray);
57
+ }
58
+
59
  function updateFooter() {
60
  let proto = window.location.protocol;
61
  if (proto.startsWith('http')) {
 
67
  }
68
  }
69
 
70
+ function loadEditedArtists() {
71
+ const arr = JSON.parse(localStorage.getItem('editedArtists')) || [];
72
+ editedArtists = new Set(arr);
73
+ let proto = window.location.protocol;
74
+ let anyChanges = false;
75
+ for (let i=0, il=artistsData.length; i<il; i++) {
76
+ // find match in artistsData if first and last names match
77
+ let artist = artistsData[i];
78
+ let artistFound = Array.from(editedArtists).find(editedA => editedA[0] === artist[0] && editedA[1] === artist[1]);
79
+ if(artistFound) {
80
+ // check if the edit now matches the original
81
+ let match = true;
82
+ for (let j=0, jl=artist.length; j<jl; j++) {
83
+ if (artist[j] !== artistFound[j]) {
84
+ match = false;
85
+ }
86
+ }
87
+ if(match) {
88
+ anyChanges = true;
89
+ editedArtists.delete(artistFound);
90
+ } else {
91
+ if (!proto.startsWith('http')) {
92
+ // if this is a local file, then update artistData with the saved edits
93
+ artistsData[i] = artistFound;
94
+ }
95
+ }
96
+ }
97
+ }
98
+ if(anyChanges) {
99
+ localStorage.setItem('editedArtists', JSON.stringify(Array.from(editedArtists)));
100
+ }
101
+ }
102
+
103
  function insertArtists() {
104
  // artistsData is defined in the artists_and_tags.js file
105
  let missingFiles = '';
 
108
  var last = artist[0];
109
  var first = artist[1];
110
  var tags1 = artist[2].replaceAll('|', ' ').toLowerCase(); // for classes
111
+ var tags2 = artist[2].replaceAll('|', ', '); // for display
112
  // class names can't start with a number, but some tags do
113
  // in these cases we prepend the class with 'qqqq-'
114
  tags1 = tags1.replace(/(^|\s)(\d)/g, '$1qqqq-$2');
 
134
  h3.title = 'copy to clipboard';
135
  var h4 = document.createElement('h4');
136
  h4.textContent = tags2;
137
+ h4.title = 'copy to clipboard';
138
  itemHeader.appendChild(h4);
139
  itemDiv.appendChild(itemHeader);
140
  var box = document.createElement('div');
 
158
  artNextSpan.textContent = '🏞️';
159
  artNext.appendChild(artNextSpan);
160
  imgTools.appendChild(artNext);
161
+ var artEdit = document.createElement('div');
162
+ artEdit.className = 'art_edit';
163
+ var artEditSpan = document.createElement('span');
164
+ artEditSpan.textContent = '✍️';
165
+ artEdit.appendChild(artEditSpan);
166
+ imgTools.appendChild(artEdit);
167
  box.appendChild(imgTools);
168
  var imgBox = document.createElement('div');
169
  imgBox.className = 'imgBox';
 
496
  if(deprecatedCheckbox.checked) {
497
  count = filteredDivs.length;
498
  } else {
499
+ count = matchingDivs.length;
500
  }
501
  checkbox.parentNode.classList.remove('no_matches');
502
  checkbox.parentNode.querySelector('input').disabled = false;
503
+ // editing tags can cause count to be null
504
+ if(count) {
505
+ checkbox.parentNode.querySelector('.count').textContent = ' - ' + count.toLocaleString();
506
+ } else {
507
+ checkbox.parentNode.querySelector('.count').textContent = ' - ' + 'edited';
508
+ }
509
  }
510
  });
511
  updateArtistsCountPerCategory();
 
526
  if(deprecatedCheckbox.checked) {
527
  count = filteredDivs.length;
528
  } else {
529
+ count = matchingDivs.length;
530
  }
531
  if(count == 0) {
532
  checkbox.parentNode.classList.add('no_matches');
 
768
  }
769
 
770
  function showExport() {
 
771
  hideToggles();
772
+ document.getElementById('export').classList.add('shown');
773
+ // favorites
774
+ var textareaF = document.getElementById('export_favorites_list');
775
  var favorites = localStorage.getItem('favoritedArtists');
776
  var value = '';
777
  if(favorites) {
 
784
  }
785
  }
786
  value += '\r\n\r\nTo import these favorites later, click "copy to clipboard" and save to any file. Then paste the text from that file into this text box, and click "import". The imported text must contain the JSON string below (the curly brackets and what\'s between them). It must not contain any other more than one set of curly brackets.\r\n\r\n' + favorites;
787
+ textareaF.value = value;
788
  } else {
789
  value += 'You haven\'t favorited any artists yet.\r\n\r\n';
790
  value += 'To import favorites that you exported earlier, paste the text into this text box, and click "import".';
791
  }
792
+ // edits
793
+ var textareaE = document.getElementById('export_edits_list');
794
+ let editedArtistsArr = Array.from(editedArtists);
795
+ if(editedArtistsArr.length > 0) {
796
+ value = 'Post a comment with these edits on Hugging Face:\r\n\r\n';
797
+ for(i=0,il=editedArtistsArr.length;i<il;i++) {
798
+ let edit = editedArtistsArr[i];
799
+ value += '["'+edit[0]+'","'+edit[1]+'","'+edit[2]+'",'+edit[3]+'],\r\n';
800
+ }
801
+ } else {
802
+ value = '';
803
+ }
804
+ textareaE.value = value;
805
+ // db
806
+ var textareaA = document.getElementById('export_artists_list');
807
+ value = '';
808
+ for(i=0,il=artistsData.length;i<il;i++) {
809
+ let edit = artistsData[i];
810
+ value += '["'+edit[0]+'","'+edit[1]+'","'+edit[2]+'",'+edit[3]+'],\r\n';
811
+ }
812
+ textareaA.value = value;
813
  }
814
 
815
+ function exportTextarea(type) {
816
+ let contents = '';
817
+ if(type == 'favorites') {
818
+ contents = document.getElementById('export_favorites_list').value;
819
+ } else if(type == 'edits') {
820
+ contents = document.getElementById('export_edits_list').value;
821
+ } else if(type == 'artists') {
822
+ contents = document.getElementById('export_artists_list').value;
823
+ }
824
+ navigator.clipboard.writeText(contents)
825
+ .then(() => {
826
+ doAlert(type + ' copied to clipboard!',1);
827
+ })
828
+ .catch(() => {
829
+ doAlert('😭😭 Can\'t access clipboard',1);
830
+ });
831
  }
832
 
833
  function importFavorites() {
834
+ let el = document.getElementById('export_favorites_list');
835
  let favorites = el.value;
836
  let startCount = (favorites.match(/{/g) || []).length;
837
  let endCount = (favorites.match(/}/g) || []).length;
 
1096
 
1097
  function enterExitEditMostUsedMode(doExit) {
1098
  let inputs = Array.from(document.querySelectorAll('input'));
1099
+ if(editMostUsedMode || doExit) {
1100
  // exit edit mode
1101
+ editMostUsedMode = false;
1102
  document.getElementById('edit_most_used').textContent = 'edit';
1103
  document.getElementById('layout').classList.remove('edit_mode');
1104
  inputs.forEach(function(input) {
 
1115
  updateArtistsCountPerCategory();
1116
  } else {
1117
  // enter edit mode
1118
+ editMostUsedMode = true;
1119
  document.getElementById('edit_most_used').textContent = 'exit editing';
1120
  document.getElementById('layout').classList.add('edit_mode');
1121
  inputs.forEach(function(input) {
 
1263
  if(stuff == 'name') {
1264
  var str = item.closest('.image-item').getElementsByClassName('firstN')[0].textContent +
1265
  ' ' + item.closest('.image-item').getElementsByClassName('lastN')[0].textContent;
1266
+ navigator.clipboard.writeText(str)
1267
+ .then(() => {
1268
+ doAlert('Copied to name clipboard!',1);
1269
+ })
1270
+ .catch(() => {
1271
+ doAlert('😭😭 Can\'t access clipboard',1);
1272
+ });
1273
  } else if(stuff == 'tags') {
1274
  var str = item.textContent;
1275
+ navigator.clipboard.writeText(str)
1276
+ .then(() => {
1277
+ doAlert('Copied to tags clipboard!',1);
1278
+ })
1279
+ .catch(() => {
1280
+ doAlert('😭😭 Can\'t access clipboard',1);
1281
+ });
1282
  }
 
1283
  }
1284
 
1285
  function toggleThisArtistsTags(tagsStr) {
 
1453
  }
1454
  }
1455
 
1456
+ function editTagsClicked(clickedImageItem) {
1457
+ let indicatorEl = clickedImageItem.querySelector('.art_edit span');
1458
+ if(indicatorEl.textContent == '✍️') {
1459
+ let artistWasInEditMode = editTagsFindArtistInEditMode(clickedImageItem);
1460
+ if(!artistWasInEditMode) {
1461
+ doAlert('Read help ⁉️ first',1);
1462
+ }
1463
+ editTagsEnterEditMode(clickedImageItem);
1464
+ } else {
1465
+ editTagsFindArtistInEditMode();
1466
+ }
1467
+ }
1468
+
1469
+ function editTagsEnterEditMode(imageItem) {
1470
+ // enter edit mode for item
1471
+ let indicatorEl = imageItem.querySelector('.art_edit span');
1472
+ indicatorEl.textContent = '❌';
1473
+ let tagArea = imageItem.querySelector('h4');
1474
+ tagArea.title = '';
1475
+ tagArea.classList.add('edit_mode');
1476
+ imageItem.classList.add('edit_mode');
1477
+ let firstN = imageItem.querySelector('.firstN').textContent;
1478
+ let lastN = imageItem.querySelector('.lastN').textContent;
1479
+ let tagList = [];
1480
+ for (let i=0, il=artistsData.length; i<il; i++) {
1481
+ let artist = artistsData[i];
1482
+ if(artist[0] == lastN && artist[1] == firstN) {
1483
+ let tagListStr = artist[2].replace(/\|added-(\d|-)*/g,'');
1484
+ tagList = tagListStr.split('|');
1485
+ tagList.unshift(artist[3]);
1486
+ break;
1487
+ }
1488
+ }
1489
+ tagArea.textContent = '';
1490
+ for (var i=0, il=tagList.length; i<il; i++) {
1491
+ addTagToEditor(tagArea,tagList[i]);
1492
+ }
1493
+ var adder = document.createElement('input');
1494
+ adder.type = 'text';
1495
+ adder.name = 'new_tag';
1496
+ adder.placeholder = 'add another tag';
1497
+ adder.dataset.match = '';
1498
+ adder.style.marginTop = '10px';
1499
+ tagArea.appendChild(adder);
1500
+ // add event listeners
1501
+ adder.addEventListener('focus', function(e) {
1502
+ var helper = document.createElement('span');
1503
+ helper.id = 'edit_mode_helper';
1504
+ this.parentNode.appendChild(helper);
1505
+ });
1506
+ adder.addEventListener('keyup', function(e) {
1507
+ searchForTags(this,e,tagList);
1508
+ });
1509
+ adder.addEventListener('blur', function(e) {
1510
+ this.value = '';
1511
+ window.setTimeout(function() {
1512
+ // need delay to allow helper row to be clicked
1513
+ if(document.getElementById('edit_mode_helper')) {
1514
+ document.getElementById('edit_mode_helper').remove();
1515
+ }
1516
+ }, 100);
1517
+ });
1518
+ }
1519
+
1520
+ function editTagsFindArtistInEditMode(clickedImageItem) {
1521
+ let imageItems = document.querySelectorAll('.image-item');
1522
+ imageItems.forEach(function(imageItem) {
1523
+ if(imageItem !== clickedImageItem) {
1524
+ // for any other other artist in editing mode, exit
1525
+ let indicatorEl = imageItem.querySelector('.art_edit span');
1526
+ if(indicatorEl.textContent == '❌') {
1527
+ editTagsExitEditMode(imageItem);
1528
+ // let caller know that an artistWasInEditMode
1529
+ return true;
1530
+ }
1531
+ }
1532
+ });
1533
+ }
1534
+
1535
+ function editTagsExitEditMode(imageItem) {
1536
+ // exit item edit mode for item
1537
+ let indicatorEl = imageItem.querySelector('.art_edit span');
1538
+ indicatorEl.textContent = '✍️';
1539
+ let tagArea = imageItem.querySelector('h4');
1540
+ tagArea.title = 'copy to clipboard';
1541
+ tagArea.classList.remove('edit_mode');
1542
+ imageItem.classList.remove('edit_mode');
1543
+ let tagList = '';
1544
+ let tagLabels = tagArea.querySelectorAll('label');
1545
+ tagLabels.forEach(function(label) {
1546
+ let input = label.querySelector('input');
1547
+ if(input.checked) {
1548
+ tagList += input.value + ', ';
1549
+ label.remove();
1550
+ }
1551
+ });
1552
+ tagArea.querySelector('input').remove();
1553
+ tagArea.textContent = tagList.substring(0,tagList.length-2);
1554
+ }
1555
+
1556
+ function addTagToEditor(tagArea, tagName) {
1557
+ var label = document.createElement('label');
1558
+ var input = document.createElement('input');
1559
+ input.type = 'checkbox';
1560
+ if(tagName === true || tagName === false) {
1561
+ input.name = 'known'
1562
+ input.value = 'known';
1563
+ // in db, true = hide unknown, but here true = known
1564
+ if(tagName) {
1565
+ input.checked = false;
1566
+ } else {
1567
+ input.checked = true;
1568
+ }
1569
+ tagName = 'known to SDXL'
1570
+ } else {
1571
+ input.name = tagName
1572
+ input.value = tagName;
1573
+ input.checked = true;
1574
+ }
1575
+ var span = document.createElement('span');
1576
+ span.textContent = tagName;
1577
+ label.appendChild(input);
1578
+ label.appendChild(span);
1579
+ tagArea.appendChild(label);
1580
+ // event listener
1581
+ input.addEventListener('change', function(e) {
1582
+ saveTagsForArtist(tagArea);
1583
+ });
1584
+ }
1585
+
1586
+ function searchForTags(input, event, tagList) {
1587
+ if(input.dataset.match != '') {
1588
+ event.preventDefault();
1589
+ if(event.key === 'Backspace' || event.keyCode === 8) {
1590
+ input.value = '';
1591
+ input.dataset.match = '';
1592
+ } else if (event.key === 'Return' || event.keyCode === 13) {
1593
+ input.value = '';
1594
+ input.dispatchEvent(new Event('blur'));
1595
+ insertTag(input,input.dataset.match);
1596
+ input.dataset.match = '';
1597
+ } else {
1598
+ input.value = input.dataset.match;
1599
+ }
1600
+ return;
1601
+ }
1602
+ let helper = document.getElementById('edit_mode_helper');
1603
+ helper.innerHTML = '';
1604
+ let matches = 0;
1605
+ let match = '';
1606
+ let range = 'start'
1607
+ tagsConcatenated.forEach(function(tag) {
1608
+ for (var i=0, il=tagList.length; i<il; i++) {
1609
+ if(tag.toLowerCase() == tagList[i].toLowerCase()) {
1610
+ return;
1611
+ }
1612
+ }
1613
+ if(tag.toLowerCase().indexOf(input.value.toLowerCase()) == 0) {
1614
+ range = 'continue';
1615
+ let matchSpan = document.createElement('span');
1616
+ matchSpan.textContent = tag;
1617
+ helper.appendChild(matchSpan);
1618
+ matchSpan.addEventListener('click', function(e) {
1619
+ insertTag(e.target,e.target.textContent);
1620
+ });
1621
+ match = tag;
1622
+ matches++;
1623
+ } else {
1624
+ if(range != 'start') {
1625
+ range = 'stop';
1626
+ }
1627
+ }
1628
+ if(range == 'stop') {
1629
+ return;
1630
+ }
1631
+ });
1632
+ if(matches == 1) {
1633
+ input.value = match;
1634
+ event.preventDefault();
1635
+ input.dataset.match = match;
1636
+ }
1637
+ }
1638
+
1639
+ function insertTag(matchSpan,tag) {
1640
+ let tagArea = matchSpan.closest('h4');
1641
+ let input = tagArea.querySelector('input[type="text"]');
1642
+ addTagToEditor(tagArea,tag);
1643
+ tagArea.appendChild(input);
1644
+ document.getElementById('edit_mode_helper').remove();
1645
+ saveTagsForArtist(tagArea);
1646
+ timer = setTimeout(focusInput.bind(this, input), 100);
1647
+ }
1648
+
1649
+ function focusInput(input) {
1650
+ input.focus();
1651
+ }
1652
+
1653
+ function saveTagsForArtist(tagArea) {
1654
+ // get new tags
1655
+ let tagLabels = tagArea.querySelectorAll('label');
1656
+ let newTagsArr = [];
1657
+ let artistKnown = true;
1658
+ tagLabels.forEach(function(label) {
1659
+ let input = label.querySelector('input');
1660
+ if(input.value == 'known') {
1661
+ artistKnown = input.checked;
1662
+ } else {
1663
+ if(input.checked) {
1664
+ newTagsArr.push(input.value);
1665
+ }
1666
+ }
1667
+ });
1668
+ // find match in artistsData
1669
+ let firstN = tagArea.closest('.image-item').querySelector('.firstN').textContent;
1670
+ let lastN = tagArea.closest('.image-item').querySelector('.lastN').textContent;
1671
+ let edit = [];
1672
+ for (let i=0, il=artistsData.length; i<il; i++) {
1673
+ let artist = artistsData[i];
1674
+ if(artist[0] == lastN && artist[1] == firstN) {
1675
+ // artists can have a tag in the format of "added-YYYY-MM-DD"
1676
+ // this was stripped earlier, so we need to add it back in
1677
+ let oldTagsArr = artist[2].split('|');
1678
+ for (let j=oldTagsArr.length-1; j>=0; j--) {
1679
+ // loop backwards because it should be at the end
1680
+ if(oldTagsArr[j].match(/added-(\d|-)*/)) {
1681
+ newTagsArr.push(oldTagsArr[j]);
1682
+ }
1683
+ }
1684
+ let newTagsStr = newTagsArr.join('|');
1685
+ artist[2] = newTagsStr;
1686
+ // in db, true = hide unknown, but here true = known
1687
+ if(artistKnown) {
1688
+ artist[3] = false;
1689
+ } else {
1690
+ artist[3] = true;
1691
+ }
1692
+ edit = artist;
1693
+ break;
1694
+ }
1695
+ }
1696
+ // replace old edits with new edits
1697
+ for (let i=0, il=editedArtists.length; i<il; i++) {
1698
+ let oldEdit = editedArtists[i];
1699
+ if(edit[0] == oldEdit[0] && edit[1] == oldEdit[1]) {
1700
+ editedArtists.delete(oldEdit);
1701
+ }
1702
+ }
1703
+ editedArtists.add(edit)
1704
+ // save edited artists locally
1705
+ localStorage.setItem('editedArtists', JSON.stringify(Array.from(editedArtists)));
1706
+ }
1707
+
1708
+ function deleteAllEdits() {
1709
+ if(confirm('This will delete all of your edits. Are you sure?')) {
1710
+ localStorage.removeItem('editedArtists');
1711
+ alert('official database restored! this page will reload...');
1712
+ location.reload();
1713
+ } else {
1714
+ alert('restore was cancelled!');
1715
+ }
1716
+ }
1717
  //
1718
  //
1719
  //
 
1821
  storeOptionsState();
1822
  });
1823
  // add information event listeners
1824
+ var export_favorites = document.getElementById('export_favorites_button');
1825
+ export_favorites.addEventListener('click', function(e) {
1826
+ exportTextarea('favorites');
1827
  });
1828
+ var import_favorites = document.getElementById('import_favorites_button');
1829
+ import_favorites.addEventListener('click', function(e) {
1830
  importFavorites();
1831
  });
1832
+ var export_edits = document.getElementById('export_edits_button');
1833
+ export_edits.addEventListener('click', function(e) {
1834
+ exportTextarea('edits');
1835
+ });
1836
+ var delete_edits = document.getElementById('delete_edits_button');
1837
+ delete_edits.addEventListener('click', function(e) {
1838
+ deleteAllEdits();
1839
+ });
1840
+ var export_artists = document.getElementById('export_artists_button');
1841
+ export_artists.addEventListener('click', function(e) {
1842
+ exportTextarea('artists');
1843
+ });
1844
  var information = document.querySelectorAll('.information');
1845
  information.forEach(function(element) {
1846
  element.addEventListener('mouseleave', function(e) {
 
1883
  if (event.key === 'Escape' || event.keyCode === 27) {
1884
  // event.key for modern browsers, event.keyCode for older ones
1885
  enterExitEditMostUsedMode('exit');
1886
+ editTagsFindArtistInEditMode();
1887
  hideInformation();
1888
  }
1889
  });
1890
  var labels = document.querySelectorAll('label');
1891
  Array.from(labels).forEach(function(label) {
1892
  label.addEventListener('click', function(e) {
1893
+ if(editMostUsedMode) {
1894
  addRemoveIsMostUsed(this);
1895
  storeMostUsedState(this);
1896
  }
 
1922
  rotatePromptsImages();
1923
  storeOptionsState();
1924
  });
1925
+ imageItem.querySelector('.art_edit').addEventListener('click', function(e) {
1926
+ editTagsClicked(this.closest('.image-item'));
1927
+ });
1928
  imageItem.getElementsByTagName('h3')[0].addEventListener('click', function(e) {
1929
  copyStuffToClipboard(this,'name');
1930
  });
1931
  imageItem.getElementsByTagName('h4')[0].addEventListener('click', function(e) {
1932
+ if(!this.classList.contains('edit_mode')) {
1933
+ copyStuffToClipboard(this, 'tags')
1934
+ // toggleThisArtistsTags(this.textContent);
1935
+ }
1936
  });
1937
  });
1938
  // add gutter event listener