Preferences.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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. getPreferencesFullPath(): string {
  58. var filePath = this.fileSystem.getAppPreferencesDir("AtomicEditor", "Preferences");
  59. filePath += "prefs.json";
  60. return filePath;
  61. }
  62. read(): void {
  63. var filePath = this.getPreferencesFullPath();
  64. var jsonFile;
  65. //check if file doesn't exist, create default json
  66. if (!this.fileSystem.fileExists(filePath)) {
  67. this.useDefaultConfig();
  68. this.write();
  69. return;
  70. }
  71. //Read file
  72. jsonFile = new Atomic.File(filePath, Atomic.FILE_READ);
  73. var prefs = null;
  74. try {
  75. if (jsonFile.isOpen())
  76. prefs = <PreferencesFormat>JSON.parse(jsonFile.readText());
  77. } catch (e) {
  78. prefs = null;
  79. }
  80. if (prefs) {
  81. const defaultPrefs = new PreferencesFormat();
  82. const shouldWrite = defaultPrefs.applyMissingDefaults(prefs);
  83. this._prefs = prefs;
  84. if (shouldWrite) {
  85. this.write();
  86. }
  87. } else {
  88. console.log("Editor preference file missing or invalid, regenerating default configuration");
  89. this.useDefaultConfig();
  90. this.write();
  91. }
  92. }
  93. write(): boolean {
  94. var filePath = this.getPreferencesFullPath();
  95. var jsonFile = new Atomic.File(filePath, Atomic.FILE_WRITE);
  96. if (!jsonFile.isOpen()) return false;
  97. jsonFile.writeString(JSON.stringify(this._prefs, null, 2));
  98. }
  99. saveEditorWindowData(windowData: WindowData) {
  100. this._prefs.editorWindow = windowData;
  101. this.write();
  102. }
  103. savePlayerWindowData(windowData: WindowData) {
  104. this._prefs.playerWindow = windowData;
  105. this.write();
  106. }
  107. useDefaultConfig(): void {
  108. this._prefs = new PreferencesFormat();
  109. }
  110. get cachedProjectPreferences(): any {
  111. return this._cachedProjectPreferences;
  112. }
  113. get cachedApplicationPreferences(): PreferencesFormat {
  114. return this._prefs;
  115. }
  116. get editorWindow(): WindowData {
  117. return this._prefs.editorWindow;
  118. }
  119. get playerWindow(): WindowData {
  120. return this._prefs.playerWindow;
  121. }
  122. get recentProjects(): string[] {
  123. return this._prefs.recentProjects;
  124. }
  125. get uiData(): UserInterfaceData {
  126. return this._prefs.uiData;
  127. }
  128. get editorBuildData(): EditorBuildData {
  129. return this._prefs.editorBuildData;
  130. }
  131. static getInstance(): Preferences {
  132. return Preferences.instance;
  133. }
  134. /**
  135. * Load up the user preferences for the project
  136. */
  137. loadUserPrefs() {
  138. const prefsFileLoc = ToolCore.toolSystem.project.userPrefsFullPath;
  139. if (Atomic.fileSystem.fileExists(prefsFileLoc)) {
  140. let prefsFile = new Atomic.File(prefsFileLoc, Atomic.FILE_READ);
  141. try {
  142. let prefs = JSON.parse(prefsFile.readText());
  143. this._cachedProjectPreferences = prefs;
  144. } finally {
  145. prefsFile.close();
  146. }
  147. }
  148. }
  149. /**
  150. * Return a preference value or the provided default from the user settings file located in the project
  151. * @param {string} settingsGroup name of the group these settings should fall under
  152. * @param {string} preferenceName name of the preference to retrieve
  153. * @param {number | boolean | string} defaultValue value to return if pref doesn't exist
  154. * @return {number|boolean|string}
  155. */
  156. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: number): number;
  157. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: string): string;
  158. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: boolean): boolean;
  159. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: any): any {
  160. // Cache the settings so we don't keep going out to the file
  161. if (this._cachedProjectPreferences == null) {
  162. this.loadUserPrefs();
  163. }
  164. if (this._cachedProjectPreferences && this._cachedProjectPreferences[settingsGroup]) {
  165. return this._cachedProjectPreferences[settingsGroup][preferenceName] || defaultValue;
  166. }
  167. // if all else fails
  168. return defaultValue;
  169. }
  170. /**
  171. * Sets a user preference value in the user settings file located in the project
  172. * @param {string} settingsGroup name of the group the preference lives under
  173. * @param {string} preferenceName name of the preference to set
  174. * @param {number | boolean | string} value value to set
  175. */
  176. setUserPreference(settingsGroup: string, preferenceName: string, value: number | boolean | string) {
  177. const prefsFileLoc = ToolCore.toolSystem.project.userPrefsFullPath;
  178. let prefs = {};
  179. if (Atomic.fileSystem.fileExists(prefsFileLoc)) {
  180. let prefsFile = new Atomic.File(prefsFileLoc, Atomic.FILE_READ);
  181. try {
  182. prefs = JSON.parse(prefsFile.readText());
  183. } finally {
  184. prefsFile.close();
  185. }
  186. }
  187. prefs[settingsGroup] = prefs[settingsGroup] || {};
  188. prefs[settingsGroup][preferenceName] = value;
  189. let saveFile = new Atomic.File(prefsFileLoc, Atomic.FILE_WRITE);
  190. try {
  191. saveFile.writeString(JSON.stringify(prefs, null, " "));
  192. } finally {
  193. saveFile.flush();
  194. saveFile.close();
  195. }
  196. // Cache the update
  197. this._cachedProjectPreferences = prefs;
  198. }
  199. /**
  200. * Sets a group of user preference values in the user settings file located in the project. Elements in the
  201. * group will merge in with existing group preferences. Use this method if setting a bunch of settings
  202. * at once.
  203. * @param {string} settingsGroup name of the group the preference lives under
  204. * @param {string} groupPreferenceValues an object literal containing all of the preferences for the group.
  205. */
  206. setUserPreferenceGroup(settingsGroup: string, groupPreferenceValues: Object) {
  207. const prefsFileLoc = ToolCore.toolSystem.project.userPrefsFullPath;
  208. let prefs = {};
  209. if (Atomic.fileSystem.fileExists(prefsFileLoc)) {
  210. let prefsFile = new Atomic.File(prefsFileLoc, Atomic.FILE_READ);
  211. try {
  212. prefs = JSON.parse(prefsFile.readText());
  213. } finally {
  214. prefsFile.close();
  215. }
  216. }
  217. prefs[settingsGroup] = prefs[settingsGroup] || {};
  218. for (let preferenceName in groupPreferenceValues) {
  219. prefs[settingsGroup][preferenceName] = groupPreferenceValues[preferenceName];
  220. }
  221. let saveFile = new Atomic.File(prefsFileLoc, Atomic.FILE_WRITE);
  222. try {
  223. saveFile.writeString(JSON.stringify(prefs, null, " "));
  224. } finally {
  225. saveFile.flush();
  226. saveFile.close();
  227. }
  228. // Cache the update
  229. this._cachedProjectPreferences = prefs;
  230. }
  231. }
  232. interface WindowData {
  233. x: number;
  234. y: number;
  235. width: number;
  236. height: number;
  237. monitor: number;
  238. maximized: boolean;
  239. }
  240. interface MonacoEditorSettings {
  241. theme: string;
  242. fontSize: number;
  243. fontFamily: string;
  244. showInvisibles: boolean;
  245. useSoftTabs: boolean;
  246. tabSize: number;
  247. }
  248. interface UserInterfaceData {
  249. skinPath: string;
  250. defaultSkinPath: string;
  251. fontFile: string;
  252. fontName: string;
  253. fontSize: number;
  254. }
  255. interface EditorBuildData {
  256. lastEditorBuildSHA: string;
  257. }
  258. class PreferencesFormat {
  259. constructor() {
  260. this.setDefault();
  261. }
  262. setDefault() {
  263. this.recentProjects = [];
  264. this.editorWindow = {
  265. x: 0,
  266. y: 0,
  267. width: 0,
  268. height: 0,
  269. monitor: 0,
  270. maximized: true
  271. };
  272. this.playerWindow = {
  273. x: 0,
  274. y: 0,
  275. width: 0,
  276. height: 0,
  277. monitor: 0,
  278. maximized: false
  279. };
  280. this.codeEditor = {
  281. theme: "vs-dark",
  282. fontSize: 12,
  283. fontFamily: "",
  284. showInvisibles: false,
  285. useSoftTabs: true,
  286. tabSize: 4
  287. };
  288. this.uiData = {
  289. skinPath: "AtomicEditor/editor/skin/",
  290. defaultSkinPath: "AtomicEditor/resources/default_skin/",
  291. fontFile: "AtomicEditor/resources/vera.ttf",
  292. fontName: "Vera",
  293. fontSize: 12
  294. };
  295. this.editorBuildData = {
  296. lastEditorBuildSHA: "Unversioned Build"
  297. };
  298. }
  299. /**
  300. * Run through a provided prefs block and verify that all the sections are present. If any
  301. * are missing, add the defaults in
  302. * @param {PreferencesFormat} prefs
  303. * @return boolean returns true if any missing defaults were updated
  304. */
  305. applyMissingDefaults(prefs: PreferencesFormat) {
  306. let updatedMissingDefaults = false;
  307. if (!prefs.recentProjects) {
  308. prefs.recentProjects = this.recentProjects;
  309. updatedMissingDefaults = true;
  310. }
  311. if (!prefs.editorWindow) {
  312. prefs.editorWindow = this.editorWindow;
  313. updatedMissingDefaults = true;
  314. }
  315. if (!prefs.playerWindow) {
  316. prefs.playerWindow = this.playerWindow;
  317. updatedMissingDefaults = true;
  318. }
  319. if (!prefs.codeEditor) {
  320. prefs.codeEditor = this.codeEditor;
  321. updatedMissingDefaults = true;
  322. }
  323. if (!prefs.uiData) {
  324. prefs.uiData = this.uiData;
  325. updatedMissingDefaults = true;
  326. }
  327. if (!prefs.editorBuildData) {
  328. prefs.editorBuildData = this.editorBuildData;
  329. updatedMissingDefaults = true;
  330. }
  331. return updatedMissingDefaults;
  332. }
  333. recentProjects: string[];
  334. editorWindow: WindowData;
  335. playerWindow: WindowData;
  336. codeEditor: MonacoEditorSettings;
  337. uiData: UserInterfaceData;
  338. editorBuildData: EditorBuildData;
  339. }
  340. export = Preferences;