Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Command } from "../../commands-setup/Command";

type SupportedResponseTypes = "text" | "arraybuffer";
export type SupportedResponseTypes = "application/json" | "arraybuffer";

export class NetworkRequestCommand implements Command {
readonly type = "networkRequest";
Expand Down
6 changes: 5 additions & 1 deletion src/commands-setup/CommandsMapping.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NetworkRequestCommandHandler } from "../browser-commands/network-request/NetworkRequestCommandHandler";
import { OfficialFigmaPluginApi } from "../infrastructure/OfficialFigmaPluginApi";
import { CancelCommandHandler } from "../scene-commands/cancel/CancelCommandHandler";
import { CreateShapesCommandHandler } from "../scene-commands/create-shapes/CreateShapesCommandHandler";
import { PaintCurrentUserAvatarCommandHandler } from "../scene-commands/paint-current-user-avatar/PaintCurrentUserAvatarCommandHandler";
Expand All @@ -8,8 +9,11 @@ import { CommandHandler } from "./CommandHandler";
// 👋 Add below your new commands.
// Define its arbitrary key and its corresponding Handler class.
// Tip: Declare your Command and CommandHandler classes creating a folder inside the `src/scene-commands` or `src/browser-commands` ones depending on the things you need to get access to (see the README explanation) 😊
const officialFigmaPluginApi = new OfficialFigmaPluginApi(figma);
const cancelCommandHandler = new CancelCommandHandler(officialFigmaPluginApi);

export const CommandsMapping: Record<string, () => CommandHandler<Command>> = {
cancel: () => new CancelCommandHandler(figma),
cancel: () => cancelCommandHandler,
createShapes: () => new CreateShapesCommandHandler(figma),
paintCurrentUserAvatar: () => new PaintCurrentUserAvatarCommandHandler(figma),
networkRequest: () => new NetworkRequestCommandHandler(),
Expand Down
21 changes: 21 additions & 0 deletions src/domain/FigmaPluginApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SupportedResponseTypes } from "../browser-commands/network-request/NetworkRequestCommand";

export type FigmaUser = {
photoUrl: string;
name: string;
};

export interface FigmaPluginApi {
currentUser(): FigmaUser;

notify(message: string): void;

select(message: string): void;

request<ResponseType extends SupportedResponseTypes>(
url: string,
responseType: ResponseType
): Promise<
ResponseType extends "arraybuffer" ? ArrayBuffer : Record<string, unknown>
>;
}
63 changes: 63 additions & 0 deletions src/infrastructure/OfficialFigmaPluginApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
NetworkRequestCommand,
SupportedResponseTypes,
} from "../browser-commands/network-request/NetworkRequestCommand";
import { executeCommand } from "../commands-setup/executeCommand";
import { FigmaPluginApi, FigmaUser } from "../domain/FigmaPluginApi";

export class OfficialFigmaPluginApi implements FigmaPluginApi {
constructor(private readonly figma: PluginAPI) {}

currentUser(): FigmaUser {
const currentUserAvatarUrl = this.figma.currentUser?.photoUrl;
const currentUserName = this.figma.currentUser?.name;

if (currentUserAvatarUrl === undefined || currentUserAvatarUrl === null) {
throw new Error("Current user does not have a photo");
}

if (currentUserName === undefined || currentUserName === null) {
throw new Error("Current user does not have a name");
}

return { photoUrl: currentUserAvatarUrl, name: currentUserName };
}

notify(message: string): void {
this.figma.notify(message);
}

// 🚩🚩🚩 🚩🚩🚩 🚩🚩🚩 🚩🚩🚩 🚩🚩🚩
// 🚩🚩🚩 Gente que se flipa desacoplando 🚩🚩🚩
// 🚩🚩🚩 Tendríamos que modelar nuestro SceneNode 🚩🚩🚩
// 🚩🚩🚩 🚩🚩🚩 🚩🚩🚩 🚩🚩🚩 🚩🚩🚩
select(elements: ReadonlyArray<SceneNode>): void {
this.figma.currentPage.selection = elements;
}

request<ResponseType extends SupportedResponseTypes>(
url: string,
responseType: ResponseType
): Promise<
ResponseType extends "arraybuffer" ? ArrayBuffer : Record<string, unknown>
> {
executeCommand(new NetworkRequestCommand(url, responseType));

return new Promise((resolve) => {
this.figma.ui.onmessage = async (command) => {
this.ensureToOnlyReceiveNetworkRequestResponse(command);

resolve(command.payload);
};
});
}

private ensureToOnlyReceiveNetworkRequestResponse(command: { type: string }) {
if (command.type !== "networkRequestResponse") {
const errorMessage =
"Unexpected command received while performing the request for painting the user avatar.";

throw new Error(errorMessage);
}
}
}
3 changes: 2 additions & 1 deletion src/scene-commands/cancel/CancelCommandHandler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { CommandHandler } from "../../commands-setup/CommandHandler";
import { FigmaPluginApi } from "../../domain/FigmaPluginApi";
import { CancelCommand } from "./CancelCommand";

