UNPKG

superpowers-game-ftext-plugin

Version:

Generic text asset for the Superpowers Game system of Superpowers, the extensible HTML5 2D+3D game engine.

235 lines (194 loc) 7.57 kB
import info from "./info"; import ui from "./ui"; import * as async from "async"; import * as OT from "operational-transform"; import fTextAsset from "../../data/fTextAsset"; import fTextSettingsResource from "../../data/fTextSettingsResource"; export let data: { projectClient?: SupClient.ProjectClient; asset?: fTextAsset; assetInstructions?: { [key: string]: any }, assetSyntax?: string; fTextSettingsResourcePub?: any, // set in onResourceReceived(), used in onfTextSettingsUpdated() } = {}; export let socket: SocketIOClient.Socket; // ---------------------------------------- // Ressource // fText resource is sub at the end of onAssetReceived() // updates the editor when the resource is received or edited // called from the resources handlers function onfTextSettingsResourceUpdated() { if (ui.editor != null) { let pub = data.fTextSettingsResourcePub; let defaultValues = fTextSettingsResource.defaultValues; let syntax: string = data.assetSyntax; for (let optionName in defaultValues) { let optionValue = (pub[optionName] != null) ? pub[optionName] : defaultValues[optionName]; // can't do 'pub[optionName] || defaultValues[optionName]' because if pub[optionName] == false, the defautl optionValue is always chosen. if (optionName === "indentWithTabs" || optionName === "tabSize" || optionName === "customTheme") continue; if (optionName.indexOf("lint_") === 0) { if (optionName === "lint_"+syntax) allowLinting(optionValue); continue; } if (optionValue != ui.editor.codeMirrorInstance.getOption(optionName)) { if (optionName === "theme") { if (optionValue === "custom") optionValue = pub["customTheme"]; loadThemeStyle(optionValue); } ui.editor.codeMirrorInstance.setOption(optionName, optionValue); } } } } function loadThemeStyle(theme: string) { let link = document.createElement("link"); link.rel = "stylesheet"; link.href = `codemirror-themes/${theme}.css`; document.head.appendChild(link); } // used in assetHandlers/onAssetReceived() and onfTextSettingsResourceUpdated-) function allowLinting(allow: boolean = true) { ui.editor.codeMirrorInstance.setOption("lint", allow); ui.isAssetLinted = allow; if (allow === false) ui.refreshErrors([]); } let resourceHandlers: any = { onResourceReceived: (resourceId: string, resource: fTextSettingsResource) => { data.fTextSettingsResourcePub = resource.pub; onfTextSettingsResourceUpdated(); }, onResourceEdited: (resourceId: string, command: string, propertyName: string) => { onfTextSettingsResourceUpdated(); } } // ---------------------------------------- // read the asset's content then return a list of instructions and their values // used to populate data.localEditorSettings // called from onAssetReceived() function parseInstructions() { let regex = /\[ftext\s*:\s*([a-zA-Z0-9\/+-]+)(\s*:\s*([a-zA-Z0-9\.\/+-]+))?\]/ig let match: any; let text = ui.editor.codeMirrorInstance.getDoc().getValue(); let instructionsCount = (text.match(/\[\s*ftext/ig) || []).length; // prevent infinite loop let instructions: any = {}; do { match = regex.exec(text); if (match != null && match[1] != null) { let name = match[1].trim().toLowerCase(); let value = match[3]; if (value != null) value = value.trim(); else value = ""; if (name === "include") { if (instructions[name] == null) instructions[name] = []; (<string[]>instructions[name]).push(value); } else instructions[name] = value.trim().toLowerCase(); } instructionsCount--; } while (match != null && instructionsCount > 0); return instructions; } // used in assetHandlers.onAssetEdited() let onAssetCommands: any = { editText: (operationData: OperationData) => { ui.hasDraft(true); ui.editor.receiveEditText(operationData); }, saveText: () => { ui.hasDraft(false); } }; let assetHandlers: any = { onAssetReceived: (err: string, asset: fTextAsset) => { if (asset.id !== info.assetId) return; data.asset = asset; // (<any>ui.errorPaneStatus.classList.toggle)("has-draft", data.asset.hasDraft); ui.hasDraft(data.asset.hasDraft); ui.editor.setText(data.asset.pub.draft); if (info.line != null && info.ch != null) ui.editor.codeMirrorInstance.getDoc().setCursor({ line: info.line, ch: info.ch }); // fText specific settings : data.assetInstructions = parseInstructions(); // get asset syntax data.assetSyntax = ""; let _languagesByExtensions: any = { md: "markdown", styl: "stylus", js: "javascript", yml: "yaml", }; let name = data.projectClient.entries.getPathFromId(data.asset.id); let match = name.match(/\.[a-zA-Z]+$/gi); if (match != null) { let syntax = match[0].replace(".", ""); if (_languagesByExtensions[syntax] != null) syntax = _languagesByExtensions[syntax]; data.assetSyntax = syntax; } // set Codemirror's mode based on the asset's syntax let syntax: string = data.assetSyntax; if (syntax != "") { let modesBySyntaxes: { [key: string]: string } = { cson: "coffeescript", html: "htmlmixed", json: "application/json", md: "markdown", shader: "x-shader/x-fragment", }; let mode = modesBySyntaxes[syntax] || syntax; ui.editor.codeMirrorInstance.setOption("mode", mode); } if (fTextSettingsResource.defaultValues["lint_"+syntax] != null) { // always put the lint gutter, because adding or removing it on the fly mess the other gutters let gutters = ui.editor.codeMirrorInstance.getOption("gutters"); gutters.unshift("CodeMirror-lint-markers"); allowLinting(true); // this value may be modified when the resource is received, from onfTextSettingsUpdated() } data.projectClient.subResource("fTextSettings", resourceHandlers); }, onAssetEdited: (id: string, command: string, ...args: any[]) => { if (id !== info.assetId) return; if (onAssetCommands[command] != null) onAssetCommands[command].apply(data.asset, args); }, onAssetTrashed: (id: string) => { if (id !== info.assetId) return; ui.editor.clear(); SupClient.onAssetTrashed(); }, }; // ---------------------------------------- let entriesHandlers: any = { onEntriesReceived: (entries: SupCore.Data.Entries) => { entries.walk((entry: any) => { if (entry.type !== "fText") return; data.projectClient.subAsset(entry.id, "fText", assetHandlers); }) }, /*onEntryAdded: (newEntry: any, parentId: string, index: number) => { }, onEntryMoved: (id: string, parentId: string, index: number) => { }, onSetEntryProperty: (id: string, key: string, value: any) => { }, onEntryTrashed: (id: string) => { },*/ } // ---------------------------------------- function onWelcomed(clientId: number) { data.projectClient = new SupClient.ProjectClient(socket, { subEntries: true }); data.projectClient.subEntries(entriesHandlers); // data.projectClient.subResource("fTextSettings", resourceHandlers); // done in onAssetReceived() ui.setupEditor(clientId); // defined in ui.ts } // start SupClient.i18n.load([{ root: `${window.location.pathname}/../..`, name: "ftextEditor" }], () => { socket = SupClient.connect(SupClient.query.project); socket.on("welcome", onWelcomed); socket.on("disconnect", SupClient.onDisconnected); });