Skip to content

Commit

Permalink
Merge pull request 'feat/mobile-draw' (#225) from feat/mobile-draw in…
Browse files Browse the repository at this point in the history
…to release/v8.3.0
  • Loading branch information
maxkadushkin committed Jan 16, 2025
2 parents 0fc5e4b + 5dc7e2d commit 712d466
Show file tree
Hide file tree
Showing 51 changed files with 799 additions and 473 deletions.
37 changes: 4 additions & 33 deletions apps/common/mobile/lib/component/ThemeColorPalette.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useState, useEffect } from 'react';
import { f7, ListItem, List, Icon } from 'framework7-react';
import React from 'react';
import { ListItem, List } from 'framework7-react';
import { useTranslation } from 'react-i18next';
import { LocalStorage } from '../../utils/LocalStorage.mjs';
import { WheelColorPicker } from "./WheelColorPicker";

const ThemeColors = ({ themeColors, onColorClick, curColor, isTypeColors, isTypeCustomColors }) => {
return (
Expand Down Expand Up @@ -159,23 +160,6 @@ const CustomColorPicker = props => {
}

const countDynamicColors = props.countdynamiccolors || 10;
const [stateColor, setColor] = useState(`#${currentColor}`);

useEffect(() => {
if (document.getElementsByClassName('color-picker-wheel').length < 1) {
const colorPicker = f7.colorPicker.create({
containerEl: document.getElementsByClassName('color-picker-container')[0],
value: {
hex: `#${currentColor}`
},
on: {
change: function (value) {
setColor(value.getValue().hex);
}
}
});
}
});

const addNewColor = (color) => {
let colors = LocalStorage.getItem('mobile-custom-colors');
Expand All @@ -186,20 +170,7 @@ const CustomColorPicker = props => {
props.onAddNewColor && props.onAddNewColor(colors, newColor);
};

return (
<div id='color-picker'>
<div className='color-picker-container'></div>
<div className='right-block'>
<div className='color-hsb-preview'>
<div className='new-color-hsb-preview' style={{backgroundColor: stateColor}}></div>
<div className='current-color-hsb-preview' style={{backgroundColor: `#${currentColor}`}}></div>
</div>
<a href='#' id='add-new-color' className='button button-round' onClick={()=>{addNewColor(stateColor)}}>
<Icon icon={'icon-plus'} slot="media" />
</a>
</div>
</div>
)
return <WheelColorPicker initialColor={`#${currentColor}`} onSelectColor={addNewColor}/>
};

export { ThemeColorPalette, CustomColorPicker };
31 changes: 31 additions & 0 deletions apps/common/mobile/lib/component/WheelColorPicker.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { useEffect, useState } from 'react'
import { f7, Icon } from 'framework7-react';

export const WheelColorPicker = ({ initialColor = '#ffffff', onSelectColor }) => {
const [color, setColor] = useState(initialColor);

useEffect(() => {
if (!document.getElementsByClassName('color-picker-wheel').length) {
f7.colorPicker.create({
containerEl: document.getElementsByClassName('color-picker-container')[0],
value: { hex: initialColor },
on: { change: (value) => setColor(value.getValue().hex) }
});
}
});

return (
<div id='color-picker'>
<div className='color-picker-container'/>
<div className='right-block'>
<div className='color-hsb-preview'>
<div className='new-color-hsb-preview' style={{ backgroundColor: color }}/>
<div className='current-color-hsb-preview' style={{ backgroundColor: initialColor }}/>
</div>
<a href='#' id='add-new-color' className='button button-round' onClick={() => onSelectColor(color)}>
<Icon icon='icon-plus' slot="media"/>
</a>
</div>
</div>
)
}
1 change: 0 additions & 1 deletion apps/common/mobile/lib/controller/ContextMenu.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { Component, Fragment } from 'react';
import { f7 } from 'framework7-react';
import {observer, inject} from "mobx-react";
import { Device } from '../../../../common/mobile/utils/device';

import ContextMenuView, { idContextMenuElement, ActionsWithExtraItems } from '../view/ContextMenu';
Expand Down
95 changes: 95 additions & 0 deletions apps/common/mobile/lib/controller/Draw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useEffect, useState } from 'react'
import { DrawView } from "../view/Draw";
import { inject, observer } from "mobx-react";
import { Device } from "../../utils/device";
import { LocalStorage } from '../../utils/LocalStorage.mjs';

const DEFAULT_TOOL_SETTINGS = {
pen: { color: '#FF0000', opacity: 100, lineSize: 2 },
highlighter: { color: '#FFFC54', opacity: 50, lineSize: 5 },
}
const DEFAULT_ANDROID_COLORS = ['#FF0000', '#FFC000', '#FFFF00', '#92D050', '#00B050', '#00B0F0', '#0070C0', '#002060', '#C00000']
const DEFAULT_IOS_COLORS = ['#FFFC54', '#72F54A', '#74F9FD', '#EB51F7', '#A900F9', '#FF0303', '#EF8B3A', '#D3D3D4', '#000000']

export const DrawController = inject('storeAppOptions')(observer(({ storeAppOptions }) => {
const [currentTool, setCurrentTool] = useState(null);
const [toolSettings, setToolSettings] = useState(() => {
const stored = LocalStorage.getJson('draw-settings');
return stored || DEFAULT_TOOL_SETTINGS;
});
const [colors, setColors] = useState(() => {
const storageColors = LocalStorage.getJson('draw-colors', []);
if (!storageColors.length) {
return Device.android ? DEFAULT_ANDROID_COLORS : DEFAULT_IOS_COLORS
}
return storageColors
})

useEffect(() => {
Common.Notifications.on('draw:start', () => {
storeAppOptions.changeDrawMode(true);
setCurrentToolAndApply('pen');
})

Common.Notifications.on('draw:stop', () => {
storeAppOptions.changeDrawMode(false);
setCurrentToolAndApply('scroll');
})

return () => {
Common.Notifications.off('draw:start');
Common.Notifications.off('draw:stop');
}
}, []);

const createStroke = (color, lineSize, opacity) => {
const stroke = new Asc.asc_CStroke();
stroke.put_type(Asc.c_oAscStrokeType.STROKE_COLOR);
stroke.put_color(Common.Utils.ThemeColor.getRgbColor(color));
stroke.asc_putPrstDash(Asc.c_oDashType.solid);
stroke.put_width(lineSize);
stroke.put_transparent(opacity * 2.55);
return stroke;
};

const toolActions = {
pen: (api, settings) => api.asc_StartDrawInk(createStroke(settings.pen.color, settings.pen.lineSize, settings.pen.opacity), 0),
highlighter: (api, settings) => api.asc_StartDrawInk(createStroke(settings.highlighter.color, settings.highlighter.lineSize, settings.highlighter.opacity), 1),
eraser: (api) => api.asc_StartInkEraser(),
eraseEntireScreen: (api) => {/* fixme: method */
},
scroll: (api) => api.asc_StopInkDrawer(),
};

const setCurrentToolAndApply = (tool) => {
const api = Common.EditorApi.get();
toolActions[tool]?.(api, toolSettings);
setCurrentTool(tool);
};

const updateToolSettings = (newSettings) => {
setToolSettings(prev => {
const updatedSettings = { ...prev, [currentTool]: { ...prev[currentTool], ...newSettings } };
const api = Common.EditorApi.get();
toolActions[currentTool]?.(api, updatedSettings);
LocalStorage.setJson('draw-settings', updatedSettings)
return updatedSettings;
});
};

const addCustomColor = (color) => {
const updatedColors = [...colors, color]
setColors(updatedColors)
updateToolSettings({ color })
LocalStorage.setJson('draw-colors', updatedColors)
}

return storeAppOptions.isDrawMode ? <DrawView
currentTool={currentTool}
setTool={setCurrentToolAndApply}
settings={toolSettings}
setSettings={updateToolSettings}
colors={colors}
addCustomColor={addCustomColor}
/> : null
}));
118 changes: 118 additions & 0 deletions apps/common/mobile/lib/view/Draw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Button, f7, Icon, Range, Sheet } from 'framework7-react';
import SvgIcon from '../../../../common/mobile//lib/component/SvgIcon'
import IconDrawPen from '../../../../common/mobile/resources/icons/draw-pen.svg'
import IconDrawHighlighter from '../../../../common/mobile/resources/icons/draw-highlighter.svg'
import IconClearAll from '../../../../common/mobile/resources/icons/clear-all.svg'
import IconClearObject from '../../../../common/mobile/resources/icons/clear-object.svg'
import IconScroll from '../../../../common/mobile/resources/icons/scroll.svg'
import { WheelColorPicker } from "../component/WheelColorPicker";
import { Device } from "../../utils/device";

