1
0
Fork 0

wip: Drop swizzle folder

This commit is contained in:
joostdecock 2025-02-23 12:32:51 +01:00
parent a50e3ca50e
commit 4b6944b72f
16 changed files with 0 additions and 3488 deletions

View file

@ -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
}

View file

@ -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>
)

View file

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

View file

@ -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,
}

View file

@ -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>
)

View file

@ -1 +0,0 @@
export const LargeScreenOnly = ({ children }) => <div className="hidden xl:block">{children}</div>

View file

@ -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 }

View file

@ -1,3 +0,0 @@
import Markdown from 'react-markdown'
export { Markdown }

View file

@ -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>
}

View file

@ -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>
)
}

View file

@ -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" />
)

View file

@ -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)

View file

@ -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
}

View file

@ -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,
}
}

View file

@ -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
}