Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image file edit using luna paint extension for V1 and V2 data model #733

Merged
merged 5 commits into from Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/web/client/context/entityData.ts
Expand Up @@ -7,15 +7,15 @@ import { IEntityInfo } from "../common/interfaces";

export interface IEntityData extends IEntityInfo {
entityEtag: string;
entityColumn: Map<string, string>;
entityColumn: Map<string, string | Uint8Array>;
mappingEntityId?: string;
}

export class EntityData implements IEntityData {
private _entityName!: string;
private _entityId!: string;
private _entityEtag!: string;
private _entityColumn!: Map<string, string>;
private _entityColumn!: Map<string, string | Uint8Array>;
private _mappingEntityId?: string;

public get entityName(): string {
Expand All @@ -27,7 +27,7 @@ export class EntityData implements IEntityData {
public get entityEtag(): string {
return this._entityEtag;
}
public get entityColumn(): Map<string, string> {
public get entityColumn(): Map<string, string | Uint8Array> {
return this._entityColumn;
}
public get mappingEntityId(): string | undefined {
Expand All @@ -43,7 +43,7 @@ export class EntityData implements IEntityData {
entityId: string,
entityName: string,
entityEtag: string,
entityColumn: Map<string, string>,
entityColumn: Map<string, string | Uint8Array>,
mappingEntityId?: string
) {
this._entityId = entityId;
Expand Down
6 changes: 3 additions & 3 deletions src/web/client/context/entityDataMap.ts
Expand Up @@ -12,7 +12,7 @@ export class EntityDataMap {
private updateEntityContent(
entityId: string,
columnName: string,
columnContent: string
columnContent: string | Uint8Array
) {
const existingEntity = this.entityMap.get(entityId);

Expand All @@ -36,7 +36,7 @@ export class EntityDataMap {
attributeContent: string,
mappingEntityId?: string
) {
let entityColumnMap = new Map<string, string>();
let entityColumnMap = new Map<string, string | Uint8Array>();
const existingEntity = this.entityMap.get(entityId);

if (existingEntity) {
Expand Down Expand Up @@ -84,7 +84,7 @@ export class EntityDataMap {
public updateEntityColumnContent(
entityId: string,
columnName: IAttributePath,
columnAttributeContent: string
columnAttributeContent: string | Uint8Array
) {
const existingEntity = this.entityMap.get(entityId);

Expand Down
21 changes: 17 additions & 4 deletions src/web/client/dal/fileSystemProvider.ts
Expand Up @@ -30,7 +30,7 @@ import {
updateFileDirtyChanges,
updateFileEntityEtag,
} from "../utilities/fileAndEntityUtil";
import { isVersionControlEnabled } from "../utilities/commonUtil";
import { getImageFileContent, isImageFileSupportedForEdit, isVersionControlEnabled, updateFileContentInFileDataMap } from "../utilities/commonUtil";
import { IFileInfo } from "../common/interfaces";

export class File implements vscode.FileStat {
Expand Down Expand Up @@ -158,11 +158,13 @@ export class PortalsFS implements vscode.FileSystemProvider {
async writeFile(
uri: vscode.Uri,
content: Uint8Array,
options: { create: boolean; overwrite: boolean }
options: { create: boolean; overwrite: boolean },
isFirstTimeWrite = false
): Promise<void> {
const basename = path.posix.basename(uri.path);
const parent = await this._lookupParentDirectory(uri);
let entry = parent.entries.get(basename);
const isImageEdit = isImageFileSupportedForEdit(basename);

if (entry instanceof Directory) {
throw vscode.FileSystemError.FileIsADirectory(uri);
Expand All @@ -180,10 +182,21 @@ export class PortalsFS implements vscode.FileSystemProvider {
entry = new File(basename);
parent.entries.set(basename, entry);
this._fireSoon({ type: vscode.FileChangeType.Created, uri });
} else if (
WebExtensionContext.fileDataMap.getFileMap.get(uri.fsPath)
}

if (!isFirstTimeWrite &&
(WebExtensionContext.fileDataMap.getFileMap.get(uri.fsPath)
?.hasDirtyChanges
|| isImageEdit)
) {
if (isImageEdit) {
WebExtensionContext.telemetry.sendInfoTelemetry(
telemetryEventNames.WEB_EXTENSION_SAVE_IMAGE_FILE_TRIGGERED
);

updateFileContentInFileDataMap(uri.fsPath, getImageFileContent(uri.fsPath, content), true);
}

// Save data to dataverse
await vscode.window.withProgress(
{
Expand Down
3 changes: 2 additions & 1 deletion src/web/client/dal/remoteFetchProvider.ts
Expand Up @@ -629,7 +629,8 @@ async function createVirtualFile(
await portalsFS.writeFile(
vscode.Uri.parse(fileUri),
fileContent,
{ create: true, overwrite: true }
{ create: true, overwrite: true },
true
);

// Maintain entity details in context
Expand Down
21 changes: 4 additions & 17 deletions src/web/client/extension.ts
Expand Up @@ -18,13 +18,11 @@ import {
showErrorDialog,
} from "./common/errorHandler";
import { WebExtensionTelemetry } from "./telemetry/webExtensionTelemetry";
import { convertContentToString, isCoPresenceEnabled } from "./utilities/commonUtil";
import { isCoPresenceEnabled, updateFileContentInFileDataMap } from "./utilities/commonUtil";
import { NPSService } from "./services/NPSService";
import { vscodeExtAppInsightsResourceProvider } from "../../common/telemetry-generated/telemetryConfiguration";
import { NPSWebView } from "./webViews/NPSWebView";
import {
updateFileDirtyChanges,
updateEntityColumnContent,
getFileEntityId,
getFileEntityName,
} from "./utilities/fileAndEntityUtil";
Expand Down Expand Up @@ -246,6 +244,8 @@ export function processWorkspaceStateChanges(context: vscode.ExtensionContext) {
);
}

// This function will not be triggered for image file content update
// Image file content write to images needs to be handled in writeFile call directly
export function processWillSaveDocument(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.workspace.onWillSaveTextDocument(async (e) => {
Expand All @@ -254,20 +254,7 @@ export function processWillSaveDocument(context: vscode.ExtensionContext) {
if (vscode.window.activeTextEditor === undefined) {
return;
} else if (isActiveDocument(fileFsPath)) {
const fileData =
WebExtensionContext.fileDataMap.getFileMap.get(fileFsPath);

// Update the latest content in context
if (fileData?.entityId && fileData.attributePath) {
let fileContent = e.document.getText();
fileContent = convertContentToString(fileContent, fileData.encodeAsBase64 as boolean);
updateEntityColumnContent(
fileData?.entityId,
fileData.attributePath,
fileContent
);
updateFileDirtyChanges(fileFsPath, true);
}
updateFileContentInFileDataMap(fileFsPath, e.document.getText());
}
})
);
Expand Down
2 changes: 2 additions & 0 deletions src/web/client/telemetry/constants.ts
Expand Up @@ -97,4 +97,6 @@ export enum telemetryEventNames {
WEB_EXTENSION_POWER_PAGES_WEB_VIEW_REGISTER_FAILED = 'webExtensionPowerPagesWebViewRegisterFailed',
WEB_EXTENSION_BACK_TO_STUDIO_TRIGGERED = 'webExtensionBackToStudioTriggered',
WEB_EXTENSION_PREVIEW_SITE_TRIGGERED = 'webExtensionPreviewSiteTriggered',
WEB_EXTENSION_IMAGE_EDIT_SUPPORTED_FILE_EXTENSION = 'webExtensionImageEditSupportedFileExtension',
WEB_EXTENSION_SAVE_IMAGE_FILE_TRIGGERED = 'webExtensionSaveImageFileTriggered'
}
45 changes: 43 additions & 2 deletions src/web/client/utilities/commonUtil.ts
Expand Up @@ -21,7 +21,8 @@ import { schemaEntityName } from "../schema/constants";
import { telemetryEventNames } from "../telemetry/constants";
import WebExtensionContext from "../WebExtensionContext";
import { SETTINGS_EXPERIMENTAL_STORE_NAME } from "../../../client/constants";
import { doesFileExist } from "./fileAndEntityUtil";
import { doesFileExist, getFileAttributePath, getFileEntityName, updateEntityColumnContent, updateFileDirtyChanges } from "./fileAndEntityUtil";
import { isWebFileV2 } from "./schemaHelperUtil";

// decodes file content to UTF-8
export function convertContentToUint8Array(content: string, isBase64Encoded: boolean): Uint8Array {
Expand All @@ -30,7 +31,7 @@ export function convertContentToUint8Array(content: string, isBase64Encoded: boo
}

// encodes file content to base64 or returns the content as is
export function convertContentToString(content: string, isBase64Encoded: boolean): string {
export function convertContentToString(content: string | Uint8Array, isBase64Encoded: boolean): string | Uint8Array {
return isBase64Encoded ? Buffer.from(content).toString(BASE_64) : content;
}

Expand Down Expand Up @@ -223,3 +224,43 @@ export function getBackToStudioURL() {
.replace("{.region}", region.toLowerCase() === STUDIO_PROD_REGION ? "" : `.${WebExtensionContext.urlParametersMap.get(queryParameters.REGION) as string}`)
.replace("{webSiteId}", WebExtensionContext.urlParametersMap.get(queryParameters.WEBSITE_ID) as string);
}
export function getSupportedImageFileExtensionsForEdit() {
return ['png', 'jpg', 'webp', 'bmp', 'tga', 'ico', 'jpeg', 'bmp', 'dib', 'jif', 'jpe', 'tpic']; // Luna paint supported image file extensions
}

export function isImageFileSupportedForEdit(fileName: string): boolean {
const fileExtension = getFileExtension(fileName) as string;
const supportedImageFileExtensions = getSupportedImageFileExtensionsForEdit();
const isSupported = fileExtension !== undefined ?
supportedImageFileExtensions.includes(fileExtension.toLowerCase()) : false;

if (isSupported) {
WebExtensionContext.telemetry.sendInfoTelemetry(telemetryEventNames.WEB_EXTENSION_IMAGE_EDIT_SUPPORTED_FILE_EXTENSION,
{ fileExtension: fileExtension });
}

return isSupported;
}

export function updateFileContentInFileDataMap(fileFsPath: string, fileContent: string | Uint8Array, isFileContentBase64Encoded = false) {
const fileData =
WebExtensionContext.fileDataMap.getFileMap.get(fileFsPath);

// Update the latest content in context
if (fileData?.entityId && fileData.attributePath) {
fileContent = convertContentToString(fileContent, isFileContentBase64Encoded ? false : fileData.encodeAsBase64 as boolean);

updateEntityColumnContent(
fileData?.entityId,
fileData.attributePath,
fileContent
);
updateFileDirtyChanges(fileFsPath, true);
}
}

export function getImageFileContent(fileFsPath: string, fileContent: Uint8Array) {
const webFileV2 = isWebFileV2(getFileEntityName(fileFsPath), getFileAttributePath(fileFsPath)?.source);

return webFileV2 ? fileContent : Buffer.from(fileContent).toString(BASE_64);
}
7 changes: 6 additions & 1 deletion src/web/client/utilities/fileAndEntityUtil.ts
Expand Up @@ -28,6 +28,11 @@ export function getFileEntityName(fileFsPath: string) {
?.entityName as string ?? WebExtensionContext.getVscodeWorkspaceState(fileFsPath)?.entityName as string;
}

export function getFileAttributePath(fileFsPath: string) {
return WebExtensionContext.fileDataMap.getFileMap.get(fileFsPath)
?.attributePath as IAttributePath;
}

export function getFileEntityEtag(fileFsPath: string) {
return WebExtensionContext.fileDataMap.getFileMap.get(fileFsPath)
?.entityEtag as string;
Expand Down Expand Up @@ -91,7 +96,7 @@ export function updateEntityEtag(entityId: string, entityEtag: string) {
export function updateEntityColumnContent(
entityId: string,
attributePath: IAttributePath,
fileContent: string
fileContent: string | Uint8Array
) {
WebExtensionContext.entityDataMap.updateEntityColumnContent(
entityId,
Expand Down