export const DrawView = ({ currentTool, setTool, settings, setSettings, colors, addCustomColor }) => {
const { t } = useTranslation();
const _t = t('Draw', { returnObjects: true });
const isDrawingTool = currentTool === 'pen' || currentTool === 'highlighter';

return (
<React.Fragment>
{isDrawingTool && (<>
<Sheet className='draw-sheet draw-sheet--color-picker' backdrop swipeToClose onSheetClosed={() => f7.sheet.open('.draw-sheet--settings')}>
<div className='draw-sheet-label'><span>{_t.textCustomColor}</span></div>
<WheelColorPicker
initialColor={settings[currentTool].color}
onSelectColor={(color) => {
f7.sheet.close('.draw-sheet--color-picker')
addCustomColor(color)
}}
/>
</Sheet>
<Sheet className="draw-sheet draw-sheet--settings" backdrop swipeToClose style={{ height: 'auto' }}>
<div id='swipe-handler' className='swipe-container'>
<Icon icon='icon icon-swipe'/>
</div>
<div className='draw-sheet-label'><span>{_t.textColor}</span></div>
<div className='draw-sheet--settings-colors'>
<div className="draw-sheet--settings-colors-list">
{colors.map((color, index) => (
<div
key={index} className="draw-sheet--settings-colors-list-item" style={{ backgroundColor: color }}
onClick={() => setSettings({ color })}
/>
))}
<div
className="draw-sheet--settings-colors-list-add" style={{ backgroundColor: settings[currentTool].color }}
onClick={() => {
f7.sheet.close('.draw-sheet--settings')
f7.sheet.open('.draw-sheet--color-picker')
}}
>
<Icon icon="icon-plus" style={{ backgroundColor: 'var(--brand-word)' }}/>
</div>
</div>
</div>
<div className='draw-sheet-label'><span>{_t.textLineSize}</span></div>
<div className='draw-sheet-item'>
{/*{Device.android ? (*/}
<Range
min={0.5} max={10} step={0.5} value={settings[currentTool].lineSize}
onRangeChange={(value) => setSettings({ lineSize: value })}
/>
{/*) : (*/}
{/* <input className='line-size-range--ios' type='range' min={0.5} max={10} step={0.5} value={settings[currentTool].lineSize} onChange={(e) => setSettings({ lineSize: parseInt(e.target.value) })} />*/}
{/* )}*/}
</div>
<div className='draw-sheet-label'><span>{_t.textOpacity}</span></div>
<div className='draw-sheet-item'>
<input style={{ '--color': settings[currentTool].color }}
className={Device.android ? 'opacity-range-input--android' : 'opacity-range-input--ios'} type='range' min={0} max={100} step={1}
value={settings[currentTool].opacity}
onChange={(e) => setSettings({ opacity: parseInt(e.target.value) })}/>
</div>
</Sheet>
</>)}
<div className="draw-toolbar">
<div className="draw-toolbar-item">
<Button type='button' fill={currentTool === 'pen'} onClick={() => setTool('pen')}>
<SvgIcon symbolId={IconDrawPen.id} className='icon icon-svg'/>
</Button>
</div>
<div className="draw-toolbar-item">
<Button type='button' fill={currentTool === 'highlighter'} onClick={() => setTool('highlighter')}>
<SvgIcon symbolId={IconDrawHighlighter.id} className='icon icon-svg'/>
</Button>
</div>
<div className="draw-toolbar-item">
<Button type='button' sheetOpen={isDrawingTool ? ".draw-sheet--settings" : undefined} disabled={!isDrawingTool}>
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<circle cx="50%" cy="50%" r="8" fill={settings[currentTool]?.color || '#808080'}/>
</svg>
</Button>
</div>
<div className="draw-toolbar-item">
<div className='draw-toolbar-divider'/>
</div>
<div className="draw-toolbar-item">
<Button type='button' disabled={false} fill={currentTool === 'eraser'} onClick={() => setTool('eraser')}>
<SvgIcon symbolId={IconClearObject.id} className='icon icon-svg'/>
</Button>
</div>
<div className="draw-toolbar-item">
<Button type='button' disabled={false} onClick={() => setTool('eraseEntireScreen')}>
<SvgIcon symbolId={IconClearAll.id} className='icon icon-svg'/>
</Button>
</div>
<div className="draw-toolbar-item">
<div className='draw-toolbar-divider'/>
</div>
<div className="draw-toolbar-item">
<Button type='button' fill={currentTool === 'scroll'} onClick={() => setTool('scroll')} tabIndex='-1'>
<SvgIcon symbolId={IconScroll.id} className='icon icon-svg'/>
</Button>
</div>
</div>
</React.Fragment>
)
}

4 changes: 4 additions & 0 deletions apps/common/mobile/resources/icons/clear-all.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions apps/common/mobile/resources/icons/clear-object.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions apps/common/mobile/resources/icons/draw-highlighter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions apps/common/mobile/resources/icons/draw-pen.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions apps/common/mobile/resources/icons/draw.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 712d466

Please sign in to comment.