Compare commits

...

7 commits

Author SHA1 Message Date
anatawa12
9a1044df51
Merge pull request #2941 from nekobako/feature/project-sorting-by-creation-date
プロジェクトの登録順ソート
2026-06-18 22:40:23 +09:00
anatawa12
e71c48141b
chore: Apply localization suggestions from code review
Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com>
2026-06-18 22:34:31 +09:00
nekobako
467434903a docs(changelog-gui): update changelog 2026-06-17 23:20:01 +09:00
nekobako
ba8606d312 chore(gui): adjust project list spacing 2026-06-17 23:20:01 +09:00
nekobako
ed42cdfca4 feat(gui): implement project sorting by creation date 2026-06-17 23:20:01 +09:00
anatawa12
dbd479fbc9
Merge pull request #2976 from vrc-get/dependabot/cargo/uuid-1.23.3
chore(deps): bump uuid from 1.23.2 to 1.23.3
2026-06-15 19:44:33 +09:00
dependabot[bot]
da419a0374
chore(deps): bump uuid from 1.23.2 to 1.23.3
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.23.2 to 1.23.3.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.2...v1.23.3)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-15 07:43:41 +00:00
9 changed files with 112 additions and 24 deletions

View file

@ -8,6 +8,7 @@ The format is based on [Keep a Changelog].
## [Unreleased]
### Added
- Implement project sorting by creation date `#2941`
### Changed

4
Cargo.lock generated
View file

