internal CanvasDocument(CanvasDocument other) { foreach (var kvp in other._unknownFiles) { _unknownFiles.Add(kvp.Key, new FileEntry(kvp.Value)); } foreach (var kvp in other._assetFiles) { _assetFiles.Add(kvp.Key, new FileEntry(kvp.Value)); } foreach (var kvp in other._screens) { _screens.Add(kvp.Key, kvp.Value.Clone()); } foreach (var kvp in other._components) { _components.Add(kvp.Key, kvp.Value.Clone()); } _editorStateStore = new EditorStateStore(other._editorStateStore); _templateStore = new TemplateStore(other._templateStore); _dataSources = other._dataSources.JsonClone(); _screenOrder = new List <string>(other._screenOrder); _header = other._header.JsonClone(); _properties = other._properties.JsonClone(); _parameterSchema = other._parameterSchema.JsonClone(); _publishInfo = other._publishInfo.JsonClone(); _templates = other._templates.JsonClone(); _themes = other._themes.JsonClone(); _resourcesJson = other._resourcesJson.JsonClone(); _appCheckerResultJson = other._appCheckerResultJson.JsonClone(); _pcfControls = other._pcfControls.JsonClone(); _appInsights = other._appInsights.JsonClone(); _connections = other._connections.JsonClone(); _dataSourceReferences = other._dataSourceReferences.JsonClone(); _libraryReferences = other._libraryReferences.JsonClone(); _logoFile = other._logoFile != null ? new FileEntry(other._logoFile) : null; _entropy = other._entropy.JsonClone(); _checksum = other._checksum.JsonClone(); this._idRestorer = new UniqueIdRestorer(this._entropy); _localAssetInfoJson = other._localAssetInfoJson.JsonClone(); }
public AppTestTransform(ErrorContainer errors, TemplateStore templateStore, EditorStateStore stateStore, Entropy entropy) { _testStepTemplateName = "TestStep"; int i = 1; while (templateStore.TryGetTemplate(_testStepTemplateName, out _)) { _testStepTemplateName = "TestStep" + i; } var idRestorer = new UniqueIdRestorer(entropy); _screenIdToScreenName = stateStore.Contents .Where(state => state.TopParentName == state.Name) .Select(state => new KeyValuePair <string, string>(idRestorer.GetControlId(state.Name).ToString(), state.Name)).ToList(); _errors = errors; }
// Returns pair of item and index (with respect to parent order) private static (ControlInfoJson.Item item, int index) CombineIRAndState(BlockNode blockNode, ErrorContainer errors, string parent, bool isInResponsiveLayout, EditorStateStore stateStore, TemplateStore templateStore, UniqueIdRestorer uniqueIdRestorer, Entropy entropy) { var controlName = blockNode.Name.Identifier; var templateIR = blockNode.Name.Kind; var templateName = templateIR.TypeName; var variantName = templateIR.OptionalVariant; // Bottom up, merge children first var children = new List <(ControlInfoJson.Item item, int index)>(); foreach (var childBlock in blockNode.Children) { children.Add(CombineIRAndState(childBlock, errors, controlName, DynamicProperties.AddsChildDynamicProperties(templateName, variantName), stateStore, templateStore, uniqueIdRestorer, entropy)); } var orderedChildren = children.OrderBy(childPair => childPair.index).Select(pair => pair.item).ToArray(); ControlInfoJson.Template template; if (!templateStore.TryGetTemplate(templateName, out var templateState)) { template = ControlInfoJson.Template.CreateDefaultTemplate(templateName, null); } else { template = templateState.ToControlInfoTemplate(); } RecombineCustomTemplates(entropy, template, controlName); var uniqueId = uniqueIdRestorer.GetControlId(controlName); ControlInfoJson.Item resultControlInfo; if (stateStore.TryGetControlState(controlName, out var state)) { var properties = new List <ControlInfoJson.RuleEntry>(); var dynamicProperties = new List <ControlInfoJson.DynamicPropertyJson>(); foreach (var propIR in blockNode.Properties) { // Dynamic properties could be null for the galleryTemplateTemplate if (isInResponsiveLayout && state.DynamicProperties != null && DynamicProperties.IsResponsiveLayoutProperty(propIR.Identifier)) { dynamicProperties.Add(CombineDynamicPropertyIRAndState(propIR, state)); } else { properties.Add(CombinePropertyIRAndState(propIR, errors, state)); } } if (isInResponsiveLayout && state.DynamicProperties != null) { // Add dummy dynamic output props in the state at the end foreach (var dynPropState in state.DynamicProperties.Where(propState => propState.Property == null)) { dynamicProperties.Add(new ControlInfoJson.DynamicPropertyJson() { PropertyName = dynPropState.PropertyName }); } // Reorder to preserve roundtripping dynamicProperties = dynamicProperties.OrderBy(prop => state.DynamicProperties.Select(propState => propState.PropertyName).ToList().IndexOf(prop.PropertyName)).ToList(); } if (blockNode.Functions.Any()) { foreach (var func in blockNode.Functions) { var funcName = func.Identifier; var thisPropertyBlock = func.Metadata.FirstOrDefault(metadata => metadata.Identifier == PAConstants.ThisPropertyIdentifier); if (thisPropertyBlock == default) { errors.ParseError(func.SourceSpan.GetValueOrDefault(), "Function definition missing ThisProperty block"); throw new DocumentException(); } properties.Add(GetPropertyEntry(state, errors, funcName, thisPropertyBlock.Default.Expression)); foreach (var arg in func.Metadata) { if (arg.Identifier == PAConstants.ThisPropertyIdentifier) { continue; } var propName = funcName + "_" + arg.Identifier; properties.Add(GetPropertyEntry(state, errors, propName, entropy.GetInvariantScript(propName, arg.Default.Expression))); } RepopulateTemplateCustomProperties(func, templateState, errors, entropy); } } else if (template.CustomProperties?.Any(prop => prop.IsFunctionProperty) ?? false) { // For component uses, recreate the dummy props for function parameters foreach (var hiddenScopeRule in template.CustomProperties.Where(prop => prop.IsFunctionProperty).SelectMany(prop => prop.PropertyScopeKey.PropertyScopeRulesKey)) { if (!properties.Any(x => x.Property == hiddenScopeRule.Name)) { var script = entropy.GetInvariantScript(hiddenScopeRule.Name, hiddenScopeRule.ScopeVariableInfo.DefaultRule); properties.Add(GetPropertyEntry(state, errors, hiddenScopeRule.Name, script)); } } } // Preserve ordering from serialized IR // Required for roundtrip checks properties = properties.OrderBy(prop => state.Properties?.Select(propState => propState.PropertyName).ToList().IndexOf(prop.Property) ?? -1).ToList(); resultControlInfo = new ControlInfoJson.Item() { Parent = parent, Name = controlName, ControlUniqueId = uniqueId.ToString(), VariantName = variantName ?? string.Empty, Rules = properties.ToArray(), DynamicProperties = (isInResponsiveLayout && dynamicProperties.Any()) ? dynamicProperties.ToArray() : null, HasDynamicProperties = state.HasDynamicProperties, StyleName = state.StyleName, ExtensionData = state.ExtensionData, IsGroupControl = state.IsGroupControl, GroupedControlsKey = state.GroupedControlsKey, AllowAccessToGlobals = (state.IsComponentDefinition ?? false) ? templateState?.ComponentManifest?.AllowAccessToGlobals : state.AllowAccessToGlobals, }; if (state.IsComponentDefinition ?? false) { // Before AllowAccessToGlobals added to ComponentDefinition in msapp, it is present in component manifest as well. // So when reconstructing componentdefinition, we need to identify if it was ever present on component definition or not. // For this, we use state IsAllowAccessToGlobalsPresent. templateState.ComponentDefinitionInfo = new ComponentDefinitionInfoJson(resultControlInfo, template.LastModifiedTimestamp, orderedChildren, entropy.IsLegacyComponentAllowGlobalScopeCase ? null : templateState.ComponentManifest.AllowAccessToGlobals); template = templateState.ToControlInfoTemplate(); template.IsComponentDefinition = true; // Set it null so that it can be ignored. We have this information at other place. template.ComponentDefinitionInfo = null; } else { template.IsComponentDefinition = state.IsComponentDefinition; } } else { state = null; resultControlInfo = ControlInfoJson.Item.CreateDefaultControl(); resultControlInfo.Name = controlName; resultControlInfo.ControlUniqueId = uniqueId.ToString(); resultControlInfo.Parent = parent; resultControlInfo.VariantName = variantName ?? string.Empty; resultControlInfo.StyleName = $"default{templateName.FirstCharToUpper()}Style"; var properties = new List <ControlInfoJson.RuleEntry>(); var dynamicProperties = new List <ControlInfoJson.DynamicPropertyJson>(); foreach (var propIR in blockNode.Properties) { if (isInResponsiveLayout && DynamicProperties.IsResponsiveLayoutProperty(propIR.Identifier)) { dynamicProperties.Add(CombineDynamicPropertyIRAndState(propIR)); } else { properties.Add(CombinePropertyIRAndState(propIR, errors)); } } resultControlInfo.Rules = properties.ToArray(); bool hasDynamicProperties = isInResponsiveLayout && dynamicProperties.Any(); resultControlInfo.DynamicProperties = hasDynamicProperties ? dynamicProperties.ToArray() : null; resultControlInfo.HasDynamicProperties = hasDynamicProperties; resultControlInfo.AllowAccessToGlobals = templateState?.ComponentManifest?.AllowAccessToGlobals; } resultControlInfo.Template = template; resultControlInfo.Children = orderedChildren; return(resultControlInfo, state?.ParentIndex ?? -1); }
internal static SourceFile CombineIRAndState(BlockNode blockNode, ErrorContainer errors, EditorStateStore stateStore, TemplateStore templateStore, UniqueIdRestorer uniqueIdRestorer, Entropy entropy) { var topParentJson = CombineIRAndState(blockNode, errors, string.Empty, false, stateStore, templateStore, uniqueIdRestorer, entropy); return(SourceFile.New(new ControlInfoJson() { TopParent = topParentJson.item })); }
// Get everything that should be stored as a file in the .msapp. private static IEnumerable <FileEntry> GetMsAppFiles(this CanvasDocument app, ErrorContainer errors) { // Loose files foreach (var file in app._unknownFiles.Values) { yield return(file); } yield return(ToFile(FileKind.Themes, app._themes)); var header = app._header.JsonClone(); header.LastSavedDateTimeUTC = app._entropy.GetHeaderLastSaved(); yield return(ToFile(FileKind.Header, header)); var props = app._properties.JsonClone(); if (app._connections != null) { var json = Utilities.JsonSerialize(app._connections); props.LocalConnectionReferences = json; } if (app._dataSourceReferences != null) { var json = Utilities.JsonSerialize(app._dataSourceReferences); // Some formats serialize empty as "", some serialize as "{}" if (app._dataSourceReferences.Count == 0) { if (app._entropy.LocalDatabaseReferencesAsEmpty) { json = ""; } else { json = "{}"; } } props.LocalDatabaseReferences = json; } if (app._libraryReferences != null) { var json = Utilities.JsonSerialize(app._libraryReferences); props.LibraryDependencies = json; } yield return(ToFile(FileKind.Properties, props)); var(publishInfo, logoFile) = app.TransformLogoOnSave(); yield return(logoFile); if (publishInfo != null) { yield return(ToFile(FileKind.PublishInfo, publishInfo)); } // "DataComponent" data sources are not part of DataSource.json, and instead in their own file var dataSources = new DataSourcesJson { DataSources = app.GetDataSources() .SelectMany(x => x.Value) .Where(x => !x.IsDataComponent) .OrderBy(x => app._entropy.GetOrder(x)) .ToArray() }; yield return(ToFile(FileKind.DataSources, dataSources)); var sourceFiles = new List <SourceFile>(); var idRestorer = new UniqueIdRestorer(app._entropy); var maxPublishOrderIndex = app._entropy.PublishOrderIndices.Any() ? app._entropy.PublishOrderIndices.Values.Max() : 0; // Rehydrate sources before yielding any to be written, processing component defs first foreach (var controlData in app._screens.Concat(app._components) .OrderBy(source => (app._editorStateStore.TryGetControlState(source.Value.Name.Identifier, out var control) && (control.IsComponentDefinition ?? false)) ? -1 : 1)) { var sourceFile = IRStateHelpers.CombineIRAndState(controlData.Value, errors, app._editorStateStore, app._templateStore, idRestorer, app._entropy); // Offset the publishOrderIndex based on Entropy.json foreach (var ctrl in sourceFile.Flatten()) { if (app._entropy.PublishOrderIndices.TryGetValue(ctrl.Name, out var index)) { ctrl.PublishOrderIndex = index; } else { ctrl.PublishOrderIndex = ++maxPublishOrderIndex; } } sourceFiles.Add(sourceFile); } CheckUniqueIds(errors, sourceFiles); // Repair order when screens are unchanged if (sourceFiles.Where(file => !ExcludeControlFromScreenOrdering(file)).Count() == app._screenOrder.Count && sourceFiles.Where(file => !ExcludeControlFromScreenOrdering(file)).All(file => app._screenOrder.Contains(file.ControlName))) { double i = 0.0; foreach (var screen in app._screenOrder) { sourceFiles.First(file => file.ControlName == screen).Value.TopParent.Index = i; i += 1; } } else { // Make up an order, it doesn't really matter. double i = 0.0; foreach (var sourceFile in sourceFiles) { if (ExcludeControlFromScreenOrdering(sourceFile)) { continue; } sourceFile.Value.TopParent.Index = i; i += 1; } } RepairComponentInstanceIndex(app._entropy?.ComponentIndexes ?? new Dictionary <string, double>(), sourceFiles); // This ordering is essential, we need to match the order in which Studio writes the files to replicate certain order-dependent behavior. foreach (var sourceFile in sourceFiles.OrderBy(file => file.GetMsAppFilename())) { yield return(sourceFile.ToMsAppFile()); } var componentTemplates = new List <TemplateMetadataJson>(); foreach (var template in app._templateStore.Contents.Where(template => template.Value.IsComponentTemplate ?? false)) { if (((template.Value.CustomProperties?.Any() ?? false) || template.Value.ComponentAllowCustomization.HasValue) && !(template.Value.ComponentManifest?.IsDataComponent ?? false)) { componentTemplates.Add(template.Value.ToTemplateMetadata(app._entropy)); } } app._templates = new TemplatesJson() { ComponentTemplates = componentTemplates.Any() ? componentTemplates.OrderBy(x => app._entropy.GetComponentOrder(x)).ToArray() : null, UsedTemplates = app._templates.UsedTemplates.OrderBy(x => app._entropy.GetOrder(x)).ToArray() }; yield return(ToFile(FileKind.Templates, app._templates)); var componentsMetadata = new List <ComponentsMetadataJson.Entry>(); var dctemplate = new List <TemplateMetadataJson>(); foreach (var componentTemplate in app._templateStore.Contents.Values.Where(state => state.ComponentManifest != null)) { var manifest = componentTemplate.ComponentManifest; componentsMetadata.Add(new ComponentsMetadataJson.Entry { Name = manifest.Name, TemplateName = manifest.TemplateGuid, ExtensionData = manifest.ExtensionData }); if (manifest.IsDataComponent) { var controlId = GetDataComponentDefinition(sourceFiles.Select(source => source.Value), manifest.TemplateGuid, errors).ControlUniqueId; var template = new TemplateMetadataJson { Name = manifest.TemplateGuid, ComponentType = ComponentType.DataComponent, Version = app._entropy.GetTemplateVersion(manifest.TemplateGuid), IsComponentLocked = false, ComponentChangedSinceFileImport = true, ComponentAllowCustomization = true, CustomProperties = componentTemplate.CustomProperties, DataComponentDefinitionKey = manifest.DataComponentDefinitionKey }; // Rehydrate fields. template.DataComponentDefinitionKey.ControlUniqueId = controlId; dctemplate.Add(template); } } if (componentsMetadata.Count > 0) { // If the components file is present, then write out all files. yield return(ToFile(FileKind.ComponentsMetadata, new ComponentsMetadataJson { Components = componentsMetadata .OrderBy(x => app._entropy.GetOrder(x)) .ToArray() })); } if (dctemplate.Count > 0) { yield return(ToFile(FileKind.DataComponentTemplates, new DataComponentTemplatesJson { ComponentTemplates = dctemplate .OrderBy(x => app._entropy.GetOrder(x)) .ToArray() })); } // Rehydrate the DataComponent DataSource file. { IEnumerable <DataComponentSourcesJson.Entry> ds = from item in app.GetDataSources().SelectMany(x => x.Value).Where(x => x.IsDataComponent) select item.DataComponentDetails; var dsArray = ds.ToArray(); // backcompat-nit: if we have any DC, then always emit the DC Sources file, even if empty. // if (dcmetadataList.Count > 0) if (dctemplate.Count > 0 || dsArray.Length > 0) { yield return(ToFile(FileKind.DataComponentSources, new DataComponentSourcesJson { DataSources = dsArray })); } } if (app._appCheckerResultJson != null) { yield return(ToFile(FileKind.AppCheckerResult, app._appCheckerResultJson)); } if (app._resourcesJson != null) { var resources = app._resourcesJson.JsonClone(); foreach (var resource in resources.Resources) { if (resource.ResourceKind == "LocalFile") { var rootPath = string.Empty; if (app._entropy?.LocalResourceRootPaths.TryGetValue(resource.Name, out rootPath) ?? false) { resource.RootPath = rootPath; } else { resource.RootPath = string.Empty; } } } yield return(ToFile(FileKind.Resources, resources)); } foreach (var assetFile in app._assetFiles) { yield return(new FileEntry { Name = FilePath.RootedAt("Assets", assetFile.Value.Name), RawBytes = assetFile.Value.RawBytes }); } }
// Returns pair of item and index (with respect to parent order) private static (ControlInfoJson.Item item, int index) CombineIRAndState(BlockNode blockNode, ErrorContainer errors, string parent, EditorStateStore stateStore, TemplateStore templateStore, UniqueIdRestorer uniqueIdRestorer) { var controlName = blockNode.Name.Identifier; // Bottom up, merge children first var children = new List <(ControlInfoJson.Item item, int index)>(); foreach (var childBlock in blockNode.Children) { children.Add(CombineIRAndState(childBlock, errors, controlName, stateStore, templateStore, uniqueIdRestorer)); } var orderedChildren = children.OrderBy(childPair => childPair.index).Select(pair => pair.item).ToArray(); var templateIR = blockNode.Name.Kind; var templateName = templateIR.TypeName; var variantName = templateIR.OptionalVariant; ControlInfoJson.Template template; if (!templateStore.TryGetTemplate(templateName, out var templateState)) { template = ControlInfoJson.Template.CreateDefaultTemplate(templateName, null); } else { template = templateState.ToControlInfoTemplate(); } var uniqueId = uniqueIdRestorer.GetControlId(controlName); ControlInfoJson.Item resultControlInfo; if (stateStore.TryGetControlState(controlName, out var state)) { var properties = new List <ControlInfoJson.RuleEntry>(); foreach (var propIR in blockNode.Properties) { properties.Add(CombinePropertyIRAndState(propIR, errors, state)); } if (blockNode.Functions.Any()) { foreach (var func in blockNode.Functions) { var funcName = func.Identifier; var thisPropertyBlock = func.Metadata.FirstOrDefault(metadata => metadata.Identifier == PAConstants.ThisPropertyIdentifier); if (thisPropertyBlock == default) { errors.ParseError(func.SourceSpan.GetValueOrDefault(), "Function definition missing ThisProperty block"); throw new DocumentException(); } properties.Add(GetPropertyEntry(state, errors, funcName, thisPropertyBlock.Default.Expression)); foreach (var arg in func.Metadata) { if (arg.Identifier == PAConstants.ThisPropertyIdentifier) { continue; } properties.Add(GetPropertyEntry(state, errors, funcName + "_" + arg.Identifier, arg.Default.Expression)); } RepopulateTemplateCustomProperties(func, templateState, errors); } } else if (template.CustomProperties?.Any(prop => prop.IsFunctionProperty) ?? false) { // For component uses, recreate the dummy props for function parameters foreach (var hiddenScopeRule in template.CustomProperties.Where(prop => prop.IsFunctionProperty).SelectMany(prop => prop.PropertyScopeKey.PropertyScopeRulesKey)) { properties.Add(GetPropertyEntry(state, errors, hiddenScopeRule.Name, hiddenScopeRule.ScopeVariableInfo.DefaultRule)); } } // Preserve ordering from serialized IR // Required for roundtrip checks properties = properties.OrderBy(prop => state.Properties.Select(propState => propState.PropertyName).ToList().IndexOf(prop.Property)).ToList(); resultControlInfo = new ControlInfoJson.Item() { Parent = parent, Name = controlName, ControlUniqueId = uniqueId.ToString(), PublishOrderIndex = state.PublishOrderIndex, VariantName = variantName ?? string.Empty, Rules = properties.ToArray(), StyleName = state.StyleName, ExtensionData = state.ExtensionData, }; if (state.IsComponentDefinition ?? false) { templateState.ComponentDefinitionInfo = new ComponentDefinitionInfoJson(resultControlInfo, template.LastModifiedTimestamp, orderedChildren); template = templateState.ToControlInfoTemplate(); template.IsComponentDefinition = true; template.ComponentDefinitionInfo = null; } else { template.IsComponentDefinition = state.IsComponentDefinition; } } else { state = null; resultControlInfo = ControlInfoJson.Item.CreateDefaultControl(); resultControlInfo.Name = controlName; resultControlInfo.ControlUniqueId = uniqueId.ToString(); resultControlInfo.Parent = parent; resultControlInfo.VariantName = variantName ?? string.Empty; resultControlInfo.StyleName = $"default{templateName.FirstCharToUpper()}Style"; var properties = new List <ControlInfoJson.RuleEntry>(); foreach (var propIR in blockNode.Properties) { properties.Add(CombinePropertyIRAndState(propIR, errors)); } resultControlInfo.Rules = properties.ToArray(); } resultControlInfo.Template = template; resultControlInfo.Children = orderedChildren; return(resultControlInfo, state?.ParentIndex ?? -1); }