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()
function setProject(id: string) {
clearProject()
project.value = getProjectById(id, { shallow: false }) as Project
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">
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({
layout: 'create',
})
const { projects } = useData()
const { saveProject, setProject, publishProject, saveProjectImage } = useProject()
const { setProject, saveProjectImage } = useProject()
const { project, isPublishing } = storeToRefs(useProject())
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({
accept: 'image/*', // Set to accept only image files
})
@ -69,84 +37,19 @@ onChange((files) => {
saveProjectImage(file)
})
const projectNameInput = ref<HTMLInputElement | null>(null)
function useProjectName() {
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'
const { next, jumpTo, publish, toggleEditName } = useProjectForm()
const { currentComponent, selectedTab, tabsArray, isEditingName, name, nameError } = storeToRefs(useProjectForm())
name.value = project.value?.name || 'Untitled'
function toggleEdit() {
isEditing.value = !isEditing.value
if (isEditing.value) {
const projectNameInput = ref<HTMLInputElement | null>(null)
watch(isEditingName, () => {
if (isEditingName.value) {
setTimeout(() => {
projectNameInput.value?.focus()
}, 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)
</script>
@ -219,7 +122,7 @@ const transitionDone = ref(false)
relative
>
<input
v-if="isEditing"
v-if="isEditingName"
ref="projectNameInput"
v-model="name"
w-fit
@ -230,7 +133,7 @@ const transitionDone = ref(false)
leading-28px
bg-app-bg-dark_grey
onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
@blur="toggleEdit()"
@blur="toggleEditName()"
>
<h2
v-else
@ -241,8 +144,8 @@ const transitionDone = ref(false)
{{ name }}
</h2>
<button
v-if="!isEditing"
@click="toggleEdit()"
v-if="!isEditingName"
@click="toggleEditName()"
>
<UnoIcon
text-20px
@ -273,7 +176,7 @@ const transitionDone = ref(false)
<SelectBox
:model-value="selectedTab"
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"
w-full
lg:hidden
@ -282,14 +185,14 @@ const transitionDone = ref(false)
@update:model-value="$event => jumpTo($event)"
/>
<button
v-for="tab in tabs"
v-for="(tab, index) in tabsArray"
:key="tab.value"
lg:block
hidden
pb-8px
leading-40px
:class="selectedTab === tab.value ? 'font-bold border-b-4 border-app-white' : ''"
@click="jumpTo(tab.value)"
:class="selectedTab === index ? 'font-bold border-b-4 border-app-white' : ''"
@click="jumpTo(index)"
>
{{ tab.label }}
</button>
@ -316,7 +219,7 @@ const transitionDone = ref(false)
@after-enter="transitionDone = true"
>
<component
:is="getCurrentComponent()"
:is="tabsArray[selectedTab].component"
ref="currentComponent"
:project="project"
w-full
@ -326,7 +229,7 @@ const transitionDone = ref(false)
/>
</Transition>
<component
:is="getCurrentComponent()"
:is="tabsArray[selectedTab].component"
v-else
ref="currentComponent"
:project="project"
@ -336,7 +239,7 @@ const transitionDone = ref(false)
gap-8px
/>
<Button
v-if="selectedTab !== tabs[tabs.length - 1].value"
v-if="selectedTab !== tabsArray.length - 1"
class="hidden!"
mt-48px
lg="w-fit flex!"
@ -363,7 +266,7 @@ const transitionDone = ref(false)
p-12px
>
<Button
v-if="selectedTab !== tabs[tabs.length - 1].value"
v-if="selectedTab !== tabsArray.length - 1"
flex
lg="w-fit hidden!"
border
@ -372,7 +275,7 @@ const transitionDone = ref(false)
<span px-24px>NEXT SECTION</span>
</Button>
<span
v-if="selectedTab !== tabs[tabs.length - 1].value"
v-if="selectedTab !== tabsArray.length - 1"
lg="hidden"
block
text="12px italic app-white/50"

View file

@ -1,43 +1,11 @@
<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({
layout: 'create',
})
const { saveProject, publishProject, saveProjectImage } = useProject()
const { saveProjectImage } = 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({
accept: 'image/*', // Set to accept only image files
})
@ -56,81 +24,18 @@ onChange((files) => {
saveProjectImage(file)
})
const projectNameInput = ref<HTMLInputElement | null>(null)
function useProjectName() {
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'
const { next, jumpTo, publish, toggleEditName } = useProjectForm()
const { currentComponent, selectedTab, tabsArray, isEditingName, name, nameError } = storeToRefs(useProjectForm())
function toggleEdit() {
isEditing.value = !isEditing.value
if (isEditing.value) {
const projectNameInput = ref<HTMLInputElement | null>(null)
watch(isEditingName, () => {
if (isEditingName.value) {
setTimeout(() => {
projectNameInput.value?.focus()
}, 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)
</script>
@ -204,7 +109,7 @@ const transitionDone = ref(false)
relative
>
<input
v-if="isEditing"
v-if="isEditingName"
ref="projectNameInput"
v-model="name"
w-fit
@ -215,7 +120,7 @@ const transitionDone = ref(false)
leading-28px
bg-app-bg-dark_grey
onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
@blur="toggleEdit()"
@blur="toggleEditName()"
>
<h2
v-else
@ -226,8 +131,8 @@ const transitionDone = ref(false)
{{ name }}
</h2>
<button
v-if="!isEditing"
@click="toggleEdit()"
v-if="!isEditingName"
@click="toggleEditName()"
>
<UnoIcon
text-20px
@ -258,7 +163,7 @@ const transitionDone = ref(false)
<SelectBox
:model-value="selectedTab"
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"
w-full
lg:hidden
@ -267,14 +172,14 @@ const transitionDone = ref(false)
@update:model-value="$event => jumpTo($event)"
/>
<button
v-for="tab in tabs"
v-for="(tab, index) in tabsArray"
:key="tab.value"
lg:block
hidden
pb-8px
leading-40px
:class="selectedTab === tab.value ? 'font-bold border-b-4 border-app-white' : ''"
@click="jumpTo(tab.value)"
:class="selectedTab === index ? 'font-bold border-b-4 border-app-white' : ''"
@click="jumpTo(index)"
>
{{ tab.label }}
</button>
@ -301,7 +206,7 @@ const transitionDone = ref(false)
@after-enter="transitionDone = true"
>
<component
:is="getCurrentComponent()"
:is="tabsArray[selectedTab].component"
ref="currentComponent"
:project="project"
w-full
@ -311,7 +216,7 @@ const transitionDone = ref(false)
/>
</Transition>
<component
:is="getCurrentComponent()"
:is="tabsArray[selectedTab].component"
v-else
ref="currentComponent"
:project="project"
@ -321,7 +226,7 @@ const transitionDone = ref(false)
gap-24px
/>
<Button
v-if="selectedTab !== tabs[tabs.length - 1].value"
v-if="selectedTab !== tabsArray.length - 1"
class="hidden!"
mt-48px
lg="w-fit flex!"
@ -348,7 +253,7 @@ const transitionDone = ref(false)
p-12px
>
<Button
v-if="selectedTab !== tabs[tabs.length - 1].value"
v-if="selectedTab !== tabsArray.length - 1"
flex
lg="w-fit hidden!"
border
@ -357,7 +262,7 @@ const transitionDone = ref(false)
<span px-24px>NEXT SECTION</span>
</Button>
<span
v-if="selectedTab !== tabs[tabs.length - 1].value"
v-if="selectedTab !== tabsArray.length - 1"
lg="hidden"
block
text="12px italic app-white/50"
@ -375,7 +280,7 @@ const transitionDone = ref(false)
w-full
lg="w-fit"
inverted-color
@click="publish()"
@click="publish(true)"
>
<UnoIcon
v-if="isPublishing"