Skip to content

Commit

Permalink
Merge branch 'main' into grady/wipportfoliogroup
Browse files Browse the repository at this point in the history
  • Loading branch information
gbdubs committed Dec 17, 2023
2 parents 207b5ad + acac0f8 commit 5d49993
Show file tree
Hide file tree
Showing 44 changed files with 870 additions and 531 deletions.
4 changes: 3 additions & 1 deletion frontend/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
"@typescript-eslint/promise-function-async": 0,
"@typescript-eslint/prefer-function-type": 0,
"comma-dangle": "off",
"@typescript-eslint/comma-dangle": ["error", "always-multiline" ]
"@typescript-eslint/comma-dangle": ["error", "always-multiline" ],
"require-await": "off",
"@typescript-eslint/require-await": "error"
},
"overrides": [
{
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/CopyToClipboardButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface Props {
}
const props = defineProps<Props>()
const prefix = 'CopyToClipboardButton'
const prefix = 'components/CopyToClipboardButton'
const tt = (key: string) => t(`${prefix}.${key}`)
const statePrefix = `${prefix}[${useStateIDGenerator().id()}]`
Expand Down
42 changes: 42 additions & 0 deletions frontend/components/DownloadButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script setup lang="ts">
import { computed } from 'vue'
const { t } = useI18n()
interface Props {
value: string
cta: string
fileName: string
}
const props = defineProps<Props>()
const prefix = 'components/DownloadButton'
const tt = (key: string) => t(`${prefix}.${key}`)
const statePrefix = `${prefix}[${useStateIDGenerator().id()}]`
const downloaded = useState<boolean>(`${statePrefix}.downloaded`, () => false)
const message = computed(() => downloaded.value ? tt('Downloaded') : props.cta)
const icon = computed(() => downloaded.value ? 'pi pi-check' : 'pi pi-download')
const download = () => {
downloaded.value = true
const a = document.createElement('a')
const file = new Blob([props.value])
a.href = URL.createObjectURL(file)
a.download = props.fileName
a.click()
a.remove()
setTimeout(() => { downloaded.value = false }, 5000)
}
</script>

<template>
<PVButton
:disabled="downloaded"
:label="message"
:icon="icon"
icon-pos="right"
class="text-sm"
@click="download"
/>
</template>
4 changes: 2 additions & 2 deletions frontend/components/IconButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ const props = withDefaults(defineProps<Props>(), {
tooltipClass: undefined,
offlineSafe: undefined,
})
const tt = computed(() => {
const tooltip = computed(() => {
const result = { value: props.alt, class: props.tooltipClass }
return result
})
</script>

<template>
<PVButton
v-tooltip="tt"
v-tooltip="tooltip"
:icon="props.icon"
:aria-label="props.alt"
:class="props.buttonClass"
Expand Down
52 changes: 31 additions & 21 deletions frontend/components/form/EditorField.vue
Original file line number Diff line number Diff line change
@@ -1,42 +1,52 @@
<script setup lang="ts">
import { useSlots } from 'vue'
import { type EditorField, isValid } from '@/lib/editor'
import { type EditorField, type EditorValue, isValid } from '@/lib/editor'
interface Props {
editorField: EditorField<any, keyof any>
helpText?: string
startHelpTextExpanded?: boolean
const { t } = useI18n()
const tt = (key: string) => t(`components/form/EditorField.${key}`)
// Why this convoluted type structure?
// In order for typchecking down the line, the EditorField and EditorValue need to have the SAME key.
// Enforcing that requires a check at a higher level, otherwise we'd have to directly parameterize Props
// with a non-any parameter value, which is a no-no. This leads us to Indirect1.
// Then, we have a second problem: we want to specify the `any`
// on props, but we don't want to use `keyof any`, which doesn't guarantee that the key will correspond to the parameterized type.
// The Indirect2 allows us to condense these two constraints down to one, which allows us to use the single `any` in props.
interface Indirect1<T, K extends keyof T> {
editorField: EditorField<T, K>
editorValue: EditorValue<T, K>
}
interface Indirect2<T> extends Indirect1<T, keyof T> {}
interface Props extends Indirect2<any> {
isLoading?: boolean
loadingLabel?: string
invalidLabel?: string
validLabel?: string
}
const props = withDefaults(defineProps<Props>(), {
helpText: undefined,
startHelpTextExpanded: false,
loading: false,
loadingLabel: 'Loading...',
invalidLabel: 'Needs Attention',
validLabel: '',
isLoading: false,
})
const slots = useSlots()
const helpTextSlotExists = computed(() => slots['help-text'] !== undefined)
const valid = computed(() => isValid(props.editorField))
const valid = computed(() => isValid(props.editorField, props.editorValue))
const hasValidation = computed(() => (props.editorField.validation ?? []).length > 0)
const loadingLabel = computed(() => props.editorField.loadingLabel ?? tt('Loading...'))
const invalidLabel = computed(() => props.editorField.invalidLabel ?? tt('Needs Attention'))
const validLabel = computed(() => props.editorField.validLabel ?? '')
const startHelpTextExpanded = computed(() => props.editorField.startHelpTextExpanded ?? false)
</script>

<template>
<FormField
:label="props.editorField.label"
:help-text="props.helpText"
:start-help-text-expanded="props.startHelpTextExpanded"
:is-loading="props.loading"
:loading-label="props.loadingLabel"
:help-text="props.editorField.helpText"
:start-help-text-expanded="startHelpTextExpanded"
:is-loading="props.isLoading"
:loading-label="loadingLabel"
:has-validation="hasValidation"
:is-valid="valid"
:invalid-label="props.invalidLabel"
:valid-label="props.validLabel"
:invalid-label="invalidLabel"
:valid-label="validLabel"
>
<template
v-if="helpTextSlotExists"
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/form/Field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useSlots } from 'vue'
const slots = useSlots()
const { t } = useI18n()
const prefix = 'FormField'
const prefix = 'components/form/Field'
const tt = (s: string) => t(`${prefix}.${s}`)
interface Props {
Expand Down
44 changes: 27 additions & 17 deletions frontend/components/incompleteupload/Editor.vue
Original file line number Diff line number Diff line change
@@ -1,48 +1,58 @@
<script setup lang="ts">
import { type EditorIncompleteUpload } from '@/lib/editor'
import {
type EditorIncompleteUploadFields as EditorFields,
type EditorIncompleteUploadValues as EditorValues,
} from '@/lib/editor'
const prefix = 'components/incompleteupload/Editor'
const { t } = useI18n()
const tt = (key: string) => t(`${prefix}.${key}`)
interface Props {
editorIncompleteUpload: EditorIncompleteUpload
editorValues: EditorValues
editorFields: EditorFields
}
interface Emits {
(e: 'update:editorIncompleteUpload', ei: EditorIncompleteUpload): void
(e: 'update:editorValues', evs: EditorValues): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const model = computed({
get: () => props.editorIncompleteUpload,
set: (editorIncompleteUpload: EditorIncompleteUpload) => { emit('update:editorIncompleteUpload', editorIncompleteUpload) },
const efs = computed(() => props.editorFields)
const evs = computed({
get: () => props.editorValues,
set: (editorValues: EditorValues) => { emit('update:editorValues', editorValues) },
})
</script>

<template>
<div>
<FormEditorField
help-text="The name of this uploaded source file."
:editor-field="model.name"
:editor-field="efs.name"
:editor-value="evs.name"
>
<PVInputText
v-model="model.name.currentValue"
v-model="evs.name.currentValue"
/>
</FormEditorField>
<FormEditorField
help-text="The description of this upload - helpful for record keeping, not used for anything."
:editor-field="model.description"
:editor-field="efs.description"
:editor-value="evs.description"
>
<PVTextarea
v-model="model.description.currentValue"
v-model="evs.description.currentValue"
auto-resize
/>
</FormEditorField>
<FormEditorField
help-text="When enabled, this upload can be accessed by administrators to help with debugging. Only turn this on if you're comfortable with system administrators accessing this data."
:editor-field="model.adminDebugEnabled"
:editor-field="efs.adminDebugEnabled"
:editor-value="evs.adminDebugEnabled"
>
<ExplicitInputSwitch
v-model:value="model.adminDebugEnabled.currentValue"
on-label="Administrator Debugging Access Enabled"
off-label="No Administrator Access Enabled"
v-model:value="evs.adminDebugEnabled.currentValue"
:on-label="tt('Administrator Debugging Access Enabled')"
:off-label="tt('No Administrator Access Enabled')"
/>
</FormEditorField>
</div>
Expand Down
Loading

0 comments on commit 5d49993

Please sign in to comment.