Skip to content

Commit 28a9fc2

Browse files
feat(#245): sponsorship message
Shown once every 2 weeks on the initial application page. Last shown date is stored in settings.
1 parent 8a6e56f commit 28a9fc2

File tree

6 files changed

+136
-6
lines changed

6 files changed

+136
-6
lines changed

src/renderer/components/NewJobPane/index.tsx

+59-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
Select a script and submit a new job
33
*/
4-
import { useState } from 'react'
4+
import { useState, useMemo } from 'react'
55
import { ScriptForm } from '../ScriptForm'
66
import { useWindowStore } from 'renderer/store'
77
import { ID } from 'renderer/utils/utils'
@@ -11,11 +11,55 @@ import {
1111
removeJob,
1212
updateJob,
1313
} from 'shared/data/slices/pipeline'
14+
import {
15+
save,
16+
setSponsorshipMessageLastShown,
17+
} from 'shared/data/slices/settings'
18+
import { externalLinkClick } from 'renderer/utils'
1419

1520
const { App } = window
21+
import {
22+
defaultSponsorshipMessage,
23+
updateSponsorshipMessage,
24+
} from '../../utils'
25+
26+
// is datestring more than 2 weeks old
27+
let isExpired = (datestring) => {
28+
if (datestring == '') return true
1629

30+
const TWOWEEKS_MS = 1209600000
31+
let date = new Date(datestring)
32+
let now = Date.now()
33+
if (now - date.getMilliseconds() > TWOWEEKS_MS) {
34+
return true
35+
}
36+
return false
37+
}
1738
export function NewJobPane({ job }: { job: Job }) {
18-
const { pipeline } = useWindowStore()
39+
const { settings, pipeline } = useWindowStore()
40+
const [sponsorshipMessage, setSponsorshipMessage] = useState(
41+
defaultSponsorshipMessage
42+
)
43+
const [showSponsorshipMessage, setShowSponsorshipMessage] = useState(
44+
isExpired(settings.sponsorshipMessageLastShown)
45+
)
46+
47+
// useMemo should only run once per render
48+
useMemo(() => {
49+
const fetchData = async () => {
50+
let updatedSponsorshipMessage = await updateSponsorshipMessage()
51+
setSponsorshipMessage({ ...updatedSponsorshipMessage })
52+
}
53+
fetchData().catch()
54+
if (isExpired(settings.sponsorshipMessageLastShown)) {
55+
setShowSponsorshipMessage(true)
56+
// update settings with a new date
57+
App.store.dispatch(
58+
setSponsorshipMessageLastShown(Date.now().toString())
59+
)
60+
App.store.dispatch(save())
61+
}
62+
}, [])
1963

2064
let onSelectChange = (e) => {
2165
let selection = pipeline.scripts.find(
@@ -108,6 +152,19 @@ export function NewJobPane({ job }: { job: Job }) {
108152
Cancel
109153
</button>
110154
)}
155+
{job.script == null && showSponsorshipMessage ? (
156+
<div className="sponsorship">
157+
<a
158+
href={sponsorshipMessage.url}
159+
onClick={(e) => externalLinkClick(e, App)}
160+
>
161+
{sponsorshipMessage.buttonText}
162+
</a>
163+
<p>{sponsorshipMessage.messageText}</p>
164+
</div>
165+
) : (
166+
''
167+
)}
111168
</>
112169
)
113170
}

src/renderer/style/style.scss

+9
Original file line numberDiff line numberDiff line change
@@ -569,4 +569,13 @@ button.grayedout {
569569

570570
button.grayedout:hover {
571571
background: none !important;
572+
}
573+
574+
.sponsorship {
575+
align-self: center;
576+
border: 2px var(--fg) solid;
577+
bottom: 5%;
578+
position: absolute;
579+
border-radius: 5px;
580+
padding: 10px;
572581
}

src/renderer/utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './utils'
2+
export * from './sponsorship'

src/renderer/utils/sponsorship.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// default message
2+
let defaultSponsorshipMessage = {
3+
active: true,
4+
buttonText: 'Support our work',
5+
messageText:
6+
'If you find this tool useful, please help us by donating to support its ongoing maintenance.',
7+
url: 'https://daisy.org/pipelineappSponsor',
8+
}
9+
10+
async function updateSponsorshipMessage() {
11+
// fetch the latest sponsorship message
12+
try {
13+
let sponsorshipData = await fetch(
14+
'https://dl.daisy.org/tools/sponsorship.json'
15+
)
16+
if (sponsorshipData) {
17+
return sponsorshipData['PipelineApp']['en']
18+
} else {
19+
return defaultSponsorshipMessage
20+
}
21+
} catch (err) {
22+
return defaultSponsorshipMessage
23+
}
24+
}
25+
export { defaultSponsorshipMessage, updateSponsorshipMessage }

src/shared/data/slices/settings.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ export const settings = createSlice({
9999
) => {
100100
state.ttsConfig = action.payload
101101
},
102+
setSponsorshipMessageLastShown: (
103+
state: ApplicationSettings,
104+
action: PayloadAction<string>
105+
) => {
106+
state.sponsorshipMessageLastShown = action.payload
107+
},
102108
},
103109
})
104110

