mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 18:56:00 +00:00
Merge branch 'master' into feature/translation
This commit is contained in:
@@ -125,8 +125,8 @@
|
||||
{/if}
|
||||
|
||||
<div class="purchase-info">
|
||||
For more info about DbGate licensing, you could visit <Link href="https://dbgate.eu/">dbgate.eu</Link> web or contact
|
||||
us at <Link href="mailto:sales@dbgate.eu">sales@dbgate.eu</Link>
|
||||
For more info about DbGate licensing, you could visit <Link href="https://dbgate.io/">dbgate.io</Link> web or contact
|
||||
us at <Link href="mailto:sales@dbgate.io">sales@dbgate.io</Link>
|
||||
</div>
|
||||
{:else}
|
||||
<ErrorInfo message="License for DbGate is not valid. Please contact administrator." />
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="heading">Configuration error</div>
|
||||
{#if $config?.checkedLicense?.status == 'error'}
|
||||
<ErrorInfo
|
||||
message={`Invalid license. Please contact sales@dbgate.eu for more details. ${$config?.checkedLicense?.error}`}
|
||||
message={`Invalid license. Please contact sales@dbgate.io for more details. ${$config?.checkedLicense?.error}`}
|
||||
/>
|
||||
{:else if $config?.configurationError}
|
||||
<ErrorInfo message={$config?.configurationError} />
|
||||
|
||||
@@ -468,12 +468,14 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
||||
|
||||
{ divider: true },
|
||||
isSqlOrDoc &&
|
||||
isProApp() &&
|
||||
!connection.isReadOnly &&
|
||||
hasPermission(`dbops/import`) && {
|
||||
onClick: handleImport,
|
||||
text: _t('database.import', { defaultMessage: 'Import' }),
|
||||
},
|
||||
isSqlOrDoc &&
|
||||
isProApp() &&
|
||||
hasPermission(`dbops/export`) && {
|
||||
onClick: handleExport,
|
||||
text: _t('database.export', { defaultMessage: 'Export' }),
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
export let title = null;
|
||||
export let skipWidth = false;
|
||||
export let outline = false;
|
||||
export let colorClass = '';
|
||||
|
||||
function handleClick() {
|
||||
if (!disabled) dispatch('click');
|
||||
@@ -31,6 +32,8 @@
|
||||
bind:this={domButton}
|
||||
class:skipWidth
|
||||
class:outline
|
||||
class={colorClass}
|
||||
class:setBackgroundColor={!colorClass}
|
||||
/>
|
||||
|
||||
<style>
|
||||
@@ -38,19 +41,26 @@
|
||||
border: 1px solid var(--theme-bg-button-inv-2);
|
||||
padding: 5px;
|
||||
margin: 2px;
|
||||
background-color: var(--theme-bg-button-inv);
|
||||
color: var(--theme-font-inv-1);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.setBackgroundColor {
|
||||
background-color: var(--theme-bg-button-inv);
|
||||
}
|
||||
|
||||
input:not(.skipWidth) {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
input:hover:not(.disabled):not(.outline) {
|
||||
input:not(.setBackgroundColor) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input.setBackgroundColor:hover:not(.disabled):not(.outline) {
|
||||
background-color: var(--theme-bg-button-inv-2);
|
||||
}
|
||||
input:active:not(.disabled):not(.outline) {
|
||||
input.setBackgroundColor:active:not(.disabled):not(.outline) {
|
||||
background-color: var(--theme-bg-button-inv-3);
|
||||
}
|
||||
input.disabled {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
getCloudSigninTokenHolder,
|
||||
getExtensions,
|
||||
getVisibleToolbar,
|
||||
promoWidgetPreview,
|
||||
visibleToolbar,
|
||||
visibleWidgetSideBar,
|
||||
} from '../stores';
|
||||
@@ -50,6 +51,7 @@ import { isProApp } from '../utility/proTools';
|
||||
import { openWebLink } from '../utility/simpleTools';
|
||||
import { _t } from '../translations';
|
||||
import ExportImportConnectionsModal from '../modals/ExportImportConnectionsModal.svelte';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
|
||||
// function themeCommand(theme: ThemeDefinition) {
|
||||
// return {
|
||||
@@ -689,7 +691,7 @@ registerCommand({
|
||||
name: 'Export database',
|
||||
toolbar: true,
|
||||
icon: 'icon export',
|
||||
testEnabled: () => getCurrentDatabase() != null && hasPermission(`dbops/export`),
|
||||
testEnabled: () => getCurrentDatabase() != null && hasPermission(`dbops/export`) && isProApp(),
|
||||
onClick: () => {
|
||||
openImportExportTab({
|
||||
targetStorageType: getDefaultFileFormat(getExtensions()).storageType,
|
||||
@@ -1164,6 +1166,41 @@ registerCommand({
|
||||
onClick: () => currentDatabase.set(null),
|
||||
});
|
||||
|
||||
let loadedCampaignList = [];
|
||||
|
||||
registerCommand({
|
||||
id: 'internal.loadCampaigns',
|
||||
category: 'Internal',
|
||||
name: 'Load campaign list',
|
||||
testEnabled: () => getBoolSettingsValue('internal.showCampaigns', false),
|
||||
onClick: async () => {
|
||||
const resp = await apiCall('cloud/promo-widget-list', {});
|
||||
loadedCampaignList = resp;
|
||||
},
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'internal.showCampaigns',
|
||||
category: 'Internal',
|
||||
name: 'Show campaigns',
|
||||
testEnabled: () => getBoolSettingsValue('internal.showCampaigns', false) && loadedCampaignList?.length > 0,
|
||||
getSubCommands: () => {
|
||||
return loadedCampaignList.map(campaign => ({
|
||||
text: `${campaign.campaignName} (${campaign.countries || 'Global'}) - #${campaign.quantileRank ?? '*'}/${
|
||||
campaign.quantileGroupCount ?? '*'
|
||||
} - ${campaign.variantIdentifier}`,
|
||||
onClick: async () => {
|
||||
promoWidgetPreview.set(
|
||||
await apiCall('cloud/promo-widget-preview', {
|
||||
campaign: campaign.campaignIdentifier,
|
||||
variant: campaign.variantIdentifier,
|
||||
})
|
||||
);
|
||||
},
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
const electron = getElectron();
|
||||
if (electron) {
|
||||
electron.addEventListener('run-command', (e, commandId) => runCommand(commandId));
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import DefineDictionaryDescriptionModal from '../modals/DefineDictionaryDescriptionModal.svelte';
|
||||
import { sleep } from '../utility/common';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
|
||||
export let column;
|
||||
export let conid = undefined;
|
||||
@@ -72,29 +73,35 @@
|
||||
|
||||
column.foreignKey && [{ divider: true }, { onClick: openReferencedTable, text: column.foreignKey.refTableName }],
|
||||
|
||||
setGrouping && { divider: true },
|
||||
setGrouping && { onClick: () => setGrouping('GROUP'), text: 'Group by' },
|
||||
setGrouping && { onClick: () => setGrouping('MAX'), text: 'MAX' },
|
||||
setGrouping && { onClick: () => setGrouping('MIN'), text: 'MIN' },
|
||||
setGrouping && { onClick: () => setGrouping('SUM'), text: 'SUM' },
|
||||
setGrouping && { onClick: () => setGrouping('AVG'), text: 'AVG' },
|
||||
setGrouping && { onClick: () => setGrouping('COUNT'), text: 'COUNT' },
|
||||
setGrouping && { onClick: () => setGrouping('COUNT DISTINCT'), text: 'COUNT DISTINCT' },
|
||||
isProApp() &&
|
||||
setGrouping && [
|
||||
{ divider: true },
|
||||
{ onClick: () => setGrouping('GROUP'), text: 'Group by' },
|
||||
{ onClick: () => setGrouping('MAX'), text: 'MAX' },
|
||||
{ onClick: () => setGrouping('MIN'), text: 'MIN' },
|
||||
{ onClick: () => setGrouping('SUM'), text: 'SUM' },
|
||||
{ onClick: () => setGrouping('AVG'), text: 'AVG' },
|
||||
{ onClick: () => setGrouping('COUNT'), text: 'COUNT' },
|
||||
{ onClick: () => setGrouping('COUNT DISTINCT'), text: 'COUNT DISTINCT' },
|
||||
],
|
||||
|
||||
isTypeDateTime(column.dataType) && [
|
||||
{ divider: true },
|
||||
{ onClick: () => setGrouping('GROUP:YEAR'), text: 'Group by YEAR' },
|
||||
{ onClick: () => setGrouping('GROUP:MONTH'), text: 'Group by MONTH' },
|
||||
{ onClick: () => setGrouping('GROUP:DAY'), text: 'Group by DAY' },
|
||||
],
|
||||
isProApp() &&
|
||||
isTypeDateTime(column.dataType) && [
|
||||
{ divider: true },
|
||||
{ onClick: () => setGrouping('GROUP:YEAR'), text: 'Group by YEAR' },
|
||||
{ onClick: () => setGrouping('GROUP:MONTH'), text: 'Group by MONTH' },
|
||||
{ onClick: () => setGrouping('GROUP:DAY'), text: 'Group by DAY' },
|
||||
],
|
||||
|
||||
{ divider: true },
|
||||
|
||||
allowDefineVirtualReferences && { onClick: handleDefineVirtualForeignKey, text: 'Define virtual foreign key' },
|
||||
column.foreignKey && {
|
||||
onClick: handleCustomizeDescriptions,
|
||||
text: 'Customize description',
|
||||
},
|
||||
isProApp() &&
|
||||
allowDefineVirtualReferences && { onClick: handleDefineVirtualForeignKey, text: 'Define virtual foreign key' },
|
||||
column.foreignKey &&
|
||||
isProApp() && {
|
||||
onClick: handleCustomizeDescriptions,
|
||||
text: 'Customize description',
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import ColumnEditorModal from '../tableeditor/ColumnEditorModal.svelte';
|
||||
import { editorDeleteColumn } from 'dbgate-tools';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
|
||||
export let column;
|
||||
export let display;
|
||||
@@ -59,13 +60,17 @@
|
||||
on:mouseup
|
||||
>
|
||||
<div>
|
||||
<span class="expandColumnIcon" style={`margin-right: ${5 + (column.uniquePath.length - 1) * 10}px`}>
|
||||
<FontIcon
|
||||
icon={column.isExpandable ? plusExpandIcon(display.isExpandedColumn(column.uniqueName)) : 'icon invisible-box'}
|
||||
on:click={() => display.toggleExpandedColumn(column.uniqueName)}
|
||||
data-testid="ColumnManagerRow_expand_{column.uniqueName}"
|
||||
/>
|
||||
</span>
|
||||
{#if isProApp()}
|
||||
<span class="expandColumnIcon" style={`margin-right: ${5 + (column.uniquePath.length - 1) * 10}px`}>
|
||||
<FontIcon
|
||||
icon={column.isExpandable
|
||||
? plusExpandIcon(display.isExpandedColumn(column.uniqueName))
|
||||
: 'icon invisible-box'}
|
||||
on:click={() => display.toggleExpandedColumn(column.uniqueName)}
|
||||
data-testid="ColumnManagerRow_expand_{column.uniqueName}"
|
||||
/>
|
||||
</span>
|
||||
{/if}
|
||||
{#if isJsonView}
|
||||
<FontIcon icon="img column" />
|
||||
{:else}
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
import { getLocalStorage, setLocalStorage } from '../utility/storageCache';
|
||||
import { __t, _t } from '../translations';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
|
||||
export let config;
|
||||
export let setConfig;
|
||||
@@ -206,7 +207,7 @@
|
||||
name="references"
|
||||
height="30%"
|
||||
collapsed={isDetailView}
|
||||
skip={!(showReferences && display?.hasReferences)}
|
||||
skip={!(showReferences && display?.hasReferences && isProApp())}
|
||||
data-testid="DataGrid_itemReferences"
|
||||
>
|
||||
<ReferenceManager {...$$props} {managerSize} />
|
||||
@@ -215,7 +216,7 @@
|
||||
<WidgetColumnBarItem
|
||||
title={_t('dataGrid.macros', { defaultMessage: 'Macros' })}
|
||||
name="macros"
|
||||
skip={!showMacros}
|
||||
skip={!(showMacros && isProApp())}
|
||||
collapsed={!expandMacros}
|
||||
data-testid="DataGrid_itemMacros"
|
||||
>
|
||||
|
||||
@@ -1413,7 +1413,11 @@
|
||||
|
||||
function handleGridWheel(event) {
|
||||
if (event.shiftKey) {
|
||||
scrollHorizontal(event.deltaY, event.deltaX);
|
||||
if (isMac()) {
|
||||
scrollHorizontal(event.deltaX, event.deltaY);
|
||||
} else {
|
||||
scrollHorizontal(event.deltaY, event.deltaX);
|
||||
}
|
||||
} else {
|
||||
scrollHorizontal(event.deltaX, event.deltaY);
|
||||
scrollVertical(event.deltaX, event.deltaY);
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
{/if}
|
||||
|
||||
{#if dependencies.length > 0}
|
||||
<div class="bold nowrap ml-1">Dependend tables ({dependencies.length})</div>
|
||||
<div class="bold nowrap ml-1">Dependent tables ({dependencies.length})</div>
|
||||
{#each dependencies.filter(fk => filterName(filter, fk.pureName)) as fk}
|
||||
<div
|
||||
class="link"
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
import SqlFormView from '../formview/SqlFormView.svelte';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
import { getDictionaryDescription } from '../utility/dictionaryDescriptionTools';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
@@ -82,7 +83,8 @@
|
||||
extendedDbInfo?.tables?.find(x => x.pureName == pureName && x.schemaName == schemaName)
|
||||
?.tablePermissionRole == 'read',
|
||||
isRawMode,
|
||||
$settingsValue
|
||||
$settingsValue,
|
||||
isProApp()
|
||||
)
|
||||
: null;
|
||||
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
<script lang="ts">
|
||||
import JsonUiCountdown from './JsonUiCountdown.svelte';
|
||||
import JsonUiHeading from './JsonUiHeading.svelte';
|
||||
import JsonUiHighlight from './JsonUiHighlight.svelte';
|
||||
import JsonUiLinkButton from './JsonUiLinkButton.svelte';
|
||||
import JsonUiLinkButtonBlock from './JsonUiLinkButtonBlock.svelte';
|
||||
import JsonUiMarkdown from './JsonUiMarkdown.svelte';
|
||||
import JsonUiTextBlock from './JsonUiTextBlock.svelte';
|
||||
import JsonUiTickList from './JsonUiTickList.svelte';
|
||||
import { JsonUiBlock } from './jsonuitypes';
|
||||
|
||||
export let blocks: JsonUiBlock[] = [];
|
||||
export let passProps = {};
|
||||
|
||||
const componentMap = {
|
||||
text: JsonUiTextBlock,
|
||||
heading: JsonUiHeading,
|
||||
ticklist: JsonUiTickList,
|
||||
button: JsonUiLinkButton,
|
||||
markdown: JsonUiMarkdown,
|
||||
highlight: JsonUiHighlight,
|
||||
countdown: JsonUiCountdown,
|
||||
buttonblock: JsonUiLinkButtonBlock,
|
||||
} as const;
|
||||
</script>
|
||||
|
||||
{#each blocks as block, i}
|
||||
{#if block.type in componentMap}
|
||||
<svelte:component this={componentMap[block.type]} {...block} />
|
||||
<svelte:component this={componentMap[block.type]} {...block} {...passProps} />
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
87
packages/web/src/jsonui/JsonUiCountdown.svelte
Normal file
87
packages/web/src/jsonui/JsonUiCountdown.svelte
Normal file
@@ -0,0 +1,87 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { openWebLink } from '../utility/simpleTools';
|
||||
|
||||
export let colorClass: string = 'premium-gradient';
|
||||
export let validTo;
|
||||
export let link;
|
||||
|
||||
function formatRemaining(validTo, now) {
|
||||
let diffMs = validTo.getTime() - now.getTime();
|
||||
if (diffMs <= 0) return '0 minutes';
|
||||
|
||||
const totalMinutes = Math.floor(diffMs / 60000);
|
||||
const days = Math.floor(totalMinutes / (24 * 60));
|
||||
const hours = Math.floor((totalMinutes % (24 * 60)) / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
|
||||
const parts = [];
|
||||
const en = (n, unit) => ({
|
||||
num: n,
|
||||
unit: n == 1 ? unit : unit + 's',
|
||||
});
|
||||
|
||||
if (days) parts.push(en(days, 'day'));
|
||||
if (hours) parts.push(en(hours, 'hour'));
|
||||
// Always include minutes to report down to minutes
|
||||
parts.push(en(minutes, 'minute'));
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
let currentDate = new Date();
|
||||
|
||||
onMount(() => {
|
||||
const interval = setInterval(() => {
|
||||
currentDate = new Date();
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
});
|
||||
|
||||
$: parts = formatRemaining(new Date(validTo), currentDate);
|
||||
</script>
|
||||
|
||||
{#if validTo}
|
||||
<div
|
||||
class="countdown {colorClass}"
|
||||
class:isLink={!!link}
|
||||
on:click={() => {
|
||||
if (link) {
|
||||
openWebLink(link);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span class="big">Offer ends in:</span><br />
|
||||
{#each parts as part}
|
||||
<span class="part">
|
||||
<span class="big">{part.num}</span>
|
||||
{part.unit}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.countdown {
|
||||
text-align: center;
|
||||
margin: 10px;
|
||||
border: 1px solid;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.countdown.isLink {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.big {
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.part {
|
||||
margin: 0 5px;
|
||||
}
|
||||
</style>
|
||||
35
packages/web/src/jsonui/JsonUiHighlight.svelte
Normal file
35
packages/web/src/jsonui/JsonUiHighlight.svelte
Normal file
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { openWebLink } from '../utility/simpleTools';
|
||||
|
||||
export let text: string;
|
||||
export let colorClass: string = 'premium-gradient';
|
||||
export let link: string;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="highlight {colorClass}"
|
||||
class:isLink={!!link}
|
||||
on:click={() => {
|
||||
if (link) {
|
||||
openWebLink(link);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.highlight {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin: 10px;
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
border: 1px solid;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.highlight.isLink {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -4,14 +4,11 @@
|
||||
|
||||
export let text: string;
|
||||
export let link: string;
|
||||
export let newTab: boolean = true;
|
||||
|
||||
// very light url guard
|
||||
const safe = /^(https?:)?\/\//i.test(link) || link.startsWith('/');
|
||||
export let colorClass: string = '';
|
||||
</script>
|
||||
|
||||
<div class="center">
|
||||
<FormStyledButton on:click={() => openWebLink(link)} value={text} skipWidth />
|
||||
<FormStyledButton on:click={() => openWebLink(link)} value={text} skipWidth {colorClass} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
21
packages/web/src/jsonui/JsonUiLinkButtonBlock.svelte
Normal file
21
packages/web/src/jsonui/JsonUiLinkButtonBlock.svelte
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||
import { openWebLink } from '../utility/simpleTools';
|
||||
|
||||
export let text: string;
|
||||
export let link: string;
|
||||
export let colorClass: string = '';
|
||||
export let items: any[] = [];
|
||||
</script>
|
||||
|
||||
<div class="center">
|
||||
{#each items as item}
|
||||
<FormStyledButton on:click={() => openWebLink(item.link)} value={item.text} skipWidth {colorClass} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
15
packages/web/src/jsonui/JsonUiMarkdown.svelte
Normal file
15
packages/web/src/jsonui/JsonUiMarkdown.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import Markdown from '../elements/Markdown.svelte';
|
||||
|
||||
export let text: string;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Markdown source={text} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
margin: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -95,6 +95,7 @@
|
||||
title: _t('common.exportDatabase', { defaultMessage: 'Export database' }),
|
||||
description: _t('newObject.exportDescription', { defaultMessage: 'Export to file like CSV, JSON, Excel, or other DB' }),
|
||||
command: 'database.export',
|
||||
isProFeature: true,
|
||||
testid: 'NewObjectModal_databaseExport',
|
||||
disabledMessage: _t('newObject.exportDisabled', { defaultMessage: 'Export is not available for current database' }),
|
||||
},
|
||||
|
||||
@@ -187,6 +187,9 @@ export const seenPremiumPromoWidget = writableWithStorage(null, 'seenPremiumProm
|
||||
|
||||
export const cloudConnectionsStore = writable({});
|
||||
|
||||
export const promoWidgetPreview = writable(null);
|
||||
|
||||
|
||||
export const DEFAULT_OBJECT_SEARCH_SETTINGS = {
|
||||
pureName: true,
|
||||
schemaName: false,
|
||||
|
||||
@@ -358,6 +358,8 @@
|
||||
import { handleAfterTabClick } from '../utility/changeCurrentDbByTab';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
import NewObjectModal from '../modals/NewObjectModal.svelte';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import { openWebLink } from '../utility/simpleTools';
|
||||
|
||||
export let multiTabIndex;
|
||||
export let shownTab;
|
||||
@@ -583,7 +585,13 @@
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<div class="tabs" class:can-split={allowSplitTab} on:wheel={handleTabsWheel} bind:this={domTabs}>
|
||||
<div
|
||||
class="tabs"
|
||||
class:can-split={allowSplitTab && isProApp()}
|
||||
class:tabs-upgrade-button={!isProApp()}
|
||||
on:wheel={handleTabsWheel}
|
||||
bind:this={domTabs}
|
||||
>
|
||||
{#each groupedTabs as tabGroup}
|
||||
<div class="db-wrapper">
|
||||
{#if !$lockedDatabaseMode}
|
||||
@@ -713,7 +721,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
<div class="icons-wrapper">
|
||||
{#if allowSplitTab}
|
||||
{#if allowSplitTab && isProApp()}
|
||||
<div
|
||||
class="icon-button"
|
||||
on:click={() => splitTab(multiTabIndex)}
|
||||
@@ -723,6 +731,22 @@
|
||||
<FontIcon icon="icon split" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !isProApp()}
|
||||
<div
|
||||
class="upgrade-button"
|
||||
on:click={() => {
|
||||
openWebLink(
|
||||
`https://www.dbgate.io/purchase/${isElectronAvailable() ? 'premium' : 'team-premium'}/?utm_campaign=premiumUpgradeButton`
|
||||
);
|
||||
}}
|
||||
title="Upgrade to Premium"
|
||||
data-testid="TabsPanel_buttonUpgrade"
|
||||
>
|
||||
<FontIcon icon="icon premium" padRight /> Upgrade
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="icon-button"
|
||||
on:click={() => showModal(NewObjectModal, { multiTabIndex })}
|
||||
@@ -756,6 +780,18 @@
|
||||
color: var(--theme-font-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
.upgrade-button {
|
||||
background: linear-gradient(135deg, #1686c8, #8a25b1);
|
||||
border: 1px solid var(--theme-border);
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 10pt;
|
||||
padding: 5px;
|
||||
}
|
||||
.upgrade-button:hover {
|
||||
background: linear-gradient(135deg, #0f5a85, #5c1870);
|
||||
}
|
||||
.icon-button:hover {
|
||||
color: var(--theme-font-1);
|
||||
}
|
||||
@@ -769,6 +805,10 @@
|
||||
right: 35px;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.tabs-upgrade-button {
|
||||
right: 120px;
|
||||
}
|
||||
.tabs.can-split {
|
||||
right: 60px;
|
||||
}
|
||||
|
||||
@@ -266,9 +266,11 @@
|
||||
if (sid) {
|
||||
apiOn(`session-done-${sid}`, handleSessionDone);
|
||||
apiOn(`session-closed-${sid}`, handleSessionClosed);
|
||||
apiOn(`session-changedb-${sid}`, handleChangedDatabase);
|
||||
return () => {
|
||||
apiOff(`session-done-${sid}`, handleSessionDone);
|
||||
apiOff(`session-closed-${sid}`, handleSessionClosed);
|
||||
apiOff(`session-changedb-${sid}`, handleChangedDatabase);
|
||||
};
|
||||
}
|
||||
return () => {};
|
||||
@@ -567,6 +569,17 @@
|
||||
handleSessionDone();
|
||||
};
|
||||
|
||||
const handleChangedDatabase = async props => {
|
||||
changeTab(tabid, tab => ({
|
||||
...tab,
|
||||
props: {
|
||||
...tab.props,
|
||||
conid,
|
||||
database: props.database,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const { editorState, editorValue, setEditorData } = useEditorData({
|
||||
tabid,
|
||||
loadFromArgs:
|
||||
|
||||
@@ -2,11 +2,12 @@ import type { QuickExportDefinition } from 'dbgate-types';
|
||||
import { currentArchive, getCurrentArchive, getExtensions } from '../stores';
|
||||
import hasPermission from './hasPermission';
|
||||
import { _t } from '../translations'
|
||||
import { isProApp } from './proTools';
|
||||
|
||||
export function createQuickExportMenuItems(handler: (fmt: QuickExportDefinition) => Function, advancedExportMenuItem) {
|
||||
const extensions = getExtensions();
|
||||
return [
|
||||
{
|
||||
isProApp() && {
|
||||
text: _t('export.exportAdvanced', { defaultMessage : 'Export advanced...'}),
|
||||
...advancedExportMenuItem,
|
||||
},
|
||||
@@ -16,7 +17,7 @@ export function createQuickExportMenuItems(handler: (fmt: QuickExportDefinition)
|
||||
onClick: handler(fmt),
|
||||
})),
|
||||
{ divider: true },
|
||||
{
|
||||
isProApp() && {
|
||||
text: _t('export.currentArchive', { defaultMessage : 'Current archive'}),
|
||||
onClick: handler({
|
||||
extension: 'jsonl',
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<script lang="ts">
|
||||
import JsonUiContentRenderer from '../jsonui/JsonUiContentRenderer.svelte';
|
||||
import { promoWidgetPreview } from '../stores';
|
||||
import { usePromoWidget } from '../utility/metadataLoaders';
|
||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||
|
||||
const promoWidget = usePromoWidget({});
|
||||
|
||||
$: promoWidgetData = $promoWidgetPreview || $promoWidget;
|
||||
</script>
|
||||
|
||||
<WidgetsInnerContainer>
|
||||
{#if $promoWidget?.state == 'data'}
|
||||
<JsonUiContentRenderer blocks={$promoWidget?.blocks} />
|
||||
{#if promoWidgetData?.state == 'data'}
|
||||
<JsonUiContentRenderer blocks={promoWidgetData?.blocks} passProps={{ validTo: promoWidgetData?.validTo }} />
|
||||
{/if}
|
||||
</WidgetsInnerContainer>
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
import { useConnectionColor } from '../utility/useConnectionColor';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { statusBarTabInfo } from '../utility/statusBarStore';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
|
||||
$: databaseName = $currentDatabase && $currentDatabase.name;
|
||||
$: connection = $currentDatabase && $currentDatabase.connection;
|
||||
@@ -155,7 +156,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $currentArchive && $currentArchive != 'default'}
|
||||
{#if isProApp() && $currentArchive && $currentArchive != 'default'}
|
||||
<div
|
||||
class="item flex clickable"
|
||||
title="Current archive"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import PublicCloudWidget from './PublicCloudWidget.svelte';
|
||||
import PrivateCloudWidget from './PrivateCloudWidget.svelte';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
</script>
|
||||
|
||||
{#if hasPermission('widgets/database')}
|
||||
@@ -22,7 +23,7 @@
|
||||
{#if $visibleSelectedWidget == 'history' && hasPermission('widgets/history')}
|
||||
<HistoryWidget />
|
||||
{/if}
|
||||
{#if $visibleSelectedWidget == 'archive' && hasPermission('widgets/archive')}
|
||||
{#if $visibleSelectedWidget == 'archive' && hasPermission('widgets/archive') && isProApp()}
|
||||
<ArchiveWidget />
|
||||
{/if}
|
||||
{#if $visibleSelectedWidget == 'plugins' && hasPermission('widgets/plugins')}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
getCurrentConfig,
|
||||
cloudSigninTokenHolder,
|
||||
seenPremiumPromoWidget,
|
||||
promoWidgetPreview,
|
||||
} from '../stores';
|
||||
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
@@ -60,7 +61,7 @@
|
||||
name: 'history',
|
||||
title: 'Query history & Closed tabs',
|
||||
},
|
||||
{
|
||||
isProApp() && {
|
||||
icon: 'icon archive',
|
||||
name: 'archive',
|
||||
title: 'Archive (saved tabular data)',
|
||||
@@ -167,6 +168,8 @@
|
||||
openWebLink(url, true);
|
||||
}
|
||||
}
|
||||
|
||||
$: promoWidgetData = $promoWidgetPreview || $promoWidget;
|
||||
</script>
|
||||
|
||||
<div class="main">
|
||||
@@ -177,7 +180,7 @@
|
||||
{/if}
|
||||
{#each widgets
|
||||
.filter(x => x && hasPermission(`widgets/${x.name}`))
|
||||
.filter(x => !x.isPremiumPromo || (!isProApp() && $promoWidget?.state == 'data'))
|
||||
.filter(x => !x.isPremiumPromo || (!isProApp() && promoWidgetData?.state == 'data'))
|
||||
// .filter(x => !x.isPremiumOnly || isProApp())
|
||||
.filter(x => x.name != 'cloud-private' || $cloudSigninTokenHolder) as item}
|
||||
<div
|
||||
@@ -186,10 +189,18 @@
|
||||
data-testid={`WidgetIconPanel_${item.name}`}
|
||||
on:click={() => handleChangeWidget(item.name)}
|
||||
>
|
||||
<FontIcon icon={item.icon} title={item.title} />
|
||||
{#if item.isPremiumPromo && promoWidgetData?.isColoredIcon}
|
||||
<FontIcon
|
||||
icon={item.icon}
|
||||
title={item.title}
|
||||
colorClass="premium-background-gradient widget-icon-panel-rounded"
|
||||
/>
|
||||
{:else}
|
||||
<FontIcon icon={item.icon} title={item.title} />
|
||||
{/if}
|
||||
{#if item.isPremiumPromo}
|
||||
<div class="premium-promo">Premium</div>
|
||||
{#if $promoWidget?.identifier != $seenPremiumPromoWidget}
|
||||
{#if promoWidgetData?.identifier != $seenPremiumPromoWidget}
|
||||
<div class="premium-promo-not-seen">•</div>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -281,4 +292,9 @@
|
||||
top: -5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
:global(.widget-icon-panel-rounded) {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user