wip: Saving works now
This commit is contained in:
parent
ce7f7c65e0
commit
77ee973355
19 changed files with 363 additions and 278 deletions
|
@ -83,6 +83,7 @@ packageJson:
|
||||||
"./components/Design": "./components/Design/index.mjs"
|
"./components/Design": "./components/Design/index.mjs"
|
||||||
"./components/Docusaurus": "./components/Docusaurus/index.mjs"
|
"./components/Docusaurus": "./components/Docusaurus/index.mjs"
|
||||||
"./components/Editor": "./components/Editor/index.mjs"
|
"./components/Editor": "./components/Editor/index.mjs"
|
||||||
|
"./components/Heading": "./components/Heading/index.mjs"
|
||||||
"./components/Highlight": "./components/Highlight/index.mjs"
|
"./components/Highlight": "./components/Highlight/index.mjs"
|
||||||
"./components/Icon": "./components/Icon/index.mjs"
|
"./components/Icon": "./components/Icon/index.mjs"
|
||||||
"./components/Input": "./components/Input/index.mjs"
|
"./components/Input": "./components/Input/index.mjs"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { missingMeasurements, flattenFlags } from '../lib/index.mjs'
|
import { missingMeasurements, flattenFlags } from '../lib/index.mjs'
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
|
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
|
||||||
// Components
|
// Components
|
||||||
import { Null } from './Null.mjs'
|
import { Null } from './Null.mjs'
|
||||||
|
@ -207,11 +208,11 @@ export const HeaderMenuDraftViewFlags = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderMenuDraftViewIcons = (props) => {
|
export const HeaderMenuDraftViewIcons = (props) => {
|
||||||
const { update } = props
|
const { update, state } = props
|
||||||
const Button = HeaderMenuButton
|
const Button = HeaderMenuButton
|
||||||
const size = 'tw-w-5 tw-h-5'
|
const size = 'tw-w-5 tw-h-5'
|
||||||
const muted = 'tw-text-current tw-opacity-50'
|
const muted = 'tw-text-current tw-opacity-50'
|
||||||
const ux = props.state.ui.ux
|
const ux = state.ui.ux
|
||||||
const levels = {
|
const levels = {
|
||||||
...props.config.uxLevels.core,
|
...props.config.uxLevels.core,
|
||||||
...props.config.uxLevels.ui,
|
...props.config.uxLevels.ui,
|
||||||
|
@ -225,25 +226,20 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
||||||
updateHandler={update.toggleSa}
|
updateHandler={update.toggleSa}
|
||||||
tooltip="Turns Seam Allowance on or off (see Core Settings)"
|
tooltip="Turns Seam Allowance on or off (see Core Settings)"
|
||||||
>
|
>
|
||||||
<SaIcon
|
<SaIcon className={`${size} ${state.settings.sabool ? 'tw-text-secondary' : muted}`} />
|
||||||
className={`${size} ${props.state.settings.sabool ? 'tw-text-secondary' : muted}`}
|
|
||||||
/>
|
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{ux >= levels.units ? (
|
{ux >= levels.units ? (
|
||||||
<Button
|
<Button
|
||||||
lgOnly
|
lgOnly
|
||||||
updateHandler={() =>
|
updateHandler={() =>
|
||||||
update.settings(
|
update.settings('units', state.settings.units === 'imperial' ? 'metric' : 'imperial')
|
||||||
'units',
|
|
||||||
props.state.settings.units === 'imperial' ? 'metric' : 'imperial'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
tooltip="Switches Units between metric and imperial (see Core Settings)"
|
tooltip="Switches Units between metric and imperial (see Core Settings)"
|
||||||
>
|
>
|
||||||
<UnitsIcon
|
<UnitsIcon
|
||||||
className={`${size} ${
|
className={`${size} ${
|
||||||
props.state.settings.units === 'imperial' ? 'tw-text-secondary' : muted
|
state.settings.units === 'imperial' ? 'tw-text-secondary' : muted
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -251,33 +247,33 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
||||||
{ux >= levels.paperless ? (
|
{ux >= levels.paperless ? (
|
||||||
<Button
|
<Button
|
||||||
lgOnly
|
lgOnly
|
||||||
updateHandler={() => update.settings('paperless', props.state.settings.paperless ? 0 : 1)}
|
updateHandler={() => update.settings('paperless', state.settings.paperless ? 0 : 1)}
|
||||||
tooltip="Turns Paperless on or off (see Core Settings)"
|
tooltip="Turns Paperless on or off (see Core Settings)"
|
||||||
>
|
>
|
||||||
<PaperlessIcon
|
<PaperlessIcon
|
||||||
className={`${size} ${props.state.settings.paperless ? 'tw-text-secondary' : muted}`}
|
className={`${size} ${state.settings.paperless ? 'tw-text-secondary' : muted}`}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{ux >= levels.complete ? (
|
{ux >= levels.complete ? (
|
||||||
<Button
|
<Button
|
||||||
lgOnly
|
lgOnly
|
||||||
updateHandler={() => update.settings('complete', props.state.settings.complete ? 0 : 1)}
|
updateHandler={() => update.settings('complete', state.settings.complete ? 0 : 1)}
|
||||||
tooltip="Turns Details on or off (see Core Settings)"
|
tooltip="Turns Details on or off (see Core Settings)"
|
||||||
>
|
>
|
||||||
<DetailIcon
|
<DetailIcon
|
||||||
className={`${size} ${!props.state.settings.complete ? 'tw-text-secondary' : muted}`}
|
className={`${size} ${!state.settings.complete ? 'tw-text-secondary' : muted}`}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{ux >= levels.expand ? (
|
{ux >= levels.expand ? (
|
||||||
<Button
|
<Button
|
||||||
lgOnly
|
lgOnly
|
||||||
updateHandler={() => update.settings('expand', props.state.settings.expand ? 0 : 1)}
|
updateHandler={() => update.settings('expand', state.settings.expand ? 0 : 1)}
|
||||||
tooltip="Turns Expand on or off (see Core Settings)"
|
tooltip="Turns Expand on or off (see Core Settings)"
|
||||||
>
|
>
|
||||||
<ExpandIcon
|
<ExpandIcon
|
||||||
className={`${size} ${props.state.settings.expand ? 'tw-text-secondary' : muted}`}
|
className={`${size} ${state.settings.expand ? 'tw-text-secondary' : muted}`}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -285,24 +281,22 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
||||||
{ux >= levels.rotate ? (
|
{ux >= levels.rotate ? (
|
||||||
<Button
|
<Button
|
||||||
lgOnly
|
lgOnly
|
||||||
updateHandler={() => update.ui('rotate', props.state.ui.rotate ? 0 : 1)}
|
updateHandler={() => update.ui('rotate', state.ui.rotate ? 0 : 1)}
|
||||||
tooltip="Turns Rotate Pattern on or off (see UI Preferences)"
|
tooltip="Turns Rotate Pattern on or off (see UI Preferences)"
|
||||||
>
|
>
|
||||||
<RotateIcon
|
<RotateIcon className={`${size} ${state.ui.rotate ? 'tw-text-secondary' : muted}`} />
|
||||||
className={`${size} ${props.state.ui.rotate ? 'tw-text-secondary' : muted}`}
|
|
||||||
/>
|
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{ux >= levels.renderer ? (
|
{ux >= levels.renderer ? (
|
||||||
<Button
|
<Button
|
||||||
lgOnly
|
lgOnly
|
||||||
updateHandler={() =>
|
updateHandler={() =>
|
||||||
update.ui('renderer', props.state.ui.renderer === 'react' ? 'svg' : 'react')
|
update.ui('renderer', state.ui.renderer === 'react' ? 'svg' : 'react')
|
||||||
}
|
}
|
||||||
tooltip="Switches the Render Engine between React and SVG (see UI Preferences)"
|
tooltip="Switches the Render Engine between React and SVG (see UI Preferences)"
|
||||||
>
|
>
|
||||||
<RocketIcon
|
<RocketIcon
|
||||||
className={`${size} ${props.state.ui.renderer === 'svg' ? 'tw-text-secondary' : muted}`}
|
className={`${size} ${state.ui.renderer === 'svg' ? 'tw-text-secondary' : muted}`}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -314,7 +308,7 @@ export const HeaderMenuUndoIcons = (props) => {
|
||||||
const { update, state, Design } = props
|
const { update, state, Design } = props
|
||||||
const Button = HeaderMenuButton
|
const Button = HeaderMenuButton
|
||||||
const size = 'tw-w-5 tw-h-5'
|
const size = 'tw-w-5 tw-h-5'
|
||||||
const undos = props.state._?.undos && props.state._.undos.length > 0 ? props.state._.undos : false
|
const undos = state._?.undos && state._.undos.length > 0 ? state._.undos : false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw-flex tw-flex-row tw-flex-wrap tw-items-center tw-justify-center tw-px-0.5 lg:tw-px-1">
|
<div className="tw-flex tw-flex-row tw-flex-wrap tw-items-center tw-justify-center tw-px-0.5 lg:tw-px-1">
|
||||||
|
@ -386,18 +380,35 @@ export const HeaderMenuUndoIcons = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderMenuSaveIcons = (props) => {
|
export const HeaderMenuSaveIcons = (props) => {
|
||||||
const { update } = props
|
const { update, state } = props
|
||||||
|
const backend = useBackend()
|
||||||
const Button = HeaderMenuButton
|
const Button = HeaderMenuButton
|
||||||
const size = 'tw-w-5 tw-h-5'
|
const size = 'tw-w-5 tw-h-5'
|
||||||
const saveable = props.state._?.undos && props.state._.undos.length > 0
|
const saveable = state._?.undos && state._.undos.length > 0
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save or Save As, depending on state.pattern
|
||||||
|
*/
|
||||||
|
const savePattern = async () => {
|
||||||
|
const pid = state.pid || false
|
||||||
|
if (pid) {
|
||||||
|
const loadingId = 'savePattern'
|
||||||
|
update.startLoading(loadingId)
|
||||||
|
const patternData = {
|
||||||
|
settings: state.settings,
|
||||||
|
}
|
||||||
|
const result = await backend.updatePattern(pid, patternData)
|
||||||
|
if (result[0] === 200) {
|
||||||
|
update.stopLoading(loadingId)
|
||||||
|
update.view('draft')
|
||||||
|
update.notifySuccess('Pattern saved')
|
||||||
|
} else update.notifyFailure('oops', loadingId)
|
||||||
|
} else update.view('save')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw-flex tw-flex-row tw-flex-wrap tw-items-center tw-justify-center tw-px-2">
|
<div className="tw-flex tw-flex-row tw-flex-wrap tw-items-center tw-justify-center tw-px-2">
|
||||||
<Button
|
<Button updateHandler={savePattern} tooltip="Save pattern" disabled={saveable ? false : true}>
|
||||||
updateHandler={update.clearPattern}
|
|
||||||
tooltip="Save pattern"
|
|
||||||
disabled={saveable ? false : true}
|
|
||||||
>
|
|
||||||
<SaveIcon className={`${size} ${saveable ? 'tw-text-success' : ''}`} />
|
<SaveIcon className={`${size} ${saveable ? 'tw-text-success' : ''}`} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button updateHandler={() => update.view('save')} tooltip="Save pattern as...">
|
<Button updateHandler={() => update.view('save')} tooltip="Save pattern as...">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { Spinner } from '@freesewing/react/components/Spinner'
|
import { Spinner } from '@freesewing/react/components/Spinner'
|
||||||
import { TipIcon } from '@freesewing/react/components/Icon'
|
import { TipIcon, OkIcon } from '@freesewing/react/components/Icon'
|
||||||
import { Null } from './Null.mjs'
|
import { Null } from './Null.mjs'
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
@ -14,6 +14,7 @@ const config = {
|
||||||
const icons = {
|
const icons = {
|
||||||
tip: TipIcon,
|
tip: TipIcon,
|
||||||
spinner: Spinner,
|
spinner: Spinner,
|
||||||
|
success: () => <OkIcon stroke={4} />,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LoadingStatus = ({ state, update }) => {
|
export const LoadingStatus = ({ state, update }) => {
|
||||||
|
@ -34,6 +35,11 @@ export const LoadingStatus = ({ state, update }) => {
|
||||||
|
|
||||||
if (!state._.loading || Object.keys(state._.loading).length < 1) return null
|
if (!state._.loading || Object.keys(state._.loading).length < 1) return null
|
||||||
|
|
||||||
|
const colorClasses = {
|
||||||
|
info: 'tw-bg-info tw-text-info-content',
|
||||||
|
primary: 'tw-bg-primary tw-text-primary-content',
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw-fixed tw-bottom-4 md:tw-buttom-28 tw-left-0 tw-w-full tw-z-30 md:tw-px-4 md:tw-mx-auto mb-4">
|
<div className="tw-fixed tw-bottom-4 md:tw-buttom-28 tw-left-0 tw-w-full tw-z-30 md:tw-px-4 md:tw-mx-auto mb-4">
|
||||||
<div className="tw-flex tw-flex-col tw-gap-2">
|
<div className="tw-flex tw-flex-col tw-gap-2">
|
||||||
|
@ -48,16 +54,18 @@ export const LoadingStatus = ({ state, update }) => {
|
||||||
key={id}
|
key={id}
|
||||||
className={`tw-w-full md:tw-max-w-2xl tw-m-auto tw-bg-${
|
className={`tw-w-full md:tw-max-w-2xl tw-m-auto tw-bg-${
|
||||||
conf.color
|
conf.color
|
||||||
} tw-flex tw-flex-row tw-items-center tw-gap-4 tw-p-4 tw-px-4 ${
|
} tw-text-${conf.color}-content tw-flex tw-flex-row tw-items-center tw-gap-4 tw-p-4 tw-px-4 ${
|
||||||
conf.fading ? 'tw-opacity-0' : 'tw-opacity-100'
|
conf.fading ? 'tw-opacity-0' : 'tw-opacity-100'
|
||||||
}
|
}
|
||||||
tw-transition-opacity tw-delay-[${config.timeout * 1000 - 400}ms] tw-duration-300
|
tw-transition-opacity tw-delay-[${config.timeout * 1000 - 400}ms] tw-duration-300
|
||||||
md:tw-rounded-lg tw-shadow tw-text-secondary-content tw-text-lg lg:tw-text-xl tw-font-medium md:tw-bg-opacity-90`}
|
md:tw-rounded-lg tw-shadow tw-text-secondary-content tw-text-lg lg:tw-text-xl tw-font-medium md:tw-bg-opacity-90
|
||||||
|
${conf.color === 'info' ? 'tw-text-neutral' : ''}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
<span className="tw-shrink-0">
|
<span className={`tw-shrink-0 tw-text-${conf.color}-content`}>
|
||||||
<Icon />
|
<Icon />
|
||||||
</span>
|
</span>
|
||||||
{conf.msg}
|
<div className={conf.color === 'info' ? 'tw-text-neutral' : ''}>{conf.msg}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { t, designMeasurements } from '../../lib/index.mjs'
|
import { t, designMeasurements } from '../../lib/index.mjs'
|
||||||
import { capitalize } from '@freesewing/utils'
|
import { capitalize, horFlexClasses as horFlexClasses } from '@freesewing/utils'
|
||||||
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { Fragment, useEffect } from 'react'
|
import React, { Fragment, useEffect } from 'react'
|
||||||
|
@ -21,7 +21,6 @@ const iconClasses = {
|
||||||
className: 'tw-w-8 tw-h-8 md:tw-w-10 md:tw-h-10 lg:tw-w-12 lg:tw-h-12 tw-shrink-0',
|
className: 'tw-w-8 tw-h-8 md:tw-w-10 md:tw-h-10 lg:tw-w-12 lg:tw-h-12 tw-shrink-0',
|
||||||
stroke: 1.5,
|
stroke: 1.5,
|
||||||
}
|
}
|
||||||
const horFlexClasses = 'tw-flex tw-flex-row tw-items-center tw-justify-between tw-gap-4 tw-w-full'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The measurements view is loaded to update/set measurements
|
* The measurements view is loaded to update/set measurements
|
||||||
|
@ -72,7 +71,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
items.push(
|
items.push(
|
||||||
[
|
[
|
||||||
<Fragment key={1}>
|
<Fragment key={1}>
|
||||||
<div className={horFlexClasses}>
|
<div className={`${horFlexClasses} tw-w-full`}>
|
||||||
<h4 id="ownsets">Choose one of your own measurements sets</h4>
|
<h4 id="ownsets">Choose one of your own measurements sets</h4>
|
||||||
<MeasurementsSetIcon {...iconClasses} />
|
<MeasurementsSetIcon {...iconClasses} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,7 +91,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
<Fragment key={1}>
|
<Fragment key={1}>
|
||||||
<div className={horFlexClasses}>
|
<div className={`${horFlexClasses} tw-w-full`}>
|
||||||
<h4 id="bookmarkedsets">Choose one of the measurements sets you have bookmarked</h4>
|
<h4 id="bookmarkedsets">Choose one of the measurements sets you have bookmarked</h4>
|
||||||
<BookmarkIcon {...iconClasses} />
|
<BookmarkIcon {...iconClasses} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,7 +110,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
<Fragment key={1}>
|
<Fragment key={1}>
|
||||||
<div className={horFlexClasses}>
|
<div className={`${horFlexClasses} tw-w-full`}>
|
||||||
<h4 id="curatedsets">Choose one of FreeSewing's curated measurements sets</h4>
|
<h4 id="curatedsets">Choose one of FreeSewing's curated measurements sets</h4>
|
||||||
<CuratedMeasurementsSetIcon {...iconClasses} />
|
<CuratedMeasurementsSetIcon {...iconClasses} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -127,7 +126,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
// Manual editing is always an option
|
// Manual editing is always an option
|
||||||
items.push([
|
items.push([
|
||||||
<Fragment key={1}>
|
<Fragment key={1}>
|
||||||
<div className={horFlexClasses}>
|
<div className={`${horFlexClasses} tw-w-full`}>
|
||||||
<h4 id="editmeasurements">Edit Measurements</h4>
|
<h4 id="editmeasurements">Edit Measurements</h4>
|
||||||
<EditIcon {...iconClasses} />
|
<EditIcon {...iconClasses} />
|
||||||
</div>
|
</div>
|
||||||
|
|
181
packages/react/components/Editor/components/views/SaveView.mjs
Normal file
181
packages/react/components/Editor/components/views/SaveView.mjs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
// Dependencies
|
||||||
|
import yaml from 'js-yaml'
|
||||||
|
import { capitalize, shortDate, notEmpty } from '@freesewing/utils'
|
||||||
|
import { linkClasses, classesHorFlexNoSm } from '@freesewing/utils'
|
||||||
|
// Hooks
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
// Components
|
||||||
|
import { RoleBlock } from '@freesewing/react/components/Role'
|
||||||
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
|
import { StringInput } from '@freesewing/react/components/Input'
|
||||||
|
import { SaveAsIcon } from '@freesewing/react/components/Icon'
|
||||||
|
import { H1 } from '@freesewing/react/components/Heading'
|
||||||
|
import { Link, SuccessLink } from '@freesewing/react/components/Link'
|
||||||
|
import { HeaderMenu } from '../HeaderMenu.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the save view, it allows you to save your pattern to a FreeSewing account
|
||||||
|
*
|
||||||
|
* @param {Object} props - All the props
|
||||||
|
* @param {Function} props.config - The editor configuration
|
||||||
|
* @param {Object} props.state - The editor state object
|
||||||
|
* @param {Object} props.update - Helper object for updating the editor state
|
||||||
|
*/
|
||||||
|
export const SaveView = ({ config, state, update }) => {
|
||||||
|
// Hooks
|
||||||
|
const backend = useBackend()
|
||||||
|
|
||||||
|
// State
|
||||||
|
const [name, setName] = useState(`${capitalize(state.design)} / ${shortDate()}`)
|
||||||
|
const [withNotes, setWithNotes] = useState(false)
|
||||||
|
const [notes, setNotes] = useState('')
|
||||||
|
const [savedId, setSavedId] = useState()
|
||||||
|
const [saveAs] = useState(false) // FIXME
|
||||||
|
|
||||||
|
const addSettingsToNotes = () => {
|
||||||
|
setNotes(notes + '\n\n#### Settings\n\n```yaml\n' + yaml.dump(state.settings) + '\n````')
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveAsNewPattern = async () => {
|
||||||
|
const loadingId = 'savePatternAs'
|
||||||
|
update.startLoading(loadingId)
|
||||||
|
const patternData = {
|
||||||
|
design: state.design,
|
||||||
|
name,
|
||||||
|
public: false,
|
||||||
|
settings: state.settings,
|
||||||
|
data: {},
|
||||||
|
}
|
||||||
|
if (withNotes) patternData.notes = notes
|
||||||
|
const result = await backend.createPattern(patternData)
|
||||||
|
if (result[0] === 201 && result[1].pattern.id) {
|
||||||
|
const id = result[1].pattern.id
|
||||||
|
update.stopLoading(loadingId)
|
||||||
|
update.view('draft')
|
||||||
|
update.pid(id)
|
||||||
|
update.notifySuccess(
|
||||||
|
<span>
|
||||||
|
{' '}
|
||||||
|
Pattern saved as:{' '}
|
||||||
|
<SuccessLink href={`/account/data/patterns/pattern?id=${id}`}> #{id} </SuccessLink>
|
||||||
|
</span>,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
} else update.notifyFailure('oops', loadingId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const savePattern = async () => {
|
||||||
|
setLoadingStatus([true, 'Saving pattern...'])
|
||||||
|
const patternData = {
|
||||||
|
design: state.design,
|
||||||
|
settings: state.settings,
|
||||||
|
name,
|
||||||
|
public: false,
|
||||||
|
data: {},
|
||||||
|
}
|
||||||
|
const result = await backend.updatePattern(saveAs.pattern, patternData)
|
||||||
|
if (result.success) {
|
||||||
|
//setLoadingStatus([
|
||||||
|
// true,
|
||||||
|
// <>
|
||||||
|
// {t('status:patternSaved')} <small>[#{saveAs.pattern}]</small>
|
||||||
|
// </>,
|
||||||
|
// true,
|
||||||
|
// true,
|
||||||
|
//])
|
||||||
|
setSavedId(saveAs.pattern)
|
||||||
|
update.notify({ color: 'success', msg: 'boom' }, saveAs.pattern)
|
||||||
|
} //else setLoadingStatus([true, 'backendError', true, false])
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RoleBlock user>
|
||||||
|
<HeaderMenu state={state} {...{ config, update }} />
|
||||||
|
<div className="tw-m-auto tw-mt-8 tw-max-w-2xl tw-px-4">
|
||||||
|
{saveAs && saveAs.pattern ? (
|
||||||
|
<>
|
||||||
|
<h2>Save Pattern</h2>
|
||||||
|
{savedId && (
|
||||||
|
<Popout link>
|
||||||
|
<h5>Pattern Saved</h5>
|
||||||
|
See: <Link href={`/account/patterns/${savedId}`}>/account/patterns/{savedId}</Link>
|
||||||
|
</Popout>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className={`${classeshorFlexNoSm} tw-btn tw-btn-primary tw-btn-lg tw-w-full tw-mt-2 tw-my-8`}
|
||||||
|
onClick={savePattern}
|
||||||
|
>
|
||||||
|
<SaveIcon className="tw-h-8 tw-w-8" />
|
||||||
|
Save Patter #{saveAs.pattern}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<H1>Save As New Pattern</H1>
|
||||||
|
<div className="tw-mb-4">
|
||||||
|
<StringInput
|
||||||
|
label="Pattern title"
|
||||||
|
current={name}
|
||||||
|
update={setName}
|
||||||
|
valid={notEmpty}
|
||||||
|
labelBR={
|
||||||
|
<>
|
||||||
|
{withNotes ? (
|
||||||
|
<div className="tw-flex tw-flex-row tw-items-center tw-gap-4">
|
||||||
|
<button
|
||||||
|
className={`tw-font-bold ${linkClasses}`}
|
||||||
|
onClick={() => setWithNotes(false)}
|
||||||
|
>
|
||||||
|
Hide notes
|
||||||
|
</button>
|
||||||
|
<button className={`tw-font-bold ${linkClasses}`} onClick={addSettingsToNotes}>
|
||||||
|
Add settings to notes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className={`tw-font-bold ${linkClasses}`}
|
||||||
|
onClick={() => setWithNotes(true)}
|
||||||
|
>
|
||||||
|
Add notes
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{withNotes ? (
|
||||||
|
<Swizzled.components.MarkdownInput
|
||||||
|
label="Pattern notes"
|
||||||
|
current={notes}
|
||||||
|
update={setNotes}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div className="tw-flex tw-flex-row tw-gap-2 tw-mt-8">
|
||||||
|
<button
|
||||||
|
className={`tw-daisy-btn tw-daisy-btn-primary lg:tw-daisy-btn-lg tw-daisy-btn-outline`}
|
||||||
|
onClick={update.viewBack}
|
||||||
|
title="Cancel"
|
||||||
|
>
|
||||||
|
<span>Cancel</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`tw-flex tw-flex-row tw-items-center tw-justify-between tw-daisy-btn tw-daisy-btn-primary lg:tw-daisy-btn-lg tw-grow`}
|
||||||
|
onClick={saveAsNewPattern}
|
||||||
|
title="Save as new pattern"
|
||||||
|
>
|
||||||
|
<SaveAsIcon className="tw-w-8 tw-h-8" />
|
||||||
|
<span>Save as new pattern</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="tw-text-sm tw-text-right">
|
||||||
|
To access your saved patterns, go to:
|
||||||
|
<b>
|
||||||
|
{' '}
|
||||||
|
<Link href="/account/patterns">/account/patterns</Link>
|
||||||
|
</b>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RoleBlock>
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import { ViewPicker } from './ViewPicker.mjs'
|
||||||
import { DesignsView } from './DesignsView.mjs'
|
import { DesignsView } from './DesignsView.mjs'
|
||||||
import { MeasurementsView } from './MeasurementsView.mjs'
|
import { MeasurementsView } from './MeasurementsView.mjs'
|
||||||
import { DraftView } from './DraftView.mjs'
|
import { DraftView } from './DraftView.mjs'
|
||||||
|
import { SaveView } from './SaveView.mjs'
|
||||||
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
||||||
import {
|
import {
|
||||||
OptionsIcon,
|
OptionsIcon,
|
||||||
|
@ -51,6 +52,7 @@ export const View = (props) => {
|
||||||
if (view === 'designs') return <DesignsView {...props} />
|
if (view === 'designs') return <DesignsView {...props} />
|
||||||
if (view === 'measurements') return <MeasurementsView {...props} />
|
if (view === 'measurements') return <MeasurementsView {...props} />
|
||||||
if (view === 'draft') return <DraftView {...props} />
|
if (view === 'draft') return <DraftView {...props} />
|
||||||
|
if (view === 'save') return <SaveView {...props} />
|
||||||
/*
|
/*
|
||||||
viewComponents: {
|
viewComponents: {
|
||||||
draft: 'DraftView',
|
draft: 'DraftView',
|
||||||
|
|
|
@ -122,11 +122,6 @@ export const defaultConfig = {
|
||||||
ux: 4,
|
ux: 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
classes: {
|
|
||||||
horFlex: 'flex flex-row items-center justify-between gap-4 w-full',
|
|
||||||
horFlexNoSm: 'md:flex md:flex-row md:items-center md:justify-between md:gap-4 md-w-full',
|
|
||||||
link: 'underline decoration-2 hover:decoration-4 text-secondary hover:text-secondary-focus',
|
|
||||||
},
|
|
||||||
roles: {
|
roles: {
|
||||||
levels: {
|
levels: {
|
||||||
readNone: 0,
|
readNone: 0,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { defaultConfig } from '../config/index.mjs'
|
import { defaultConfig } from '../config/index.mjs'
|
||||||
import { round, formatMm } from '@freesewing/utils'
|
import { round, formatMm, randomLoadingMessage, horFlexClasses } from '@freesewing/utils'
|
||||||
import { formatDesignOptionValue, menuCoreSettingsStructure } from './index.mjs'
|
import { formatDesignOptionValue, menuCoreSettingsStructure } from './index.mjs'
|
||||||
import { menuUiPreferencesStructure } from './ui-preferences.mjs'
|
import { menuUiPreferencesStructure } from './ui-preferences.mjs'
|
||||||
// Components
|
// Components
|
||||||
|
@ -13,6 +13,7 @@ import {
|
||||||
UiIcon,
|
UiIcon,
|
||||||
} from '@freesewing/react/components/Icon'
|
} from '@freesewing/react/components/Icon'
|
||||||
import { HtmlSpan } from '../components/HtmlSpan.mjs'
|
import { HtmlSpan } from '../components/HtmlSpan.mjs'
|
||||||
|
import { Spinner } from '@freesewing/react/components/Spinner'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This method drafts the pattern
|
* This method drafts the pattern
|
||||||
|
@ -531,6 +532,8 @@ export function stateUpdateFactory(setState, setEphemeralState, config) {
|
||||||
return eph
|
return eph
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
// Pattern ID (pid)
|
||||||
|
pid: (val) => setState((cur) => ({ ...cur, pid: val })),
|
||||||
ux: (val) => setState((cur) => objUpdate({ ...cur }, 'ux', val)),
|
ux: (val) => setState((cur) => objUpdate({ ...cur }, 'ux', val)),
|
||||||
clearPattern: () =>
|
clearPattern: () =>
|
||||||
setState((cur) => {
|
setState((cur) => {
|
||||||
|
@ -555,7 +558,8 @@ export function stateUpdateFactory(setState, setEphemeralState, config) {
|
||||||
if (typeof newState.loading !== 'object') newState.loading = {}
|
if (typeof newState.loading !== 'object') newState.loading = {}
|
||||||
if (typeof conf.color === 'undefined') conf.color = 'info'
|
if (typeof conf.color === 'undefined') conf.color = 'info'
|
||||||
newState.loading[id] = {
|
newState.loading[id] = {
|
||||||
msg: t('pe:genericLoadingMsg'),
|
msg: randomLoadingMessage(),
|
||||||
|
icon: 'spinner',
|
||||||
...conf,
|
...conf,
|
||||||
}
|
}
|
||||||
return newState
|
return newState
|
||||||
|
|
|
@ -1,214 +0,0 @@
|
||||||
import { useState } from 'react'
|
|
||||||
import yaml from 'js-yaml'
|
|
||||||
|
|
||||||
export const SaveView = ({ Swizzled, state, update }) => {
|
|
||||||
// Hooks
|
|
||||||
const backend = Swizzled.hooks.useBackend()
|
|
||||||
const { t } = Swizzled.methods
|
|
||||||
|
|
||||||
// State
|
|
||||||
const [name, setName] = useState(
|
|
||||||
`${Swizzled.methods.capitalize(state.design)} / ${Swizzled.methods.shortDate(status.locale)}`
|
|
||||||
)
|
|
||||||
const [withNotes, setWithNotes] = useState(false)
|
|
||||||
const [notes, setNotes] = useState('')
|
|
||||||
const [savedId, setSavedId] = useState()
|
|
||||||
const [bookmarkedId] = useState() // FIXME
|
|
||||||
const [saveAs] = useState(false) // FIXME
|
|
||||||
|
|
||||||
const addSettingsToNotes = () => {
|
|
||||||
setNotes(
|
|
||||||
notes +
|
|
||||||
'\n\n#### ' +
|
|
||||||
Swizzled.methods.t('pe:settings') +
|
|
||||||
'\n\n' +
|
|
||||||
'```yaml\n' +
|
|
||||||
yaml.dump(state.settings) +
|
|
||||||
'````'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveAsNewPattern = async () => {
|
|
||||||
const loadingId = 'savePatternAs'
|
|
||||||
update.startLoading(loadingId)
|
|
||||||
const patternData = {
|
|
||||||
design: state.design,
|
|
||||||
name,
|
|
||||||
public: false,
|
|
||||||
settings: state.settings,
|
|
||||||
data: {},
|
|
||||||
}
|
|
||||||
if (withNotes) patternData.notes = notes
|
|
||||||
const result = await backend.createPattern(patternData)
|
|
||||||
if (result.success) {
|
|
||||||
const id = result.data.pattern.id
|
|
||||||
update.stopLoading(loadingId)
|
|
||||||
update.view('draft')
|
|
||||||
update.notifySuccess(
|
|
||||||
<span>
|
|
||||||
{t('pe:patternSavedAs')}:{' '}
|
|
||||||
<Swizzled.components.Link
|
|
||||||
href={`/account/pattern?id=${id}`}
|
|
||||||
className={`${Swizzled.config.classes.link} text-secondary-content`}
|
|
||||||
>
|
|
||||||
/account/pattern?id={id}
|
|
||||||
</Swizzled.components.Link>
|
|
||||||
</span>,
|
|
||||||
id
|
|
||||||
)
|
|
||||||
} else update.notifyFailure('oops', loadingId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const savePattern = async () => {
|
|
||||||
//setLoadingStatus([true, 'savingPattern'])
|
|
||||||
const patternData = {
|
|
||||||
design: state.design,
|
|
||||||
settings: state.settings,
|
|
||||||
name,
|
|
||||||
public: false,
|
|
||||||
data: {},
|
|
||||||
}
|
|
||||||
const result = await backend.updatePattern(saveAs.pattern, patternData)
|
|
||||||
if (result.success) {
|
|
||||||
//setLoadingStatus([
|
|
||||||
// true,
|
|
||||||
// <>
|
|
||||||
// {t('status:patternSaved')} <small>[#{saveAs.pattern}]</small>
|
|
||||||
// </>,
|
|
||||||
// true,
|
|
||||||
// true,
|
|
||||||
//])
|
|
||||||
setSavedId(saveAs.pattern)
|
|
||||||
update.notify({ color: 'success', msg: 'boom' }, saveAs.pattern)
|
|
||||||
} //else setLoadingStatus([true, 'backendError', true, false])
|
|
||||||
}
|
|
||||||
|
|
||||||
//const bookmarkPattern = async () => {
|
|
||||||
// setLoadingStatus([true, 'creatingBookmark'])
|
|
||||||
// const result = await backend.createBookmark({
|
|
||||||
// type: 'pattern',
|
|
||||||
// title: name,
|
|
||||||
// url: window.location.pathname + window.location.search + window.location.hash,
|
|
||||||
// })
|
|
||||||
// if (result.success) {
|
|
||||||
// const id = result.data.bookmark.id
|
|
||||||
// setLoadingStatus([
|
|
||||||
// true,
|
|
||||||
// <>
|
|
||||||
// {t('status:bookmarkCreated')} <small>[#{id}]</small>
|
|
||||||
// </>,
|
|
||||||
// true,
|
|
||||||
// true,
|
|
||||||
// ])
|
|
||||||
// setBookmarkedId(id)
|
|
||||||
// } else setLoadingStatus([true, 'backendError', true, false])
|
|
||||||
//}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Swizzled.components.AuthWrapper>
|
|
||||||
<Swizzled.components.HeaderMenu {...{ state, update }} />
|
|
||||||
<div className="m-auto mt-8 max-w-2xl px-4">
|
|
||||||
{saveAs && saveAs.pattern ? (
|
|
||||||
<>
|
|
||||||
<h2>{t('pe:savePattern')}</h2>
|
|
||||||
{savedId && (
|
|
||||||
<Swizzled.components.Popout link>
|
|
||||||
<h5>{t('workbend:patternSaved')}</h5>
|
|
||||||
{t('pe:see')}:{' '}
|
|
||||||
<Swizzled.components.PageLink
|
|
||||||
href={`/account/patterns/${savedId}`}
|
|
||||||
txt={`/account/patterns/${savedId}`}
|
|
||||||
/>
|
|
||||||
</Swizzled.components.Popout>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
className={`${Swizzled.config.classeshorFlexNoSm} btn btn-primary btn-lg w-full mt-2 my-8`}
|
|
||||||
onClick={savePattern}
|
|
||||||
>
|
|
||||||
<Swizzled.components.SaveIcon className="h-8 w-8" />
|
|
||||||
{t('pe:savePattern')} #{saveAs.pattern}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
<h2>{t('pe:saveAsNewPattern')}</h2>
|
|
||||||
{bookmarkedId && (
|
|
||||||
<Swizzled.components.Popout link>
|
|
||||||
<h5>{t('pe:patternBookmarkCreated')}</h5>
|
|
||||||
{t('pe:see')}:{' '}
|
|
||||||
<Swizzled.components.PageLink
|
|
||||||
href={`/account/bookmarks/${bookmarkedId}`}
|
|
||||||
txt={`/account/bookmarks/${bookmarkedId}`}
|
|
||||||
/>
|
|
||||||
</Swizzled.components.Popout>
|
|
||||||
)}
|
|
||||||
<div className="mb-4">
|
|
||||||
<Swizzled.components.StringInput
|
|
||||||
label={t('pe:patternTitle')}
|
|
||||||
current={name}
|
|
||||||
update={setName}
|
|
||||||
valid={Swizzled.methods.notEmpty}
|
|
||||||
labelBR={
|
|
||||||
<>
|
|
||||||
{withNotes ? (
|
|
||||||
<div className="flex flex-row items-center gap-4">
|
|
||||||
<button
|
|
||||||
className={`font-bold ${Swizzled.config.classes.link}`}
|
|
||||||
onClick={() => setWithNotes(false)}
|
|
||||||
>
|
|
||||||
{t('pe:hideNotes')}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`font-bold ${Swizzled.config.classes.link}`}
|
|
||||||
onClick={addSettingsToNotes}
|
|
||||||
>
|
|
||||||
{t('pe:addSettingsToNotes')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
className={`font-bold ${Swizzled.config.classes.link}`}
|
|
||||||
onClick={() => setWithNotes(true)}
|
|
||||||
>
|
|
||||||
{t('pe:addNotes')}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{withNotes ? (
|
|
||||||
<Swizzled.components.MarkdownInput
|
|
||||||
label={t('pe:patternNotes')}
|
|
||||||
current={notes}
|
|
||||||
update={setNotes}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<div className="flex flex-row gap-2 mt-8">
|
|
||||||
<button
|
|
||||||
className={`btn btn-primary btn-lg btn-outline`}
|
|
||||||
onClick={update.viewBack}
|
|
||||||
title={t('pe:cancel')}
|
|
||||||
>
|
|
||||||
<span>{t('pe:cancel')}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`flex flex-row items-center justify-between btn btn-primary btn-lg grow`}
|
|
||||||
onClick={saveAsNewPattern}
|
|
||||||
title={t('pe:saveAsNewPattern')}
|
|
||||||
>
|
|
||||||
<Swizzled.components.SaveAsIcon className="w-8 h-8" />
|
|
||||||
<span>{t('pe:saveAsNewPattern')}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-right">
|
|
||||||
To access your saved patterns, go to:{' '}
|
|
||||||
<b>
|
|
||||||
<Swizzled.components.PageLink href="//account/patterns">
|
|
||||||
/account/patterns
|
|
||||||
</Swizzled.components.PageLink>
|
|
||||||
</b>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Swizzled.components.AuthWrapper>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
/*
|
|
||||||
* To spread icon + text horizontal
|
|
||||||
*/
|
|
||||||
export const horFlexClasses = 'flex flex-row items-center justify-between gap-4 w-full'
|
|
37
packages/react/components/Heading/index.mjs
Normal file
37
packages/react/components/Heading/index.mjs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export const H1 = ({ children }) => (
|
||||||
|
<h1 className="tw-text-5xl tw-pt-5 tw-pb-4 tw-font-thin tw-tracking-tighter lg:tw-text-6xl">
|
||||||
|
{children}
|
||||||
|
</h1>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const H2 = ({ children }) => (
|
||||||
|
<h2 className="tw-text-3xl tw-pt-4 tw-pb-3 tw-font-black tw-tracking-tighter tw-m-0 lg:tw-text-4xl">
|
||||||
|
{children}
|
||||||
|
</h2>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const H3 = ({ children }) => (
|
||||||
|
<h3 className="tw-text-2xl tw-pt-3 tw-pb-2 tw-font-extrabold tw-m-0 tw-tracking-tighter lg:tw-text-3xl">
|
||||||
|
{children}
|
||||||
|
</h3>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const H4 = ({ children }) => (
|
||||||
|
<h4 className="tw-text-xl tw-pt-2 tw-pb-1 tw-font-bold tw-m-0 tw-tracking-tighter lg:tw-text-2xl">
|
||||||
|
{children}
|
||||||
|
</h4>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const H5 = ({ children }) => (
|
||||||
|
<h5 className="tw-text-lg tw-py-1 tw-font-semibold tw-m-0 tw-tracking-tight lg:tw-text-xl">
|
||||||
|
{children}
|
||||||
|
</h5>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const H6 = ({ children }) => (
|
||||||
|
<h6 className="tw-text-base tw-py-1 tw-font-medium tw-italic tw-m-0 tw-tracking-tight lg:tw-text-lg">
|
||||||
|
{children}
|
||||||
|
</h6>
|
||||||
|
)
|
|
@ -138,7 +138,11 @@ export const NumberInput = ({
|
||||||
value={current}
|
value={current}
|
||||||
onChange={(evt) => update(evt.target.value)}
|
onChange={(evt) => update(evt.target.value)}
|
||||||
className={`tw-daisy-input tw-w-full tw-daisy-input-bordered ${
|
className={`tw-daisy-input tw-w-full tw-daisy-input-bordered ${
|
||||||
current === original ? 'input-secondary' : valid(current) ? 'input-success' : 'input-error'
|
current === original
|
||||||
|
? 'tw-daisy-input-secondary'
|
||||||
|
: valid(current)
|
||||||
|
? 'tw-daisy-input-success'
|
||||||
|
: 'tw-daisy-input-error'
|
||||||
}`}
|
}`}
|
||||||
{...{ max, min, step }}
|
{...{ max, min, step }}
|
||||||
/>
|
/>
|
||||||
|
@ -166,12 +170,12 @@ export const StringInput = ({
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={current}
|
value={current}
|
||||||
onChange={(evt) => update(evt.target.value)}
|
onChange={(evt) => update(evt.target.value)}
|
||||||
className={`tw-daisy-input tw-w-full tw-daisy-input-bordered ${
|
className={`tw-daisy-input tw-w-full tw-daisy-input-bordered tw-text-current ${
|
||||||
current === original
|
current === original
|
||||||
? 'daisy-input-secondary'
|
? 'tw-daisy-input-secondary'
|
||||||
: valid(current)
|
: valid(current)
|
||||||
? 'daisy-input-success'
|
? 'tw-daisy-input-success'
|
||||||
: 'daisy-input-error'
|
: 'tw-daisy-input-error'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
|
@ -33,6 +33,28 @@ export const Link = ({ href, title = false, children, className = linkClasses, s
|
||||||
|
|
||||||
const BaseLink = Link
|
const BaseLink = Link
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A regular link, but on a success background
|
||||||
|
*
|
||||||
|
* @param {object} props - All React props
|
||||||
|
* @param {array} props.children - The content to go in the layout
|
||||||
|
* @param {array} props.href - The target to link to
|
||||||
|
* @param {array} props.title - An optional link title
|
||||||
|
* @param {string} props.className - Any non-default CSS classes to apply
|
||||||
|
* @param {string} props.style - Any non-default styles to apply
|
||||||
|
*/
|
||||||
|
export const SuccessLink = ({
|
||||||
|
href,
|
||||||
|
title = false,
|
||||||
|
children,
|
||||||
|
className = `${linkClasses} tw-text-success-content hover:tw-text-success-content`,
|
||||||
|
style = {},
|
||||||
|
}) => (
|
||||||
|
<a href={href} className={className} title={title ? title : ''} style={style}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
|
||||||
export const CardLink = ({ href, title, Icon, children, Link }) => {
|
export const CardLink = ({ href, title, Icon, children, Link }) => {
|
||||||
if (!Link) Link = BaseLink
|
if (!Link) Link = BaseLink
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"./components/Design": "./components/Design/index.mjs",
|
"./components/Design": "./components/Design/index.mjs",
|
||||||
"./components/Docusaurus": "./components/Docusaurus/index.mjs",
|
"./components/Docusaurus": "./components/Docusaurus/index.mjs",
|
||||||
"./components/Editor": "./components/Editor/index.mjs",
|
"./components/Editor": "./components/Editor/index.mjs",
|
||||||
|
"./components/Heading": "./components/Heading/index.mjs",
|
||||||
"./components/Highlight": "./components/Highlight/index.mjs",
|
"./components/Highlight": "./components/Highlight/index.mjs",
|
||||||
"./components/Icon": "./components/Icon/index.mjs",
|
"./components/Icon": "./components/Icon/index.mjs",
|
||||||
"./components/Input": "./components/Input/index.mjs",
|
"./components/Input": "./components/Input/index.mjs",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { cloudflare as cloudflareConfig } from '@freesewing/config'
|
||||||
import _set from 'lodash/set.js'
|
import _set from 'lodash/set.js'
|
||||||
import _unset from 'lodash/unset.js'
|
import _unset from 'lodash/unset.js'
|
||||||
import _orderBy from 'lodash/orderBy.js'
|
import _orderBy from 'lodash/orderBy.js'
|
||||||
|
import { loadingMessages } from './loading-messages.mjs'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Re-export lodash utils
|
* Re-export lodash utils
|
||||||
|
@ -18,13 +19,14 @@ export const orderBy = _orderBy
|
||||||
/*
|
/*
|
||||||
* CSS classes to spread icon + text horizontally on a button
|
* CSS classes to spread icon + text horizontally on a button
|
||||||
*/
|
*/
|
||||||
export const horFlexClasses = 'flex flex-row items-center justify-between gap-4'
|
export const horFlexClasses =
|
||||||
|
'tw-flex tw-flex-row tw-items-center tw-justify-between tw-gap-4 tw-w-full'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CSS classes to spread icon + text horizontally on a button, only from md upwards
|
* CSS classes to spread icon + text horizontally on a button, only from md upwards
|
||||||
*/
|
*/
|
||||||
export const horFlexClassesNoSm =
|
export const horFlexClassesNoSm =
|
||||||
'md:flex md:flex-row md:items-center md:justify-between md:gap-4 md-w-full'
|
'md:tw-flex md:tw-flex-row md:tw-items-center md:tw-justify-between md:tw-gap-4 tw-w-full'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* These classes are what makes a link a link
|
* These classes are what makes a link a link
|
||||||
|
@ -415,6 +417,15 @@ export const optionType = (option) => {
|
||||||
return 'constant'
|
return 'constant'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a random loading message
|
||||||
|
*
|
||||||
|
* @return {string} msg - A random loading message
|
||||||
|
*/
|
||||||
|
export function randomLoadingMessage() {
|
||||||
|
return loadingMessages[Math.floor(Math.random() * loadingMessages.length)]
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Generic rounding method
|
* Generic rounding method
|
||||||
*
|
*
|
||||||
|
|
27
packages/utils/src/loading-messages.mjs
Normal file
27
packages/utils/src/loading-messages.mjs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
export const loadingMessages = [
|
||||||
|
'Unfolding ideas...',
|
||||||
|
'Teaching hamsters to run faster...',
|
||||||
|
'Convincing pixels to get in line...',
|
||||||
|
'Warming up the flux capacitor...',
|
||||||
|
'Untangling digital spaghetti...',
|
||||||
|
'Counting backwards from infinity...',
|
||||||
|
'Generating witty loading messages...',
|
||||||
|
'Brewing digital coffee...',
|
||||||
|
'Herding cats into quantum boxes...',
|
||||||
|
'Downloading more RAM...',
|
||||||
|
'Dividing by zero...',
|
||||||
|
'Spinning up the hamster wheel...',
|
||||||
|
'Converting caffeine to code...',
|
||||||
|
'Bending the space-time continuum...',
|
||||||
|
'Charging laser batteries...',
|
||||||
|
'Summoning the data spirits...',
|
||||||
|
'Searching for the lost semicolon...',
|
||||||
|
'Consulting the digital rolodex...',
|
||||||
|
'Calibrating the flux capacitor...',
|
||||||
|
'Collecting magic internet points...',
|
||||||
|
'Solving P vs NP...',
|
||||||
|
'Compressing time and space...',
|
||||||
|
'Entering the matrix...',
|
||||||
|
'Upgrading to Web 4.0...',
|
||||||
|
'Debugging the debugger...',
|
||||||
|
]
|
|
@ -15,8 +15,8 @@ export default function SignInPage() {
|
||||||
title="Sign In"
|
title="Sign In"
|
||||||
description="Sign In to your FreeSewing account to unlock all features"
|
description="Sign In to your FreeSewing account to unlock all features"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center h-screen justify-center text-base-content px-4">
|
<div className="tw-flex tw-flex-col tw-items-center tw-text-base-content tw-px-4">
|
||||||
<div className="max-w-lg w-full">
|
<div className="tw-max-w-lg tw-w-full">
|
||||||
<SignIn />
|
<SignIn />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const theme = {
|
||||||
'neutral-focus': colors.neutral['700'],
|
'neutral-focus': colors.neutral['700'],
|
||||||
'neutral-content': colors.neutral['50'],
|
'neutral-content': colors.neutral['50'],
|
||||||
|
|
||||||
info: colors.indigo['400'],
|
info: colors.yellow['400'],
|
||||||
success: colors.green['400'],
|
success: colors.green['400'],
|
||||||
warning: colors.orange['400'],
|
warning: colors.orange['400'],
|
||||||
error: colors.red['400'],
|
error: colors.red['400'],
|
||||||
|
|
|
@ -79,9 +79,9 @@ export const theme = {
|
||||||
|
|
||||||
// info: Used rarely, can be another color best somewhat neutral looking
|
// info: Used rarely, can be another color best somewhat neutral looking
|
||||||
// and should work with the default text color
|
// and should work with the default text color
|
||||||
info: colors.indigo['600'],
|
info: colors.yellow['300'],
|
||||||
// Text color on info
|
// Text color on info
|
||||||
'info-content': colors.neutral[50],
|
'info-content': colors.neutral[900],
|
||||||
|
|
||||||
// success: Used rarely, but if it is it's in notifications indicating success
|
// success: Used rarely, but if it is it's in notifications indicating success
|
||||||
// Typically some shade of green
|
// Typically some shade of green
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue