Weirdcore/Dreamcore names โ€• Perchance Generator (2024)

โš„๏ธŽ

๐Ÿ‘ฅ๏ธŽ forum (...)

๐ŸŒˆ hub

๐Ÿ“š learn

๐ŸŽฒ๏ธŽ generators

โž• new

๐Ÿ› ๏ธ edit

๐Ÿ”‘ login

`, "single-equals-in-dynamic-odds": `

Warning: The dynamic odds notation on line number ###lineNumber### (in ###generatorNameText### generator's code) has a single equals sign instead of a double-equals sign. A single equals sign is used to put a value into a variable - for example age = 10 means "put the value 10 into the age variable" whereas age == 10 means "is age equal to 10?".

`, "single-equals-in-if-else-condition": `

Warning: The if/else condition on line number ###lineNumber### (in ###generatorNameText### generator's code) has a single equals sign instead of a double-equals sign. A single equals sign is used to put a value into a variable - for example age = 10 means "put the value 10 into the age variable" whereas age == 10 means "is age equal to 10?".

`, "square-brackets-wrapping-variable-name-within-square-block": `

Warning: On line number ###lineNumber### (in ###generatorNameText### generator's code), you've got a square block - i.e. some square brackets with some stuff inside. Nothing wrong with that. But inside that square block it looks like you've got another square block? Whenever you're writing stuff inside a square block, you can refer to variable and list name

directly

- no need to put them inside square blocks. So instead of [[noun].pluralForm], you'd just write [noun.pluralForm], and instead of [[characterAge] + 3], you'd just write [characterAge + 3], and instead of ^[[age] < 10], you'd just write ^[age < 10], and instead of [[age] > 60 ? [oldDescription] : [youngDescription]], you'd just write [age > 60 ? oldDescription : youngDescription]. You should use normal parentheses to group calculations and conditions, like this: [(age + 1) * height] and this: [isMammal || (isReptile && isBrown)]. In fact, if you wrap list/variable names in square brackets when you're already inside a square block then this can sometimes cause all sorts of sneaky errors that are hard to "debug". Square brackets

can

"legally" be used within square brackets in some circ*mstances, but they have a special meaning (learn more about this on the examples page - especially the "Dynamic Sub-list Referencing" section). One final note: It's okay to use square brackets within square brackets if the inner square brackets are themselves within double-quotes, like so: [n == 1 ? "[prefix]er" : "[prefix]ers"].

`, "top-level-list-name-same-as-html-element-id": `

Warning: It looks like you have a list name on line number ###lineNumber### that has the same name as an element's id="..." attribute? If so, note that these will conflict with one another and potentially cause errors. For example if you had a list called animal, then you shouldn't have an element like this: <p id="animal">...</p>

`, "duplicate-top-level-list-name": `

Warning: It looks like you have a list name on line number ###lineNumber### (in ###generatorNameText### generator's code) that has the same name as an earlier list/import/variable?

