mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-12-28 09:51:06 -06:00
Merge pull request #1123 from psjostrom/SDA-2533-AnnotateP4
feat: SDA-2705 Add possibility for panning
This commit is contained in:
commit
1a30bd98bc
@ -1,29 +1,50 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<AnnotateArea/> should render correctly 1`] = `
|
||||
<svg
|
||||
data-testid="annotate-area"
|
||||
height={800}
|
||||
id="annotate-area"
|
||||
onMouseDown={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseMove={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
style={
|
||||
<AnnotateArea
|
||||
annotateAreaDimensions={
|
||||
Object {
|
||||
"cursor": "crosshair",
|
||||
"height": 800,
|
||||
"width": 800,
|
||||
}
|
||||
}
|
||||
width={800}
|
||||
chosenTool="PEN"
|
||||
highlightColor="rgba(233, 0, 0, 0.64)"
|
||||
imageDimensions={
|
||||
Object {
|
||||
"height": 800,
|
||||
"width": 800,
|
||||
}
|
||||
}
|
||||
onChange={[MockFunction]}
|
||||
paths={Array []}
|
||||
penColor="rgba(38, 196, 58, 1)"
|
||||
screenSnippetPath="very-nice-path"
|
||||
>
|
||||
<image
|
||||
className="SnippetImage"
|
||||
height={800}
|
||||
id="screenSnippet"
|
||||
width={800}
|
||||
x={0}
|
||||
xlinkHref="very-nice-path"
|
||||
y={0}
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
id="annotate-wrapper"
|
||||
style={
|
||||
Object {
|
||||
"height": 800,
|
||||
"width": 800,
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
data-testid="annotate-area"
|
||||
height={800}
|
||||
id="annotate-area"
|
||||
onMouseDown={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseMove={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"cursor": "crosshair",
|
||||
}
|
||||
}
|
||||
width={800}
|
||||
/>
|
||||
</div>
|
||||
</AnnotateArea>
|
||||
`;
|
||||
|
@ -62,6 +62,13 @@ exports[`Snipping Tool should render correctly 1`] = `
|
||||
className="imageContainer"
|
||||
>
|
||||
<AnnotateArea
|
||||
annotateAreaDimensions={
|
||||
Object {
|
||||
"height": 600,
|
||||
"width": 800,
|
||||
}
|
||||
}
|
||||
backgroundImagePath=""
|
||||
chosenTool="PEN"
|
||||
data-testid="annotate-component"
|
||||
highlightColor="rgba(0, 142, 255, 0.64)"
|
||||
@ -74,7 +81,6 @@ exports[`Snipping Tool should render correctly 1`] = `
|
||||
onChange={[Function]}
|
||||
paths={Array []}
|
||||
penColor="rgba(0, 142, 255, 1)"
|
||||
screenSnippetPath="Screen-Snippet"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import { mount } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
import AnnotateArea from '../src/renderer/components/annotate-area';
|
||||
import { Tool } from '../src/renderer/components/snipping-tool';
|
||||
@ -9,6 +9,7 @@ const defaultProps = {
|
||||
penColor: 'rgba(38, 196, 58, 1)',
|
||||
onChange: jest.fn(),
|
||||
imageDimensions: { width: 800, height: 800 },
|
||||
annotateAreaDimensions: { width: 800, height: 800 },
|
||||
chosenTool: Tool.pen,
|
||||
screenSnippetPath: 'very-nice-path',
|
||||
};
|
||||
@ -19,25 +20,27 @@ afterEach(() => {
|
||||
|
||||
describe('<AnnotateArea/>', () => {
|
||||
it('should render correctly', () => {
|
||||
const wrapper = shallow(<AnnotateArea {...defaultProps} />);
|
||||
const wrapper = mount(<AnnotateArea {...defaultProps} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should call onChange when drawn on annotate area', () => {
|
||||
const wrapper = shallow(<AnnotateArea {...defaultProps} />);
|
||||
wrapper.simulate('mousedown', { pageX: 2, pageY: 49 });
|
||||
wrapper.simulate('mouseup');
|
||||
const wrapper = mount(<AnnotateArea {...defaultProps} />);
|
||||
const area = wrapper.find('[data-testid="annotate-area"]');
|
||||
area.simulate('mousedown', { pageX: 2, pageY: 49 });
|
||||
area.simulate('mouseup');
|
||||
expect(defaultProps.onChange).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should call onChange with correct pen props if drawn drawn on annotate area with pen', () => {
|
||||
const wrapper = shallow(<AnnotateArea {...defaultProps} />);
|
||||
wrapper.simulate('mousedown', { pageX: 2, pageY: 49 });
|
||||
wrapper.simulate('mouseup');
|
||||
const wrapper = mount(<AnnotateArea {...defaultProps} />);
|
||||
const area = wrapper.find('[data-testid="annotate-area"]');
|
||||
area.simulate('mousedown', { pageX: 2, pageY: 49 });
|
||||
area.simulate('mouseup');
|
||||
expect(defaultProps.onChange).toHaveBeenCalledWith([{
|
||||
color: 'rgba(38, 196, 58, 1)',
|
||||
key: 'path0',
|
||||
points: [{ x: 2, y: 1 }],
|
||||
points: [{ x: 0, y: 0 }],
|
||||
shouldShow: true,
|
||||
strokeWidth: 5,
|
||||
}]);
|
||||
@ -50,12 +53,14 @@ describe('<AnnotateArea/>', () => {
|
||||
penColor: 'rgba(38, 196, 58, 1)',
|
||||
onChange: jest.fn(),
|
||||
imageDimensions: { width: 800, height: 800 },
|
||||
annotateAreaDimensions: { width: 800, height: 800 },
|
||||
chosenTool: Tool.highlight,
|
||||
screenSnippetPath: 'very-nice-path',
|
||||
};
|
||||
const wrapper = shallow(<AnnotateArea {...highlightProps} />);
|
||||
wrapper.simulate('mousedown', { pageX: 2, pageY: 49 });
|
||||
wrapper.simulate('mouseup');
|
||||
const wrapper = mount(<AnnotateArea {...highlightProps} />);
|
||||
const area = wrapper.find('[data-testid="annotate-area"]');
|
||||
area.simulate('mousedown', { pageX: 2, pageY: 49 });
|
||||
area.simulate('mouseup');
|
||||
expect(highlightProps.onChange).toHaveBeenCalledWith([{
|
||||
color: 'rgba(233, 0, 0, 0.64)',
|
||||
key: 'path0',
|
||||
@ -78,15 +83,16 @@ describe('<AnnotateArea/>', () => {
|
||||
penColor: 'rgba(38, 196, 58, 1)',
|
||||
onChange: jest.fn(),
|
||||
imageDimensions: { width: 800, height: 800 },
|
||||
annotateAreaDimensions: { width: 800, height: 800 },
|
||||
chosenTool: Tool.highlight,
|
||||
screenSnippetPath: 'very-nice-path',
|
||||
};
|
||||
const wrapper = shallow(<AnnotateArea {...pathProps} />);
|
||||
const wrapper = mount(<AnnotateArea {...pathProps} />);
|
||||
expect(wrapper.find('[data-testid="path0"]').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should not render any path if no path is provided in props', () => {
|
||||
const wrapper = shallow(<AnnotateArea {...defaultProps} />);
|
||||
const wrapper = mount(<AnnotateArea {...defaultProps} />);
|
||||
expect(wrapper.find('[data-testid="path0"]').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
@ -103,10 +109,11 @@ describe('<AnnotateArea/>', () => {
|
||||
penColor: 'rgba(38, 196, 58, 1)',
|
||||
onChange: jest.fn(),
|
||||
imageDimensions: { width: 800, height: 800 },
|
||||
annotateAreaDimensions: { width: 800, height: 800 },
|
||||
chosenTool: Tool.eraser,
|
||||
screenSnippetPath: 'very-nice-path',
|
||||
};
|
||||
const wrapper = shallow(<AnnotateArea {...pathProps} />);
|
||||
const wrapper = mount(<AnnotateArea {...pathProps} />);
|
||||
const path = wrapper.find('[data-testid="path0"]');
|
||||
path.simulate('click');
|
||||
expect(pathProps.onChange).toHaveBeenCalledWith([{
|
||||
|
@ -13,7 +13,7 @@ describe('Snipping Tool', () => {
|
||||
|
||||
it('should set up a "once" listener for snipping-tool-data event on mounting', () => {
|
||||
const spy = jest.spyOn(ipcRenderer, 'once');
|
||||
shallow(React.createElement(SnippingTool));
|
||||
mount(React.createElement(SnippingTool));
|
||||
expect(spy).toBeCalledWith('snipping-tool-data', expect.any(Function));
|
||||
});
|
||||
|
||||
@ -61,7 +61,7 @@ describe('Snipping Tool', () => {
|
||||
await waitForPromisesToResolve();
|
||||
expect(spy).toBeCalledWith('upload-snippet', {
|
||||
base64PngData: 'NO CANVAS',
|
||||
screenSnippetPath: 'Screen-Snippet',
|
||||
screenSnippetPath: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -963,8 +963,10 @@ export class WindowHandler {
|
||||
const MIN_WIDTH = 320;
|
||||
const CONTAINER_HEIGHT = 175;
|
||||
const OS_PADDING = 25;
|
||||
let height: number = dimensions?.height || 0;
|
||||
let width: number = dimensions?.width || 0;
|
||||
const snippetImageHeight = dimensions?.height || 0;
|
||||
const snippetImageWidth = dimensions?.width || 0;
|
||||
let annotateAreaHeight = snippetImageHeight;
|
||||
let annotateAreaWidth = snippetImageWidth;
|
||||
|
||||
if (parentWindow) {
|
||||
const { bounds: { height: sHeight, width: sWidth } } = electron.screen.getDisplayMatching(parentWindow.getBounds());
|
||||
@ -972,24 +974,24 @@ export class WindowHandler {
|
||||
// This calculation is to make sure the
|
||||
// snippet window does not cover the entire screen
|
||||
const maxScreenHeight: number = calculatePercentage(sHeight, 90);
|
||||
if (height > maxScreenHeight) {
|
||||
height = maxScreenHeight;
|
||||
if (annotateAreaHeight > maxScreenHeight) {
|
||||
annotateAreaHeight = maxScreenHeight;
|
||||
}
|
||||
const maxScreenWidth: number = calculatePercentage(sWidth, 90);
|
||||
if (width > maxScreenWidth) {
|
||||
width = maxScreenWidth;
|
||||
if (annotateAreaWidth > maxScreenWidth) {
|
||||
annotateAreaWidth = maxScreenWidth;
|
||||
}
|
||||
|
||||
// decrease image height when there is no space for the container window
|
||||
if ((sHeight - height) < CONTAINER_HEIGHT) {
|
||||
height -= CONTAINER_HEIGHT;
|
||||
if ((sHeight - annotateAreaHeight) < CONTAINER_HEIGHT) {
|
||||
annotateAreaHeight -= CONTAINER_HEIGHT;
|
||||
}
|
||||
}
|
||||
const windowHeight = height + CONTAINER_HEIGHT - OS_PADDING;
|
||||
const windowHeight = annotateAreaHeight + CONTAINER_HEIGHT - OS_PADDING;
|
||||
|
||||
const opts: ICustomBrowserWindowConstructorOpts = this.getWindowOpts(
|
||||
{
|
||||
width,
|
||||
width: annotateAreaWidth,
|
||||
height: windowHeight,
|
||||
minHeight: MIN_HEIGHT,
|
||||
minWidth: MIN_WIDTH,
|
||||
@ -1022,8 +1024,10 @@ export class WindowHandler {
|
||||
this.snippingToolWindow.webContents.once('did-finish-load', async () => {
|
||||
const snippingToolInfo = {
|
||||
snipImage,
|
||||
height,
|
||||
width,
|
||||
annotateAreaHeight,
|
||||
annotateAreaWidth,
|
||||
snippetImageHeight,
|
||||
snippetImageWidth,
|
||||
};
|
||||
if (this.snippingToolWindow && windowExists(this.snippingToolWindow)) {
|
||||
this.snippingToolWindow.webContents.send(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { LazyBrush } from 'lazy-brush';
|
||||
import * as React from 'react';
|
||||
import { IImageDimensions, IPath, IPoint, Tool } from './snipping-tool';
|
||||
import { IDimensions, IPath, IPoint, Tool } from './snipping-tool';
|
||||
|
||||
const { useState } = React;
|
||||
|
||||
@ -17,9 +17,10 @@ export interface IAnnotateAreaProps {
|
||||
highlightColor: string;
|
||||
penColor: string;
|
||||
onChange: (paths: IPath[]) => void;
|
||||
imageDimensions: IImageDimensions;
|
||||
imageDimensions: IDimensions;
|
||||
annotateAreaDimensions: IDimensions;
|
||||
chosenTool: Tool;
|
||||
screenSnippetPath: string;
|
||||
backgroundImagePath?: string;
|
||||
}
|
||||
|
||||
const lazy = new LazyBrush({
|
||||
@ -27,9 +28,6 @@ const lazy = new LazyBrush({
|
||||
enabled: true,
|
||||
initialPoint: { x: 0, y: 0 },
|
||||
});
|
||||
const TOP_MENU_HEIGHT = 48;
|
||||
const MIN_ANNOTATE_AREA_HEIGHT = 200;
|
||||
const MIN_ANNOTATE_AREA_WIDTH = 312;
|
||||
const PEN_WIDTH = 5;
|
||||
const HIGHLIGHT_WIDTH = 28;
|
||||
|
||||
@ -40,7 +38,8 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({
|
||||
onChange,
|
||||
imageDimensions,
|
||||
chosenTool,
|
||||
screenSnippetPath,
|
||||
backgroundImagePath,
|
||||
annotateAreaDimensions,
|
||||
}) => {
|
||||
const [isDrawing, setIsDrawing] = useState(false);
|
||||
|
||||
@ -66,10 +65,19 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({
|
||||
// Utility functions
|
||||
|
||||
const getMousePosition = (e: React.MouseEvent) => {
|
||||
// We need to offset for elements in the window that is not the annotate area
|
||||
const x = imageDimensions.width >= MIN_ANNOTATE_AREA_WIDTH ? e.pageX : e.pageX - (MIN_ANNOTATE_AREA_WIDTH - imageDimensions.width) / 2;
|
||||
const y = imageDimensions.height >= MIN_ANNOTATE_AREA_HEIGHT ? (e.pageY - TOP_MENU_HEIGHT) : (e.pageY - ((MIN_ANNOTATE_AREA_HEIGHT - imageDimensions.height) / 2) - TOP_MENU_HEIGHT);
|
||||
return { x, y };
|
||||
const target = document.getElementById('annotate-area');
|
||||
if (target) {
|
||||
const rect = target.getBoundingClientRect();
|
||||
// Offseting the scrolled X and Y inside the annotate area
|
||||
return {
|
||||
x: e.clientX - rect.left,
|
||||
y: e.clientY - rect.top,
|
||||
};
|
||||
}
|
||||
return {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
};
|
||||
};
|
||||
|
||||
// Render and preparing render functions
|
||||
@ -113,12 +121,12 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({
|
||||
const addPathPoint = (e: React.MouseEvent) => {
|
||||
const p = [...paths];
|
||||
const mousePos: IPoint = getMousePosition(e);
|
||||
lazy.update({ x: mousePos.x, y: mousePos.y });
|
||||
const point: IPoint = lazy.getBrushCoordinates();
|
||||
if (chosenTool === Tool.highlight) {
|
||||
lazy.update({ x: mousePos.x, y: mousePos.y });
|
||||
const point: IPoint = lazy.getBrushCoordinates();
|
||||
onChange(addHighlightPoint(p, point));
|
||||
} else {
|
||||
onChange(addPenPoint(p, mousePos));
|
||||
onChange(addPenPoint(p, point));
|
||||
}
|
||||
if (!isDrawing) {
|
||||
setIsDrawing(true);
|
||||
@ -216,29 +224,46 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({
|
||||
addPathPoint(e);
|
||||
};
|
||||
|
||||
const getAnnotateWrapperStyle = () => {
|
||||
const shouldShowScrollBars =
|
||||
imageDimensions.height > annotateAreaDimensions.height ||
|
||||
imageDimensions.width > annotateAreaDimensions.width;
|
||||
return {
|
||||
width: annotateAreaDimensions.width,
|
||||
height: annotateAreaDimensions.height,
|
||||
...(shouldShowScrollBars && { overflow: 'scroll' }),
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<svg
|
||||
data-testid='annotate-area'
|
||||
style={{ cursor: 'crosshair' }}
|
||||
id='annotate-area'
|
||||
width={imageDimensions.width}
|
||||
height={imageDimensions.height}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={stopDrawing}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={stopDrawing}
|
||||
>
|
||||
<image
|
||||
x={0}
|
||||
y={0}
|
||||
id='screenSnippet'
|
||||
xlinkHref={screenSnippetPath}
|
||||
<div
|
||||
id='annotate-wrapper'
|
||||
style={getAnnotateWrapperStyle()}>
|
||||
<svg
|
||||
data-testid='annotate-area'
|
||||
style={{ cursor: 'crosshair' }}
|
||||
id='annotate-area'
|
||||
width={imageDimensions.width}
|
||||
height={imageDimensions.height}
|
||||
className='SnippetImage'
|
||||
/>
|
||||
{renderPaths(getSvgPathsData(paths))}
|
||||
</svg>
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={stopDrawing}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={stopDrawing}
|
||||
>
|
||||
{
|
||||
backgroundImagePath &&
|
||||
<image
|
||||
x={0}
|
||||
y={0}
|
||||
id='backgroundImage'
|
||||
xlinkHref={backgroundImagePath}
|
||||
width={imageDimensions.width}
|
||||
height={imageDimensions.height}
|
||||
/>}
|
||||
{renderPaths(getSvgPathsData(paths))}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { i18n } from '../../common/i18n-preload';
|
||||
import AnnotateArea from './annotate-area';
|
||||
import ColorPickerPill, { IColor } from './color-picker-pill';
|
||||
|
||||
const { useState, useRef, useEffect } = React;
|
||||
const { useState, useRef, useEffect, useLayoutEffect } = React;
|
||||
|
||||
export enum Tool {
|
||||
pen = 'PEN',
|
||||
@ -25,15 +25,7 @@ export interface IPoint {
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface ISvgPath {
|
||||
svgPath: string;
|
||||
key: string;
|
||||
strokeWidth: number;
|
||||
color: string;
|
||||
shouldShow: boolean;
|
||||
}
|
||||
|
||||
export interface IImageDimensions {
|
||||
export interface IDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
@ -60,11 +52,15 @@ const SNIPPING_TOOL_NAMESPACE = 'ScreenSnippet';
|
||||
const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPaths }) => {
|
||||
// State preparation functions
|
||||
|
||||
const [screenSnippetPath, setScreenSnippetPath] = useState('Screen-Snippet');
|
||||
const [screenSnippetPath, setScreenSnippetPath] = useState('');
|
||||
const [imageDimensions, setImageDimensions] = useState({
|
||||
height: 600,
|
||||
width: 800,
|
||||
});
|
||||
const [annotateAreaDimensions, setAnnotateAreaDimensions] = useState({
|
||||
height: 600,
|
||||
width: 800,
|
||||
});
|
||||
const [paths, setPaths] = useState<IPath[]>(existingPaths || []);
|
||||
const [chosenTool, setChosenTool] = useState(Tool.pen);
|
||||
const [penColor, setPenColor] = useState<IColor>({ rgbaColor: 'rgba(0, 142, 255, 1)' });
|
||||
@ -79,12 +75,24 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
false,
|
||||
);
|
||||
|
||||
const getSnipImageData = ({ }, { snipImage, height, width }) => {
|
||||
const getSnipImageData = ({ }, {
|
||||
snipImage,
|
||||
annotateAreaHeight,
|
||||
annotateAreaWidth,
|
||||
snippetImageHeight,
|
||||
snippetImageWidth,
|
||||
}) => {
|
||||
setScreenSnippetPath(snipImage);
|
||||
setImageDimensions({ height, width });
|
||||
setImageDimensions({ height: snippetImageHeight, width: snippetImageWidth });
|
||||
setAnnotateAreaDimensions({ height: annotateAreaHeight, width: annotateAreaWidth });
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
ipcRenderer.once('snipping-tool-data', getSnipImageData);
|
||||
return () => {
|
||||
ipcRenderer.removeListener('snipping-tool-data', getSnipImageData);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Hook that alerts clicks outside of the passed refs
|
||||
const useClickOutsideExaminer = (
|
||||
@ -179,12 +187,12 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
'src',
|
||||
'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData))),
|
||||
);
|
||||
const screenSnippet = document.getElementById('screenSnippet') as HTMLImageElement;
|
||||
const backgroundImage = document.getElementById('backgroundImage') as HTMLImageElement;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Listens to when the img is loaded in memory and adds the data from the SVG paths + screenSnippet to the canvas
|
||||
img.onload = () => {
|
||||
ctx.drawImage(screenSnippet, 0, 0);
|
||||
ctx.drawImage(backgroundImage, 0, 0);
|
||||
ctx.drawImage(img, 0, 0);
|
||||
try {
|
||||
// Extracts base 64 png img data from the canvas
|
||||
@ -310,8 +318,9 @@ const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPat
|
||||
penColor={penColor.rgbaColor}
|
||||
onChange={setPaths}
|
||||
imageDimensions={imageDimensions}
|
||||
screenSnippetPath={screenSnippetPath}
|
||||
backgroundImagePath={screenSnippetPath}
|
||||
chosenTool={chosenTool}
|
||||
annotateAreaDimensions={annotateAreaDimensions}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
@ -4,10 +4,6 @@
|
||||
@version-text-color: rgb(47, 47, 47, 1);
|
||||
@text-padding: 10px;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: white;
|
||||
margin: 0;
|
||||
@ -118,6 +114,8 @@ button {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(255, 255, 255, 0.96),
|
||||
|
Loading…
Reference in New Issue
Block a user