109 |
110 | }
--------------------------------------------------------------------------------
/src/components/card/Feature.js:
--------------------------------------------------------------------------------
1 | // Chakra imports
2 | import {Box, Button, ButtonGroup, Flex, Heading, Image, Text} from "@chakra-ui/react";
3 | import {Link} from "react-router-dom";
4 | // Custom components
5 | import Card from "components/card/Card.js";
6 | // Assets
7 | import React, {useContext} from "react";
8 | import {GuildContext} from "../../contexts/guild/GuildContext";
9 | import {Locale, useLocale} from "../../utils/Language";
10 | import {useEnableFeatureMutation} from "../../api/utils";
11 | import {useBrandBg, useDetailColor, useTextColor} from "../../utils/colors";
12 |
13 | export default function Feature({banner, name, description, id: featureId, enabled}) {
14 | const {id: serverId} = useContext(GuildContext);
15 | const configUrl = `/guild/${serverId}/features/${featureId}`
16 | const enableMutation = useEnableFeatureMutation(serverId, featureId)
17 | const locale = useLocale()
18 |
19 | //chakra colors
20 | const textColor = useTextColor();
21 | const detailColor = useDetailColor();
22 | const brandColor = useBrandBg()
23 |
24 | return (
25 |
26 | {banner?
27 | :
33 |
38 | }
39 |
40 |
41 |
42 |
43 | {locale(name)}
44 |
45 |
50 | {description}
51 |
52 |
53 |
54 | {enabled && (
55 |
56 | )}
57 |
62 |
63 |
64 |
65 | );
66 | }
67 |
68 | function ConfigButton({configUrl}) {
69 | return (
70 |
73 |
82 |
83 | );
84 | }
85 |
86 | function EnableButton({enabled, isLoading, onChange}) {
87 | return (
88 |
102 | );
103 | }
104 |
--------------------------------------------------------------------------------
/src/views/guild/action/task/index.jsx:
--------------------------------------------------------------------------------
1 | import React, {useContext, useMemo} from "react";
2 |
3 | // Chakra imports
4 | import {Button, SimpleGrid, Stack, Text,} from "@chakra-ui/react";
5 |
6 | // Custom components
7 | import {usePageInfo} from "contexts/PageInfoContext";
8 | import {useActionInfo} from "contexts/actions/ActionDetailContext";
9 | import {ConfigGridSkeleton, MultiConfigPanel} from "components/fields/ConfigPanel";
10 | import {Link, useParams} from "react-router-dom";
11 | import {updateTask} from "api/internal";
12 | import {GuildContext} from "contexts/guild/GuildContext";
13 | import {useActionBanner} from "../components/ActionBanner";
14 | import {BiArrowBack} from "react-icons/bi";
15 | import {useTaskDetailQuery} from "../../../../contexts/actions/TaskDetailContext";
16 | import {useQueryClient} from "react-query";
17 | import {usePageState} from "../../../../utils/State";
18 | import {Locale, useLocale} from "../../../../utils/Language";
19 |
20 | export default function TaskBoard() {
21 | const {name: action} = useActionInfo()
22 | const locale = useLocale()
23 | useActionBanner([
])
24 |
25 | usePageInfo(
26 | [{zh: "動作", en: "Action"}, action].map(locale)
27 | )
28 |
29 | return
30 |
31 |
32 |
33 |
34 |
35 |
36 | }
37 |
38 | function TaskConfigPanel() {
39 | const query = useTaskDetailQuery()
40 |
41 | return query.isLoading?
:
42 | }
43 |
44 | export function Config({detail}) {
45 | const {id: guild, action, task} = useParams()
46 |
47 | const {name, values} = detail
48 | const client = useQueryClient()
49 |
50 | const onSaved = data => {
51 | return client.setQueryData(["task_detail", guild, action, task], data)
52 | }
53 |
54 | return (
55 |
56 |
61 |
62 | );
63 | }
64 |
65 | function ConfigPanel({savedName, onSaved, values}) {
66 | const {id: guild, action, task} = useParams();
67 | const info = useActionInfo()
68 | const state = usePageState()
69 |
70 | const options = useMemo(
71 | () => info.options(values, state),
72 | [values]
73 | )
74 |
75 | const onSave = ([nameChanges, changes]) => {
76 | return updateTask(guild, action, task, nameChanges.get("name"), changes)
77 | }
78 |
79 | const nameOption = {
80 | id: "name",
81 | type: "string",
82 | name:
,
83 | value: savedName,
84 | required: true
85 | }
86 |
87 | return (
88 |
95 | );
96 | }
97 |
98 | function BackButton() {
99 | const {action} = useParams()
100 | const {id: guild} = useContext(GuildContext)
101 |
102 | const actionUrl = `/guild/${guild}/actions/${action}`
103 |
104 | return (
105 |
106 |
}
109 | >
110 |
111 |
112 |
113 | );
114 | }
--------------------------------------------------------------------------------
/src/components/card/data/List.js:
--------------------------------------------------------------------------------
1 | import {Flex, Icon, Text, useColorModeValue} from "@chakra-ui/react";
2 | import Card from "components/card/Card";
3 | import React from "react";
4 | import {useTextColor} from "../../../utils/colors";
5 |
6 | /**
7 | * input:
8 | * {
9 | * title: "Hello World"
10 | * description: "Brun"
11 | * icon: MiMoney
12 | * items: [
13 | * {
14 | * name: "Money"
15 | * description: "Money you have"
16 | * value: 0
17 | * //icon: <...> (optional)
18 | * }
19 | * ]
20 | * }
21 | */
22 | export function List({title, description, icon, items}) {
23 | const textColor = useTextColor()
24 |
25 | return
26 |
32 |
33 | {title}
34 |
35 |
36 | {description}
37 |
38 |
39 | {
40 | items.map((item, key) => (
41 |
42 | ))
43 | }
44 |
45 | }
46 |
47 | function ListItem({name, description, value, icon}) {
48 | // Chakra Color Mode
49 | const textColor = useColorModeValue("brands.900", "white");
50 | const bgItem = useColorModeValue(
51 | {bg: "white", boxShadow: "0px 40px 58px -20px rgba(112, 144, 176, 0.12)"},
52 | {bg: "navy.700", boxShadow: "unset"}
53 | );
54 |
55 | return (
56 |
63 |
64 |
65 |
69 |
77 | {name}
78 |
79 |
86 | {description}
87 |
88 |
89 |
92 |
93 |
94 | {value}
95 |
96 |
97 |
98 |
99 |
100 | );
101 | }
--------------------------------------------------------------------------------
/src/components/card/data/DataCard.js:
--------------------------------------------------------------------------------
1 | import {DataTypes} from "../../../variables/type";
2 | import MiniStatistics from "./MiniStatistics";
3 | import React from "react";
4 | import {Icon, SimpleGrid, useColorModeValue} from "@chakra-ui/react";
5 | import IconBox from "../../icons/IconBox";
6 | import ChartData from "../../charts/data/ChartData";
7 | import DataTable from "./DataTable";
8 | import PieChartData from "../../charts/data/PieChartData";
9 | import {List} from "./List";
10 | import InformationMap from "./InformationMap";
11 | import {useIconColor} from "../../../utils/colors";
12 |
13 | export function DataList({items}) {
14 | return items.map((item, key) => {
15 |
16 | return
17 | })
18 | }
19 |
20 | export default function DataCard({name, value, type, ...optional}) {
21 | const getItem = () => {
22 |
23 | switch (type) {
24 | case DataTypes.Statistics: {
25 | const {icon, growth} = optional
26 |
27 | return
30 | }
31 | growth={growth}
32 | name={name}
33 | value={value}
34 | />
35 | }
36 | case DataTypes.InfoMap: {
37 | const {description} = optional
38 |
39 | return
44 | }
45 | case DataTypes.Bar_Chart:
46 | case DataTypes.Line_Chart:
47 | const {time_unit, description, status, options} = optional
48 |
49 | return
58 |
59 | case DataTypes.Pie_Chart: {
60 | const {options, unit} = optional
61 |
62 | return
63 | }
64 |
65 | case DataTypes.Table:{
66 | const {columns} = optional
67 |
68 | return
69 | }
70 |
71 | case DataTypes.Group: {
72 | return
73 | {value.map((item, key) => )}
74 |
75 | }
76 |
77 | case DataTypes.List: {
78 | const {description, icon} = optional
79 |
80 | return
81 | }
82 |
83 | case DataTypes.Custom: {
84 | return value
85 | }
86 |
87 | default: return <>>
88 | }
89 | }
90 |
91 | return getItem()
92 | }
93 |
94 | function getChartType(type) {
95 | switch (type) {
96 | case DataTypes.Line_Chart:
97 | return "line"
98 | case DataTypes.Bar_Chart:
99 | return "bar"
100 | }
101 | }
102 |
103 | function BaseIcon({icon}) {
104 | // Chakra Color Mode
105 | const iconColor = useIconColor()
106 | const boxBg = useColorModeValue("secondaryGray.300", "whiteAlpha.100");
107 |
108 | return
114 | }
115 | />
116 | }
--------------------------------------------------------------------------------
/src/config/features/kill-kane.js:
--------------------------------------------------------------------------------
1 | import {Locale} from "../../utils/Language";
2 |
3 | export const KillKane = {
4 | name: {
5 | zh: "自動殺死凱恩",
6 | en: "Auto Kill Kane"
7 | },
8 | description: ,
9 | options: values => [
10 | {
11 | id: "kill_friends",
12 | name: "殺死他的朋友",
13 | description: "當他的朋友加入時也殺死他們",
14 | type: "boolean", //boolean, string, enum, number, color, message_create, array, id_enum, image
15 | choices: null, //only enum type option have choices array
16 | value: true, //value must be nonnull in string, boolean, number type
17 | },
18 | {
19 | id: "message",
20 | name: "殺死他的朋友",
21 | description: "當他的朋友加入時也殺死他們",
22 | type: "string",
23 | value: values.message,
24 | },
25 | {
26 | id: "kill_friends_types",
27 | name: "殺死他的朋友",
28 | description: "當他的朋友加入時也殺死他們",
29 | type: "enum",
30 | choices: ["Gay", "Dalao", "None"],
31 | multiple: true, //allow selecting multi options, the value might be return as an array
32 | value: "Gay",
33 | },
34 | {
35 | id: "kill_friends_count",
36 | name: "殺死他的朋友",
37 | description: "當他的朋友加入時也殺死他們",
38 | type: "number",
39 | value: 1,
40 | },
41 | {
42 | id: "color",
43 | name: "我的顏色",
44 | description: "",
45 | type: "color",
46 | value: null,
47 | },
48 | {
49 | id: "kill_message",
50 | name: "我的顏色",
51 | description: "",
52 | type: "message_create",
53 | value: null,
54 | },
55 | {
56 | id: "all_targets",
57 | name: "All Targets",
58 | description: "",
59 | type: "array",
60 | element: {
61 | type: "string",
62 | holder: ""
63 | },
64 | value: null,
65 | },
66 | /**
67 | * to add a role or channel option, use id_enum and define choosable entries at choices
68 | * description is nullable, you may add the color arg to customize option color
69 | */
70 | {
71 | id: "channel",
72 | name: "Target Channel",
73 | description: "Where to kill people",
74 | type: "id_enum",
75 | choices: [
76 | { name: "General", description: "My chat channel", id: 432423 },
77 | { name: "Off-Topic", description: "Share your cool things", id: 534424 },
78 | { name: "NSFW", id: 696969, color: "#de5656" }
79 | ],
80 | element: { //nullable
81 | type: "channel" //supports channel and role
82 | },
83 | value: 432423, //id of enum
84 | },
85 | {
86 | id: "image",
87 | name: "Example Image",
88 | description: "A image",
89 | type: "image",
90 | value: "", //as image url, non-nullable
91 | },
92 | {
93 | id: "emoji",
94 | name: "Emoji Example",
95 | description: "Select an Emoji to see",
96 | type: "emoji",
97 | value: "", //id of enum
98 | },
99 | {
100 | id: "pair",
101 | name: "Pair Example",
102 | description: "Create a pair of strings",
103 | type: "pair",
104 | element: {
105 | first: {
106 | type: "emoji",
107 | },
108 | second: { //option holder
109 | type: "id_enum",
110 | choices: [
111 | {name: "Admin", id: 432423},
112 | ],
113 | element: {
114 | type: "role"
115 | },
116 | multiple: true,
117 | }
118 | },
119 | value: ["☝️", []], //pair is an 2-length array
120 | }
121 | ]
122 | }
--------------------------------------------------------------------------------
/src/components/navbar/searchBar/SearchBar.js:
--------------------------------------------------------------------------------
1 | import React, {useContext, useEffect, useMemo, useState} from "react";
2 | import {Heading, Skeleton, Stack, Text, useDisclosure, VStack,} from "@chakra-ui/react";
3 | import {FeaturesContext} from "contexts/FeaturesContext";
4 | import Feature from "../../card/Feature";
5 | import {useLocation} from "react-router-dom";
6 | import SearchInput from "../../fields/impl/SearchInput";
7 | import {GuildContext} from "contexts/guild/GuildContext";
8 | import {useQuery} from "react-query";
9 | import {getFeatures} from "api/internal";
10 | import {Query} from "contexts/components/AsyncContext";
11 | import {config} from "../../../config/config";
12 | import {Action} from "../../card/Action";
13 | import {Locale, useLocale} from "../../../utils/Language";
14 | import Modal from "../../modal/Modal";
15 |
16 | export function SearchBar({...rest}) {
17 | const {isOpen, onOpen, onClose} = useDisclosure()
18 |
19 | const [search, setSearch] = useState("")
20 | const location = useLocation();
21 | useEffect(onClose, [location.pathname, onClose])
22 |
23 | const groupStyle = {
24 | w: {base: "100%", md: "200px"},
25 | ...rest
26 | }
27 | return (
28 | <>
29 |
30 |
31 | >
32 | );
33 | }
34 |
35 | function SearchList({search}) {
36 | const locale = useLocale()
37 |
38 | function filter(map) {
39 | return Object.entries(map)
40 | .filter(([, feature]) =>
41 | locale(feature.name).includes(search)
42 | )
43 | }
44 |
45 | const {features, actions} = useMemo(
46 | () => ({
47 | features: filter(config.features),
48 | actions: filter(config.actions)
49 | }),
50 | [search]
51 | )
52 |
53 | const empty = features.length === 0 || actions.length === 0
54 |
55 | return
56 | {empty?
57 |
58 |
59 |
60 | :
61 |
62 | }
63 |
64 |
65 | }
66 |
67 | function Content({features, actions}) {
68 | const {enabled} = useContext(FeaturesContext)
69 |
70 | return <>
71 |
72 |
73 |
74 | {features.map(([id, feature]) =>
75 |
81 | )}
82 |
83 |
84 |
85 | {actions.map(([id, action]) =>
86 |
93 | )}
94 | >
95 | }
96 |
97 | function SearchModal({isOpen, onClose, search }) {
98 | const all = search.length === 0
99 |
100 | return
107 |
108 |
109 |
110 |
111 | }
112 |
113 | function DataProvider({children}) {
114 | const {id: serverId} = useContext(GuildContext)
115 |
116 | const features = useQuery(["features", serverId],
117 | () => getFeatures(serverId),
118 | { retry: 0 }
119 | )
120 |
121 | return (
122 |
126 |
127 |
128 |
129 | }>
130 |
131 | {children}
132 |
133 |
134 | );
135 | }
136 |
--------------------------------------------------------------------------------
/src/components/fields/ConfigPanel.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import {OptionPanel} from "./OptionPanel";
3 | import {SaveAlert} from "components/alert/SaveAlert";
4 | import ErrorModal from "../modal/ErrorModal";
5 | import {useMutation} from "react-query";
6 | import {Flex, SimpleGrid, Skeleton, SlideFade} from "@chakra-ui/react";
7 |
8 | export function ConfigItemListAnimated({options, changes, onChange}) {
9 | return options.map((option) => (
10 |
11 |
12 | onChange(option.id, v)}
18 | />
19 |
20 |
21 | ))
22 | }
23 |
24 | export function ConfigGridSkeleton() {
25 | return
26 |
27 |
28 |
29 |
30 |
31 | }
32 |
33 | export function ConfigGrid(props) {
34 | return
35 |
36 |
37 | }
38 |
39 | export function MultiConfigPanel({groups, onSave: save, onSaved}) {
40 | function getInitial() {
41 | return groups.map(() => new Map())
42 | }
43 |
44 | const [changes, setChanges] = useState(getInitial())
45 |
46 | const mutation = useMutation(save, {
47 | onSuccess(data) {
48 | setChanges(getInitial())
49 | return onSaved && onSaved(data)
50 | }
51 | })
52 |
53 | const onChange = (i, id, value) => {
54 | if (mutation.isLoading) return;
55 |
56 | const clone = [...changes]
57 | clone[i].set(id, value)
58 |
59 | setChanges(clone)
60 | }
61 |
62 | return (
63 | <>
64 |
69 | {
70 | groups.map((options, i) =>
71 | onChange(i, id, value)}
76 | />
77 | )
78 | }
79 | item.size > 0)}
81 | saving={mutation.isLoading}
82 | onSave={() => mutation.mutate(changes)}
83 | onDiscard={() => setChanges(getInitial())}
84 | />
85 | >
86 | );
87 | }
88 |
89 | export function ConfigPanel({options, onDiscard, onSave: save, onSaved}) {
90 | const [changes, setChanges] = useState(new Map());
91 | const mutation = useMutation(save, {
92 | onSuccess(data) {
93 | setChanges(new Map());
94 | return onSaved && onSaved(data)
95 | }
96 | })
97 |
98 | const onChange = (id, value) => {
99 | if (mutation.isLoading) return;
100 |
101 | setChanges(new Map(
102 | changes.set(id, value)
103 | ))
104 | };
105 |
106 | return (
107 | <>
108 |
113 |
118 | mutation.mutate(changes)}
122 | onDiscard={() => {
123 | setChanges(new Map())
124 |
125 | if (onDiscard != null) {
126 | onDiscard()
127 | }
128 | }}
129 | />
130 | >
131 | );
132 | }
--------------------------------------------------------------------------------
/src/components/charts/data/ChartData.js:
--------------------------------------------------------------------------------
1 | // Chakra imports
2 | import {Box, Button, Flex, Icon, Text, useColorModeValue, VStack,} from "@chakra-ui/react";
3 | // Custom components
4 | import Card from "components/card/Card.js";
5 | import React from "react";
6 | import {IoCheckmarkCircle} from "react-icons/io5";
7 | import {MdBarChart, MdOutlineCalendarToday} from "react-icons/md";
8 |
9 | import {AiFillWarning} from "react-icons/ai";
10 | import ApexChart from "components/charts/ApexChart";
11 | import {useIconColor, useNoteColor, useTextColor} from "../../../utils/colors";
12 |
13 | export default function ChartData({name, description, value, time_unit, status, options, chartType}) {
14 | // Chakra Color Mode
15 | const textColor = useTextColor()
16 | const textColorSecondary = useNoteColor();
17 | const boxBg = useColorModeValue("secondaryGray.300", "whiteAlpha.100");
18 | const iconColor = useIconColor()
19 | const bgButton = useColorModeValue("secondaryGray.300", "whiteAlpha.100");
20 |
21 | return (
22 |
26 |
27 |
28 | {time_unit &&
41 | }
42 |
43 |
54 |
55 |
56 |
57 |
58 |
63 | {name}
64 |
65 |
71 | {description}
72 |
73 |
74 | {
75 | status &&
76 | {
77 | status.success? <>
78 |
79 |
80 | {status.text}
81 |
82 | > : <>
83 |
84 |
85 | {status.text}
86 |
87 | >
88 | }
89 |
90 | }
91 |
92 |
93 |
98 |
99 |
100 |
101 | );
102 | }
--------------------------------------------------------------------------------
/src/theme/components/button.js:
--------------------------------------------------------------------------------
1 | import { mode } from "@chakra-ui/theme-tools";
2 | export const buttonStyles = {
3 | components: {
4 | Button: {
5 | baseStyle: {
6 | borderRadius: "16px",
7 | boxShadow: "45px 76px 113px 7px rgba(112, 144, 176, 0.08)",
8 | transition: ".25s all ease",
9 | boxSizing: "border-box",
10 | _focus: {
11 | boxShadow: "none",
12 | },
13 | _active: {
14 | boxShadow: "none",
15 | },
16 | },
17 | variants: {
18 | white: () => ({
19 | bg: "white",
20 | color: "black",
21 | _hover: { bg: "whiteAlpha.900" },
22 | _active: { bg: "white" },
23 | _focus: { bg: "white" },
24 | fontWeight: "500",
25 | fontSize: "14px",
26 | py: "20px",
27 | px: "27",
28 | }),
29 | outline: () => ({
30 | borderRadius: "16px",
31 | }),
32 | brand: (props) => ({
33 | bg: mode("brand.500", "brand.400")(props),
34 | color: "white",
35 | _focus: {
36 | bg: mode("brand.500", "brand.400")(props),
37 | },
38 | _active: {
39 | bg: mode("brand.500", "brand.400")(props),
40 | },
41 | _hover: {
42 | bg: mode("brand.600", "brand.400")(props),
43 | },
44 | }),
45 | darkBrand: (props) => ({
46 | bg: mode("brand.900", "brand.400")(props),
47 | color: "white",
48 | _focus: {
49 | bg: mode("brand.900", "brand.400")(props),
50 | },
51 | _active: {
52 | bg: mode("brand.900", "brand.400")(props),
53 | },
54 | _hover: {
55 | bg: mode("brand.800", "brand.400")(props),
56 | },
57 | }),
58 | lightBrand: (props) => ({
59 | bg: mode("#F2EFFF", "whiteAlpha.100")(props),
60 | color: mode("brand.500", "white")(props),
61 | _focus: {
62 | bg: mode("#F2EFFF", "whiteAlpha.100")(props),
63 | },
64 | _active: {
65 | bg: mode("secondaryGray.300", "whiteAlpha.100")(props),
66 | },
67 | _hover: {
68 | bg: mode("secondaryGray.400", "whiteAlpha.200")(props),
69 | },
70 | }),
71 | light: (props) => ({
72 | bg: mode("secondaryGray.300", "whiteAlpha.100")(props),
73 | color: mode("secondaryGray.900", "white")(props),
74 | _focus: {
75 | bg: mode("secondaryGray.300", "whiteAlpha.100")(props),
76 | },
77 | _active: {
78 | bg: mode("secondaryGray.300", "whiteAlpha.100")(props),
79 | },
80 | _hover: {
81 | bg: mode("secondaryGray.400", "whiteAlpha.200")(props),
82 | },
83 | }),
84 | action: (props) => ({
85 | fontWeight: "500",
86 | borderRadius: "50px",
87 | bg: mode("secondaryGray.300", "brand.400")(props),
88 | color: mode("brand.500", "white")(props),
89 | _focus: {
90 | bg: mode("secondaryGray.300", "brand.400")(props),
91 | },
92 | _active: { bg: mode("secondaryGray.300", "brand.400")(props) },
93 | _hover: {
94 | bg: mode("secondaryGray.200", "brand.400")(props),
95 | },
96 | }),
97 | danger: (props) => ({
98 | bg: mode("red.500", "red.400")(props),
99 | color: "white",
100 | _focus: {
101 | bg: mode("red.500", "red.400")(props),
102 | },
103 | _active: {
104 | bg: mode("red.500", "red.400")(props),
105 | },
106 | _hover: {
107 | bg: mode("red.600", "red.400")(props),
108 | },
109 | }),
110 | setup: (props) => ({
111 | fontWeight: "500",
112 | borderRadius: "50px",
113 | bg: mode("transparent", "brand.400")(props),
114 | border: mode("1px solid", "0px solid")(props),
115 | borderColor: mode("secondaryGray.400", "transparent")(props),
116 | color: mode("secondaryGray.900", "white")(props),
117 | _focus: {
118 | bg: mode("transparent", "brand.400")(props),
119 | },
120 | _active: { bg: mode("transparent", "brand.400")(props) },
121 | _hover: {
122 | bg: mode("secondaryGray.100", "brand.400")(props),
123 | },
124 | }),
125 | },
126 | },
127 | },
128 | };
129 |
--------------------------------------------------------------------------------