{text}
@@ -14,8 +17,7 @@ export const Title0 = ({ text, color = 'text-grey1', margin = 'mb-6 md:mb-8 lg:m
)
}
-export const Title1Factory = (props: TextProps): JSX.Element =>
-export const Title1 = ({ text, color = 'text-grey1', margin = 'mb-6 md:mb-8 lg:mb-10' }: TextProps): JSX.Element => {
+export const Title1 = ({ text, color = 'text-grey1', margin = 'mb-6 md:mb-8 lg:mb-10' }: Weak
): JSX.Element => {
return (
{text}
@@ -23,8 +25,7 @@ export const Title1 = ({ text, color = 'text-grey1', margin = 'mb-6 md:mb-8 lg:m
)
}
-export const Title2Factory = (props: TextProps): JSX.Element =>
-export const Title2 = ({ text, color = 'text-grey1', margin = 'mb-6 md:mb-8 lg:mb-10' }: TextProps): JSX.Element => {
+export const Title2 = ({ text, color = 'text-grey1', margin = 'mb-6 md:mb-8 lg:mb-10' }: Weak
): JSX.Element => {
return (
{text}
diff --git a/src/framework/visualisation/react/ui/pages/donation_page.tsx b/src/framework/visualisation/react/ui/pages/donation_page.tsx
new file mode 100644
index 00000000..0d7ef253
--- /dev/null
+++ b/src/framework/visualisation/react/ui/pages/donation_page.tsx
@@ -0,0 +1,53 @@
+import React from 'react'
+import { Weak } from '../../../../helpers'
+import { Translator } from '../../../../translator'
+import { PropsUIPageDonation } from '../../../../types/pages'
+import { isPropsUIPromptConfirm, isPropsUIPromptConsentForm, isPropsUIPromptFileInput } from '../../../../types/prompts'
+import { ReactFactoryContext } from '../../factory'
+import { Spinner } from '../elements/spinner'
+import { Title0 } from '../elements/text'
+import { Confirm } from '../prompts/confirm'
+import { ConsentForm } from '../prompts/consent_form'
+import { FileInput } from '../prompts/file_input'
+
+interface Copy {
+ title: string
+}
+
+type Props = Weak
& ReactFactoryContext
+
+function prepareCopy ({ header: { title }, locale }: Props): Copy {
+ return {
+ title: Translator.translate(title, locale)
+ }
+}
+
+export const DonationPage = (props: Props): JSX.Element => {
+ const [spinnerHidden] = React.useState(true)
+ const { title } = prepareCopy(props)
+
+ function renderBody (props: Props): JSX.Element {
+ const context = { locale: props.locale, resolve: props.resolve }
+ const body = props.body
+ if (isPropsUIPromptFileInput(body)) {
+ return
+ }
+ if (isPropsUIPromptConfirm(body)) {
+ return
+ }
+ if (isPropsUIPromptConsentForm(body)) {
+ return
+ }
+ throw new TypeError('Unknown body type')
+ }
+
+ return (
+ <>
+
+ {renderBody(props)}
+
+
+
+ >
+ )
+}
diff --git a/src/framework/visualisation/react/ui/pages/end_page.tsx b/src/framework/visualisation/react/ui/pages/end_page.tsx
new file mode 100644
index 00000000..dabbde05
--- /dev/null
+++ b/src/framework/visualisation/react/ui/pages/end_page.tsx
@@ -0,0 +1,12 @@
+import { Weak } from '../../../../helpers'
+import { PropsUIPageEnd } from '../../../../types/pages'
+import { ReactFactoryContext } from '../../factory'
+import { Header } from '../elements/header'
+
+type Props = Weak & ReactFactoryContext
+
+export const EndPage = (props: Props): JSX.Element => {
+ return (
+
+ )
+}
diff --git a/src/framework/visualisation/react/ui/pages/splash_screen.tsx b/src/framework/visualisation/react/ui/pages/splash_screen.tsx
new file mode 100644
index 00000000..9444841d
--- /dev/null
+++ b/src/framework/visualisation/react/ui/pages/splash_screen.tsx
@@ -0,0 +1,44 @@
+import { Weak } from '../../../../helpers'
+import TextBundle from '../../../../text_bundle'
+import { Translator } from '../../../../translator'
+import { PropsUIPageSplashScreen } from '../../../../types/pages'
+import { ReactFactoryContext } from '../../factory'
+import { Spinner } from '../elements/spinner'
+import { BodyLarge, Title0 } from '../elements/text'
+
+interface Copy {
+ title: string
+ description: string
+}
+
+type Props = Weak & ReactFactoryContext
+
+function prepareCopy ({ locale }: Props): Copy {
+ return {
+ title: Translator.translate(title, locale),
+ description: Translator.translate(description, locale)
+ }
+}
+
+export const SplashScreen = (props: Props): JSX.Element => {
+ const { title, description } = prepareCopy(props)
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
+const title = new TextBundle()
+ .add('en', 'Welcome')
+ .add('nl', 'Welkom')
+
+const description = new TextBundle()
+ .add('en', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.')
+ .add('nl', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.')
+
+const spinnerText = new TextBundle()
+ .add('en', 'One moment please..')
+ .add('nl', 'Een moment geduld..')
diff --git a/src/framework/visualisation/react/ui/pages/start_page.tsx b/src/framework/visualisation/react/ui/pages/start_page.tsx
new file mode 100644
index 00000000..d3a023e9
--- /dev/null
+++ b/src/framework/visualisation/react/ui/pages/start_page.tsx
@@ -0,0 +1,54 @@
+import { Weak } from '../../../../helpers'
+import TextBundle from '../../../../text_bundle'
+import { Translator } from '../../../../translator'
+import { PropsUIPageStart } from '../../../../types/pages'
+import { ReactFactoryContext } from '../../factory'
+import { PrimaryButton } from '../elements/button'
+import { BodyLarge, Title0 } from '../elements/text'
+
+type Props = Weak & ReactFactoryContext
+
+export const StartPage = (props: Props): JSX.Element => {
+ const { resolve } = props
+ const { title, description, startButton } = prepareCopy(props)
+
+ function handleStart (): void {
+ resolve?.({ __type__: 'PayloadVoid', value: undefined })
+ }
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
+interface Copy {
+ title: string
+ description: string
+ startButton: string
+}
+
+function prepareCopy ({ locale }: Props): Copy {
+ return {
+ title: Translator.translate(title, locale),
+ description: Translator.translate(description, locale),
+ startButton: Translator.translate(startButtonLabel, locale)
+ }
+}
+
+const title = new TextBundle()
+ .add('en', 'Instructions')
+ .add('nl', 'Instructies')
+
+const startButtonLabel = new TextBundle()
+ .add('en', 'Start')
+ .add('nl', 'Start')
+
+const description = new TextBundle()
+ .add('en', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.')
+ .add('nl', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.')
diff --git a/src/framework/visualisation/react/ui/prompts/confirm.tsx b/src/framework/visualisation/react/ui/prompts/confirm.tsx
new file mode 100644
index 00000000..989c5d4c
--- /dev/null
+++ b/src/framework/visualisation/react/ui/prompts/confirm.tsx
@@ -0,0 +1,45 @@
+import { Weak } from '../../../../helpers'
+import { ReactFactoryContext } from '../../factory'
+import { PropsUIPromptConfirm } from '../../../../types/prompts'
+import { Translator } from '../../../../translator'
+import { BodyLarge } from '../elements/text'
+import { LabelButton, PrimaryButton } from '../elements/button'
+
+type Props = Weak & ReactFactoryContext
+
+export const Confirm = (props: Props): JSX.Element => {
+ const { resolve } = props
+ const { text, ok, cancel } = prepareCopy(props)
+
+ function handleOk (): void {
+ resolve?.({ __type__: 'PayloadTrue', value: true })
+ }
+
+ function handleCancel (): void {
+ resolve?.({ __type__: 'PayloadFalse', value: false })
+ }
+
+ return (
+ <>
+
+
+ >
+ )
+}
+
+interface Copy {
+ text: string
+ ok: string
+ cancel: string
+}
+
+function prepareCopy ({ text, ok, cancel, locale }: Props): Copy {
+ return {
+ text: Translator.translate(text, locale),
+ ok: Translator.translate(ok, locale),
+ cancel: Translator.translate(cancel, locale)
+ }
+}
diff --git a/src/framework/visualisation/react/ui/prompts/consent_form.tsx b/src/framework/visualisation/react/ui/prompts/consent_form.tsx
new file mode 100644
index 00000000..c4688a0d
--- /dev/null
+++ b/src/framework/visualisation/react/ui/prompts/consent_form.tsx
@@ -0,0 +1,122 @@
+import { Weak } from '../../../../helpers'
+import { PropsUITable, PropsUITableCell, PropsUITableRow, Translatable } from '../../../../types/elements'
+import { PropsUIPromptConsentForm } from '../../../../types/prompts'
+import { Table } from '../elements/table'
+import { PrimaryButton, SecondaryButton } from '../elements/button'
+import { BodyLarge, Title1, Title2 } from '../elements/text'
+import TextBundle from '../../../../text_bundle'
+import { Translator } from '../../../../translator'
+import { ReactFactoryContext } from '../../factory'
+
+type Props = Weak & ReactFactoryContext
+
+export const ConsentForm = (props: Props): JSX.Element => {
+ const { tables, resolve } = props
+ const { title, description, donateButton, declineButton } = prepareCopy(props)
+
+ function handleDonate (): void {
+ resolve?.({ __type__: 'PayloadString', value: JSON.stringify(tables) })
+ }
+
+ function handleDecline (): void {
+ resolve?.({ __type__: 'PayloadFalse', value: false })
+ }
+
+ function rowCell (dataFrame: any, column: string, row: number): PropsUITableCell {
+ const text = dataFrame[column][`${row}`] as string
+ return { __type__: 'PropsUITableCell', text: text }
+ }
+
+ function headCell (dataFrame: any, column: string): PropsUITableCell {
+ return { __type__: 'PropsUITableCell', text: column }
+ }
+
+ function columnNames (dataFrame: any): string[] {
+ return Object.keys(dataFrame)
+ }
+
+ function columnCount (dataFrame: any): number {
+ return columnNames(dataFrame).length
+ }
+
+ function rowCount (dataFrame: any): number {
+ if (columnCount(dataFrame) === 0) {
+ return 0
+ } else {
+ const firstColumn = dataFrame[columnNames(dataFrame)[0]]
+ return Object.keys(firstColumn).length
+ }
+ }
+
+ function rows (data: any): PropsUITableRow[] {
+ const result: PropsUITableRow[] = []
+ for (let row = 0; row <= rowCount(data); row++) {
+ const cells = columnNames(data).map((column: string) => rowCell(data, column, row))
+ result.push({ __type__: 'PropsUITableRow', cells: cells })
+ }
+ return result
+ }
+
+ function parse (tableData: any): PropsUITable {
+ const id = tableData.id as string
+ const dataFrame = JSON.parse(tableData.data_frame)
+ const head = { cells: columnNames(dataFrame).map((column: string) => headCell(dataFrame, column)) }
+ const body = { rows: rows(dataFrame) }
+
+ return { __type__: 'PropsUITable', id, head, body }
+ }
+
+ function renderTable (tableData: any): JSX.Element {
+ const title = tableData.title as string
+ const tableProps = parse(tableData)
+
+ return (
+
+ )
+ }
+
+ return (
+ <>
+
+
+
+ {tables.map((table) => renderTable(table))}
+
+
+ >
+ )
+}
+
+interface Copy {
+ title: string
+ description: string
+ donateButton: string
+ declineButton: string
+}
+
+function prepareCopy ({ title, description, locale }: Props): Copy {
+ return {
+ title: Translator.translate(title, locale),
+ description: Translator.translate(description, locale),
+ donateButton: Translator.translate(donateButtonLabel(), locale),
+ declineButton: Translator.translate(declineButtonLabel(), locale)
+ }
+}
+
+const donateButtonLabel = (): Translatable => {
+ return new TextBundle()
+ .add('en', 'Yes, donate')
+ .add('nl', 'Ja, doneer')
+}
+
+const declineButtonLabel = (): Translatable => {
+ return new TextBundle()
+ .add('en', 'No')
+ .add('nl', 'Nee')
+}
diff --git a/src/framework/visualisation/react/ui/prompts/file_input.tsx b/src/framework/visualisation/react/ui/prompts/file_input.tsx
new file mode 100644
index 00000000..8c00b7dd
--- /dev/null
+++ b/src/framework/visualisation/react/ui/prompts/file_input.tsx
@@ -0,0 +1,116 @@
+import { Weak } from '../../../../helpers'
+import * as React from 'react'
+import { Translatable } from '../../../../types/elements'
+import TextBundle from '../../../../text_bundle'
+import { Translator } from '../../../../translator'
+import { ReactFactoryContext } from '../../factory'
+import { PropsUIPromptFileInput } from '../../../../types/prompts'
+import { ForwardButton, PrimaryButton } from '../elements/button'
+
+type Props = Weak & ReactFactoryContext
+
+export const FileInput = (props: Props): JSX.Element => {
+ const [selectedFile, setSelectedFile] = React.useState()
+ const [confirmHidden, setConfirmHidden] = React.useState(true)
+ const input = React.useRef(null)
+
+ const { resolve } = props
+ const { title, description, extensions, selectButton, continueButton, forwardButton } = prepareCopy(props)
+
+ function handleClick (): void {
+ input.current?.click()
+ }
+
+ function handleSkip (): void {
+ resolve?.({ __type__: 'PayloadFalse', value: false })
+ }
+
+ function handleSelect (event: React.ChangeEvent): void {
+ const files = event.target.files
+ if (files != null && files.length > 0) {
+ setSelectedFile(files[0])
+ setConfirmHidden(false)
+ } else {
+ console.log('Error selecting file: ' + JSON.stringify(files))
+ }
+ }
+
+ function handleConfirm (): void {
+ if (selectedFile !== undefined) {
+ resolve?.({ __type__: 'PayloadFile', value: selectedFile })
+ }
+ }
+
+ return (
+ <>
+
+ {title}
+
+
+
+
+
+ {description}
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+interface Copy {
+ title: string
+ description: string
+ extensions: string
+ selectButton: string
+ continueButton: string
+ forwardButton: string
+}
+
+function prepareCopy ({ title, description, extensions, locale }: Props): Copy {
+ return {
+ title: Translator.translate(title, locale),
+ description: Translator.translate(description, locale),
+ extensions: extensions,
+ selectButton: Translator.translate(selectButtonLabel(), locale),
+ continueButton: Translator.translate(continueButtonLabel(), locale),
+ forwardButton: Translator.translate(forwardButtonLabel(), locale)
+ }
+}
+
+const continueButtonLabel = (): Translatable => {
+ return new TextBundle()
+ .add('en', 'Continue')
+ .add('nl', 'Doorgaan')
+}
+
+const selectButtonLabel = (): Translatable => {
+ return new TextBundle()
+ .add('en', 'Choose file')
+ .add('nl', 'Kies bestand')
+}
+
+const forwardButtonLabel = (): Translatable => {
+ return new TextBundle()
+ .add('en', 'Skip this step')
+ .add('nl', 'Sla deze stap over')
+}
diff --git a/src/framework/visualisation/react/components/radio_input.tsx b/src/framework/visualisation/react/ui/prompts/radio_input.tsx
similarity index 68%
rename from src/framework/visualisation/react/components/radio_input.tsx
rename to src/framework/visualisation/react/ui/prompts/radio_input.tsx
index 682930ed..e23c5223 100644
--- a/src/framework/visualisation/react/components/radio_input.tsx
+++ b/src/framework/visualisation/react/ui/prompts/radio_input.tsx
@@ -1,15 +1,12 @@
import * as React from 'react'
-import Translatable from '../../../translatable'
-import RadioSvg from '../../../../assets/images/radio.svg'
-import RadioActiveSvg from '../../../../assets/images/radio_active.svg'
-
-export interface RadioInputProps {
- title: any
- description: any
- items: string[]
- locale: string
- resolve: (value: any) => void
-}
+import { Weak } from '../../../../helpers'
+import { Translatable } from '../../../../types/elements'
+import TextBundle from '../../../../text_bundle'
+import RadioSvg from '../../../../../assets/images/spinner.svg'
+import RadioActiveSvg from '../../../../../assets/images/radio_active.svg'
+import { Translator } from '../../../../translator'
+import { ReactFactoryContext } from '../../factory'
+import { PropsUIPromptRadioInput } from '../../../../types/prompts'
interface Copy {
title: string
@@ -17,17 +14,19 @@ interface Copy {
continueButton: string
}
-function prepareCopy ({ title, description, locale }: RadioInputProps): Copy {
+type Props = Weak & ReactFactoryContext
+
+function prepareCopy ({ title, description, locale }: Props): Copy {
return {
- title: title.en,
- description: description.en,
- continueButton: continueButtonLabel().text(locale)
+ title: Translator.translate(title, locale),
+ description: Translator.translate(description, locale),
+ continueButton: Translator.translate(continueButtonLabel(), locale)
}
}
-export const RadioInputFactory = (props: RadioInputProps): JSX.Element =>
+export const RadioInputFactory = (props: Props): JSX.Element =>
-export const RadioInput = (props: RadioInputProps): JSX.Element => {
+export const RadioInput = (props: Props): JSX.Element => {
const [selectedId, setSelectedId] = React.useState(-1)
const [confirmHidden, setConfirmHidden] = React.useState(true)
@@ -40,8 +39,10 @@ export const RadioInput = (props: RadioInputProps): JSX.Element => {
}
function handleConfirm (): void {
- const value = items.at(selectedId)
- resolve(value)
+ const item = items.at(selectedId)
+ if (item !== undefined) {
+ resolve?.({ __type__: 'PayloadString', value: item })
+ }
}
return (
@@ -74,7 +75,7 @@ export const RadioInput = (props: RadioInputProps): JSX.Element => {
}
const continueButtonLabel = (): Translatable => {
- return new Translatable()
+ return new TextBundle()
.add('en', 'Continue')
.add('nl', 'Doorgaan')
}
diff --git a/src/index.tsx b/src/index.tsx
index 7ba408b5..470ba56c 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,14 +1,16 @@
import './fonts.css'
import './framework/styles.css'
-import { Assembly } from './framework/assembly'
+import Assembly from './framework/assembly'
import { PyScript } from './py_script'
+import LocalSystem from './local_system'
+const rootElement = document.getElementById('root') as HTMLElement
+
+const locale = 'en'
+const system = new LocalSystem()
const workerFile = new URL('./framework/processing/python/worker.js', import.meta.url)
const worker = new Worker(workerFile)
-const rootElement = document.getElementById('root') as HTMLElement
-const visualisationEngine = Assembly(worker)
-visualisationEngine.start(PyScript, rootElement, 'en').then(
- () => {},
- () => {}
-)
+const assembly = new Assembly(worker, system)
+assembly.visualisationEngine.start(rootElement, locale)
+assembly.processingEngine.start(PyScript)
diff --git a/src/local_system.ts b/src/local_system.ts
new file mode 100644
index 00000000..e5fecd01
--- /dev/null
+++ b/src/local_system.ts
@@ -0,0 +1,16 @@
+import { CommandSystem, CommandSystemDonate, isCommandSystemDonate } from './framework/types/commands'
+import { System } from './framework/types/modules'
+
+export default class LocalSystem implements System {
+ send (command: CommandSystem): void {
+ if (isCommandSystemDonate(command)) {
+ this.handleDonation(command)
+ } else {
+ console.log('[LocalSystem] received unknown command: ' + JSON.stringify(command))
+ }
+ }
+
+ handleDonation (command: CommandSystemDonate): void {
+ console.log('[LocalSystem] received donation: ' + JSON.stringify(command))
+ }
+}
diff --git a/src/py_script.ts b/src/py_script.ts
index a2bc957a..21b1d0a3 100644
--- a/src/py_script.ts
+++ b/src/py_script.ts
@@ -1,64 +1,137 @@
export const PyScript: string = `
import pandas as pd
-
+import zipfile
def process():
- chat_file_name = yield prompt_file()
- usernames = extract_usernames(chat_file_name)
- username = yield prompt_radio(usernames)
- yield result(usernames, username)
-
-
-def prompt_file():
- title = Translatable()
- title.add("en", "Step 1: Select the chat file")
- title.add("nl", "Stap 1: Selecteer het chat file")
-
- description = Translatable()
- description.add("en", "We previously asked you to export a chat file from Whatsapp. Please select this file so we can extract relevant information for our research.")
- description.add("nl", "We hebben je gevraagd een chat bestand te exporteren uit Whatsapp. Je kan deze file nu selecteren zodat wij er relevante informatie uit kunnen halen voor ons onderzoek.")
-
- extensions = "application/zip, text/plain"
-
- return FileInput(title, description, extensions)
-
-
-def prompt_radio(usernames):
- title = Translatable()
- title.add("en", "Step 2: Select your username")
- title.add("nl", "Stap 2: Selecteer je gebruikersnaam")
-
- description = Translatable()
- description.add("en", "The following users are extracted from the chat file. Which one are you?")
- description.add("nl", "De volgende gebruikers hebben we uit de chat file gehaald. Welke ben jij?")
-
- return RadioInput(title, description, usernames)
-
-
-def extract_usernames(chat_file_name):
- print(f"filename: {chat_file_name}")
-
- with open(chat_file_name) as chat_file:
- while (line := chat_file.readline().rstrip()):
- print(line)
-
- return ["emielvdveen", "a.m.mendrik", "9bitcat"]
-
-
-def result(usernames, selected_username):
- data = []
- for username in usernames:
- description = "you" if username == selected_username else "-"
- data.append((username, description))
-
- data_frame = pd.DataFrame(data, columns=["username", "description"])
-
- print(data_frame)
-
- result = [{
- "id": "overview",
- "title": "The following usernames where extracted:",
- "data_frame": data_frame
- }]
- return EndOfFlow(result)
+ yield render_start_page()
+
+ platforms = ["Twitter", "Instagram", "Youtube"]
+ for index, platform in enumerate(platforms):
+ data = None
+ while True:
+ promptFile = prompt_file(platform, "application/zip, text/plain")
+ fileResult = yield render_donation_page(index+1, platform, promptFile)
+ if fileResult.__type__ == 'PayloadString':
+ extractionResult = doSomethingWithTheFile(platform, fileResult.value)
+ if extractionResult != 'invalid':
+ data = extractionResult
+ break
+ else:
+ retry_result = yield render_donation_page(index+1, platform, retry_confirmation())
+ if retry_result.__type__ == 'PayloadTrue':
+ continue
+ else:
+ break
+ else:
+ break
+
+ if data is not None:
+ prompt = prompt_consent(platform, data)
+ consent_result = yield render_donation_page(index+1, platform, prompt)
+ if consent_result.__type__ == "PayloadString":
+ yield donate(platform, consent_result.value)
+
+ yield render_end_page()
+
+
+def render_start_page():
+ header = PropsUIHeader(Translatable({
+ "en": "Welcome",
+ "nl": "Welkom"
+ }))
+ page = PropsUIPageStart(header, spinner())
+ return CommandUIRender(page)
+
+
+def render_end_page():
+ header = PropsUIHeader(Translatable({
+ "en": "Thank you",
+ "nl": "Dank je wel"
+ }))
+ page = PropsUIPageEnd(header)
+ return CommandUIRender(page)
+
+
+def render_donation_page(index, platform, body):
+ header = PropsUIHeader(Translatable({
+ "en": f"Step {index}: {platform}",
+ "nl": f"Stap {index}: {platform}"
+ }))
+ page = PropsUIPageDonation(header, body, spinner())
+ return CommandUIRender(page)
+
+
+def retry_confirmation():
+ text = Translatable({
+ "en": "The selected file is invalid. Do you want to select a different file?",
+ "nl": "Het geselecteerde bestaand is ongeldig. Wil je een ander bestand selecteren ?"
+ })
+ ok = Translatable({
+ "en": "Different file",
+ "nl": "Ander bestand"
+ })
+ cancel = Translatable({
+ "en": "Cancel",
+ "nl": "Annuleren"
+ })
+ return PropsUIPromptConfirm(text, ok, cancel)
+
+
+def spinner():
+ return PropsUISpinner(Translatable({
+ "en": "One moment please",
+ "nl": "Een moment geduld"
+ }))
+
+
+def prompt_file(platform, extensions):
+ title = Translatable({
+ "en": f"Select {platform} file",
+ "nl": f"Selecteer {platform} bestand"
+ })
+
+ description = Translatable({
+ "en": "Please select this file so we can extract relevant information for our research.",
+ "nl": "Je kan deze file nu selecteren zodat wij er relevante informatie uit kunnen halen voor ons onderzoek."
+ })
+
+ return PropsUIPromptFileInput(title, description, extensions)
+
+
+def doSomethingWithTheFile(platform, filename):
+ return extract_zip_contents(filename)
+
+
+def extract_zip_contents(filename):
+ names = []
+ try:
+ file = zipfile.ZipFile(filename)
+ data = []
+ for name in file.namelist():
+ names.append(name)
+ info = file.getinfo(name)
+ data.append((name, info.compress_size, info.file_size))
+ return data
+ except:
+ return "invalid"
+
+
+def prompt_consent(id, data):
+ title = Translatable({
+ "en": "Extracted data",
+ "nl": "Gevonden gegevens"
+ })
+
+ description = Translatable({
+ "en": "Please have a good look at the extracted data before giving consent to use this data.",
+ "nl": "Bekijk de gegevens goed voordat je consent geeft om deze te gebruiken."
+ })
+
+ data_frame = pd.DataFrame(data, columns=["filename", "compressed size", "size"])
+ table = PropsUIPromptConsentFormTable(id, "The zip contains the following files:", data_frame)
+ return PropsUIPromptConsentForm(title, description, [table])
+
+
+def donate(key, consent_data):
+ return CommandSystemDonate(key, consent_data)
`