From 12b9590c9c48097513db54a063e861c210c39d41 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 2 Oct 2025 11:21:14 +0100 Subject: [PATCH 01/11] put a progress bar back underneath the imports so you can guage how long it's taking. --- .../src/components/usync-progress-box.ts | 61 ++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts index b880c44d8..a67247eb7 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts @@ -6,6 +6,8 @@ import { property, nothing, state, + when, + classMap, } from '@umbraco-cms/backoffice/external/lit'; import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; import { @@ -52,10 +54,26 @@ export class uSyncProcessBox extends UmbElementMixin(LitElement) { @property({ type: Boolean }) complete: boolean = false; + @state() + showProgress: boolean = true; + + #hideProgress() { + window.setTimeout(() => { + this.showProgress = false; + }, 2000); + } + render() { if (!this.actions) return nothing; - var actionHtml = this.actions?.map((action) => { + let progress = 0; + const actionCount = this.actions.length; + const boxSize = 100 / actionCount; + const actionProgress = (this.updateMsg?.count ?? 0) / (this.updateMsg?.total ?? 1); + + let actionHtml = this.actions?.map((action) => { + if (action.status == HandlerStatus.COMPLETE) progress++; + return html`
${actionHtml}
${this.updateMsg?.message}
+ `; } @@ -83,7 +116,7 @@ export class uSyncProcessBox extends UmbElementMixin(LitElement) { if (action.status == HandlerStatus.PENDING) return; if (action.status == HandlerStatus.PROCESSING) { return html` - `; } @@ -136,6 +169,21 @@ export class uSyncProcessBox extends UmbElementMixin(LitElement) { padding: 0 var(--uui-size-7); } + .rotating { + animation: spin-animation 1s infinite; + animation-timing-function: linear; + display: inline-block; + } + + @keyframes spin-animation { + 0% { + transform: rotate(360deg); + } + 100% { + transform: rotate(0deg); + } + } + .action uui-icon { font-size: var(--uui-size-12); } @@ -157,6 +205,15 @@ export class uSyncProcessBox extends UmbElementMixin(LitElement) { font-weight: bold; text-align: center; } + + uui-progress-bar { + padding: 0; + margin: 0; + } + + .hidden { + opacity: 0; + } `; } From adb7c72f0c4dc150f8dfda4275a03f0138602334 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 2 Oct 2025 14:43:24 +0100 Subject: [PATCH 02/11] return of the single item import. button --- uSync.BackOffice/Services/ISyncService.cs | 6 + uSync.BackOffice/Services/SyncService.cs | 12 + .../SyncHandlers/Interfaces/ISyncHandler.cs | 7 + .../SyncHandlers/SyncHandlerRoot.cs | 15 + .../Controllers/Actions/SyncItemController.cs | 29 + .../usync-assets/src/api/sdk.gen.ts | 13 +- .../usync-assets/src/api/types.gen.ts | 689 ++++++++++++++++++ .../src/components/usync-progress-box.ts | 1 - .../src/components/usync-results-view.ts | 4 + .../src/dialogs/details-modal-element.ts | 63 +- .../src/dialogs/details-modal-token.ts | 1 + .../usync-assets/src/dialogs/index.ts | 2 + .../usync-assets/src/dialogs/manifest.ts | 9 +- .../dialogs/single-import-modal.element.ts | 101 +++ .../src/dialogs/single-import-modal.token.ts | 19 + .../usync-assets/src/lang/files/en-us.ts | 6 + .../src/repository/SyncAction.respositoy.ts | 5 + .../repository/sources/SyncAction.source.ts | 10 + .../src/workspace/workspace.context.ts | 7 + .../Serializers/DomainSerializer.cs | 12 +- 20 files changed, 1000 insertions(+), 11 deletions(-) create mode 100644 uSync.Backoffice.Management.Api/Controllers/Actions/SyncItemController.cs create mode 100644 uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts create mode 100644 uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.token.ts diff --git a/uSync.BackOffice/Services/ISyncService.cs b/uSync.BackOffice/Services/ISyncService.cs index 103c23bd9..a962c400e 100644 --- a/uSync.BackOffice/Services/ISyncService.cs +++ b/uSync.BackOffice/Services/ISyncService.cs @@ -106,6 +106,12 @@ public interface ISyncService /// Task ImportSingleActionAsync(uSyncAction action); + + /// + /// Import single item from disk given the key and handler alias + /// + Task ImportSingleItemAsync(Guid key, string handlerAlias); + /// /// get the ordered nodes for a process and handler /// diff --git a/uSync.BackOffice/Services/SyncService.cs b/uSync.BackOffice/Services/SyncService.cs index 9ea4ab32f..d02719ba9 100644 --- a/uSync.BackOffice/Services/SyncService.cs +++ b/uSync.BackOffice/Services/SyncService.cs @@ -239,6 +239,18 @@ public async Task ImportSingleActionAsync(uSyncAction action) return (await handlerConfig.Handler.ImportAsync(action.FileName, handlerConfig.Settings, true)).FirstOrDefault(); } + public async Task ImportSingleItemAsync(Guid key, string handlerAlias) + { + var handlerConfig = _handlerFactory.GetValidHandler(handlerAlias); + if (handlerConfig is null) return uSyncAction.Fail("Unknown", handlerAlias, "Unknown", ChangeType.Fail, $"Could not find handler with alias {handlerAlias}", new KeyNotFoundException(handlerAlias)); + + var node = await handlerConfig.Handler.TryFindItemNodeAsync(key); + if (node is null) return uSyncAction.Fail("Unknown", handlerAlias, handlerConfig.Handler.ItemType, ChangeType.Fail , $"Could not find item with key {key}", new FileNotFoundException(key.ToString())); + + var result = await handlerConfig.Handler.ImportElementAsync(node, $"Single Item {key}", handlerConfig.Settings, new uSyncImportOptions { Flags = SerializerFlags.Force }); + return result.FirstOrDefault(); + } + #endregion #region Exporting diff --git a/uSync.BackOffice/SyncHandlers/Interfaces/ISyncHandler.cs b/uSync.BackOffice/SyncHandlers/Interfaces/ISyncHandler.cs index 24126cbdc..9bae83a8b 100644 --- a/uSync.BackOffice/SyncHandlers/Interfaces/ISyncHandler.cs +++ b/uSync.BackOffice/SyncHandlers/Interfaces/ISyncHandler.cs @@ -153,4 +153,11 @@ public interface ISyncHandler /// /// Task> FetchAllNodesAsync(string[] folders); + + /// + /// find an element by its key + /// + /// + /// + Task TryFindItemNodeAsync(Guid key) => Task.FromResult(null); } diff --git a/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs b/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs index f65bdd9fb..b590b1aa2 100644 --- a/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs +++ b/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs @@ -1996,4 +1996,19 @@ private async Task RootItemExistsAsync(TObject item) } #endregion + + /// + public async Task TryFindItemNodeAsync(Guid key) + { + var folders = GetDefaultHandlerFolders(); + var items = await GetMergedItemsAsync(folders); + + foreach (var item in items) + { + if (item.Node.GetKey() == key) + return item.Node; + } + + return null; + } } \ No newline at end of file diff --git a/uSync.Backoffice.Management.Api/Controllers/Actions/SyncItemController.cs b/uSync.Backoffice.Management.Api/Controllers/Actions/SyncItemController.cs new file mode 100644 index 000000000..461b05c89 --- /dev/null +++ b/uSync.Backoffice.Management.Api/Controllers/Actions/SyncItemController.cs @@ -0,0 +1,29 @@ +using Asp.Versioning; + +using Microsoft.AspNetCore.Mvc; + +using uSync.Backoffice.Management.Api.Models; +using uSync.BackOffice; + +namespace uSync.Backoffice.Management.Api.Controllers.Actions; + +[ApiVersion("1.0")] +[ApiExplorerSettings(GroupName = "Actions")] +public class SyncItemController : uSyncControllerBase +{ + private readonly ISyncService _syncService; + + public SyncItemController(ISyncService syncService) + { + _syncService = syncService; + } + + [HttpPost("Import")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(uSyncAction), 200)] + public async Task ImportSingle([FromBody] uSyncActionView action) + { + var result = await _syncService.ImportSingleItemAsync(action.Key, action.Handler); + return Ok(result); + } +} diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/api/sdk.gen.ts b/uSync.Backoffice.Management.Client/usync-assets/src/api/sdk.gen.ts index 4b5e95080..0ffbb3639 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/api/sdk.gen.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/api/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { GetActionsData, GetActionsResponse, GetActionsBySetData, GetActionsBySetResponse, DownloadData, DownloadResponse, PerformActionData, PerformActionResponse2, ProcessUploadData, ProcessUploadResponse, CheckLegacyData, CheckLegacyResponse, CopyLegacyData, CopyLegacyResponse, IgnoreLegacyData, IgnoreLegacyResponse, GetAddOnsData, GetAddOnsResponse, GetAddonSplashData, GetAddonSplashResponse, GetHandlerSetSettingsData, GetHandlerSetSettingsResponse, GetSetsData, GetSetsResponse, GetSettingsData, GetSettingsResponse } from './types.gen'; +import type { GetActionsData, GetActionsResponse, GetActionsBySetData, GetActionsBySetResponse, DownloadData, DownloadResponse, ImportSingleData, ImportSingleResponse, PerformActionData, PerformActionResponse2, ProcessUploadData, ProcessUploadResponse, CheckLegacyData, CheckLegacyResponse, CopyLegacyData, CopyLegacyResponse, IgnoreLegacyData, IgnoreLegacyResponse, GetAddOnsData, GetAddOnsResponse, GetAddonSplashData, GetAddonSplashResponse, GetHandlerSetSettingsData, GetHandlerSetSettingsResponse, GetSetsData, GetSetsResponse, GetSettingsData, GetSettingsResponse } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -43,6 +43,17 @@ export class ActionsService { }); } + public static importSingle(options?: Options) { + return (options?.client ?? _heyApiClient).post({ + url: '/umbraco/usync/api/v1/Import', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); + } + public static performAction(options?: Options) { return (options?.client ?? _heyApiClient).post({ url: '/umbraco/usync/api/v1/Perform', diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/api/types.gen.ts b/uSync.Backoffice.Management.Client/usync-assets/src/api/types.gen.ts index 830a4365e..4f15fc5ef 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/api/types.gen.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/api/types.gen.ts @@ -1,5 +1,43 @@ // This file is auto-generated by @hey-api/openapi-ts +export type Assembly = { + readonly definedTypes: Array; + readonly exportedTypes: Array; + /** + * @deprecated + */ + readonly codeBase?: string | null; + entryPoint?: MethodInfo | null; + readonly fullName?: string | null; + readonly imageRuntimeVersion: string; + readonly isDynamic: boolean; + readonly location: string; + readonly reflectionOnly: boolean; + readonly isCollectible: boolean; + readonly isFullyTrusted: boolean; + readonly customAttributes: Array; + /** + * @deprecated + */ + readonly escapedCodeBase: string; + manifestModule: Module; + readonly modules: Array; + /** + * @deprecated + */ + readonly globalAssemblyCache: boolean; + readonly hostContext: number; + securityRuleSet: SecurityRuleSet; +}; + +export enum CallingConventions { + STANDARD = 'Standard', + VAR_ARGS = 'VarArgs', + ANY = 'Any', + HAS_THIS = 'HasThis', + EXPLICIT_THIS = 'ExplicitThis' +} + export enum ChangeDetailType { NO_CHANGE = 'NoChange', CREATE = 'Create', @@ -28,6 +66,85 @@ export enum ChangeType { REMOVED = 'Removed' } +export type ConstructorInfo = { + readonly name: string; + declaringType?: Type | null; + reflectedType?: Type | null; + module: Module; + readonly customAttributes: Array; + readonly isCollectible: boolean; + readonly metadataToken: number; + attributes: MethodAttributes; + methodImplementationFlags: MethodImplAttributes; + callingConvention: CallingConventions; + readonly isAbstract: boolean; + readonly isConstructor: boolean; + readonly isFinal: boolean; + readonly isHideBySig: boolean; + readonly isSpecialName: boolean; + readonly isStatic: boolean; + readonly isVirtual: boolean; + readonly isAssembly: boolean; + readonly isFamily: boolean; + readonly isFamilyAndAssembly: boolean; + readonly isFamilyOrAssembly: boolean; + readonly isPrivate: boolean; + readonly isPublic: boolean; + readonly isConstructedGenericMethod: boolean; + readonly isGenericMethod: boolean; + readonly isGenericMethodDefinition: boolean; + readonly containsGenericParameters: boolean; + methodHandle: RuntimeMethodHandle; + readonly isSecurityCritical: boolean; + readonly isSecuritySafeCritical: boolean; + readonly isSecurityTransparent: boolean; + memberType: MemberTypes; +}; + +export type CustomAttributeData = { + attributeType: Type; + constructor: ConstructorInfo; + readonly constructorArguments: Array; + readonly namedArguments: Array; +}; + +export type CustomAttributeNamedArgument = { + memberInfo: MemberInfo; + typedValue: CustomAttributeTypedArgument; + readonly memberName: string; + readonly isField: boolean; +}; + +export type CustomAttributeTypedArgument = { + argumentType: Type; + value?: unknown; +}; + +export enum EventAttributes { + NONE = 'None', + SPECIAL_NAME = 'SpecialName', + RT_SPECIAL_NAME = 'RTSpecialName', + RESERVED_MASK = 'ReservedMask' +} + +export type EventInfo = { + readonly name: string; + declaringType?: Type | null; + reflectedType?: Type | null; + module: Module; + readonly customAttributes: Array; + readonly isCollectible: boolean; + readonly metadataToken: number; + memberType: MemberTypes; + attributes: EventAttributes; + readonly isSpecialName: boolean; + addMethod?: MethodInfo | null; + removeMethod?: MethodInfo | null; + raiseMethod?: MethodInfo | null; + readonly isMulticast: boolean; + eventHandlerType?: Type | null; +}; + export enum EventMessageTypeModel { DEFAULT = 'Default', INFO = 'Info', @@ -36,6 +153,85 @@ export enum EventMessageTypeModel { WARNING = 'Warning' } +export type Exception = { + targetSite?: MethodBase | null; + readonly message: string; + readonly data: { + [key: string]: unknown; + }; + innerException?: Exception | null; + helpLink?: string | null; + source?: string | null; + hResult: number; + readonly stackTrace?: string | null; +}; + +export enum FieldAttributes { + PRIVATE_SCOPE = 'PrivateScope', + PRIVATE = 'Private', + FAM_AND_ASSEM = 'FamANDAssem', + ASSEMBLY = 'Assembly', + FAMILY = 'Family', + FAM_OR_ASSEM = 'FamORAssem', + PUBLIC = 'Public', + FIELD_ACCESS_MASK = 'FieldAccessMask', + STATIC = 'Static', + INIT_ONLY = 'InitOnly', + LITERAL = 'Literal', + NOT_SERIALIZED = 'NotSerialized', + HAS_FIELD_RVA = 'HasFieldRVA', + SPECIAL_NAME = 'SpecialName', + RT_SPECIAL_NAME = 'RTSpecialName', + HAS_FIELD_MARSHAL = 'HasFieldMarshal', + PINVOKE_IMPL = 'PinvokeImpl', + HAS_DEFAULT = 'HasDefault', + RESERVED_MASK = 'ReservedMask' +} + +export type FieldInfo = { + readonly name: string; + declaringType?: Type | null; + reflectedType?: Type | null; + module: Module; + readonly customAttributes: Array; + readonly isCollectible: boolean; + readonly metadataToken: number; + memberType: MemberTypes; + attributes: FieldAttributes; + fieldType: Type; + readonly isInitOnly: boolean; + readonly isLiteral: boolean; + /** + * @deprecated + */ + readonly isNotSerialized: boolean; + readonly isPinvokeImpl: boolean; + readonly isSpecialName: boolean; + readonly isStatic: boolean; + readonly isAssembly: boolean; + readonly isFamily: boolean; + readonly isFamilyAndAssembly: boolean; + readonly isFamilyOrAssembly: boolean; + readonly isPrivate: boolean; + readonly isPublic: boolean; + readonly isSecurityCritical: boolean; + readonly isSecuritySafeCritical: boolean; + readonly isSecurityTransparent: boolean; + fieldHandle: RuntimeFieldHandle; +}; + +export enum GenericParameterAttributes { + NONE = 'None', + COVARIANT = 'Covariant', + CONTRAVARIANT = 'Contravariant', + VARIANCE_MASK = 'VarianceMask', + REFERENCE_TYPE_CONSTRAINT = 'ReferenceTypeConstraint', + NOT_NULLABLE_VALUE_TYPE_CONSTRAINT = 'NotNullableValueTypeConstraint', + DEFAULT_CONSTRUCTOR_CONSTRAINT = 'DefaultConstructorConstraint', + SPECIAL_CONSTRAINT_MASK = 'SpecialConstraintMask', + ALLOW_BY_REF_LIKE = 'AllowByRefLike' +} + export type HandlerSettings = { enabled: boolean; actions: Array; @@ -57,12 +253,217 @@ export enum HandlerStatus { ERROR = 'Error' } +export type ICustomAttributeProvider = { + [key: string]: never; +}; + +export type IntPtr = { + [key: string]: never; +}; + +export enum LayoutKind { + SEQUENTIAL = 'Sequential', + EXPLICIT = 'Explicit', + AUTO = 'Auto' +} + +export type MemberInfo = { + memberType: MemberTypes; + readonly name: string; + declaringType?: Type | null; + reflectedType?: Type | null; + module: Module; + readonly customAttributes: Array; + readonly isCollectible: boolean; + readonly metadataToken: number; +}; + +export enum MemberTypes { + CONSTRUCTOR = 'Constructor', + EVENT = 'Event', + FIELD = 'Field', + METHOD = 'Method', + PROPERTY = 'Property', + TYPE_INFO = 'TypeInfo', + CUSTOM = 'Custom', + NESTED_TYPE = 'NestedType', + ALL = 'All' +} + +export enum MethodAttributes { + PRIVATE_SCOPE = 'PrivateScope', + REUSE_SLOT = 'ReuseSlot', + PRIVATE = 'Private', + FAM_AND_ASSEM = 'FamANDAssem', + ASSEMBLY = 'Assembly', + FAMILY = 'Family', + FAM_OR_ASSEM = 'FamORAssem', + PUBLIC = 'Public', + MEMBER_ACCESS_MASK = 'MemberAccessMask', + UNMANAGED_EXPORT = 'UnmanagedExport', + STATIC = 'Static', + FINAL = 'Final', + VIRTUAL = 'Virtual', + HIDE_BY_SIG = 'HideBySig', + NEW_SLOT = 'NewSlot', + VTABLE_LAYOUT_MASK = 'VtableLayoutMask', + CHECK_ACCESS_ON_OVERRIDE = 'CheckAccessOnOverride', + ABSTRACT = 'Abstract', + SPECIAL_NAME = 'SpecialName', + RT_SPECIAL_NAME = 'RTSpecialName', + PINVOKE_IMPL = 'PinvokeImpl', + HAS_SECURITY = 'HasSecurity', + REQUIRE_SEC_OBJECT = 'RequireSecObject', + RESERVED_MASK = 'ReservedMask' +} + +export type MethodBase = { + memberType: MemberTypes; + readonly name: string; + declaringType?: Type | null; + reflectedType?: Type | null; + module: Module; + readonly customAttributes: Array; + readonly isCollectible: boolean; + readonly metadataToken: number; + attributes: MethodAttributes; + methodImplementationFlags: MethodImplAttributes; + callingConvention: CallingConventions; + readonly isAbstract: boolean; + readonly isConstructor: boolean; + readonly isFinal: boolean; + readonly isHideBySig: boolean; + readonly isSpecialName: boolean; + readonly isStatic: boolean; + readonly isVirtual: boolean; + readonly isAssembly: boolean; + readonly isFamily: boolean; + readonly isFamilyAndAssembly: boolean; + readonly isFamilyOrAssembly: boolean; + readonly isPrivate: boolean; + readonly isPublic: boolean; + readonly isConstructedGenericMethod: boolean; + readonly isGenericMethod: boolean; + readonly isGenericMethodDefinition: boolean; + readonly containsGenericParameters: boolean; + methodHandle: RuntimeMethodHandle; + readonly isSecurityCritical: boolean; + readonly isSecuritySafeCritical: boolean; + readonly isSecurityTransparent: boolean; +}; + +export enum MethodImplAttributes { + IL = 'IL', + MANAGED = 'Managed', + NATIVE = 'Native', + OPTIL = 'OPTIL', + CODE_TYPE_MASK = 'CodeTypeMask', + RUNTIME = 'Runtime', + MANAGED_MASK = 'ManagedMask', + UNMANAGED = 'Unmanaged', + NO_INLINING = 'NoInlining', + FORWARD_REF = 'ForwardRef', + SYNCHRONIZED = 'Synchronized', + NO_OPTIMIZATION = 'NoOptimization', + PRESERVE_SIG = 'PreserveSig', + AGGRESSIVE_INLINING = 'AggressiveInlining', + AGGRESSIVE_OPTIMIZATION = 'AggressiveOptimization', + INTERNAL_CALL = 'InternalCall', + MAX_METHOD_IMPL_VAL = 'MaxMethodImplVal' +} + +export type MethodInfo = { + readonly name: string; + declaringType?: Type | null; + reflectedType?: Type | null; + module: Module; + readonly customAttributes: Array; + readonly isCollectible: boolean; + readonly metadataToken: number; + attributes: MethodAttributes; + methodImplementationFlags: MethodImplAttributes; + callingConvention: CallingConventions; + readonly isAbstract: boolean; + readonly isConstructor: boolean; + readonly isFinal: boolean; + readonly isHideBySig: boolean; + readonly isSpecialName: boolean; + readonly isStatic: boolean; + readonly isVirtual: boolean; + readonly isAssembly: boolean; + readonly isFamily: boolean; + readonly isFamilyAndAssembly: boolean; + readonly isFamilyOrAssembly: boolean; + readonly isPrivate: boolean; + readonly isPublic: boolean; + readonly isConstructedGenericMethod: boolean; + readonly isGenericMethod: boolean; + readonly isGenericMethodDefinition: boolean; + readonly containsGenericParameters: boolean; + methodHandle: RuntimeMethodHandle; + readonly isSecurityCritical: boolean; + readonly isSecuritySafeCritical: boolean; + readonly isSecurityTransparent: boolean; + memberType: MemberTypes; + returnParameter: ParameterInfo; + returnType: Type; + returnTypeCustomAttributes: ICustomAttributeProvider; +}; + +export type Module = { + assembly: Assembly; + readonly fullyQualifiedName: string; + readonly name: string; + readonly mdStreamVersion: number; + readonly moduleVersionId: string; + readonly scopeName: string; + moduleHandle: ModuleHandle; + readonly customAttributes: Array; + readonly metadataToken: number; +}; + +export type ModuleHandle = { + readonly mdStreamVersion: number; +}; + export type NotificationHeaderModel = { message: string; category: string; type: EventMessageTypeModel; }; +export enum ParameterAttributes { + NONE = 'None', + IN = 'In', + OUT = 'Out', + LCID = 'Lcid', + RETVAL = 'Retval', + OPTIONAL = 'Optional', + HAS_DEFAULT = 'HasDefault', + HAS_FIELD_MARSHAL = 'HasFieldMarshal', + RESERVED3 = 'Reserved3', + RESERVED4 = 'Reserved4', + RESERVED_MASK = 'ReservedMask' +} + +export type ParameterInfo = { + attributes: ParameterAttributes; + member: MemberInfo; + readonly name?: string | null; + parameterType: Type; + readonly position: number; + readonly isIn: boolean; + readonly isLcid: boolean; + readonly isOptional: boolean; + readonly isOut: boolean; + readonly isRetval: boolean; + readonly defaultValue?: unknown; + readonly rawDefaultValue?: unknown; + readonly hasDefaultValue: boolean; + readonly customAttributes: Array; + readonly metadataToken: number; +}; + export type PerformActionRequest = { requestId: string; action: string; @@ -78,6 +479,58 @@ export type PerformActionResponse = { complete: boolean; }; +export enum PropertyAttributes { + NONE = 'None', + SPECIAL_NAME = 'SpecialName', + RT_SPECIAL_NAME = 'RTSpecialName', + HAS_DEFAULT = 'HasDefault', + RESERVED2 = 'Reserved2', + RESERVED3 = 'Reserved3', + RESERVED4 = 'Reserved4', + RESERVED_MASK = 'ReservedMask' +} + +export type PropertyInfo = { + readonly name: string; + declaringType?: Type | null; + reflectedType?: Type | null; + module: Module; + readonly customAttributes: Array; + readonly isCollectible: boolean; + readonly metadataToken: number; + memberType: MemberTypes; + propertyType: Type; + attributes: PropertyAttributes; + readonly isSpecialName: boolean; + readonly canRead: boolean; + readonly canWrite: boolean; + getMethod?: MethodInfo | null; + setMethod?: MethodInfo | null; +}; + +export type RuntimeFieldHandle = { + value: IntPtr; +}; + +export type RuntimeMethodHandle = { + value: IntPtr; +}; + +export type RuntimeTypeHandle = { + value: IntPtr; +}; + +export enum SecurityRuleSet { + NONE = 'None', + LEVEL1 = 'Level1', + LEVEL2 = 'Level2' +} + +export type StructLayoutAttribute = { + readonly typeId: unknown; + value: LayoutKind; +}; + export type SyncActionButton = { key: string; label: string; @@ -117,11 +570,229 @@ export type SyncSelectableSet = { settings: USyncHandlerSetSettings; }; +export type Type = { + readonly name: string; + readonly customAttributes: Array; + readonly isCollectible: boolean; + readonly metadataToken: number; + memberType: MemberTypes; + readonly namespace?: string | null; + readonly assemblyQualifiedName?: string | null; + readonly fullName?: string | null; + assembly: Assembly; + module: Module; + readonly isInterface: boolean; + readonly isNested: boolean; + declaringType?: Type | null; + declaringMethod?: MethodBase | null; + reflectedType?: Type | null; + underlyingSystemType: Type; + readonly isTypeDefinition: boolean; + readonly isArray: boolean; + readonly isByRef: boolean; + readonly isPointer: boolean; + readonly isConstructedGenericType: boolean; + readonly isGenericParameter: boolean; + readonly isGenericTypeParameter: boolean; + readonly isGenericMethodParameter: boolean; + readonly isGenericType: boolean; + readonly isGenericTypeDefinition: boolean; + readonly isSZArray: boolean; + readonly isVariableBoundArray: boolean; + readonly isByRefLike: boolean; + readonly isFunctionPointer: boolean; + readonly isUnmanagedFunctionPointer: boolean; + readonly hasElementType: boolean; + readonly genericTypeArguments: Array; + readonly genericParameterPosition: number; + genericParameterAttributes: GenericParameterAttributes; + attributes: TypeAttributes; + readonly isAbstract: boolean; + readonly isImport: boolean; + readonly isSealed: boolean; + readonly isSpecialName: boolean; + readonly isClass: boolean; + readonly isNestedAssembly: boolean; + readonly isNestedFamANDAssem: boolean; + readonly isNestedFamily: boolean; + readonly isNestedFamORAssem: boolean; + readonly isNestedPrivate: boolean; + readonly isNestedPublic: boolean; + readonly isNotPublic: boolean; + readonly isPublic: boolean; + readonly isAutoLayout: boolean; + readonly isExplicitLayout: boolean; + readonly isLayoutSequential: boolean; + readonly isAnsiClass: boolean; + readonly isAutoClass: boolean; + readonly isUnicodeClass: boolean; + readonly isCOMObject: boolean; + readonly isContextful: boolean; + readonly isEnum: boolean; + readonly isMarshalByRef: boolean; + readonly isPrimitive: boolean; + readonly isValueType: boolean; + readonly isSignatureType: boolean; + readonly isSecurityCritical: boolean; + readonly isSecuritySafeCritical: boolean; + readonly isSecurityTransparent: boolean; + structLayoutAttribute?: StructLayoutAttribute | null; + typeInitializer?: ConstructorInfo | null; + typeHandle: RuntimeTypeHandle; + readonly guid: string; + baseType?: Type | null; + /** + * @deprecated + */ + readonly isSerializable: boolean; + readonly containsGenericParameters: boolean; + readonly isVisible: boolean; +}; + +export enum TypeAttributes { + NOT_PUBLIC = 'NotPublic', + AUTO_LAYOUT = 'AutoLayout', + ANSI_CLASS = 'AnsiClass', + CLASS = 'Class', + PUBLIC = 'Public', + NESTED_PUBLIC = 'NestedPublic', + NESTED_PRIVATE = 'NestedPrivate', + NESTED_FAMILY = 'NestedFamily', + NESTED_ASSEMBLY = 'NestedAssembly', + NESTED_FAM_AND_ASSEM = 'NestedFamANDAssem', + VISIBILITY_MASK = 'VisibilityMask', + NESTED_FAM_OR_ASSEM = 'NestedFamORAssem', + SEQUENTIAL_LAYOUT = 'SequentialLayout', + EXPLICIT_LAYOUT = 'ExplicitLayout', + LAYOUT_MASK = 'LayoutMask', + INTERFACE = 'Interface', + CLASS_SEMANTICS_MASK = 'ClassSemanticsMask', + ABSTRACT = 'Abstract', + SEALED = 'Sealed', + SPECIAL_NAME = 'SpecialName', + RT_SPECIAL_NAME = 'RTSpecialName', + IMPORT = 'Import', + SERIALIZABLE = 'Serializable', + WINDOWS_RUNTIME = 'WindowsRuntime', + UNICODE_CLASS = 'UnicodeClass', + AUTO_CLASS = 'AutoClass', + STRING_FORMAT_MASK = 'StringFormatMask', + CUSTOM_FORMAT_CLASS = 'CustomFormatClass', + HAS_SECURITY = 'HasSecurity', + RESERVED_MASK = 'ReservedMask', + BEFORE_FIELD_INIT = 'BeforeFieldInit', + CUSTOM_FORMAT_MASK = 'CustomFormatMask' +} + +export type TypeInfo = { + readonly name: string; + readonly customAttributes: Array; + readonly isCollectible: boolean; + readonly metadataToken: number; + memberType: MemberTypes; + readonly namespace?: string | null; + readonly assemblyQualifiedName?: string | null; + readonly fullName?: string | null; + assembly: Assembly; + module: Module; + readonly isInterface: boolean; + readonly isNested: boolean; + declaringType?: Type | null; + declaringMethod?: MethodBase | null; + reflectedType?: Type | null; + underlyingSystemType: Type; + readonly isTypeDefinition: boolean; + readonly isArray: boolean; + readonly isByRef: boolean; + readonly isPointer: boolean; + readonly isConstructedGenericType: boolean; + readonly isGenericParameter: boolean; + readonly isGenericTypeParameter: boolean; + readonly isGenericMethodParameter: boolean; + readonly isGenericType: boolean; + readonly isGenericTypeDefinition: boolean; + readonly isSZArray: boolean; + readonly isVariableBoundArray: boolean; + readonly isByRefLike: boolean; + readonly isFunctionPointer: boolean; + readonly isUnmanagedFunctionPointer: boolean; + readonly hasElementType: boolean; + readonly genericTypeArguments: Array; + readonly genericParameterPosition: number; + genericParameterAttributes: GenericParameterAttributes; + attributes: TypeAttributes; + readonly isAbstract: boolean; + readonly isImport: boolean; + readonly isSealed: boolean; + readonly isSpecialName: boolean; + readonly isClass: boolean; + readonly isNestedAssembly: boolean; + readonly isNestedFamANDAssem: boolean; + readonly isNestedFamily: boolean; + readonly isNestedFamORAssem: boolean; + readonly isNestedPrivate: boolean; + readonly isNestedPublic: boolean; + readonly isNotPublic: boolean; + readonly isPublic: boolean; + readonly isAutoLayout: boolean; + readonly isExplicitLayout: boolean; + readonly isLayoutSequential: boolean; + readonly isAnsiClass: boolean; + readonly isAutoClass: boolean; + readonly isUnicodeClass: boolean; + readonly isCOMObject: boolean; + readonly isContextful: boolean; + readonly isEnum: boolean; + readonly isMarshalByRef: boolean; + readonly isPrimitive: boolean; + readonly isValueType: boolean; + readonly isSignatureType: boolean; + readonly isSecurityCritical: boolean; + readonly isSecuritySafeCritical: boolean; + readonly isSecurityTransparent: boolean; + structLayoutAttribute?: StructLayoutAttribute | null; + typeInitializer?: ConstructorInfo | null; + typeHandle: RuntimeTypeHandle; + readonly guid: string; + baseType?: Type | null; + /** + * @deprecated + */ + readonly isSerializable: boolean; + readonly containsGenericParameters: boolean; + readonly isVisible: boolean; + readonly genericTypeParameters: Array; + readonly declaredConstructors: Array; + readonly declaredEvents: Array; + readonly declaredFields: Array; + readonly declaredMembers: Array; + readonly declaredMethods: Array; + readonly declaredNestedTypes: Array; + readonly declaredProperties: Array; + readonly implementedInterfaces: Array; +}; + export type UploadImportResult = { success: boolean; errors: Array; }; +export type USyncAction = { + handlerAlias?: string | null; + success: boolean; + itemType: string; + message?: string | null; + exception?: Exception | null; + change: ChangeType; + fileName?: string | null; + name: string; + path?: string | null; + requiresPostProcessing: boolean; + detailMessage?: string | null; + details?: Array | null; + key: string; +}; + export type USyncActionView = { key: string; name: string; @@ -179,6 +850,7 @@ export type USyncSettings = { stopFile: string; onceFile: string; lockRootTypes: Array; + backgroundStartup: boolean; defaultSet: string; importAtStartup: string; exportAtStartup: string; @@ -204,6 +876,7 @@ export type USyncSettings = { hideAddOns: string; disableNotificationSuppression: boolean; backgroundNotifications: boolean; + moveToSection: boolean; }; export type GetActionsData = { @@ -258,6 +931,22 @@ export type DownloadResponses = { export type DownloadResponse = DownloadResponses[keyof DownloadResponses]; +export type ImportSingleData = { + body?: USyncActionView; + path?: never; + query?: never; + url: '/umbraco/usync/api/v1/Import'; +}; + +export type ImportSingleResponses = { + /** + * OK + */ + 200: USyncAction; +}; + +export type ImportSingleResponse = ImportSingleResponses[keyof ImportSingleResponses]; + export type PerformActionData = { body?: PerformActionRequest; path?: never; diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts index a67247eb7..e45e5fa0c 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts @@ -6,7 +6,6 @@ import { property, nothing, state, - when, classMap, } from '@umbraco-cms/backoffice/external/lit'; import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-results-view.ts b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-results-view.ts index 47b0c9017..7f1107d67 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-results-view.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-results-view.ts @@ -47,6 +47,10 @@ export class uSyncResultsView extends UmbElementMixin(LitElement) { const detailsModal = this.#modalContext?.open(this, USYNC_DETAILS_MODAL, { data: { item: e.action, + showActions: + e.action.change == ChangeType.CREATE || + e.action.change == ChangeType.UPDATE || + e.action.change == ChangeType.IMPORT, }, }); diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-element.ts b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-element.ts index 325b42e96..dcdf2406c 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-element.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-element.ts @@ -1,12 +1,55 @@ -import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; -import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit'; -import { uSyncDetailsModalData, uSyncDetailsModalValue } from '@jumoo/uSync'; +import { + UMB_MODAL_MANAGER_CONTEXT, + UmbModalBaseElement, +} from '@umbraco-cms/backoffice/modal'; +import { + css, + customElement, + html, + nothing, + state, +} from '@umbraco-cms/backoffice/external/lit'; +import { + USYNC_IMPORT_SINGLE_MODAL, + uSyncDetailsModalData, + uSyncDetailsModalValue, +} from '@jumoo/uSync'; +import { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; @customElement('usync-details-modal') export class uSyncDetailsModalElement extends UmbModalBaseElement< uSyncDetailsModalData, uSyncDetailsModalValue > { + @state() + importState: UUIButtonState; + + constructor() { + super(); + // this.consumeContext(USYNC_CORE_CONTEXT_TOKEN, (_instance) => { + // if (!_instance) return; + // this.#actionContext = _instance; + // }); + } + + async #onImport(e: Event) { + e.stopPropagation(); + if (!this.data?.item) return; + + const modalContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + if (!modalContext) return; + + const modal = modalContext.open(this, USYNC_IMPORT_SINGLE_MODAL, { + data: { action: this.data?.item }, + }); + + const data = await modal?.onSubmit().catch(() => { + return; + }); + + return data; + } + #onClose() { this.modalContext?.reject(); } @@ -19,8 +62,9 @@ export class uSyncDetailsModalElement extends UmbModalBaseElement<

