Preferences.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. //
  2. // Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. class Preferences {
  23. private static Ctor = (() => {
  24. new Preferences();
  25. })();
  26. private fileSystem: Atomic.FileSystem;
  27. private static instance: Preferences;
  28. private _prefs: PreferencesFormat;
  29. private _cachedProjectPreferences: Object = null;
  30. constructor() {
  31. this.fileSystem = Atomic.getFileSystem();
  32. Preferences.instance = this;
  33. }
  34. registerRecentProject(path: string): void {
  35. var index = this._prefs.recentProjects.indexOf(path);
  36. if (index >= 0) {
  37. this._prefs.recentProjects.splice(index, 1);
  38. }
  39. this._prefs.recentProjects.unshift(path);
  40. this.updateRecentProjects(true);
  41. }
  42. updateRecentProjects(write: boolean = false): void {
  43. for (var i = 0; i < this._prefs.recentProjects.length; i++) {
  44. var path = this._prefs.recentProjects[i];
  45. if (!this.fileSystem.exists(path)) {
  46. this._prefs.recentProjects.splice(i, 1);
  47. write = true;
  48. }
  49. }
  50. if (write)
  51. this.write();
  52. }
  53. deleteRecentProjects(): void {
  54. this._prefs.recentProjects.length = 0;
  55. this.write();
  56. }
  57. addColorHistory(path: string): void {
  58. var index = this._prefs.colorHistory.indexOf(path); // search array for entry
  59. if (index >= 0) { // if its in there,
  60. this._prefs.colorHistory.splice(index, 1); // REMOVE it.
  61. }
  62. this._prefs.colorHistory.unshift(path); // now add it to beginning of array
  63. this.updateColorHistory(true); // update and write out
  64. }
  65. updateColorHistory(write: boolean = false): void {
  66. var len = this._prefs.colorHistory.length; // we only need indexes 0-7 now
  67. var over = 8;
  68. if ( len >= over ) // have MOaR than we need
  69. this._prefs.colorHistory.splice( over, len - over ); // remove the excess
  70. if (write)
  71. this.write();
  72. }
  73. getPreferencesFullPath(): string {
  74. var filePath = this.fileSystem.getAppPreferencesDir("AtomicEditor", "Preferences");
  75. filePath += "prefs.json";
  76. return filePath;
  77. }
  78. read(): void {
  79. var filePath = this.getPreferencesFullPath();
  80. var jsonFile;
  81. //check if file doesn't exist, create default json
  82. if (!this.fileSystem.fileExists(filePath)) {
  83. this.useDefaultConfig();
  84. this.write();
  85. return;
  86. }
  87. //Read file
  88. jsonFile = new Atomic.File(filePath, Atomic.FileMode.FILE_READ);
  89. var prefs = null;
  90. try {
  91. if (jsonFile.isOpen())
  92. prefs = <PreferencesFormat>JSON.parse(jsonFile.readText());
  93. } catch (e) {
  94. prefs = null;
  95. }
  96. if (prefs) {
  97. const defaultPrefs = new PreferencesFormat();
  98. const shouldWrite = defaultPrefs.applyMissingDefaults(prefs);
  99. this._prefs = prefs;
  100. if (shouldWrite) {
  101. this.write();
  102. }
  103. } else {
  104. console.log("Editor preference file missing or invalid, regenerating default configuration");
  105. this.useDefaultConfig();
  106. this.write();
  107. }
  108. }
  109. write(): boolean {
  110. var filePath = this.getPreferencesFullPath();
  111. var jsonFile = new Atomic.File(filePath, Atomic.FileMode.FILE_WRITE);
  112. if (!jsonFile.isOpen()) return false;
  113. jsonFile.writeString(JSON.stringify(this._prefs, null, 2));
  114. }
  115. saveEditorWindowData(windowData: WindowData) {
  116. this._prefs.editorWindow = windowData;
  117. this.write();
  118. }
  119. savePlayerWindowData(windowData: WindowData) {
  120. this._prefs.playerWindow = windowData;
  121. this.write();
  122. }
  123. saveEditorUiData(uiData: UserInterfaceData) {
  124. this._prefs.uiData = uiData;
  125. this.write();
  126. }
  127. toggleTheme() : void { // swap the themes
  128. var uiData = this.uiData;
  129. if ( this.uiData.defaultSkinPath == "AtomicEditor/resources/default_skin/" ) {
  130. this.uiData.defaultSkinPath = "AtomicEditor/resources/default_skin_light/";
  131. this.uiData.skinPath = "AtomicEditor/editor/skin_light/";
  132. }
  133. else {
  134. this.uiData.defaultSkinPath = "AtomicEditor/resources/default_skin/";
  135. this.uiData.skinPath = "AtomicEditor/editor/skin/";
  136. }
  137. var ui = Atomic.ui; // install the new skins, live action
  138. ui.loadSkin(this.uiData.skinPath + "/skin.tb.txt", this.uiData.defaultSkinPath + "/skin.tb.txt");
  139. this.saveEditorUiData(this.uiData); // save preferences
  140. }
  141. useDefaultConfig(): void {
  142. this._prefs = new PreferencesFormat();
  143. }
  144. get cachedProjectPreferences(): any {
  145. return this._cachedProjectPreferences;
  146. }
  147. get cachedApplicationPreferences(): PreferencesFormat {
  148. return this._prefs;
  149. }
  150. get editorWindow(): WindowData {
  151. return this._prefs.editorWindow;
  152. }
  153. get playerWindow(): WindowData {
  154. return this._prefs.playerWindow;
  155. }
  156. get recentProjects(): string[] {
  157. return this._prefs.recentProjects;
  158. }
  159. get colorHistory(): string[] {
  160. return this._prefs.colorHistory;
  161. }
  162. get uiData(): UserInterfaceData {
  163. return this._prefs.uiData;
  164. }
  165. get editorBuildData(): EditorBuildData {
  166. return this._prefs.editorBuildData;
  167. }
  168. get editorFeatures(): EditorFeatures {
  169. return this._prefs.editorFeatures;
  170. }
  171. static getInstance(): Preferences {
  172. return Preferences.instance;
  173. }
  174. /**
  175. * Load up the user preferences for the project
  176. */
  177. loadUserPrefs() {
  178. const prefsFileLoc = ToolCore.toolSystem.project.userPrefsFullPath;
  179. if (Atomic.fileSystem.fileExists(prefsFileLoc)) {
  180. let prefsFile = new Atomic.File(prefsFileLoc, Atomic.FileMode.FILE_READ);
  181. try {
  182. let prefs = JSON.parse(prefsFile.readText());
  183. this._cachedProjectPreferences = prefs;
  184. } finally {
  185. prefsFile.close();
  186. }
  187. }
  188. }
  189. /**
  190. * Return a preference value or the provided default from the user settings file located in the project
  191. * @param {string} settingsGroup name of the group these settings should fall under
  192. * @param {string} preferenceName name of the preference to retrieve
  193. * @param {number | boolean | string} defaultValue value to return if pref doesn't exist
  194. * @return {number|boolean|string}
  195. */
  196. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: number): number;
  197. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: string): string;
  198. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: boolean): boolean;
  199. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: any): any {
  200. // Cache the settings so we don't keep going out to the file
  201. if (this._cachedProjectPreferences == null) {
  202. this.loadUserPrefs();
  203. }
  204. if (this._cachedProjectPreferences && this._cachedProjectPreferences[settingsGroup]) {
  205. return this._cachedProjectPreferences[settingsGroup][preferenceName] || defaultValue;
  206. }
  207. // if all else fails
  208. return defaultValue;
  209. }
  210. /**
  211. * Sets a preference value in a preferences file that is provided
  212. * @param {string} preferencesFilePath path to the prefs file to update
  213. * @param {string} settingsGroup name of the group the preference lives under
  214. * @param {string} preferenceName name of the preference to set
  215. * @param {number | boolean | string} value value to set
  216. */
  217. setGenericPreference(preferencesFilePath: string, settingsGroup: string, preferenceName: string, value: number | boolean | string): Object {
  218. let prefs = {};
  219. if (Atomic.fileSystem.fileExists(preferencesFilePath)) {
  220. let prefsFile = new Atomic.File(preferencesFilePath, Atomic.FileMode.FILE_READ);
  221. try {
  222. prefs = JSON.parse(prefsFile.readText());
  223. } finally {
  224. prefsFile.close();
  225. }
  226. }
  227. prefs[settingsGroup] = prefs[settingsGroup] || {};
  228. prefs[settingsGroup][preferenceName] = value;
  229. let saveFile = new Atomic.File(preferencesFilePath, Atomic.FileMode.FILE_WRITE);
  230. try {
  231. saveFile.writeString(JSON.stringify(prefs, null, " "));
  232. } finally {
  233. saveFile.flush();
  234. saveFile.close();
  235. }
  236. // Cache the update
  237. return prefs;
  238. }
  239. /**
  240. * Sets a user preference value in the user settings file located in the project
  241. * @param {string} settingsGroup name of the group the preference lives under
  242. * @param {string} preferenceName name of the preference to set
  243. * @param {number | boolean | string} value value to set
  244. */
  245. setUserPreference(settingsGroup: string, preferenceName: string, value: number | boolean | string) {
  246. const prefsFileLoc = ToolCore.toolSystem.project.userPrefsFullPath;
  247. const prefs = this.setGenericPreference(prefsFileLoc, settingsGroup, preferenceName, value);
  248. // Cache the update
  249. this._cachedProjectPreferences = prefs;
  250. }
  251. /**
  252. * Sets an editor preference value in the global user settings file
  253. * @param {string} settingsGroup name of the group the preference lives under
  254. * @param {string} preferenceName name of the preference to set
  255. * @param {number | boolean | string} value value to set
  256. */
  257. setApplicationPreference(settingsGroup: string, preferenceName: string, value: number | boolean | string) {
  258. const prefsFileLoc = this.getPreferencesFullPath();
  259. const prefs = this.setGenericPreference(prefsFileLoc, settingsGroup, preferenceName, value);
  260. // Cache the update
  261. this._prefs = prefs as PreferencesFormat;
  262. }
  263. /**
  264. * Return a preference value or the provided default from the global user settings file located in the project
  265. * @param {string} settingsGroup name of the group these settings should fall under
  266. * @param {string} preferenceName name of the preference to retrieve
  267. * @param {number | boolean | string} defaultValue value to return if pref doesn't exist
  268. * @return {number|boolean|string}
  269. */
  270. getApplicationPreference(settingsGroup: string, preferenceName: string, defaultValue?: number): number;
  271. getApplicationPreference(settingsGroup: string, preferenceName: string, defaultValue?: string): string;
  272. getApplicationPreference(settingsGroup: string, preferenceName: string, defaultValue?: boolean): boolean;
  273. getApplicationPreference(settingsGroup: string, preferenceName: string, defaultValue?: any): any {
  274. // Cache the settings so we don't keep going out to the file
  275. if (this._prefs == null) {
  276. this.read();
  277. }
  278. if (this._prefs && this._prefs[settingsGroup]) {
  279. return this._prefs[settingsGroup][preferenceName] || defaultValue;
  280. }
  281. // if all else fails
  282. return defaultValue;
  283. }
  284. /**
  285. * Sets a group of user preference values in the user settings file located in the project. Elements in the
  286. * group will merge in with existing group preferences. Use this method if setting a bunch of settings
  287. * at once.
  288. * @param {string} settingsGroup name of the group the preference lives under
  289. * @param {string} groupPreferenceValues an object literal containing all of the preferences for the group.
  290. */
  291. setUserPreferenceGroup(settingsGroup: string, groupPreferenceValues: Object) {
  292. const prefsFileLoc = ToolCore.toolSystem.project.userPrefsFullPath;
  293. let prefs = {};
  294. if (Atomic.fileSystem.fileExists(prefsFileLoc)) {
  295. let prefsFile = new Atomic.File(prefsFileLoc, Atomic.FileMode.FILE_READ);
  296. try {
  297. prefs = JSON.parse(prefsFile.readText());
  298. } finally {
  299. prefsFile.close();
  300. }
  301. }
  302. prefs[settingsGroup] = prefs[settingsGroup] || {};
  303. for (let preferenceName in groupPreferenceValues) {
  304. prefs[settingsGroup][preferenceName] = groupPreferenceValues[preferenceName];
  305. }
  306. let saveFile = new Atomic.File(prefsFileLoc, Atomic.FileMode.FILE_WRITE);
  307. try {
  308. saveFile.writeString(JSON.stringify(prefs, null, " "));
  309. } finally {
  310. saveFile.flush();
  311. saveFile.close();
  312. }
  313. // Cache the update
  314. this._cachedProjectPreferences = prefs;
  315. }
  316. }
  317. interface WindowData {
  318. x: number;
  319. y: number;
  320. width: number;
  321. height: number;
  322. monitor: number;
  323. maximized: boolean;
  324. }
  325. interface MonacoEditorSettings {
  326. theme: string;
  327. fontSize: number;
  328. fontFamily: string;
  329. showInvisibles: boolean;
  330. useSoftTabs: boolean;
  331. tabSize: number;
  332. }
  333. interface UserInterfaceData {
  334. skinPath: string;
  335. defaultSkinPath: string;
  336. fontFile: string;
  337. fontName: string;
  338. fontSize: number;
  339. }
  340. interface EditorBuildData {
  341. lastEditorBuildSHA: string;
  342. }
  343. interface EditorFeatures {
  344. closePlayerLog: boolean;
  345. defaultPath: string;
  346. defaultLanguage: string;
  347. screenshotPath: string;
  348. screenshotFormat: string;
  349. }
  350. interface DevelopmentUI {
  351. projectFrameWidthScalar: number;
  352. }
  353. class PreferencesFormat {
  354. constructor() {
  355. this.setDefault();
  356. }
  357. setDefault() {
  358. this.recentProjects = [];
  359. this.colorHistory = [ "#000000", "#ffffff", "#00ff00", "#0000ff", "#ff0000", "#ff00ff", "#ffff00", "#668866" ];
  360. this.editorWindow = {
  361. x: 0,
  362. y: 0,
  363. width: 0,
  364. height: 0,
  365. monitor: 0,
  366. maximized: true
  367. };
  368. this.playerWindow = {
  369. x: 0,
  370. y: 0,
  371. width: 0,
  372. height: 0,
  373. monitor: 0,
  374. maximized: false
  375. };
  376. this.codeEditor = {
  377. theme: "vs-dark",
  378. fontSize: 12,
  379. fontFamily: "",
  380. showInvisibles: false,
  381. useSoftTabs: true,
  382. tabSize: 4
  383. };
  384. this.uiData = {
  385. skinPath: "AtomicEditor/editor/skin/",
  386. defaultSkinPath: "AtomicEditor/resources/default_skin/",
  387. fontFile: "AtomicEditor/resources/vera.ttf",
  388. fontName: "Vera",
  389. fontSize: 12
  390. };
  391. this.editorBuildData = {
  392. lastEditorBuildSHA: "Unversioned Build"
  393. };
  394. var fileSystem = Atomic.getFileSystem();
  395. var userDocuments = fileSystem.userDocumentsDir;
  396. if (Atomic.platform == "MacOSX") userDocuments += "Documents/";
  397. userDocuments += "AtomicProjects";
  398. this.editorFeatures = {
  399. closePlayerLog: true,
  400. defaultPath: userDocuments,
  401. defaultLanguage: "JavaScript",
  402. screenshotPath: userDocuments,
  403. screenshotFormat: "png"
  404. };
  405. this.developmentUI = {
  406. projectFrameWidthScalar: 1
  407. };
  408. }
  409. /**
  410. * Run through a provided prefs block and verify that all the sections are present. If any
  411. * are missing, add the defaults in
  412. * @param {PreferencesFormat} prefs
  413. * @return boolean returns true if any missing defaults were updated
  414. */
  415. applyMissingDefaults(prefs: PreferencesFormat) {
  416. let updatedMissingDefaults = false;
  417. if (!prefs.recentProjects) {
  418. prefs.recentProjects = this.recentProjects;
  419. updatedMissingDefaults = true;
  420. }
  421. if (!prefs.colorHistory) {
  422. prefs.colorHistory = this.colorHistory;
  423. updatedMissingDefaults = true;
  424. }
  425. if (!prefs.editorWindow) {
  426. prefs.editorWindow = this.editorWindow;
  427. updatedMissingDefaults = true;
  428. }
  429. if (!prefs.playerWindow) {
  430. prefs.playerWindow = this.playerWindow;
  431. updatedMissingDefaults = true;
  432. }
  433. if (!prefs.codeEditor) {
  434. prefs.codeEditor = this.codeEditor;
  435. updatedMissingDefaults = true;
  436. }
  437. if (!prefs.uiData) {
  438. prefs.uiData = this.uiData;
  439. updatedMissingDefaults = true;
  440. }
  441. if (!prefs.editorBuildData) {
  442. prefs.editorBuildData = this.editorBuildData;
  443. updatedMissingDefaults = true;
  444. }
  445. if (!prefs.editorFeatures) {
  446. prefs.editorFeatures = this.editorFeatures;
  447. updatedMissingDefaults = true;
  448. }
  449. if (!prefs.editorFeatures.defaultPath) {
  450. prefs.editorFeatures.defaultPath = this.editorFeatures.defaultPath;
  451. updatedMissingDefaults = true;
  452. }
  453. if (!prefs.editorFeatures.screenshotPath) {
  454. prefs.editorFeatures.screenshotPath = this.editorFeatures.screenshotPath;
  455. updatedMissingDefaults = true;
  456. }
  457. if (!prefs.editorFeatures.screenshotFormat) {
  458. prefs.editorFeatures.screenshotFormat = this.editorFeatures.screenshotFormat;
  459. updatedMissingDefaults = true;
  460. }
  461. if (!prefs.developmentUI) {
  462. prefs.developmentUI = this.developmentUI;
  463. updatedMissingDefaults = true;
  464. }
  465. return updatedMissingDefaults;
  466. }
  467. recentProjects: string[];
  468. editorWindow: WindowData;
  469. playerWindow: WindowData;
  470. codeEditor: MonacoEditorSettings;
  471. uiData: UserInterfaceData;
  472. editorBuildData: EditorBuildData;
  473. colorHistory: string[];
  474. editorFeatures: EditorFeatures;
  475. developmentUI: DevelopmentUI;
  476. }
  477. export = Preferences;