refactor: project create/edit

This commit is contained in:
DomWane 2024-10-01 14:54:35 +02:00
parent ed9e80af29
commit 9a43af8b7a
4 changed files with 156 additions and 239 deletions

View file

@ -9,6 +9,7 @@ export const useProject = defineStore('project', () => {
const { getProjectById } = useData() const { getProjectById } = useData()
function setProject(id: string) { function setProject(id: string) {
clearProject()
project.value = getProjectById(id, { shallow: false }) as Project project.value = getProjectById(id, { shallow: false }) as Project
delete project.value.ratings delete project.value.ratings
} }

View file

@ -0,0 +1,108 @@
import * as yup from 'yup'
import ProjectCreateCategoriesBasicInfo from '~/components/Project/Create/Categories/BasicInfo.vue'
import ProjectCreateCategoriesAssets from '~/components/Project/Create/Categories/Assets.vue'
import ProjectCreateCategoriesLinks from '~/components/Project/Create/Categories/Links.vue'
import ProjectCreateCategoriesTechnology from '~/components/Project/Create/Categories/Technology.vue'
import ProjectCreateCategoriesPrivacy from '~/components/Project/Create/Categories/Privacy.vue'
import ProjectCreateCategoriesSecurity from '~/components/Project/Create/Categories/Security.vue'
import ProjectCreateCategoriesTeam from '~/components/Project/Create/Categories/Team.vue'
import ProjectCreateCategoriesFunding from '~/components/Project/Create/Categories/Funding.vue'
import ProjectCreateCategoriesHistory from '~/components/Project/Create/Categories/History.vue'
export const useProjectForm = defineStore('useProjectForm', () => {
const { saveProject, publishProject } = useProject()
const { project, isPublishing } = storeToRefs(useProject())
const isEditingName = ref(false)
const { value: name, errorMessage: nameError } = useField<string>('name', yup.string().required().notOneOf(['Untitled', 'Undefined', 'Create', 'create']))
name.value = project.value?.name || 'Untitled'
function toggleEditName() {
isEditingName.value = !isEditingName.value
}
function saveName() {
saveProject({
...project.value,
name: name.value,
})
}
const tabs = reactive({
BASIC_INFO: { label: 'Basic Info', value: 'basic_info', component: markRaw(ProjectCreateCategoriesBasicInfo) },
ASSETS: { label: 'Assets', value: 'assets', component: markRaw(ProjectCreateCategoriesAssets) },
LINKS: { label: 'Links', value: 'links', component: markRaw(ProjectCreateCategoriesLinks) },
TECHNOLOGY: { label: 'Technology', value: 'technology', component: markRaw(ProjectCreateCategoriesTechnology) },
PRIVACY: { label: 'Privacy', value: 'privacy', component: markRaw(ProjectCreateCategoriesPrivacy) },
SECURITY: { label: 'Security', value: 'security', component: markRaw(ProjectCreateCategoriesSecurity) },
TEAM: { label: 'Team', value: 'team', component: markRaw(ProjectCreateCategoriesTeam) },
FUNDING: { label: 'Funding', value: 'funding', component: markRaw(ProjectCreateCategoriesFunding) },
HISTORY: { label: 'History', value: 'history', component: markRaw(ProjectCreateCategoriesHistory) },
})
const selectedTab = ref(0)
const tabsArray = computed(() => Object.values(tabs))
const currentComponent = ref()
async function next() {
if (selectedTab.value === 0) {
if (!(await currentComponent.value.isFormValid()) || nameError.value)
return
}
saveName()
currentComponent.value.save()
if (selectedTab.value === tabsArray.value.length - 1)
return
selectedTab.value = selectedTab.value + 1
}
async function jumpTo(index: number) {
if (selectedTab.value === 0) {
if (!(await currentComponent.value.isFormValid()) || nameError.value)
return
if (nameError.value)
return
else saveName()
}
saveName()
currentComponent.value.save()
selectedTab.value = index
}
async function publish(isNew?: boolean) {
if (selectedTab.value === 0) {
if (!(await currentComponent.value.isFormValid()) || nameError.value)
return
}
else if (isPublishing) {
return
}
saveName()
currentComponent.value?.save()
await publishProject()
if (isNew)
navigateTo('/')
else
navigateTo(`/project/${project.value?.id || project.value?.name?.toLowerCase().replace(/\s+/g, '-')}`)
}
return {
isEditingName,
name,
nameError,
toggleEditName,
saveName,
selectedTab,
tabsArray,
currentComponent,
next,
publish,
jumpTo,
}
})

View file

@ -1,21 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import * as yup from 'yup'
import ProjectCreateCategoriesBasicInfo from '~/components/Project/Create/Categories/BasicInfo.vue'
import ProjectCreateCategoriesAssets from '~/components/Project/Create/Categories/Assets.vue'
import ProjectCreateCategoriesLinks from '~/components/Project/Create/Categories/Links.vue'
import ProjectCreateCategoriesTechnology from '~/components/Project/Create/Categories/Technology.vue'
import ProjectCreateCategoriesPrivacy from '~/components/Project/Create/Categories/Privacy.vue'
import ProjectCreateCategoriesSecurity from '~/components/Project/Create/Categories/Security.vue'
import ProjectCreateCategoriesTeam from '~/components/Project/Create/Categories/Team.vue'
import ProjectCreateCategoriesFunding from '~/components/Project/Create/Categories/Funding.vue'
import ProjectCreateCategoriesHistory from '~/components/Project/Create/Categories/History.vue'
definePageMeta({ definePageMeta({
layout: 'create', layout: 'create',
}) })
const { projects } = useData() const { projects } = useData()
const { saveProject, setProject, publishProject, saveProjectImage } = useProject() const { setProject, saveProjectImage } = useProject()
const { project, isPublishing } = storeToRefs(useProject()) const { project, isPublishing } = storeToRefs(useProject())
const route = useRoute() const route = useRoute()
@ -30,27 +19,6 @@ if (!project.value) {
}) })
} }
const tabs = reactive([
{ label: 'Basic Info', value: 'basic_info', component: markRaw(ProjectCreateCategoriesBasicInfo) },
{ label: 'Assets', value: 'assets', component: markRaw(ProjectCreateCategoriesAssets) },
{ label: 'Links', value: 'links', component: markRaw(ProjectCreateCategoriesLinks) },
{ label: 'Technology', value: 'technology', component: markRaw(ProjectCreateCategoriesTechnology) },
{ label: 'Privacy', value: 'privacy', component: markRaw(ProjectCreateCategoriesPrivacy) },
{ label: 'Security', value: 'security', component: markRaw(ProjectCreateCategoriesSecurity) },
{ label: 'Team', value: 'team', component: markRaw(ProjectCreateCategoriesTeam) },
{ label: 'Funding', value: 'funding', component: markRaw(ProjectCreateCategoriesFunding) },
{ label: 'History', value: 'history', component: markRaw(ProjectCreateCategoriesHistory) },
])
const selectedTab = ref(tabs[0].value)
function getCurrentComponent() {
const tab = tabs.find(t => t.value === selectedTab.value) || tabs[0]
return tab.component || null
}
const currentComponent = ref()
const { open, onChange } = useFileDialog({ const { open, onChange } = useFileDialog({
accept: 'image/*', // Set to accept only image files accept: 'image/*', // Set to accept only image files
}) })
@ -69,84 +37,19 @@ onChange((files) => {
saveProjectImage(file) saveProjectImage(file)
}) })
const projectNameInput = ref<HTMLInputElement | null>(null) const { next, jumpTo, publish, toggleEditName } = useProjectForm()
function useProjectName() { const { currentComponent, selectedTab, tabsArray, isEditingName, name, nameError } = storeToRefs(useProjectForm())
const isEditing = ref(false) name.value = project.value?.name || 'Untitled'
// const name = ref('Untitled')
const { value: name, errorMessage: nameError } = useField<string>('name', yup.string().required().notOneOf(['Untitled', 'Undefined', 'Create', 'create']))
name.value = project.value?.name || 'Untitled'
function toggleEdit() { const projectNameInput = ref<HTMLInputElement | null>(null)
isEditing.value = !isEditing.value watch(isEditingName, () => {
if (isEditing.value) { if (isEditingName.value) {
setTimeout(() => { setTimeout(() => {
projectNameInput.value?.focus() projectNameInput.value?.focus()
}, 0) }, 0)
} }
} })
return {
isEditing,
name,
nameError,
toggleEdit,
}
}
const { isEditing, name, nameError, toggleEdit } = useProjectName()
name.value = project.value?.name || 'Untitled'
function save() {
saveProject({
...project.value,
name: name.value,
})
}
async function next() {
if (nameError.value)
return
if (selectedTab.value === 'basic_info') {
if (!(await currentComponent.value.isFormValid()))
return
else save()
}
currentComponent.value.save()
const currentIndex = tabs.findIndex(tab => tab.value === selectedTab.value)
const nextTab = tabs[currentIndex + 1] || tabs[0]
selectedTab.value = nextTab.value
}
async function publish() {
if (selectedTab.value === 'basic_info') {
if (!(await currentComponent.value.isFormValid()))
return
else save()
}
else if (isPublishing) {
return
}
currentComponent.value.save()
await publishProject()
navigateTo(`/project/${project.value?.id || project.value?.name?.toLowerCase().replace(/\s+/g, '-')}`)
}
async function jumpTo(tab: string) {
if (selectedTab.value === 'basic_info') {
if (!(await currentComponent.value.isFormValid()))
return
else save()
}
currentComponent.value.save()
selectedTab.value = tab
}
const transitionDone = ref(false) const transitionDone = ref(false)
</script> </script>
@ -219,7 +122,7 @@ const transitionDone = ref(false)
relative relative
> >
<input <input
v-if="isEditing" v-if="isEditingName"
ref="projectNameInput" ref="projectNameInput"
v-model="name" v-model="name"
w-fit w-fit
@ -230,7 +133,7 @@ const transitionDone = ref(false)
leading-28px leading-28px
bg-app-bg-dark_grey bg-app-bg-dark_grey
onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';" onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
@blur="toggleEdit()" @blur="toggleEditName()"
> >
<h2 <h2
v-else v-else
@ -241,8 +144,8 @@ const transitionDone = ref(false)
{{ name }} {{ name }}
</h2> </h2>
<button <button
v-if="!isEditing" v-if="!isEditingName"
@click="toggleEdit()" @click="toggleEditName()"
> >
<UnoIcon <UnoIcon
text-20px text-20px
@ -273,7 +176,7 @@ const transitionDone = ref(false)
<SelectBox <SelectBox
:model-value="selectedTab" :model-value="selectedTab"
label="Choose category" label="Choose category"
:options="tabs.map(t => ({ label: t.label, value: t.value }))" :options="tabsArray.map(t => ({ label: t.label, value: t.value }))"
:border-opacity="100" :border-opacity="100"
w-full w-full
lg:hidden lg:hidden
@ -282,14 +185,14 @@ const transitionDone = ref(false)
@update:model-value="$event => jumpTo($event)" @update:model-value="$event => jumpTo($event)"
/> />
<button <button
v-for="tab in tabs" v-for="(tab, index) in tabsArray"
:key="tab.value" :key="tab.value"
lg:block lg:block
hidden hidden
pb-8px pb-8px
leading-40px leading-40px
:class="selectedTab === tab.value ? 'font-bold border-b-4 border-app-white' : ''" :class="selectedTab === index ? 'font-bold border-b-4 border-app-white' : ''"
@click="jumpTo(tab.value)" @click="jumpTo(index)"
> >
{{ tab.label }} {{ tab.label }}
</button> </button>
@ -316,7 +219,7 @@ const transitionDone = ref(false)
@after-enter="transitionDone = true" @after-enter="transitionDone = true"
> >
<component <component
:is="getCurrentComponent()" :is="tabsArray[selectedTab].component"
ref="currentComponent" ref="currentComponent"
:project="project" :project="project"
w-full w-full
@ -326,7 +229,7 @@ const transitionDone = ref(false)
/> />
</Transition> </Transition>
<component <component
:is="getCurrentComponent()" :is="tabsArray[selectedTab].component"
v-else v-else
ref="currentComponent" ref="currentComponent"
:project="project" :project="project"
@ -336,7 +239,7 @@ const transitionDone = ref(false)
gap-8px gap-8px
/> />
<Button <Button
v-if="selectedTab !== tabs[tabs.length - 1].value" v-if="selectedTab !== tabsArray.length - 1"
class="hidden!" class="hidden!"
mt-48px mt-48px
lg="w-fit flex!" lg="w-fit flex!"
@ -363,7 +266,7 @@ const transitionDone = ref(false)
p-12px p-12px
> >
<Button <Button
v-if="selectedTab !== tabs[tabs.length - 1].value" v-if="selectedTab !== tabsArray.length - 1"
flex flex
lg="w-fit hidden!" lg="w-fit hidden!"
border border
@ -372,7 +275,7 @@ const transitionDone = ref(false)
<span px-24px>NEXT SECTION</span> <span px-24px>NEXT SECTION</span>
</Button> </Button>
<span <span
v-if="selectedTab !== tabs[tabs.length - 1].value" v-if="selectedTab !== tabsArray.length - 1"
lg="hidden" lg="hidden"
block block
text="12px italic app-white/50" text="12px italic app-white/50"