`, }; this.generatorMetaData = undefined; this.toggleOutputEditorWrap = function() { let btn = this.refs.outputEditorRefButton; let state = this.outputTemplateEditor.getOption('lineWrapping'); if(state) { btn.innerText = "wrap"; } else { btn.innerText = "unwrap"; } this.outputTemplateEditor.setOption('lineWrapping', !state); }; this.toggleCodeEditorWrap = function() { let btn = this.refs.codeEditorWrapRefButton; let state = this.modelTextEditor.getOption('lineWrapping'); if(state) { btn.innerText = "wrap"; } else { btn.innerText = "unwrap"; } // record cursor position: let cursorLine = this.modelTextEditor.getCursor().line; let cursorTop = this.modelTextEditor.cursorCoords().top - this.modelTextEditor.display.wrapper.getBoundingClientRect().top; this.modelTextEditor.setOption('lineWrapping', !state); // recover scroll to cursor position: this.modelTextEditor.scrollIntoView({line:cursorLine, char:0}, cursorTop); }; this.allListsFolded = false; this.toggleCodeEditorFold = function() { let btn = this.refs.codeEditorFoldRefButton; let state = this.modelTextEditor.getOption('lineWrapping'); this.allListsFolded ? CodeMirror.commands.unfoldAll(this.modelTextEditor) : CodeMirror.commands.foldAll(this.modelTextEditor); this.allListsFolded = !this.allListsFolded; btn.innerText = this.allListsFolded ? "unfold" : "fold"; }; this.outputIframe = this.root.querySelector("#output iframe"); this.refs.autoReloadCheckbox.checked = this.autoReload; window.addEventListener("message", async (e) => { let origin = e.origin || e.originalEvent.origin; if(origin !== "https://null.perchance.org" && origin !== `https://${window.generatorPublicId}.perchance.org`) return; if(e.data.type === "evaluateTextResponse" && typeof e.data.text === "string" && typeof e.data.callerId === "string") { let resolveMap = this.evaluateTextResolveMap; if(resolveMap[e.data.callerId]) { resolveMap[e.data.callerId](e.data.text); delete resolveMap[e.data.callerId]; } } if(e.data.type === "requestOutputUpdate") { // for 'hard reload' without having to first save data to server // user clicks reload button which triggers a fullRefresh, but it adds __initWithDataFromParentWindow=1 to the URL, which causes the outputIframeContent.html code to not use the data embedded in the HTML, and instead request this parent frame for the 'fresh' data await window.mostRecentFullRefreshUpdateDependenciesPromise; // so dependency stuff can load in parrallel with iframe for __initWithDataFromParentWindow fullRefresh this.updateOutputRequest(); } if(e.data.type === "saveKeyboardShortcut") { app.handleIframeSaveRequest(); } // if(e.data.type === "finishedLoading") { // window.perchanceOutputIframeFinishedFirstLoad = true; // // initialPageLoadSpinner.style.display = "none"; // outputLoadSpinner.style.display = "none"; // } if(e.data.type === "finishedLoadingIncludingMetaData" || e.data.type === "failedToLoadDueToGeneratorErrors") { if(e.data.imports) { // <-- need to check because failedToLoadDueToGeneratorErrors doesn't give us imports // we need to check if there are new imports before resolving the hard reload stuff because the saveGenerator function uses a hard reload before saving (which triggers these messages and resolves below), and we want to make sure we save the generator with all its up-to-date dependencies included. let wereNewDeps = await window.lastImportsUpdateDependencyCheckPromise; if(wereNewDeps) return; // don't want to resolve yet - the importsUpdate handler will trigger a second hard reload } for(let resolver of window.outputIframeHardReloadFinishedResolvers) { resolver(); } window.outputIframeHardReloadFinishedResolvers = []; } if(e.data.type === "importsUpdate" && Array.isArray(e.data.imports) && e.data.imports.filter(i => typeof i !== 'string').length === 0) { await window.thisIsNotAnOldCachedPagePromise; // <-- wait until we've confirmed that this is the latest version of this generator. this prevents a weird race condition where an import update will happen fast during page load and beat the cache bust, which cause an auto-save (importsUpdates can do this), and overwrites the new code with old code. // (note that the above promise won't resolve unless it's true) window.lastImportsUpdateDependencyCheckPromise = this.updateDependencies(e.data.imports); let wereNewDeps = await window.lastImportsUpdateDependencyCheckPromise; // <-- this actually downloads new dependencies (**if needed**, else returns false) if(wereNewDeps) { // we only update if there were new dependencies otherwise we will get into infinite loops // (due to the way I've handled removing dependencies (I keep the cached still)) TODO: this whole // imports-update part is terrible and needs re-writing to make it simple and understandable. // Wouldn't take toooo much work. this.updateOutput(true); // <-- this triggers iframe updateOutput function. `true` => fullRefresh, i.e. fully reload the iframe with __initWithDataFromParentWindow app.generatorEditedCallback({imports:e.data.imports}); } else if(app.data.dependencies.length > e.data.imports.length) { // if dependency was *dropped*, no need to update the output - just need to update the data: app.generatorEditedCallback({imports:e.data.imports}); } } if(e.data.type === "metaUpdate") { if(e.data._validation.generatorName !== app.data.generator.name) return; // <-- trying to stop weird Google crawler page title bug // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // WARNING WARNING WARNING WARNING WARNING WARNING WARNING: If you change this at all, make sure to do a thorough check for XSS bugs. Saved meta info must be sanitised ON THE SERVER during rendering. // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ let title = undefined; let description = undefined; let image = undefined; let tags = undefined; let header = undefined; let dynamic = undefined; // if(typeof e.data.html === "string") { // if(e.data.html.length < 100_000 && window.shouldLiftGeneratorHtmlFromEmbed) { // window.document.querySelector("#generator-description").innerHTML = DOMPurify.sanitize(e.data.html, {ALLOWED_TAGS: ['h1', 'h2', 'h3', 'h4', 'p', 'div', 'pre', 'button', 'i', 'b', 'a', 'ul', 'li', 'br', 'ol', 'hr'], ALLOWED_ATTR: ['href']}); // if(!window.generatorCanLinkWithRelFollow) window.document.querySelectorAll("#generator-description a").forEach(a => a.rel="nofollow"); // } // } if(typeof e.data.title === "string") { title = e.data.title.slice(0, 500); // MUST be sanitised on the server during rendering } if(typeof e.data.description === "string") { description = e.data.description.slice(0, 3000); // MUST be sanitised on the server during rendering } if(typeof e.data.image === "string") { image = e.data.image.slice(0, 500); // MUST be sanitised on the server during rendering } if(typeof e.data.dynamic === "string") { dynamic = e.data.dynamic.slice(0, 40000); } if(typeof e.data.tags === "string") { tags = e.data.tags.slice(0, 1000).split(",").map(t => t.trim()).filter(t => t.length > 0); } if(e.data.header && typeof e.data.header === "object") { header = {}; if(typeof e.data.header.background === "string") { if(CSS.supports("background", e.data.header.background)) { header.background = e.data.header.background.slice(0, 2000); menuBarEl.style.background = header.background; enableRobustBackdropFilter(); if(CSS.supports("color", header.background)) { menuBarEl.style.color = colorIsDark(header.background) ? "#e3e3e3" : "#050505"; } } } if(typeof e.data.header.mode === "string") { header.mode = e.data.header.mode.slice(0, 100); } } this.generatorMetaData = {title, image, description, dynamic, tags, header}; } if(e.data.type === "codeWarningsUpdate") { let validWarningsCount = 0; this.refs.warningsModalOpenButton.style.display = "none"; this.refs.warningsWrapper.innerHTML = ""; let warningsHTML = ""; for(let warning of e.data.warnings) { if(typeof warning.lineNumber !== "number" || typeof warning.warningId !== "string" || /[^a-z0-9\-]/.test(warning.generatorName)) return; let warningTemplate = warnings[warning.warningId]; if(!warningTemplate) { console.error("invalid warning id?"); continue; } warningTemplate = warningTemplate.replace(/###lineNumber###/g, warning.lineNumber); let generatorNameText; if(warning.generatorName === window.location.pathname.slice(1)) generatorNameText = "this"; else generatorNameText = "the "+warning.generatorName+""; warningTemplate = warningTemplate.replace(/###generatorNameText###/g, generatorNameText); warningTemplate = warningTemplate.replace(/ \(in this<\/b> generator's code\)/g, ""); // hackily remove this, since it actually makes it more confusing for newbies I think. warningsHTML += warningTemplate; validWarningsCount++; if(validWarningsCount > 10) break; } if(validWarningsCount > 0) { this.refs.warningsModalOpenButton.style.display = "inline-block"; this.refs.warningsWrapper.innerHTML = warningsHTML; this.refs.warningsModal.querySelector(".modal-body").scrollTop = 0; } } // if(e.data.type === "requestFullscreen") { // if(this.outputIframe.requestFullscreen) this.outputIframe.requestFullscreen(); // else if(this.outputIframe.webkitRequestFullscreen) this.outputIframe.webkitRequestFullscreen(); // else if(this.outputIframe.mozRequestFullscreen) this.outputIframe.mozRequestFullscreen(); // else if(this.outputIframe.msRequestFullscreen) this.outputIframe.msRequestFullscreen(); // } // // if(e.data.type === "exitFullscreen") { // if(this.outputIframe.exitFullscreen) this.outputIframe.exitFullscreen(); // else if(this.outputIframe.webkitExitFullscreen) this.outputIframe.webkitExitFullscreen(); // else if(this.outputIframe.mozCancelFullscreen) this.outputIframe.mozCancelFullscreen(); // else if(this.outputIframe.msExitFullscreen) this.outputIframe.msExitFullscreen(); // } }); // codemirror settings CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; CodeMirror.keyMap.default["Tab"] = "indentMore"; this.root.querySelector("#input").value = this.root.querySelector("#input").value.replace(/\n[\t ]+/g, (match) => match.replace(/\\t/g, " ")); // I'm reluctant to mess with tabs in the HTML editor because they are significant in e.g.

, and I'm not sure if replacing with entity () would actually work in all cases. Should be solved for all future generators anyway (via pasting intercept code below). let systemIsInDarkMode = !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches); this.modelTextEditor = CodeMirror.fromTextArea(this.root.querySelector("#input"), { lineNumbers: true, foldGutter: true, extraKeys: { //"Ctrl-Q": cm => cm.foldCode(cm.getCursor()), //"Ctrl-Y": cm => CodeMirror.commands.foldAll(cm), //"Ctrl-I": cm => CodeMirror.commands.unfoldAll(cm), }, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], tabSize: 2, indentUnit: 2, indentWithTabs: false, matchBrackets: true, mode: "perchancelists", styleActiveLine: true, lineWrapping:false, theme: systemIsInDarkMode ? "one-dark" : "one-light", keyMap: "sublime", highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: true}, }); // hacky fix for a bug where if you type two consecutive backticks, the `perchancelists` mode's JS state gets messed up for some reason. // we just retokenize when backtick is typed. let backtickHighlightRetokenizeTimeout = null; this.modelTextEditor.on("beforeChange", function(cm, changeObj) { if(changeObj.text.some(line => line.includes('`'))) { clearTimeout(backtickHighlightRetokenizeTimeout); backtickHighlightRetokenizeTimeout = setTimeout(() => cm.setOption("mode", "perchancelists"), 200); } }); this.outputTemplateEditor = CodeMirror.fromTextArea(this.root.querySelector("#template"), { // lineNumbers: true, // foldGutter: true, // gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], mode: {name: "htmlmixed"}, selectionPointer: true, tabSize: 2, indentUnit: 2, indentWithTabs: false, lineWrapping:false, styleActiveLine: true, theme: systemIsInDarkMode ? "one-dark" : "one-light", keyMap: "sublime", highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: true}, }); // async function initCodeMirror6(targetElement, {mode, initialCode}={}) { // if(typeof CM === 'undefined') { // await new Promise(resolve => { // const script = document.createElement('script'); // // Snapshot of below url on sept 2nd 2024: https://user-uploads.perchance.org/file/b6ff332ae4c0ae17cc05de619ad5dcb5.js // script.src = 'https://codemirror.net/6/codemirror.js'; // TODO: create a leaner bundle (this contains *everything* - 1.4mb) // document.head.appendChild(script); // script.onload = resolve; // }); // } // const { EditorView, keymap, lineNumbers } = CM["@codemirror/view"]; // const { EditorState } = CM["@codemirror/state"]; // const { defaultKeymap } = CM["@codemirror/commands"]; // const { syntaxHighlighting, defaultHighlightStyle, bracketMatching } = CM["@codemirror/language"]; // let languageSupport; // switch (mode) { // case 'javascript': // languageSupport = CM["@codemirror/lang-javascript"].javascript(); // break; // case 'html': // languageSupport = CM["@codemirror/lang-html"].html(); // break; // case 'css': // languageSupport = CM["@codemirror/lang-css"].css(); // break; // // Add more language modes as needed // default: // languageSupport = CM["@codemirror/lang-javascript"].javascript(); // } // return new EditorView({ // state: EditorState.create({ // doc: initialCode, // extensions: [ // keymap.of(defaultKeymap), // lineNumbers(), // bracketMatching(), // languageSupport, // syntaxHighlighting(defaultHighlightStyle) // // TODO: https://github.com/val-town/codemirror-codeium // ] // }), // parent: targetElement // }); // } // // Usage // document.body.innerHTML = ``; // let editorView = await initCodeMirror6(containerEl, {mode:'javascript', initialCode:'console.log("Hello, CodeMirror 6!");'}); if(localStorage.editorFontSize && !isNaN(Number(localStorage.editorFontSize))) { let size = Number(localStorage.editorFontSize); let style = document.createElement("style"); style.innerHTML = `.CodeMirror { font-size: ${size}px !important; line-height: ${size*1.4}px !important; }`; document.head.appendChild(style); } window.editsHaveBeenMadeSincePageLoad = false; // these are also called after the generator is saved, so long as window.lastEditorsChangeTime is the same as the instant that the save operation was initiated. // codemirror will make editor.isClean() return true when it comes back to this state, **including via undo** this.modelTextEditor.markClean(); this.outputTemplateEditor.markClean(); // so the save code can access them for markClean stuff: window.modelTextEditor = this.modelTextEditor; window.outputTemplateEditor = this.outputTemplateEditor; // hide wrap/fold buttons if cursor is near them, or on first line async function updateToolbarVisibility(editor) { await new Promise(r => setTimeout(r, 10)); let editorCtn = editor.getWrapperElement().parentElement; const cursor = editorCtn.querySelector(".CodeMirror-cursor"); const hoverToolbar = editorCtn.querySelector('.code-editor-buttons-ctn') || editorCtn.querySelector('.toggle-wrap-button-html'); if(!cursor || !hoverToolbar) return; const cursorRect = cursor.getBoundingClientRect(); const toolbarRect = hoverToolbar.getBoundingClientRect(); let isNear = cursorRect.top <= toolbarRect.bottom+20 && cursorRect.left >= toolbarRect.left-50 && cursorRect.left <= toolbarRect.right+50; if(editor.getCursor().line === 0) isNear = true; hoverToolbar.style.pointerEvents = isNear ? 'none' : 'auto'; hoverToolbar.style.opacity = isNear ? '0' : '1'; } window.modelTextEditor.on('keyHandled', updateToolbarVisibility); window.outputTemplateEditor.on('keyHandled', updateToolbarVisibility); window.modelTextEditor.getWrapperElement().addEventListener('mouseup', () => updateToolbarVisibility(window.modelTextEditor)); window.outputTemplateEditor.getWrapperElement().addEventListener('mouseup', () => updateToolbarVisibility(window.outputTemplateEditor)); // this is also set in the save handler. they're used as an extra check in doLocalGeneratorBackupIfNeeded to prevent backing up text that is already saved. window.lastModelTextSaved = this.modelTextEditor.getValue(); window.lastOutputTemplateSaved = this.outputTemplateEditor.getValue(); // this is stupid but it's the only way I could get chrome to stop suggesting an email/password in codemirror's search dialogue try { function watchForChildWithClass(target, className, callback) { new MutationObserver(mutations => { mutations.forEach(mutation => { Array.from(mutation.addedNodes).forEach(node => { if(node.nodeType === 1 && node.classList.contains(className)) callback(node); }); }); }).observe(target, { childList: true }); } setTimeout(() => { for(let container of Array.from(document.querySelectorAll(".CodeMirror"))) { watchForChildWithClass(container, "CodeMirror-dialog", element => { for(let inputEl of Array.from(element.querySelectorAll("input"))) { let dummyEl = document.createElement("div"); dummyEl.innerHTML = ``; dummyEl.style.cssText = "opacity:0; pointer-events:none; position:absolute;"; inputEl.parentElement.insertBefore(dummyEl, inputEl); } }); } }, 400); } catch(e) { console.error(e); } // there was some funky business happening with tabs when people *pasted* them. // This replaces all tabs with two spaces (users need to write \t if they want a tab in their text). // EDIT: also just added non-breaking space replacement with normal space, since it was messing with indenting stuff. this.modelTextEditor.on("beforeChange", (cm, change) => { if(change.origin === "paste") { change.update(null, null, change.text.map(line => line.replace(/\t/g, " ").replaceAll(`\u00A0`, " "))); } }); // I took this out because in HTML, some text with to consecutive spaces is different to , and I think the same is true of tabs. So I think this would turn code indents into a bunch of visible whitespace. // this.outputTemplateEditor.on("beforeChange", (cm, change) => { // if(change.origin === "paste") { // change.update(null, null, change.text.map(line => line.replace(/\t/g, ""))); // } // }); // This is hacky, but it seems to work. It's from here: https://codemirror.net/demo/indentwrap.html // It makes it so wrapped text is indented at the same indent level as the line itself. let basePadding = 4; // Only do the indentwrap stuff if there are no tabs (literal `\t` is fine), because otherwise it (for some reason) doesn't work - it messes things up: jsbin.com/tukuximome if(!this.root.querySelector("#template").value.includes("\t")) { let charWidthOutputTemplate = this.outputTemplateEditor.defaultCharWidth(); this.outputTemplateEditor.on("renderLine", function(cm, line, elt) { let off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidthOutputTemplate; elt.style.textIndent = "-" + off + "px"; elt.style.paddingLeft = (basePadding + off) + "px"; }); } if(!this.root.querySelector("#input").value.includes("\t")) { let charWidthModelText = this.modelTextEditor.defaultCharWidth(); this.modelTextEditor.on("renderLine", function(cm, line, elt) { let off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidthModelText; elt.style.textIndent = "-" + off + "px"; elt.style.paddingLeft = (basePadding + off) + "px"; }); } ////////////////////////////////////// // LOCAL STORAGE BACKUPS // ////////////////////////////////////// const maxLocalBackupInterval = localStorage.__speedUpLocalBackupStuffForTesting ? 1000*5 : 1000*60*5; // CAUTION: if you change this, change `doLocalBackupDebounce6MinTimeout` to be larger than it const oldLocalBackupCleaningInterval = localStorage.__speedUpLocalBackupStuffForTesting ? 1000*60*2 : 1000*60*20; const maxLocalBackupAge = localStorage.__speedUpLocalBackupStuffForTesting ? 1000*60*10 : 1000*60*60*24*30; const maxLocalBackupsPerGenerator = localStorage.__speedUpLocalBackupStuffForTesting ? 10 : 30; if(localStorage.__speedUpLocalBackupStuffForTesting) { console.warn(`WARNING: local backups sped up due to truthy localStorage.__speedUpLocalBackupStuffForTesting value\n`.repeat(20)); } let lastLocalBackupTime = Date.now(); const doLocalGeneratorBackupIfNeeded = async (modelText, outputTemplate) => { if(!app.userOwnsThisGenerator) return; // they don't own this gen (i.e. are just playing around in editor for now) if(Date.now()-window.generatorLastSaveTime < maxLocalBackupInterval) return; // hasn't been a few mins since last save if(Date.now()-lastLocalBackupTime < maxLocalBackupInterval) return; // at most once every few mins if(window.lastModelTextSaved === modelText && window.lastOutputTemplateSaved === outputTemplate) return; // since they may have made an edit, but then undone it lastLocalBackupTime = Date.now(); if(!kv) await window.initKv(); let backupsArray = await kv.localBackups.get(app.data.generator.name) || []; backupsArray.unshift({modelText, outputTemplate, time:Date.now()}); // if there are more than `maxLocalBackupsPerGenerator` backups, delete every second one in the second half of the array: if(backupsArray.length > maxLocalBackupsPerGenerator) { backupsArray = backupsArray.filter((backup, i) => i < maxLocalBackupsPerGenerator/2 ? true : i%2===0); } await kv.localBackups.set(app.data.generator.name, backupsArray); console.debug("Saved local backup. NEW backupsArray:", backupsArray); }; setTimeout(async () => { if(app.userOwnsThisGenerator) { let persistent = await navigator.storage.persist(); if(!persistent) console.warn("Browser denied persistent local storage."); } }, 1000*60*20); setInterval(async () => { if(!kv) return; let entries = await kv.localBackups.entries(); // for *every* backed-up generator (not just the one they're currently viewing/editing) stored in this user's browser: for(let [generatorName, backupsArray] of entries) { let originalLength = backupsArray.length; // delete the backup array entries older than several weeks: backupsArray = backupsArray.filter(backup => Date.now()-backup.time < maxLocalBackupAge); if(originalLength === backupsArray.length) continue; // save changes: if(backupsArray.length === 0) { await kv.localBackups.delete(generatorName); await kv.localBackupsLastWarnTimes.delete(generatorName); } else { console.debug(`Cleared ${originalLength-backupsArray.length} old backups. NEW backupsArray:`, backupsArray); await kv.localBackups.set(generatorName, backupsArray); } } }, oldLocalBackupCleaningInterval); // if the page loads, and user owns this generator, and the last backup time is more than a few mins forward in time from last save, then warn them to click header 'backups' button setTimeout(async () => { let startTime = Date.now(); while(app.userOwnsThisGenerator === undefined) await new Promise(r => setTimeout(r, 1000*10)); if(Date.now()-startTime > 1000*60) { console.warn(`Took a ${Date.now()-startTime}ms (i.e. much longer than expected) to determine if this user owns this generator?`); return; } if(!app.userOwnsThisGenerator) return; if(!kv) await window.initKv(); let localBackupsArr = await kv.localBackups.get(app.data.generator.name); let lastWarnTimeForThisGen = await kv.localBackupsLastWarnTimes.get(app.data.generator.name) || 0; if(localBackupsArr) { for(let data of localBackupsArr) { if(data.time-window.generatorLastSaveTime > 1000*60*3 && data.time > lastWarnTimeForThisGen) { try { app.goToEditMode(); document.querySelector(`#menuBarEl [data-ref="revisionsButton"]`).style.backgroundColor = "#7d5100"; } catch(e) { console.error(e); } await kv.localBackupsLastWarnTimes.set(app.data.generator.name, Date.now()); alert(`Warning: There's a backup of this generator stored in your browser memory which is newer than the version of this generator you're currently viewing. This could have been caused by a problem where the server didn't properly save your generator, or if your browser previously crashed while you were editing, or it could just be that you made a little edit but then decided not to save it. If you lost some work, click the 'backups' button in the header to download the code for a backed-up version.`); return; } } } }, 1000*3); let editorContentAlreadySavedDebounceTimeout; function checkIfEditorContentAlreadySavedWithDebounce(modelText, outputTemplate) { clearTimeout(editorContentAlreadySavedDebounceTimeout); editorContentAlreadySavedDebounceTimeout = setTimeout(() => { if(window.lastModelTextSaved === modelText && window.lastOutputTemplateSaved === outputTemplate) { if(app.userOwnsThisGenerator) { window.setSaveState("saved"); } } }, 300); } let doLocalBackupDebounce6MinTimeout; window.lastEditorsChangeTime = Date.now(); this.outputTemplateEditor.on("changes", () => { window.lastEditorsChangeTime = Date.now(); window.editsHaveBeenMadeSincePageLoad = true; let modelText = this.modelTextEditor.getValue(); let outputTemplate = this.outputTemplateEditor.getValue(); app.generatorEditedCallback({modelText, outputTemplate}); if(this.outputTemplateEditor.isClean() && this.modelTextEditor.isClean()) { // in case they undo and go back to a saved state if(app.userOwnsThisGenerator) { window.setSaveState("saved"); // must come after generatorEditedCallback, above, since that sets the save state to "unsaved" } } else { checkIfEditorContentAlreadySavedWithDebounce(modelText, outputTemplate); } try { doLocalGeneratorBackupIfNeeded(modelText, outputTemplate); clearTimeout(doLocalBackupDebounce6MinTimeout); doLocalBackupDebounce6MinTimeout = setTimeout(() => { // without this, we can still lose up to 5 minutes of work, which is annoying doLocalGeneratorBackupIfNeeded(modelText, outputTemplate); }, 1000*60*6); } catch(e) { console.error(e); } }); this.modelTextEditor.on("changes", () => { window.lastEditorsChangeTime = Date.now(); window.editsHaveBeenMadeSincePageLoad = true; let modelText = this.modelTextEditor.getValue(); let outputTemplate = this.outputTemplateEditor.getValue(); app.generatorEditedCallback({modelText, outputTemplate}); if(this.outputTemplateEditor.isClean() && this.modelTextEditor.isClean()) { // in case they undo and go back to a saved state if(app.userOwnsThisGenerator) { window.setSaveState("saved"); // must come after generatorEditedCallback, above, since that sets the save state to "unsaved" } } else { checkIfEditorContentAlreadySavedWithDebounce(modelText, outputTemplate); } try { doLocalGeneratorBackupIfNeeded(modelText, outputTemplate); clearTimeout(doLocalBackupDebounce6MinTimeout); doLocalBackupDebounce6MinTimeout = setTimeout(() => { // without this, we can still lose up to 5 minutes of work, which is annoying doLocalGeneratorBackupIfNeeded(modelText, outputTemplate); }, 1000*60*6); } catch(e) { console.error(e); } }); // disable overwrite insert mode: this.outputTemplateEditor.on("keyup", () => { this.outputTemplateEditor.toggleOverwrite(false); }); this.modelTextEditor.on("keyup", () => { this.modelTextEditor.toggleOverwrite(false); }); this.outputTemplateEditor.on("changes", () => { clearTimeout(this.updateOutputDelayTimeout); if(this.autoReload) { this.updateOutputDelayTimeout = setTimeout(() => { this.updateOutput(false, true); }, 800); } }); this.modelTextEditor.on("changes", () => { clearTimeout(this.updateOutputDelayTimeout); if(this.autoReload) { this.updateOutputDelayTimeout = setTimeout(() => { this.updateOutput(false, true); // EDIT: this is commented out because it doesn't make sense - if they haven't reloaded, then the computed meta data is obviously going to be using the old code. // TODO: Make it so that when they send the save request, that's when we send updateMetadataIfNeeded, and we send the updated modelText which is executed with createPerchanceTree or whatever, but is then discarded, rather than replacing the "real" tree and updating everything. // this.outputIframe.contentWindow.postMessage({command:"updateMetadataIfNeeded"}, '*'); // needed because they may not have 'auto' checked, and so they make some changes to their $meta props, then hit save, but the updated $meta stuff isn't sent along with the save request. }, 800); } }); // if(window.DEBUG_FREEZE_MODE) { // this.refs.autoReloadCheckbox.checked = false; // //this.root.querySelector("#output iframe").src = "https://null.perchance.org/debug-freeze"; // } else { // // let url = "https://null.perchance.org/" + app.data.generator.name + "?v=1"; // // let url = `https://${window.generatorPublicId}.perchance.org/` + app.data.generator.name + "?v=2"; // // if(window.needToBustCacheOfIframeOnInitialLoad) url += `?__cacheBuster=${Math.random()}`; // let url = new URL(window.location.href); // url.hostname = `${window.generatorPublicId}.perchance.org`; // if(window.needToBustCacheOfIframeOnInitialLoad) { // url.searchParams.set("__cacheBust", Math.random().toString()+Math.random().toString()); // } // // This is to ensure cache is busted even if they have URL params added to the URL. // // Cloudflare has limits to the number of cache clears, so this is a hacky but full-proof workaround. // // The top-level frame has the `window.thisIsNotAnOldCachedPagePromise` stuff to handle this, but we still // // need to make sure the iframe is also busted. // // We only need this during initial page load - not when reload button / save button is pressed, since in those // // cases the __initWithDataFromParentWindow query param is added to the URL, which means the iframe gets fresh // // data from the parent, regardless of the html that the server sends (i.e. the data that's embedded in the HTML // // is ignored) // url.searchParams.set("__generatorLastEditTime", window.generatorLastSaveTime); // this.root.querySelector("#output iframe").src = url; // } if(window.DEBUG_FREEZE_MODE) { this.refs.autoReloadCheckbox.checked = false; this.root.querySelector("#output iframe").src = "https://null.perchance.org/debug-freeze"; } else if(window.needToBustCacheOfIframeOnInitialLoad) { let url = new URL(window.location.href); url.hostname = `${window.generatorPublicId}.perchance.org`; url.searchParams.set("__cacheBust", Math.random().toString()+Math.random().toString()); this.root.querySelector("#output iframe").src = url; } // whenever the codemirror scrollbar appears or dissapears, update the buttons positioning so they sit beside the scrollbar: const updateCodeEditorButtonsPos = () => { document.querySelector(".code-editor-buttons-ctn").style.right = (document.querySelector(".CodeMirror-vscrollbar").offsetWidth+5)+"px"; }; new MutationObserver(updateCodeEditorButtonsPos).observe(document.querySelector(".CodeMirror-vscrollbar"), {attributes: true}); setTimeout(updateCodeEditorButtonsPos, 2000); // <-- call it once at the start for init // same for html area: const updateHtmlEditorButtonsPos = () => { document.querySelector(".toggle-wrap-button-html").style.right = (document.querySelectorAll(".CodeMirror-vscrollbar")[1].offsetWidth+5)+"px"; }; new MutationObserver(updateHtmlEditorButtonsPos).observe(document.querySelectorAll(".CodeMirror-vscrollbar")[1], {attributes: true}); setTimeout(updateHtmlEditorButtonsPos, 2000); // <-- call it once at the start for init // Initialise Split.js panels let sizesStore = new Store('editor-split-sizes'); let sizes; let defaultSizes = { ab: [60,40], cd: [90,10], ef: [75,25], }; if(sizesStore.data[app.data.generator.name]) { sizes = sizesStore.data[app.data.generator.name]; } else { // default sizes sizes = JSON.parse(JSON.stringify(defaultSizes)); sizesStore.data[app.data.generator.name] = sizes; sizesStore.save(); } function trimSplitSizes(arr) { // for some reason split sizes sometimes add to over 100% and the editor breaks. // this trims it to 100% arr = arr.slice(0); let sum = arr[0] + arr[1]; if(sum <= 100) return arr; else { arr[0] = Math.round(arr[0]); arr[1] = Math.round(arr[1]); while(arr[0] + arr[1] > 100) { if(Math.random() < 0.5) { arr[0]--; } else { arr[1]--; } } return arr; } } let split_ab = Split([this.root.querySelector('#a'), this.root.querySelector('#b')], { gutterSize: 8, sizes: sizes.ab, cursor: 'col-resize', minSize: 30, onDragEnd: function() { let data = sizesStore.data[app.data.generator.name]; if(!data) { data = JSON.parse(JSON.stringify(defaultSizes)); } // they could have changed generator's name data.ab = split_ab.getSizes(); data.ab = trimSplitSizes(data.ab); sizesStore.save(); } }); let split_cd = Split([this.root.querySelector('#c'), this.root.querySelector('#perchanceConsoleEl')], { direction: 'vertical', sizes: sizes.cd, gutterSize: 8, cursor: 'row-resize', minSize: 30, onDragEnd: function() { let data = sizesStore.data[app.data.generator.name]; if(!data) { data = JSON.parse(JSON.stringify(defaultSizes)); } // they could have changed generator's name data.cd = split_cd.getSizes(); data.cd = trimSplitSizes(data.cd); sizesStore.save(); } }); let split_ef = Split([this.root.querySelector('#e'), this.root.querySelector('#f')], { direction: 'vertical', sizes: sizes.ef, gutterSize: 8, cursor: 'row-resize', minSize: 30, onDragEnd: function() { let data = sizesStore.data[app.data.generator.name]; if(!data) { data = JSON.parse(JSON.stringify(defaultSizes)); } // they could have changed generator's name data.ef = split_ef.getSizes(); data.ef = trimSplitSizes(data.ef); sizesStore.save(); } }); // need this because otherwise for some reason there's some weird `overscroll-behaviour` stuff near the edges of the screen (even with touch-action:none overlay workaround). if(window.location.hash !== "#edit") this.root.querySelectorAll("#main .gutter").forEach(el => el.style.display="none"); setTimeout(() => { this.modelTextEditor.refresh(); this.outputTemplateEditor.refresh(); }, 10); ////////////////////// // CONSOLE // ////////////////////// function escapeHtml(html) { return html.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } let consoleOutputStyle = ""; let consoleCommandStore = new Store("console-commands"); if(!consoleCommandStore.data.generators) { consoleCommandStore.data.generators = {}; } let consoleOutputEl = document.querySelector("#console-output"); let consoleInputEl = document.querySelector("#console-input"); consoleOutputEl.addEventListener("click", function() { consoleInputEl.focus(); }); consoleOutputEl.srcdoc = consoleOutputStyle + `e.g. try typing I rolled a \{1-6\}! or [yourListName] (including the brackets) above, and then press enter.`; let previousCommands = consoleCommandStore.data.generators[app.data.generator.name] || [""]; let currCommandIndex = 0; // NOT previousCommands array index!! 0 represents most recent element, 1 is second most recent, etc. consoleInputEl.addEventListener("keydown", async (e) => { // e.preventDefault(); let command = consoleInputEl.value; if(e.which === 13) { // enter let optEl = document.querySelector("#consoleInterpreterOption"); let interpreter = optEl.options[optEl.selectedIndex].value; if(interpreter === "plaintext") { consoleOutputEl.srcdoc = consoleOutputStyle.replace("font-family:monospace;", "font-family:monospace; white-space:pre-wrap;")+(escapeHtml(await this.evaluateText(command))); } else if(interpreter === "html") { consoleOutputEl.srcdoc = consoleOutputStyle+(await this.evaluateText(command)); } // this.root.style.background = "#f1f1f1"; // setTimeout(() => { this.root.style.background = "white"; },100) let prevCommand = previousCommands[previousCommands.length-1]; if(prevCommand !== command) { previousCommands.pop(); previousCommands.push(command); previousCommands.push(""); // put the empty one back on the end } if(previousCommands.length > 1000) { previousCommands.shift(); } currCommandIndex = 1; // <-- set index to that of the command that was just submitted consoleCommandStore.data.generators[app.data.generator.name] = previousCommands; // (not necessary because it keeps reference??) consoleCommandStore.save(); return; } if(e.which === 38) { // up arrow currCommandIndex++; // move towards start of array (counter-intuitive) currCommandIndex = Math.min(previousCommands.length-1, currCommandIndex); // must stay below or at length of command stack consoleInputEl.value = previousCommands[previousCommands.length-1-currCommandIndex]; setTimeout(function() { consoleInputEl.selectionStart = consoleInputEl.selectionEnd = 100000; }, 1); return; } if(e.which === 40) { // down arrow currCommandIndex--; // move towards end off array (counter-intuitive) currCommandIndex = Math.max(0, currCommandIndex); // must stay above or at zero consoleInputEl.value = previousCommands[previousCommands.length-1-currCommandIndex]; setTimeout(function() { consoleInputEl.selectionStart = consoleInputEl.selectionEnd = 100000; }, 1); return; } }); }, }; 

Weirdcore/Dreamcore names โ€• Perchance Generator (2024)

FAQs

What are Weirdcore themed names? โ€บ

more weird core name ideas cage, drizzle, sour, blaze, fungus, cyanide, ice, silver, vortex, crackle moth, and skull.

What is dream Weirdcore? โ€บ

New Word Suggestion. a surrelist aesthetic that uses motifs associated with dreams, daydreams or nightmares, portrayed through media such as videos, images.

What is the most Cottagecore name? โ€บ

  • Flora.
  • Juniper.
  • Hazel.
  • Ivy.
  • Iris.
  • Olive.
  • Dahlia.
  • Jasmine.
Aug 25, 2023

What is Weirdcore aesthetic? โ€บ

If you want a quick idea of how the Weirdcore aesthetic looks visually, think of low-quality, early internet pictures (think early 90s to mid-2000s) that look distorted and familiar. Now pair that with images with no connection or context. This gives a pretty good idea of what the aesthetic looks like.

What is Nightmarecore? โ€บ

Nightmarecore is the dark and creepy version of dreamcore. It can be associated with feeling emptiness, dread, or nostalgia. Related Aesthetics: Dreamcore, Weirdcore, Surrealism.

Is Weirdcore a real genre? โ€บ

Weirdcore is a genre of electronic music that combines elements of experimental, ambient, and noise music. It is characterized by its surreal, abstract, and often chaotic soundscapes, often featuring distorted and heavily processed samples, glitchy beats, and abstract sound design.

