This commit is contained in:
SPRINX0\prochazka
2025-10-27 13:10:45 +01:00
parent c741434e3c
commit 64c2faf538
11 changed files with 195 additions and 6 deletions

View File

@@ -9,6 +9,8 @@ const {
putCloudContent, putCloudContent,
removeCloudCachedConnection, removeCloudCachedConnection,
getPromoWidgetData, getPromoWidgetData,
getPromoWidgetList,
getPromoWidgetPreview,
} = require('../utility/cloudIntf'); } = require('../utility/cloudIntf');
const connections = require('./connections'); const connections = require('./connections');
const socket = require('../utility/socket'); const socket = require('../utility/socket');
@@ -296,6 +298,16 @@ module.exports = {
return data; return data;
}, },
promoWidgetList_meta: true,
async promoWidgetList() {
return getPromoWidgetList();
},
promoWidgetPreview_meta: true,
async promoWidgetPreview({ campaign, variant }) {
return getPromoWidgetPreview(campaign, variant);
},
// chatStream_meta: { // chatStream_meta: {
// raw: true, // raw: true,
// method: 'post', // method: 'post',

View File

@@ -480,6 +480,16 @@ async function getPromoWidgetData() {
return promoWidgetData; return promoWidgetData;
} }
async function getPromoWidgetPreview(campaign, variant) {
const resp = await axios.default.get(`${DBGATE_CLOUD_URL}/premium-promo-widget-preview/${campaign}/${variant}`);
return resp.data;
}
async function getPromoWidgetList() {
const resp = await axios.default.get(`${DBGATE_CLOUD_URL}/promo-widget-list`);
return resp.data;
}
module.exports = { module.exports = {
createDbGateIdentitySession, createDbGateIdentitySession,
startCloudTokenChecking, startCloudTokenChecking,
@@ -498,4 +508,6 @@ module.exports = {
readCloudTestTokenHolder, readCloudTestTokenHolder,
getPublicIpInfo, getPublicIpInfo,
getPromoWidgetData, getPromoWidgetData,
getPromoWidgetPreview,
getPromoWidgetList,
}; };

View File

@@ -35,6 +35,11 @@
background: linear-gradient(135deg, #1686c8, #8a25b1); background: linear-gradient(135deg, #1686c8, #8a25b1);
} }
.premium-gradient {
background: linear-gradient(135deg, #1686c8, #8a25b1);
color: white;
}
.web-color-primary { .web-color-primary {
background: #1686c8; background: #1686c8;
} }

View File

@@ -8,6 +8,7 @@ import {
getCloudSigninTokenHolder, getCloudSigninTokenHolder,
getExtensions, getExtensions,
getVisibleToolbar, getVisibleToolbar,
promoWidgetPreview,
visibleToolbar, visibleToolbar,
visibleWidgetSideBar, visibleWidgetSideBar,
} from '../stores'; } from '../stores';
@@ -50,6 +51,7 @@ import { isProApp } from '../utility/proTools';
import { openWebLink } from '../utility/simpleTools'; import { openWebLink } from '../utility/simpleTools';
import { _t } from '../translations'; import { _t } from '../translations';
import ExportImportConnectionsModal from '../modals/ExportImportConnectionsModal.svelte'; import ExportImportConnectionsModal from '../modals/ExportImportConnectionsModal.svelte';
import { getBoolSettingsValue } from '../settings/settingsTools';
// function themeCommand(theme: ThemeDefinition) { // function themeCommand(theme: ThemeDefinition) {
// return { // return {
@@ -1164,6 +1166,41 @@ registerCommand({
onClick: () => currentDatabase.set(null), 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(); const electron = getElectron();
if (electron) { if (electron) {
electron.addEventListener('run-command', (e, commandId) => runCommand(commandId)); electron.addEventListener('run-command', (e, commandId) => runCommand(commandId));

View File

@@ -1,22 +1,29 @@
<script lang="ts"> <script lang="ts">
import JsonUiCountdown from './JsonUiCountdown.svelte';
import JsonUiHeading from './JsonUiHeading.svelte'; import JsonUiHeading from './JsonUiHeading.svelte';
import JsonUiHighlight from './JsonUiHighlight.svelte';
import JsonUiLinkButton from './JsonUiLinkButton.svelte'; import JsonUiLinkButton from './JsonUiLinkButton.svelte';
import JsonUiMarkdown from './JsonUiMarkdown.svelte';
import JsonUiTextBlock from './JsonUiTextBlock.svelte'; import JsonUiTextBlock from './JsonUiTextBlock.svelte';
import JsonUiTickList from './JsonUiTickList.svelte'; import JsonUiTickList from './JsonUiTickList.svelte';
import { JsonUiBlock } from './jsonuitypes'; import { JsonUiBlock } from './jsonuitypes';
export let blocks: JsonUiBlock[] = []; export let blocks: JsonUiBlock[] = [];
export let passProps = {};
const componentMap = { const componentMap = {
text: JsonUiTextBlock, text: JsonUiTextBlock,
heading: JsonUiHeading, heading: JsonUiHeading,
ticklist: JsonUiTickList, ticklist: JsonUiTickList,
button: JsonUiLinkButton, button: JsonUiLinkButton,
markdown: JsonUiMarkdown,
highlight: JsonUiHighlight,
countdown: JsonUiCountdown,
} as const; } as const;
</script> </script>
{#each blocks as block, i} {#each blocks as block, i}
{#if block.type in componentMap} {#if block.type in componentMap}
<svelte:component this={componentMap[block.type]} {...block} /> <svelte:component this={componentMap[block.type]} {...block} {...passProps} />
{/if} {/if}
{/each} {/each}

View File

@@ -0,0 +1,73 @@
<script lang="ts">
import { onMount } from 'svelte';
export let colorClass: string = 'premium-gradient';
export let validTo;
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}">
<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;
}
.big {
font-size: large;
font-weight: bold;
}
.part {
margin: 0 5px;
}
</style>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
export let text: string;
export let colorClass: string = 'premium-gradient';
</script>
<div class="highlight {colorClass}">
{text}
</div>
<style>
.highlight {
text-align: center;
margin: 10px;
font-size: large;
font-weight: bold;
border: 1px solid;
padding: 5px;
}
</style>

View 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>

View File

@@ -187,6 +187,9 @@ export const seenPremiumPromoWidget = writableWithStorage(null, 'seenPremiumProm
export const cloudConnectionsStore = writable({}); export const cloudConnectionsStore = writable({});
export const promoWidgetPreview = writable(null);
export const DEFAULT_OBJECT_SEARCH_SETTINGS = { export const DEFAULT_OBJECT_SEARCH_SETTINGS = {
pureName: true, pureName: true,
schemaName: false, schemaName: false,

View File

@@ -1,14 +1,17 @@
<script lang="ts"> <script lang="ts">
import JsonUiContentRenderer from '../jsonui/JsonUiContentRenderer.svelte'; import JsonUiContentRenderer from '../jsonui/JsonUiContentRenderer.svelte';
import { promoWidgetPreview } from '../stores';
import { usePromoWidget } from '../utility/metadataLoaders'; import { usePromoWidget } from '../utility/metadataLoaders';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte'; import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
const promoWidget = usePromoWidget({}); const promoWidget = usePromoWidget({});
$: promoWidgetData = $promoWidgetPreview || $promoWidget;
</script> </script>
<WidgetsInnerContainer> <WidgetsInnerContainer>
{#if $promoWidget?.state == 'data'} {#if promoWidgetData?.state == 'data'}
<JsonUiContentRenderer blocks={$promoWidget?.blocks} /> <JsonUiContentRenderer blocks={promoWidgetData?.blocks} passProps={{ validTo: promoWidgetData?.validTo }} />
{/if} {/if}
</WidgetsInnerContainer> </WidgetsInnerContainer>

View File

@@ -11,6 +11,7 @@
getCurrentConfig, getCurrentConfig,
cloudSigninTokenHolder, cloudSigninTokenHolder,
seenPremiumPromoWidget, seenPremiumPromoWidget,
promoWidgetPreview,
} from '../stores'; } from '../stores';
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition'; import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
import hasPermission from '../utility/hasPermission'; import hasPermission from '../utility/hasPermission';
@@ -167,6 +168,8 @@
openWebLink(url, true); openWebLink(url, true);
} }
} }
$: promoWidgetData = $promoWidgetPreview || $promoWidget;
</script> </script>
<div class="main"> <div class="main">
@@ -177,7 +180,7 @@
{/if} {/if}
{#each widgets {#each widgets
.filter(x => x && hasPermission(`widgets/${x.name}`)) .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.isPremiumOnly || isProApp())
.filter(x => x.name != 'cloud-private' || $cloudSigninTokenHolder) as item} .filter(x => x.name != 'cloud-private' || $cloudSigninTokenHolder) as item}
<div <div
@@ -186,7 +189,7 @@
data-testid={`WidgetIconPanel_${item.name}`} data-testid={`WidgetIconPanel_${item.name}`}
on:click={() => handleChangeWidget(item.name)} on:click={() => handleChangeWidget(item.name)}
> >
{#if item.isPremiumPromo && $promoWidget?.isColoredIcon} {#if item.isPremiumPromo && promoWidgetData?.isColoredIcon}
<FontIcon <FontIcon
icon={item.icon} icon={item.icon}
title={item.title} title={item.title}
@@ -197,7 +200,7 @@
{/if} {/if}
{#if item.isPremiumPromo} {#if item.isPremiumPromo}
<div class="premium-promo">Premium</div> <div class="premium-promo">Premium</div>
{#if $promoWidget?.identifier != $seenPremiumPromoWidget} {#if promoWidgetData?.identifier != $seenPremiumPromoWidget}
<div class="premium-promo-not-seen">•</div> <div class="premium-promo-not-seen">•</div>
{/if} {/if}
{/if} {/if}