@ -6152,9 +6152,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.23.2"
version = "1.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7"
dependencies = [
"getrandom 0.4.2",
"js-sys",

View file

@ -29,7 +29,11 @@ import {
} from "@/components/ui/tooltip";
import type { TauriProject } from "@/lib/bindings";
import { commands } from "@/lib/bindings";
import { dateToString, formatDateOffset } from "@/lib/dateToString";
import {
dateToString,
dayToString,
formatDateOffset,
} from "@/lib/dateToString";
import { openSingleDialog } from "@/lib/dialog";
import { tc } from "@/lib/i18n";
import { toastThrownError } from "@/lib/toast";
@ -45,7 +49,7 @@ export function ProjectGridItem({
const typeIconClass = "w-5 h-5";
const { projectTypeKind, displayType, isLegacy, lastModified } =
const { projectTypeKind, displayType, isLegacy, createdAt, lastModified } =
getProjectDisplayInfo(project);
const removed = !project.is_exists;
@ -165,22 +169,44 @@ export function ProjectGridItem({
</div>
</div>
<div className="text-xs text-muted-foreground">
{tc("general:last modified")}:{" "}
<Tooltip>
<TooltipTrigger>
<time dateTime={lastModified.toISOString()}>
<time className="font-normal">
{formatDateOffset(project.last_modified)}
<div className="flex flex-row gap-1">
<div className="text-xs text-muted-foreground">
{tc("general:created at")}:{" "}
<Tooltip>
<TooltipTrigger>
<time dateTime={createdAt.toISOString()}>
<time className="font-normal">
{dayToString(project.created_at)}
</time>
</time>
</time>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{dateToString(project.last_modified)}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{dateToString(project.created_at)}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
<p className="text-xs text-muted-foreground">/</p>
<div className="text-xs text-muted-foreground">
{tc("general:last modified")}:{" "}
<Tooltip>
<TooltipTrigger>
<time dateTime={lastModified.toISOString()}>
<time className="font-normal">
{formatDateOffset(project.last_modified)}
</time>
</time>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{dateToString(project.last_modified)}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
</div>
<div className="mt-2 flex flex-wrap gap-2 justify-end compact:gap-1">

View file

@ -29,7 +29,11 @@ import {
import { assertNever } from "@/lib/assert-never";
import type { TauriProject, TauriProjectType } from "@/lib/bindings";
import { commands } from "@/lib/bindings";
import { dateToString, formatDateOffset } from "@/lib/dateToString";
import {
dateToString,
dayToString,
formatDateOffset,
} from "@/lib/dateToString";
import { type DialogContext, openSingleDialog, showDialog } from "@/lib/dialog";
import { tc, tt } from "@/lib/i18n";
import { router } from "@/lib/main";
@ -78,7 +82,7 @@ export function ProjectRow({
const noGrowCellClass = `${cellClass} w-1`;
const typeIconClass = "w-5 h-5";
const { projectTypeKind, displayType, isLegacy, lastModified } =
const { projectTypeKind, displayType, isLegacy, createdAt, lastModified } =
getProjectDisplayInfo(project);
const openProjectFolder = () =>
@ -105,7 +109,7 @@ export function ProjectRow({
<tr
className={`group even:bg-secondary/30 ${removed || loading || !(project.is_valid ?? true) ? "opacity-50" : ""}`}
>
<td className={`${cellClass} w-3`}>
<td className={noGrowCellClass}>
<div className={"relative flex"}>
<FavoriteStarToggleButton
favorite={project.favorite}
@ -147,7 +151,7 @@ export function ProjectRow({
</TooltipPortal>
</Tooltip>
</td>
<td className={`${cellClass} w-[8em] min-w-[8em]`}>
<td className={noGrowCellClass}>
<div className="flex flex-row gap-2">
<div className="flex items-center">
{projectTypeKind === "avatars" ? (
@ -171,6 +175,22 @@ export function ProjectRow({
<td className={noGrowCellClass}>
<p className="font-normal">{project.unity}</p>
</td>
<td className={noGrowCellClass}>
<Tooltip>
<TooltipTrigger>
<time dateTime={createdAt.toISOString()}>
<time className="font-normal">
{dayToString(project.created_at)}
</time>
</time>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{dateToString(project.created_at)}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</td>
<td className={noGrowCellClass}>
<Tooltip>
<TooltipTrigger>
@ -501,12 +521,14 @@ export function getProjectDisplayInfo(project: TauriProject) {
const projectTypeKind = ProjectDisplayType[project.project_type] ?? "unknown";
const displayType = tc(`projects:type:${projectTypeKind}`);
const isLegacy = LegacyProjectTypes.includes(project.project_type);
const createdAt = new Date(project.created_at);
const lastModified = new Date(project.last_modified);
return {
projectTypeKind,
displayType,
isLegacy,
createdAt,
lastModified,
};
}

View file

@ -30,6 +30,7 @@ const sortingOptions: { key: SimpleSorting; label: string }[] = [
{ key: "name", label: "general:name" },
{ key: "type", label: "projects:type" },
{ key: "unity", label: "projects:unity" },
{ key: "createdAt", label: "general:created at" },
{ key: "lastModified", label: "general:last modified" },
];

View file

@ -11,7 +11,13 @@ import { toastThrownError } from "@/lib/toast";
import { compareUnityVersionString } from "@/lib/version";
import { ProjectRow } from "./-project-row";
export const sortings = ["lastModified", "name", "unity", "type"] as const;
export const sortings = [
"createdAt",
"lastModified",
"name",
"unity",
"type",
] as const;
type SimpleSorting = (typeof sortings)[number];
type Sorting = SimpleSorting | `${SimpleSorting}Reversed`;
@ -158,6 +164,18 @@ export function ProjectsTableCard({
</small>
</button>
</th>
<th className={`${thClass} ${headerBg("createdAt")}`}>
<button
type="button"
className={"flex w-full project-table-button"}
onClick={() => setSorting("createdAt")}
>
{icon("createdAt")}
<small className="font-normal leading-none">
{tc("general:created at")}
</small>
</button>
</th>
<th className={`${thClass} ${headerBg("lastModified")}`}>
<button
type="button"
@ -194,6 +212,12 @@ export function sortSearchProjects(
searched.sort((a, b) => b.last_modified - a.last_modified);
switch (sorting) {
case "createdAt":
searched.sort((a, b) => b.created_at - a.created_at);
break;
case "createdAtReversed":
searched.sort((a, b) => a.created_at - b.created_at);
break;
case "lastModified":
searched.sort((a, b) => b.last_modified - a.last_modified);
break;

View file

@ -1,6 +1,16 @@
import type React from "react";
import { tc } from "@/lib/i18n";
export function dayToString(dateIn: Date | number | string) {
const date = typeof dateIn !== "object" ? new Date(dateIn) : dateIn;
const year = date.getFullYear().toString().padStart(4, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
return `${year}-${month}-${day}`;
}
export function dateToString(dateIn: Date | number | string) {
const date = typeof dateIn !== "object" ? new Date(dateIn) : dateIn;

View file

@ -37,6 +37,8 @@
"general:error:failed to create dir": "Failed to create directory (missing permission?): {{err}}",
"general:error:failed to create dir missing drive": "Failed to create directory. You may forget to connect the drive.",
"general:created at": "Added",
"general:last modified": "Last Modified",
"general:last modified:moments": "Moments ago",
"general:last modified:minutes_one": "{{count}} minute ago",

View file

@ -37,6 +37,8 @@
"general:error:failed to create dir": "ディレクトリの作成に失敗しました: {{err}}",
"general:error:failed to create dir missing drive": "ディレクトリの作成に失敗しました。保存先のドライブの接続を忘れているかもしれません。",
"general:created at": "追加日",
"general:last modified": "最終更新日",
"general:last modified:moments": "たった今",
"general:last modified:minutes": "{{count}}分前",