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
52 changes: 26 additions & 26 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ import Ui from './ui';
import Uploader from './uploader';

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


type ImageToolConstructorOptions = BlockToolConstructorOptions<ImageToolData, ImageConfig>
type ImageToolConstructorOptions< CustomActions = {},AdditionalUploadResponse = {}> = BlockToolConstructorOptions<ImageToolData<CustomActions, AdditionalUploadResponse>, ImageConfig>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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


export default class ImageTool implements BlockTool {
export default class ImageTool<CustomActions = {} ,AdditionalUploadResponse = {}> implements BlockTool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export default class ImageTool<CustomActions = {} ,AdditionalUploadResponse = {}> implements BlockTool {
export default class ImageTool<CustomActions = {}, AdditionalUploadResponse = {}> implements BlockTool {

/**
* Editor.js API instance
*/
Expand All @@ -67,17 +67,17 @@ export default class ImageTool implements BlockTool {
/**
* Uploader module instance
*/
private uploader: Uploader;
private uploader: Uploader<AdditionalUploadResponse>;

/**
* UI module instance
*/
private ui: Ui;
private ui: Ui<CustomActions, AdditionalUploadResponse>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to pass ImageToolData<CustomActions, AdditionalUploadResponse> because UI should not depend on AdditionalUploadResponse, it depends on data format instead since it may be used for rendering


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

/**
* @param {object} tool - tool properties got from editor.js
Expand All @@ -87,7 +87,7 @@ export default class ImageTool implements BlockTool {
* @param {boolean} tool.readOnly - read-only mode flag
* @param {BlockAPI|{}} 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 @@ -110,16 +110,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) => this.onUpload(response),
onUpload: (response: UploadResponseFormat<AdditionalUploadResponse>) => this.onUpload(response),
onError: (error) => this.uploadingFailed(error),
});

/**
* Module for working with UI
*/
this.ui = new Ui({
this.ui = new Ui<CustomActions, AdditionalUploadResponse>({
api,
config: this.config,
onSelectFile: () => {
Expand All @@ -143,7 +143,7 @@ export default class ImageTool implements BlockTool {
file: {
url: '',
},
};
} as ImageToolData<CustomActions, AdditionalUploadResponse>;
this.data = data;
}
/**
Expand Down Expand Up @@ -215,7 +215,7 @@ export default class ImageTool implements BlockTool {
* @returns {boolean} false if saved data is not correct, otherwise true
* @public
*/
validate(savedData: ImageToolData): boolean {
validate(savedData: ImageToolData<CustomActions, AdditionalUploadResponse>): boolean {
return !!savedData.file.url;
}

Expand All @@ -226,7 +226,7 @@ export default class ImageTool implements BlockTool {
*
* @returns {ImageToolData}
*/
save(): ImageToolData {
save(): ImageToolData<CustomActions, AdditionalUploadResponse> {
const caption = this.ui.nodes.caption;

this._data.caption = caption.innerHTML;
Expand All @@ -251,15 +251,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 @@ -360,16 +360,16 @@ export default class ImageTool implements BlockTool {
*
* @param {ImageToolData} data - data in Image Tool format
*/
set data(data: ImageToolData) {
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 value = typeof data[tune as keyof ImageToolData<CustomActions, AdditionalUploadResponse>] !== 'undefined' ? data[tune as keyof ImageToolData<CustomActions, AdditionalUploadResponse>] === true || data[tune as keyof ImageToolData<CustomActions, AdditionalUploadResponse>] === 'true' : false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line is too long and hard to read. Can we do something with it?


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

Expand All @@ -380,7 +380,7 @@ export default class ImageTool implements BlockTool {
*
* @returns {ImageToolData}
*/
get data(): ImageToolData {
get data(): ImageToolData<CustomActions, AdditionalUploadResponse> {
return this._data;
}

Expand All @@ -391,8 +391,8 @@ export default class ImageTool implements BlockTool {
*
* @param {object} file - uploaded file data
*/
set image(file: { url: string } | undefined) {
this._data.file = file || {url: ''};
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 @@ -407,7 +407,7 @@ export default class ImageTool implements BlockTool {
* @param {UploadResponseFormat} response - uploading server response
* @returns {void}
*/
onUpload(response: UploadResponseFormat): void {
onUpload(response: UploadResponseFormat<AdditionalUploadResponse>): void {
if (response.success && response.file) {
this.image = response.file;
} else {
Expand Down Expand Up @@ -440,9 +440,9 @@ export default class ImageTool implements BlockTool {
* @param {string} tuneName - tune that has been clicked
* @returns {void}
*/
tuneToggled(tuneName: keyof ImageToolData): void {
tuneToggled(tuneName: keyof ImageToolData<CustomActions, AdditionalUploadResponse>): void {
// inverse tune state
this.setTune(tuneName, !this._data[tuneName as keyof ImageToolData]);
this.setTune(tuneName, !this._data[tuneName as keyof ImageToolData<CustomActions, AdditionalUploadResponse>]);
}

/**
Expand All @@ -452,10 +452,10 @@ export default class ImageTool implements BlockTool {
* @param {boolean} value - tune state
* @returns {void}
*/
setTune(tuneName: keyof ImageToolData, value: boolean): void {
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
2 changes: 1 addition & 1 deletion src/types/codexteam__ajax.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ declare module '@codexteam/ajax' {
url?: string;
data?: any;
accept?: string;
headers?: Headers;
headers?: Record<string, string> | Headers;
beforeSend?: (files: File[]) => void;
fieldName?: string;
type?: string;
Expand Down
24 changes: 12 additions & 12 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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 @@ -49,12 +59,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 @@ -85,12 +90,7 @@ 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;
file: FileObjectData<AdditionalFileData>;
} & (Actions extends Record<string, boolean> ? Actions : {});

/**
Expand Down
4 changes: 2 additions & 2 deletions src/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ interface ConstructorParams {
* - show/hide preview
* - apply tune view
*/
export default class Ui {
export default class Ui<CustomActions = {}, AdditionalUploadResponse = {}> {
/**
* API instance for Editor.js.
*/
Expand Down Expand Up @@ -180,7 +180,7 @@ public nodes: Nodes;
* @param {ImageToolData} toolData - saved tool data
* @returns {Element}
*/
render(toolData: ImageToolData): HTMLElement {
render(toolData: ImageToolData<CustomActions, AdditionalUploadResponse>): HTMLElement {
if (!toolData.file || Object.keys(toolData.file).length === 0) {
this.toggleStatus(UiState.Empty);
} else {
Expand Down
28 changes: 14 additions & 14 deletions src/uploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { UploadResponseFormat, ImageConfig } from './types/types';
/**
* Params interface for Uploader constructor
*/
interface UploaderParams {
interface UploaderParams<AdditionalUploadResponse = {}> {
/**
* Configuration for the uploader
*/
Expand All @@ -16,7 +16,7 @@ interface UploaderParams {
* @param response: Callback function for successful upload
* @returns void
*/
onUpload: (response: UploadResponseFormat) => void;
onUpload: (response: UploadResponseFormat<AdditionalUploadResponse>) => void;
/**
*
* @param error : error type
Expand All @@ -31,17 +31,17 @@ interface UploaderParams {
* 2. Upload by pasting URL
* 3. Upload by pasting file from Clipboard or by Drag'n'Drop
*/
export default class Uploader {
export default class Uploader<AdditionalUploadResponse = {}> {
private config: ImageConfig;
private onUpload: (response: UploadResponseFormat) => void;
private onUpload: (response: UploadResponseFormat<AdditionalUploadResponse>) => void;
private onError: (error: any) => void;
/**
* @param {object} params - uploader module params
* @param {ImageConfig} params.config - image tool config
* @param {Function} params.onUpload - one callback for all uploading (file, url, d-n-d, pasting)
* @param {Function} params.onError - callback for uploading errors
*/
constructor({ config, onUpload, onError }: UploaderParams) {
constructor({ config, onUpload, onError }: UploaderParams<AdditionalUploadResponse>) {
this.config = config;
this.onUpload = onUpload;
this.onError = onError;
Expand All @@ -67,7 +67,7 @@ export default class Uploader {
* Custom uploading
* or default uploading
*/
let upload: Promise<UploadResponseFormat>;
let upload: Promise<UploadResponseFormat<AdditionalUploadResponse>>;

// custom uploading
if (this.config.uploader && typeof this.config.uploader.uploadByFile === 'function') {
Expand All @@ -81,7 +81,7 @@ export default class Uploader {
console.warn('Custom uploader method uploadByFile should return a Promise');
}

return customUpload as Promise<UploadResponseFormat>;
return customUpload as Promise<UploadResponseFormat<AdditionalUploadResponse>>;
});

// default uploading
Expand All @@ -90,16 +90,16 @@ export default class Uploader {
url: this.config.endpoints.byFile,
data: this.config.additionalRequestData,
accept: this.config.types,
headers: new Headers(this.config.additionalRequestHeaders as Record<string, string>),
headers: this.config.additionalRequestHeaders as Record<string, string>,
beforeSend: (files: File[]) => {
preparePreview(files[0]);
},
fieldName: this.config.field,
}).then((response: any) => response.body);
}

upload.then((response) => {
this.onUpload(response);
upload.then((response) => {
this.onUpload(response as UploadResponseFormat<AdditionalUploadResponse>);
}).catch((error) => {
this.onError(error);
});
Expand Down Expand Up @@ -133,11 +133,11 @@ export default class Uploader {
url: url,
}, this.config.additionalRequestData),
type: ajax.contentType.JSON,
headers: new Headers(this.config.additionalRequestHeaders as Record<string, string>),
headers: this.config.additionalRequestHeaders as Record<string, string>,
}).then((response: any) => response.body);
}

upload.then((response: UploadResponseFormat) => {
upload.then((response: UploadResponseFormat<AdditionalUploadResponse>) => {
this.onUpload(response);
}).catch((error: any) => {
this.onError(error);
Expand Down Expand Up @@ -193,12 +193,12 @@ export default class Uploader {
url: this.config.endpoints.byFile,
data: formData,
type: ajax.contentType.JSON,
headers: new Headers(this.config.additionalRequestHeaders as Record<string, string>),
headers: this.config.additionalRequestHeaders as Record<string, string>,
}).then((response: any) => response.body);
}

upload.then((response) => {
this.onUpload(response);
this.onUpload(response as UploadResponseFormat<AdditionalUploadResponse>);
}).catch((error) => {
this.onError(error);
});
Expand Down
Loading