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

🔧 Some fixes and features. #728

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions content/effects/flip.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
The Flip effect lets users flip their images before uploading them. Users can choose to flip vertically or horizontally.

```ts
import { Uppload, Rotate } from "uppload";
import { Uppload, Flip } from "uppload";

const profilePicture = new Uppload();
profilePicture.use(new Rotate());
profilePicture.use(new Flip());
```

![Screenshot of the Flip effect](/assets/screenshots/flip.png)
17 changes: 17 additions & 0 deletions src/helpers/elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,23 @@ export const safeListen = (
listening.push({ element, type });
};

/**
* Safely remove an event listener
* @param element - HTML element to remove event listener to
* @param type - Type of event listener to remove
* @param fn - Callback function to associated listener
*/
export const safeUnlisten = (
element: Element,
type: string,
fn: EventListenerOrEventListenerObject
) => {
const listener = listening.findIndex((a) => a.element === element && a.type === type);
if (listener < 0) return;
element.removeEventListener(type, fn);
listening.splice(listener, 1);
};

const safeRequestAnimationFrame = (callback: FrameRequestCallback) => {
if (window.requestAnimationFrame)
return window.requestAnimationFrame(callback);
Expand Down
5 changes: 5 additions & 0 deletions src/helpers/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export type IElements = string | string[] | Element | Element[];

export type ITranslator = (key: string, params?: string | string[]) => string;
export interface IUppload {
id: string;
wrapper?: string;
services: UpploadService[];
effects: UpploadEffect[];
isOpen: boolean;
Expand All @@ -15,6 +17,8 @@ export interface IUppload {
}

export interface IUpploadSettings {
id?: string;
wrapper?: string;
value?: string;
bind?: IElements;
call?: IElements;
Expand All @@ -33,6 +37,7 @@ export interface IUpploadSettings {
compressor?: (file: Blob) => Promise<Blob>;
transitionDuration?: number;
disableModalClickClose?: boolean;
disableHelp?: boolean;
}

export interface IHandlersParams {
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/microlink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class MicrolinkBaseClass extends UpploadService {
exampleURL = "";
validator: (value: string) => boolean = () => true;

template = ({ translate }: IServiceTemplateParams): string => {
template = ({ translate, uppload }: IServiceTemplateParams): string => {
return `
<div class="microlink-container">
<form class="microlink-search-form">
Expand Down Expand Up @@ -49,9 +49,9 @@ export class MicrolinkBaseClass extends UpploadService {
"services.microlink.button",
translate(`services.${this.name}.title`) || this.name
)
}</button></form><button class="need-help-link"><span>${translate(
}</button></form>${!uppload.settings.disableHelp ? `<button class="need-help-link"><span>${translate(
"needHelp"
)}</span aria-hidden="true"><span>?</span></button></div>
)}</span aria-hidden="true"><span>?</span></button>` : ""}</div>
<div class="uppload-loader microlink-loader">
<div></div>
<p>${
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export class SearchBaseClass<ImageResult = any> extends UpploadService {
if (loader) loader.style.display = this.loading ? "flex" : "none";
}

template = ({ translate }: IServiceTemplateParams): string => {
template = ({ translate, uppload }: IServiceTemplateParams): string => {
return `
<div class="search-container"><form class="search-search-form">
<div class="service-icon">${colorSVG(this.icon, this)}</div>
Expand All @@ -131,9 +131,9 @@ export class SearchBaseClass<ImageResult = any> extends UpploadService {
`services.${this.name}.title`
)}</a>`
)}</p></div>
<button class="need-help-link"><span>${translate(
${!uppload.settings.disableHelp ? `<button class="need-help-link"><span>${translate(
"needHelp"
)}</span aria-hidden="true"><span>?</span></button>
)}</span aria-hidden="true"><span>?</span></button>` : ""}
<div class="uppload-loader search-loader">
<div></div>
<p>${translate(
Expand Down
6 changes: 3 additions & 3 deletions src/services/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default class Camera extends UpploadService {
!/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
);

template = ({ translate }: IServiceTemplateParams) => {
template = ({ translate, uppload }: IServiceTemplateParams) => {
return `
<div class="service-main">
<div class="camera-waiting">${translate(
Expand All @@ -49,9 +49,9 @@ export default class Camera extends UpploadService {
style="background: ${this.color}"
>${translate("services.camera.button")}</button>
</footer>
<button class="need-help-link"><span>${translate(
${!uppload.settings.disableHelp ? `<button class="need-help-link"><span>${translate(
"needHelp"
)}</span aria-hidden="true"><span>?</span></button>
)}</span aria-hidden="true"><span>?</span></button>` : ""}
`;
};

Expand Down
4 changes: 2 additions & 2 deletions src/services/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export default class Local extends UpploadService {
<div class="alternate-input">
<input type="file" accept="${this.mimeTypes.join()}"${
params.uppload.settings.multiple ? " multiple" : ""
}></div><button class="need-help-link"><span>${translate(
}></div>${!params.uppload.settings.disableHelp ? `<button class="need-help-link"><span>${translate(
"needHelp"
)}</span aria-hidden="true"><span>?</span></button>`;
)}</span aria-hidden="true"><span>?</span></button>` : ""}`;
};

handlers = (params: IHandlersParams) => {
Expand Down
66 changes: 41 additions & 25 deletions src/uppload.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UpploadService } from "./service";
import { UpploadEffect } from "./effect";
import { setI18N, translate } from "./helpers/i18n";
import { getElements, safeListen, compressImage } from "./helpers/elements";
import { getElements, safeListen, safeUnlisten, compressImage } from "./helpers/elements";
import { colorSVG } from "./helpers/assets";
import createFocusTrap, { FocusTrap, Options } from "focus-trap";
import mitt from "mitt";
Expand Down Expand Up @@ -37,6 +37,8 @@ class UploadingService extends UpploadService {
* Uppload image uploading widget
*/
export class Uppload implements IUppload {
id: string = `${+new Date()}`;
wrapper?: string;
services: UpploadService[] = [new DefaultService(), new UploadingService()];
effects: UpploadEffect[] = [];
isOpen = false;
Expand All @@ -62,11 +64,12 @@ export class Uppload implements IUppload {
this.settings = {};
this.updateSettings(settings || {});
this.container = document.createElement("div");
this.container.setAttribute("id", `uppload-${this.id}`);
this.renderContainer();
this.container.classList.add("uppload-container");
const body = document.body;
if (body) {
body.appendChild(this.container);
const wrapper = this.wrapper ? document.querySelector(this.wrapper) : document.body;
if (wrapper) {
wrapper.appendChild(this.container);
}
this.focusTrap = createFocusTrap(this.container, {
initialFocus: () => this.container.querySelector("button"),
Expand Down Expand Up @@ -98,6 +101,8 @@ export class Uppload implements IUppload {
updateSettings(settings: IUpploadSettings) {
this.settings = { ...this.settings, ...settings };
this.emitter.emit("settingsUpdated", settings);
if (settings.id) this.id = settings.id;
if (settings.wrapper) this.wrapper = settings.wrapper;
if (settings.lang) setI18N(settings.lang);
if (settings.defaultService) this.activeService = settings.defaultService;
if (settings.lang) this.lang = settings.lang;
Expand Down Expand Up @@ -221,19 +226,24 @@ export class Uppload implements IUppload {
this.file = { blob: new Blob() };
this.activeService = "default";
this.activeEffect = "";
const serviceRadio = this.container.querySelector(
`input[type=radio][value='${this.activeService}']`
);
if (serviceRadio) serviceRadio.setAttribute("checked", "checked");
this.container.style.transition = `${this.transitionDuration}ms`;
this.container.style.opacity = "0";
this.update();
let firstService = this.settings.defaultService;
if (this.services.length === 3) this.navigate(this.services[2].name);
const firstService = this.settings.defaultService;
if (firstService) this.navigate(firstService);
safeListen(document.body, "keyup", (e) => {
if ((e as KeyboardEvent).key === "Escape" && this.open) this.close();
});
else if (this.services.length === 3) this.navigate(this.services[2].name);
const serviceRadio = this.container.querySelector(
`input[type=radio][value='${this.activeService}']`
) as HTMLInputElement;
if (serviceRadio) {
serviceRadio.setAttribute("checked", "checked");
serviceRadio.checked = true;
}
const escape = (e: KeyboardEvent) => {
if (e.key === "Escape" && this.isOpen) this.close();
};
safeUnlisten(document.body, "keyup", escape);
safeListen(document.body, "keyup", escape);
setTimeout(() => {
this.container.style.opacity = "1";
}, 1);
Expand Down Expand Up @@ -341,12 +351,12 @@ export class Uppload implements IUppload {
}" class="uppload-service-name">
${
sidebar
? `<input type="radio" id="uppload-service-radio-${service.name}" value="${service.name}" name="uppload-radio">`
? `<input type="radio" id="uppload-service-radio-${service.name}-${this.id}" value="${service.name}" name="uppload-radio" ${service.name === this.activeService ? `checked="checked"` : ""}>`
: ""
}
<${
sidebar
? `label for="uppload-service-radio-${service.name}"`
? `label for="uppload-service-radio-${service.name}-${this.id}"`
: "button"
} data-uppload-service="${service.name}">
${
Expand Down Expand Up @@ -378,10 +388,10 @@ export class Uppload implements IUppload {
${this.effects
.map(
(effect) => `
<input type="radio" id="uppload-effect-radio-${effect.name}" value="${
<input type="radio" id="uppload-effect-radio-${effect.name}-${this.id}" value="${
effect.name
}" name="uppload-effect-radio">
<label for="uppload-effect-radio-${effect.name}">
<label for="uppload-effect-radio-${effect.name}-${this.id}">
${
effect.icon.indexOf("http") === 0
? `<img class="effect-icon" alt="" src="${effect.icon}">`
Expand Down Expand Up @@ -419,6 +429,7 @@ export class Uppload implements IUppload {
<div class="uppload-active-container"></div>
<footer style="display: none" class="effects-nav">${this.getEffectsNavbar()}</footer>
</section>
${!this.settings.disableHelp ? `
<div class="uppload-help-loading">
<div class="uppload-loader">
<div></div>
Expand All @@ -430,7 +441,7 @@ export class Uppload implements IUppload {
"help.close"
)}</span><span aria-hidden="true">&times;</span></button></div>
<iframe></iframe>
</div>
</div>` : ""}
</div>
<div class="uppload-modal-bg">
<button class="uppload-close" aria-label="${translate(
Expand Down Expand Up @@ -621,8 +632,11 @@ export class Uppload implements IUppload {
// Set active state to current effect
const activeRadio = this.container.querySelector(
`input[name='uppload-effect-radio'][value='${this.activeEffect}']`
);
if (activeRadio) activeRadio.setAttribute("checked", "checked");
) as HTMLInputElement;
if (activeRadio) {
activeRadio.setAttribute("checked", "checked");
activeRadio.checked = true;
}
}

compress(file: Blob) {
Expand Down Expand Up @@ -713,6 +727,13 @@ export class Uppload implements IUppload {
defaultServiceLinks.forEach((link) => {
const linkFunction = (e: Event) => {
const service = link.getAttribute("data-uppload-service");
const serviceRadio = this.container.querySelector(
`input[type=radio][value='${service}']`
) as HTMLInputElement;
if (serviceRadio) {
serviceRadio.setAttribute("checked", "checked");
serviceRadio.checked = true;
}
if (service) {
this.navigate(service);
const serviceDiv = this.container.querySelector(
Expand All @@ -732,10 +753,6 @@ export class Uppload implements IUppload {
} catch (error) {}
}
}
const serviceRadio = this.container.querySelector(
`input[type=radio][value='${service}']`
);
if (serviceRadio) serviceRadio.setAttribute("checked", "checked");
e.preventDefault();
return false;
};
Expand Down Expand Up @@ -809,7 +826,6 @@ export class Uppload implements IUppload {
if (cancelButton)
safeListen(cancelButton, "click", () => {
this.file = { blob: new Blob() };
this.activeService = "default";
this.activeEffect = "";
this.update();
});
Expand Down