diff --git a/Core/GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h b/Core/GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h index fc38c95c6e6d..e0bd19792b3c 100644 --- a/Core/GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h +++ b/Core/GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h @@ -33,6 +33,14 @@ class GD_CORE_API EditorSettings { * \brief Unserialize the settings. */ void UnserializeFrom(const SerializerElement& element); + + /** + * \brief Return true if the settings contain no serialized data. + */ + bool IsEmpty() const { + gd::SerializerElement contentCopy(content); + return contentCopy.IsEmpty() && contentCopy.IsValueUndefined(); + } ///@} private: diff --git a/Core/GDCore/IDE/ProjectStripper.cpp b/Core/GDCore/IDE/ProjectStripper.cpp index d13cd8326991..9bd9ea26ce2f 100644 --- a/Core/GDCore/IDE/ProjectStripper.cpp +++ b/Core/GDCore/IDE/ProjectStripper.cpp @@ -7,15 +7,25 @@ #include "GDCore/Project/EventsFunctionsContainer.h" #include "GDCore/Project/EventsFunctionsExtension.h" +#include "GDCore/Project/EventsBasedObject.h" #include "GDCore/Project/ExternalEvents.h" #include "GDCore/Project/ExternalLayout.h" #include "GDCore/Project/Layout.h" #include "GDCore/Project/Project.h" #include "GDCore/IDE/WholeProjectBrowser.h" +#include "GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h" #include "GDCore/IDE/Events/BehaviorDefaultFlagClearer.h" +#include "GDCore/Serialization/SerializerElement.h" namespace gd { +namespace { +void ClearEditorSettings(gd::EditorSettings &editorSettings) { + gd::SerializerElement emptyEditorSettings; + editorSettings.UnserializeFrom(emptyEditorSettings); +} +} // namespace + void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project &project) { project.GetObjects().GetObjectGroups().Clear(); while (project.GetExternalEventsCount() > 0) @@ -26,7 +36,14 @@ void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project &project) { wholeProjectBrowser.ExposeObjects(project, behaviorDefaultFlagClearer); for (unsigned int i = 0; i < project.GetLayoutsCount(); ++i) { - project.GetLayout(i).GetEvents().Clear(); + auto &layout = project.GetLayout(i); + layout.GetEvents().Clear(); + ClearEditorSettings(layout.GetAssociatedEditorSettings()); + } + + for (unsigned int i = 0; i < project.GetExternalLayoutsCount(); ++i) { + ClearEditorSettings( + project.GetExternalLayout(i).GetAssociatedEditorSettings()); } // Keep: @@ -58,6 +75,13 @@ void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project &project) { auto &eventsBasedObject = eventsBasedObjects.at(objectIndex); eventsBasedObject.SetFullName(""); eventsBasedObject.SetDescription(""); + ClearEditorSettings( + eventsBasedObject.GetDefaultVariant().GetAssociatedEditorSettings()); + for (auto &&eventsBasedObjectVariant : + eventsBasedObject.GetVariants().GetInternalVector()) { + ClearEditorSettings( + eventsBasedObjectVariant->GetAssociatedEditorSettings()); + } eventsBasedObject.GetEventsFunctions().GetInternalVector().clear(); eventsBasedObject.GetPropertyDescriptors().GetInternalVector().clear(); } diff --git a/Core/GDCore/Project/EventsBasedObjectVariant.cpp b/Core/GDCore/Project/EventsBasedObjectVariant.cpp index 2f3ba0e03577..53b5de956ceb 100644 --- a/Core/GDCore/Project/EventsBasedObjectVariant.cpp +++ b/Core/GDCore/Project/EventsBasedObjectVariant.cpp @@ -37,7 +37,9 @@ void EventsBasedObjectVariant::SerializeTo(SerializerElement &element) const { layers.SerializeLayersTo(element.AddChild("layers")); initialInstances.SerializeTo(element.AddChild("instances")); - editorSettings.SerializeTo(element.AddChild("editionSettings")); + if (!editorSettings.IsEmpty()) { + editorSettings.SerializeTo(element.AddChild("editionSettings")); + } } void EventsBasedObjectVariant::UnserializeFrom( diff --git a/Core/GDCore/Project/ExternalLayout.cpp b/Core/GDCore/Project/ExternalLayout.cpp index 2da46ae3ef8e..be1616ef0537 100644 --- a/Core/GDCore/Project/ExternalLayout.cpp +++ b/Core/GDCore/Project/ExternalLayout.cpp @@ -22,8 +22,10 @@ void ExternalLayout::UnserializeFrom(gd::Project &project, void ExternalLayout::SerializeTo(SerializerElement& element) const { element.SetAttribute("name", name); - instances.SerializeTo(element.AddChild("instances")); - editorSettings.SerializeTo(element.AddChild("editionSettings")); + instances.SerializeTo(element.AddChild("instances")); + if (!editorSettings.IsEmpty()) { + editorSettings.SerializeTo(element.AddChild("editionSettings")); + } element.SetAttribute("associatedLayout", associatedLayout); } diff --git a/Core/GDCore/Project/Layout.cpp b/Core/GDCore/Project/Layout.cpp index d631e411fc43..a784cd8764dc 100644 --- a/Core/GDCore/Project/Layout.cpp +++ b/Core/GDCore/Project/Layout.cpp @@ -252,8 +252,10 @@ void Layout::SerializeTo(SerializerElement& element) const { element.SetAttribute("resourcesUnloading", resourcesUnloading); element.SetAttribute("disableInputWhenNotFocused", disableInputWhenNotFocused); - - editorSettings.SerializeTo(element.AddChild("uiSettings")); + + if (!editorSettings.IsEmpty()) { + editorSettings.SerializeTo(element.AddChild("uiSettings")); + } objectsContainer.GetObjectGroups().SerializeTo( element.AddChild("objectsGroups")); diff --git a/newIDE/app/scripts/import-libGD.js b/newIDE/app/scripts/import-libGD.js index 2accaaeac2f9..f4910c82cabc 100644 --- a/newIDE/app/scripts/import-libGD.js +++ b/newIDE/app/scripts/import-libGD.js @@ -49,7 +49,11 @@ if (shell.test('-f', path.join(sourceDirectory, 'libGD.js'))) { let branch = (branchShellString.stdout || '').trim(); if (branch === 'HEAD') { // We're in detached HEAD. Try to read the branch from the CI environment variables. - if (process.env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH) { + if (process.env.SEMAPHORE_GIT_PR_BRANCH) { + branch = process.env.SEMAPHORE_GIT_PR_BRANCH; + } else if (process.env.SEMAPHORE_GIT_BRANCH) { + branch = process.env.SEMAPHORE_GIT_BRANCH; + } else if (process.env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH) { branch = process.env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH; } else if (process.env.APPVEYOR_REPO_BRANCH) { branch = process.env.APPVEYOR_REPO_BRANCH; diff --git a/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectOpener.js b/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectOpener.js index 19805526b1cb..f2717dccec34 100644 --- a/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectOpener.js +++ b/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectOpener.js @@ -3,6 +3,10 @@ import optionalRequire from '../../Utils/OptionalRequire'; import { type FileMetadata } from '../index'; import { unsplit } from '../../Utils/ObjectSplitter'; import { openFilePicker, readJSONFile } from '../../Utils/FileSystem'; +import { + applyLocalProjectUiSettings, + getLocalProjectUiSettingsFilePath, +} from './LocalProjectUiSettings'; const fs = optionalRequire('fs'); const path = optionalRequire('path'); @@ -24,6 +28,10 @@ export const onOpen = ( |}> => { const filePath = fileMetadata.fileIdentifier; const projectPath = path.dirname(filePath); + const localProjectUiSettingsPath = getLocalProjectUiSettingsFilePath( + filePath, + path + ); return readJSONFile(filePath).then(object => { return unsplit(object, { getReferencePartialObject: referencePath => { @@ -34,9 +42,19 @@ export const onOpen = ( // to be un-splitted, but not the content of these properties), to avoid very slow processing // of large game files. maxUnsplitDepth: 3, - }).then(() => { - return { content: object }; - }); + }) + .then(() => { + if (!fs.existsSync(localProjectUiSettingsPath)) return; + + return readJSONFile(localProjectUiSettingsPath).then( + localProjectUiSettings => { + applyLocalProjectUiSettings(object, localProjectUiSettings); + } + ); + }) + .then(() => { + return { content: object }; + }); }); }; diff --git a/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectUiSettings.js b/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectUiSettings.js new file mode 100644 index 000000000000..56858891efe4 --- /dev/null +++ b/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectUiSettings.js @@ -0,0 +1,263 @@ +// @flow + +export const localProjectUiSettingsFolderName = '.gdevelop'; +export const localProjectUiSettingsFileExtension = '.editor-settings.json'; + +export const getLocalProjectUiSettingsFilePath = ( + projectFilePath: string, + pathModule: any +): string => { + const projectFilePathWithoutAutoSaveSuffix = projectFilePath.endsWith( + '.autosave' + ) + ? projectFilePath.substring(0, projectFilePath.length - '.autosave'.length) + : projectFilePath; + const parsedProjectFilePath = pathModule.parse( + projectFilePathWithoutAutoSaveSuffix + ); + return pathModule.join( + parsedProjectFilePath.dir, + localProjectUiSettingsFolderName, + parsedProjectFilePath.name + localProjectUiSettingsFileExtension + ); +}; + +export type LocalProjectUiSettings = {| + version: 1, + layouts?: { [string]: Object }, + externalLayouts?: { [string]: Object }, + eventsFunctionsExtensions?: { [string]: Object }, +|}; + +const hasOwnProperty = (object: Object, propertyName: string) => + (Object.prototype: any).hasOwnProperty.call(object, propertyName); + +const getName = (object: Object): ?string => + typeof object.name === 'string' ? object.name : null; + +const getOrCreateEventBasedObjectSettings = ( + localProjectUiSettings: LocalProjectUiSettings, + extensionName: string, + eventsBasedObjectName: string +) => { + let extensionsSettings = localProjectUiSettings.eventsFunctionsExtensions; + if (!extensionsSettings) { + extensionsSettings = ({}: { [string]: Object }); + localProjectUiSettings.eventsFunctionsExtensions = extensionsSettings; + } + const extensionSettings = extensionsSettings[extensionName] || {}; + extensionsSettings[extensionName] = extensionSettings; + + const eventsBasedObjectsSettings = + extensionSettings.eventsBasedObjects || ({}: { [string]: Object }); + extensionSettings.eventsBasedObjects = eventsBasedObjectsSettings; + + const eventsBasedObjectSettings = + eventsBasedObjectsSettings[eventsBasedObjectName] || {}; + eventsBasedObjectsSettings[eventsBasedObjectName] = eventsBasedObjectSettings; + + return eventsBasedObjectSettings; +}; + +export const createEmptyLocalProjectUiSettings = (): LocalProjectUiSettings => ({ + version: 1, +}); + +export const hasLocalProjectUiSettings = ( + localProjectUiSettings: LocalProjectUiSettings +): boolean => { + return ( + !!localProjectUiSettings.layouts || + !!localProjectUiSettings.externalLayouts || + !!localProjectUiSettings.eventsFunctionsExtensions + ); +}; + +export const extractLocalProjectUiSettings = ( + projectObject: Object +): LocalProjectUiSettings => { + const localProjectUiSettings = createEmptyLocalProjectUiSettings(); + + if (Array.isArray(projectObject.layouts)) { + for (const layout of projectObject.layouts) { + const layoutName = getName(layout); + if (!layoutName || !hasOwnProperty(layout, 'uiSettings')) continue; + + let layoutsSettings = localProjectUiSettings.layouts; + if (!layoutsSettings) { + layoutsSettings = ({}: { [string]: Object }); + localProjectUiSettings.layouts = layoutsSettings; + } + layoutsSettings[layoutName] = { + uiSettings: layout.uiSettings, + }; + delete layout.uiSettings; + } + } + + if (Array.isArray(projectObject.externalLayouts)) { + for (const externalLayout of projectObject.externalLayouts) { + const externalLayoutName = getName(externalLayout); + if ( + !externalLayoutName || + !hasOwnProperty(externalLayout, 'editionSettings') + ) { + continue; + } + + let externalLayoutsSettings = localProjectUiSettings.externalLayouts; + if (!externalLayoutsSettings) { + externalLayoutsSettings = ({}: { [string]: Object }); + localProjectUiSettings.externalLayouts = externalLayoutsSettings; + } + externalLayoutsSettings[externalLayoutName] = { + editionSettings: externalLayout.editionSettings, + }; + delete externalLayout.editionSettings; + } + } + + if (Array.isArray(projectObject.eventsFunctionsExtensions)) { + for (const extension of projectObject.eventsFunctionsExtensions) { + const extensionName = getName(extension); + if (!extensionName || !Array.isArray(extension.eventsBasedObjects)) { + continue; + } + + for (const eventsBasedObject of extension.eventsBasedObjects) { + const eventsBasedObjectName = getName(eventsBasedObject); + if (!eventsBasedObjectName) { + continue; + } + + if (hasOwnProperty(eventsBasedObject, 'editionSettings')) { + const eventsBasedObjectSettings = getOrCreateEventBasedObjectSettings( + localProjectUiSettings, + extensionName, + eventsBasedObjectName + ); + eventsBasedObjectSettings.editionSettings = + eventsBasedObject.editionSettings; + delete eventsBasedObject.editionSettings; + } + + if (!Array.isArray(eventsBasedObject.variants)) { + continue; + } + + for (const variant of eventsBasedObject.variants) { + const variantName = getName(variant); + if (!variantName || !hasOwnProperty(variant, 'editionSettings')) { + continue; + } + + const eventsBasedObjectSettings = getOrCreateEventBasedObjectSettings( + localProjectUiSettings, + extensionName, + eventsBasedObjectName + ); + const variantsSettings = + eventsBasedObjectSettings.variants || ({}: { [string]: Object }); + eventsBasedObjectSettings.variants = variantsSettings; + + variantsSettings[variantName] = { + editionSettings: variant.editionSettings, + }; + delete variant.editionSettings; + } + } + } + } + + return localProjectUiSettings; +}; + +export const applyLocalProjectUiSettings = ( + projectObject: Object, + localProjectUiSettings: LocalProjectUiSettings +) => { + const layoutsSettings = localProjectUiSettings.layouts; + if (layoutsSettings && Array.isArray(projectObject.layouts)) { + for (const layout of projectObject.layouts) { + const layoutName = getName(layout); + const layoutSettings = layoutName && layoutsSettings[layoutName]; + if (layoutSettings && hasOwnProperty(layoutSettings, 'uiSettings')) { + layout.uiSettings = layoutSettings.uiSettings; + } + } + } + + const externalLayoutsSettings = localProjectUiSettings.externalLayouts; + if (externalLayoutsSettings && Array.isArray(projectObject.externalLayouts)) { + for (const externalLayout of projectObject.externalLayouts) { + const externalLayoutName = getName(externalLayout); + const externalLayoutSettings = + externalLayoutName && externalLayoutsSettings[externalLayoutName]; + if ( + externalLayoutSettings && + hasOwnProperty(externalLayoutSettings, 'editionSettings') + ) { + externalLayout.editionSettings = externalLayoutSettings.editionSettings; + } + } + } + + const extensionsSettings = localProjectUiSettings.eventsFunctionsExtensions; + if (Array.isArray(projectObject.eventsFunctionsExtensions)) { + for (const extension of projectObject.eventsFunctionsExtensions) { + const extensionName = getName(extension); + const extensionSettings = + extensionName && + extensionsSettings && + extensionsSettings[extensionName]; + if ( + !extensionSettings || + !extensionSettings.eventsBasedObjects || + !Array.isArray(extension.eventsBasedObjects) + ) { + continue; + } + + for (const eventsBasedObject of extension.eventsBasedObjects) { + const eventsBasedObjectName = getName(eventsBasedObject); + const eventsBasedObjectsSettings = extensionSettings.eventsBasedObjects; + const eventsBasedObjectSettings = + eventsBasedObjectName && + eventsBasedObjectsSettings && + eventsBasedObjectsSettings[eventsBasedObjectName]; + if ( + !eventsBasedObjectSettings || + (!eventsBasedObjectSettings.variants && + !hasOwnProperty(eventsBasedObjectSettings, 'editionSettings')) + ) { + continue; + } + + if (hasOwnProperty(eventsBasedObjectSettings, 'editionSettings')) { + eventsBasedObject.editionSettings = + eventsBasedObjectSettings.editionSettings; + } + + if ( + !eventsBasedObjectSettings.variants || + !Array.isArray(eventsBasedObject.variants) + ) { + continue; + } + + for (const variant of eventsBasedObject.variants) { + const variantName = getName(variant); + const variantsSettings = eventsBasedObjectSettings.variants; + const variantSettings = + variantName && variantsSettings && variantsSettings[variantName]; + if ( + variantSettings && + hasOwnProperty(variantSettings, 'editionSettings') + ) { + variant.editionSettings = variantSettings.editionSettings; + } + } + } + } + } +}; diff --git a/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectUiSettings.spec.js b/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectUiSettings.spec.js new file mode 100644 index 000000000000..0b16db100315 --- /dev/null +++ b/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectUiSettings.spec.js @@ -0,0 +1,234 @@ +// @flow +import { + applyLocalProjectUiSettings, + extractLocalProjectUiSettings, + getLocalProjectUiSettingsFilePath, + hasLocalProjectUiSettings, +} from './LocalProjectUiSettings'; +import path from 'path-browserify'; + +const clone = (object: Object) => JSON.parse(JSON.stringify(object)); + +describe('LocalProjectUiSettings', () => { + it('builds a sidecar path scoped to the project file name', () => { + expect( + getLocalProjectUiSettingsFilePath( + path.join('games', 'folder', 'game.json'), + path + ) + ).toBe( + path.join('games', 'folder', '.gdevelop', 'game.editor-settings.json') + ); + expect( + getLocalProjectUiSettingsFilePath( + path.join('games', 'folder', 'prototype.json'), + path + ) + ).toBe( + path.join( + 'games', + 'folder', + '.gdevelop', + 'prototype.editor-settings.json' + ) + ); + expect( + getLocalProjectUiSettingsFilePath( + path.join('games', 'folder', 'game.json.autosave'), + path + ) + ).toBe( + path.join('games', 'folder', '.gdevelop', 'game.editor-settings.json') + ); + }); + + it('extracts layout editor settings from project content', () => { + const projectObject = { + layouts: [ + { + name: 'Scene', + uiSettings: { + grid: true, + gridWidth: 16, + zoomFactor: 0.5, + }, + }, + ], + externalLayouts: [ + { + name: 'External layout', + editionSettings: { + grid: false, + zoomFactor: 2, + }, + }, + ], + eventsFunctionsExtensions: [ + { + name: 'Extension', + eventsBasedObjects: [ + { + name: 'CustomObject', + editionSettings: { + zoomFactor: 1.5, + }, + variants: [ + { + name: 'Variant', + editionSettings: { + grid: true, + snap: true, + }, + }, + ], + }, + ], + }, + ], + }; + + const localProjectUiSettings = extractLocalProjectUiSettings(projectObject); + + expect(projectObject.layouts[0].uiSettings).toBeUndefined(); + expect(projectObject.externalLayouts[0].editionSettings).toBeUndefined(); + expect( + projectObject.eventsFunctionsExtensions[0].eventsBasedObjects[0] + .editionSettings + ).toBeUndefined(); + expect( + projectObject.eventsFunctionsExtensions[0].eventsBasedObjects[0] + .variants[0].editionSettings + ).toBeUndefined(); + expect(hasLocalProjectUiSettings(localProjectUiSettings)).toBe(true); + expect(localProjectUiSettings).toMatchInlineSnapshot(` + Object { + "eventsFunctionsExtensions": Object { + "Extension": Object { + "eventsBasedObjects": Object { + "CustomObject": Object { + "editionSettings": Object { + "zoomFactor": 1.5, + }, + "variants": Object { + "Variant": Object { + "editionSettings": Object { + "grid": true, + "snap": true, + }, + }, + }, + }, + }, + }, + }, + "externalLayouts": Object { + "External layout": Object { + "editionSettings": Object { + "grid": false, + "zoomFactor": 2, + }, + }, + }, + "layouts": Object { + "Scene": Object { + "uiSettings": Object { + "grid": true, + "gridWidth": 16, + "zoomFactor": 0.5, + }, + }, + }, + "version": 1, + } + `); + }); + + it('reports when there are no local UI settings to persist', () => { + const projectObject = { + layouts: [ + { + name: 'Scene', + instances: [], + }, + ], + }; + + const localProjectUiSettings = extractLocalProjectUiSettings(projectObject); + + expect(hasLocalProjectUiSettings(localProjectUiSettings)).toBe(false); + }); + + it('applies extracted settings back to matching project items', () => { + const projectObject = { + layouts: [ + { + name: 'Scene', + uiSettings: { + grid: true, + }, + }, + ], + externalLayouts: [ + { + name: 'External layout', + editionSettings: { + grid: false, + }, + }, + ], + eventsFunctionsExtensions: [ + { + name: 'Extension', + eventsBasedObjects: [ + { + name: 'CustomObject', + editionSettings: { + zoomFactor: 1.5, + }, + variants: [ + { + name: 'Variant', + editionSettings: { + snap: true, + }, + }, + ], + }, + ], + }, + ], + }; + const originalProjectObject = clone(projectObject); + const localProjectUiSettings = extractLocalProjectUiSettings(projectObject); + + applyLocalProjectUiSettings(projectObject, localProjectUiSettings); + + expect(projectObject).toEqual(originalProjectObject); + }); + + it('keeps legacy embedded ui settings when no sidecar value exists', () => { + const projectObject = { + layouts: [ + { + name: 'Scene', + uiSettings: { + grid: true, + }, + }, + ], + }; + + applyLocalProjectUiSettings(projectObject, { + version: 1, + layouts: { + OtherScene: { + uiSettings: { + grid: false, + }, + }, + }, + }); + + expect(projectObject.layouts[0].uiSettings).toEqual({ grid: true }); + }); +}); diff --git a/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectWriter.js b/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectWriter.js index c201d2505b6d..f632985c5fe0 100644 --- a/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectWriter.js +++ b/newIDE/app/src/ProjectsStorage/LocalFileStorageProvider/LocalProjectWriter.js @@ -1,11 +1,7 @@ // @flow import { t, Trans } from '@lingui/macro'; import * as React from 'react'; -import { - serializeToJSObject, - serializeToJSON, - addFinalNewline, -} from '../../Utils/Serializer'; +import { serializeToJSObject, addFinalNewline } from '../../Utils/Serializer'; import { serializeToJSObjectInBackground } from '../../Utils/BackgroundSerializer'; import { type FileMetadata, @@ -19,6 +15,11 @@ import { splitPaths, getSlugifiedUniqueNameFromProperty, } from '../../Utils/ObjectSplitter'; +import { + extractLocalProjectUiSettings, + getLocalProjectUiSettingsFilePath, + hasLocalProjectUiSettings, +} from './LocalProjectUiSettings'; import type { MessageDescriptor } from '../../Utils/i18n/MessageDescriptor.flow'; import LocalFolderPicker from '../../UI/LocalFolderPicker'; import SaveAsOptionsDialog from '../SaveAsOptionsDialog'; @@ -112,6 +113,24 @@ const writeAndCheckFormattedJSONFile = async ( await writeAndCheckFile(content, filePath); }; +const writeLocalProjectUiSettings = async ( + localProjectUiSettings: Object, + projectFilePath: string +): Promise => { + const localProjectUiSettingsPath = getLocalProjectUiSettingsFilePath( + projectFilePath, + path + ); + if (hasLocalProjectUiSettings(localProjectUiSettings)) { + await writeAndCheckFormattedJSONFile( + localProjectUiSettings, + localProjectUiSettingsPath + ); + } else { + await fs.remove(localProjectUiSettingsPath); + } +}; + const writeProjectFiles = async ({ project, filePath, @@ -140,6 +159,9 @@ const writeProjectFiles = async ({ }); } const serializeEndTime = Date.now(); + const localProjectUiSettings = extractLocalProjectUiSettings( + serializedProjectObject + ); if (project.isFolderProject()) { const partialObjects = split(serializedProjectObject, { @@ -153,17 +175,25 @@ const writeProjectFiles = async ({ isReferenceMagicPropertyName: '__REFERENCE_TO_SPLIT_OBJECT', }); - return Promise.all( - partialObjects.map(partialObject => { - return writeAndCheckFormattedJSONFile( - partialObject.object, - path.join(projectPath, partialObject.reference) + '.json' - ).catch(err => { - console.error('Unable to write a partial file:', err); + const writePromises = partialObjects.map(partialObject => { + return writeAndCheckFormattedJSONFile( + partialObject.object, + path.join(projectPath, partialObject.reference) + '.json' + ).catch(err => { + console.error('Unable to write a partial file:', err); + throw err; + }); + }); + writePromises.push( + writeLocalProjectUiSettings(localProjectUiSettings, filePath).catch( + err => { + console.error('Unable to write the local project UI settings:', err); throw err; - }); - }) - ).then(() => { + } + ) + ); + + return Promise.all(writePromises).then(() => { return writeAndCheckFormattedJSONFile( serializedProjectObject, filePath @@ -173,6 +203,7 @@ const writeProjectFiles = async ({ }); }); } else { + await writeLocalProjectUiSettings(localProjectUiSettings, filePath); await writeAndCheckFormattedJSONFile(serializedProjectObject, filePath); } @@ -384,12 +415,18 @@ export const onAutoSaveProject = ( fileMetadata: FileMetadata ): Promise => { const autoSavePath = fileMetadata.fileIdentifier + '.autosave'; - return writeAndCheckFile(serializeToJSON(project), autoSavePath).catch( - err => { + const serializedProjectObject = serializeToJSObject(project); + const localProjectUiSettings = extractLocalProjectUiSettings( + serializedProjectObject + ); + return writeLocalProjectUiSettings(localProjectUiSettings, autoSavePath) + .then(() => + writeAndCheckFormattedJSONFile(serializedProjectObject, autoSavePath) + ) + .catch(err => { console.error(`Unable to write ${autoSavePath}:`, err); throw err; - } - ); + }); }; export const getWriteErrorMessage = (error: Error): MessageDescriptor =>