mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
Desktop: Add support for custom notebook icons (#6110)
This commit is contained in:
parent
db497ee0a5
commit
9f252ea673
@ -235,6 +235,9 @@ packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map
|
|||||||
packages/app-desktop/gui/ErrorBoundary.d.ts
|
packages/app-desktop/gui/ErrorBoundary.d.ts
|
||||||
packages/app-desktop/gui/ErrorBoundary.js
|
packages/app-desktop/gui/ErrorBoundary.js
|
||||||
packages/app-desktop/gui/ErrorBoundary.js.map
|
packages/app-desktop/gui/ErrorBoundary.js.map
|
||||||
|
packages/app-desktop/gui/FolderIconBox.d.ts
|
||||||
|
packages/app-desktop/gui/FolderIconBox.js
|
||||||
|
packages/app-desktop/gui/FolderIconBox.js.map
|
||||||
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.d.ts
|
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.d.ts
|
||||||
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js
|
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js
|
||||||
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js.map
|
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js.map
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -225,6 +225,9 @@ packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map
|
|||||||
packages/app-desktop/gui/ErrorBoundary.d.ts
|
packages/app-desktop/gui/ErrorBoundary.d.ts
|
||||||
packages/app-desktop/gui/ErrorBoundary.js
|
packages/app-desktop/gui/ErrorBoundary.js
|
||||||
packages/app-desktop/gui/ErrorBoundary.js.map
|
packages/app-desktop/gui/ErrorBoundary.js.map
|
||||||
|
packages/app-desktop/gui/FolderIconBox.d.ts
|
||||||
|
packages/app-desktop/gui/FolderIconBox.js
|
||||||
|
packages/app-desktop/gui/FolderIconBox.js.map
|
||||||
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.d.ts
|
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.d.ts
|
||||||
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js
|
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js
|
||||||
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js.map
|
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js.map
|
||||||
|
@ -9,6 +9,13 @@ interface LastSelectedPath {
|
|||||||
directory: string;
|
directory: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OpenDialogOptions {
|
||||||
|
properties?: string[];
|
||||||
|
defaultPath?: string;
|
||||||
|
createDirectory?: boolean;
|
||||||
|
filters?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
export class Bridge {
|
export class Bridge {
|
||||||
|
|
||||||
private electronWrapper_: ElectronAppWrapper;
|
private electronWrapper_: ElectronAppWrapper;
|
||||||
@ -155,14 +162,14 @@ export class Bridge {
|
|||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
async showOpenDialog(options: any = null) {
|
async showOpenDialog(options: OpenDialogOptions = null) {
|
||||||
const { dialog } = require('electron');
|
const { dialog } = require('electron');
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
let fileType = 'file';
|
let fileType = 'file';
|
||||||
if (options.properties && options.properties.includes('openDirectory')) fileType = 'directory';
|
if (options.properties && options.properties.includes('openDirectory')) fileType = 'directory';
|
||||||
if (!('defaultPath' in options) && (this.lastSelectedPaths_ as any)[fileType]) options.defaultPath = (this.lastSelectedPaths_ as any)[fileType];
|
if (!('defaultPath' in options) && (this.lastSelectedPaths_ as any)[fileType]) options.defaultPath = (this.lastSelectedPaths_ as any)[fileType];
|
||||||
if (!('createDirectory' in options)) options.createDirectory = true;
|
if (!('createDirectory' in options)) options.createDirectory = true;
|
||||||
const { filePaths } = await dialog.showOpenDialog(this.window(), options);
|
const { filePaths } = await dialog.showOpenDialog(this.window(), options as any);
|
||||||
if (filePaths && filePaths.length) {
|
if (filePaths && filePaths.length) {
|
||||||
(this.lastSelectedPaths_ as any)[fileType] = dirname(filePaths[0]);
|
(this.lastSelectedPaths_ as any)[fileType] = dirname(filePaths[0]);
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,11 @@ import StyledInput from '../style/StyledInput';
|
|||||||
import { IconSelector, ChangeEvent } from './IconSelector';
|
import { IconSelector, ChangeEvent } from './IconSelector';
|
||||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||||
import Folder from '@joplin/lib/models/Folder';
|
import Folder from '@joplin/lib/models/Folder';
|
||||||
import { FolderEntity, FolderIcon } from '@joplin/lib/services/database/types';
|
import { FolderEntity, FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
|
||||||
import Button from '../Button/Button';
|
import Button from '../Button/Button';
|
||||||
import bridge from '../../services/bridge';
|
import bridge from '../../services/bridge';
|
||||||
|
import shim from '@joplin/lib/shim';
|
||||||
|
import FolderIconBox from '../FolderIconBox';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
@ -93,6 +95,27 @@ export default function(props: Props) {
|
|||||||
setFolderIcon(null);
|
setFolderIcon(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onBrowseClick = useCallback(async () => {
|
||||||
|
const filePaths = await bridge().showOpenDialog();
|
||||||
|
if (filePaths.length !== 1) return;
|
||||||
|
const filePath = filePaths[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dataUrl = await shim.imageToDataUrl(filePath, 256);
|
||||||
|
setFolderIcon(icon => {
|
||||||
|
return {
|
||||||
|
...icon,
|
||||||
|
emoji: '',
|
||||||
|
name: '',
|
||||||
|
type: FolderIconType.DataUrl,
|
||||||
|
dataUrl,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await bridge().showErrorMessageBox(error.message);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
function renderForm() {
|
function renderForm() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -105,11 +128,14 @@ export default function(props: Props) {
|
|||||||
<div className="form-input-group">
|
<div className="form-input-group">
|
||||||
<label>{_('Icon')}</label>
|
<label>{_('Icon')}</label>
|
||||||
<div className="icon-selector-row">
|
<div className="icon-selector-row">
|
||||||
|
{ folderIcon && <div className="foldericon"><FolderIconBox folderIcon={folderIcon} /></div> }
|
||||||
<IconSelector
|
<IconSelector
|
||||||
|
title={_('Select emoji...')}
|
||||||
icon={folderIcon}
|
icon={folderIcon}
|
||||||
onChange={onFolderIconChange}
|
onChange={onFolderIconChange}
|
||||||
/>
|
/>
|
||||||
<Button ml={1} title={_('Clear')} onClick={onClearClick}/>
|
<Button ml={1} title={_('Select file...')} onClick={onBrowseClick}/>
|
||||||
|
{ folderIcon && <Button ml={1} title={_('Clear')} onClick={onClearClick}/> }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import { useEffect, useState, useCallback, useRef } from 'react';
|
|||||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||||
import { loadScript } from '../utils/loadScript';
|
import { loadScript } from '../utils/loadScript';
|
||||||
import Button from '../Button/Button';
|
import Button from '../Button/Button';
|
||||||
import { FolderIcon } from '@joplin/lib/services/database/types';
|
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
|
||||||
import bridge from '../../services/bridge';
|
import bridge from '../../services/bridge';
|
||||||
|
|
||||||
export interface ChangeEvent {
|
export interface ChangeEvent {
|
||||||
@ -15,6 +15,7 @@ type ChangeHandler = (event: ChangeEvent)=> void;
|
|||||||
interface Props {
|
interface Props {
|
||||||
onChange: ChangeHandler;
|
onChange: ChangeHandler;
|
||||||
icon: FolderIcon | null;
|
icon: FolderIcon | null;
|
||||||
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IconSelector = (props: Props) => {
|
export const IconSelector = (props: Props) => {
|
||||||
@ -62,7 +63,7 @@ export const IconSelector = (props: Props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onEmoji = (selection: FolderIcon) => {
|
const onEmoji = (selection: FolderIcon) => {
|
||||||
props.onChange({ value: selection });
|
props.onChange({ value: { ...selection, type: FolderIconType.Emoji } });
|
||||||
};
|
};
|
||||||
|
|
||||||
p.on('emoji', onEmoji);
|
p.on('emoji', onEmoji);
|
||||||
@ -78,16 +79,25 @@ export const IconSelector = (props: Props) => {
|
|||||||
picker.togglePicker(buttonRef.current);
|
picker.togglePicker(buttonRef.current);
|
||||||
}, [picker]);
|
}, [picker]);
|
||||||
|
|
||||||
const buttonText = props.icon ? props.icon.emoji : '...';
|
// const buttonText = props.icon ? props.icon.emoji : '...';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
disabled={!picker}
|
disabled={!picker}
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
title={buttonText}
|
title={props.title}
|
||||||
isSquare={true}
|
|
||||||
fontSize={20}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <Button
|
||||||
|
// disabled={!picker}
|
||||||
|
// ref={buttonRef}
|
||||||
|
// onClick={onClick}
|
||||||
|
// title={buttonText}
|
||||||
|
// isSquare={true}
|
||||||
|
// fontSize={20}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
.icon-selector-row {
|
.icon-selector-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-selector-row > .foldericon {
|
||||||
|
margin-right: 5px;
|
||||||
}
|
}
|
17
packages/app-desktop/gui/FolderIconBox.tsx
Normal file
17
packages/app-desktop/gui/FolderIconBox.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
folderIcon: FolderIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(props: Props) {
|
||||||
|
const folderIcon = props.folderIcon;
|
||||||
|
|
||||||
|
if (folderIcon.type === FolderIconType.Emoji) {
|
||||||
|
return <span style={{ fontSize: 20 }}>{folderIcon.emoji}</span>;
|
||||||
|
} else if (folderIcon.type === FolderIconType.DataUrl) {
|
||||||
|
return <img style={{ width: 20, height: 20 }} src={folderIcon.dataUrl} />;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unsupported folder icon type: ${folderIcon.type}`);
|
||||||
|
}
|
||||||
|
}
|
@ -17,11 +17,12 @@ import Folder from '@joplin/lib/models/Folder';
|
|||||||
import Note from '@joplin/lib/models/Note';
|
import Note from '@joplin/lib/models/Note';
|
||||||
import Tag from '@joplin/lib/models/Tag';
|
import Tag from '@joplin/lib/models/Tag';
|
||||||
import Logger from '@joplin/lib/Logger';
|
import Logger from '@joplin/lib/Logger';
|
||||||
import { FolderEntity } from '@joplin/lib/services/database/types';
|
import { FolderEntity, FolderIcon } from '@joplin/lib/services/database/types';
|
||||||
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
|
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
|
||||||
import { store } from '@joplin/lib/reducer';
|
import { store } from '@joplin/lib/reducer';
|
||||||
import PerFolderSortOrderService from '../../services/sortOrder/PerFolderSortOrderService';
|
import PerFolderSortOrderService from '../../services/sortOrder/PerFolderSortOrderService';
|
||||||
import { getFolderCallbackUrl, getTagCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
import { getFolderCallbackUrl, getTagCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
||||||
|
import FolderIconBox from '../FolderIconBox';
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const shared = require('@joplin/lib/components/shared/side-menu-shared.js');
|
const shared = require('@joplin/lib/components/shared/side-menu-shared.js');
|
||||||
const { themeStyle } = require('@joplin/lib/theme');
|
const { themeStyle } = require('@joplin/lib/theme');
|
||||||
@ -77,6 +78,12 @@ function ExpandLink(props: any) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderFolderIcon = (folderIcon: FolderIcon) => {
|
||||||
|
if (!folderIcon) return null;
|
||||||
|
|
||||||
|
return <div style={{ marginRight: 5, display: 'flex' }}><FolderIconBox folderIcon={folderIcon}/></div>;
|
||||||
|
};
|
||||||
|
|
||||||
function FolderItem(props: any) {
|
function FolderItem(props: any) {
|
||||||
const { hasChildren, isExpanded, parentId, depth, selected, folderId, folderTitle, folderIcon, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_, shareId } = props;
|
const { hasChildren, isExpanded, parentId, depth, selected, folderId, folderTitle, folderIcon, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_, shareId } = props;
|
||||||
|
|
||||||
@ -84,8 +91,6 @@ function FolderItem(props: any) {
|
|||||||
|
|
||||||
const shareIcon = shareId && !parentId ? <StyledShareIcon className="fas fa-share-alt"></StyledShareIcon> : null;
|
const shareIcon = shareId && !parentId ? <StyledShareIcon className="fas fa-share-alt"></StyledShareIcon> : null;
|
||||||
|
|
||||||
const icon = folderIcon ? <span style={{ fontSize: 20, marginRight: 5 }}>{folderIcon.emoji}</span> : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth} ${selected ? 'selected' : ''}`} onDragStart={onFolderDragStart_} onDragOver={onFolderDragOver_} onDrop={onFolderDrop_} draggable={true} data-folder-id={folderId}>
|
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth} ${selected ? 'selected' : ''}`} onDragStart={onFolderDragStart_} onDragOver={onFolderDragOver_} onDrop={onFolderDrop_} draggable={true} data-folder-id={folderId}>
|
||||||
<ExpandLink themeId={props.themeId} hasChildren={hasChildren} folderId={folderId} onClick={onFolderToggleClick_} isExpanded={isExpanded}/>
|
<ExpandLink themeId={props.themeId} hasChildren={hasChildren} folderId={folderId} onClick={onFolderToggleClick_} isExpanded={isExpanded}/>
|
||||||
@ -105,7 +110,7 @@ function FolderItem(props: any) {
|
|||||||
}}
|
}}
|
||||||
onDoubleClick={onFolderToggleClick_}
|
onDoubleClick={onFolderToggleClick_}
|
||||||
>
|
>
|
||||||
{icon}<span className="title" style={{ lineHeight: 0 }}>{folderTitle}</span>
|
{renderFolderIcon(folderIcon)}<span className="title" style={{ lineHeight: 0 }}>{folderTitle}</span>
|
||||||
{shareIcon} {noteCountComp}
|
{shareIcon} {noteCountComp}
|
||||||
</StyledListItemAnchor>
|
</StyledListItemAnchor>
|
||||||
</StyledListItem>
|
</StyledListItem>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const Component = React.Component;
|
const Component = React.Component;
|
||||||
const { Easing, Animated, TouchableOpacity, Text, StyleSheet, ScrollView, View, Alert } = require('react-native');
|
const { Easing, Animated, TouchableOpacity, Text, StyleSheet, ScrollView, View, Alert, Image } = require('react-native');
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const Icon = require('react-native-vector-icons/Ionicons').default;
|
const Icon = require('react-native-vector-icons/Ionicons').default;
|
||||||
const Folder = require('@joplin/lib/models/Folder').default;
|
const Folder = require('@joplin/lib/models/Folder').default;
|
||||||
@ -74,7 +74,7 @@ class SideMenuContentComponent extends Component {
|
|||||||
|
|
||||||
styles.folderButton = Object.assign({}, styles.button);
|
styles.folderButton = Object.assign({}, styles.button);
|
||||||
styles.folderButton.paddingLeft = 0;
|
styles.folderButton.paddingLeft = 0;
|
||||||
styles.folderButtonText = Object.assign({}, styles.buttonText);
|
styles.folderButtonText = Object.assign({}, styles.buttonText, { paddingLeft: 0 });
|
||||||
styles.folderButtonSelected = Object.assign({}, styles.folderButton);
|
styles.folderButtonSelected = Object.assign({}, styles.folderButton);
|
||||||
styles.folderButtonSelected.backgroundColor = theme.selectedColor;
|
styles.folderButtonSelected.backgroundColor = theme.selectedColor;
|
||||||
styles.folderIcon = Object.assign({}, theme.icon);
|
styles.folderIcon = Object.assign({}, theme.icon);
|
||||||
@ -219,6 +219,18 @@ class SideMenuContentComponent extends Component {
|
|||||||
if (actionDone === 'auth') this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
if (actionDone === 'auth') this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderFolderIcon(theme, folderIcon) {
|
||||||
|
if (!folderIcon) return null;
|
||||||
|
|
||||||
|
if (folderIcon.type === 1) { // FolderIconType.Emoji
|
||||||
|
return <Text style={{ fontSize: theme.fontSize, marginRight: 4 }}>{folderIcon.emoji}</Text>;
|
||||||
|
} else if (folderIcon.type === 2) { // FolderIconType.DataUrl
|
||||||
|
return <Image style={{ width: 20, height: 20, marginRight: 4, resizeMode: 'contain' }} source={{ uri: folderIcon.dataUrl }}/>;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unsupported folder icon type: ${folderIcon.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderFolderItem(folder, selected, hasChildren, depth) {
|
renderFolderItem(folder, selected, hasChildren, depth) {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
@ -228,6 +240,7 @@ class SideMenuContentComponent extends Component {
|
|||||||
height: 36,
|
height: 36,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingRight: theme.marginRight,
|
paddingRight: theme.marginRight,
|
||||||
|
paddingLeft: 10,
|
||||||
};
|
};
|
||||||
if (selected) folderButtonStyle.backgroundColor = theme.selectedColor;
|
if (selected) folderButtonStyle.backgroundColor = theme.selectedColor;
|
||||||
folderButtonStyle.paddingLeft = depth * 10 + theme.marginLeft;
|
folderButtonStyle.paddingLeft = depth * 10 + theme.marginLeft;
|
||||||
@ -253,7 +266,6 @@ class SideMenuContentComponent extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folderIcon = Folder.unserializeIcon(folder.icon);
|
const folderIcon = Folder.unserializeIcon(folder.icon);
|
||||||
const icon = folderIcon ? `${folderIcon.emoji} ` : '';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View key={folder.id} style={{ flex: 1, flexDirection: 'row' }}>
|
<View key={folder.id} style={{ flex: 1, flexDirection: 'row' }}>
|
||||||
@ -267,8 +279,9 @@ class SideMenuContentComponent extends Component {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View style={folderButtonStyle}>
|
<View style={folderButtonStyle}>
|
||||||
|
{this.renderFolderIcon(theme, folderIcon)}
|
||||||
<Text numberOfLines={1} style={this.styles().folderButtonText}>
|
<Text numberOfLines={1} style={this.styles().folderButtonText}>
|
||||||
{icon + Folder.displayTitle(folder)}
|
{Folder.displayTitle(folder)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FolderEntity, FolderIcon, NoteEntity } from '../services/database/types';
|
import { defaultFolderIcon, FolderEntity, FolderIcon, NoteEntity } from '../services/database/types';
|
||||||
import BaseModel, { DeleteOptions } from '../BaseModel';
|
import BaseModel, { DeleteOptions } from '../BaseModel';
|
||||||
import time from '../time';
|
import time from '../time';
|
||||||
import { _ } from '../locale';
|
import { _ } from '../locale';
|
||||||
@ -767,7 +767,11 @@ export default class Folder extends BaseItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static unserializeIcon(icon: string): FolderIcon {
|
public static unserializeIcon(icon: string): FolderIcon {
|
||||||
return icon ? JSON.parse(icon) : null;
|
if (!icon) return null;
|
||||||
|
return {
|
||||||
|
...defaultFolderIcon(),
|
||||||
|
...JSON.parse(icon),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,26 @@ export interface BaseItemEntity {
|
|||||||
created_time?: number;
|
created_time?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum FolderIconType {
|
||||||
|
Emoji = 1,
|
||||||
|
DataUrl = 2,
|
||||||
|
}
|
||||||
|
|
||||||
export interface FolderIcon {
|
export interface FolderIcon {
|
||||||
|
type: FolderIconType;
|
||||||
emoji: string;
|
emoji: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
dataUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultFolderIcon = () => {
|
||||||
|
const icon:FolderIcon = {
|
||||||
|
type: FolderIconType.Emoji,
|
||||||
|
emoji: '',
|
||||||
|
name: '',
|
||||||
|
dataUrl: '',
|
||||||
|
};
|
||||||
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -330,6 +330,23 @@ function shimInit(options = null) {
|
|||||||
return Note.save(newNote);
|
return Note.save(newNote);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
shim.imageToDataUrl = async (filePath, maxSize) => {
|
||||||
|
if (shim.isElectron()) {
|
||||||
|
const nativeImage = require('electron').nativeImage;
|
||||||
|
const image = nativeImage.createFromPath(filePath);
|
||||||
|
if (!image) throw new Error(`Could not load image: ${filePath}`);
|
||||||
|
|
||||||
|
if (maxSize) {
|
||||||
|
const size = image.getSize();
|
||||||
|
if (size.width > maxSize || size.height > maxSize) throw new Error(`Image cannot be larger than ${maxSize}x${maxSize} pixels`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return image.toDataURL();
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported method');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
shim.imageFromDataUrl = async function(imageDataUrl, filePath, options = null) {
|
shim.imageFromDataUrl = async function(imageDataUrl, filePath, options = null) {
|
||||||
if (options === null) options = {};
|
if (options === null) options = {};
|
||||||
|
|
||||||
|
@ -228,6 +228,10 @@ const shim = {
|
|||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
imageToDataUrl: async (_filePath: string, _maxSize: number = 0): Promise<string> => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
imageFromDataUrl: async (_imageDataUrl: string, _filePath: string, _options: any = null) => {
|
imageFromDataUrl: async (_imageDataUrl: string, _filePath: string, _options: any = null) => {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user