wip: Drop swizzle folder
This commit is contained in:
parent
a50e3ca50e
commit
4b6944b72f
16 changed files with 0 additions and 3488 deletions
|
@ -1,210 +0,0 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
|
||||
export const AuthMessageWrapper = ({ children }) => (
|
||||
<div className="m-auto max-w-xl text-center mt-8 p-8">{children}</div>
|
||||
)
|
||||
|
||||
export const ContactSupport = ({ t, Swizzled }) => (
|
||||
<div className="flex flex-row items-center justify-center gap-4 mt-8">
|
||||
<Swizzled.components.Link href="/support" className="btn btn-success w-full">
|
||||
{t('contactSupport')}
|
||||
</Swizzled.components.Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const AuthRequired = ({ t, banner, Swizzled }) => (
|
||||
<Swizzled.components.AuthMessageWrapper>
|
||||
{banner}
|
||||
<h2>{Swizzled.methods.t('pe:authRequired')}</h2>
|
||||
<p>{t('pe:membersOnly')}</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 mt-8">
|
||||
<Swizzled.components.Link
|
||||
href="/signup"
|
||||
className={`${Swizzled.config.classes.horFlex} btn btn-secondary w-full`}
|
||||
>
|
||||
<Swizzled.components.PlusIcon />
|
||||
{t('pe:signUp')}
|
||||
</Swizzled.components.Link>
|
||||
<Swizzled.components.Link
|
||||
href="/signin"
|
||||
className={`${Swizzled.config.classes.horFlex} btn btn-secondary btn-outline w-full`}
|
||||
>
|
||||
<Swizzled.components.LockIcon />
|
||||
{t('signIn')}
|
||||
</Swizzled.components.Link>
|
||||
</div>
|
||||
</Swizzled.components.AuthMessageWrapper>
|
||||
)
|
||||
|
||||
export const AccountInactive = ({ t, banner, Swizzled }) => (
|
||||
<Swizzled.components.AuthMessageWrapper>
|
||||
{banner}
|
||||
<h1>{t('accountInactive')}</h1>
|
||||
<p>{t('accountInactiveMsg')}</p>
|
||||
<p>{t('signupAgain')}</p>
|
||||
<div className="flex flex-row items-center justify-center gap-4 mt-8">
|
||||
<Swizzled.components.Link href="/signup" className="btn btn-primary w-full">
|
||||
{t('signUp')}
|
||||
</Swizzled.components.Link>
|
||||
</div>
|
||||
</Swizzled.components.AuthMessageWrapper>
|
||||
)
|
||||
|
||||
export const AccountDisabled = ({ t, banner, Swizzled }) => (
|
||||
<Swizzled.components.AuthMessageWrapper>
|
||||
{banner}
|
||||
<h1>{t('accountDisabled')}</h1>
|
||||
<p>{t('accountDisabledMsg')}</p>
|
||||
<ContactSupport t={t} />
|
||||
</Swizzled.components.AuthMessageWrapper>
|
||||
)
|
||||
|
||||
export const AccountProhibited = ({ t, banner, Swizzled }) => (
|
||||
<Swizzled.components.AuthMessageWrapper>
|
||||
{banner}
|
||||
<h1>{t('accountProhibited')}</h1>
|
||||
<p>{t('accountProhibitedMsg')}</p>
|
||||
<Swizzled.components.ContactSupport t={t} />
|
||||
</Swizzled.components.AuthMessageWrapper>
|
||||
)
|
||||
|
||||
export const AccountStatusUnknown = ({ t, banner, Swizzled }) => (
|
||||
<Swizzled.components.AuthMessageWrapper>
|
||||
{banner}
|
||||
<h1>{t('statusUnknown')}</h1>
|
||||
<p>{t('statusUnknownMsg')}</p>
|
||||
<Swizzled.components.ContactSupport t={t} />
|
||||
</Swizzled.components.AuthMessageWrapper>
|
||||
)
|
||||
|
||||
export const RoleLacking = ({ t, requiredRole, role, banner, Swizzled }) => (
|
||||
<Swizzled.components.AuthMessageWrapper>
|
||||
{banner}
|
||||
<h1>{t('roleLacking')}</h1>
|
||||
<p dangerouslySetInnerHTML={{ __html: t('roleLackingMsg', { requiredRole, role }) }} />
|
||||
<Swizzled.components.ContactSupport t={t} />
|
||||
</Swizzled.components.AuthMessageWrapper>
|
||||
)
|
||||
|
||||
export const ConsentLacking = ({ banner, Swizzled }) => {
|
||||
//const { setAccount, setToken, setSeenUser } = Swizzled.hooks.useAccount()
|
||||
//const backend = Swizzled.hooks.useBackend()
|
||||
|
||||
//const updateConsent = async ({ consent1, consent2 }) => {
|
||||
// let consent = 0
|
||||
// if (consent1) consent = 1
|
||||
// if (consent1 && consent2) consent = 2
|
||||
// if (consent > 0) {
|
||||
// const result = await backend.updateConsent(consent)
|
||||
// if (result.success) {
|
||||
// setToken(result.data.token)
|
||||
// setAccount(result.data.account)
|
||||
// setSeenUser(result.data.account.username)
|
||||
// refresh()
|
||||
// } else {
|
||||
// console.log('something went wrong', result)
|
||||
// refresh()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
return (
|
||||
<Swizzled.components.AuthMessageWrapper>
|
||||
<div className="text-left">
|
||||
{banner}
|
||||
<p>FIXME: Handle content form</p>
|
||||
{/*<ConsentForm submit={updateConsent} />*/}
|
||||
</div>
|
||||
</Swizzled.components.AuthMessageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export const AuthWrapper = ({ children, requiredRole = 'user', Swizzled }) => {
|
||||
const { t } = Swizzled.methods
|
||||
const { useAccount, useBackend } = Swizzled.hooks
|
||||
const { account, setAccount, token, admin, stopImpersonating, signOut } = useAccount()
|
||||
const backend = useBackend()
|
||||
|
||||
const [ready, setReady] = useState(false)
|
||||
const [impersonating, setImpersonating] = useState(false)
|
||||
const [error, setError] = useState(false)
|
||||
const [refreshCount, setRefreshCount] = useState(0)
|
||||
|
||||
/*
|
||||
* Avoid hydration errors
|
||||
*/
|
||||
useEffect(() => {
|
||||
const verifyAdmin = async () => {
|
||||
const result = await backend.adminPing(admin.token)
|
||||
if (result.success && result.data.account.role === 'admin') {
|
||||
setImpersonating({
|
||||
admin: result.data.account.username,
|
||||
user: account.username,
|
||||
})
|
||||
}
|
||||
setReady(true)
|
||||
}
|
||||
const verifyUser = async () => {
|
||||
const result = await backend.ping()
|
||||
if (result.success) {
|
||||
// Refresh account in local storage
|
||||
setAccount({
|
||||
...account,
|
||||
...result.data.account,
|
||||
})
|
||||
} else {
|
||||
if (result.data?.error?.error) setError(result.data.error.error)
|
||||
else signOut()
|
||||
}
|
||||
setReady(true)
|
||||
}
|
||||
if (admin && admin.token) verifyAdmin()
|
||||
if (token) verifyUser()
|
||||
else setReady(true)
|
||||
}, [admin, token, refreshCount])
|
||||
|
||||
const refresh = () => {
|
||||
setRefreshCount(refreshCount + 1)
|
||||
setError(false)
|
||||
}
|
||||
|
||||
if (!ready) return <Swizzled.components.Loading />
|
||||
|
||||
const banner = impersonating ? (
|
||||
<div className="bg-warning rounded-lg shadow py-4 px-6 flex flex-row items-center gap-4 justify-between">
|
||||
<span className="text-base-100 text-left">
|
||||
Hi <b>{impersonating.admin}</b>, you are currently impersonating <b>{impersonating.user}</b>
|
||||
</span>
|
||||
<button className="btn btn-neutral" onClick={stopImpersonating}>
|
||||
Stop Impersonating
|
||||
</button>
|
||||
</div>
|
||||
) : null
|
||||
|
||||
const childProps = { t, banner, Swizzled }
|
||||
|
||||
if (!token || !account.username) return <Swizzled.components.AuthRequired {...childProps} />
|
||||
if (error) {
|
||||
if (error === 'accountInactive') return <Swizzled.components.AccountInactive {...childProps} />
|
||||
if (error === 'accountDisabled') return <Swizzled.components.AccountDisabled {...childProps} />
|
||||
if (error === 'accountBlocked') return <Swizzled.components.AccountProhibited {...childProps} />
|
||||
if (error === 'consentLacking')
|
||||
return <Swizzled.components.ConsentLacking {...childProps} refresh={refresh} />
|
||||
return <Swizzled.components.AccountStatusUnknown {...childProps} />
|
||||
}
|
||||
|
||||
if (
|
||||
!Swizzled.config.roles.levels[account.role] ||
|
||||
Swizzled.config.roles.levels[account.role] < Swizzled.config.roles.levels[requiredRole]
|
||||
) {
|
||||
return (
|
||||
<Swizzled.components.RoleLacking
|
||||
{...childProps}
|
||||
role={account.role}
|
||||
requiredRole={requiredRole}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* Loading MDX dynamically is non-trivial an depends on the
|
||||
* environment in which the component is loaded.
|
||||
* By default, we use this component, which disabled loading
|
||||
* inline docs. If you want this to work, you need to pass in
|
||||
* your own DynamicMdx component into the editor:
|
||||
*
|
||||
* <PatternEditor components={{ DynamicMdx: MyComponent }} />
|
||||
*/
|
||||
export const DynamicMdx = ({ Swizzled }) => (
|
||||
<Swizzled.components.Popout node>Not implemented</Swizzled.components.Popout>
|
||||
)
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* The error view is loaded if and only an error occurs that we can't handle
|
||||
*
|
||||
* @param {object} props - The component's props
|
||||
* @param {object} props.Swizzled - Swizzled code
|
||||
* @param {function} props.Design - The design constructor
|
||||
* @param {object} props.state - The ViewWrapper state object
|
||||
* @param {object} props.state.settings - The current settings
|
||||
* @param {object} props.update - Helper object for updating the ViewWrapper state
|
||||
* @param {array} props.missingMeasurements - List of missing measurements for the current design
|
||||
* @param {object} props.components - The possibly swizzled components
|
||||
* @param {object} props.methods - The possibly swizzled methods
|
||||
* @param {function} props.methods.t - The translation method
|
||||
* @param {object} props.config - The possibly swizzled pattern editor configuration
|
||||
* @return {function} MeasurementsView - React component
|
||||
*/
|
||||
export const ErrorView = ({ Swizzled, state }) => (
|
||||
<div className="text-center mt-8">
|
||||
<h2>{Swizzled.methods.t('oops')}</h2>
|
||||
<p>FIXME: Something went wrong</p>
|
||||
<pre>{JSON.stringify(state, null, 2)}</pre>
|
||||
</div>
|
||||
)
|
File diff suppressed because one or more lines are too long
|
@ -1,659 +0,0 @@
|
|||
/*************************************************************************
|
||||
* *
|
||||
* FreeSewing's pattern editor allows swizzling components *
|
||||
* *
|
||||
* To 'swizzle' means to replace the default implementation of a *
|
||||
* component with a custom one. It allows one to customize *
|
||||
* the pattern editor. *
|
||||
* *
|
||||
* This file holds the 'swizzleComponents' method that will return *
|
||||
* the various components that can be swizzled, or their default *
|
||||
* implementation. *
|
||||
* *
|
||||
* To use a custom version, simply pas it as a prop into the editor *
|
||||
* under the 'components' key. So to pass a custom 'TemporaryLoader' *
|
||||
* component, you do: *
|
||||
* *
|
||||
* <PatternEditor compnents={{ TemporaryLoader: MyTemporaryLoader }} /> *
|
||||
* *
|
||||
*************************************************************************/
|
||||
|
||||
/*
|
||||
* Import of components that can be swizzled
|
||||
*/
|
||||
import { Link, AnchorLink, PageLink, WebLink, CardLink } from './link.mjs'
|
||||
// Accordion
|
||||
import { BaseAccordion, SubAccordion, Accordion } from './accordion.mjs'
|
||||
// Auth wrapper
|
||||
import {
|
||||
AuthWrapper,
|
||||
AuthMessageWrapper,
|
||||
ContactSupport,
|
||||
AuthRequired,
|
||||
AccountInactive,
|
||||
AccountDisabled,
|
||||
AccountProhibited,
|
||||
AccountStatusUnknown,
|
||||
RoleLacking,
|
||||
ConsentLacking,
|
||||
} from './auth-wrapper.mjs'
|
||||
// Ux
|
||||
import { Ux } from './ux.mjs'
|
||||
// Popout
|
||||
import { Popout } from './popout.mjs'
|
||||
// Loader
|
||||
import { TemporaryLoader } from './loaders.mjs'
|
||||
// Measurements Sets
|
||||
import { UserSetPicker, BookmarkedSetPicker, CuratedSetPicker } from './sets.mjs'
|
||||
// Curated Measurements Sets
|
||||
import { CuratedMeasurementsSetLineup } from './curated-sets.mjs'
|
||||
import { MeasurementsSetCard } from './measurements-set-card.mjs'
|
||||
// Icons
|
||||
import {
|
||||
ApplyIcon,
|
||||
BackIcon,
|
||||
BeakerIcon,
|
||||
BookmarkIcon,
|
||||
BoolNoIcon,
|
||||
BoolYesIcon,
|
||||
CircleIcon,
|
||||
CloseIcon,
|
||||
CuratedMeasurementsSetIcon,
|
||||
DesignIcon,
|
||||
DetailIcon,
|
||||
DocsIcon,
|
||||
DownIcon,
|
||||
EditIcon,
|
||||
ExpandIcon,
|
||||
ExportIcon,
|
||||
FailureIcon,
|
||||
FlagIcon,
|
||||
FlagNoteIcon,
|
||||
FlagInfoIcon,
|
||||
FlagTipIcon,
|
||||
FlagWarningIcon,
|
||||
FlagErrorIcon,
|
||||
FlagFixmeIcon,
|
||||
FlagExpandIcon,
|
||||
FlagOtionsIcon,
|
||||
GaugeIcon,
|
||||
GroupIcon,
|
||||
IncludeIcon,
|
||||
HelpIcon,
|
||||
KioskIcon,
|
||||
LeftIcon,
|
||||
ListIcon,
|
||||
LockIcon,
|
||||
MarginIcon,
|
||||
MeasurementsIcon,
|
||||
MeasurementsSetIcon,
|
||||
MenuIcon,
|
||||
NoIcon,
|
||||
OkIcon,
|
||||
OptionsIcon,
|
||||
PaperlessIcon,
|
||||
PlusIcon,
|
||||
PrintIcon,
|
||||
ResetAllIcon,
|
||||
ResetIcon,
|
||||
RightIcon,
|
||||
RocketIcon,
|
||||
RotateIcon,
|
||||
SaIcon,
|
||||
SaveIcon,
|
||||
SaveAsIcon,
|
||||
ScaleIcon,
|
||||
SettingsIcon,
|
||||
SpinnerIcon,
|
||||
SuccessIcon,
|
||||
TipIcon,
|
||||
TrashIcon,
|
||||
UiIcon,
|
||||
UndoIcon,
|
||||
UnitsIcon,
|
||||
UpIcon,
|
||||
UploadIcon,
|
||||
UxIcon,
|
||||
XrayIcon,
|
||||
ViewDraftIcon,
|
||||
ViewMeasurementsIcon,
|
||||
ViewTestIcon,
|
||||
ViewTimingIcon,
|
||||
ViewPrintLayoutIcon,
|
||||
ViewSaveIcon,
|
||||
ViewExportIcon,
|
||||
ViewEditSettingsIcon,
|
||||
ViewLogsIcon,
|
||||
ViewInspectIcon,
|
||||
ViewDocsIcon,
|
||||
ViewDesignsIcon,
|
||||
ViewViewPickerIcon,
|
||||
ViewUndosIcon,
|
||||
} from './icons.mjs'
|
||||
// Measurements Editor
|
||||
import { MeasurementsEditor } from './measurements-editor.mjs'
|
||||
// Zoomable pattern
|
||||
import { ZoomablePattern, ZoomContextProvider } from './zoomable-pattern.mjs'
|
||||
import { PatternLayout } from './pattern-layout.mjs'
|
||||
// inputs
|
||||
import {
|
||||
FormControl,
|
||||
ButtonFrame,
|
||||
NumberInput,
|
||||
StringInput,
|
||||
ListInput,
|
||||
MarkdownInput,
|
||||
MeasurementInput,
|
||||
ToggleInput,
|
||||
} from './inputs.mjs'
|
||||
// Views
|
||||
import { DesignsView } from './designs-view.mjs'
|
||||
import { DraftView } from './draft-view.mjs'
|
||||
import { ErrorView } from './error-view.mjs'
|
||||
import { MeasurementsView } from './measurements-view.mjs'
|
||||
import { SaveView } from './save-view.mjs'
|
||||
import { ViewPicker } from './view-picker.mjs'
|
||||
import { UndoStep, UndoStepTimeAgo, UndosView } from './undos-view.mjs'
|
||||
// Pattern
|
||||
import { Pattern } from '@freesewing/react-components/pattern'
|
||||
// Menus
|
||||
import { DraftMenu } from './menus/draft-menu.mjs'
|
||||
import { CoreSettingsMenu, CoreSetting } from './menus/core-settings-menu.mjs'
|
||||
import { DesignOptionsMenu, DesignOption } from './menus/design-options-menu.mjs'
|
||||
import { UiPreferencesMenu, UiPreference } from './menus/ui-preferences-menu.mjs'
|
||||
import { MenuItem, MenuItemGroup, MenuItemTitle } from './menus/containers.mjs'
|
||||
import {
|
||||
MenuBoolInput,
|
||||
MenuConstantInput,
|
||||
MenuDegInput,
|
||||
MenuEditOption,
|
||||
MenuListInput,
|
||||
MenuListToggle,
|
||||
MenuMmInput,
|
||||
//MenuNumberInput,
|
||||
MenuUxSettingInput,
|
||||
MenuOnlySettingInput,
|
||||
MenuPctInput,
|
||||
MenuSliderInput,
|
||||
} from './menus/shared-inputs.mjs'
|
||||
import {
|
||||
MenuBoolValue,
|
||||
MenuConstantOptionValue,
|
||||
MenuCountOptionValue,
|
||||
MenuDegOptionValue,
|
||||
MenuHighlightValue,
|
||||
MenuListOptionValue,
|
||||
MenuListValue,
|
||||
MenuMmOptionValue,
|
||||
MenuMmValue,
|
||||
MenuOnlySettingValue,
|
||||
MenuPctOptionValue,
|
||||
MenuScaleSettingValue,
|
||||
MenuShowValue,
|
||||
} from './menus/shared-values.mjs'
|
||||
import {
|
||||
HeaderMenu,
|
||||
HeaderMenuAllViews,
|
||||
HeaderMenuDraftView,
|
||||
HeaderMenuButton,
|
||||
HeaderMenuDropdown,
|
||||
HeaderMenuDraftViewDesignOptions,
|
||||
HeaderMenuDraftViewCoreSettings,
|
||||
HeaderMenuDraftViewUiPreferences,
|
||||
HeaderMenuDraftViewFlags,
|
||||
HeaderMenuDraftViewIcons,
|
||||
HeaderMenuIcon,
|
||||
HeaderMenuIconSpacer,
|
||||
HeaderMenuSaveIcons,
|
||||
HeaderMenuUndoIcons,
|
||||
HeaderMenuViewMenu,
|
||||
} from './header-menu.mjs'
|
||||
// Flags
|
||||
import { Flag, FlagTypeIcon, FlagsAccordionTitle, FlagsAccordionEntries } from './flags.mjs'
|
||||
// View Menu
|
||||
import {
|
||||
AsideViewMenu,
|
||||
AsideViewMenuIcons,
|
||||
AsideViewMenuButton,
|
||||
AsideViewMenuSpacer,
|
||||
ViewTypeIcon,
|
||||
} from './aside-view-menu.mjs'
|
||||
import { Null } from './null.mjs'
|
||||
import { LargeScreenOnly } from './large-screen-only.mjs'
|
||||
import { Tooltip } from './tooltip.mjs'
|
||||
import { LoadingStatus } from './loading-status.mjs'
|
||||
import { Spinner, Loading } from './spinner.mjs'
|
||||
import { Tab, Tabs } from './tabs.mjs'
|
||||
import { Markdown } from './markdown.mjs'
|
||||
import { HtmlSpan } from './html-span.mjs'
|
||||
/**
|
||||
* This object holds all components that can be swizzled
|
||||
*/
|
||||
const defaultComponents = {
|
||||
Accordion,
|
||||
AuthWrapper,
|
||||
AuthMessageWrapper,
|
||||
BackIcon,
|
||||
ContactSupport,
|
||||
AuthRequired,
|
||||
AccountInactive,
|
||||
AccountDisabled,
|
||||
AccountProhibited,
|
||||
AccountStatusUnknown,
|
||||
AnchorLink,
|
||||
AsideViewMenu,
|
||||
AsideViewMenuIcons,
|
||||
AsideViewMenuButton,
|
||||
AsideViewMenuSpacer,
|
||||
RoleLacking,
|
||||
ConsentLacking,
|
||||
BaseAccordion,
|
||||
BookmarkedSetPicker,
|
||||
ButtonFrame,
|
||||
CardLink,
|
||||
CircleIcon,
|
||||
CoreSetting,
|
||||
CoreSettingsMenu,
|
||||
CuratedMeasurementsSetIcon,
|
||||
CuratedMeasurementsSetLineup,
|
||||
CuratedSetPicker,
|
||||
DesignOption,
|
||||
DesignOptionsMenu,
|
||||
DesignsView,
|
||||
DraftMenu,
|
||||
DraftView,
|
||||
ErrorView,
|
||||
SaveView,
|
||||
Flag,
|
||||
FlagsAccordionTitle,
|
||||
FlagsAccordionEntries,
|
||||
FlagTypeIcon,
|
||||
FormControl,
|
||||
HeaderMenu,
|
||||
HeaderMenuAllViews,
|
||||
HeaderMenuDraftView,
|
||||
HeaderMenuDraftViewDesignOptions,
|
||||
HeaderMenuDraftViewCoreSettings,
|
||||
HeaderMenuDraftViewUiPreferences,
|
||||
HeaderMenuDraftViewFlags,
|
||||
HeaderMenuDraftViewIcons,
|
||||
HeaderMenuButton,
|
||||
HeaderMenuDropdown,
|
||||
HeaderMenuIcon,
|
||||
HeaderMenuIconSpacer,
|
||||
HeaderMenuSaveIcons,
|
||||
HeaderMenuUndoIcons,
|
||||
HtmlSpan,
|
||||
LargeScreenOnly,
|
||||
Link,
|
||||
ListInput,
|
||||
Loading,
|
||||
LoadingStatus,
|
||||
Markdown,
|
||||
MarkdownInput,
|
||||
MeasurementInput,
|
||||
MeasurementsSetCard,
|
||||
MeasurementsView,
|
||||
MeasurementsEditor,
|
||||
MenuIcon,
|
||||
NumberInput,
|
||||
Null,
|
||||
PageLink,
|
||||
Pattern,
|
||||
PatternLayout,
|
||||
Popout,
|
||||
StringInput,
|
||||
SubAccordion,
|
||||
Spinner,
|
||||
SpinnerIcon,
|
||||
Tab,
|
||||
Tabs,
|
||||
TemporaryLoader,
|
||||
ToggleInput,
|
||||
Tooltip,
|
||||
UiPreferencesMenu,
|
||||
UiPreference,
|
||||
UndoStep,
|
||||
UndoStepTimeAgo,
|
||||
UndosView,
|
||||
UserSetPicker,
|
||||
Ux,
|
||||
HeaderMenuViewMenu,
|
||||
ViewPicker,
|
||||
ViewTypeIcon,
|
||||
WebLink,
|
||||
ZoomablePattern,
|
||||
ZoomContextProvider,
|
||||
// icons
|
||||
ApplyIcon,
|
||||
BeakerIcon,
|
||||
BookmarkIcon,
|
||||
BoolNoIcon,
|
||||
BoolYesIcon,
|
||||
CloseIcon,
|
||||
DesignIcon,
|
||||
DetailIcon,
|
||||
DocsIcon,
|
||||
DownIcon,
|
||||
EditIcon,
|
||||
ExpandIcon,
|
||||
ExportIcon,
|
||||
FailureIcon,
|
||||
FlagIcon,
|
||||
FlagNoteIcon,
|
||||
FlagInfoIcon,
|
||||
FlagTipIcon,
|
||||
FlagWarningIcon,
|
||||
FlagErrorIcon,
|
||||
FlagFixmeIcon,
|
||||
FlagExpandIcon,
|
||||
FlagOtionsIcon,
|
||||
GaugeIcon,
|
||||
GroupIcon,
|
||||
HelpIcon,
|
||||
IncludeIcon,
|
||||
KioskIcon,
|
||||
LeftIcon,
|
||||
ListIcon,
|
||||
LockIcon,
|
||||
MarginIcon,
|
||||
MeasurementsIcon,
|
||||
MeasurementsSetIcon,
|
||||
NoIcon,
|
||||
OkIcon,
|
||||
OptionsIcon,
|
||||
PaperlessIcon,
|
||||
PlusIcon,
|
||||
PrintIcon,
|
||||
ResetAllIcon,
|
||||
ResetIcon,
|
||||
RightIcon,
|
||||
RocketIcon,
|
||||
RotateIcon,
|
||||
SaIcon,
|
||||
SaveIcon,
|
||||
SaveAsIcon,
|
||||
ScaleIcon,
|
||||
SettingsIcon,
|
||||
SuccessIcon,
|
||||
TipIcon,
|
||||
TrashIcon,
|
||||
UiIcon,
|
||||
UndoIcon,
|
||||
UnitsIcon,
|
||||
UpIcon,
|
||||
UploadIcon,
|
||||
UxIcon,
|
||||
XrayIcon,
|
||||
ViewDraftIcon,
|
||||
ViewMeasurementsIcon,
|
||||
ViewTestIcon,
|
||||
ViewTimingIcon,
|
||||
ViewPrintLayoutIcon,
|
||||
ViewSaveIcon,
|
||||
ViewExportIcon,
|
||||
ViewEditSettingsIcon,
|
||||
ViewLogsIcon,
|
||||
ViewInspectIcon,
|
||||
ViewDocsIcon,
|
||||
ViewDesignsIcon,
|
||||
ViewViewPickerIcon,
|
||||
ViewUndosIcon,
|
||||
// menus
|
||||
MenuItem,
|
||||
MenuItemGroup,
|
||||
MenuItemTitle,
|
||||
MenuBoolInput,
|
||||
MenuConstantInput,
|
||||
MenuDegInput,
|
||||
MenuEditOption,
|
||||
MenuListInput,
|
||||
MenuListToggle,
|
||||
MenuMmInput,
|
||||
//MenuNumberInput,
|
||||
MenuUxSettingInput,
|
||||
MenuOnlySettingInput,
|
||||
MenuPctInput,
|
||||
MenuSliderInput,
|
||||
MenuBoolValue,
|
||||
MenuConstantOptionValue,
|
||||
MenuCountOptionValue,
|
||||
MenuDegOptionValue,
|
||||
MenuHighlightValue,
|
||||
MenuListOptionValue,
|
||||
MenuListValue,
|
||||
MenuMmOptionValue,
|
||||
MenuMmValue,
|
||||
MenuOnlySettingValue,
|
||||
MenuPctOptionValue,
|
||||
MenuScaleSettingValue,
|
||||
MenuShowValue,
|
||||
}
|
||||
|
||||
/*
|
||||
* This method returns a component that can be swizzled
|
||||
* So either the passed-in component, or the default one
|
||||
*/
|
||||
const swizzleComponents = (components = {}, Swizzled) => {
|
||||
/*
|
||||
* We need to return all resulting components, swizzled or not
|
||||
* So we create this object so we can pass that down
|
||||
*/
|
||||
const all = {}
|
||||
for (let [name, Component] of Object.entries(defaultComponents)) {
|
||||
all[name] = components[name]
|
||||
? (props) => components[name]({ Swizzled, ...props })
|
||||
: (props) => <Component {...props} Swizzled={Swizzled} />
|
||||
}
|
||||
|
||||
/*
|
||||
* Return all components
|
||||
*/
|
||||
return all
|
||||
}
|
||||
|
||||
/*
|
||||
* Named exports
|
||||
*/
|
||||
export {
|
||||
swizzleComponents,
|
||||
// Re-export all components for specific imports
|
||||
Accordion,
|
||||
AuthWrapper,
|
||||
AuthMessageWrapper,
|
||||
BackIcon,
|
||||
ContactSupport,
|
||||
AuthRequired,
|
||||
AccountInactive,
|
||||
AccountDisabled,
|
||||
AccountProhibited,
|
||||
AccountStatusUnknown,
|
||||
AnchorLink,
|
||||
AsideViewMenu,
|
||||
AsideViewMenuIcons,
|
||||
AsideViewMenuButton,
|
||||
AsideViewMenuSpacer,
|
||||
RoleLacking,
|
||||
ConsentLacking,
|
||||
BaseAccordion,
|
||||
BookmarkedSetPicker,
|
||||
ButtonFrame,
|
||||
CardLink,
|
||||
CircleIcon,
|
||||
CoreSetting,
|
||||
CoreSettingsMenu,
|
||||
CuratedMeasurementsSetIcon,
|
||||
CuratedMeasurementsSetLineup,
|
||||
CuratedSetPicker,
|
||||
DesignOption,
|
||||
DesignOptionsMenu,
|
||||
DesignsView,
|
||||
DraftMenu,
|
||||
DraftView,
|
||||
ErrorView,
|
||||
SaveView,
|
||||
Flag,
|
||||
FlagsAccordionTitle,
|
||||
FlagsAccordionEntries,
|
||||
FlagTypeIcon,
|
||||
FormControl,
|
||||
HeaderMenu,
|
||||
HeaderMenuAllViews,
|
||||
HeaderMenuDraftView,
|
||||
HeaderMenuDraftViewDesignOptions,
|
||||
HeaderMenuDraftViewCoreSettings,
|
||||
HeaderMenuDraftViewUiPreferences,
|
||||
HeaderMenuDraftViewFlags,
|
||||
HeaderMenuDraftViewIcons,
|
||||
HeaderMenuButton,
|
||||
HeaderMenuDropdown,
|
||||
HeaderMenuIcon,
|
||||
HeaderMenuIconSpacer,
|
||||
HeaderMenuSaveIcons,
|
||||
HeaderMenuUndoIcons,
|
||||
HtmlSpan,
|
||||
LargeScreenOnly,
|
||||
Link,
|
||||
ListInput,
|
||||
Loading,
|
||||
LoadingStatus,
|
||||
Markdown,
|
||||
MarkdownInput,
|
||||
MeasurementInput,
|
||||
MeasurementsSetCard,
|
||||
MeasurementsView,
|
||||
MeasurementsEditor,
|
||||
MenuIcon,
|
||||
NumberInput,
|
||||
Null,
|
||||
PageLink,
|
||||
Pattern,
|
||||
PatternLayout,
|
||||
Popout,
|
||||
StringInput,
|
||||
SubAccordion,
|
||||
Spinner,
|
||||
SpinnerIcon,
|
||||
Tab,
|
||||
Tabs,
|
||||
TemporaryLoader,
|
||||
ToggleInput,
|
||||
Tooltip,
|
||||
UiPreferencesMenu,
|
||||
UiPreference,
|
||||
UndoStep,
|
||||
UndoStepTimeAgo,
|
||||
UndosView,
|
||||
UserSetPicker,
|
||||
Ux,
|
||||
HeaderMenuViewMenu,
|
||||
ViewPicker,
|
||||
ViewTypeIcon,
|
||||
WebLink,
|
||||
ZoomablePattern,
|
||||
ZoomContextProvider,
|
||||
// icons
|
||||
ApplyIcon,
|
||||
BeakerIcon,
|
||||
BookmarkIcon,
|
||||
BoolNoIcon,
|
||||
BoolYesIcon,
|
||||
CloseIcon,
|
||||
DesignIcon,
|
||||
DetailIcon,
|
||||
DocsIcon,
|
||||
DownIcon,
|
||||
EditIcon,
|
||||
ExpandIcon,
|
||||
ExportIcon,
|
||||
FailureIcon,
|
||||
FlagIcon,
|
||||
FlagNoteIcon,
|
||||
FlagInfoIcon,
|
||||
FlagTipIcon,
|
||||
FlagWarningIcon,
|
||||
FlagErrorIcon,
|
||||
FlagFixmeIcon,
|
||||
FlagExpandIcon,
|
||||
FlagOtionsIcon,
|
||||
GaugeIcon,
|
||||
GroupIcon,
|
||||
HelpIcon,
|
||||
IncludeIcon,
|
||||
KioskIcon,
|
||||
LeftIcon,
|
||||
ListIcon,
|
||||
LockIcon,
|
||||
MarginIcon,
|
||||
MeasurementsIcon,
|
||||
MeasurementsSetIcon,
|
||||
NoIcon,
|
||||
OkIcon,
|
||||
OptionsIcon,
|
||||
PaperlessIcon,
|
||||
PlusIcon,
|
||||
PrintIcon,
|
||||
ResetAllIcon,
|
||||
ResetIcon,
|
||||
RightIcon,
|
||||
RocketIcon,
|
||||
RotateIcon,
|
||||
SaIcon,
|
||||
SaveIcon,
|
||||
SaveAsIcon,
|
||||
ScaleIcon,
|
||||
SettingsIcon,
|
||||
SuccessIcon,
|
||||
TipIcon,
|
||||
TrashIcon,
|
||||
UiIcon,
|
||||
UndoIcon,
|
||||
UnitsIcon,
|
||||
UpIcon,
|
||||
UploadIcon,
|
||||
UxIcon,
|
||||
XrayIcon,
|
||||
ViewDraftIcon,
|
||||
ViewMeasurementsIcon,
|
||||
ViewTestIcon,
|
||||
ViewTimingIcon,
|
||||
ViewPrintLayoutIcon,
|
||||
ViewSaveIcon,
|
||||
ViewExportIcon,
|
||||
ViewEditSettingsIcon,
|
||||
ViewLogsIcon,
|
||||
ViewInspectIcon,
|
||||
ViewDocsIcon,
|
||||
ViewDesignsIcon,
|
||||
ViewViewPickerIcon,
|
||||
ViewUndosIcon,
|
||||
// menus
|
||||
MenuItem,
|
||||
MenuItemGroup,
|
||||
MenuItemTitle,
|
||||
MenuBoolInput,
|
||||
MenuConstantInput,
|
||||
MenuDegInput,
|
||||
MenuEditOption,
|
||||
MenuListInput,
|
||||
MenuListToggle,
|
||||
MenuMmInput,
|
||||
//MenuNumberInput,
|
||||
MenuUxSettingInput,
|
||||
MenuOnlySettingInput,
|
||||
MenuPctInput,
|
||||
MenuSliderInput,
|
||||
MenuBoolValue,
|
||||
MenuConstantOptionValue,
|
||||
MenuCountOptionValue,
|
||||
MenuDegOptionValue,
|
||||
MenuHighlightValue,
|
||||
MenuListOptionValue,
|
||||
MenuListValue,
|
||||
MenuMmOptionValue,
|
||||
MenuMmValue,
|
||||
MenuOnlySettingValue,
|
||||
MenuPctOptionValue,
|
||||
MenuScaleSettingValue,
|
||||
MenuShowValue,
|
||||
}
|
|
@ -1,349 +0,0 @@
|
|||
// Hooks
|
||||
import { useState } from 'react'
|
||||
|
||||
/*
|
||||
* Helper component to wrap a form control with a label
|
||||
*/
|
||||
export const FormControl = ({
|
||||
label, // the (top-left) label
|
||||
children, // Children to go inside the form control
|
||||
docs = false, // Optional top-right label
|
||||
labelBL = false, // Optional bottom-left label
|
||||
labelBR = false, // Optional bottom-right label
|
||||
forId = false, // ID of the for element we are wrapping
|
||||
Swizzled, // Object holding swizzled code
|
||||
}) => {
|
||||
if (labelBR && !labelBL) labelBL = <span></span>
|
||||
|
||||
const topLabelChildren = (
|
||||
<>
|
||||
<span className="label-text text-lg font-bold mb-0 text-inherit">{label}</span>
|
||||
{docs ? (
|
||||
<span className="label-text-alt">
|
||||
<button
|
||||
className="btn btn-ghost btn-sm btn-circle hover:btn-secondary"
|
||||
onClick={() =>
|
||||
Swizzled.methods.setModal(
|
||||
<Swizzled.components.ModalWrapper
|
||||
flex="col"
|
||||
justify="top lg:justify-center"
|
||||
slideFrom="right"
|
||||
keepOpenOnClick
|
||||
>
|
||||
<div className="mdx max-w-prose">{docs}</div>
|
||||
</Swizzled.components.ModalWrapper>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Swizzled.components.DocsIcon />
|
||||
</button>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
const bottomLabelChildren = (
|
||||
<>
|
||||
{labelBL ? <span className="label-text-alt">{labelBL}</span> : null}
|
||||
{labelBR ? <span className="label-text-alt">{labelBR}</span> : null}
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="form-control w-full mt-2">
|
||||
{forId ? (
|
||||
<label className="label pb-0" htmlFor={forId}>
|
||||
{topLabelChildren}
|
||||
</label>
|
||||
) : (
|
||||
<div className="label pb-0">{topLabelChildren}</div>
|
||||
)}
|
||||
{children}
|
||||
{labelBL || labelBR ? (
|
||||
forId ? (
|
||||
<label className="label" htmlFor={forId}>
|
||||
{bottomLabelChildren}
|
||||
</label>
|
||||
) : (
|
||||
<div className="label">{bottomLabelChildren}</div>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper method to wrap content in a button
|
||||
*/
|
||||
export const ButtonFrame = ({
|
||||
children, // Children of the button
|
||||
onClick, // onClick handler
|
||||
active, // Whether or not to render the button as active/selected
|
||||
accordion = false, // Set this to true to not set a background color when active
|
||||
dense = false, // Use less padding
|
||||
}) => (
|
||||
<button
|
||||
className={`
|
||||
btn btn-ghost btn-secondary
|
||||
w-full ${dense ? 'mt-1 py-0 btn-sm' : 'mt-2 py-4 h-auto content-start'}
|
||||
border-2 border-secondary text-left bg-opacity-20
|
||||
${accordion ? 'hover:bg-transparent' : 'hover:bg-secondary hover:bg-opacity-10'}
|
||||
hover:border-secondary hover:border-solid hover:border-2
|
||||
${active ? 'border-solid' : 'border-dotted'}
|
||||
${active && !accordion ? 'bg-secondary' : 'bg-transparent'}
|
||||
`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
|
||||
/*
|
||||
* Input for integers
|
||||
*/
|
||||
export const NumberInput = ({
|
||||
label, // Label to use
|
||||
update, // onChange handler
|
||||
valid, // Method that should return whether the value is valid or not
|
||||
current, // The current value
|
||||
original, // The original value
|
||||
placeholder, // The placeholder text
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
labelBL = false, // Bottom-Left label
|
||||
labelBR = false, // Bottom-Right label
|
||||
max = 0,
|
||||
min = 220,
|
||||
step = 1,
|
||||
Swizzled, // Object holding swizzled code
|
||||
}) => (
|
||||
<Swizzled.components.FormControl {...{ label, labelBL, labelBR, docs }} forId={id}>
|
||||
<input
|
||||
id={id}
|
||||
type="number"
|
||||
placeholder={placeholder}
|
||||
value={current}
|
||||
onChange={(evt) => update(evt.target.value)}
|
||||
className={`input w-full input-bordered ${
|
||||
current === original ? 'input-secondary' : valid(current) ? 'input-success' : 'input-error'
|
||||
}`}
|
||||
{...{ max, min, step }}
|
||||
/>
|
||||
</Swizzled.components.FormControl>
|
||||
)
|
||||
|
||||
/*
|
||||
* Input for strings
|
||||
*/
|
||||
export const StringInput = ({
|
||||
label, // Label to use
|
||||
update, // onChange handler
|
||||
valid, // Method that should return whether the value is valid or not
|
||||
current, // The current value
|
||||
original, // The original value
|
||||
placeholder, // The placeholder text
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
labelBL = false, // Bottom-Left label
|
||||
labelBR = false, // Bottom-Right label
|
||||
Swizzled, // Object holding swizzled code
|
||||
}) => (
|
||||
<Swizzled.components.FormControl {...{ label, labelBL, labelBR, docs }} forId={id}>
|
||||
<input
|
||||
id={id}
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
value={current}
|
||||
onChange={(evt) => update(evt.target.value)}
|
||||
className={`input w-full input-bordered ${
|
||||
current === original ? 'input-secondary' : valid(current) ? 'input-success' : 'input-error'
|
||||
}`}
|
||||
/>
|
||||
</Swizzled.components.FormControl>
|
||||
)
|
||||
|
||||
/*
|
||||
* Input for a list of things to pick from
|
||||
*/
|
||||
export const ListInput = ({
|
||||
update, // the onChange handler
|
||||
label, // The label
|
||||
list, // The list of items to present { val, label, desc }
|
||||
current, // The (value of the) current item
|
||||
docs = false, // Docs to load, if any
|
||||
Swizzled, // Object holding swizzled code
|
||||
}) => (
|
||||
<Swizzled.components.FormControl label={label} docs={docs}>
|
||||
{list.map((item, i) => (
|
||||
<Swizzled.components.ButtonFrame
|
||||
key={i}
|
||||
active={item.val === current}
|
||||
onClick={() => update(item.val)}
|
||||
>
|
||||
<div className="w-full flex flex-col gap-2">
|
||||
<div className="w-full text-lg leading-5">{item.label}</div>
|
||||
{item.desc ? (
|
||||
<div className="w-full text-normal font-normal normal-case pt-1 leading-5">
|
||||
{item.desc}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Swizzled.components.ButtonFrame>
|
||||
))}
|
||||
</Swizzled.components.FormControl>
|
||||
)
|
||||
|
||||
/*
|
||||
* Input for markdown content
|
||||
*/
|
||||
export const MarkdownInput = ({
|
||||
label, // The label
|
||||
current, // The current value (markdown)
|
||||
update, // The onChange handler
|
||||
placeholder, // The placeholder content
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
labelBL = false, // Bottom-Left label
|
||||
labelBR = false, // Bottom-Right label
|
||||
Swizzled, // Swizzled code
|
||||
}) => (
|
||||
<Swizzled.components.FormControl {...{ label, labelBL, labelBR, docs }} forId={id}>
|
||||
<Swizzled.components.Tabs tabs={['edit', 'preview']}>
|
||||
<Swizzled.components.Tab key="edit">
|
||||
<div className="flex flex-row items-center mt-4">
|
||||
<textarea
|
||||
id={id}
|
||||
rows="5"
|
||||
className="textarea textarea-bordered textarea-lg w-full"
|
||||
value={current}
|
||||
placeholder={placeholder}
|
||||
onChange={(evt) => update(evt.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</Swizzled.components.Tab>
|
||||
<Swizzled.components.Tab key="preview">
|
||||
<div className="mdx mt-4 shadow p-2 px-4 rounded">
|
||||
<Swizzled.components.Markdown>{current}</Swizzled.components.Markdown>
|
||||
</div>
|
||||
</Swizzled.components.Tab>
|
||||
</Swizzled.components.Tabs>
|
||||
</Swizzled.components.FormControl>
|
||||
)
|
||||
|
||||
export const MeasurementInput = ({
|
||||
imperial, // True for imperial, False for metric
|
||||
m, // The measurement name
|
||||
original, // The original value
|
||||
update, // The onChange handler
|
||||
placeholder, // The placeholder content
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
Swizzled, // Swizzled code
|
||||
}) => {
|
||||
const { t } = Swizzled.methods
|
||||
const isDegree = Swizzled.methods.isDegreeMeasurement(m)
|
||||
const units = imperial ? 'imperial' : 'metric'
|
||||
|
||||
const [localVal, setLocalVal] = useState(
|
||||
typeof original === 'undefined'
|
||||
? original
|
||||
: isDegree
|
||||
? Number(original)
|
||||
: Swizzled.methods.measurementAsUnits(original, units)
|
||||
)
|
||||
const [validatedVal, setValidatedVal] = useState(
|
||||
Swizzled.methods.measurementAsUnits(original, units)
|
||||
)
|
||||
const [valid, setValid] = useState(null)
|
||||
|
||||
// Update onChange
|
||||
const localUpdate = (newVal) => {
|
||||
setLocalVal(newVal)
|
||||
const parsedVal = isDegree
|
||||
? Number(newVal)
|
||||
: Swizzled.methods.parseDistanceInput(newVal, imperial)
|
||||
if (parsedVal) {
|
||||
update(m, isDegree ? parsedVal : Swizzled.methods.measurementAsMm(parsedVal, units))
|
||||
setValid(true)
|
||||
setValidatedVal(parsedVal)
|
||||
} else setValid(false)
|
||||
}
|
||||
|
||||
if (!m) return null
|
||||
|
||||
// Various visual indicators for validating the input
|
||||
let inputClasses = 'input-secondary'
|
||||
let bottomLeftLabel = null
|
||||
if (valid === true) {
|
||||
inputClasses = 'input-success'
|
||||
const val = `${validatedVal}${isDegree ? '°' : imperial ? '"' : 'cm'}`
|
||||
bottomLeftLabel = <span className="font-medium text-base text-success -mt-2 block">{val}</span>
|
||||
} else if (valid === false) {
|
||||
inputClasses = 'input-error'
|
||||
bottomLeftLabel = (
|
||||
<span className="font-medium text-error text-base -mt-2 block">¯\_(ツ)_/¯</span>
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* I'm on the fence here about using a text input rather than number
|
||||
* Obviously, number is the more correct option, but when the user enter
|
||||
* text, it won't fire an onChange event and thus they can enter text and it
|
||||
* will not be marked as invalid input.
|
||||
* See: https://github.com/facebook/react/issues/16554
|
||||
*/
|
||||
return (
|
||||
<Swizzled.components.FormControl
|
||||
label={t(m) + (isDegree ? ' (°)' : '')}
|
||||
docs={docs}
|
||||
forId={id}
|
||||
labelBL={bottomLeftLabel}
|
||||
>
|
||||
<input
|
||||
id={id}
|
||||
type="number"
|
||||
placeholder={placeholder}
|
||||
value={localVal}
|
||||
onChange={(evt) => localUpdate(evt.target.value)}
|
||||
className={`input w-full input-bordered ${inputClasses}`}
|
||||
/>
|
||||
</Swizzled.components.FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* Input for booleans
|
||||
*/
|
||||
export const ToggleInput = ({
|
||||
label, // Label to use
|
||||
update, // onChange handler
|
||||
current, // The current value
|
||||
disabled = false, // Allows rendering a disabled view
|
||||
list = [true, false], // The values to chose between
|
||||
labels = ['Yes', 'No'], // The labels for the values
|
||||
on = true, // The value that should show the toggle in the 'on' state
|
||||
id = '', // An id to tie the input to the label
|
||||
labelTR = false, // Top-Right label
|
||||
labelBL = false, // Bottom-Left label
|
||||
labelBR = false, // Bottom-Right label
|
||||
Swizzled, // Object holding swizzled code
|
||||
}) => (
|
||||
<Swizzled.components.FormControl
|
||||
{...{ labelBL, labelBR, labelTR }}
|
||||
label={
|
||||
label
|
||||
? `${label} (${current === on ? labels[0] : labels[1]})`
|
||||
: `${current === on ? labels[0] : labels[1]}`
|
||||
}
|
||||
forId={id}
|
||||
>
|
||||
<input
|
||||
id={id}
|
||||
disabled={disabled}
|
||||
type="checkbox"
|
||||
value={current}
|
||||
onChange={() => update(list.indexOf(current) === 0 ? list[1] : list[0])}
|
||||
className="toggle my-3 toggle-primary"
|
||||
checked={list.indexOf(current) === 0 ? true : false}
|
||||
/>
|
||||
</Swizzled.components.FormControl>
|
||||
)
|
|
@ -1 +0,0 @@
|
|||
export const LargeScreenOnly = ({ children }) => <div className="hidden xl:block">{children}</div>
|
|
@ -1,48 +0,0 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
const AnchorLink = ({ id, txt = false, children, Swizzled }) => (
|
||||
<a href={`#${id}`} className={Swizzled.config.classes.link} title={txt ? txt : ''}>
|
||||
{txt ? txt : children}
|
||||
</a>
|
||||
)
|
||||
|
||||
const PageLink = ({ href, txt = false, children, Swizzled }) => (
|
||||
<Swizzled.components.Link
|
||||
href={href}
|
||||
className={Swizzled.config.classes.link}
|
||||
title={txt ? txt : ''}
|
||||
>
|
||||
{children ? children : txt}
|
||||
</Swizzled.components.Link>
|
||||
)
|
||||
|
||||
const WebLink = ({ href, txt = false, children, Swizzled }) => (
|
||||
<a href={href} className={Swizzled.config.classes.link} title={txt ? txt : ''}>
|
||||
{children ? children : txt}
|
||||
</a>
|
||||
)
|
||||
|
||||
const CardLink = ({
|
||||
bg = 'bg-base-200',
|
||||
textColor = 'text-base-content',
|
||||
href,
|
||||
title,
|
||||
text,
|
||||
icon,
|
||||
Swizzled,
|
||||
}) => (
|
||||
<Swizzled.components.Link
|
||||
href={href}
|
||||
className={`px-8 ${bg} py-10 rounded-lg block ${textColor}
|
||||
hover:bg-secondary hover:bg-opacity-10 shadow-lg
|
||||
transition-color duration-300 grow`}
|
||||
>
|
||||
<h2 className="mb-4 text-inherit flex flex-row gap-4 justify-between items-center font-medium">
|
||||
{title}
|
||||
<span className="shrink-0">{icon}</span>
|
||||
</h2>
|
||||
<p className="font-medium text-inherit italic text-lg">{text}</p>
|
||||
</Swizzled.components.Link>
|
||||
)
|
||||
|
||||
export { Link, AnchorLink, PageLink, WebLink, CardLink }
|
|
@ -1,3 +0,0 @@
|
|||
import Markdown from 'react-markdown'
|
||||
|
||||
export { Markdown }
|
|
@ -1,94 +0,0 @@
|
|||
const sizes = { lg: 96, md: 52, sm: 36 }
|
||||
|
||||
export const MeasurementsSetCard = ({
|
||||
set,
|
||||
onClick = false,
|
||||
href = false,
|
||||
useA = false,
|
||||
Design = false,
|
||||
language = false,
|
||||
size = 'lg',
|
||||
Swizzled,
|
||||
}) => {
|
||||
const s = sizes[size]
|
||||
const { t, hasRequiredMeasurements } = Swizzled.methods
|
||||
const { NoIcon, OkIcon } = Swizzled.components
|
||||
|
||||
const wrapperProps = {
|
||||
className: `bg-base-300 aspect-square h-${s} w-${s} mb-2
|
||||
mx-auto flex flex-col items-start text-center justify-between rounded-none md:rounded shadow`,
|
||||
style: {
|
||||
backgroundImage: `url(${Swizzled.methods.cloudImageUrl({ type: 'w500', id: set.img })})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: '50%',
|
||||
},
|
||||
}
|
||||
if (!set.img || set.img === 'default-avatar')
|
||||
wrapperProps.style.backgroundPosition = 'bottom right'
|
||||
|
||||
let icon = <span></span>
|
||||
let missingMeasies = ''
|
||||
let linebreak = ''
|
||||
const maxLength = 75
|
||||
if (Design) {
|
||||
const [hasMeasies, missing] = hasRequiredMeasurements(Design, set.measies)
|
||||
const iconClasses = 'w-8 h-8 p-1 rounded-full -mt-2 -ml-2 shadow'
|
||||
icon = hasMeasies ? (
|
||||
<OkIcon className={`${iconClasses} bg-success text-success-content`} stroke={4} />
|
||||
) : (
|
||||
<NoIcon className={`${iconClasses} bg-error text-error-content`} stroke={3} />
|
||||
)
|
||||
if (missing.length > 0) {
|
||||
const translated = missing.map((m) => {
|
||||
return t(m)
|
||||
})
|
||||
let missingString = translated.join(', ')
|
||||
if (missingString.length > maxLength) {
|
||||
const lastSpace = missingString.lastIndexOf(', ', maxLength)
|
||||
missingString = missingString.substring(0, lastSpace) + ', ' + '...'
|
||||
}
|
||||
const measieClasses = 'font-normal text-xs'
|
||||
missingMeasies = <span className={`${measieClasses}`}>{missingString}</span>
|
||||
linebreak = <br />
|
||||
}
|
||||
}
|
||||
|
||||
const inner = (
|
||||
<>
|
||||
{icon}
|
||||
<span className="bg-neutral text-neutral-content px-4 w-full bg-opacity-50 py-2 rounded rounded-t-none font-bold leading-5">
|
||||
{language ? set[`name${Swizzled.methods.capitalize(language)}`] : set.name}
|
||||
{linebreak}
|
||||
{missingMeasies}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
|
||||
// Is it a button with an onClick handler?
|
||||
if (onClick)
|
||||
return (
|
||||
<button {...wrapperProps} onClick={() => onClick(set)}>
|
||||
{inner}
|
||||
</button>
|
||||
)
|
||||
|
||||
// Returns a link to an internal page
|
||||
if (href && !useA)
|
||||
return (
|
||||
<Swizzled.components.Link {...wrapperProps} href={href}>
|
||||
{inner}
|
||||
</Swizzled.components.Link>
|
||||
)
|
||||
|
||||
// Returns a link to an external page
|
||||
if (href && useA)
|
||||
return (
|
||||
<a {...wrapperProps} href={href}>
|
||||
{inner}
|
||||
</a>
|
||||
)
|
||||
|
||||
// Returns a div
|
||||
return <div {...wrapperProps}>{inner}</div>
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
const colors = {
|
||||
comment: 'secondary',
|
||||
note: 'primary',
|
||||
tip: 'accent',
|
||||
warning: 'error',
|
||||
error: 'error',
|
||||
fixme: 'warning',
|
||||
link: 'secondary',
|
||||
related: 'info',
|
||||
tldr: 'info',
|
||||
none: '',
|
||||
}
|
||||
|
||||
/**
|
||||
* This popout component is a way to make some content stand out
|
||||
*
|
||||
* @param {object} components - Object holding possibly swizzled components
|
||||
* @param {object} methods - Object holding possibly swizzled methods
|
||||
*/
|
||||
export const Popout = (props) => {
|
||||
/*
|
||||
* Load (swizzled) components
|
||||
*/
|
||||
const { CloseIcon } = props.Swizzled.components
|
||||
|
||||
/*
|
||||
* Load (swizzled) methods
|
||||
*/
|
||||
const { t } = props.Swizzled.methods
|
||||
|
||||
const [hide, setHide] = useState(false)
|
||||
if (hide) return null
|
||||
|
||||
let type = 'none'
|
||||
for (const c in colors) {
|
||||
if (props[c]) type = c
|
||||
}
|
||||
const color = colors[type]
|
||||
const { className = '' } = props
|
||||
|
||||
return props.compact ? (
|
||||
<div
|
||||
className={`relative ${
|
||||
props.dense ? 'my-1' : 'my-8'
|
||||
} bg-${color} bg-opacity-5 -ml-4 -mr-4 sm:ml-0 sm:mr-0 ${className}`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
border-y-4 sm:border-0 sm:border-l-4 px-4
|
||||
shadow text-base border-${color}
|
||||
flex flex-row items-center
|
||||
`}
|
||||
>
|
||||
<div className={`font-bold uppercase text-${color}`}>
|
||||
{props.title || (
|
||||
<>
|
||||
<span>{t(`pe:${type}`).toUpperCase()}</span>
|
||||
<span className="px-3">|</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="popout-content">{props.noP ? props.children : <p>{props.children}</p>}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`relative my-8 bg-${color} bg-opacity-5 -ml-4 -mr-4 sm:ml-0 sm:mr-0 ${className}`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
border-y-4 sm:border-0 sm:border-l-4 px-6 sm:px-8 py-4 sm:py-2
|
||||
shadow text-base border-${color}
|
||||
`}
|
||||
>
|
||||
<div className={`font-bold flex flex-row gap-1 items-end justify-between`}>
|
||||
<div>
|
||||
<span className={`font-bold uppercase text-${color}`}>
|
||||
{type === 'tldr' ? 'TL;DR' : t(`pe:${type}`).toUpperCase()}
|
||||
</span>
|
||||
<span className={`font-normal text-base text-${color}`}>
|
||||
{type === 'comment' && (
|
||||
<>
|
||||
{' '}
|
||||
by <b>{props.by}</b>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{props.hideable && (
|
||||
<button onClick={() => setHide(true)} className="hover:text-secondary" title="Close">
|
||||
<CloseIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-1 first:mt-0 popout-content">{props.children}</div>
|
||||
{type === 'comment' && <div className={`font-bold italic text-${color}`}>{props.by}</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
export const Spinner = ({ className = 'h-6 w-6 animate-spin' }) => (
|
||||
<svg className={className} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const Loading = () => (
|
||||
<Spinner className="w-24 h-24 color-primary animate-spin m-auto mt-8" />
|
||||
)
|
|
@ -1,66 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
export const Tabs = ({ tabs = '', active = 0, children, withModal = false, Swizzled }) => {
|
||||
// Keep active tab in state
|
||||
const [activeTab, setActiveTab] = useState(active)
|
||||
|
||||
/*
|
||||
* In MDX, tabs are passed as a comma-seperated list.
|
||||
* In React, they come as an array.
|
||||
* So we need to handle both cases.
|
||||
*/
|
||||
const tablist = Array.isArray(tabs) ? tabs : tabs.split(',').map((tab) => tab.trim())
|
||||
|
||||
/*
|
||||
* Don't bother unless there's actual tabs to show
|
||||
*/
|
||||
if (!tablist) return null
|
||||
|
||||
/*
|
||||
* Pass down activeTab and tabId for conditional rendering
|
||||
*/
|
||||
const childrenWithTabSetter = children.map((child, tabId) =>
|
||||
React.cloneElement(child, { activeTab, tabId, key: tabId })
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="my-4">
|
||||
<div className="tabs">
|
||||
{tablist.map((title, tabId) => {
|
||||
const btnClasses = `text-lg font-bold capitalize tab h-auto tab-bordered grow py-2 ${
|
||||
activeTab === tabId ? 'tab-active' : ''
|
||||
}`
|
||||
|
||||
return withModal && activeTab === tabId ? (
|
||||
<button
|
||||
key={tabId}
|
||||
className={btnClasses}
|
||||
onClick={() =>
|
||||
Swizzled.methods.setModal(
|
||||
<Swizzled.components.ModalWrapper
|
||||
flex="col"
|
||||
justify="top lg:justify-center"
|
||||
slideFrom="right"
|
||||
fullWidth
|
||||
>
|
||||
{childrenWithTabSetter}
|
||||
</Swizzled.components.ModalWrapper>
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="pr-2">{title}</span>
|
||||
<Swizzled.components.KioskIcon className="w-6 h-6 hover:text-secondary" />
|
||||
</button>
|
||||
) : (
|
||||
<button key={tabId} className={btnClasses} onClick={() => setActiveTab(tabId)}>
|
||||
{title}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div>{childrenWithTabSetter}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Tab = ({ children, tabId, activeTab }) => (activeTab === tabId ? children : null)
|
|
@ -1,73 +0,0 @@
|
|||
/*************************************************************************
|
||||
* *
|
||||
* FreeSewing's pattern editor allows swizzling hooks *
|
||||
* *
|
||||
* To 'swizzle' means to replace the default implementation of a *
|
||||
* hook with a custom one. It allows one to customize *
|
||||
* the pattern editor. *
|
||||
* *
|
||||
* This file holds the 'swizzleHooks' method that will return *
|
||||
* the various hooks that can be swizzled, or their default *
|
||||
* implementation. *
|
||||
* *
|
||||
* To use a custom version, simply pas it as a prop into the editor *
|
||||
* under the 'hooks' key. So to pass a custom 'useAccount' method *
|
||||
* (used for loading the user's account data) you do: *
|
||||
* *
|
||||
* <PatternEditor hooks={{ useAccount: myCustomHook }} /> *
|
||||
* *
|
||||
*************************************************************************/
|
||||
|
||||
/*
|
||||
* Import of components that can be swizzled
|
||||
*/
|
||||
// useAccount
|
||||
import { useAccount } from './use-account.mjs'
|
||||
import { useBackend } from './use-backend.mjs'
|
||||
import {
|
||||
useReactEditorState,
|
||||
useStorageEditorState,
|
||||
useSessionEditorState,
|
||||
useUrlEditorState,
|
||||
} from './use-editor-state.mjs'
|
||||
|
||||
/*
|
||||
* We support different state backend, so let's handle those
|
||||
*/
|
||||
const stateBackends = {
|
||||
react: useReactEditorState,
|
||||
storage: useStorageEditorState,
|
||||
session: useSessionEditorState,
|
||||
url: useUrlEditorState,
|
||||
}
|
||||
|
||||
/**
|
||||
* This object holds all hooks that can be swizzled
|
||||
*/
|
||||
const defaultHooks = (config) => ({
|
||||
useAccount,
|
||||
useBackend,
|
||||
useEditorState: stateBackends[config.stateBackend] || useReactEditorState,
|
||||
})
|
||||
|
||||
/*
|
||||
* This method returns hooks that can be swizzled
|
||||
* So either the passed-in methods, or the default ones
|
||||
*/
|
||||
export const swizzleHooks = (hooks = {}, Swizzled) => {
|
||||
/*
|
||||
* We need to return the resulting hooks, swizzled or not
|
||||
* So we put this in this object so we can pass that down
|
||||
*/
|
||||
const all = {}
|
||||
for (const [name, hook] of Object.entries(defaultHooks(Swizzled.config))) {
|
||||
all[name] = hooks[name]
|
||||
? (...params) => hooks[name](Swizzled, ...params)
|
||||
: (...params) => hook(Swizzled, ...params)
|
||||
}
|
||||
|
||||
/*
|
||||
* Return all hooks
|
||||
*/
|
||||
return all
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
import useLocalStorageState from 'use-local-storage-state'
|
||||
|
||||
/*
|
||||
* Make it possible to always check for account.username and account.ux
|
||||
*/
|
||||
const noAccount = { username: false, ux: 3 }
|
||||
|
||||
/*
|
||||
* The useAccount hook
|
||||
*/
|
||||
export function useAccount(Swizzled) {
|
||||
// (persisted) State (saved to local storage)
|
||||
const [account, setAccount] = useLocalStorageState('fs-account', { defaultValue: noAccount })
|
||||
const [admin, setAdmin] = useLocalStorageState('fs-admin', { defaultValue: noAccount })
|
||||
const [token, setToken] = useLocalStorageState('fs-token', { defaultValue: null })
|
||||
const [seenUser, setSeenUser] = useLocalStorageState('fs-seen-user', { defaultValue: false })
|
||||
|
||||
// Clear user data. This gets called when signing out
|
||||
const signOut = () => {
|
||||
setAccount(noAccount)
|
||||
setToken(null)
|
||||
}
|
||||
|
||||
// Impersonate a user.
|
||||
// Only admins can do this but that is enforced at the backend.
|
||||
const impersonate = (data) => {
|
||||
setAdmin({ token, account })
|
||||
const newAccount = {
|
||||
...data.account,
|
||||
impersonatingAdmin: { id: account.id, username: account.username },
|
||||
}
|
||||
setAdmin({ token, account: { ...account } })
|
||||
setAccount(newAccount)
|
||||
setToken(data.token)
|
||||
}
|
||||
|
||||
const stopImpersonating = () => {
|
||||
setAccount(admin.account)
|
||||
setToken(admin.token)
|
||||
clearAdmin()
|
||||
}
|
||||
|
||||
const clearAdmin = () => setAdmin(noAccount)
|
||||
|
||||
return {
|
||||
account,
|
||||
setAccount,
|
||||
token,
|
||||
setToken,
|
||||
seenUser,
|
||||
setSeenUser,
|
||||
signOut,
|
||||
admin,
|
||||
clearAdmin,
|
||||
impersonate,
|
||||
stopImpersonating,
|
||||
ux: account?.control || account?.ux || Swizzled.config.defaultUx,
|
||||
}
|
||||
}
|
|
@ -1,633 +0,0 @@
|
|||
// Dependencies
|
||||
import axios from 'axios'
|
||||
// Hooks
|
||||
import { useMemo } from 'react'
|
||||
import { freeSewingConfig } from 'shared/config/freesewing.config.mjs'
|
||||
|
||||
/*
|
||||
* Helper methods to interact with the FreeSewing backend
|
||||
*/
|
||||
const apiHandler = axios.create({
|
||||
baseURL: freeSewingConfig.backend,
|
||||
timeout: 6660,
|
||||
})
|
||||
|
||||
const auth = (token) =>
|
||||
token ? { headers: { Authorization: 'Bearer ' + token } } : { headers: {} }
|
||||
|
||||
/*
|
||||
* This api object handles async code for different HTTP methods
|
||||
*/
|
||||
const api = {
|
||||
get: async (uri, config = {}) => {
|
||||
let result
|
||||
try {
|
||||
result = await apiHandler.get(uri, config)
|
||||
return result
|
||||
} catch (err) {
|
||||
return err
|
||||
}
|
||||
},
|
||||
post: async (uri, data = null, config = {}) => {
|
||||
let result
|
||||
try {
|
||||
result = await apiHandler.post(uri, data, config)
|
||||
return result
|
||||
} catch (err) {
|
||||
return err
|
||||
}
|
||||
},
|
||||
put: async (uri, data = null, config = {}) => {
|
||||
let result
|
||||
try {
|
||||
result = await apiHandler.put(uri, data, config)
|
||||
return result
|
||||
} catch (err) {
|
||||
return err
|
||||
}
|
||||
},
|
||||
patch: async (uri, data = null, config = {}) => {
|
||||
let result
|
||||
try {
|
||||
result = await apiHandler.patch(uri, data, config)
|
||||
return result
|
||||
} catch (err) {
|
||||
return err
|
||||
}
|
||||
},
|
||||
delete: async (uri, config = {}) => {
|
||||
let result
|
||||
try {
|
||||
result = await apiHandler.delete(uri, config)
|
||||
return result
|
||||
} catch (err) {
|
||||
return err
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper method to handle the response and verify that it was successful
|
||||
*/
|
||||
const responseHandler = (response, expectedStatus = 200, expectData = true) => {
|
||||
if (response && response.status === expectedStatus) {
|
||||
if (!expectData || response.data) {
|
||||
return { success: true, data: response.data, response }
|
||||
}
|
||||
return { success: true, response }
|
||||
}
|
||||
|
||||
// Unpack axios errors
|
||||
if (response?.name === 'AxiosError')
|
||||
return {
|
||||
success: false,
|
||||
status: response.response?.status,
|
||||
data: response.response?.data,
|
||||
error: response.message,
|
||||
}
|
||||
|
||||
return { success: false, response }
|
||||
}
|
||||
|
||||
function Backend(auth) {
|
||||
this.auth = auth
|
||||
}
|
||||
|
||||
/*
|
||||
* backend.signUp: User signup
|
||||
*/
|
||||
Backend.prototype.signUp = async function ({ email, language }) {
|
||||
return responseHandler(await api.post('/signup', { email, language }), 201)
|
||||
}
|
||||
|
||||
/*
|
||||
* backend.oauthInit: Init Oauth flow for oauth provider
|
||||
*/
|
||||
Backend.prototype.oauthInit = async function ({ provider, language }) {
|
||||
return responseHandler(await api.post('/signin/oauth/init', { provider, language }))
|
||||
}
|
||||
|
||||
/*
|
||||
* backend.oauthSignIn: User sign in via oauth provider
|
||||
*/
|
||||
Backend.prototype.oauthSignIn = async function ({ state, code, provider }) {
|
||||
return responseHandler(await api.post('/signin/oauth', { state, code, provider }))
|
||||
}
|
||||
|
||||
/*
|
||||
* Backend.prototype.loadConfirmation: Load a confirmation
|
||||
*/
|
||||
Backend.prototype.loadConfirmation = async function ({ id, check }) {
|
||||
return responseHandler(await api.get(`/confirmations/${id}/${check}`))
|
||||
}
|
||||
/*
|
||||
* Backend.prototype.confirmSignup: Confirm a signup
|
||||
*/
|
||||
Backend.prototype.confirmSignup = async function ({ id, consent }) {
|
||||
return responseHandler(await api.post(`/confirm/signup/${id}`, { consent }))
|
||||
}
|
||||
|
||||
/*
|
||||
* Backend.prototype.signIn: User signin/login
|
||||
*/
|
||||
Backend.prototype.signIn = async function ({ username, password = false, token = false }) {
|
||||
return password === false
|
||||
? responseHandler(await api.post('/signinlink', { username }))
|
||||
: responseHandler(await api.post('/signin', { username, password, token }))
|
||||
}
|
||||
|
||||
/*
|
||||
* Backend.prototype.signInFromLink: Trade in sign-in link id & check for JWT token
|
||||
*/
|
||||
Backend.prototype.signInFromLink = async function ({ id, check }) {
|
||||
return responseHandler(await api.post(`/signinlink/${id}/${check}`))
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic update account method
|
||||
*/
|
||||
Backend.prototype.updateAccount = async function (data) {
|
||||
return responseHandler(await api.patch(`/account/jwt`, data, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Update consent (uses the jwt-guest middleware)
|
||||
*/
|
||||
Backend.prototype.updateConsent = async function (consent) {
|
||||
return responseHandler(await api.patch(`/consent/jwt`, { consent }, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks whether a username is available
|
||||
*/
|
||||
Backend.prototype.isUsernameAvailable = async function (username) {
|
||||
const response = await api.post(`/available/username/jwt`, { username }, this.auth)
|
||||
|
||||
// 404 means username is available, which is success in this case
|
||||
return response.status === 404
|
||||
? { success: true, data: false, available: true, response }
|
||||
: { success: false, available: false, response }
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove account method
|
||||
*/
|
||||
Backend.prototype.removeAccount = async function () {
|
||||
return responseHandler(await api.delete(`/account/jwt`, this.auth))
|
||||
}
|
||||
/*
|
||||
* Enable MFA
|
||||
*/
|
||||
Backend.prototype.enableMfa = async function () {
|
||||
return responseHandler(await api.post(`/account/mfa/jwt`, { mfa: true }, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Confirm MFA
|
||||
*/
|
||||
Backend.prototype.confirmMfa = async function (data) {
|
||||
return responseHandler(await api.post(`/account/mfa/jwt`, { ...data, mfa: true }, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable MFA
|
||||
*/
|
||||
Backend.prototype.disableMfa = async function (data) {
|
||||
return responseHandler(await api.post(`/account/mfa/jwt`, { ...data, mfa: false }, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Reload account
|
||||
*/
|
||||
Backend.prototype.reloadAccount = async function () {
|
||||
return responseHandler(await api.get(`/whoami/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Export account data
|
||||
*/
|
||||
Backend.prototype.exportAccount = async function () {
|
||||
return responseHandler(await api.get(`/account/export/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Restrict processing of account data
|
||||
*/
|
||||
Backend.prototype.restrictAccount = async function () {
|
||||
return responseHandler(await api.get(`/account/restrict/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove account
|
||||
*/
|
||||
Backend.prototype.restrictAccount = async function () {
|
||||
return responseHandler(await api.delete(`/account/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Load all user data
|
||||
*/
|
||||
Backend.prototype.getUserData = async function (uid) {
|
||||
return responseHandler(await api.get(`/users/${uid}/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Load user profile
|
||||
*/
|
||||
Backend.prototype.getProfile = async function (uid) {
|
||||
return responseHandler(await api.get(`/users/${uid}`))
|
||||
}
|
||||
|
||||
/*
|
||||
* Load user count
|
||||
*/
|
||||
Backend.prototype.getUserCount = async function () {
|
||||
return responseHandler(await api.get(`/info/users`))
|
||||
}
|
||||
|
||||
/*
|
||||
* Load stats
|
||||
*/
|
||||
Backend.prototype.getStats = async function () {
|
||||
return responseHandler(await api.get(`/info/stats`))
|
||||
}
|
||||
|
||||
/*
|
||||
* Create bookmark
|
||||
*/
|
||||
Backend.prototype.createBookmark = async function (data) {
|
||||
return responseHandler(await api.post(`/bookmarks/jwt`, data, this.auth), 201)
|
||||
}
|
||||
|
||||
/*
|
||||
* Get bookmark
|
||||
*/
|
||||
Backend.prototype.getBookmark = async function (id) {
|
||||
return responseHandler(await api.get(`/bookmarks/${id}/jwt`, this.auth))
|
||||
}
|
||||
/*
|
||||
* Get bookmarks
|
||||
*/
|
||||
Backend.prototype.getBookmarks = async function () {
|
||||
return responseHandler(await api.get(`/bookmarks/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove bookmark
|
||||
*/
|
||||
Backend.prototype.removeBookmark = async function (id) {
|
||||
const response = await api.delete(`/bookmarks/${id}/jwt`, this.auth)
|
||||
|
||||
return response && response.status === 204 ? true : false
|
||||
}
|
||||
|
||||
/*
|
||||
* Create API key
|
||||
*/
|
||||
Backend.prototype.createApikey = async function (data) {
|
||||
return responseHandler(await api.post(`/apikeys/jwt`, data, this.auth), 201)
|
||||
}
|
||||
|
||||
/*
|
||||
* Get API key
|
||||
*/
|
||||
Backend.prototype.getApikey = async function (id) {
|
||||
return responseHandler(await api.get(`/apikeys/${id}/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Get API keys
|
||||
*/
|
||||
Backend.prototype.getApikeys = async function () {
|
||||
return responseHandler(await api.get(`/apikeys/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove API key
|
||||
*/
|
||||
Backend.prototype.removeApikey = async function (id) {
|
||||
const response = await api.delete(`/apikeys/${id}/jwt`, this.auth)
|
||||
|
||||
return response && response.status === 204 ? true : false
|
||||
}
|
||||
|
||||
/*
|
||||
* Get measurements sets
|
||||
*/
|
||||
Backend.prototype.getSets = async function () {
|
||||
return responseHandler(await api.get(`/sets/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Get measurements set
|
||||
*/
|
||||
Backend.prototype.getSet = async function (id) {
|
||||
return responseHandler(await api.get(`/sets/${id}/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Get public measurements set
|
||||
*/
|
||||
Backend.prototype.getPublicSet = async function (id) {
|
||||
return responseHandler(await api.get(`/sets/${id}.json`))
|
||||
}
|
||||
|
||||
/*
|
||||
* Create measurements set
|
||||
*/
|
||||
Backend.prototype.createSet = async function (data) {
|
||||
return responseHandler(await api.post(`/sets/jwt`, data, this.auth), 201)
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove measurements set
|
||||
*/
|
||||
Backend.prototype.removeSet = async function (id) {
|
||||
const response = await api.delete(`/sets/${id}/jwt`, this.auth)
|
||||
|
||||
return response && response.status === 204 ? true : false
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic update measurements set method
|
||||
*/
|
||||
Backend.prototype.updateSet = async function (id, data) {
|
||||
return responseHandler(await api.patch(`/sets/${id}/jwt`, data, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Get curated measurements sets
|
||||
*/
|
||||
Backend.prototype.getCuratedSets = async function () {
|
||||
return responseHandler(await api.get(`/curated-sets`))
|
||||
}
|
||||
|
||||
/*
|
||||
* Get measurements sets suggested for curation
|
||||
*/
|
||||
Backend.prototype.getSuggestedSets = async function () {
|
||||
return responseHandler(await api.get(`/suggested-sets/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Get option packs suggested for curation
|
||||
*/
|
||||
Backend.prototype.getSuggestedPacks = async function () {
|
||||
return responseHandler(await api.get(`/suggested-packs/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove suggested measurements set
|
||||
*/
|
||||
Backend.prototype.removeSuggestedMeasurementsSet = async function (id) {
|
||||
const response = await api.delete(`/suggested-sets/${id}/jwt`, this.auth)
|
||||
|
||||
return response && response.status === 204 ? true : false
|
||||
}
|
||||
|
||||
/*
|
||||
* Get curated measurements set
|
||||
*/
|
||||
Backend.prototype.getCuratedSet = async function (id) {
|
||||
return responseHandler(await api.get(`/curated-sets/${id}`))
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic update curated measurements set method
|
||||
*/
|
||||
Backend.prototype.updateCuratedSet = async function (id, data) {
|
||||
return responseHandler(await api.patch(`/curated-sets/${id}/jwt`, data, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove curated measurements set
|
||||
*/
|
||||
Backend.prototype.removeCuratedMeasurementsSet = async function (id) {
|
||||
const response = await api.delete(`/curated-sets/${id}/jwt`, this.auth)
|
||||
|
||||
return response && response.status === 204 ? true : false
|
||||
}
|
||||
|
||||
/*
|
||||
* Get pattern
|
||||
*/
|
||||
Backend.prototype.getPattern = async function (id) {
|
||||
return responseHandler(await api.get(`/patterns/${id}/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Get public pattern
|
||||
*/
|
||||
Backend.prototype.getPublicPattern = async function (id) {
|
||||
return responseHandler(await api.get(`/patterns/${id}.json`))
|
||||
}
|
||||
|
||||
/*
|
||||
* Get patterns
|
||||
*/
|
||||
Backend.prototype.getPatterns = async function () {
|
||||
return responseHandler(await api.get(`/patterns/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Create pattern
|
||||
*/
|
||||
Backend.prototype.createPattern = async function (data) {
|
||||
return responseHandler(await api.post(`/patterns/jwt`, data, this.auth), 201)
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove pattern
|
||||
*/
|
||||
Backend.prototype.removePattern = async function (id) {
|
||||
const response = await api.delete(`/patterns/${id}/jwt`, this.auth)
|
||||
|
||||
return response && response.status === 204 ? true : false
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic update pattern set method
|
||||
*/
|
||||
Backend.prototype.updatePattern = async function (id, data) {
|
||||
return responseHandler(await api.patch(`/patterns/${id}/jwt`, data, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Create GitHub issue
|
||||
*/
|
||||
Backend.prototype.createIssue = async function (data) {
|
||||
return responseHandler(await api.post(`/issues`, data), 201)
|
||||
}
|
||||
|
||||
/*
|
||||
* Create GitHub discussion
|
||||
*/
|
||||
Backend.prototype.createDiscussion = async function (data) {
|
||||
return responseHandler(await api.post(`/discussions`, data), 201)
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether a slug is available
|
||||
*/
|
||||
Backend.prototype.isSlugAvailable = async function ({ slug, type }) {
|
||||
const response = await api.get(`/slugs/${type}/${slug}/jwt`, this.auth)
|
||||
|
||||
// 404 means username is available, which is success in this case
|
||||
return response.status === 200
|
||||
? { success: false, available: false, response }
|
||||
: { success: true, data: false, available: true, response }
|
||||
}
|
||||
|
||||
/*
|
||||
* Create showcase/blog post (pull request)
|
||||
*/
|
||||
Backend.prototype.createPost = async function (type, data) {
|
||||
return responseHandler(await api.post(`/flows/pr/${type}/jwt`, data, this.auth), 201)
|
||||
}
|
||||
|
||||
/*
|
||||
* Send translation invite
|
||||
*/
|
||||
Backend.prototype.sendTranslatorInvite = async function (language) {
|
||||
return responseHandler(await api.post(`/flows/translator-invite/jwt`, { language }, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Send language suggestion
|
||||
*/
|
||||
Backend.prototype.sendLanguageSuggestion = async function (data) {
|
||||
return responseHandler(await api.post(`/flows/language-suggestion/jwt`, data, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Subscribe to newsletter
|
||||
*/
|
||||
Backend.prototype.newsletterSubscribe = async function ({ email, language }) {
|
||||
return responseHandler(await api.post('/subscriber', { email, language }))
|
||||
}
|
||||
|
||||
/*
|
||||
* Confirm newsletter subscribe
|
||||
*/
|
||||
Backend.prototype.confirmNewsletterSubscribe = async function ({ id, ehash }) {
|
||||
return responseHandler(await api.put('/subscriber', { id, ehash }))
|
||||
}
|
||||
|
||||
/*
|
||||
* Newsletter unsubscribe
|
||||
*/
|
||||
Backend.prototype.newsletterUnsubscribe = async function (ehash) {
|
||||
return responseHandler(await api.delete(`/subscriber/${ehash}`))
|
||||
}
|
||||
|
||||
/*
|
||||
* Upload an image
|
||||
*/
|
||||
Backend.prototype.uploadImage = async function (body) {
|
||||
return responseHandler(await api.post('/images/jwt', body, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Upload an image anonymously
|
||||
*/
|
||||
Backend.prototype.uploadAnonImage = async function (body) {
|
||||
return responseHandler(await api.post('/images', body))
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove an (uploaded) image
|
||||
*/
|
||||
Backend.prototype.removeImage = async function (id) {
|
||||
return responseHandler(await api.delete(`/images/${id}/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Suggest a measurements set for curation
|
||||
*/
|
||||
Backend.prototype.suggestCset = async function (data) {
|
||||
return responseHandler(await api.post(`/curated-sets/suggest/jwt`, data, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Suggest an option pack
|
||||
*/
|
||||
Backend.prototype.suggestOpack = async function (data) {
|
||||
return responseHandler(await api.post(`/option-packs/suggest/jwt`, data, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a curated set from a suggested set
|
||||
*/
|
||||
Backend.prototype.csetFromSuggestedSet = async function (id) {
|
||||
return responseHandler(await api.post(`/curated-sets/from/${id}/jwt`, {}, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Ping backend to see if current token is still valid
|
||||
*/
|
||||
Backend.prototype.ping = async function () {
|
||||
return responseHandler(await api.get(`/whoami/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Search user (admin method)
|
||||
*/
|
||||
Backend.prototype.adminSearchUsers = async function (q) {
|
||||
return responseHandler(await api.post('/admin/search/users/jwt', { q }, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Load user (admin method)
|
||||
*/
|
||||
Backend.prototype.adminLoadUser = async function (id) {
|
||||
return responseHandler(await api.get(`/admin/user/${id}/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Update user (admin method)
|
||||
*/
|
||||
Backend.prototype.adminUpdateUser = async function ({ id, data }) {
|
||||
return responseHandler(await api.patch(`/admin/user/${id}/jwt`, data, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Impersonate user (admin method)
|
||||
*/
|
||||
Backend.prototype.adminImpersonateUser = async function (id) {
|
||||
return responseHandler(await api.get(`/admin/impersonate/${id}/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Load newsletter subscribers (admin method)
|
||||
*/
|
||||
Backend.prototype.adminLoadSubscribers = async function () {
|
||||
return responseHandler(await api.get(`/admin/subscribers/jwt`, this.auth))
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify an admin account while impersonating another user
|
||||
*/
|
||||
Backend.prototype.adminPing = async function (token) {
|
||||
return responseHandler(await api.get(`/whoami/jwt`, auth(token)))
|
||||
}
|
||||
|
||||
/*
|
||||
* backend.img: Generate a social media image
|
||||
*/
|
||||
Backend.prototype.img = async function (data) {
|
||||
return responseHandler(await api.post('/img', data, { responseType: 'arraybuffer' }))
|
||||
}
|
||||
|
||||
export function useBackend(Swizzled) {
|
||||
/*
|
||||
* Load swizzled useAccount hook and use it
|
||||
*/
|
||||
const { token } = Swizzled.hooks.useAccount()
|
||||
|
||||
/*
|
||||
* This backend object is what we'll end up returning
|
||||
*/
|
||||
const backend = useMemo(() => new Backend(auth(token)), [token])
|
||||
|
||||
return backend
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue