-
Notifications
You must be signed in to change notification settings - Fork 19
feat: Add timezone select UI to the status bar. #336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| .timezone-category-chip { | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| .timezone-category-chip-default-timezone { | ||
| /* Disable `Chip`'s background style. */ | ||
| background-color: initial !important; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import {Chip} from "@mui/joy"; | ||
| import {DefaultColorPalette} from "@mui/joy/styles/types/colorSystem"; | ||
|
|
||
| import {TIMEZONE_CATEGORY} from "../../../typings/date"; | ||
|
|
||
| import "./TimezoneCategoryChip.css"; | ||
|
|
||
|
|
||
| interface TimezoneTypeMetadata { | ||
| label: string; | ||
| color: DefaultColorPalette; | ||
| } | ||
|
|
||
| const TIMEZONE_CATEGORY_METADATA: Record<TIMEZONE_CATEGORY, TimezoneTypeMetadata> = { | ||
| [TIMEZONE_CATEGORY.DEFAULT]: { | ||
| label: "Default", | ||
| color: "neutral", | ||
| }, | ||
| [TIMEZONE_CATEGORY.BROWSER]: { | ||
| label: "Browser", | ||
| color: "warning", | ||
| }, | ||
| [TIMEZONE_CATEGORY.LOGGER]: { | ||
| label: "Logger", | ||
| color: "primary", | ||
| }, | ||
| [TIMEZONE_CATEGORY.MANUAL]: { | ||
| label: "Manual", | ||
| color: "success", | ||
| }, | ||
| }; | ||
|
|
||
| interface TimezoneCategoryChipProps { | ||
| category: TIMEZONE_CATEGORY; | ||
| disabled: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Render a chip that represents the category of the timezone. | ||
| * | ||
| * @param props | ||
| * @param props.category | ||
| * @param props.disabled | ||
| * @return | ||
| */ | ||
| const TimezoneCategoryChip = ({ | ||
| category, | ||
| disabled, | ||
| }: TimezoneCategoryChipProps) => { | ||
| const isDefault = category === TIMEZONE_CATEGORY.DEFAULT; | ||
|
|
||
| return ( | ||
| <Chip | ||
| color={TIMEZONE_CATEGORY_METADATA[category].color} | ||
| disabled={disabled} | ||
| sx={{borderRadius: "xs"}} | ||
| className={`timezone-category-chip ${isDefault ? | ||
| "timezone-category-chip-default-timezone" : | ||
| ""}`} | ||
| > | ||
| {"Timezone"} | ||
| {false === isDefault && " | "} | ||
| {false === isDefault && TIMEZONE_CATEGORY_METADATA[category].label} | ||
| </Chip> | ||
| ); | ||
| }; | ||
|
|
||
|
|
||
| export default TimezoneCategoryChip; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| .timezone-select { | ||
| /* stylelint-disable-next-line custom-property-pattern */ | ||
| --Input-focusedThickness: 0 !important; | ||
| } | ||
|
|
||
| .timezone-select-listbox { | ||
| min-width: fit-content; | ||
| max-height: var(--ylv-list-box-max-height) !important; | ||
| } | ||
|
|
||
| .timezone-select-pop-up-indicator { | ||
| /* stylelint-disable-next-line custom-property-pattern */ | ||
| --Icon-fontSize: 1.1rem !important; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| import { | ||
| useCallback, | ||
| useEffect, | ||
| useMemo, | ||
| useState, | ||
| } from "react"; | ||
|
|
||
| import {SelectValue} from "@mui/base/useSelect"; | ||
| import {Autocomplete} from "@mui/joy"; | ||
|
|
||
| import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | ||
|
|
||
| import useUiStore from "../../../stores/uiStore"; | ||
| import useViewStore from "../../../stores/viewStore"; | ||
| import { | ||
| BROWSER_TIMEZONE_NAME, | ||
| DEFAULT_TIMEZONE_NAME, | ||
| getTimezoneCategory, | ||
| INTL_SUPPORTED_TIMEZONE_NAMES, | ||
| LOGGER_TIMEZONE_NAME, | ||
| UTC_TIMEZONE_OFFSET_NAMES, | ||
| } from "../../../typings/date"; | ||
| import {UI_ELEMENT} from "../../../typings/states"; | ||
| import {HASH_PARAM_NAMES} from "../../../typings/url"; | ||
| import {isDisabled} from "../../../utils/states"; | ||
| import {updateWindowUrlHashParams} from "../../../utils/url"; | ||
| import TimezoneCategoryChip from "./TimezoneCategoryChip.tsx"; | ||
|
|
||
| import "./index.css"; | ||
|
|
||
|
|
||
| /** | ||
| * The timezone select dropdown menu, the selectable options can be classified as three types: | ||
| * - Default (use the origin timezone of the log events) | ||
| * - Browser Timezone (use the timezone that the browser is currently using) | ||
| * - Frequently-used Timezone | ||
| * | ||
| * @return A timezone select dropdown menu | ||
| */ | ||
| const TimezoneSelect = () => { | ||
| const uiState = useUiStore((state) => state.uiState); | ||
| const timezoneName = useViewStore((state) => state.timezoneName); | ||
|
|
||
| const [inputWidth, setInputWidth] = useState<string>(`${timezoneName.length}ch`); | ||
| const timezoneNameOptions = useMemo(() => [ | ||
| DEFAULT_TIMEZONE_NAME, | ||
| BROWSER_TIMEZONE_NAME, | ||
| LOGGER_TIMEZONE_NAME, | ||
| ...UTC_TIMEZONE_OFFSET_NAMES, | ||
| ...INTL_SUPPORTED_TIMEZONE_NAMES.filter( | ||
| (tzName) => tzName !== BROWSER_TIMEZONE_NAME | ||
| ), | ||
| ], []); | ||
|
|
||
| const handleTimezoneSelectChange = | ||
| useCallback((_: unknown, value: SelectValue<string, false>) => { | ||
| if (null === value) { | ||
| throw new Error("Unexpected null value in non-clearable timezone select."); | ||
| } | ||
|
|
||
| const {updateTimezoneName} = useViewStore.getState(); | ||
| updateTimezoneName(value); | ||
| updateWindowUrlHashParams({ | ||
| [HASH_PARAM_NAMES.TIMEZONE]: value, | ||
| }); | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| // Update the input width based on the selected timezone name. | ||
| setInputWidth(`${timezoneName.length}ch`); | ||
| }, [timezoneName]); | ||
|
|
||
|
|
||
| const disabled = isDisabled(uiState, UI_ELEMENT.TIMEZONE_SETTER); | ||
|
|
||
| return ( | ||
| <Autocomplete | ||
| className={"timezone-select"} | ||
| componentName={"button"} | ||
| disableClearable={true} | ||
| disabled={disabled} | ||
| groupBy={getTimezoneCategory} | ||
| openOnFocus={true} | ||
| options={timezoneNameOptions} | ||
| popupIcon={<KeyboardArrowUpIcon/>} | ||
| size={"sm"} | ||
| value={timezoneName} | ||
| variant={"soft"} | ||
| slotProps={{ | ||
| popupIndicator: { | ||
| className: "timezone-select-pop-up-indicator", | ||
| }, | ||
| input: { | ||
| sx: { | ||
| width: inputWidth, | ||
| }, | ||
| }, | ||
| listbox: { | ||
| className: "timezone-select-listbox", | ||
| placement: "top-end", | ||
| modifiers: [ | ||
| // Remove gap between the listbox and the `Select` button. | ||
| {name: "offset", enabled: false}, | ||
| ], | ||
| }, | ||
| }} | ||
| startDecorator={<TimezoneCategoryChip | ||
| category={getTimezoneCategory(timezoneName)} | ||
| disabled={disabled}/>} | ||
| onChange={handleTimezoneSelectChange}/> | ||
| ); | ||
| }; | ||
|
|
||
|
|
||
| export default TimezoneSelect; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,6 +24,7 @@ import { | |||||||||||||||||||||||
| } from "../../utils/url"; | ||||||||||||||||||||||||
| import LogLevelSelect from "./LogLevelSelect"; | ||||||||||||||||||||||||
| import StatusBarToggleButton from "./StatusBarToggleButton"; | ||||||||||||||||||||||||
| import TimezoneSelect from "./TimezoneSelect"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
Comment on lines
+27
to
28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Consider lazy-loading the new TimezoneSelect import
-import TimezoneSelect from "./TimezoneSelect";
+const TimezoneSelect = React.lazy(() => import("./TimezoneSelect"));
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| import "./index.css"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -89,6 +90,7 @@ const StatusBar = () => { | |||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||
| <Divider orientation={"vertical"}/> | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| <TimezoneSelect/> | ||||||||||||||||||||||||
| <LogLevelSelect/> | ||||||||||||||||||||||||
| <Divider orientation={"vertical"}/> | ||||||||||||||||||||||||
|
Comment on lines
91
to
95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Add a divider before LogLevelSelect for visual symmetry The status-bar controls are currently separated by vertical dividers, except between the newly-inserted - <TimezoneSelect/>
- <LogLevelSelect/>
+ <TimezoneSelect/>
+ <Divider orientation={"vertical"}/>
+ <LogLevelSelect/>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,11 +1,13 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {StateCreator} from "zustand"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {DEFAULT_TIMEZONE_NAME} from "../../typings/date"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {UI_STATE} from "../../typings/states"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CURSOR_CODE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CursorType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "../../typings/worker"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import useLogFileManagerStore from "../logFileManagerProxyStore"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import useLogFileStore from "../logFileStore"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {handleErrorWithNotification} from "../notificationStore"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import useUiStore from "../uiStore"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {VIEW_EVENT_DEFAULT} from "./createViewEventSlice"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -18,6 +20,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const VIEW_FORMATTING_DEFAULT: ViewFormattingValues = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isPrettified: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timezoneName: DEFAULT_TIMEZONE_NAME, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -59,6 +62,48 @@ const createViewFormattingSlice: StateCreator< | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatePageData(pageData); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })().catch(handleErrorWithNotification); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateTimezoneName: (newTimezoneName: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ("" === newTimezoneName) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| newTimezoneName = DEFAULT_TIMEZONE_NAME; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const {numEvents} = useLogFileStore.getState(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (0 === numEvents) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const {timezoneName} = get(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (newTimezoneName === timezoneName) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use a local variable instead of reassigning the parameter. The method reassigns the updateTimezoneName: (newTimezoneName: string) => {
- if ("" === newTimezoneName) {
- newTimezoneName = DEFAULT_TIMEZONE_NAME;
- }
+ const timezoneName = "" === newTimezoneName ? DEFAULT_TIMEZONE_NAME : newTimezoneName;
const {numEvents} = useLogFileStore.getState();
if (0 === numEvents) {
return;
}
- const {timezoneName} = get();
- if (newTimezoneName === timezoneName) {
+ const {timezoneName: currentTimezoneName} = get();
+ if (timezoneName === currentTimezoneName) {
return;
}📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (1.9.4)[error] 67-67: Reassigning a function parameter is confusing. The parameter is declared here: Use a local variable instead. (lint/style/noParameterAssign) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const {setUiState} = useUiStore.getState(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setUiState(UI_STATE.FAST_LOADING); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| set({timezoneName: newTimezoneName}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const {logEventNum} = get(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (VIEW_EVENT_DEFAULT.logEventNum !== logEventNum) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cursor = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| code: CURSOR_CODE.EVENT_NUM, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args: {eventNum: logEventNum}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const {logFileManagerProxy} = useLogFileManagerStore.getState(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const {isPrettified, updatePageData} = get(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // await logFileManagerProxy.setTimezone(newTimezoneName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shall we add a TODO? |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const pageData = await logFileManagerProxy.loadPage( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cursor, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isPrettified, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatePageData(pageData); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })().catch(handleErrorWithNotification); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default createViewFormattingSlice; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Nit: make the chip truly non-interactive when disabled
If a category ever needs a disabled state, consider adding
pointer-events: none;alongside the existing background override to prevent misleading hover cues.No action required for this PR.
🤖 Prompt for AI Agents