mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 02:26:21 +08:00
init: the first public commit for AFFiNE
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
import { FC, useState, useEffect, useRef } from 'react';
|
||||
import { CreateView } from '@toeverything/framework/virgo';
|
||||
import { Upload } from '../../components/upload/upload';
|
||||
import { services } from '@toeverything/datasource/db-service';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import DeleteSweepOutlinedIcon from '@mui/icons-material/DeleteSweepOutlined';
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import { Image as SourceView } from '../../components/ImageView';
|
||||
import {
|
||||
useOnSelect,
|
||||
useRecastBlockScene,
|
||||
WrapperWithPendantAndDragDrop,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import './styles.css';
|
||||
import { SCENE_CONFIG } from '../group/config';
|
||||
const MESSAGES = {
|
||||
ADD_AN_IMAGE: 'Add an image',
|
||||
};
|
||||
interface ImageView extends CreateView {
|
||||
imageFile?: File;
|
||||
}
|
||||
const ImageBlock = styled('div')({
|
||||
position: 'relative',
|
||||
marginTop: '10px',
|
||||
'.option': {
|
||||
position: 'absolute',
|
||||
background: 'rgba(0,0,0,.4)',
|
||||
height: '30px',
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
top: '-30px',
|
||||
color: '#fff',
|
||||
padding: '4px',
|
||||
transition: 'top .5s',
|
||||
},
|
||||
'&:hover': {
|
||||
'.option': {
|
||||
top: '0px',
|
||||
},
|
||||
},
|
||||
'.img': {
|
||||
width: '100%',
|
||||
},
|
||||
'.progress': {},
|
||||
});
|
||||
const KanbanImageContainer = styled('div')<{ isSelected: boolean }>(
|
||||
({ theme, isSelected }) => {
|
||||
return {
|
||||
display: 'flex',
|
||||
borderRadius: theme.affine.shape.xsBorderRadius,
|
||||
background: isSelected ? 'rgba(152, 172, 189, 0.1)' : 'transparent',
|
||||
padding: '8px',
|
||||
// border: `2px solid ${
|
||||
// isSelected ? theme.affine.palette.primary : '#e0e0e0'
|
||||
// }`,
|
||||
};
|
||||
}
|
||||
);
|
||||
export const ImageView: FC<ImageView> = ({ block, editor }) => {
|
||||
const workspace = editor.workspace;
|
||||
const [imgUrl, set_image_url] = useState<string>();
|
||||
const [imgWidth, setImgWidth] = useState<number>(0);
|
||||
const [ratio, set_ratio] = useState<number>(0);
|
||||
const resize_box = useRef(null);
|
||||
const { scene } = useRecastBlockScene();
|
||||
const [isSelect, setIsSelect] = useState<boolean>();
|
||||
useOnSelect(block.id, (isSelect: boolean) => {
|
||||
setIsSelect(isSelect);
|
||||
});
|
||||
|
||||
const img_load = (url: string) => {
|
||||
let boxWidth = resize_box.current.offsetWidth;
|
||||
|
||||
const imageStyle = block.getProperty('image_style');
|
||||
if (imageStyle?.width) {
|
||||
set_ratio(imageStyle.width / imageStyle.height);
|
||||
setImgWidth(imageStyle.width);
|
||||
set_image_url(url);
|
||||
return;
|
||||
}
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
// load complete execution
|
||||
img.onload = async () => {
|
||||
const img_radio = img.width / img.height;
|
||||
|
||||
if (img.width >= boxWidth - 20) {
|
||||
img.width = boxWidth - 20;
|
||||
}
|
||||
img.height = img.width / img_radio;
|
||||
|
||||
block.setProperty('image_style', {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
});
|
||||
setImgWidth(img.width);
|
||||
set_image_url(url);
|
||||
set_ratio(img_radio);
|
||||
};
|
||||
img.onerror = e => {
|
||||
console.log(e);
|
||||
};
|
||||
};
|
||||
useEffect(() => {
|
||||
const style = window.getComputedStyle(block?.dom);
|
||||
const image_info = block.getProperty('image');
|
||||
const image_block_id = image_info?.value;
|
||||
const image_info_url = image_info?.url;
|
||||
|
||||
if (image_info_url) {
|
||||
img_load(image_info_url);
|
||||
return;
|
||||
}
|
||||
if (image_block_id) {
|
||||
services.api.file.get(image_block_id, workspace).then(file_info => {
|
||||
img_load(file_info.url);
|
||||
});
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
const down_ref = useRef(null);
|
||||
const on_file_change = async (file: File) => {
|
||||
const result = await services.api.file.create({
|
||||
workspace: editor.workspace,
|
||||
file: file,
|
||||
});
|
||||
img_load(result.url);
|
||||
block.setProperty('image', {
|
||||
value: result.id,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
});
|
||||
};
|
||||
const delete_file = () => {
|
||||
block.remove();
|
||||
};
|
||||
const sava_link = (link: string) => {
|
||||
img_load(link);
|
||||
block.setProperty('image', {
|
||||
value: '',
|
||||
url: link,
|
||||
name: link,
|
||||
size: 0,
|
||||
type: 'link',
|
||||
});
|
||||
};
|
||||
const handle_click = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
//TODO clear active selection
|
||||
// document.getElementsByTagName('body')[0].click();
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopPropagation();
|
||||
editor.selectionManager.setSelectedNodesIds([block.id]);
|
||||
editor.selectionManager.activeNodeByNodeId(block.id);
|
||||
};
|
||||
const down_file = () => {
|
||||
if (down_ref) {
|
||||
down_ref.current.click();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
||||
<ImageBlock>
|
||||
<div ref={resize_box}>
|
||||
{imgUrl ? (
|
||||
<div
|
||||
onClick={handle_click}
|
||||
onMouseDown={e => {
|
||||
// e.nativeEvent.stopPropagation();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{scene === SCENE_CONFIG.PAGE ? (
|
||||
<SourceView
|
||||
block={block}
|
||||
viewStyle={{
|
||||
width: imgWidth,
|
||||
maxWidth:
|
||||
resize_box.current.offsetWidth - 20,
|
||||
minWidth: 32,
|
||||
ratio: ratio,
|
||||
}}
|
||||
isSelected={isSelect}
|
||||
link={imgUrl}
|
||||
/>
|
||||
) : (
|
||||
<KanbanImageContainer isSelected={isSelect}>
|
||||
<img width={'100%'} src={imgUrl} alt="" />
|
||||
</KanbanImageContainer>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
// </ResizableBox>
|
||||
<Upload
|
||||
firstCreate={block.firstCreateFlag}
|
||||
uploadType={'image'}
|
||||
fileChange={on_file_change}
|
||||
deleteFile={delete_file}
|
||||
savaLink={sava_link}
|
||||
isSelected={isSelect}
|
||||
defaultAddBtnText={MESSAGES.ADD_AN_IMAGE}
|
||||
accept={
|
||||
'image/gif,image/jpeg,image/jpg,image/png,image/svg'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* <div>
|
||||
<DownloadIcon
|
||||
onClick={down_file}
|
||||
className="delete-icon"
|
||||
fontSize="small"
|
||||
sx={{
|
||||
color: '#000',
|
||||
cursor: 'pointer',
|
||||
marginRight: '10px'
|
||||
}}
|
||||
></DownloadIcon>
|
||||
<DeleteSweepOutlinedIcon
|
||||
onClick={delete_file}
|
||||
className="delete-icon"
|
||||
fontSize="small"
|
||||
sx={{ color: '#000', cursor: 'pointer' }}
|
||||
></DeleteSweepOutlinedIcon>
|
||||
<a
|
||||
href={imgUrl}
|
||||
ref={down_ref}
|
||||
style={{ display: 'none' }}
|
||||
download="text.png"
|
||||
></a>
|
||||
</div> */}
|
||||
</div>
|
||||
</ImageBlock>
|
||||
</WrapperWithPendantAndDragDrop>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
SelectBlock,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { ImageView } from './ImageView';
|
||||
|
||||
export class ImageBlock extends BaseView {
|
||||
public override selectable = true;
|
||||
public override activatable = false;
|
||||
type = Protocol.Block.Type.image;
|
||||
View = ImageView;
|
||||
|
||||
// TODO: needs to download the image and then upload it to get a new link and then assign it
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'IMG') {
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
value: '',
|
||||
url: el.getAttribute('src'),
|
||||
name: el.getAttribute('src'),
|
||||
size: 0,
|
||||
type: 'link',
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
const text = block.getProperty('text');
|
||||
const content = '';
|
||||
if (text) {
|
||||
text.value.map(text => `<span>${text}</span>`).join('');
|
||||
}
|
||||
// TODO: child
|
||||
return `<p><img src=${content}></p>`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
.react-resizable {
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
height: auto !important;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.react-resizable-handle {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #3e6fdb;
|
||||
background-repeat: no-repeat;
|
||||
background-origin: content-box;
|
||||
box-sizing: border-box;
|
||||
background-position: bottom right;
|
||||
border-radius: 10px;
|
||||
padding: 0 3px 3px 0;
|
||||
}
|
||||
.react-resizable-handle-sw {
|
||||
bottom: -6px;
|
||||
left: -6px;
|
||||
cursor: sw-resize;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.react-resizable-handle-se {
|
||||
bottom: -6px;
|
||||
right: -6px;
|
||||
cursor: se-resize;
|
||||
}
|
||||
.react-resizable-handle-nw {
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
cursor: nw-resize;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.react-resizable-handle-ne {
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
cursor: ne-resize;
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
.react-resizable-handle-w,
|
||||
.react-resizable-handle-e {
|
||||
top: 50%;
|
||||
margin-top: -10px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.react-resizable-handle-w {
|
||||
left: 0;
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
.react-resizable-handle-e {
|
||||
right: 0;
|
||||
transform: rotate(315deg);
|
||||
}
|
||||
.react-resizable-handle-n,
|
||||
.react-resizable-handle-s {
|
||||
left: 50%;
|
||||
margin-left: -10px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
.react-resizable-handle-n {
|
||||
top: 0;
|
||||
transform: rotate(225deg);
|
||||
}
|
||||
.react-resizable-handle-s {
|
||||
bottom: 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
Reference in New Issue
Block a user