+
${this.renderActions()}
- +
@@ -33,6 +77,17 @@ export class uSyncDetailsModalElement extends UmbModalBaseElement< `; } + renderActions() { + if (!this.data?.showActions) return nothing; + return html` `; + } + static styles = css` #header h3 { margin: 0; diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-token.ts b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-token.ts index 8ab4f8f2f..716dfc299 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-token.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-token.ts @@ -3,6 +3,7 @@ import { USyncActionView } from '@jumoo/uSync'; export interface uSyncDetailsModalData { item: USyncActionView; + showActions?: boolean; } export interface uSyncDetailsModalValue { diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/index.ts b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/index.ts index 78d3c19a5..67e7a90ee 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/index.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/index.ts @@ -2,3 +2,5 @@ export * from './details-modal-element.js'; export * from './details-modal-token.js'; export * from './error-modal-element.js'; export * from './error-modal-token.js'; +export * from './single-import-modal.token.js'; +export * from './single-import-modal.element.js'; diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/manifest.ts b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/manifest.ts index b91304a7d..2ebad333a 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/manifest.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/manifest.ts @@ -19,4 +19,11 @@ const errorModal: UmbExtensionManifest = { js: () => import('./error-modal-element.js'), }; -export const manifests = [modal, legacyModal, errorModal]; +const singleImportModal: UmbExtensionManifest = { + type: 'modal', + alias: 'usync.import.single.modal', + name: 'uSync single import modal', + js: () => import('./single-import-modal.element.js'), +}; + +export const manifests = [modal, legacyModal, errorModal, singleImportModal]; diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts new file mode 100644 index 000000000..16d3fabce --- /dev/null +++ b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts @@ -0,0 +1,101 @@ +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { + SyncImportSingleModalData, + SyncImportSingleModalResult, +} from './single-import-modal.token'; +import { customElement, state } from 'lit/decorators.js'; +import { css, html, nothing } from 'lit'; +import { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; +import { USYNC_CORE_CONTEXT_TOKEN, uSyncWorkspaceContext } from '../workspace'; +import { USyncAction } from '../api'; +import { when } from '@umbraco-cms/backoffice/external/lit'; + +@customElement('usync-import-single-modal') +export default class SyncImportSingleModalElement extends UmbModalBaseElement< + SyncImportSingleModalData, + SyncImportSingleModalResult +> { + #actionContext?: uSyncWorkspaceContext; + + @state() + importState: UUIButtonState; + + @state() + result?: USyncAction; + + constructor() { + super(); + + this.consumeContext(USYNC_CORE_CONTEXT_TOKEN, (_instance) => { + if (!_instance) return; + this.#actionContext = _instance; + }); + } + + #onClose() { + this.modalContext?.reject(); + } + + async #doImport() { + if (!this.data?.action) return; + console.log('import single item', this.data); + this.importState = 'waiting'; + + const result = await this.#actionContext?.importSingle(this.data.action); + console.log('import result', result); + this.result = result?.data; + if (result?.data.success) this.importState = 'success'; + else this.importState = 'failed'; + } + + render() { + return html` + ${this.renderResult()} +
+ + ${when( + !this.result, + () => + html` `, + )} +
+
`; + } + + renderResult() { + if (!this.result) + return html` + + `; + + if (this.result.success) { + return html`
+ +
`; + } else { + return html`
+ +
`; + } + } + + static styles = css` + umb-body-layout { + min-width: 450px; + max-width: 450px; + } + `; +} diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.token.ts b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.token.ts new file mode 100644 index 000000000..1839d8b77 --- /dev/null +++ b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.token.ts @@ -0,0 +1,19 @@ +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +import { USyncActionView } from '../api'; + +export interface SyncImportSingleModalData { + action: USyncActionView; +} + +export interface SyncImportSingleModalResult { + result: boolean; +} + +export const USYNC_IMPORT_SINGLE_MODAL = new UmbModalToken< + SyncImportSingleModalData, + SyncImportSingleModalResult +>('usync.import.single.modal', { + modal: { + type: 'dialog', + }, +}); diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/lang/files/en-us.ts b/uSync.Backoffice.Management.Client/usync-assets/src/lang/files/en-us.ts index c058d1097..c59c0cd62 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/lang/files/en-us.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/lang/files/en-us.ts @@ -18,6 +18,12 @@ export default { detailHeadline: 'Detected Changes', detailHeader: 'Things that are different', + importSingle: 'Import', + importSingleWarning: + 'This will import this item into Umbraco. If this item has dependencies they will not be imported, and will have to be resolved manually.', + importSingleSuccess: 'The item has been imported successfully', + importSingleFailed: `