export class CancelCommandHandler implements CommandHandler<CancelCommand> {
constructor(private readonly figma: PluginAPI) {}
constructor(private readonly figma: FigmaPluginApi) {}

// `command` argument needed due to polymorphism.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,33 @@
import { NetworkRequestCommand } from "../../browser-commands/network-request/NetworkRequestCommand";
import { CommandHandler } from "../../commands-setup/CommandHandler";
import { executeCommand } from "../../commands-setup/executeCommand";
import { FigmaPluginApi, FigmaUser } from "../../domain/FigmaPluginApi";
import { PaintCurrentUserAvatarCommand } from "./PaintCurrentUserAvatarCommand";

export class PaintCurrentUserAvatarCommandHandler
implements CommandHandler<PaintCurrentUserAvatarCommand>
{
private readonly avatarImageSize = 100;

constructor(private readonly figma: PluginAPI) {}
constructor(private readonly figma: FigmaPluginApi) {}

// `command` argument needed due to polymorphism.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
handle(command: PaintCurrentUserAvatarCommand): Promise<void> {
const currentUserAvatarUrl = this.figma.currentUser?.photoUrl;
const currentUserName = this.figma.currentUser?.name;
async handle(command: PaintCurrentUserAvatarCommand): Promise<void> {
let currentUser: FigmaUser;

if (currentUserAvatarUrl === undefined || currentUserAvatarUrl === null) {
try {
currentUser = this.figma.currentUser();
} catch (e) {
this.figma.notify("Sorry but you do not have an avatar to add 😅");

return Promise.resolve();
}

const responseType = "arraybuffer";
executeCommand(
new NetworkRequestCommand(currentUserAvatarUrl, responseType)
const response = await this.figma.request(
currentUser.photoUrl,
"arraybuffer"
);

return new Promise((resolve) => {
this.figma.ui.onmessage = async (command) => {
this.ensureToOnlyReceiveNetworkRequestResponse(command);

await this.createAvatarBadge(
command.payload as ArrayBuffer,
currentUserName as string
);
resolve();
};
});
}

private ensureToOnlyReceiveNetworkRequestResponse(command: { type: string }) {
if (command.type !== "networkRequestResponse") {
const errorMessage =
"Unexpected command received while performing the request for painting the user avatar.";

throw new Error(errorMessage);
}
await this.createAvatarBadge(response, currentUser.name);
}

private async createAvatarBadge(
Expand Down
5 changes: 3 additions & 2 deletions tests/scene-commands/CancelCommandHandler.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { mock } from "jest-mock-extended";

import { FigmaPluginApi } from "../../src/domain/FigmaPluginApi";
import { CancelCommand } from "../../src/scene-commands/cancel/CancelCommand";
import { CancelCommandHandler } from "../../src/scene-commands/cancel/CancelCommandHandler";

describe("CancelCommandHandler", () => {
it("can be instantiated without throwing errors", () => {
const figmaPluginApiMock = mock<PluginAPI>();
const figmaPluginApiMock = mock<FigmaPluginApi>();

const cancelCommandHandlerInstantiator = () => {
new CancelCommandHandler(figmaPluginApiMock);
Expand All @@ -15,7 +16,7 @@ describe("CancelCommandHandler", () => {
});

it("notifies the end used with a farewell message", () => {
const figmaPluginApiMock = mock<PluginAPI>();
const figmaPluginApiMock = mock<FigmaPluginApi>();
const cancelCommandHandler = new CancelCommandHandler(figmaPluginApiMock);
const randomCancelCommand = new CancelCommand();

Expand Down