Migrate to Vue 3 and Vite
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
52
js/src/components/Account/ActorCard.story.vue
Normal file
52
js/src/components/Account/ActorCard.story.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<Story>
|
||||
<Variant title="local">
|
||||
<ActorCard :actor="stateLocal"></ActorCard>
|
||||
<template #controls>
|
||||
<HstText v-model="stateLocal.preferredUsername" title="username" />
|
||||
<HstText v-model="stateLocal.name" title="Name" />
|
||||
</template>
|
||||
</Variant>
|
||||
<Variant title="remote">
|
||||
<ActorCard :actor="stateRemote"></ActorCard>
|
||||
<template #controls>
|
||||
<HstText v-model="stateRemote.preferredUsername" title="username" />
|
||||
<HstText v-model="stateRemote.name" title="Name" />
|
||||
<HstText v-model="stateRemote.domain" title="Domain" />
|
||||
<HstText v-model="avatarUrl" title="Avatar" />
|
||||
</template>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ActorCard from "./ActorCard.vue";
|
||||
import { reactive, ref } from "vue";
|
||||
import { IActor } from "@/types/actor";
|
||||
import { ActorType } from "@/types/enums";
|
||||
|
||||
const avatarUrl = ref<string>(
|
||||
"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/399/original/aa56a445efb72803.jpg"
|
||||
);
|
||||
|
||||
const stateLocal = reactive<IActor>({
|
||||
name: "Thomas Citharel",
|
||||
preferredUsername: "tcit",
|
||||
avatar: null,
|
||||
domain: null,
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
});
|
||||
|
||||
const stateRemote = reactive<IActor>({
|
||||
name: "Framasoft",
|
||||
preferredUsername: "framasoft",
|
||||
avatar: { url: avatarUrl.value, id: "", name: "", alt: "", metadata: {} },
|
||||
domain: "framapiaf.org",
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
});
|
||||
</script>
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-white rounded-lg flex space-x-4 items-center"
|
||||
class="bg-white dark:bg-mbz-purple rounded-lg flex space-x-4 items-center"
|
||||
:class="{ 'flex-col p-4 shadow-md sm:p-8 pb-10 w-80': !inline }"
|
||||
>
|
||||
<div>
|
||||
<div class="flex pl-2">
|
||||
<figure class="w-12 h-12" v-if="actor.avatar">
|
||||
<img
|
||||
class="rounded-lg"
|
||||
@@ -13,16 +13,15 @@
|
||||
height="48"
|
||||
/>
|
||||
</figure>
|
||||
<b-icon
|
||||
<AccountCircle
|
||||
v-else
|
||||
:size="inline ? 'is-medium' : 'is-large'"
|
||||
icon="account-circle"
|
||||
:size="inline ? 24 : 48"
|
||||
class="ltr:-mr-0.5 rtl:-ml-0.5"
|
||||
/>
|
||||
</div>
|
||||
<div :class="{ 'text-center': !inline }" class="overflow-hidden w-full">
|
||||
<h5
|
||||
class="text-xl font-medium violet-title tracking-tight text-gray-900 whitespace-pre-line line-clamp-2"
|
||||
class="text-xl font-medium violet-title tracking-tight text-gray-900 dark:text-gray-200 whitespace-pre-line line-clamp-2"
|
||||
>
|
||||
{{ displayName(actor) }}
|
||||
</h5>
|
||||
@@ -54,9 +53,9 @@
|
||||
height="48"
|
||||
/>
|
||||
</figure>
|
||||
<b-icon
|
||||
<o-icon
|
||||
v-else
|
||||
size="is-large"
|
||||
size="large"
|
||||
icon="account-circle"
|
||||
class="ltr:-mr-0.5 rtl:-ml-0.5"
|
||||
/>
|
||||
@@ -78,29 +77,28 @@
|
||||
</div>
|
||||
</div> -->
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
<script lang="ts" setup>
|
||||
import { displayName, IActor, usernameWithDomain } from "../../types/actor";
|
||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||
|
||||
@Component
|
||||
export default class ActorCard extends Vue {
|
||||
@Prop({ required: true, type: Object }) actor!: IActor;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: false }) full!: boolean;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: false }) inline!: boolean;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: false }) popover!: boolean;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: true }) limit!: boolean;
|
||||
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
|
||||
displayName = displayName;
|
||||
}
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
actor: IActor;
|
||||
full?: boolean;
|
||||
inline?: boolean;
|
||||
popover?: boolean;
|
||||
limit?: boolean;
|
||||
}>(),
|
||||
{
|
||||
full: false,
|
||||
inline: false,
|
||||
popover: false,
|
||||
limit: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style scoped>
|
||||
.only-first-child ::v-deep :not(:first-child) {
|
||||
.only-first-child :deep(:not(:first-child)) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
52
js/src/components/Account/ActorInline.story.vue
Normal file
52
js/src/components/Account/ActorInline.story.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<Story>
|
||||
<Variant title="local">
|
||||
<ActorInline :actor="stateLocal" />
|
||||
<template #controls>
|
||||
<HstText v-model="stateLocal.preferredUsername" title="username" />
|
||||
<HstText v-model="stateLocal.name" title="Name" />
|
||||
</template>
|
||||
</Variant>
|
||||
<Variant title="remote">
|
||||
<ActorInline :actor="stateRemote" />
|
||||
<template #controls>
|
||||
<HstText v-model="stateRemote.preferredUsername" title="username" />
|
||||
<HstText v-model="stateRemote.name" title="Name" />
|
||||
<HstText v-model="stateRemote.domain" title="Domain" />
|
||||
<HstText v-model="avatarUrl" title="Avatar" />
|
||||
</template>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ActorInline from "./ActorInline.vue";
|
||||
import { reactive, ref } from "vue";
|
||||
import { IActor } from "@/types/actor";
|
||||
import { ActorType } from "@/types/enums";
|
||||
|
||||
const avatarUrl = ref<string>(
|
||||
"https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/399/original/aa56a445efb72803.jpg"
|
||||
);
|
||||
|
||||
const stateLocal = reactive<IActor>({
|
||||
name: "Thomas Citharel",
|
||||
preferredUsername: "tcit",
|
||||
avatar: null,
|
||||
domain: null,
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
});
|
||||
|
||||
const stateRemote = reactive<IActor>({
|
||||
name: "Framasoft",
|
||||
preferredUsername: "framasoft",
|
||||
avatar: { url: avatarUrl.value, id: "", name: "", alt: "", metadata: {} },
|
||||
domain: "framapiaf.org",
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
});
|
||||
</script>
|
||||
@@ -1,34 +1,37 @@
|
||||
<template>
|
||||
<div class="inline-flex items-start">
|
||||
<div
|
||||
class="inline-flex items-start bg-white dark:bg-violet-1 dark:text-white p-2 rounded-md"
|
||||
>
|
||||
<div class="flex-none mr-2">
|
||||
<figure class="image is-48x48" v-if="actor.avatar">
|
||||
<img class="is-rounded" :src="actor.avatar.url" alt="" />
|
||||
<figure v-if="actor.avatar">
|
||||
<img
|
||||
class="rounded-xl"
|
||||
:src="actor.avatar.url"
|
||||
alt=""
|
||||
width="36"
|
||||
height="36"
|
||||
/>
|
||||
</figure>
|
||||
<b-icon v-else size="is-large" icon="account-circle" />
|
||||
<AccountCircle :size="36" v-else />
|
||||
</div>
|
||||
|
||||
<div class="flex-auto">
|
||||
<p class="text-base line-clamp-3 md:line-clamp-2 max-w-xl">
|
||||
<p class="text-lg line-clamp-3 md:line-clamp-2 max-w-xl">
|
||||
{{ displayName(actor) }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 truncate">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-300 truncate">
|
||||
@{{ usernameWithDomain(actor) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
<script lang="ts" setup>
|
||||
import { displayName, IActor, usernameWithDomain } from "../../types/actor";
|
||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||
|
||||
@Component
|
||||
export default class ActorInline extends Vue {
|
||||
@Prop({ required: true, type: Object }) actor!: IActor;
|
||||
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
|
||||
displayName = displayName;
|
||||
}
|
||||
defineProps<{
|
||||
actor: IActor;
|
||||
}>();
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/_mixins" as *;
|
||||
@@ -42,7 +45,7 @@ div.actor-inline {
|
||||
flex-basis: auto;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
@include margin-right(0.5rem);
|
||||
// @include margin-right(0.5rem);
|
||||
}
|
||||
div.actor-name {
|
||||
flex-basis: auto;
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<section>
|
||||
<h1 class="title">
|
||||
{{ $t("My identities") }}
|
||||
</h1>
|
||||
|
||||
<ul class="identities">
|
||||
<li v-for="identity in identities" :key="identity.id">
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'UpdateIdentity',
|
||||
params: { identityName: identity.preferredUsername },
|
||||
}"
|
||||
class="media identity"
|
||||
v-bind:class="{ 'is-current-identity': isCurrentIdentity(identity) }"
|
||||
>
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48" v-if="identity.avatar">
|
||||
<img class="is-rounded" :src="identity.avatar.url" />
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<div class="media-content">
|
||||
{{ identity.displayName() }}
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<router-link
|
||||
:to="{ name: 'CreateIdentity' }"
|
||||
class="button create-identity is-primary"
|
||||
>
|
||||
{{ $t("Create a new identity") }}
|
||||
</router-link>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IDENTITIES } from "../../graphql/actor";
|
||||
import { IPerson, Person } from "../../types/actor";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
identities: {
|
||||
query: IDENTITIES,
|
||||
|
||||
update(result) {
|
||||
return result.identities.map((i: IPerson) => new Person(i));
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class Identities extends Vue {
|
||||
@Prop({ type: String }) currentIdentityName!: string;
|
||||
|
||||
identities: Person[] = [];
|
||||
|
||||
errors: string[] = [];
|
||||
|
||||
isCurrentIdentity(identity: IPerson): boolean {
|
||||
return identity.preferredUsername === this.currentIdentityName;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.identities {
|
||||
border-right: 1px solid grey;
|
||||
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.media.identity {
|
||||
align-items: center;
|
||||
font-size: 1.3rem;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 15px;
|
||||
color: #000;
|
||||
|
||||
&.is-current-identity {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
59
js/src/components/Account/PopoverActorCard.story.vue
Normal file
59
js/src/components/Account/PopoverActorCard.story.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<Story>
|
||||
<Variant :setup-app="setupApp" title="Person">
|
||||
<div class="p-5">
|
||||
<PopoverActorCard :actor="baseActor">
|
||||
<div><b> Popover me !</b></div></PopoverActorCard
|
||||
>
|
||||
</div>
|
||||
</Variant>
|
||||
<Variant :setup-app="setupApp" title="Group">
|
||||
<div class="p-5">
|
||||
<PopoverActorCard :actor="group">
|
||||
<div><b> Popover me !</b></div></PopoverActorCard
|
||||
>
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PopoverActorCard from "./PopoverActorCard.vue";
|
||||
import FloatingVue from "floating-vue";
|
||||
import "floating-vue/dist/style.css";
|
||||
import { ActorType } from "@/types/enums";
|
||||
|
||||
const baseActorAvatar = {
|
||||
id: "",
|
||||
name: "",
|
||||
alt: "",
|
||||
metadata: {},
|
||||
url: "https://social.tcit.fr/system/accounts/avatars/000/000/001/original/a28c50ce5f2b13fd.jpg",
|
||||
};
|
||||
|
||||
const baseActor = {
|
||||
name: "Thomas Citharel",
|
||||
preferredUsername: "tcit",
|
||||
avatar: baseActorAvatar,
|
||||
domain: null,
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
};
|
||||
|
||||
const group = {
|
||||
...baseActor,
|
||||
name: "Framasoft",
|
||||
preferredUsername: "framasoft",
|
||||
domain: "mobilizon.fr",
|
||||
avatar: {
|
||||
...baseActorAvatar,
|
||||
url: "https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/399/original/aa56a445efb72803.jpg",
|
||||
},
|
||||
};
|
||||
|
||||
function setupApp({ app }) {
|
||||
app.use(FloatingVue);
|
||||
}
|
||||
</script>
|
||||
@@ -1,44 +1,38 @@
|
||||
<template>
|
||||
<v-popover
|
||||
offset="16"
|
||||
trigger="hover"
|
||||
<VMenu
|
||||
:distance="16"
|
||||
:triggers="['hover']"
|
||||
class="popover"
|
||||
:class="{ inline, clickable: actor && actor.type === ActorType.GROUP }"
|
||||
>
|
||||
<slot></slot>
|
||||
<template slot="popover">
|
||||
<template #popper>
|
||||
<actor-card :full="true" :actor="actor" :popover="true" />
|
||||
</template>
|
||||
</v-popover>
|
||||
</VMenu>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { ActorType } from "@/types/enums";
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
import { IActor } from "../../types/actor";
|
||||
import ActorCard from "./ActorCard.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
ActorCard,
|
||||
},
|
||||
})
|
||||
export default class PopoverActorCard extends Vue {
|
||||
@Prop({ required: true, type: Object }) actor!: IActor;
|
||||
|
||||
@Prop({ required: false, type: Boolean, default: false }) inline!: boolean;
|
||||
|
||||
ActorType = ActorType;
|
||||
}
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
actor: IActor;
|
||||
inline?: boolean;
|
||||
}>(),
|
||||
{
|
||||
inline: false,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.inline {
|
||||
display: inline;
|
||||
<style lang="scss">
|
||||
.v-popper__inner {
|
||||
padding: 0 !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
.popover {
|
||||
cursor: default;
|
||||
}
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
.v-popper__arrow-outer {
|
||||
border-color: $violet-1 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
29
js/src/components/Account/ProfileOnboarding.story.vue
Normal file
29
js/src/components/Account/ProfileOnboarding.story.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<Story>
|
||||
<Variant>
|
||||
<div class="p-5">
|
||||
<ProfileOnboarding
|
||||
:current-actor="baseActor"
|
||||
instance-name="Instance name"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ProfileOnboarding from "./ProfileOnboarding.vue";
|
||||
import { ActorType } from "@/types/enums";
|
||||
import { IPerson } from "@/types/actor";
|
||||
|
||||
const baseActor: IPerson = {
|
||||
name: "Thomas Citharel",
|
||||
preferredUsername: "tcit",
|
||||
avatar: null,
|
||||
domain: null,
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
};
|
||||
</script>
|
||||
@@ -1,71 +1,60 @@
|
||||
<template>
|
||||
<div class="section container">
|
||||
<div class="setting-title">
|
||||
<h2>{{ $t("Profiles and federation") }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<p class="content">
|
||||
{{
|
||||
$t(
|
||||
"Mobilizon uses a system of profiles to compartiment your activities. You will be able to create as many profiles as you want."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<hr role="presentation" />
|
||||
<p class="content">
|
||||
<span>
|
||||
{{
|
||||
$t(
|
||||
"Mobilizon is a federated software, meaning you can interact - depending on your admin's federation settings - with content from other instances, such as joining groups or events that were created elsewhere."
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
v-if="config"
|
||||
v-html="
|
||||
$t(
|
||||
'This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.',
|
||||
{
|
||||
domain,
|
||||
instanceName: config.name,
|
||||
}
|
||||
)
|
||||
"
|
||||
/>
|
||||
</p>
|
||||
<hr role="presentation" />
|
||||
<p class="content">
|
||||
{{
|
||||
$t(
|
||||
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:"
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<div class="has-text-centered">
|
||||
<code>{{ `${currentActor.preferredUsername}@${domain}` }}</code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<h2 class="text-2xl">{{ t("Profiles and federation") }}</h2>
|
||||
</div>
|
||||
<p class="my-2">
|
||||
{{
|
||||
t(
|
||||
"Mobilizon uses a system of profiles to compartiment your activities. You will be able to create as many profiles as you want."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<hr role="presentation" />
|
||||
<p class="my-2">
|
||||
<span>
|
||||
{{
|
||||
t(
|
||||
"Mobilizon is a federated software, meaning you can interact - depending on your admin's federation settings - with content from other instances, such as joining groups or events that were created elsewhere."
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<i18n-t
|
||||
keypath="This instance, {instanceName}, hosts your profile, so remember its name."
|
||||
>
|
||||
<template v-slot:instanceName>
|
||||
<b>{{
|
||||
t("{instanceName} ({domain})", {
|
||||
domain,
|
||||
instanceName,
|
||||
})
|
||||
}}</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</p>
|
||||
<hr role="presentation" />
|
||||
<p class="my-2">
|
||||
{{
|
||||
t(
|
||||
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:"
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<div class="text-center">
|
||||
<code>{{ `${currentActor?.preferredUsername}@${domain}` }}</code>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
|
||||
import { CONFIG } from "@/graphql/config";
|
||||
<script lang="ts" setup>
|
||||
import { IPerson } from "@/types/actor";
|
||||
import { IConfig } from "@/types/config.model";
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
config: CONFIG,
|
||||
currentActor: CURRENT_ACTOR_CLIENT,
|
||||
},
|
||||
})
|
||||
export default class ProfileOnboarding extends Vue {
|
||||
config!: IConfig;
|
||||
defineProps<{
|
||||
currentActor: IPerson;
|
||||
instanceName: string;
|
||||
}>();
|
||||
|
||||
currentActor!: IPerson;
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
|
||||
domain = window.location.hostname;
|
||||
}
|
||||
const domain = computed(() => window.location.hostname);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user