fix: 修改红绿图模式下透明度恢复的默认值,优化导出逻辑
This commit is contained in:
@@ -827,7 +827,7 @@ export class CanvasManager {
|
|||||||
restoreOpacityInRedGreen:
|
restoreOpacityInRedGreen:
|
||||||
options.restoreOpacityInRedGreen !== undefined
|
options.restoreOpacityInRedGreen !== undefined
|
||||||
? options.restoreOpacityInRedGreen
|
? options.restoreOpacityInRedGreen
|
||||||
: true, // 默认在红绿图模式下恢复透明度
|
: false, // 默认在红绿图模式下恢复透明度
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层
|
// 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层
|
||||||
@@ -851,7 +851,6 @@ export class CanvasManager {
|
|||||||
console.log("红绿图模式导出图层:", normalLayerIds);
|
console.log("红绿图模式导出图层:", normalLayerIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.exportManager.exportImage(enhancedOptions);
|
return await this.exportManager.exportImage(enhancedOptions);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("CanvasManager导出图片失败:", error);
|
console.error("CanvasManager导出图片失败:", error);
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ export class ExportManager {
|
|||||||
return this._exportMultipleLayers(
|
return this._exportMultipleLayers(
|
||||||
layerIdArray,
|
layerIdArray,
|
||||||
expPicType,
|
expPicType,
|
||||||
|
isContainBg,
|
||||||
isContainFixed,
|
isContainFixed,
|
||||||
isRedGreenMode,
|
isRedGreenMode,
|
||||||
restoreOpacityInRedGreen,
|
restoreOpacityInRedGreen,
|
||||||
isContainBg,
|
|
||||||
isCropByBg
|
isCropByBg
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -619,7 +619,7 @@ export class ExportManager {
|
|||||||
// this.canvasManager?.canvasHeight?.value || this.canvas.height;
|
// this.canvasManager?.canvasHeight?.value || this.canvas.height;
|
||||||
|
|
||||||
// console.log(`普通模式导出,画布尺寸: ${canvasWidth}x${canvasHeight}`);
|
// console.log(`普通模式导出,画布尺寸: ${canvasWidth}x${canvasHeight}`);
|
||||||
|
debugger;
|
||||||
// 使用图层栅格化的方法导出图片
|
// 使用图层栅格化的方法导出图片
|
||||||
const dataURL = await createRasterizedImage({
|
const dataURL = await createRasterizedImage({
|
||||||
canvas: this.canvas,
|
canvas: this.canvas,
|
||||||
@@ -629,6 +629,7 @@ export class ExportManager {
|
|||||||
maskObject: maskObject ?? null, // 使用裁剪对象
|
maskObject: maskObject ?? null, // 使用裁剪对象
|
||||||
trimWhitespace: true, // 裁剪空白
|
trimWhitespace: true, // 裁剪空白
|
||||||
trimPadding: 0, // 裁剪边距
|
trimPadding: 0, // 裁剪边距
|
||||||
|
restoreOpacityInRedGreen,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("导出图片数据URL:", dataURL);
|
console.log("导出图片数据URL:", dataURL);
|
||||||
|
|||||||
@@ -55,10 +55,14 @@ export class RedGreenModeManager {
|
|||||||
if (typeof options.normalLayerOpacity === "number") {
|
if (typeof options.normalLayerOpacity === "number") {
|
||||||
if (options.normalLayerOpacity > 1) {
|
if (options.normalLayerOpacity > 1) {
|
||||||
// 如果大于1,认为是百分比值(0-100)
|
// 如果大于1,认为是百分比值(0-100)
|
||||||
this.normalLayerOpacity = Math.max(0, Math.min(100, options.normalLayerOpacity)) / 100;
|
this.normalLayerOpacity =
|
||||||
|
Math.max(0, Math.min(100, options.normalLayerOpacity)) / 100;
|
||||||
} else {
|
} else {
|
||||||
// 如果小于等于1,认为是小数值(0-1)
|
// 如果小于等于1,认为是小数值(0-1)
|
||||||
this.normalLayerOpacity = Math.max(0, Math.min(1, options.normalLayerOpacity));
|
this.normalLayerOpacity = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(1, options.normalLayerOpacity)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +97,10 @@ export class RedGreenModeManager {
|
|||||||
this.registerRedGreenMouseUpEvent();
|
this.registerRedGreenMouseUpEvent();
|
||||||
|
|
||||||
// 启用图层管理器的红绿图模式
|
// 启用图层管理器的红绿图模式
|
||||||
if (this.layerManager && typeof this.layerManager.enableRedGreenMode === "function") {
|
if (
|
||||||
|
this.layerManager &&
|
||||||
|
typeof this.layerManager.enableRedGreenMode === "function"
|
||||||
|
) {
|
||||||
this.layerManager.enableRedGreenMode();
|
this.layerManager.enableRedGreenMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +139,9 @@ export class RedGreenModeManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.onImageGenerated) {
|
if (this.onImageGenerated) {
|
||||||
const imageData = await this.canvasManager.exportImage();
|
const imageData = await this.canvasManager.exportImage({
|
||||||
|
restoreOpacityInRedGreen: true, // 恢复红绿图模式下的透明度
|
||||||
|
});
|
||||||
this.onImageGenerated(imageData);
|
this.onImageGenerated(imageData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -187,7 +196,9 @@ export class RedGreenModeManager {
|
|||||||
// 更新内部状态
|
// 更新内部状态
|
||||||
this.normalLayerOpacity = normalizedOpacity;
|
this.normalLayerOpacity = normalizedOpacity;
|
||||||
|
|
||||||
console.log(`普通图层透明度已更新为: ${Math.round(normalizedOpacity * 100)}%`);
|
console.log(
|
||||||
|
`普通图层透明度已更新为: ${Math.round(normalizedOpacity * 100)}%`
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("更新普通图层透明度失败:", error);
|
console.error("更新普通图层透明度失败:", error);
|
||||||
@@ -271,7 +282,9 @@ export class RedGreenModeManager {
|
|||||||
const layers = this.layerManager.layers.value || [];
|
const layers = this.layerManager.layers.value || [];
|
||||||
const backgroundLayer = layers.find((layer) => layer.isBackground);
|
const backgroundLayer = layers.find((layer) => layer.isBackground);
|
||||||
const fixedLayer = layers.find((layer) => layer.isFixed);
|
const fixedLayer = layers.find((layer) => layer.isFixed);
|
||||||
const normalLayers = layers.filter((layer) => !layer.isBackground && !layer.isFixed);
|
const normalLayers = layers.filter(
|
||||||
|
(layer) => !layer.isBackground && !layer.isFixed
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
backgroundLayer:
|
backgroundLayer:
|
||||||
@@ -307,7 +320,10 @@ export class RedGreenModeManager {
|
|||||||
cleanup() {
|
cleanup() {
|
||||||
try {
|
try {
|
||||||
// 禁用图层管理器的红绿图模式
|
// 禁用图层管理器的红绿图模式
|
||||||
if (this.layerManager && typeof this.layerManager.disableRedGreenMode === "function") {
|
if (
|
||||||
|
this.layerManager &&
|
||||||
|
typeof this.layerManager.disableRedGreenMode === "function"
|
||||||
|
) {
|
||||||
this.layerManager.disableRedGreenMode();
|
this.layerManager.disableRedGreenMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const createRasterizedImage = async ({
|
|||||||
isReturenDataURL = false, // 是否返回DataURL而不是fabric.Image对象
|
isReturenDataURL = false, // 是否返回DataURL而不是fabric.Image对象
|
||||||
preserveOriginalQuality = true, // 是否保持原始质量(新增)
|
preserveOriginalQuality = true, // 是否保持原始质量(新增)
|
||||||
selectionManager = null, // 选区管理器,用于获取羽化值等设置
|
selectionManager = null, // 选区管理器,用于获取羽化值等设置
|
||||||
|
restoreOpacityInRedGreen, // 是否在红绿图模式下恢复透明度
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
try {
|
try {
|
||||||
console.log(`📊 开始栅格化 ${fabricObjects.length} 个对象`);
|
console.log(`📊 开始栅格化 ${fabricObjects.length} 个对象`);
|
||||||
@@ -85,12 +86,18 @@ const createClippedObjects = async ({
|
|||||||
console.log("🎯 使用新的图像遮罩裁剪方法创建对象");
|
console.log("🎯 使用新的图像遮罩裁剪方法创建对象");
|
||||||
|
|
||||||
// 使用优化后的边界计算,确保包含描边区域
|
// 使用优化后的边界计算,确保包含描边区域
|
||||||
const optimizedBounds = calculateOptimizedBounds(clippingObject, fabricObjects);
|
const optimizedBounds = calculateOptimizedBounds(
|
||||||
|
clippingObject,
|
||||||
|
fabricObjects
|
||||||
|
);
|
||||||
console.log("📐 优化后的选区边界框:", optimizedBounds);
|
console.log("📐 优化后的选区边界框:", optimizedBounds);
|
||||||
|
|
||||||
// 获取羽化值
|
// 获取羽化值
|
||||||
let featherAmount = 0;
|
let featherAmount = 0;
|
||||||
if (selectionManager && typeof selectionManager.getFeatherAmount === "function") {
|
if (
|
||||||
|
selectionManager &&
|
||||||
|
typeof selectionManager.getFeatherAmount === "function"
|
||||||
|
) {
|
||||||
featherAmount = selectionManager.getFeatherAmount();
|
featherAmount = selectionManager.getFeatherAmount();
|
||||||
console.log(`🌟 应用羽化效果: ${featherAmount}px`);
|
console.log(`🌟 应用羽化效果: ${featherAmount}px`);
|
||||||
}
|
}
|
||||||
@@ -171,7 +178,10 @@ const createClippedDataURLByCanvas = async ({
|
|||||||
console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL");
|
console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL");
|
||||||
|
|
||||||
// 使用优化后的边界计算,确保包含描边区域
|
// 使用优化后的边界计算,确保包含描边区域
|
||||||
const optimizedBounds = calculateOptimizedBounds(clippingObject, fabricObjects);
|
const optimizedBounds = calculateOptimizedBounds(
|
||||||
|
clippingObject,
|
||||||
|
fabricObjects
|
||||||
|
);
|
||||||
|
|
||||||
// 使用高分辨率以保证质量
|
// 使用高分辨率以保证质量
|
||||||
const pixelRatio = window.devicePixelRatio || 1;
|
const pixelRatio = window.devicePixelRatio || 1;
|
||||||
@@ -230,7 +240,13 @@ const createClippedDataURLByCanvas = async ({
|
|||||||
* 创建简单克隆对象
|
* 创建简单克隆对象
|
||||||
* 当不需要裁剪时,直接克隆原对象
|
* 当不需要裁剪时,直接克隆原对象
|
||||||
*/
|
*/
|
||||||
const createSimpleClone = async ({ canvas, fabricObjects, isReturenDataURL, quality, format }) => {
|
const createSimpleClone = async ({
|
||||||
|
canvas,
|
||||||
|
fabricObjects,
|
||||||
|
isReturenDataURL,
|
||||||
|
quality,
|
||||||
|
format,
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
console.log("📋 创建简单克隆对象");
|
console.log("📋 创建简单克隆对象");
|
||||||
|
|
||||||
@@ -444,7 +460,10 @@ const createLegacyRasterization = async ({
|
|||||||
|
|
||||||
// 这里保留原有的离屏渲染逻辑作为备选方案
|
// 这里保留原有的离屏渲染逻辑作为备选方案
|
||||||
const currentZoom = canvas.getZoom?.() || 1;
|
const currentZoom = canvas.getZoom?.() || 1;
|
||||||
scaleFactor = Math.max(scaleFactor || canvas?.getRetinaScaling?.(), currentZoom);
|
scaleFactor = Math.max(
|
||||||
|
scaleFactor || canvas?.getRetinaScaling?.(),
|
||||||
|
currentZoom
|
||||||
|
);
|
||||||
scaleFactor = Math.min(scaleFactor, 3);
|
scaleFactor = Math.min(scaleFactor, 3);
|
||||||
|
|
||||||
const { absoluteBounds, relativeBounds } = calculateBounds(fabricObjects);
|
const { absoluteBounds, relativeBounds } = calculateBounds(fabricObjects);
|
||||||
@@ -574,7 +593,9 @@ const createOffscreenRasterization = async ({
|
|||||||
height: canvasHeight,
|
height: canvasHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`);
|
console.log(
|
||||||
|
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
|
||||||
|
);
|
||||||
|
|
||||||
// 克隆对象到离屏画布
|
// 克隆对象到离屏画布
|
||||||
const clonedObjects = [];
|
const clonedObjects = [];
|
||||||
@@ -729,7 +750,11 @@ export const getObjectsBounds = (fabricObjects) => {
|
|||||||
* @param {Number} qualityMultiplier 质量倍数
|
* @param {Number} qualityMultiplier 质量倍数
|
||||||
* @returns {Promise<String>} 遮罩图像的DataURL
|
* @returns {Promise<String>} 遮罩图像的DataURL
|
||||||
*/
|
*/
|
||||||
const createMaskImageFromPath = async ({ clippingObject, selectionBounds, qualityMultiplier }) => {
|
const createMaskImageFromPath = async ({
|
||||||
|
clippingObject,
|
||||||
|
selectionBounds,
|
||||||
|
qualityMultiplier,
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
console.log("🎭 创建路径遮罩图像");
|
console.log("🎭 创建路径遮罩图像");
|
||||||
|
|
||||||
@@ -745,7 +770,11 @@ const createMaskImageFromPath = async ({ clippingObject, selectionBounds, qualit
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 克隆路径对象并处理描边转填充
|
// 克隆路径对象并处理描边转填充
|
||||||
const maskPath = await createSolidMaskPath(clippingObject, selectionBounds, qualityMultiplier);
|
const maskPath = await createSolidMaskPath(
|
||||||
|
clippingObject,
|
||||||
|
selectionBounds,
|
||||||
|
qualityMultiplier
|
||||||
|
);
|
||||||
|
|
||||||
// 添加路径到遮罩画布
|
// 添加路径到遮罩画布
|
||||||
maskCanvas.add(maskPath);
|
maskCanvas.add(maskPath);
|
||||||
@@ -776,7 +805,11 @@ const createMaskImageFromPath = async ({ clippingObject, selectionBounds, qualit
|
|||||||
* @param {Number} qualityMultiplier 质量倍数
|
* @param {Number} qualityMultiplier 质量倍数
|
||||||
* @returns {Promise<String>} 内容图像的DataURL
|
* @returns {Promise<String>} 内容图像的DataURL
|
||||||
*/
|
*/
|
||||||
const renderContentToImage = async ({ fabricObjects, selectionBounds, qualityMultiplier }) => {
|
const renderContentToImage = async ({
|
||||||
|
fabricObjects,
|
||||||
|
selectionBounds,
|
||||||
|
qualityMultiplier,
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
console.log("🖼️ 渲染内容图像");
|
console.log("🖼️ 渲染内容图像");
|
||||||
|
|
||||||
@@ -808,8 +841,11 @@ const renderContentToImage = async ({ fabricObjects, selectionBounds, qualityMul
|
|||||||
// 如果有裁剪路径,也需要调整裁剪路径
|
// 如果有裁剪路径,也需要调整裁剪路径
|
||||||
if (clonedObj.clipPath) {
|
if (clonedObj.clipPath) {
|
||||||
clonedObj.clipPath.set({
|
clonedObj.clipPath.set({
|
||||||
left: (clonedObj.clipPath.left - selectionBounds.left) * qualityMultiplier,
|
left:
|
||||||
top: (clonedObj.clipPath.top - selectionBounds.top) * qualityMultiplier,
|
(clonedObj.clipPath.left - selectionBounds.left) *
|
||||||
|
qualityMultiplier,
|
||||||
|
top:
|
||||||
|
(clonedObj.clipPath.top - selectionBounds.top) * qualityMultiplier,
|
||||||
scaleX: (clonedObj.clipPath.scaleX || 1) * qualityMultiplier,
|
scaleX: (clonedObj.clipPath.scaleX || 1) * qualityMultiplier,
|
||||||
scaleY: (clonedObj.clipPath.scaleY || 1) * qualityMultiplier,
|
scaleY: (clonedObj.clipPath.scaleY || 1) * qualityMultiplier,
|
||||||
});
|
});
|
||||||
@@ -943,7 +979,11 @@ const createAdvancedMaskImage = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 克隆路径对象并处理描边转填充
|
// 克隆路径对象并处理描边转填充
|
||||||
const maskPath = await createSolidMaskPath(clippingObject, selectionBounds, qualityMultiplier);
|
const maskPath = await createSolidMaskPath(
|
||||||
|
clippingObject,
|
||||||
|
selectionBounds,
|
||||||
|
qualityMultiplier
|
||||||
|
);
|
||||||
|
|
||||||
// 如果有羽化值,添加模糊效果
|
// 如果有羽化值,添加模糊效果
|
||||||
if (featherAmount > 0) {
|
if (featherAmount > 0) {
|
||||||
@@ -962,7 +1002,10 @@ const createAdvancedMaskImage = async ({
|
|||||||
|
|
||||||
// 如果有羽化,需要进行后处理
|
// 如果有羽化,需要进行后处理
|
||||||
if (featherAmount > 0) {
|
if (featherAmount > 0) {
|
||||||
return await applyCanvasBlur(maskCanvas, featherAmount * qualityMultiplier);
|
return await applyCanvasBlur(
|
||||||
|
maskCanvas,
|
||||||
|
featherAmount * qualityMultiplier
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成遮罩图像
|
// 生成遮罩图像
|
||||||
@@ -1038,7 +1081,11 @@ const applyCanvasBlur = async (canvas, blurAmount) => {
|
|||||||
* @param {Number} qualityMultiplier 质量倍数
|
* @param {Number} qualityMultiplier 质量倍数
|
||||||
* @returns {Promise<fabric.Object>} 处理后的遮罩路径对象
|
* @returns {Promise<fabric.Object>} 处理后的遮罩路径对象
|
||||||
*/
|
*/
|
||||||
const createSolidMaskPath = async (clippingObject, selectionBounds, qualityMultiplier) => {
|
const createSolidMaskPath = async (
|
||||||
|
clippingObject,
|
||||||
|
selectionBounds,
|
||||||
|
qualityMultiplier
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
console.log("🔧 创建实体遮罩路径,处理描边转填充");
|
console.log("🔧 创建实体遮罩路径,处理描边转填充");
|
||||||
|
|
||||||
@@ -1049,19 +1096,29 @@ const createSolidMaskPath = async (clippingObject, selectionBounds, qualityMulti
|
|||||||
const hasStroke = maskPath.stroke && maskPath.strokeWidth > 0;
|
const hasStroke = maskPath.stroke && maskPath.strokeWidth > 0;
|
||||||
|
|
||||||
if (hasStroke) {
|
if (hasStroke) {
|
||||||
console.log(`📏 检测到描边: ${maskPath.stroke}, 宽度: ${maskPath.strokeWidth}`);
|
console.log(
|
||||||
|
`📏 检测到描边: ${maskPath.stroke}, 宽度: ${maskPath.strokeWidth}`
|
||||||
|
);
|
||||||
|
|
||||||
// 对于有描边的路径,我们需要更精确的处理
|
// 对于有描边的路径,我们需要更精确的处理
|
||||||
const strokeWidth = maskPath.strokeWidth;
|
const strokeWidth = maskPath.strokeWidth;
|
||||||
|
|
||||||
// 方法1: 如果是简单的几何形状(矩形、圆形等),可以通过调整尺寸来补偿描边
|
// 方法1: 如果是简单的几何形状(矩形、圆形等),可以通过调整尺寸来补偿描边
|
||||||
if (maskPath.type === "rect" || maskPath.type === "circle" || maskPath.type === "ellipse") {
|
if (
|
||||||
|
maskPath.type === "rect" ||
|
||||||
|
maskPath.type === "circle" ||
|
||||||
|
maskPath.type === "ellipse"
|
||||||
|
) {
|
||||||
// 对于矩形和椭圆,增加宽高来包含描边
|
// 对于矩形和椭圆,增加宽高来包含描边
|
||||||
const strokeOffset = strokeWidth;
|
const strokeOffset = strokeWidth;
|
||||||
|
|
||||||
maskPath.set({
|
maskPath.set({
|
||||||
left: (maskPath.left - selectionBounds.left - strokeOffset / 2) * qualityMultiplier,
|
left:
|
||||||
top: (maskPath.top - selectionBounds.top - strokeOffset / 2) * qualityMultiplier,
|
(maskPath.left - selectionBounds.left - strokeOffset / 2) *
|
||||||
|
qualityMultiplier,
|
||||||
|
top:
|
||||||
|
(maskPath.top - selectionBounds.top - strokeOffset / 2) *
|
||||||
|
qualityMultiplier,
|
||||||
scaleX: (maskPath.scaleX || 1) * qualityMultiplier,
|
scaleX: (maskPath.scaleX || 1) * qualityMultiplier,
|
||||||
scaleY: (maskPath.scaleY || 1) * qualityMultiplier,
|
scaleY: (maskPath.scaleY || 1) * qualityMultiplier,
|
||||||
width: (maskPath.width || 0) + strokeOffset,
|
width: (maskPath.width || 0) + strokeOffset,
|
||||||
@@ -1080,8 +1137,12 @@ const createSolidMaskPath = async (clippingObject, selectionBounds, qualityMulti
|
|||||||
const strokeOffset = strokeWidth / 2;
|
const strokeOffset = strokeWidth / 2;
|
||||||
|
|
||||||
maskPath.set({
|
maskPath.set({
|
||||||
left: (maskPath.left - selectionBounds.left - strokeOffset) * qualityMultiplier,
|
left:
|
||||||
top: (maskPath.top - selectionBounds.top - strokeOffset) * qualityMultiplier,
|
(maskPath.left - selectionBounds.left - strokeOffset) *
|
||||||
|
qualityMultiplier,
|
||||||
|
top:
|
||||||
|
(maskPath.top - selectionBounds.top - strokeOffset) *
|
||||||
|
qualityMultiplier,
|
||||||
scaleX: (maskPath.scaleX || 1) * qualityMultiplier * expandRatio,
|
scaleX: (maskPath.scaleX || 1) * qualityMultiplier * expandRatio,
|
||||||
scaleY: (maskPath.scaleY || 1) * qualityMultiplier * expandRatio,
|
scaleY: (maskPath.scaleY || 1) * qualityMultiplier * expandRatio,
|
||||||
fill: "#ffffff",
|
fill: "#ffffff",
|
||||||
|
|||||||
Reference in New Issue
Block a user