Is Dreamcore a horror? โ€บ

Dreamcore is a psychological horror oriented title. You won't be chased and neither be able to die. Will there be a VR release? Maybe after publishing the full game.

What are the themes of Weirdcore? โ€บ

Thematically, Weirdcore often conveys a feeling of dread, brought forth by its low-quality imagery and a lack of context in regard to the location or the message being shown.

What genre is Weirdcore? โ€บ

What genres are like Weirdcore? โ€บ

Similar genres to Weirdcore
  • Pov: Indie.
  • Sped Up.
  • Glitchbreak.
  • Slowed And Reverb.
  • Lo-fi Vgm.

What is an aesthetic name? โ€บ

With most people wanting things to be aesthetically pleasing, aesthetic names for babies are starting to become a trend. The word is creating a buzz in the online community and social media. Aesthetic refers to anything that appears classy, trendy, chic, and above all, stunning.

Top Articles
Gumdrop Bars: Delicious Old-Fashioned Dessert Recipe
The Best Ever Chicken Noodle Soup Recipe
W B Crumel Funeral Home Obituaries
Houses For Sale 180 000
Irela Torres Only Fans
Shiftwizard Login Wakemed
Indiana girl set for final surgery 5 years after suffering burns in kitchen accident
83600 Block Of 11Th Street East Palmdale Ca
Married At First Sight Novel Serenity And Zachary Chapter 950
Barefoot Rentals Key Largo
Santa Cruz Craigslist Cars And Trucks - By Owner
Lowes Maytag Pet Pro Commercial Actress
Terraria Melee Build Progression Guide & Best Class Loadouts
Websites erstellen, benennen, kopieren oder lรถschen
Choke Pony Dating App
Kamala Harris is making climate action patriotic. It just might work
Fit 4 Life Murrayville Reviews
Can You Put Elvie Stride Parts In Sterilizer
Karz Insurance Quote
Spaghetti Models | Cyclocane
Tuition Fee Compensation
What Time Is First Light Tomorrow Morning
M Life Insider
Bonduel Amish Auction 2023
What Is My Walmart Store Number
Master Series Snap On Tool Box
Take Me To The Closest Chase Bank
Redgifs.comn
Wdl Nursing Abbreviation
Watch My Best Friend's Exorcism Online Free
Ixl.prentiss
Examination Policies: Finals, Midterms, General
Dicks Sporting Good Lincoln Ne
Syracuse Deadline
10000 Blaulicht-Meldungen aus Baden-Wรผrttemberg | Presseportal
Claudia Capertoni Only Fans
Gargoyle Name Generator
Jetnet Retirees Aa
Walgreens Wellington Green
Keck Healthstream
Standard Schnauzer For Sale Craigslist
Tamu Registration Worksheet
Experity Installer
Jasper William Oliver Cable Alexander
Sun Massage Tucson Reviews
Intel Core i3-4130 - CM8064601483615 / BX80646I34130
55Th And Kedzie Elite Staffing
Busted Newspaper Zapata Tx
Redbox Walmart Near Me
Creed 3 Showtimes Near Island 16 Cinema De Lux
Temperature At 12 Pm Today
Latest Posts
Article information

Author: Sen. Ignacio Ratke

Last Updated:

Views: 6045

Rating: 4.6 / 5 (56 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Sen. Ignacio Ratke

Birthday: 1999-05-27

Address: Apt. 171 8116 Bailey Via, Roberthaven, GA 58289

Phone: +2585395768220

Job: Lead Liaison

Hobby: Lockpicking, LARPing, Lego building, Lapidary, Macrame, Book restoration, Bodybuilding

Introduction: My name is Sen. Ignacio Ratke, I am a adventurous, zealous, outstanding, agreeable, precious, excited, gifted person who loves writing and wants to share my knowledge and understanding with you.