## Description - On air-gapped instances we can't fetch appsmith assets from S3, that will result in broken which is not desirable. - So this adds a script and util function which searches the client and server codebase for the assets url and downloads the image and puts it in the `public` folder so that the browser can access those even in an airgapped instance since the assets are being served locally. > Way to serve assets locally. Fixes #22004 > if no issue exists, please create an issue and ask the maintainers about this first Media https://vdqm24wed6.vmaker.com/record/IquS90WbWgS1I0bz - blocked certain api routes from getting called on airgap
190 lines
4.5 KiB
TypeScript
190 lines
4.5 KiB
TypeScript
import tinycolor from "tinycolor2";
|
|
import { darkenColor } from "widgets/WidgetUtils";
|
|
import { Toaster, Variant } from "design-system-old";
|
|
import {
|
|
createMessage,
|
|
ADMIN_BRANDING_LOGO_SIZE_ERROR,
|
|
ADMIN_BRANDING_LOGO_FORMAT_ERROR,
|
|
ADMIN_BRANDING_FAVICON_SIZE_ERROR,
|
|
ADMIN_BRANDING_FAVICON_FORMAT_ERROR,
|
|
ADMIN_BRANDING_FAVICON_DIMENSION_ERROR,
|
|
} from "@appsmith/constants/messages";
|
|
import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants";
|
|
import { getAssetUrl } from "@appsmith/utils/airgapHelpers";
|
|
|
|
const FAVICON_MAX_WIDTH = 32;
|
|
const FAVICON_MAX_HEIGHT = 32;
|
|
const DEFAULT_BRANDING_PRIMARY_COLOR = "#D7D7D7";
|
|
export const APPSMITH_BRAND_PRIMARY_COLOR = "#F86A2B";
|
|
export const APPSMITH_BRAND_FAVICON_URL = getAssetUrl(
|
|
`${ASSETS_CDN_URL}/appsmith-favicon-orange.ico`,
|
|
);
|
|
export const APPSMITH_BRAND_LOGO_URL = getAssetUrl(
|
|
`${ASSETS_CDN_URL}/appsmith-logo-no-margin.png`,
|
|
);
|
|
|
|
/**
|
|
* create brand colors from primary color
|
|
*
|
|
* @param color
|
|
*/
|
|
export function createBrandColorsFromPrimaryColor(
|
|
brand: string = DEFAULT_BRANDING_PRIMARY_COLOR,
|
|
) {
|
|
const hsl = tinycolor(brand).toHsl();
|
|
const hue = hsl.h;
|
|
const saturation = hsl.s;
|
|
|
|
let textColor = "#000";
|
|
const isReadable = tinycolor.isReadable(brand, "#000", {
|
|
level: "AAA",
|
|
size: "small",
|
|
});
|
|
|
|
// if the brand color is not readable or the color is appsmith orange, use white as the text color
|
|
if (isReadable === false || brand === APPSMITH_BRAND_PRIMARY_COLOR) {
|
|
textColor = "#fff";
|
|
}
|
|
|
|
let bgColor = `#${tinycolor(`hsl ${hue} ${saturation} ${98}}`).toHex()}`;
|
|
|
|
// if the primary color is appsmith orange, use gray shade for the bg color
|
|
if (brand === APPSMITH_BRAND_PRIMARY_COLOR) {
|
|
bgColor = "#F8F9FA";
|
|
}
|
|
|
|
const disabledColor = `#${tinycolor(
|
|
`hsl ${hue} ${saturation} ${92}}`,
|
|
).toHex()}`;
|
|
const hoverColor = darkenColor(brand);
|
|
|
|
return {
|
|
primary: brand,
|
|
background: bgColor,
|
|
hover: hoverColor,
|
|
font: textColor,
|
|
disabled: disabledColor,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* validates the uploaded logo file
|
|
*
|
|
* checks:
|
|
* 1. file size max 2MB
|
|
* 2. file type - jpg, or png
|
|
*
|
|
* @param e
|
|
* @param callback
|
|
* @returns
|
|
*/
|
|
export const logoImageValidator = (
|
|
e: React.ChangeEvent<HTMLInputElement>,
|
|
callback?: (e: React.ChangeEvent<HTMLInputElement>) => void,
|
|
) => {
|
|
const file = e.target.files?.[0];
|
|
|
|
// case 1: no file selected
|
|
if (!file) return false;
|
|
|
|
// case 2: file size > 2mb
|
|
if (file.size > 2 * 1024 * 1024) {
|
|
Toaster.show({
|
|
text: createMessage(ADMIN_BRANDING_LOGO_SIZE_ERROR),
|
|
variant: Variant.danger,
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
// case 3: image selected
|
|
const validTypes = ["image/jpeg", "image/png"];
|
|
|
|
if (!validTypes.includes(file.type)) {
|
|
Toaster.show({
|
|
text: createMessage(ADMIN_BRANDING_LOGO_FORMAT_ERROR),
|
|
variant: Variant.danger,
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
// case 4: check size
|
|
const image = new Image();
|
|
image.src = window.URL.createObjectURL(file);
|
|
|
|
callback && callback(e);
|
|
};
|
|
|
|
/**
|
|
* validates the uploaded favicon file
|
|
*
|
|
* checks:
|
|
* 1. file size max 2MB
|
|
* 2. file type - jpg, ico or png
|
|
* 3. file dimensions - height, width = [32, 32]
|
|
*
|
|
* @param e
|
|
* @param callback
|
|
* @returns
|
|
*/
|
|
export const faivconImageValidator = (
|
|
e: React.ChangeEvent<HTMLInputElement>,
|
|
callback?: (e: React.ChangeEvent<HTMLInputElement>) => void,
|
|
) => {
|
|
const file = e.target.files?.[0];
|
|
|
|
// case 1: no file selected
|
|
if (!file) return false;
|
|
|
|
// case 2: file size > 2mb
|
|
if (file.size > 2 * 1024 * 1024) {
|
|
Toaster.show({
|
|
text: createMessage(ADMIN_BRANDING_FAVICON_SIZE_ERROR),
|
|
variant: Variant.danger,
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
// case 3: image selected
|
|
const validTypes = [
|
|
"image/jpeg",
|
|
"image/png",
|
|
"image/vnd.microsoft.icon",
|
|
"image/x-icon",
|
|
"image/x-image",
|
|
];
|
|
|
|
if (!validTypes.includes(file.type)) {
|
|
Toaster.show({
|
|
text: createMessage(ADMIN_BRANDING_FAVICON_FORMAT_ERROR),
|
|
variant: Variant.danger,
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
// case 4: check size
|
|
const image = new Image();
|
|
image.src = window.URL.createObjectURL(file);
|
|
|
|
image.onload = function () {
|
|
const height = image.naturalHeight;
|
|
const width = image.naturalWidth;
|
|
|
|
window.URL.revokeObjectURL(image.src);
|
|
|
|
if (height > FAVICON_MAX_HEIGHT || width > FAVICON_MAX_WIDTH) {
|
|
Toaster.show({
|
|
text: createMessage(ADMIN_BRANDING_FAVICON_DIMENSION_ERROR),
|
|
variant: Variant.danger,
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
callback && callback(e);
|
|
};
|
|
};
|