Add support for setting image download resolution in the ERD tool. #6698

pull/9299/head
Aditya Toshniwal 2025-10-29 14:57:20 +05:30 committed by GitHub
parent c7a6056ee3
commit abdcd983f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 74 additions and 37 deletions

View File

@ -431,6 +431,19 @@ class ERDModule(PgAdminModule):
) )
) )
self.preference.register(
'options', 'image_pixel_ratio',
gettext('Image Download Resolution'), 'radioModern', '1',
category_label=PREF_LABEL_OPTIONS, options=[
{'label': gettext('Good'), 'value': '1'},
{'label': gettext('High'), 'value': '3'},
{'label': gettext('Very High'), 'value': '5'},
],
help_str=gettext(
'Higher values will use higher memory and slower rendering.'
),
)
self.preference.register( self.preference.register(
'options', 'options',
'sql_with_drop', 'sql_with_drop',

View File

@ -103,6 +103,16 @@ const StyledBox = styled(Box)(({theme})=>({
'& .ERDTool-html2canvasReset': { '& .ERDTool-html2canvasReset': {
backgroundImage: 'none !important', backgroundImage: 'none !important',
overflow: 'auto !important', overflow: 'auto !important',
textRendering: 'geometricPrecision',
'& .TableNode-tableToolbar': {
visibility: 'hidden',
},
'& .TableNode-tableContent': {
borderTopLeftRadius: theme.shape.borderRadius,
borderTopRightRadius: theme.shape.borderRadius,
},
} }
})); }));
@ -163,7 +173,6 @@ export default class ERDTool extends React.Component {
this.keyboardActionObj = null; this.keyboardActionObj = null;
this.erdDialogs = new ERDDialogs(this.context); this.erdDialogs = new ERDDialogs(this.context);
this.apiObj = getApiInstance(); this.apiObj = getApiInstance();
this.preferencesStore = usePreferences.getState();
this.fmUtilsObj = new FileManagerUtils(this.apiObj, {modal: this.context}); this.fmUtilsObj = new FileManagerUtils(this.apiObj, {modal: this.context});
this.restore = props.params.restore == 'true'; this.restore = props.params.restore == 'true';
this.eventBus = new EventBus(); this.eventBus = new EventBus();
@ -328,17 +337,25 @@ export default class ERDTool extends React.Component {
this.setLoading(gettext('Preparing...')); this.setLoading(gettext('Preparing...'));
this.registerEvents(); this.registerEvents();
this.diagramContainerRef.current?.focus(); this.diagramContainerRef.current?.focus();
const erdPref = this.preferencesStore.getPreferencesForModule('erd'); const erdPref = usePreferences.getState().getPreferencesForModule('erd');
this.setState({ this.setState({
preferences: erdPref, preferences: erdPref,
is_new_tab: (this.preferencesStore.getPreferencesForModule('browser').new_browser_tab_open || '') is_new_tab: (usePreferences.getState().getPreferencesForModule('browser').new_browser_tab_open || '')
.includes('erd_tool'), .includes('erd_tool'),
is_close_tab_warning: this.preferencesStore.getPreferencesForModule('browser').confirm_on_refresh_close, is_close_tab_warning: usePreferences.getState().getPreferencesForModule('browser').confirm_on_refresh_close,
cardinality_notation: erdPref.cardinality_notation, cardinality_notation: erdPref.cardinality_notation,
}, ()=>{ }, ()=>{
this.registerKeyboardShortcuts(); this.registerKeyboardShortcuts();
if(this.state.current_file)this.setTitle(this.state.current_file); if(this.state.current_file)this.setTitle(this.state.current_file);
}); });
usePreferences.subscribe((state)=>{
this.setState({
preferences: state.getPreferencesForModule('erd'),
is_close_tab_warning: state.getPreferencesForModule('browser').confirm_on_refresh_close,
});
});
this.registerModelEvents(); this.registerModelEvents();
this.realignGrid({ this.realignGrid({
backgroundSize: '45px 45px', backgroundSize: '45px 45px',
@ -808,7 +825,7 @@ export default class ERDTool extends React.Component {
height = 32766; height = 32766;
isCut = true; isCut = true;
} }
toPng(this.canvasEle, {width, height}) toPng(this.canvasEle, {width, height, pixelRatio: this.state.preferences.image_pixel_ratio || 1})
.then((dataUrl)=>{ .then((dataUrl)=>{
DownloadUtils.downloadBase64UrlData(dataUrl, `${this.getCurrentProjectName()}.png`); DownloadUtils.downloadBase64UrlData(dataUrl, `${this.getCurrentProjectName()}.png`);
}).catch((err)=>{ }).catch((err)=>{

View File

@ -27,7 +27,7 @@ import { Box } from '@mui/material';
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
const TYPE = 'table'; const TYPE = 'table';
const TABLE_WIDTH = 175; const TABLE_WIDTH = 180;
export class TableNodeModel extends DefaultNodeModel { export class TableNodeModel extends DefaultNodeModel {
constructor({otherInfo, ...options}) { constructor({otherInfo, ...options}) {
@ -214,16 +214,31 @@ RowIcon.propTypes = {
const StyledDiv = styled('div')(({theme})=>({ const StyledDiv = styled('div')(({theme})=>({
'&.TableNode-tableNode': { '&.TableNode-tableNode': {
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary, color: theme.palette.text.primary,
...theme.mixins.panelBorder.all,
borderRadius: theme.shape.borderRadius,
position: 'relative', position: 'relative',
width: `${TABLE_WIDTH}px`, width: `${TABLE_WIDTH}px`,
fontSize: '0.8em', fontSize: '0.8em',
'& div:last-child': {
borderBottomLeftRadius: 'inherit', '& .TableNode-tableContent': {
borderBottomRightRadius: 'inherit', backgroundColor: theme.palette.background.default,
...theme.mixins.panelBorder.all,
borderBottomLeftRadius: theme.shape.borderRadius,
borderBottomRightRadius: theme.shape.borderRadius,
},
'& .TableNode-tableToolbar': {
background: theme.otherVars.editorToolbarBg,
...theme.mixins.panelBorder.all,
borderBottom: 'none',
borderTopLeftRadius: theme.shape.borderRadius,
borderTopRightRadius: theme.shape.borderRadius,
padding: '0.125rem 0.25rem',
display: 'flex',
'& .TableNode-noteBtn': {
marginLeft: 'auto',
backgroundColor: theme.palette.warning.main,
color: theme.palette.warning.contrastText,
},
}, },
'& .TableNode-tableSection': { '& .TableNode-tableSection': {
...theme.mixins.panelBorder.bottom, ...theme.mixins.panelBorder.bottom,
@ -237,16 +252,6 @@ const StyledDiv = styled('div')(({theme})=>({
color: theme.palette.error.main, color: theme.palette.error.main,
}, },
}, },
'&.TableNode-tableToolbar': {
background: theme.otherVars.editorToolbarBg,
borderTopLeftRadius: 'inherit',
borderTopRightRadius: 'inherit',
},
'& .TableNode-noteBtn': {
marginLeft: 'auto',
backgroundColor: theme.palette.warning.main,
color: theme.palette.warning.contrastText,
},
}, },
'& .TableNode-columnSection': { '& .TableNode-columnSection': {
display:'flex', display:'flex',
@ -370,7 +375,7 @@ export class TableNodeWidget extends React.Component {
return ( return (
<StyledDiv className={['TableNode-tableNode', (this.props.node.isSelected() ? 'TableNode-tableNodeSelected': '')].join(' ')} <StyledDiv className={['TableNode-tableNode', (this.props.node.isSelected() ? 'TableNode-tableNodeSelected': '')].join(' ')}
onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}} style={styles}> onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}} style={styles}>
<div className={'TableNode-tableSection TableNode-tableToolbar'}> <div className={'TableNode-tableToolbar'}>
<PgIconButton size="xs" title={gettext('Show Details')} icon={this.state.show_details ? <VisibilityRoundedIcon /> : <VisibilityOffRoundedIcon />} <PgIconButton size="xs" title={gettext('Show Details')} icon={this.state.show_details ? <VisibilityRoundedIcon /> : <VisibilityOffRoundedIcon />}
onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}} /> onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}} />
{this.props.node.getNote() && {this.props.node.getNote() &&
@ -381,24 +386,26 @@ export class TableNodeWidget extends React.Component {
}} }}
/>} />}
</div> </div>
{tableMetaData.is_promise && <div className='TableNode-tableContent'>
{tableMetaData.is_promise &&
<div className='TableNode-tableSection'> <div className='TableNode-tableSection'>
{!tableMetaData.data_failed && <div className='TableNode-tableNameText'>{gettext('Fetching...')}</div>} {!tableMetaData.data_failed && <div className='TableNode-tableNameText'>{gettext('Fetching...')}</div>}
{tableMetaData.data_failed && <div className={'TableNode-tableNameText TableNode-error'}>{gettext('Failed to get data. Please delete this table.')}</div>} {tableMetaData.data_failed && <div className={'TableNode-tableNameText TableNode-error'}>{gettext('Failed to get data. Please delete this table.')}</div>}
</div>} </div>}
{!tableMetaData.is_promise && <> {!tableMetaData.is_promise && <>
<div className='TableNode-tableSection'> <div className='TableNode-tableSection'>
<RowIcon icon={SchemaIcon}/> <RowIcon icon={SchemaIcon}/>
<div className='TableNode-tableNameText' data-test="schema-name">{tableData.schema}</div> <div className='TableNode-tableNameText' data-test="schema-name">{tableData.schema}</div>
</div> </div>
<div className='TableNode-tableSection'> <div className='TableNode-tableSection'>
<RowIcon icon={TableIcon} /> <RowIcon icon={TableIcon} />
<div className='TableNode-tableNameText' data-test="table-name">{tableData.name}</div> <div className='TableNode-tableNameText' data-test="table-name">{tableData.name}</div>
</div> </div>
{tableData.columns.length > 0 && <div> {tableData.columns.length > 0 && <div>
{_.map(tableData.columns, (col)=>this.generateColumn(col, localFkCols, localUkCols))} {_.map(tableData.columns, (col)=>this.generateColumn(col, localFkCols, localUkCols))}
</div>} </div>}
</>} </>}
</div>
</StyledDiv> </StyledDiv>
); );
} }