@@ -112,6 +118,7 @@ export const {
112118
setTtsConfig,
113119
setAutoCheckUpdate,
114120
setEditJobOnNewTab,
121+
setSponsorshipMessageLastShown,
115122
} = settings.actions
116123

117124
export const selectors = {
@@ -134,5 +141,5 @@ export const {
134141
selectClosingAction,
135142
selectTtsConfig,
136143
selectAutoCheckUpdate,
137-
selectEditOnNewTab
144+
selectEditOnNewTab,
138145
} = selectors

src/shared/types/settings.ts

+34-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export enum ClosingMainWindowAction {
2424
* - Merged app and job actions on closing main window
2525
*/
2626
export type ApplicationSettings = {
27-
settingsVersion: '1.4.0'
27+
settingsVersion: '1.5.0'
2828
// Default folder to download the results on the user disk
2929
downloadFolder?: string
3030
// Pipeline instance properties for IPCs
@@ -37,6 +37,7 @@ export type ApplicationSettings = {
3737
// tts preferred voices
3838
ttsConfig?: TtsConfig
3939
autoCheckUpdate?: boolean
40+
sponsorshipMessageLastShown?: string
4041
}
4142

4243
export function migrateSettings(
@@ -75,9 +76,19 @@ const migrators: Map<string, (prev: any) => any> = new Map<
7576
>([
7677
// Insert new migrators here as [ 'version', (prev) => ApplicationSettings ]
7778
// Don't forget to update the settings class of previous migrators
79+
[
80+
'1.5.0',
81+
(prev: _ApplicationSettings_v140): ApplicationSettings => {
82+
const { settingsVersion, ...toKeep } = prev
83+
return {
84+
sponsorshipMessageLastShown: '',
85+
...toKeep,
86+
} as ApplicationSettings
87+
},
88+
],
7889
[
7990
'1.4.0',
80-
(prev: _ApplicationSettings_v130): ApplicationSettings => {
91+
(prev: _ApplicationSettings_v130): _ApplicationSettings_v140 => {
8192
const {
8293
// Removed, changed or renamed :
8394
settingsVersion,
@@ -94,7 +105,7 @@ const migrators: Map<string, (prev: any) => any> = new Map<
94105
defaultVoices: [], // new default voices setting
95106
},
96107
...toKeep,
97-
} as ApplicationSettings
108+
} as _ApplicationSettings_v140
98109
},
99110
],
100111
[
@@ -145,6 +156,26 @@ const migrators: Map<string, (prev: any) => any> = new Map<
145156
],
146157
])
147158

159+
export type _ApplicationSettings_v140 = {
160+
settingsVersion: '1.4.0'
161+
// Default folder to download the results on the user disk
162+
downloadFolder?: string
163+
// Pipeline instance properties for IPCs
164+
pipelineInstanceProps?: PipelineInstanceProperties
165+
// Dark mode selector
166+
colorScheme: keyof typeof ColorScheme
167+
// Actions to perform when closing the main window
168+
onClosingMainWindow?: keyof typeof ClosingMainWindowAction
169+
editJobOnNewTab?: boolean
170+
// tts preferred voices
171+
ttsConfig?: {
172+
preferredVoices: Array<TtsVoice>
173+
ttsEngineProperties: Array<TtsEngineProperty>
174+
xmlFilepath?: string
175+
defaultVoices: Array<TtsVoice>
176+
}
177+
autoCheckUpdate?: boolean
178+
}
148179
export type _ApplicationSettings_v130 = {
149180
settingsVersion: '1.3.0'
150181
// Default folder to download the results on the user disk

0 commit comments

Comments
 (0)