feat: add background picture

This commit is contained in:
gazedreamily 2023-03-24 15:52:02 +08:00 committed by Qaplagzy
parent ace1b33138
commit 688ad4457d
11 changed files with 314 additions and 38 deletions

View File

@ -652,7 +652,7 @@ export class Canvas {
const colEndX = this.sheetCtx.visibledatacolumn[colEnd];
// 表格canvas 初始化处理
renderCtx.fillStyle = "#ffffff";
renderCtx.fillStyle = "rgba(255,255,255,0)";
renderCtx.fillRect(
offsetLeft - 1,
offsetTop - 1,
@ -1641,7 +1641,7 @@ export class Canvas {
// }
if (!fillStyle) {
renderCtx.fillStyle = "#FFFFFF";
renderCtx.fillStyle = "rgba(255,255,255,0)";
} else {
renderCtx.fillStyle = fillStyle;
}
@ -1849,7 +1849,7 @@ export class Canvas {
fillStyle = checksCF.cellColor;
}
if (!fillStyle) {
renderCtx.fillStyle = "#FFFFFF";
renderCtx.fillStyle = "rgba(255,255,255,0)";
} else {
renderCtx.fillStyle = fillStyle;
}

View File

@ -10971,6 +10971,12 @@ export default {
screenshot: "Screenshot",
splitColumn: "Split text",
insertImage: "Insert image",
setBackgroundPic: "Set background picture",
unsetBackgroundPic: "Clear background picture",
backgroundSettings: "Background settings",
backgroundRepeat: "Background repeat",
backgroundNoRepeat: "Background no-repeat",
backgroundStretch: "Background stretch",
insertLink: "Insert link",
dataVerification: "Data verification",
protection: "Protect the sheet",

View File

@ -10954,6 +10954,12 @@ export default {
screenshot: "截图",
splitColumn: "分列",
insertImage: "插入图片",
setBackgroundPic: "设置背景",
unsetBackgroundPic: "清除背景",
backgroundSettings: "背景设置",
backgroundRepeat: "背景平铺",
backgroundNoRepeat: "背景不重复",
backgroundStretch: "背景拉伸填充",
insertLink: "插入链接",
dataVerification: "数据验证",
protection: "保护工作表内容",

View File

@ -57,6 +57,13 @@ export function showImgChooser() {
if (chooser) chooser.click();
}
export function showBgChooser() {
const chooser = document.getElementById(
"fortune-bg-upload"
) as HTMLInputElement;
if (chooser) chooser.click();
}
export function saveImage(ctx: Context) {
const index = getSheetIndex(ctx, ctx.currentSheetId);
if (index == null) return;
@ -284,3 +291,46 @@ export function onImageResizeEnd(ctx: Context, globalCache: GlobalCache) {
}
}
}
export function setBackgroundPic(
ctx: Context,
image: HTMLImageElement,
repeat: "repeat" | "no-repeat" | "stretch" = "no-repeat"
) {
const index = getSheetIndex(ctx, ctx.currentSheetId);
if (index == null || !image.src) return;
const file = ctx.luckysheetfile[index];
if (!file.backgroundPic) file.backgroundPic = {};
file.backgroundPic.src = image.src;
if (repeat === "repeat" || repeat === "no-repeat") {
file.backgroundPic.repeat = repeat;
} else {
file.backgroundPic.repeat = "no-repeat";
file.backgroundPic.backgroundSize = "100%";
}
}
export function clearBackground(ctx: Context) {
const index = getSheetIndex(ctx, ctx.currentSheetId);
if (index == null) return;
const file = ctx.luckysheetfile[index];
if (file.backgroundPic) {
file.backgroundPic = undefined;
}
}
export function setBackgroundPicRepeat(
ctx: Context,
repeat: string = "no-repeat"
) {
const index = getSheetIndex(ctx, ctx.currentSheetId);
if (index == null) return;
const file = ctx.luckysheetfile[index];
if (!file.backgroundPic) file.backgroundPic = {};
if (repeat === "repeat" || repeat === "no-repeat") {
file.backgroundPic.repeat = repeat;
} else if (repeat === "stretch") {
file.backgroundPic.repeat = "no-repeat";
file.backgroundPic.backgroundSize = "100%";
}
}

View File

@ -156,6 +156,11 @@ export type Sheet = {
type: "row" | "column" | "both" | "rangeRow" | "rangeColumn" | "rangeBoth";
range?: { row_focus: number; column_focus: number };
};
backgroundPic?: {
src?: string;
repeat?: "repeat" | "no-repeat";
backgroundSize?: string;
};
};
export type CommentBox = {

View File

@ -3,6 +3,7 @@
flex-direction: row;
height: 28px;
border-bottom: 1px solid #d4d4d4;
background-color: white;
}
.fortune-fx-icon {

View File

@ -304,7 +304,7 @@ const InputBox: React.FC = () => {
? {
left: firstSelection.left,
top: firstSelection.top,
zIndex: _.isEmpty(context.luckysheetCellUpdate) ? -1 : 19,
zIndex: _.isEmpty(context.luckysheetCellUpdate) ? -2 : 19,
display: "block",
}
: { left: -10000, top: -10000, display: "block" }

View File

@ -107,6 +107,8 @@
.fortune-toolbar-select-option {
font-size: 12px;
height: 24px;
line-height: 24px;
min-width: 60px;
padding: 8px 12px;
cursor: pointer;
@ -183,4 +185,29 @@
user-select: none;
font-family: Arial;
line-height: 100%;
}
}
.set-background-sub-menu {
position: absolute;
top: -8px;
box-shadow: 0 2px 4px rgb(0 0 0 / 20%);
background: #fff;
border: 1px solid rgba(0, 0, 0, .2);
cursor: default;
font-size: 12px;
z-index: 1004;
box-sizing: border-box;
user-select: none;
outline: none;
}
.set-background-item {
display: flex;
justify-content: center;
padding: 6px 18px;
z-index: 1005;
}
.set-background-item:hover {
background: #efefef;
}

View File

@ -34,6 +34,10 @@ import {
createFilter,
clearFilter,
applyLocation,
setBackgroundPic,
showBgChooser,
clearBackground,
setBackgroundPicRepeat,
} from "@fortune-sheet/core";
import _ from "lodash";
import WorkbookContext from "../../context";
@ -90,6 +94,62 @@ const Toolbar: React.FC<{
} = locale(context);
const sheetWidth = context.luckysheetTableContentHW[0];
const showSubMenu = useCallback(
(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const target = e.target as HTMLDivElement;
const menuItem =
target.className === "fortune-toolbar-menu-line"
? target.parentElement!
: target;
const menuItemRect = menuItem.getBoundingClientRect();
const workbookContainerRect =
refs.workbookContainer.current!.getBoundingClientRect();
const subMenu = menuItem.querySelector(
".set-background-sub-menu"
) as HTMLDivElement;
if (_.isNil(subMenu)) return;
const menuItemStyle = window.getComputedStyle(menuItem);
const menuItemPaddingRight = parseFloat(
menuItemStyle.getPropertyValue("padding-right").replace("px", "")
);
if (
workbookContainerRect.right - menuItemRect.right <
parseFloat(subMenu.style.width.replace("px", ""))
) {
subMenu.style.display = "block";
subMenu.style.right = `${menuItemRect.width - menuItemPaddingRight}px`;
} else {
subMenu.style.display = "block";
subMenu.style.right = `${-(
parseFloat(subMenu.style.width.replace("px", "")) +
menuItemPaddingRight
)}px`;
}
},
[refs.workbookContainer]
);
const hideSubMenu = useCallback(
(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const target = e.target as HTMLDivElement;
if (target.className === "set-background-sub-menu") {
target.style.display = "none";
return;
}
const subMenu = (
target.className === "condition-format-item"
? target.parentElement
: target.querySelector(".set-background-sub-menu")
) as HTMLDivElement;
if (_.isNil(subMenu)) return;
subMenu.style.display = "none";
},
[]
);
// rerenders the entire toolbar and trigger recalculation of item locations
useEffect(() => {
setToolbarWrapIndex(-1);
@ -725,41 +785,140 @@ const Toolbar: React.FC<{
}
if (name === "image") {
return (
<Button
iconId={name}
tooltip={toolbar.insertImage}
key={name}
onClick={() => {
if (context.allowEdit === false) return;
showImgChooser();
}}
>
<input
id="fortune-img-upload"
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={(e) => {
const file = e.currentTarget.files?.[0];
if (!file) return;
<Combo iconId={name} tooltip={toolbar.insertImage} key={name}>
{(setOpen) => (
<Select style={{ overflow: "visible" }}>
<Option
key="upload-picture"
onClick={() => {
if (context.allowEdit === false) return;
showImgChooser();
}}
>
<div className="fortune-toolbar-menu-line">
{toolbar.insertImage}
<input
id="fortune-img-upload"
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={(e) => {
const file = e.currentTarget.files?.[0];
if (!file) return;
const render = new FileReader();
render.readAsDataURL(file);
render.onload = (event) => {
if (event.target == null) return;
const src = event.target?.result;
const image = new Image();
image.onload = () => {
const render = new FileReader();
render.readAsDataURL(file);
render.onload = (event) => {
if (event.target == null) return;
const src = event.target?.result;
const image = new Image();
image.onload = () => {
setContext((draftCtx) => {
insertImage(draftCtx, image);
});
};
image.src = src as string;
};
e.currentTarget.value = "";
setOpen(false);
}}
/>
</div>
</Option>
<MenuDivider key="divider" />
<Option
key="set-background-picture"
onClick={() => {
if (context.allowEdit === false) return;
showBgChooser();
}}
>
<div className="fortune-toolbar-menu-line">
{toolbar.setBackgroundPic}
<input
id="fortune-bg-upload"
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={(e) => {
const file = e.currentTarget.files?.[0];
if (!file) return;
const render = new FileReader();
render.readAsDataURL(file);
render.onload = (event) => {
if (event.target == null) return;
const src = event.target?.result;
const image = new Image();
image.onload = () => {
setContext((draftCtx) => {
setBackgroundPic(draftCtx, image);
});
};
image.src = src as string;
};
e.currentTarget.value = "";
setOpen(false);
}}
/>
</div>
</Option>
<Option
key="clear-background-picture"
onClick={() => {
if (context.allowEdit === false) return;
setContext((draftCtx) => {
insertImage(draftCtx, image);
clearBackground(draftCtx);
});
};
image.src = src as string;
};
e.currentTarget.value = "";
}}
/>
</Button>
setOpen(false);
}}
>
<div className="fortune-toolbar-menu-line">
{toolbar.unsetBackgroundPic}
</div>
</Option>
<Option
key="backgroundSetting"
onMouseEnter={showSubMenu}
onMouseLeave={hideSubMenu}
>
<div className="fortune-toolbar-menu-line">
{toolbar.backgroundSettings}
<SVGIcon name="rightArrow" width={18} />
<div
className="set-background-sub-menu"
style={{
display: "none",
width: 150,
}}
>
{[
{ text: toolbar.backgroundRepeat, value: "repeat" },
{
text: toolbar.backgroundNoRepeat,
value: "no-repeat",
},
{ text: toolbar.backgroundStretch, value: "stretch" },
].map((v) => (
<div
className="set-background-item"
key={v.text}
onClick={() => {
setContext((draftCtx) => {
setBackgroundPicRepeat(draftCtx, v.value);
});
setOpen(false);
}}
>
{v.text}
</div>
))}
</div>
</div>
</Option>
</Select>
)}
</Combo>
);
}
if (name === "comment") {
@ -1318,6 +1477,9 @@ const Toolbar: React.FC<{
context.allowEdit,
comment,
fontarray,
hideSubMenu,
showSubMenu,
refs.canvas,
]
);

View File

@ -6,7 +6,6 @@
padding: 0;
flex-direction: column;
font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Heiti SC", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
background-color: white;
}
.fortune-workarea {
@ -28,3 +27,11 @@
align-items: center;
justify-content: center;
}
.sheet-background {
height: 100%;
width: 100%;
position: absolute;
z-index: -1;
background-color: white;
}

View File

@ -678,6 +678,18 @@ const Workbook = React.forwardRef<WorkbookInstance, Settings & AdditionalProps>(
)}
{mergedSettings.showFormulaBar && <FxEditor />}
</div>
<div
className="sheet-background"
style={{
backgroundImage: `url(${sheet.backgroundPic?.src})`,
backgroundSize: sheet.backgroundPic?.backgroundSize,
backgroundRepeat: sheet.backgroundPic?.repeat,
top: cellArea.current?.getBoundingClientRect().y,
left: cellArea.current?.getBoundingClientRect().x,
height: cellArea.current?.getBoundingClientRect().height,
width: cellArea.current?.getBoundingClientRect().width,
}}
/>
<Sheet sheet={sheet} />
{mergedSettings.showSheetTabs && <SheetTab />}
<ContextMenu />