ChannelDeck working prototype
This commit is contained in:
commit
54fe9f17bf
13 changed files with 241 additions and 0 deletions
66
ChannelDeckStore.tsx
Normal file
66
ChannelDeckStore.tsx
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import { proxyLazyWebpack, findByProps, findByPropsLazy } from "@webpack";
|
||||||
|
import { Flux, FluxDispatcher, PopoutActions, PopoutWindowStore, SnowflakeUtils } from "@webpack/common";
|
||||||
|
import DeckPopout from "./components/DeckPopout";
|
||||||
|
|
||||||
|
export interface ChannelDeck {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
color?: number;
|
||||||
|
columns: DeckColumn[];
|
||||||
|
open: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeckColumn {
|
||||||
|
channelId: string,
|
||||||
|
width: `${number}px` | `${number}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't wanna run before Flux and Dispatcher are ready!
|
||||||
|
export const ChannelDeckStore = proxyLazyWebpack(() => {
|
||||||
|
class ChannelDeckStore extends Flux.Store {
|
||||||
|
|
||||||
|
public _decks = new Map<string, ChannelDeck>();
|
||||||
|
|
||||||
|
public windowKeyPrefix = "DISCORD_CHANNELDECK_DECK_";
|
||||||
|
|
||||||
|
public createDeck(deckState: Partial<Omit<ChannelDeck, "id">>) {
|
||||||
|
const deck = {
|
||||||
|
id: SnowflakeUtils.fromTimestamp(Date.now()),
|
||||||
|
name: "",
|
||||||
|
columns: [],
|
||||||
|
open: false,
|
||||||
|
...deckState
|
||||||
|
};
|
||||||
|
this._decks.set(deck.id, deck);
|
||||||
|
this.updateDeck(deck.id);
|
||||||
|
return deck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDeck(id: string) {
|
||||||
|
return this._decks.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateDeck(id: string) {
|
||||||
|
const deck = this.getDeck(id);
|
||||||
|
if (deck?.open && !PopoutWindowStore.getWindowKeys().includes(this.windowKeyPrefix + id))
|
||||||
|
PopoutActions.open(
|
||||||
|
this.windowKeyPrefix + id,
|
||||||
|
(key) => <DeckPopout deckId={id} windowKey={key} />, {
|
||||||
|
defaultWidth: 1200,
|
||||||
|
defaultHeight: 960
|
||||||
|
});
|
||||||
|
if (!deck?.open && PopoutWindowStore.getWindowKeys().includes(this.windowKeyPrefix + id))
|
||||||
|
this.getDeckWindow(id).close();
|
||||||
|
this.emitChange();
|
||||||
|
};
|
||||||
|
|
||||||
|
public getDeckWindow(id: string) {
|
||||||
|
return PopoutWindowStore.getWindow(this.windowKeyPrefix + id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = new ChannelDeckStore(FluxDispatcher, {
|
||||||
|
});
|
||||||
|
|
||||||
|
return store;
|
||||||
|
});;
|
5
components/DeckColumn.css
Normal file
5
components/DeckColumn.css
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.vc-channelDeck-column {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
23
components/DeckColumn.tsx
Normal file
23
components/DeckColumn.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
|
import "./DeckColumn.css";
|
||||||
|
|
||||||
|
import { cl } from "./util";
|
||||||
|
import { DeckColumn } from "../ChannelDeckStore";
|
||||||
|
import { ChannelStore, GuildStore } from "@webpack/common";
|
||||||
|
|
||||||
|
const Chat = findComponentByCodeLazy("filterAfterTimestamp:", "chatInputType");
|
||||||
|
const ChatInputTypes = findByPropsLazy("FORM", "NORMAL");
|
||||||
|
|
||||||
|
export default function DeckColumn({ column }: { column: DeckColumn; }) {
|
||||||
|
const channel = ChannelStore.getChannel(column.channelId);
|
||||||
|
const guild = GuildStore.getGuild(channel.guild_id);
|
||||||
|
return <div className={cl("column")} style={{
|
||||||
|
width: column.width
|
||||||
|
}}>
|
||||||
|
<Chat
|
||||||
|
channel={channel}
|
||||||
|
guild={guild}
|
||||||
|
chatInputType={ChatInputTypes.SIDEBAR}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
};
|
4
components/DeckContent.css
Normal file
4
components/DeckContent.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.vc-channelDeck-content {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
11
components/DeckContent.tsx
Normal file
11
components/DeckContent.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import "./DeckContent.css";
|
||||||
|
|
||||||
|
import { cl, useDeck } from "./util";
|
||||||
|
import DeckColumn from "./DeckColumn";
|
||||||
|
|
||||||
|
export default function DeckContent() {
|
||||||
|
const deck = useDeck();
|
||||||
|
return <div className={cl("content")}>
|
||||||
|
{deck?.columns.map(column => <DeckColumn key={column.channelId} column={column} />)}
|
||||||
|
</div>;
|
||||||
|
};
|
23
components/DeckPopout.tsx
Normal file
23
components/DeckPopout.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
|
import { useStateFromStores } from "@webpack/common";
|
||||||
|
import { ChannelDeckStore } from "../ChannelDeckStore";
|
||||||
|
import DeckView from "./DeckView";
|
||||||
|
import { DeckContext } from "./util";
|
||||||
|
|
||||||
|
const PopoutWindow = findComponentByCodeLazy("Missing guestWindow reference");
|
||||||
|
|
||||||
|
export default function DeckPopout({ deckId, windowKey }: { deckId: string; windowKey?: string; }) {
|
||||||
|
// Copy from an unexported function of the one they use in the experiment
|
||||||
|
// right click a channel and search withTitleBar:!0,windowKey
|
||||||
|
const deck = useStateFromStores([ChannelDeckStore], () => ChannelDeckStore.getDeck(deckId));
|
||||||
|
|
||||||
|
return <PopoutWindow
|
||||||
|
withTitleBar
|
||||||
|
windowKey={windowKey}
|
||||||
|
title={deck?.name}
|
||||||
|
>
|
||||||
|
<DeckContext.Provider value={deck}>
|
||||||
|
<DeckView />
|
||||||
|
</DeckContext.Provider>
|
||||||
|
</PopoutWindow>;
|
||||||
|
};
|
16
components/DeckSidebar.css
Normal file
16
components/DeckSidebar.css
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
.vc-channelDeck-sidebar {
|
||||||
|
display: flex;
|
||||||
|
width: 40px;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--bg-overlay-5, var(--background-secondary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-channelDeck-sidebar[data-tab] {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-channelDeck-sidebar-items {
|
||||||
|
display: flex;
|
||||||
|
width: 40px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
20
components/DeckSidebar.tsx
Normal file
20
components/DeckSidebar.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { useState } from "@webpack/common";
|
||||||
|
import "./DeckSidebar.css";
|
||||||
|
|
||||||
|
import { cl } from "./util";
|
||||||
|
|
||||||
|
enum SidebarTabs {
|
||||||
|
ADD_CHANNEL = "add_channel",
|
||||||
|
DECKS = "decks",
|
||||||
|
CONFIGURE = "configure"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeckSidebar() {
|
||||||
|
const [tab, setTab] = useState<SidebarTabs | null>(null);
|
||||||
|
const toggleTab = (value) => setTab((current) => current === value ? null : value);
|
||||||
|
return <div className={cl("sidebar")} data-tab={tab}>
|
||||||
|
<div className={cl("sidebar-items")}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
5
components/DeckView.css
Normal file
5
components/DeckView.css
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.vc-channelDeck-view {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
12
components/DeckView.tsx
Normal file
12
components/DeckView.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import "./DeckView.css";
|
||||||
|
|
||||||
|
import DeckSidebar from "./DeckSidebar";
|
||||||
|
import { cl } from "./util";
|
||||||
|
import DeckContent from "./DeckContent";
|
||||||
|
|
||||||
|
export default function DeckView() {
|
||||||
|
return <div className={cl("view")}>
|
||||||
|
<DeckSidebar />
|
||||||
|
<DeckContent />
|
||||||
|
</div>;
|
||||||
|
};
|
10
components/util.ts
Normal file
10
components/util.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import { ChannelDeck } from "../ChannelDeckStore";
|
||||||
|
import { React } from "@webpack/common";
|
||||||
|
import { proxyLazy } from "@utils/index";
|
||||||
|
|
||||||
|
export const cl = classNameFactory("vc-channelDeck-");
|
||||||
|
|
||||||
|
export const DeckContext = proxyLazy(() => React.createContext<ChannelDeck | undefined>(undefined));
|
||||||
|
|
||||||
|
export const useDeck = () => React.useContext(DeckContext);
|
16
index.tsx
Normal file
16
index.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import showUnsupportedMessage from "./unsupportedMessage";
|
||||||
|
import { ChannelDeckStore } from "./ChannelDeckStore";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ChannelDeck",
|
||||||
|
description: 'Multi channel "deck" popout windows',
|
||||||
|
authors: [Devs.Sqaaakoi],
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (!Vencord?.Api?.Styles?.createStyle) return showUnsupportedMessage();
|
||||||
|
},
|
||||||
|
|
||||||
|
ChannelDeckStore
|
||||||
|
});
|
30
unsupportedMessage.tsx
Normal file
30
unsupportedMessage.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { showNotice } from "@api/Notices";
|
||||||
|
import { Alerts, Forms } from "@webpack/common";
|
||||||
|
|
||||||
|
export default function showUnsupportedMessage() {
|
||||||
|
showNotice("ChannelDeck requires Unified Styles API", "Details", () => {
|
||||||
|
Alerts.show({
|
||||||
|
title: "ChannelDeck",
|
||||||
|
body: <div>
|
||||||
|
<Forms.FormText>
|
||||||
|
Your version of Vencord does not support injecting styles into popout windows.
|
||||||
|
ChannelDeck opens in popout windows, and will look broken.
|
||||||
|
<br /><br />
|
||||||
|
You can resolve this issue by adding the Unified Styles API patch to your Vencord installation.
|
||||||
|
You will need to manually merge the PR into your current Vencord branch.
|
||||||
|
Detailed instructions are not documented here.
|
||||||
|
If you cannot do this, wait for the API to be officially supported in Vencord.
|
||||||
|
</Forms.FormText>
|
||||||
|
<br />
|
||||||
|
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
|
||||||
|
Disable the plugin to stop this notice from appearing again.
|
||||||
|
</Forms.FormText>
|
||||||
|
</div>,
|
||||||
|
confirmText: "View PR",
|
||||||
|
cancelText: "Dismiss",
|
||||||
|
onConfirm() {
|
||||||
|
open("https://github.com/Vendicated/Vencord/pull/3153", "_blank");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue