mirror of
				https://github.com/monkeytypegame/monkeytype.git
				synced 2025-11-01 03:39:15 +08:00 
			
		
		
		
	Merge c7ebb00fe7 into b5755faa30
				
					
				
			This commit is contained in:
		
						commit
						e1e0f6e23e
					
				
					 7 changed files with 361 additions and 7 deletions
				
			
		|  | @ -486,6 +486,8 @@ | |||
|         <i class="fas fa-folder"></i> | ||||
|         saved texts | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="buttonsTop2"> | ||||
|       <input id="fileInput" type="file" class="hidden" accept=".txt" /> | ||||
|       <label for="fileInput" class="button importText"> | ||||
|         <i class="fas fa-file-import"></i> | ||||
|  | @ -495,6 +497,10 @@ | |||
|         <i class="fas fa-filter"></i> | ||||
|         words filter | ||||
|       </div> | ||||
|       <div class="button customGenerator"> | ||||
|         <i class="fas fa-cogs"></i> | ||||
|         custom generator | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="savedTexts hidden" style="display: none"> | ||||
|       <div class="title">saved texts</div> | ||||
|  | @ -759,7 +765,7 @@ | |||
|         <select class="layoutInput"></select> | ||||
|       </div> | ||||
|       <!-- <div class="tip">Use the dropdowns above to generate presets</div> --> | ||||
|       <button class="generateButton">generate</button> | ||||
|       <button class="generateButton">apply</button> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="bottom"> | ||||
|  | @ -772,6 +778,79 @@ | |||
|     </div> | ||||
|   </div> | ||||
| </dialog> | ||||
| <dialog id="customGeneratorModal" class="modalWrapper hidden"> | ||||
|   <div class="modal"> | ||||
|     <div class="main"> | ||||
|       <div class="group"> | ||||
|         <div class="title">presets</div> | ||||
|         <select class="presetInput"> | ||||
|           <option value="alphas">a-z</option> | ||||
|           <option value="numbers">0-9</option> | ||||
|           <option value="special">symbols</option> | ||||
|           <option value="bigrams">bigrams</option> | ||||
|           <option value="trigrams">trigrams</option> | ||||
|         </select> | ||||
|         <button class="generateButton">apply</button> | ||||
|       </div> | ||||
|       <div class="separator"></div> | ||||
|       <div class="tip"> | ||||
|         Enter characters or strings separated by spaces. Random combinations | ||||
|         will be generated using these inputs. | ||||
|       </div> | ||||
|       <div class="group"> | ||||
|         <div class="title">character set</div> | ||||
|         <textarea | ||||
|           class="characterInput" | ||||
|           id="characterInput" | ||||
|           autocomplete="off" | ||||
|           placeholder="" | ||||
|           title="characters" | ||||
|         ></textarea> | ||||
|       </div> | ||||
|       <div class="group lengthgrid"> | ||||
|         <div class="title">min length</div> | ||||
|         <div class="title">max length</div> | ||||
| 
 | ||||
|         <input | ||||
|           class="wordLength minLengthInput" | ||||
|           autocomplete="off" | ||||
|           type="number" | ||||
|           value="2" | ||||
|           min="1" | ||||
|           title="min" | ||||
|         /> | ||||
|         <input | ||||
|           class="wordLength maxLengthInput" | ||||
|           autocomplete="off" | ||||
|           type="number" | ||||
|           value="5" | ||||
|           min="1" | ||||
|           title="max" | ||||
|         /> | ||||
|       </div> | ||||
|       <div class="group"> | ||||
|         <div class="title">word count</div> | ||||
|         <input | ||||
|           class="wordCountInput" | ||||
|           autocomplete="off" | ||||
|           type="number" | ||||
|           value="100" | ||||
|           min="1" | ||||
|           title="word count" | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="bottom"> | ||||
|       <div class="tip"> | ||||
|         "Set" replaces the current custom text with generated words, "Add" | ||||
|         appends generated words to the current custom text. | ||||
|       </div> | ||||
|       <button class="setButton">set</button> | ||||
|       <button class="addButton">add</button> | ||||
|     </div> | ||||
|   </div> | ||||
| </dialog> | ||||
| <dialog id="googleSignUpModal" class="modalWrapper hidden"> | ||||
|   <form class="modal"> | ||||
|     <div class="title">Account name</div> | ||||
|  |  | |||
|  | @ -15,6 +15,13 @@ | |||
|   #testModesNotice { | ||||
|     font-size: 0.8rem; | ||||
|   } | ||||
|   #customTextModal { | ||||
|     .modal { | ||||
|       .buttonsTop2 { | ||||
|         grid-template-columns: 1fr; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   #bannerCenter { | ||||
|     font-size: 0.85rem; | ||||
|     .banner.withImage { | ||||
|  |  | |||
|  | @ -231,6 +231,7 @@ | |||
|     .modal { | ||||
|       grid-template-areas: | ||||
|         "topButtons topButtons" | ||||
|         "topButtons2 topButtons2" | ||||
|         "textArea textArea" | ||||
|         "checkboxes checkboxes" | ||||
|         "ok ok"; | ||||
|  |  | |||
|  | @ -43,9 +43,9 @@ | |||
|       .buttonsTop { | ||||
|         grid-template-columns: 1fr 1fr; | ||||
|       } | ||||
|       textarea { | ||||
|         min-height: 426px; | ||||
|       } | ||||
|       // textarea { | ||||
|       //   min-height: 426px; | ||||
|       // } | ||||
|     } | ||||
|   } | ||||
|   .testActivity { | ||||
|  |  | |||
|  | @ -106,15 +106,20 @@ body.darkMode { | |||
|     //   "ok ok ok"; | ||||
|     grid-template-areas: | ||||
|       "topButtons topButtons checkboxes" | ||||
|       "topButtons2 topButtons2 checkboxes" | ||||
|       "textArea textArea checkboxes" | ||||
|       "ok ok checkboxes"; | ||||
|     grid-template-columns: 1fr 1fr 1fr; | ||||
|     grid-template-rows: min-content 1fr min-content; | ||||
|     grid-template-rows: min-content min-content 1fr min-content; | ||||
| 
 | ||||
|     .buttonsTop { | ||||
|       grid-area: topButtons; | ||||
|     } | ||||
| 
 | ||||
|     .buttonsTop2 { | ||||
|       grid-area: topButtons2; | ||||
|     } | ||||
| 
 | ||||
|     .textAreaWrapper { | ||||
|       grid-area: textArea; | ||||
|     } | ||||
|  | @ -170,7 +175,13 @@ body.darkMode { | |||
|     .buttonsTop { | ||||
|       display: grid; | ||||
|       // grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); | ||||
|       grid-template-columns: 1fr 1fr 1fr 1fr; | ||||
|       grid-template-columns: 1fr 1fr; | ||||
|       gap: 1rem; | ||||
|     } | ||||
| 
 | ||||
|     .buttonsTop2 { | ||||
|       display: grid; | ||||
|       grid-template-columns: 1fr 1fr 1fr; | ||||
|       gap: 1rem; | ||||
|     } | ||||
| 
 | ||||
|  | @ -199,7 +210,7 @@ body.darkMode { | |||
|       width: 100%; | ||||
|       border-radius: var(--roundness); | ||||
|       resize: vertical; | ||||
|       min-height: 589px; | ||||
|       min-height: 489px; | ||||
|       color: var(--text-color); | ||||
|       overflow-x: hidden; | ||||
|       overflow-y: scroll; | ||||
|  | @ -445,6 +456,70 @@ body.darkMode { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| #customGeneratorModal { | ||||
|   .modal { | ||||
|     max-width: 600px; | ||||
| 
 | ||||
|     .main { | ||||
|       display: grid; | ||||
|       gap: 1.5rem; | ||||
|     } | ||||
| 
 | ||||
|     .bottom { | ||||
|       display: grid; | ||||
|       gap: 1rem; | ||||
|       margin-top: 1rem; | ||||
|     } | ||||
| 
 | ||||
|     .separator { | ||||
|       height: 0.25rem; | ||||
|       width: 100%; | ||||
|       background-color: var(--sub-alt-color); | ||||
|       border-radius: var(--roundness); | ||||
|     } | ||||
| 
 | ||||
|     .group { | ||||
|       display: grid; | ||||
|       gap: 0.5rem; | ||||
| 
 | ||||
|       .title { | ||||
|         color: var(--sub-color); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .lengthgrid { | ||||
|       display: grid; | ||||
|       grid-template-columns: 1fr 1fr; | ||||
|       grid-template-rows: auto 1fr; | ||||
|       column-gap: 1rem; | ||||
|     } | ||||
| 
 | ||||
|     .tip { | ||||
|       color: var(--sub-color); | ||||
|       font-size: 0.8rem; | ||||
|     } | ||||
| 
 | ||||
|     input, | ||||
|     textarea { | ||||
|       width: 100%; | ||||
|       padding: 0.5rem; | ||||
|       background: var(--sub-alt-color); | ||||
|       color: var(--text-color); | ||||
|       border: none; | ||||
|       border-radius: var(--roundness); | ||||
| 
 | ||||
|       &::placeholder { | ||||
|         color: var(--sub-color); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     textarea { | ||||
|       min-height: 100px; | ||||
|       resize: vertical; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| #quoteRateModal { | ||||
|   .modal { | ||||
|     max-width: 800px; | ||||
|  |  | |||
							
								
								
									
										184
									
								
								frontend/src/ts/modals/custom-generator.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								frontend/src/ts/modals/custom-generator.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,184 @@ | |||
| import * as CustomText from "../test/custom-text"; | ||||
| import * as Notifications from "../elements/notifications"; | ||||
| import SlimSelect from "slim-select"; | ||||
| import AnimatedModal, { | ||||
|   HideOptions, | ||||
|   ShowOptions, | ||||
| } from "../utils/animated-modal"; | ||||
| 
 | ||||
| type Preset = { | ||||
|   display: string; | ||||
|   characters: string[]; | ||||
| }; | ||||
| 
 | ||||
| const presets: Record<string, Preset> = { | ||||
|   alphas: { | ||||
|     display: "a-z", | ||||
|     characters: "abcdefghijklmnopqrstuvwxyz".split(""), | ||||
|   }, | ||||
|   numbers: { | ||||
|     display: "0-9", | ||||
|     characters: "0123456789".split(""), | ||||
|   }, | ||||
|   special: { | ||||
|     display: "symbols", | ||||
|     characters: "!@#$%^&*()_+-=[]{}|;:',.<>?/`~".split(""), | ||||
|   }, | ||||
|   bigrams: { | ||||
|     display: "bigrams", | ||||
|     characters: [ | ||||
|       "th", | ||||
|       "he", | ||||
|       "in", | ||||
|       "er", | ||||
|       "an", | ||||
|       "re", | ||||
|       "on", | ||||
|       "at", | ||||
|       "en", | ||||
|       "nd", | ||||
|       "ed", | ||||
|       "es", | ||||
|       "or", | ||||
|       "te", | ||||
|       "st", | ||||
|       "ar", | ||||
|       "ou", | ||||
|       "it", | ||||
|       "al", | ||||
|       "as", | ||||
|     ], | ||||
|   }, | ||||
|   trigrams: { | ||||
|     display: "trigrams", | ||||
|     characters: [ | ||||
|       "the", | ||||
|       "and", | ||||
|       "ing", | ||||
|       "ion", | ||||
|       "tio", | ||||
|       "ent", | ||||
|       "ati", | ||||
|       "for", | ||||
|       "her", | ||||
|       "ter", | ||||
|       "ate", | ||||
|       "ver", | ||||
|       "all", | ||||
|       "con", | ||||
|       "res", | ||||
|       "are", | ||||
|       "rea", | ||||
|       "int", | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| let _presetSelect: SlimSelect | undefined = undefined; | ||||
| 
 | ||||
| export async function show(showOptions?: ShowOptions): Promise<void> { | ||||
|   void modal.show({ | ||||
|     ...showOptions, | ||||
|     beforeAnimation: async (modalEl) => { | ||||
|       _presetSelect = new SlimSelect({ | ||||
|         select: "#customGeneratorModal .presetInput", | ||||
|         settings: { | ||||
|           contentLocation: modalEl, | ||||
|         }, | ||||
|       }); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function applyPreset(): void { | ||||
|   const presetName = $("#customGeneratorModal .presetInput").val() as string; | ||||
|   if (presetName !== undefined && presetName !== "" && presets[presetName]) { | ||||
|     const preset = presets[presetName]; | ||||
|     $("#customGeneratorModal .characterInput").val(preset.characters.join(" ")); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function hide(hideOptions?: HideOptions<OutgoingData>): void { | ||||
|   void modal.hide({ | ||||
|     ...hideOptions, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function generateWords(): string[] { | ||||
|   const characterInput = $( | ||||
|     "#customGeneratorModal .characterInput" | ||||
|   ).val() as string; | ||||
|   const minLength = | ||||
|     parseInt($("#customGeneratorModal .minLengthInput").val() as string) || 2; | ||||
|   const maxLength = | ||||
|     parseInt($("#customGeneratorModal .maxLengthInput").val() as string) || 5; | ||||
|   const wordCount = | ||||
|     parseInt($("#customGeneratorModal .wordCountInput").val() as string) || 100; | ||||
| 
 | ||||
|   if (!characterInput || characterInput.trim() === "") { | ||||
|     Notifications.add("Character set cannot be empty", 0); | ||||
|     return []; | ||||
|   } | ||||
| 
 | ||||
|   const characters = characterInput.trim().split(/\s+/); | ||||
|   const generatedWords: string[] = []; | ||||
| 
 | ||||
|   for (let i = 0; i < wordCount; i++) { | ||||
|     const wordLength = | ||||
|       Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength; | ||||
|     let word = ""; | ||||
| 
 | ||||
|     for (let j = 0; j < wordLength; j++) { | ||||
|       const randomChar = | ||||
|         characters[Math.floor(Math.random() * characters.length)]; | ||||
|       word += randomChar; | ||||
|     } | ||||
| 
 | ||||
|     generatedWords.push(word); | ||||
|   } | ||||
| 
 | ||||
|   return generatedWords; | ||||
| } | ||||
| 
 | ||||
| async function apply(set: boolean): Promise<void> { | ||||
|   const generatedWords = generateWords(); | ||||
| 
 | ||||
|   if (generatedWords.length === 0) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   const customText = generatedWords.join( | ||||
|     CustomText.getPipeDelimiter() ? "|" : " " | ||||
|   ); | ||||
| 
 | ||||
|   hide({ | ||||
|     modalChainData: { | ||||
|       text: customText, | ||||
|       set, | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| async function setup(modalEl: HTMLElement): Promise<void> { | ||||
|   modalEl.querySelector(".setButton")?.addEventListener("click", () => { | ||||
|     void apply(true); | ||||
|   }); | ||||
| 
 | ||||
|   modalEl.querySelector(".addButton")?.addEventListener("click", () => { | ||||
|     void apply(false); | ||||
|   }); | ||||
| 
 | ||||
|   modalEl.querySelector(".generateButton")?.addEventListener("click", () => { | ||||
|     applyPreset(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| type OutgoingData = { | ||||
|   text: string; | ||||
|   set: boolean; | ||||
| }; | ||||
| 
 | ||||
| const modal = new AnimatedModal<unknown, OutgoingData>({ | ||||
|   dialogId: "customGeneratorModal", | ||||
|   setup, | ||||
| }); | ||||
|  | @ -6,6 +6,7 @@ import * as ChallengeController from "../controllers/challenge-controller"; | |||
| import Config, * as UpdateConfig from "../config"; | ||||
| import * as Strings from "../utils/strings"; | ||||
| import * as WordFilterPopup from "./word-filter"; | ||||
| import * as CustomGeneratorPopup from "./custom-generator"; | ||||
| import * as PractiseWords from "../test/practise-words"; | ||||
| import * as Notifications from "../elements/notifications"; | ||||
| import * as SavedTextsPopup from "./saved-texts"; | ||||
|  | @ -560,6 +561,13 @@ async function setup(modalEl: HTMLElement): Promise<void> { | |||
|       modalChain: modal as AnimatedModal, | ||||
|     }); | ||||
|   }); | ||||
|   modalEl | ||||
|     .querySelector(".button.customGenerator") | ||||
|     ?.addEventListener("click", () => { | ||||
|       void CustomGeneratorPopup.show({ | ||||
|         modalChain: modal as AnimatedModal, | ||||
|       }); | ||||
|     }); | ||||
|   modalEl | ||||
|     .querySelector(".button.showSavedTexts") | ||||
|     ?.addEventListener("click", () => { | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue