From 2436cfdb6cc3e3667d5743831ce9fc9e169e692b Mon Sep 17 00:00:00 2001 From: Poseidon281 Date: Sun, 26 May 2024 17:12:49 +0200 Subject: [PATCH 1/9] First Commit --- bootstrap/app.php | 2 +- package.json | 2 + .../dashboard/DashboardContainer.tsx | 216 ++++++++++++------ yarn.lock | 87 ++++++- 4 files changed, 234 insertions(+), 73 deletions(-) diff --git a/bootstrap/app.php b/bootstrap/app.php index 21b41272f5..94e5005983 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -9,7 +9,7 @@ \Prologue\Alerts\AlertsServiceProvider::class, ]) ->withRouting( - web: __DIR__.'/../routes/web.php', + web: __DIR__.'/../routes/base.php', // api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', // channels: __DIR__.'/../routes/channels.php', diff --git a/package.json b/package.json index ebb3266be3..9e70f6b32d 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "i18next-multiload-backend-adapter": "^1.0.0", "qrcode.react": "^1.0.1", "react": "^16.14.0", + "react-beautiful-dnd": "^13.1.1", "react-chartjs-2": "^4.2.0", "react-dom": "npm:@hot-loader/react-dom", "react-fast-compare": "^3.2.0", @@ -79,6 +80,7 @@ "@types/node": "^14.11.10", "@types/qrcode.react": "^1.0.1", "@types/react": "^16.14.0", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-copy-to-clipboard": "^4.3.0", "@types/react-dom": "^16.9.16", "@types/react-redux": "^7.1.1", diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 7f20383a0c..5f2dd3aad2 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -11,77 +11,153 @@ import Switch from '@/components/elements/Switch'; import tw from 'twin.macro'; import useSWR from 'swr'; import { PaginatedResult } from '@/api/http'; -import Pagination from '@/components/elements/Pagination'; import { useLocation } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; +import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; export default () => { - const { t } = useTranslation('dashboard/index'); - - const { search } = useLocation(); - const defaultPage = Number(new URLSearchParams(search).get('page') || '1'); - - const [page, setPage] = useState(!isNaN(defaultPage) && defaultPage > 0 ? defaultPage : 1); - const { clearFlashes, clearAndAddHttpError } = useFlash(); - const uuid = useStoreState((state) => state.user.data!.uuid); - const rootAdmin = useStoreState((state) => state.user.data!.rootAdmin); - const [showOnlyAdmin, setShowOnlyAdmin] = usePersistedState(`${uuid}:show_all_servers`, false); - - const { data: servers, error } = useSWR>( - ['/api/client/servers', showOnlyAdmin && rootAdmin, page], - () => getServers({ page, type: showOnlyAdmin && rootAdmin ? 'admin' : undefined }) - ); - - useEffect(() => { - if (!servers) return; - if (servers.pagination.currentPage > 1 && !servers.items.length) { - setPage(1); - } - }, [servers?.pagination.currentPage]); - - useEffect(() => { - // Don't use react-router to handle changing this part of the URL, otherwise it - // triggers a needless re-render. We just want to track this in the URL incase the - // user refreshes the page. - window.history.replaceState(null, document.title, `/${page <= 1 ? '' : `?page=${page}`}`); - }, [page]); - - useEffect(() => { - if (error) clearAndAddHttpError({ key: 'dashboard', error }); - if (!error) clearFlashes('dashboard'); - }, [error]); - - return ( - - {rootAdmin && ( -
-

- {showOnlyAdmin ? t('showing-others-servers') : t('showing-your-servers')} -

- setShowOnlyAdmin((s) => !s)} - /> -
- )} - {!servers ? ( - - ) : ( - - {({ items }) => - items.length > 0 ? ( - items.map((server, index) => ( - 0 ? tw`mt-2` : undefined} /> - )) - ) : ( -

- {showOnlyAdmin ? t('no-other-servers') : t('no-servers-associated')} -

- ) - } -
- )} -
- ); + const [allowDragDrop, setAllowDragDrop] = useState(true); + const { search } = useLocation(); + const defaultPage = Number(new URLSearchParams(search).get('page') || '1'); + + const [page, setPage] = useState(!isNaN(defaultPage) && defaultPage > 0 ? defaultPage : 1); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const uuid = useStoreState((state) => state.user.data!.uuid); + const rootAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const [showOnlyAdmin, setShowOnlyAdmin] = usePersistedState(`${uuid}:show_all_servers`, false); + + const { data: servers, error } = useSWR>( + ['/api/client/servers', showOnlyAdmin && rootAdmin, page], + () => getServers({ page, type: showOnlyAdmin && rootAdmin ? 'admin' : undefined }) + ); + + useEffect(() => { + if (!servers) return; + if (servers.pagination.currentPage > 1 && !servers.items.length) { + setPage(1); + } + }, [servers?.pagination.currentPage]); + + useEffect(() => { + window.history.replaceState(null, document.title, `/${page <= 1 ? '' : `?page=${page}`}`); + }, [page]); + + useEffect(() => { + if (error) clearAndAddHttpError({ key: 'dashboard', error }); + if (!error) clearFlashes('dashboard'); + }, [error]); + +const [adminServersOrder, setAdminServersOrder] = useState([]); +const [nonAdminServersOrder, setNonAdminServersOrder] = useState([]); + + +useEffect(() => { + const adminSavedOrder = localStorage.getItem(`admin:serversOrder:${uuid}`); + const nonAdminSavedOrder = localStorage.getItem(`nonadmin:serversOrder:${uuid}`); + setAdminServersOrder(adminSavedOrder ? JSON.parse(adminSavedOrder) : []); + setNonAdminServersOrder(nonAdminSavedOrder ? JSON.parse(nonAdminSavedOrder) : []); +}, [uuid]); + + +useEffect(() => { + if (!servers) return; + const newOrder = servers.items.map((server) => server.uuid); + + if (showOnlyAdmin) { + if (!localStorage.getItem(`admin:serversOrder:${uuid}`) || newOrder.length !== adminServersOrder.length) { + setAdminServersOrder(newOrder); + localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(newOrder)); + } + } else { + if (!localStorage.getItem(`nonadmin:serversOrder:${uuid}`) || newOrder.length !== nonAdminServersOrder.length) { + setNonAdminServersOrder(newOrder); + localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(newOrder)); + } + } +}, [servers, uuid, showOnlyAdmin]); + + +useEffect(() => { + localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(adminServersOrder)); + localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(nonAdminServersOrder)); +}, [adminServersOrder, nonAdminServersOrder]); + + +const onDragEnd = (result: DropResult ) => { + const { source, destination } = result; + + if (!destination) { + return; + } + + const newOrder = Array.from(showOnlyAdmin ? adminServersOrder : nonAdminServersOrder); + newOrder.splice(source.index, 1); + newOrder.splice(destination.index, 0, (showOnlyAdmin ? adminServersOrder : nonAdminServersOrder)[source.index]); + + if (showOnlyAdmin) { + setAdminServersOrder(newOrder); + localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(newOrder)); + } else { + setNonAdminServersOrder(newOrder); + localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(newOrder)); + } }; + + +const serversOrder = showOnlyAdmin ? adminServersOrder : nonAdminServersOrder; + + return ( + + {rootAdmin && ( +
+

+ {showOnlyAdmin ? "Showing others' servers" : 'Showing your servers'} +

+ setShowOnlyAdmin(s => !s)} /> +
+ )} +
+

+ {allowDragDrop ? "Sorting Mode Disabled" : 'Sorting Mode Enabled'} +

+ setAllowDragDrop(!allowDragDrop)} + /> +
+ {!servers ? ( + + ) : ( + + + {(provided) => ( +
+ {serversOrder.map((serverUuid, index) => { + const server = servers.items.find((s) => s.uuid === serverUuid); + if (!server) { + console.warn(`Server with uuid ${serverUuid} not found`); + return null; + } + return ( + + {(provided) => ( +
+ 0 ? tw`mt-2` : undefined} /> +
+ )} +
+ ); + })} + {provided.placeholder} +
+ )} +
+
+ )} +
+ ); +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ff431b75ed..4b2f0938bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1007,6 +1007,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.15.4": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.6.tgz#5b76eb89ad45e2e4a0a8db54c456251469a3358e" + integrity sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.10.4", "@babel/template@^7.14.5", "@babel/template@^7.16.7", "@babel/template@^7.3.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -1846,6 +1853,13 @@ dependencies: "@types/react" "*" +"@types/react-beautiful-dnd@^13.1.8": + version "13.1.8" + resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz#f52d3ea07e1e19159d6c3c4a48c8da3d855e60b4" + integrity sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ== + dependencies: + "@types/react" "*" + "@types/react-copy-to-clipboard@^4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.0.tgz#8e07becb4f11cfced4bd36038cb5bdf5c2658be5" @@ -1876,6 +1890,16 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-redux@^7.1.20": + version "7.1.33" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.33.tgz#53c5564f03f1ded90904e3c90f77e4bd4dc20b15" + integrity sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react-router-dom@^5.1.3": version "5.1.3" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" @@ -3457,6 +3481,13 @@ css-blank-pseudo@^3.0.3: dependencies: postcss-selector-parser "^6.0.9" +css-box-model@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" + integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== + dependencies: + tiny-invariant "^1.0.6" + css-color-keywords@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" @@ -4967,7 +4998,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6240,6 +6271,11 @@ memfs@^3.1.2: dependencies: fs-monkey "^1.0.3" +memoize-one@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + memoizerific@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" @@ -7556,6 +7592,11 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +raf-schd@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" + integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -7583,6 +7624,19 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +react-beautiful-dnd@^13.1.1: + version "13.1.1" + resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2" + integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ== + dependencies: + "@babel/runtime" "^7.9.2" + css-box-model "^1.2.0" + memoize-one "^5.1.1" + raf-schd "^4.0.2" + react-redux "^7.2.0" + redux "^4.0.4" + use-memo-one "^1.1.1" + react-chartjs-2@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-4.2.0.tgz#bc5693a8b161f125301cf28ab0fe980d7dce54aa" @@ -7632,7 +7686,7 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1: +react-is@^17.0.1, react-is@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== @@ -7646,6 +7700,18 @@ react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" +react-redux@^7.2.0: + version "7.2.9" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" + integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/react-redux" "^7.1.20" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^17.0.2" + react-router-dom@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" @@ -7784,6 +7850,13 @@ redux@^4.0.0, redux@^4.0.5: loose-envify "^1.4.0" symbol-observable "^1.2.0" +redux@^4.0.4: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -7805,6 +7878,11 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regenerator-transform@^0.14.2: version "0.14.5" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" @@ -8875,6 +8953,11 @@ tiny-invariant@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.4.tgz#346b5415fd93cb696b0c4e8a96697ff590f92463" +tiny-invariant@^1.0.6: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tiny-warning@^1.0.0, tiny-warning@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28" From 2adca9cdd6f74d01815c663ffddc381cd7956a4d Mon Sep 17 00:00:00 2001 From: Poseidon281 Date: Sun, 26 May 2024 17:26:04 +0200 Subject: [PATCH 2/9] Added translations --- lang/en/dashboard/index.php | 3 + .../dashboard/DashboardContainer.tsx | 96 ++++++++++--------- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/lang/en/dashboard/index.php b/lang/en/dashboard/index.php index 1dff4f01a7..a7ec4709e3 100644 --- a/lang/en/dashboard/index.php +++ b/lang/en/dashboard/index.php @@ -7,6 +7,9 @@ 'no-other-servers' => 'There are no other servers to display.', 'no-servers-associated' => 'There are no servers associated with your account.', + 'sorting_disabled' => 'Sorting mode disabled', + 'sorting_enabled' => 'Sorting mode enabled', + 'content_tabs' => 'Content tabs', 'overview' => 'Overview', 'heading' => 'Welcome to Pelican!', diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 5f2dd3aad2..950b6a13e2 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -12,10 +12,12 @@ import tw from 'twin.macro'; import useSWR from 'swr'; import { PaginatedResult } from '@/api/http'; import { useLocation } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; export default () => { const [allowDragDrop, setAllowDragDrop] = useState(true); + const { t } = useTranslation('dashboard/index'); const { search } = useLocation(); const defaultPage = Number(new URLSearchParams(search).get('page') || '1'); @@ -46,78 +48,78 @@ export default () => { if (!error) clearFlashes('dashboard'); }, [error]); -const [adminServersOrder, setAdminServersOrder] = useState([]); -const [nonAdminServersOrder, setNonAdminServersOrder] = useState([]); + const [adminServersOrder, setAdminServersOrder] = useState([]); + const [nonAdminServersOrder, setNonAdminServersOrder] = useState([]); -useEffect(() => { - const adminSavedOrder = localStorage.getItem(`admin:serversOrder:${uuid}`); - const nonAdminSavedOrder = localStorage.getItem(`nonadmin:serversOrder:${uuid}`); - setAdminServersOrder(adminSavedOrder ? JSON.parse(adminSavedOrder) : []); - setNonAdminServersOrder(nonAdminSavedOrder ? JSON.parse(nonAdminSavedOrder) : []); -}, [uuid]); - + useEffect(() => { + const adminSavedOrder = localStorage.getItem(`admin:serversOrder:${uuid}`); + const nonAdminSavedOrder = localStorage.getItem(`nonadmin:serversOrder:${uuid}`); + setAdminServersOrder(adminSavedOrder ? JSON.parse(adminSavedOrder) : []); + setNonAdminServersOrder(nonAdminSavedOrder ? JSON.parse(nonAdminSavedOrder) : []); + }, [uuid]); -useEffect(() => { - if (!servers) return; - const newOrder = servers.items.map((server) => server.uuid); - if (showOnlyAdmin) { - if (!localStorage.getItem(`admin:serversOrder:${uuid}`) || newOrder.length !== adminServersOrder.length) { - setAdminServersOrder(newOrder); - localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(newOrder)); - } - } else { - if (!localStorage.getItem(`nonadmin:serversOrder:${uuid}`) || newOrder.length !== nonAdminServersOrder.length) { - setNonAdminServersOrder(newOrder); - localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(newOrder)); + useEffect(() => { + if (!servers) return; + const newOrder = servers.items.map((server) => server.uuid); + + if (showOnlyAdmin) { + if (!localStorage.getItem(`admin:serversOrder:${uuid}`) || newOrder.length !== adminServersOrder.length) { + setAdminServersOrder(newOrder); + localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(newOrder)); + } + } else { + if (!localStorage.getItem(`nonadmin:serversOrder:${uuid}`) || newOrder.length !== nonAdminServersOrder.length) { + setNonAdminServersOrder(newOrder); + localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(newOrder)); + } } - } -}, [servers, uuid, showOnlyAdmin]); + }, [servers, uuid, showOnlyAdmin]); -useEffect(() => { - localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(adminServersOrder)); - localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(nonAdminServersOrder)); -}, [adminServersOrder, nonAdminServersOrder]); + useEffect(() => { + localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(adminServersOrder)); + localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(nonAdminServersOrder)); + }, [adminServersOrder, nonAdminServersOrder]); -const onDragEnd = (result: DropResult ) => { - const { source, destination } = result; + const onDragEnd = (result: DropResult) => { + const { source, destination } = result; - if (!destination) { - return; - } + if (!destination) { + return; + } - const newOrder = Array.from(showOnlyAdmin ? adminServersOrder : nonAdminServersOrder); - newOrder.splice(source.index, 1); - newOrder.splice(destination.index, 0, (showOnlyAdmin ? adminServersOrder : nonAdminServersOrder)[source.index]); + const newOrder = Array.from(showOnlyAdmin ? adminServersOrder : nonAdminServersOrder); + newOrder.splice(source.index, 1); + newOrder.splice(destination.index, 0, (showOnlyAdmin ? adminServersOrder : nonAdminServersOrder)[source.index]); - if (showOnlyAdmin) { - setAdminServersOrder(newOrder); - localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(newOrder)); - } else { - setNonAdminServersOrder(newOrder); - localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(newOrder)); - } -}; + if (showOnlyAdmin) { + setAdminServersOrder(newOrder); + localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(newOrder)); + } else { + setNonAdminServersOrder(newOrder); + localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(newOrder)); + } + }; -const serversOrder = showOnlyAdmin ? adminServersOrder : nonAdminServersOrder; + const serversOrder = showOnlyAdmin ? adminServersOrder : nonAdminServersOrder; return ( {rootAdmin && (

- {showOnlyAdmin ? "Showing others' servers" : 'Showing your servers'} + {showOnlyAdmin ? t('showing-others-servers') : t('showing-your-servers')}

- setShowOnlyAdmin(s => !s)} /> + setShowOnlyAdmin(s => !s)} />
)}

- {allowDragDrop ? "Sorting Mode Disabled" : 'Sorting Mode Enabled'} + {allowDragDrop ? t('sorting_disabled') : t('sorting_enabled')}

Date: Sun, 26 May 2024 18:10:45 +0200 Subject: [PATCH 3/9] Added grouping This commit has been made partly with openai since I'm not good with tsx, so the style may not be quite right and things will have to be changed --- lang/en/dashboard/index.php | 4 + .../dashboard/DashboardContainer.tsx | 376 +++++++++++------- 2 files changed, 234 insertions(+), 146 deletions(-) diff --git a/lang/en/dashboard/index.php b/lang/en/dashboard/index.php index a7ec4709e3..be5e8ac527 100644 --- a/lang/en/dashboard/index.php +++ b/lang/en/dashboard/index.php @@ -9,6 +9,10 @@ 'sorting_disabled' => 'Sorting mode disabled', 'sorting_enabled' => 'Sorting mode enabled', + 'new_group_name' => 'New Group Name', + 'add_group' => 'Add Group', + 'delete_group' => 'Delete Current Group', + 'move_server' => 'Move Server', 'content_tabs' => 'Content tabs', 'overview' => 'Overview', diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 950b6a13e2..97edbfd05a 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -16,150 +16,234 @@ import { useTranslation } from 'react-i18next'; import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; export default () => { - const [allowDragDrop, setAllowDragDrop] = useState(true); - const { t } = useTranslation('dashboard/index'); - const { search } = useLocation(); - const defaultPage = Number(new URLSearchParams(search).get('page') || '1'); - - const [page, setPage] = useState(!isNaN(defaultPage) && defaultPage > 0 ? defaultPage : 1); - const { clearFlashes, clearAndAddHttpError } = useFlash(); - const uuid = useStoreState((state) => state.user.data!.uuid); - const rootAdmin = useStoreState((state) => state.user.data!.rootAdmin); - const [showOnlyAdmin, setShowOnlyAdmin] = usePersistedState(`${uuid}:show_all_servers`, false); - - const { data: servers, error } = useSWR>( - ['/api/client/servers', showOnlyAdmin && rootAdmin, page], - () => getServers({ page, type: showOnlyAdmin && rootAdmin ? 'admin' : undefined }) - ); - - useEffect(() => { - if (!servers) return; - if (servers.pagination.currentPage > 1 && !servers.items.length) { - setPage(1); - } - }, [servers?.pagination.currentPage]); - - useEffect(() => { - window.history.replaceState(null, document.title, `/${page <= 1 ? '' : `?page=${page}`}`); - }, [page]); - - useEffect(() => { - if (error) clearAndAddHttpError({ key: 'dashboard', error }); - if (!error) clearFlashes('dashboard'); - }, [error]); - - const [adminServersOrder, setAdminServersOrder] = useState([]); - const [nonAdminServersOrder, setNonAdminServersOrder] = useState([]); - - - useEffect(() => { - const adminSavedOrder = localStorage.getItem(`admin:serversOrder:${uuid}`); - const nonAdminSavedOrder = localStorage.getItem(`nonadmin:serversOrder:${uuid}`); - setAdminServersOrder(adminSavedOrder ? JSON.parse(adminSavedOrder) : []); - setNonAdminServersOrder(nonAdminSavedOrder ? JSON.parse(nonAdminSavedOrder) : []); - }, [uuid]); - - - useEffect(() => { - if (!servers) return; - const newOrder = servers.items.map((server) => server.uuid); - - if (showOnlyAdmin) { - if (!localStorage.getItem(`admin:serversOrder:${uuid}`) || newOrder.length !== adminServersOrder.length) { - setAdminServersOrder(newOrder); - localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(newOrder)); - } - } else { - if (!localStorage.getItem(`nonadmin:serversOrder:${uuid}`) || newOrder.length !== nonAdminServersOrder.length) { - setNonAdminServersOrder(newOrder); - localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(newOrder)); - } - } - }, [servers, uuid, showOnlyAdmin]); - - - useEffect(() => { - localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(adminServersOrder)); - localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(nonAdminServersOrder)); - }, [adminServersOrder, nonAdminServersOrder]); - - - const onDragEnd = (result: DropResult) => { - const { source, destination } = result; - - if (!destination) { - return; - } - - const newOrder = Array.from(showOnlyAdmin ? adminServersOrder : nonAdminServersOrder); - newOrder.splice(source.index, 1); - newOrder.splice(destination.index, 0, (showOnlyAdmin ? adminServersOrder : nonAdminServersOrder)[source.index]); - - if (showOnlyAdmin) { - setAdminServersOrder(newOrder); - localStorage.setItem(`admin:serversOrder:${uuid}`, JSON.stringify(newOrder)); - } else { - setNonAdminServersOrder(newOrder); - localStorage.setItem(`nonadmin:serversOrder:${uuid}`, JSON.stringify(newOrder)); - } - }; - - - const serversOrder = showOnlyAdmin ? adminServersOrder : nonAdminServersOrder; - - return ( - - {rootAdmin && ( -
-

- {showOnlyAdmin ? t('showing-others-servers') : t('showing-your-servers')} -

- setShowOnlyAdmin(s => !s)} /> -
- )} -
-

- {allowDragDrop ? t('sorting_disabled') : t('sorting_enabled')} -

- setAllowDragDrop(!allowDragDrop)} - /> -
- {!servers ? ( - - ) : ( - - - {(provided) => ( -
- {serversOrder.map((serverUuid, index) => { - const server = servers.items.find((s) => s.uuid === serverUuid); - if (!server) { - console.warn(`Server with uuid ${serverUuid} not found`); - return null; - } - return ( - - {(provided) => ( -
- 0 ? tw`mt-2` : undefined} /> -
- )} -
- ); - })} - {provided.placeholder} -
+ const [allowDragDrop, setAllowDragDrop] = useState(true); + const { t } = useTranslation('dashboard/index'); + const { search } = useLocation(); + const defaultPage = Number(new URLSearchParams(search).get('page') || '1'); + + const [page, setPage] = useState(!isNaN(defaultPage) && defaultPage > 0 ? defaultPage : 1); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const uuid = useStoreState((state) => state.user.data!.uuid); + const rootAdmin = useStoreState((state) => state.user.data!.rootAdmin); + const [showOnlyAdmin, setShowOnlyAdmin] = usePersistedState(`${uuid}:show_all_servers`, false); + + const { data: servers, error } = useSWR>( + ['/api/client/servers', showOnlyAdmin && rootAdmin, page], + () => getServers({ page, type: showOnlyAdmin && rootAdmin ? 'admin' : undefined }) + ); + + const [groups, setGroups] = useState<{ [key: string]: string[] }>({ default: [] }); + const [currentGroup, setCurrentGroup] = useState('default'); + const [newGroupName, setNewGroupName] = useState(''); + const [showMoveOptions, setShowMoveOptions] = useState<{ [key: string]: boolean }>({}); + + useEffect(() => { + if (servers) { + const allServers = servers.items.map((server) => server.uuid); + setGroups((prevGroups) => { + if (!prevGroups['default'].length) { + return { ...prevGroups, default: allServers }; + } + return prevGroups; + }); + } + }, [servers]); + + useEffect(() => { + if (servers?.pagination?.currentPage && servers.pagination.currentPage > 1 && servers.items?.length === 0) { + setPage(1); + } + }, [servers?.pagination?.currentPage]); + + useEffect(() => { + window.history.replaceState(null, document.title, `/${page <= 1 ? '' : `?page=${page}`}`); + }, [page]); + + useEffect(() => { + if (error) clearAndAddHttpError({ key: 'dashboard', error }); + if (!error) clearFlashes('dashboard'); + }, [error]); + + useEffect(() => { + const savedGroups = localStorage.getItem(`groups:${uuid}`); + if (savedGroups) { + setGroups(JSON.parse(savedGroups)); + } + }, [uuid]); + + useEffect(() => { + localStorage.setItem(`groups:${uuid}`, JSON.stringify(groups)); + }, [groups]); + + const addGroup = () => { + if (newGroupName && !groups[newGroupName]) { + setGroups({ ...groups, [newGroupName]: [] }); + setNewGroupName(''); + } + }; + + const deleteGroup = (group: string) => { + const updatedGroups = { ...groups }; + delete updatedGroups[group]; + setGroups(updatedGroups); + if (currentGroup === group) { + setCurrentGroup('default'); + } + }; + + const moveServerToGroup = (serverUuid: string, targetGroup: string) => { + if (!groups[targetGroup]) { + console.warn(`Group ${targetGroup} does not exist`); + return; + } + + const updatedGroups = { ...groups }; + const currentGroup = Object.keys(updatedGroups).find((group) => updatedGroups[group].includes(serverUuid)); + if (currentGroup) { + updatedGroups[currentGroup] = updatedGroups[currentGroup].filter((uuid) => uuid !== serverUuid); + } + updatedGroups[targetGroup].push(serverUuid); + setGroups(updatedGroups); + setShowMoveOptions((prevState) => ({ ...prevState, [serverUuid]: false })); + }; + + const onDragEnd = (result: DropResult) => { + const { source, destination } = result; + + if (!destination) { + return; + } + + const newOrder = Array.from(groups[currentGroup]); + newOrder.splice(source.index, 1); + newOrder.splice(destination.index, 0, groups[currentGroup][source.index]); + + setGroups({ ...groups, [currentGroup]: newOrder }); + }; + + const serversOrder = groups[currentGroup]; + + return ( + + {rootAdmin && ( +
+

+ {showOnlyAdmin ? t('showing-others-servers') : t('showing-your-servers')} +

+ setShowOnlyAdmin((s) => !s)} + /> +
)} -
-
- )} -
- ); -}; \ No newline at end of file +
+

+ {allowDragDrop ? t('sorting_disabled') : t('sorting_enabled')} +

+ setAllowDragDrop(!allowDragDrop)} + /> +
+
+
+ + setNewGroupName(e.target.value)} + placeholder={t('new_group_name')} + /> + +
+ {currentGroup !== 'default' && ( + + )} +
+ {!servers ? ( + + ) : ( + + + {(provided) => ( +
+ {serversOrder.map((serverUuid, index) => { + const server = servers.items.find((s) => s.uuid === serverUuid); + if (!server) { + console.warn(`Server with uuid ${serverUuid} not found`); + return null; + } + return ( + + {(provided) => ( +
+ +
+ + {showMoveOptions[server.uuid] && ( + + )} +
+
+ )} +
+ ); + })} + {provided.placeholder} +
+ )} +
+
+ )} + + ); +}; From 2034ee6574725dbdc5ffab223b209d8c8c30413e Mon Sep 17 00:00:00 2001 From: Poseidon281 Date: Sun, 26 May 2024 18:42:33 +0200 Subject: [PATCH 4/9] Made footer better --- resources/scripts/components/dashboard/DashboardContainer.tsx | 2 +- resources/scripts/components/elements/PageContentBlock.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 97edbfd05a..98c3cd4c81 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -202,7 +202,7 @@ export default () => { ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} - css={tw`mt-2 p-4 border border-neutral-600 rounded-lg text-white`} // Verwijder bg-neutral-800 en voeg rounded-lg toe + css={tw`mt-2 p-4 border border-neutral-600 rounded-lg text-white`} >
diff --git a/resources/scripts/components/elements/PageContentBlock.tsx b/resources/scripts/components/elements/PageContentBlock.tsx index 1f62eedb5b..097fa8ab12 100644 --- a/resources/scripts/components/elements/PageContentBlock.tsx +++ b/resources/scripts/components/elements/PageContentBlock.tsx @@ -32,9 +32,9 @@ const PageContentBlock: React.FC = ({ title, showFlashKey target={'_blank'} css={tw`no-underline text-neutral-500 hover:text-neutral-300`} > - Panel + Pelican Panel -  Pelican© 2024 - {new Date().getFullYear()} +  © 2024 - {new Date().getFullYear()}

From 6c5ac6548373b7359cf2ff9125398b15e5aca9cf Mon Sep 17 00:00:00 2001 From: Poseidon281 Date: Mon, 27 May 2024 15:35:38 +0200 Subject: [PATCH 5/9] Style fixes --- .../dashboard/DashboardContainer.tsx | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 98c3cd4c81..e881408c63 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -124,29 +124,31 @@ export default () => { const serversOrder = groups[currentGroup]; return ( - + {rootAdmin && ( -
-

- {showOnlyAdmin ? t('showing-others-servers') : t('showing-your-servers')} -

- setShowOnlyAdmin((s) => !s)} - /> +
+
+

+ {allowDragDrop ? t('sorting_disabled') : t('sorting_enabled')} +

+ setAllowDragDrop(!allowDragDrop)} + /> +
+
+

+ {showOnlyAdmin ? t('showing-others-servers') : t('showing-your-servers')} +

+ setShowOnlyAdmin((s) => !s)} + /> +
)} -
-

- {allowDragDrop ? t('sorting_disabled') : t('sorting_enabled')} -

- setAllowDragDrop(!allowDragDrop)} - /> -
@@ -229,7 +242,7 @@ export default () => { > {Object.keys(groups).map((group) => ( ))} From 4ca475a5f87251e2210182e2be432bcfe379fe54 Mon Sep 17 00:00:00 2001 From: Poseidon281 Date: Fri, 7 Jun 2024 20:20:39 +0200 Subject: [PATCH 9/9] Pint fixes and fix saving bug --- lang/en/dashboard/index.php | 2 +- .../dashboard/DashboardContainer.tsx | 19 +++---------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/lang/en/dashboard/index.php b/lang/en/dashboard/index.php index 320746b1b8..633ead10cb 100644 --- a/lang/en/dashboard/index.php +++ b/lang/en/dashboard/index.php @@ -13,7 +13,7 @@ 'add_group' => 'Add Group', 'delete_group' => 'Delete Current Group', 'move_server' => 'Move Server', - 'all_servers' => "All Servers", + 'all_servers' => 'All Servers', 'content_tabs' => 'Content tabs', 'overview' => 'Overview', diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 9f3f2f20c5..2a625a9e86 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -64,15 +64,6 @@ export default () => { if (!error) clearFlashes('dashboard'); }, [error]); - useEffect(() => { - const storedCurrentGroup = localStorage.getItem(`currentGroup:${uuid}`); - if (storedCurrentGroup && groups[storedCurrentGroup]) { - setCurrentGroup(storedCurrentGroup); - } else { - setCurrentGroup('default'); - } - }, [uuid, groups]); - useEffect(() => { const savedGroups = localStorage.getItem(`groups:${uuid}`); if (savedGroups) { @@ -81,8 +72,8 @@ export default () => { }, [uuid]); useEffect(() => { - localStorage.setItem(`currentGroup:${uuid}`, currentGroup); - }, [uuid, currentGroup]); + localStorage.setItem(`groups:${uuid}`, JSON.stringify(groups)); + }, [groups]); const addGroup = () => { if (newGroupName && !groups[newGroupName]) { @@ -111,11 +102,7 @@ export default () => { if (currentGroup) { updatedGroups[currentGroup] = updatedGroups[currentGroup].filter((uuid) => uuid !== serverUuid); } - if (!updatedGroups[targetGroup].includes(serverUuid)) { - if (currentGroup !== targetGroup) { - updatedGroups[targetGroup].push(serverUuid); - } - } + updatedGroups[targetGroup].push(serverUuid); setGroups(updatedGroups); setShowMoveOptions((prevState) => ({ ...prevState, [serverUuid]: false })); };