There was an error importing the item

%0%`, + changeAction: 'Action', changeItem: 'Item', changeDiffrence: 'Difference', diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/repository/SyncAction.respositoy.ts b/uSync.Backoffice.Management.Client/usync-assets/src/repository/SyncAction.respositoy.ts index 308e31eaa..0f4b17e5d 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/repository/SyncAction.respositoy.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/repository/SyncAction.respositoy.ts @@ -4,6 +4,7 @@ import { uSyncActionDataSource, USyncSettingsDataSource, uSyncMigrationDataSource, + USyncActionView, } from '@jumoo/uSync'; /** @@ -137,4 +138,8 @@ export class uSyncActionRepository extends UmbControllerBase { async getSets() { return await this.#settingsDataSource.getSets(); } + + async importSingle(action: USyncActionView) { + return await this.#actionDataSource.importSingle(action); + } } diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/repository/sources/SyncAction.source.ts b/uSync.Backoffice.Management.Client/usync-assets/src/repository/sources/SyncAction.source.ts index abbb7889c..0089a81a9 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/repository/sources/SyncAction.source.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/repository/sources/SyncAction.source.ts @@ -6,6 +6,7 @@ import { PerformActionRequest, PerformActionResponse, SyncActionGroup, + USyncActionView, } from '@jumoo/uSync'; export interface SyncActionDataSource { @@ -65,4 +66,13 @@ export class uSyncActionDataSource implements SyncActionDataSource { }), ); } + + async importSingle(view: USyncActionView) { + return await tryExecute( + this.#host, + ActionsService.importSingle({ + body: view, + }), + ); + } } diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts b/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts index a02278e33..c50d76ced 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts @@ -270,6 +270,13 @@ export class uSyncWorkspaceContext download.remove(); window.URL.revokeObjectURL(url); } + + async importSingle(item: USyncActionView) { + if (!item) return; + console.log('import single item', item); + const data = await this.#repository.importSingle(item); + return data; + } } export default uSyncWorkspaceContext; diff --git a/uSync.Core/Serialization/Serializers/DomainSerializer.cs b/uSync.Core/Serialization/Serializers/DomainSerializer.cs index ecb29eca1..2a9d54a2f 100644 --- a/uSync.Core/Serialization/Serializers/DomainSerializer.cs +++ b/uSync.Core/Serialization/Serializers/DomainSerializer.cs @@ -52,15 +52,16 @@ protected override async Task> DeserializeCoreAsync(XElemen if (!string.IsNullOrWhiteSpace(isoCode)) { var language = await _languageService.GetAsync(isoCode); - if (language != null && item.LanguageId != language.Id) + if (language is null) + return SyncAttempt.Fail(node.GetAlias(), ChangeType.Fail, $"No Matching language {isoCode} exists on this site for the domain"); + + if (item.LanguageId != language.Id) { changes.AddUpdate("Id", item.LanguageId, language.Id); item.LanguageId = language.Id; } } - - var rootItem = default(IContent); var rootKey = info.Element("Root")?.Attribute(uSyncConstants.Xml.Key).ValueOrDefault(Guid.Empty) ?? Guid.Empty; @@ -78,7 +79,10 @@ protected override async Task> DeserializeCoreAsync(XElemen } } - if (rootItem != default(IContent) && item.RootContentId != rootItem.Id) + if (rootItem is null) + return SyncAttempt.Fail(node.GetAlias(), ChangeType.Fail, "No content item could be found to attach domain"); + + if (item.RootContentId != rootItem.Id) { changes.AddUpdate("RootItem", item.RootContentId, rootItem.Id); item.RootContentId = rootItem.Id; From 193df8de30bb403136fe636e5db20a5fcc4a7dde Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 2 Oct 2025 15:48:15 +0100 Subject: [PATCH 03/11] Update uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../usync-assets/src/dialogs/single-import-modal.element.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts index 16d3fabce..ca8c2a434 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts @@ -38,11 +38,9 @@ export default class SyncImportSingleModalElement extends UmbModalBaseElement< async #doImport() { if (!this.data?.action) return; - console.log('import single item', this.data); this.importState = 'waiting'; const result = await this.#actionContext?.importSingle(this.data.action); - console.log('import result', result); this.result = result?.data; if (result?.data.success) this.importState = 'success'; else this.importState = 'failed'; From 9576b69899f55a241965966b5df0fc6c6c7f9701 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 2 Oct 2025 15:48:39 +0100 Subject: [PATCH 04/11] Update uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../usync-assets/src/workspace/workspace.context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts b/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts index c50d76ced..0c442142e 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts @@ -273,7 +273,7 @@ export class uSyncWorkspaceContext async importSingle(item: USyncActionView) { if (!item) return; - console.log('import single item', item); + const data = await this.#repository.importSingle(item); return data; } From 2f3d0d3d12b5ad241177cb35265726029cb49577 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 2 Oct 2025 17:27:46 +0100 Subject: [PATCH 05/11] improve the 'blank' message on the details page to also show any messages that might be set. --- .../src/components/usync-change-view.ts | 39 +++++++++++++------ .../src/components/usync-progress-box.ts | 4 +- .../src/dialogs/details-modal-element.ts | 28 ++++++++----- .../usync-assets/src/lang/files/en-us.ts | 4 +- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts index e80b79ed2..2da83b18b 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts @@ -1,9 +1,11 @@ import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; import { LitElement, + classMap, css, customElement, html, + nothing, property, when, } from '@umbraco-cms/backoffice/external/lit'; @@ -57,27 +59,33 @@ export class uSyncChangeView extends UmbElementMixin(LitElement) { >No changes - ${when( - this.item?.change == ChangeType.IMPORT, - () => - html`
- ${(this.item?.message ?? '').length > 0 - ? this.item?.message - : 'Item was imported but no properties were changed '} -
`, - )} + ${this.renderMessage()}
`; } render_create() { return html` -

- This item is being created -

+
+

+ This item is being created +

+
`; } + renderMessage() { + const message = + (this.item?.message?.length ?? 0 > 0) + ? this.item?.message + : this.item?.change == ChangeType.IMPORT + ? 'No changes where made but the item was imported' + : '...'; + + const classes = { error: this.item?.success == false }; + return html`
${message}
`; + } + #getJsonOrString(value: string | null | undefined) { try { return JSON.stringify(JSON.parse(value ?? ''), null, 1); @@ -125,15 +133,22 @@ export class uSyncChangeView extends UmbElementMixin(LitElement) { } .change-box { + display: block; padding: var( --uui-box-header-padding, var(--uui-size-space-4, 12px) var(--uui-size-space-5, 18px) ); } + .change-box h3 { margin: 0; } + .error { + color: var(--uui-color-danger); + margin-top: var(--uui-size-space-2); + } + uui-table-cell { vertical-align: top; } diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts index e45e5fa0c..84d7edec0 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-progress-box.ts @@ -115,7 +115,7 @@ export class uSyncProcessBox extends UmbElementMixin(LitElement) { if (action.status == HandlerStatus.PENDING) return; if (action.status == HandlerStatus.PROCESSING) { return html` - `; } @@ -169,7 +169,7 @@ export class uSyncProcessBox extends UmbElementMixin(LitElement) { } .rotating { - animation: spin-animation 1s infinite; + animation: spin-animation 2s infinite; animation-timing-function: linear; display: inline-block; } diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-element.ts b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-element.ts index dcdf2406c..5e7a8ba1c 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-element.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/details-modal-element.ts @@ -57,16 +57,18 @@ export class uSyncDetailsModalElement extends UmbModalBaseElement< render() { return html` - - -
${this.renderActions()}
-
- - - +
+ + +
${this.renderActions()}
+
+ + + +
Date: Thu, 2 Oct 2025 17:29:48 +0100 Subject: [PATCH 06/11] Update uSync.Core/Serialization/Serializers/DomainSerializer.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- uSync.Core/Serialization/Serializers/DomainSerializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Core/Serialization/Serializers/DomainSerializer.cs b/uSync.Core/Serialization/Serializers/DomainSerializer.cs index 2a9d54a2f..d6278d972 100644 --- a/uSync.Core/Serialization/Serializers/DomainSerializer.cs +++ b/uSync.Core/Serialization/Serializers/DomainSerializer.cs @@ -53,7 +53,7 @@ protected override async Task> DeserializeCoreAsync(XElemen { var language = await _languageService.GetAsync(isoCode); if (language is null) - return SyncAttempt.Fail(node.GetAlias(), ChangeType.Fail, $"No Matching language {isoCode} exists on this site for the domain"); + return SyncAttempt.Fail(node.GetAlias(), ChangeType.Fail, $"No matching language '{isoCode}' exists on this site for the domain"); if (item.LanguageId != language.Id) { From 5ad0e331f0f9ec3c814abb3f8e7ea9c1dd4d4a10 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 2 Oct 2025 17:30:08 +0100 Subject: [PATCH 07/11] Update uSync.Core/Serialization/Serializers/DomainSerializer.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- uSync.Core/Serialization/Serializers/DomainSerializer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/uSync.Core/Serialization/Serializers/DomainSerializer.cs b/uSync.Core/Serialization/Serializers/DomainSerializer.cs index d6278d972..856ab2f95 100644 --- a/uSync.Core/Serialization/Serializers/DomainSerializer.cs +++ b/uSync.Core/Serialization/Serializers/DomainSerializer.cs @@ -80,7 +80,10 @@ protected override async Task> DeserializeCoreAsync(XElemen } if (rootItem is null) - return SyncAttempt.Fail(node.GetAlias(), ChangeType.Fail, "No content item could be found to attach domain"); + return SyncAttempt.Fail( + node.GetAlias(), + ChangeType.Fail, + $"No content item could be found to attach domain to. Attempted rootKey: {rootKey}, rootName: '{rootName}'"); if (item.RootContentId != rootItem.Id) { From 75d6f4d6009475c998e4c532ab1c9fd57a42a305 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 2 Oct 2025 17:30:14 +0100 Subject: [PATCH 08/11] Update uSync.Backoffice.Management.Client/usync-assets/src/lang/files/en-us.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../usync-assets/src/lang/files/en-us.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/lang/files/en-us.ts b/uSync.Backoffice.Management.Client/usync-assets/src/lang/files/en-us.ts index 70030fff7..e37636a9d 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/lang/files/en-us.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/lang/files/en-us.ts @@ -27,7 +27,7 @@ export default { changeAction: 'Action', changeItem: 'Item', changeDiffrence: 'Difference', - changeCreate: 'This item does not exist in umbraco and is being created', + changeCreate: 'This item does not exist in Umbraco and is being created', noChangesImport: 'No changes where made to this item', noChangesReport: 'No changes detected', noChangesDelete: 'This item has been deleted from Umbraco', From 515f2c0af21de3d2ccf33fa7ec9a76fe14c5c6c5 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 2 Oct 2025 17:30:23 +0100 Subject: [PATCH 09/11] Update uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../usync-assets/src/components/usync-change-view.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts index 2da83b18b..2f8034cc5 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts @@ -79,7 +79,7 @@ export class uSyncChangeView extends UmbElementMixin(LitElement) { (this.item?.message?.length ?? 0 > 0) ? this.item?.message : this.item?.change == ChangeType.IMPORT - ? 'No changes where made but the item was imported' + ? 'No changes were made but the item was imported' : '...'; const classes = { error: this.item?.success == false }; From 2eac27fb209970f7706c3c073e002477c8363cc5 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 2 Oct 2025 17:35:45 +0100 Subject: [PATCH 10/11] Fix , random co-piolot suggestion to log a varaible that isn't there. --- uSync.Core/Serialization/Serializers/DomainSerializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Core/Serialization/Serializers/DomainSerializer.cs b/uSync.Core/Serialization/Serializers/DomainSerializer.cs index 856ab2f95..a9860954c 100644 --- a/uSync.Core/Serialization/Serializers/DomainSerializer.cs +++ b/uSync.Core/Serialization/Serializers/DomainSerializer.cs @@ -83,7 +83,7 @@ protected override async Task> DeserializeCoreAsync(XElemen return SyncAttempt.Fail( node.GetAlias(), ChangeType.Fail, - $"No content item could be found to attach domain to. Attempted rootKey: {rootKey}, rootName: '{rootName}'"); + "No content item could be found to attach domain to"); if (item.RootContentId != rootItem.Id) { From c28737cfcf2e84211d666a3c78cdd92f734927b9 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 2 Oct 2025 22:05:03 +0100 Subject: [PATCH 11/11] remove unused imports --- .../usync-assets/src/components/usync-change-view.ts | 2 -- .../usync-assets/src/dialogs/single-import-modal.element.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts index 2f8034cc5..28b0a060a 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-change-view.ts @@ -5,9 +5,7 @@ import { css, customElement, html, - nothing, property, - when, } from '@umbraco-cms/backoffice/external/lit'; import { ChangeType, USyncActionView } from '@jumoo/uSync'; import { diffWords } from '@umbraco-cms/backoffice/utils'; diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts index ca8c2a434..9b5a2590d 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/dialogs/single-import-modal.element.ts @@ -4,7 +4,7 @@ import { SyncImportSingleModalResult, } from './single-import-modal.token'; import { customElement, state } from 'lit/decorators.js'; -import { css, html, nothing } from 'lit'; +import { css, html } from 'lit'; import { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; import { USYNC_CORE_CONTEXT_TOKEN, uSyncWorkspaceContext } from '../workspace'; import { USyncAction } from '../api';