Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 37 additions & 19 deletions packages/client/src/components/app/ChatApp.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@
import { goto } from "$app/navigation";
import Channel from "../channels/Channel.svelte";
import ChannelList from "../channels/ChannelList.svelte";
import Personalization from "../chat/Personalization.svelte";

type Selection =
| { type: "chat"; selectedChannelId: Id<"channels"> | undefined }
| { type: "personalization"; selectedChannelId: undefined };

interface Props {
organizationId: Id<"organizations">;
channelId?: Id<"channels">;
screenMode: Selection;
}

const { organizationId, channelId }: Props = $props();
const { organizationId, screenMode }: Props = $props();

const organization = useQuery(api.organizations.get, () => ({
id: organizationId,
}));
</script>

<div class="bg-base-100 flex h-screen">
<div class="bg-base-200 border-base-300 w-80 border-r">
<div class="bg-base-200 border-base-300 flex h-full w-80 flex-col border-r">
<div class="border-base-300 border-b p-4">
<div class="flex items-center justify-between">
<div>
Expand Down Expand Up @@ -74,29 +79,42 @@

<ChannelList
{organizationId}
bind:selectedChannelId={
() => channelId,
(id) => {
goto(`/orgs/${organizationId}/chat/${id}`);
bind:screenMode={
() => screenMode,
(screenMode) => {
if (screenMode.type === "chat") {
goto(
`/orgs/${organizationId}/chat/${screenMode.selectedChannelId}`,
);
} else if (screenMode.type === "personalization") {
goto(`/orgs/${organizationId}/personalization`);
}
}
}
/>
</div>

<div class="flex flex-1 flex-col">
{#if channelId}
<Channel {organizationId} selectedChannelId={channelId} />
{:else}
<div class="bg-base-200 flex flex-1 items-center justify-center">
<div class="text-center">
<h2 class="text-base-content/60 mb-2 text-2xl font-semibold">
{organization.data?.name || "組織"}へようこそ
</h2>
<p class="text-base-content/50">
左からチャンネルを選択して会話を始めましょう
</p>
{#if screenMode.type == "chat"}
{#if screenMode.selectedChannelId}
<Channel
{organizationId}
selectedChannelId={screenMode.selectedChannelId}
/>
{:else}
<div class="bg-base-200 flex flex-1 items-center justify-center">
<div class="text-center">
<h2 class="text-base-content/60 mb-2 text-2xl font-semibold">
{organization.data?.name || "組織"}へようこそ
</h2>
<p class="text-base-content/50">
左からチャンネルを選択して会話を始めましょう
</p>
</div>
</div>
</div>
{/if}
{:else if screenMode.type == "personalization"}
<Personalization {organizationId} />
{/if}
</div>
</div>
29 changes: 25 additions & 4 deletions packages/client/src/components/channels/ChannelList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,25 @@
import { useQuery } from "convex-svelte";
import CreateChannelButton from "./CreateChannelButton.svelte";

type Selection =
| { type: "chat"; selectedChannelId: Id<"channels"> | undefined }
| { type: "personalization"; selectedChannelId: undefined };

/*<<<<<<< HEAD:packages/client/src/components/chat/ChannelList.svelte
screenMode: Selection;
}

let {
screenMode = $bindable(),
}: Props = $props();
=======
*/
interface Props {
organizationId: Id<"organizations">;
selectedChannelId?: Id<"channels">;
screenMode: Selection;
}

let { organizationId, selectedChannelId = $bindable() }: Props = $props();
let { organizationId, screenMode = $bindable() }: Props = $props();

const channels = useQuery(api.channels.list, () => ({
organizationId,
Expand All @@ -27,11 +40,13 @@
<button
class={[
"border-base-300 w-full border-b p-3 text-left",
selectedChannelId === channel._id
screenMode.selectedChannelId === channel._id
? "bg-primary text-primary-content"
: "hover:bg-base-300",
].join(" ")}
onclick={() => (selectedChannelId = channel._id)}
onclick={() => {
screenMode = { type: "chat", selectedChannelId: channel._id };
}}
>
<div class="font-medium"># {channel.name}</div>
{#if channel.description}
Expand All @@ -51,4 +66,10 @@
</div>
{/if}
</div>
<button
class="btn btn-primary mt-auto mb-2 w-full"
onclick={() => {
screenMode = { type: "personalization", selectedChannelId: undefined };
}}>個人用設定</button
>
</div>
132 changes: 132 additions & 0 deletions packages/client/src/components/chat/Personalization.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<script lang="ts">
import { api, type Id } from "@packages/convex";
import { useConvexClient, useQuery } from "convex-svelte";

const { organizationId }: { organizationId: Id<"organizations"> } = $props();

const convex = useConvexClient();

const identity = useQuery(api.users.me, {});
const personalization = useQuery(api.personalization.getPersonalization, {
organizationId: organizationId,
});
let iconURL = $state<string | null>("");
let imageURL = $derived(iconURL || identity.data?.image);
let userName = $derived(
personalization.data?.nickname || identity.data?.name,
);
let changedImage = $state<string>("");
let changedImageFile = $state<File | undefined>();
let changedUserName = $state<string>("");

$effect(() => {
if (userName) {
changedUserName = userName;
}
if (personalization.data) {
new Promise((resolve) => {
resolve(personalization.data?.icon);
})
.then((value) => {
return new Promise((resolve, reject) => {
const storageId = value as Id<"_storage">;
if (storageId) {
resolve(
convex.mutation(api.personalization.getImageUrl, {
storageId: storageId,
}),
);
} else {
reject();
}
});
})
.then((value) => {
const url = value as string;
iconURL = url;
});
}
});

async function save() {
const image = changedImageFile;
changedImage = "";
changedImageFile = undefined;
if (changedUserName?.trim() && !(userName === changedUserName)) {
await convex.mutation(api.personalization.save, {
nickName: changedUserName,
organizationId: organizationId,
});
}
if (image) {
const postUrl = await convex.mutation(
api.personalization.generateUploadUrl,
{},
);
const result = await fetch(postUrl, {
method: "POST",
headers: { "Content-Type": image.type },
body: image,
});

const { storageId } = await result.json();

await convex.mutation(api.personalization.saveImage, {
icon: storageId,
organizationId: organizationId,
});
}
}
</script>

<h2 class="py-2 text-center text-lg font-semibold">アイコンの変更</h2>
<div class="flex justify-around">
<div class="w-32 flex-col">
<p class="mb-2 text-center">変更前</p>
<img src={imageURL} alt="googleアイコン" class="w-32" />
</div>
{#if changedImage}
<div class="w-32 flex-col">
<p class="mb-2 text-center">変更後</p>
<img src={changedImage} alt="変更後" class="w-32" />
</div>
{/if}
</div>
<input
type="file"
class="file:bg-primary file:text-primary-content
text-sm
text-gray-500 file:mr-4 file:ml-2
file:rounded file:border-0
file:px-4
file:py-2 file:font-semibold"
accept=".jpg, .png"
onchange={(event) => {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
if (file) {
changedImage = URL.createObjectURL(file);
changedImageFile = file;
}
}}
/>
<h2 class="py-2 text-center text-lg font-semibold">名前の変更</h2>
<div class="flex justify-around">
<div>
<h3 class="text-center text-base">変更前</h3>
<h4 class="text-lg">{userName}</h4>
</div>
<div>
<h3 class="text-center text-base">変更後</h3>
<input
type="text"
placeholder="ユーザー名"
class="input input-primary w-full"
bind:value={changedUserName}
/>
</div>
</div>

<button class="btn btn-primary mt-auto mr-2 mb-2 ml-auto w-16" onclick={save}
>保存</button
>
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@
{#if organizations.data && organizations.data.length === 0}
<div class="py-8 text-center">
<p class="text-base-content/60 mb-4">参加している組織がありません</p>
<a href="/organizations/create" class="btn btn-primary">
新しい組織を作成
</a>
<a href="/orgs/new" class="btn btn-primary"> 新しい組織を作成 </a>
</div>
{/if}
</div>
Expand Down
5 changes: 4 additions & 1 deletion packages/client/src/routes/orgs/[orgId]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@
const orgId = $derived(page.params.orgId as Id<"organizations">);
</script>

<ChatApp organizationId={orgId} channelId={undefined} />
<ChatApp
organizationId={orgId}
screenMode={{ type: "chat", selectedChannelId: undefined }}
/>
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@

<ChatApp
organizationId={orgId as Id<"organizations">}
channelId={channelId as Id<"channels">}
screenMode={{ type: "chat", selectedChannelId: channelId as Id<"channels"> }}
/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script lang="ts">
import { type Id } from "@packages/convex";
import { page } from "$app/state";
import ChatApp from "~/components/app/ChatApp.svelte";

const { orgId } = $derived(page.params);
</script>

<ChatApp
organizationId={orgId as Id<"organizations">}
screenMode={{ type: "personalization", selectedChannelId: undefined }}
/>
Loading