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

Provide generics for Image class #254

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@
"vite-plugin-dts": "^3.9.1"
},
"dependencies": {
"@codexteam/icons": "^0.3.0"
"@codexteam/icons": "^0.3.2"
}
}
59 changes: 32 additions & 27 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,21 @@
*/

import type { TunesMenuConfig } from '@editorjs/editorjs/types/tools';
import type { API, ToolboxConfig, PasteConfig, BlockToolConstructorOptions, BlockTool, BlockAPI, PasteEvent, PatternPasteEventDetail, FilePasteEventDetail } from '@editorjs/editorjs';
import type { API, ToolboxConfig, PasteConfig, BlockToolConstructorOptions, BlockAPI, PasteEvent, PatternPasteEventDetail, FilePasteEventDetail, BlockTool } from '@editorjs/editorjs';
import './index.css';

import Ui from './ui';
import Uploader from './uploader';

import { IconAddBorder, IconStretch, IconAddBackground, IconPicture } from '@codexteam/icons';
import type { ActionConfig, UploadResponseFormat, ImageToolData, ImageConfig, HTMLPasteEventDetailExtended, ImageSetterParam } from './types/types';
import type { ActionConfig, UploadResponseFormat, ImageToolData, ImageConfig, FileObjectData, HTMLPasteEventDetailExtended } from './types/types';

type ImageToolConstructorOptions = BlockToolConstructorOptions<ImageToolData, ImageConfig>;
type ImageToolConstructorOptions<CustomActions = {}, AdditionalUploadResponse = {}> = BlockToolConstructorOptions<ImageToolData<CustomActions, AdditionalUploadResponse>, ImageConfig<AdditionalUploadResponse>>;

/**
* Implementation of ImageTool class
*/
export default class ImageTool implements BlockTool {
export default class ImageTool<CustomActions = {}, AdditionalUploadResponse = {}> implements BlockTool {
/**
* Editor.js API instance
*/
Expand All @@ -63,22 +63,22 @@ export default class ImageTool implements BlockTool {
/**
* Configuration for the ImageTool
*/
private config: ImageConfig;
private config: ImageConfig<AdditionalUploadResponse>;

/**
* Uploader module instance
*/
private uploader: Uploader;
private uploader: Uploader<AdditionalUploadResponse>;

/**
* UI module instance
*/
private ui: Ui;
private ui: Ui<ImageToolData<CustomActions, AdditionalUploadResponse>, AdditionalUploadResponse>;

/**
* Stores current block data internally
*/
private _data: ImageToolData;
private _data: ImageToolData<CustomActions, AdditionalUploadResponse>;

/**
* @param tool - tool properties got from editor.js
Expand All @@ -88,7 +88,7 @@ export default class ImageTool implements BlockTool {
* @param tool.readOnly - read-only mode flag
* @param tool.block - current Block API
*/
constructor({ data, config, api, readOnly, block }: ImageToolConstructorOptions) {
constructor({ data, config, api, readOnly, block }: ImageToolConstructorOptions<CustomActions, AdditionalUploadResponse>) {
this.api = api;
this.readOnly = readOnly;
this.block = block;
Expand All @@ -111,16 +111,16 @@ export default class ImageTool implements BlockTool {
/**
* Module for file uploading
*/
this.uploader = new Uploader({
this.uploader = new Uploader<AdditionalUploadResponse>({
config: this.config,
onUpload: (response: UploadResponseFormat) => this.onUpload(response),
onUpload: (response: UploadResponseFormat<AdditionalUploadResponse>) => this.onUpload(response),
onError: (error: string) => this.uploadingFailed(error),
});

/**
* Module for working with UI
*/
this.ui = new Ui({
this.ui = new Ui<ImageToolData<CustomActions, AdditionalUploadResponse>, AdditionalUploadResponse>({
api,
config: this.config,
onSelectFile: () => {
Expand All @@ -144,7 +144,7 @@ export default class ImageTool implements BlockTool {
file: {
url: '',
},
};
} as ImageToolData<CustomActions, AdditionalUploadResponse>;
this.data = data;
}

Expand Down Expand Up @@ -205,14 +205,14 @@ export default class ImageTool implements BlockTool {
* @param savedData — data received after saving
* @returns false if saved data is not correct, otherwise true
*/
public validate(savedData: ImageToolData): boolean {
public validate(savedData: ImageToolData<CustomActions, AdditionalUploadResponse>): boolean {
return !!savedData.file.url;
}

/**
* Return Block data
*/
public save(): ImageToolData {
public save(): ImageToolData<CustomActions, AdditionalUploadResponse> {
const caption = this.ui.nodes.caption;

this._data.caption = caption.innerHTML;
Expand All @@ -234,15 +234,15 @@ export default class ImageTool implements BlockTool {
label: this.api.i18n.t(tune.title),
name: tune.name,
toggle: tune.toggle,
isActive: this.data[tune.name as keyof ImageToolData] as boolean,
isActive: this.data[tune.name as keyof ImageToolData<CustomActions, AdditionalUploadResponse>] as boolean,
onActivate: () => {
/** If it'a user defined tune, execute it's callback stored in action property */
if (typeof tune.action === 'function') {
tune.action(tune.name);

return;
}
this.tuneToggled(tune.name as keyof ImageToolData);
this.tuneToggled(tune.name as keyof ImageToolData<CustomActions, AdditionalUploadResponse>);
},
}));
}
Expand Down Expand Up @@ -333,32 +333,37 @@ export default class ImageTool implements BlockTool {
* Stores all Tool's data
* @param data - data in Image Tool format
*/
private set data(data: ImageToolData) {
private set data(data: ImageToolData<CustomActions, AdditionalUploadResponse>) {
this.image = data.file;

this._data.caption = data.caption || '';
this.ui.fillCaption(this._data.caption);

ImageTool.tunes.forEach(({ name: tune }) => {
const value = typeof data[tune as keyof ImageToolData] !== 'undefined' ? data[tune as keyof ImageToolData] === true || data[tune as keyof ImageToolData] === 'true' : false;
const tuneKey = tune as keyof ImageToolData<CustomActions, AdditionalUploadResponse>;
const tuneValue = data[tuneKey];

const value = typeof tuneValue !== 'undefined'
? tuneValue === true || tuneValue === 'true'
: false;

this.setTune(tune as keyof ImageToolData, value);
this.setTune(tune as keyof ImageToolData<CustomActions, AdditionalUploadResponse>, value);
});
}

/**
* Return Tool data
*/
private get data(): ImageToolData {
private get data(): ImageToolData<CustomActions, AdditionalUploadResponse> {
return this._data;
}

/**
* Set new image file
* @param file - uploaded file data
*/
private set image(file: ImageSetterParam | undefined) {
this._data.file = file || { url: '' };
private set image(file: FileObjectData<AdditionalUploadResponse> | undefined) {
this._data.file = file || ({ url: '' } as FileObjectData<AdditionalUploadResponse>);

if (file && file.url) {
this.ui.fillImage(file.url);
Expand All @@ -369,7 +374,7 @@ export default class ImageTool implements BlockTool {
* File uploading callback
* @param response - uploading server response
*/
private onUpload(response: UploadResponseFormat): void {
private onUpload(response: UploadResponseFormat<AdditionalUploadResponse>): void {
if (response.success && Boolean(response.file)) {
this.image = response.file;
} else {
Expand All @@ -395,7 +400,7 @@ export default class ImageTool implements BlockTool {
* Callback fired when Block Tune is activated
* @param tuneName - tune that has been clicked
*/
private tuneToggled(tuneName: keyof ImageToolData): void {
private tuneToggled(tuneName: keyof ImageToolData<CustomActions, AdditionalUploadResponse>): void {
// inverse tune state
this.setTune(tuneName, !(this._data[tuneName] as boolean));
}
Expand All @@ -405,10 +410,10 @@ export default class ImageTool implements BlockTool {
* @param tuneName - {@link Tunes.tunes}
* @param value - tune state
*/
private setTune(tuneName: keyof ImageToolData, value: boolean): void {
private setTune(tuneName: keyof ImageToolData<CustomActions, AdditionalUploadResponse>, value: boolean): void {
(this._data[tuneName] as boolean) = value;

this.ui.applyTune(tuneName, value);
this.ui.applyTune(String(tuneName), value);
if (tuneName === 'stretched') {
/**
* Wait until the API is ready
Expand Down
32 changes: 16 additions & 16 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ export interface UploadOptions {
onPreview: (src: string) => void;
}

/**
* Represents the format of a file object data with the additional data.
*/
export type FileObjectData<AdditionalFileData = {}> = {
/**
* The URL of the file.
*/
url: string;
} & AdditionalFileData;

/**
* User configuration of Image block tunes. Allows to add custom tunes through the config
*/
Expand Down Expand Up @@ -56,12 +66,7 @@ export interface UploadResponseFormat<AdditionalFileData = {}> {
* 'url' is required,
* also can contain any additional data that will be saved and passed back
*/
file: {
/**
* The URL of the uploaded image.
*/
url: string;
} & AdditionalFileData;
file: FileObjectData<AdditionalFileData>;
}

/**
Expand Down Expand Up @@ -92,19 +97,14 @@ export type ImageToolData<Actions = {}, AdditionalFileData = {}> = {
* Object containing the URL of the image file.
* Also can contain any additional data.
*/
file: {
/**
* The URL of the image.
*/
url: string;
} & AdditionalFileData;
} & (Actions extends Record<string, boolean> ? Actions : {});
file: FileObjectData<AdditionalFileData>;
} & Actions;

/**
*
* @description Config supported by Tool
*/
export interface ImageConfig {
export interface ImageConfig<AdditionalUploadResponse = {}> {
/**
* Endpoints for upload, whether using file or URL.
*/
Expand Down Expand Up @@ -159,12 +159,12 @@ export interface ImageConfig {
/**
* Method to upload an image by file.
*/
uploadByFile?: (file: Blob) => Promise<UploadResponseFormat>;
uploadByFile?: (file: Blob) => Promise<UploadResponseFormat<AdditionalUploadResponse>>;

/**
* Method to upload an image by URL.
*/
uploadByUrl?: (url: string) => Promise<UploadResponseFormat>;
uploadByUrl?: (url: string) => Promise<UploadResponseFormat<AdditionalUploadResponse>>;
};

/**
Expand Down
14 changes: 7 additions & 7 deletions src/ui.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IconPicture } from '@codexteam/icons';
import { make } from './utils/dom';
import type { API } from '@editorjs/editorjs';
import type { ImageToolData, ImageConfig } from './types/types';
import type { ImageConfig, ImageToolData } from './types/types';

/**
* Enumeration representing the different states of the UI.
Expand Down Expand Up @@ -61,15 +61,15 @@ interface Nodes {
/**
* ConstructorParams interface representing parameters for the Ui class constructor.
*/
interface ConstructorParams {
interface ConstructorParams<AdditionalUploadResponse = {}> {
/**
* Editor.js API.
*/
api: API;
/**
* Configuration for the image.
*/
config: ImageConfig;
config: ImageConfig<AdditionalUploadResponse>;
/**
* Callback function for selecting a file.
*/
Expand All @@ -86,7 +86,7 @@ interface ConstructorParams {
* - show/hide preview
* - apply tune view
*/
export default class Ui {
export default class Ui<ImageToolDataType extends ImageToolData = ImageToolData, AdditionalUploadResponse = {} > {
/**
* Nodes representing various elements in the UI.
*/
Expand All @@ -100,7 +100,7 @@ export default class Ui {
/**
* Configuration for the image tool.
*/
private config: ImageConfig;
private config: ImageConfig<AdditionalUploadResponse>;

/**
* Callback function for selecting a file.
Expand All @@ -119,7 +119,7 @@ export default class Ui {
* @param ui.onSelectFile - callback for clicks on Select file button
* @param ui.readOnly - read-only mode flag
*/
constructor({ api, config, onSelectFile, readOnly }: ConstructorParams) {
constructor({ api, config, onSelectFile, readOnly }: ConstructorParams<AdditionalUploadResponse>) {
this.api = api;
this.config = config;
this.onSelectFile = onSelectFile;
Expand Down Expand Up @@ -165,7 +165,7 @@ export default class Ui {
* Renders tool UI
* @param toolData - saved tool data
*/
public render(toolData: ImageToolData): HTMLElement {
public render(toolData: ImageToolDataType): HTMLElement {
if (toolData.file === undefined || Object.keys(toolData.file).length === 0) {
this.toggleStatus(UiState.Empty);
} else {
Expand Down
Loading
Loading