View file

@ -1,43 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import * as yup from 'yup'
import ProjectCreateCategoriesBasicInfo from '~/components/Project/Create/Categories/BasicInfo.vue'
import ProjectCreateCategoriesAssets from '~/components/Project/Create/Categories/Assets.vue'
import ProjectCreateCategoriesLinks from '~/components/Project/Create/Categories/Links.vue'
import ProjectCreateCategoriesTechnology from '~/components/Project/Create/Categories/Technology.vue'
import ProjectCreateCategoriesPrivacy from '~/components/Project/Create/Categories/Privacy.vue'
import ProjectCreateCategoriesSecurity from '~/components/Project/Create/Categories/Security.vue'
import ProjectCreateCategoriesTeam from '~/components/Project/Create/Categories/Team.vue'
import ProjectCreateCategoriesFunding from '~/components/Project/Create/Categories/Funding.vue'
import ProjectCreateCategoriesHistory from '~/components/Project/Create/Categories/History.vue'
definePageMeta({ definePageMeta({
layout: 'create', layout: 'create',
}) })
const { saveProject, publishProject, saveProjectImage } = useProject() const { saveProjectImage } = useProject()
const { project, isPublishing } = storeToRefs(useProject()) const { project, isPublishing } = storeToRefs(useProject())
const tabs = reactive([
{ label: 'Basic Info', value: 'basic_info', component: markRaw(ProjectCreateCategoriesBasicInfo) },
{ label: 'Assets', value: 'assets', component: markRaw(ProjectCreateCategoriesAssets) },
{ label: 'Links', value: 'links', component: markRaw(ProjectCreateCategoriesLinks) },
{ label: 'Technology', value: 'technology', component: markRaw(ProjectCreateCategoriesTechnology) },
{ label: 'Privacy', value: 'privacy', component: markRaw(ProjectCreateCategoriesPrivacy) },
{ label: 'Security', value: 'security', component: markRaw(ProjectCreateCategoriesSecurity) },
{ label: 'Team', value: 'team', component: markRaw(ProjectCreateCategoriesTeam) },
{ label: 'Funding', value: 'funding', component: markRaw(ProjectCreateCategoriesFunding) },
{ label: 'History', value: 'history', component: markRaw(ProjectCreateCategoriesHistory) },
])
const selectedTab = ref(tabs[0].value)
function getCurrentComponent() {
const tab = tabs.find(t => t.value === selectedTab.value) || tabs[0]
return tab.component || null
}
const currentComponent = ref()
const { open, onChange } = useFileDialog({ const { open, onChange } = useFileDialog({
accept: 'image/*', // Set to accept only image files accept: 'image/*', // Set to accept only image files
}) })
@ -56,81 +24,18 @@ onChange((files) => {
saveProjectImage(file) saveProjectImage(file)
}) })
const projectNameInput = ref<HTMLInputElement | null>(null) const { next, jumpTo, publish, toggleEditName } = useProjectForm()
function useProjectName() { const { currentComponent, selectedTab, tabsArray, isEditingName, name, nameError } = storeToRefs(useProjectForm())
const isEditing = ref(false)
// const name = ref('Untitled')
const { value: name, errorMessage: nameError } = useField<string>('name', yup.string().required().notOneOf(['Untitled', 'Undefined', 'Create', 'create']))
name.value = project.value?.name || 'Untitled'
function toggleEdit() { const projectNameInput = ref<HTMLInputElement | null>(null)
isEditing.value = !isEditing.value watch(isEditingName, () => {
if (isEditing.value) { if (isEditingName.value) {
setTimeout(() => { setTimeout(() => {
projectNameInput.value?.focus() projectNameInput.value?.focus()
}, 0) }, 0)
} }
} })
return {
isEditing,
name,
nameError,
toggleEdit,
}
}
const { isEditing, name, nameError, toggleEdit } = useProjectName()
function save() {
saveProject({
...project.value,
name: name.value,
})
}
async function next() {
if (selectedTab.value === 'basic_info') {
if (!(await currentComponent.value.isFormValid()))
return
else save()
}
currentComponent.value.save()
const currentIndex = tabs.findIndex(tab => tab.value === selectedTab.value)
const nextTab = tabs[currentIndex + 1] || tabs[0]
selectedTab.value = nextTab.value
}
async function publish() {
if (selectedTab.value === 'basic_info') {
if (!(await currentComponent.value.isFormValid()))
return
else save()
}
else if (isPublishing) {
return
}
currentComponent.value?.save()
await publishProject()
navigateTo('/')
}
async function jumpTo(tab: string) {
console.log(tab, selectedTab.value)
if (selectedTab.value === 'basic_info') {
if (!(await currentComponent.value.isFormValid()))
return
else save()
}
currentComponent.value.save()
selectedTab.value = tab
}
const transitionDone = ref(false) const transitionDone = ref(false)
</script> </script>
@ -204,7 +109,7 @@ const transitionDone = ref(false)
relative relative
> >
<input <input
v-if="isEditing" v-if="isEditingName"
ref="projectNameInput" ref="projectNameInput"
v-model="name" v-model="name"
w-fit w-fit
@ -215,7 +120,7 @@ const transitionDone = ref(false)
leading-28px leading-28px
bg-app-bg-dark_grey bg-app-bg-dark_grey
onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';" onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
@blur="toggleEdit()" @blur="toggleEditName()"
> >
<h2 <h2
v-else v-else
@ -226,8 +131,8 @@ const transitionDone = ref(false)
{{ name }} {{ name }}
</h2> </h2>
<button <button
v-if="!isEditing" v-if="!isEditingName"
@click="toggleEdit()" @click="toggleEditName()"
> >
<UnoIcon <UnoIcon
text-20px text-20px
@ -258,7 +163,7 @@ const transitionDone = ref(false)
<SelectBox <SelectBox
:model-value="selectedTab" :model-value="selectedTab"
label="Choose category" label="Choose category"
:options="tabs.map(t => ({ label: t.label, value: t.value }))" :options="tabsArray.map(t => ({ label: t.label, value: t.value }))"
:border-opacity="30" :border-opacity="30"
w-full w-full
lg:hidden lg:hidden
@ -267,14 +172,14 @@ const transitionDone = ref(false)
@update:model-value="$event => jumpTo($event)" @update:model-value="$event => jumpTo($event)"
/> />
<button <button
v-for="tab in tabs" v-for="(tab, index) in tabsArray"
:key="tab.value" :key="tab.value"
lg:block lg:block
hidden hidden
pb-8px pb-8px
leading-40px leading-40px
:class="selectedTab === tab.value ? 'font-bold border-b-4 border-app-white' : ''" :class="selectedTab === index ? 'font-bold border-b-4 border-app-white' : ''"
@click="jumpTo(tab.value)" @click="jumpTo(index)"
> >
{{ tab.label }} {{ tab.label }}
</button> </button>
@ -301,7 +206,7 @@ const transitionDone = ref(false)
@after-enter="transitionDone = true" @after-enter="transitionDone = true"
> >
<component <component
:is="getCurrentComponent()" :is="tabsArray[selectedTab].component"
ref="currentComponent" ref="currentComponent"
:project="project" :project="project"
w-full w-full
@ -311,7 +216,7 @@ const transitionDone = ref(false)
/> />
</Transition> </Transition>
<component <component
:is="getCurrentComponent()" :is="tabsArray[selectedTab].component"
v-else v-else
ref="currentComponent" ref="currentComponent"
:project="project" :project="project"
@ -321,7 +226,7 @@ const transitionDone = ref(false)
gap-24px gap-24px
/> />
<Button <Button
v-if="selectedTab !== tabs[tabs.length - 1].value" v-if="selectedTab !== tabsArray.length - 1"
class="hidden!" class="hidden!"
mt-48px mt-48px
lg="w-fit flex!" lg="w-fit flex!"
@ -348,7 +253,7 @@ const transitionDone = ref(false)
p-12px p-12px
> >
<Button <Button
v-if="selectedTab !== tabs[tabs.length - 1].value" v-if="selectedTab !== tabsArray.length - 1"
flex flex
lg="w-fit hidden!" lg="w-fit hidden!"
border border
@ -357,7 +262,7 @@ const transitionDone = ref(false)
<span px-24px>NEXT SECTION</span> <span px-24px>NEXT SECTION</span>
</Button> </Button>
<span <span
v-if="selectedTab !== tabs[tabs.length - 1].value" v-if="selectedTab !== tabsArray.length - 1"
lg="hidden" lg="hidden"
block block
text="12px italic app-white/50" text="12px italic app-white/50"
@ -375,7 +280,7 @@ const transitionDone = ref(false)
w-full w-full
lg="w-fit" lg="w-fit"
inverted-color inverted-color
@click="publish()" @click="publish(true)"
> >
<UnoIcon <UnoIcon
v-if="isPublishing" v-if="isPublishing"