From 703d7b16c55868c5d928e5a7ec7120251428c61f Mon Sep 17 00:00:00 2001 From: Tilman Griesel Date: Mon, 6 Jan 2025 16:08:15 +0100 Subject: [PATCH 1/4] Preserve quotes for generic tokens --- extras/theme-patcher/graphite-theme-patcher.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/extras/theme-patcher/graphite-theme-patcher.py b/extras/theme-patcher/graphite-theme-patcher.py index 3a5db02..cf20eb3 100644 --- a/extras/theme-patcher/graphite-theme-patcher.py +++ b/extras/theme-patcher/graphite-theme-patcher.py @@ -165,13 +165,26 @@ def _validate_value(self, value: Optional[str]) -> Optional[str]: if value is None: return None + # Handle raw string prefix + if value.startswith('r"') or value.startswith("r'"): + value = value[1:] # Remove 'r' prefix + + # Preserve quotes for generic tokens + if self.token_type == TokenType.GENERIC: + stripped_value = value.strip().strip("\"'") + if any(char in stripped_value for char in " ()/:"): + # Re-quote the value if it contains spaces or special characters + return f'"{stripped_value}"' + return stripped_value + + # Strip quotes for "strong typed" tokens value = value.strip().strip("\"'") try: if self.token_type == TokenType.CARD_MOD: if not isinstance(value, str): raise ValidationError("Card-mod value must be a string") - # Always double quote the user-supplied value + # Always double quote the user value return f'"{value}"' elif self.token_type == TokenType.SIZE: @@ -321,6 +334,7 @@ def _process_yaml_file( def set_token_value(self, value: Optional[str], create_token: bool = False) -> bool: """Update token value across theme files.""" if value is None: + logger.info("Skipping update: value is None") return True try: From eb6b7ec2abec365695fbd373a811976ce4b316dd Mon Sep 17 00:00:00 2001 From: Tilman Griesel Date: Mon, 6 Jan 2025 16:09:03 +0100 Subject: [PATCH 2/4] Add version information --- extras/theme-patcher/graphite-theme-patcher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extras/theme-patcher/graphite-theme-patcher.py b/extras/theme-patcher/graphite-theme-patcher.py index cf20eb3..16f70e0 100644 --- a/extras/theme-patcher/graphite-theme-patcher.py +++ b/extras/theme-patcher/graphite-theme-patcher.py @@ -21,9 +21,10 @@ from typing import Optional, List, Union, Dict, Tuple from enum import Enum, auto -__version__ = "1.4.0" +__version__ = "1.4.1" __author__ = "Tilman Griesel" __changelog__ = { + "1.4.1": "Preserve quotes for generic tokens", "1.4.0": "Added support for card-mod tokens", "1.3.0": "Enhanced color token handling: RGB tokens use comma format, other tokens use rgb()/rgba() format", "1.2.0": "Added support for custom token creation", From 7ce5dc6156de106bff6e907c84c1daf107c4ffff Mon Sep 17 00:00:00 2001 From: Tilman Griesel Date: Mon, 6 Jan 2025 16:49:21 +0100 Subject: [PATCH 3/4] Improve logging and arguments --- .../theme-patcher/graphite-theme-patcher.py | 243 ++++++------------ 1 file changed, 81 insertions(+), 162 deletions(-) diff --git a/extras/theme-patcher/graphite-theme-patcher.py b/extras/theme-patcher/graphite-theme-patcher.py index 16f70e0..ed83ba6 100644 --- a/extras/theme-patcher/graphite-theme-patcher.py +++ b/extras/theme-patcher/graphite-theme-patcher.py @@ -2,10 +2,8 @@ """ Graphite Theme Patcher ------------------ - -Updates token values in theme files. Created for the Home Assistant Graphite theme. +Updates token values in theme files for the Home Assistant Graphite theme. """ import sys @@ -24,11 +22,11 @@ __version__ = "1.4.1" __author__ = "Tilman Griesel" __changelog__ = { - "1.4.1": "Preserve quotes for generic tokens", + "1.4.1": "Improved logging and arguments", "1.4.0": "Added support for card-mod tokens", - "1.3.0": "Enhanced color token handling: RGB tokens use comma format, other tokens use rgb()/rgba() format", + "1.3.0": "Enhanced color token handling with rgb()/rgba() formats", "1.2.0": "Added support for custom token creation", - "1.1.0": "Added support for size, opacity, and radius token and multiple themes and configurable paths", + "1.1.0": "Added support for multiple themes and configurable paths", "1.0.0": "Initial release with RGB token support", } @@ -59,14 +57,14 @@ def filter(self, record): class ValidationError(Exception): - """Raised for input validation failures.""" + """Raised when input is invalid.""" pass @contextmanager def file_lock(lock_file: Path): - """File access synchronization.""" + """Provide an exclusive file lock for atomic operations.""" lock_path = lock_file.with_suffix(".lock") try: with open(lock_path, "w") as f: @@ -118,80 +116,62 @@ def __init__( self._validate_token() def _validate_paths(self) -> None: - """Validate directories exist with correct permissions.""" - # Validate base themes directory + """Check directories exist and are writable.""" base_path = self.theme_path.parent if not base_path.exists(): - raise ValidationError(f"Themes directory not found: {base_path}") + raise ValidationError(f"Directory not found: {base_path}") if not base_path.is_dir(): raise ValidationError(f"Not a directory: {base_path}") - # Validate specific theme directory if not self.theme_path.exists(): raise ValidationError(f"Theme not found: {self.theme_path}") if not self.theme_path.is_dir(): - raise ValidationError(f"Theme path is not a directory: {self.theme_path}") + raise ValidationError(f"Not a directory: {self.theme_path}") if not os.access(self.theme_path, os.W_OK): - raise ValidationError( - f"Insufficient permissions for theme: {self.theme_path}" - ) + raise ValidationError(f"Cannot write to theme: {self.theme_path}") def _validate_token(self) -> None: - """Validate token format.""" + """Ensure token is a non-empty string.""" if not isinstance(self.token, str) or not self.token.strip(): raise ValidationError("Token must be a non-empty string") def _parse_color_value(self, value: str) -> Tuple[List[int], Optional[float]]: - """Parse comma-separated color components.""" + """Parse comma-separated RGB or RGBA values.""" try: components = [x.strip() for x in value.split(",")] - if len(components) not in (3, 4): - raise ValidationError("Color must have 3 or 4 components (RGB or RGBA)") - + raise ValidationError("Color must have 3 (RGB) or 4 (RGBA) components") rgb = [int(x) for x in components[:3]] alpha = float(components[3]) if len(components) == 4 else None if not all(0 <= x <= 255 for x in rgb): raise ValidationError("RGB values must be between 0 and 255") if alpha is not None and not 0 <= alpha <= 1: - raise ValidationError("Alpha value must be between 0 and 1") + raise ValidationError("Alpha must be between 0 and 1") return rgb, alpha except ValueError: - raise ValidationError("Invalid color component values") + raise ValidationError("Invalid color values") def _validate_value(self, value: Optional[str]) -> Optional[str]: - """Validate and format value based on token name and type.""" + """Validate and format the value based on the token type.""" if value is None: return None - # Handle raw string prefix - if value.startswith('r"') or value.startswith("r'"): - value = value[1:] # Remove 'r' prefix - - # Preserve quotes for generic tokens if self.token_type == TokenType.GENERIC: - stripped_value = value.strip().strip("\"'") - if any(char in stripped_value for char in " ()/:"): - # Re-quote the value if it contains spaces or special characters - return f'"{stripped_value}"' - return stripped_value - - # Strip quotes for "strong typed" tokens + return value + + # Strip quotes for other types value = value.strip().strip("\"'") try: if self.token_type == TokenType.CARD_MOD: - if not isinstance(value, str): - raise ValidationError("Card-mod value must be a string") - # Always double quote the user value return f'"{value}"' elif self.token_type == TokenType.SIZE: num_value = int(value) if num_value < 0: - raise ValidationError("Size must be a positive integer") + raise ValidationError("Size must be positive") return f"{num_value}px" elif self.token_type == TokenType.OPACITY: @@ -206,25 +186,22 @@ def _validate_value(self, value: Optional[str]) -> Optional[str]: elif self.token_type == TokenType.RADIUS: num_value = int(value) if num_value < 0: - raise ValidationError("Radius must be a positive integer") + raise ValidationError("Radius must be positive") return f"{num_value}px" elif self.token_type == TokenType.RGB: rgb, alpha = self._parse_color_value(value) - - # Use comma format for tokens containing 'rgb' if "rgb" in self.token.lower(): + # Token name includes 'rgb', so just commas if alpha is not None: - raise ValidationError("RGB tokens cannot include alpha channel") + raise ValidationError("RGB tokens cannot have alpha") return f"{rgb[0]}, {rgb[1]}, {rgb[2]}" - - # Use rgb()/rgba() format for other tokens if alpha is not None: return f"rgba({rgb[0]}, {rgb[1]}, {rgb[2]}, {alpha})" return f"rgb({rgb[0]}, {rgb[1]}, {rgb[2]})" else: - return value # For generic tokens, accept any non-empty value + return value except ValidationError as e: raise ValidationError(f"Invalid value for token: {str(e)}") @@ -232,7 +209,7 @@ def _validate_value(self, value: Optional[str]) -> Optional[str]: def _process_yaml_file( self, file_path: Path, value: Optional[str], create_token: bool = False ) -> bool: - """Process and update a YAML file.""" + """Update or create the token in a YAML file.""" if value is None: return True @@ -240,77 +217,65 @@ def _process_yaml_file( with open(file_path, "r", encoding="utf-8") as f: content = f.read() - # Check if token exists token_exists = f"{self.token}:" in content - if not token_exists and not create_token: logger.error(f"Token '{self.token}' not found in {file_path}") return False timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - # For card-mod tokens, ensure they are placed after card-mod-theme + # Determine indentation + lines = content.split("\n") if self.token_type == TokenType.CARD_MOD: if "card-mod-theme:" not in content: - logger.error("card-mod-theme key not found in file") + logger.error("No card-mod-theme key found.") return False - - # Find the card-mod-theme section - lines = content.split("\n") card_mod_theme_index = next( (i for i, line in enumerate(lines) if "card-mod-theme:" in line), -1 ) - if card_mod_theme_index == -1: - logger.error("Could not locate card-mod-theme section") + logger.error("Could not find card-mod-theme section.") return False - - # Determine indentation level (same as card-mod-theme line) - theme_line = lines[card_mod_theme_index] - base_indent = len(theme_line) - len(theme_line.lstrip()) - # Match exactly the indentation of the `card-mod-theme:` key - token_indent = " " * base_indent - + base_indent = len(lines[card_mod_theme_index]) - len( + lines[card_mod_theme_index].lstrip() + ) else: - # Determine the indentation level of the last non-empty line - lines = content.rstrip().split("\n") + # Use indentation of the last non-empty line last_non_empty_line = next( (line for line in reversed(lines) if line.strip()), "" ) base_indent = len(last_non_empty_line) - len( last_non_empty_line.lstrip() ) - token_indent = " " * base_indent - new_value = f"{token_indent}{self.token}: {value} # Modified via Graphite Theme Patcher v{__version__} - {timestamp}" + token_indent = " " * base_indent + new_value = ( + f"{token_indent}{self.token}: {value} " + f"# Modified by Graphite Theme Patcher v{__version__} - {timestamp}" + ) if token_exists: - # Update existing token pattern = f"^[ \t]*{self.token}:.*(?:\r\n|\r|\n|$)" updated_content = re.sub( pattern, new_value + "\n", content, flags=re.MULTILINE ) else: if self.token_type == TokenType.CARD_MOD: - # Insert after card-mod-theme section - lines = content.split("\n") lines.insert(card_mod_theme_index + 1, new_value) updated_content = "\n".join(lines) else: - # Append user-defined entries at the end of the file if "# User defined entries" not in content: custom_section = ( f"\n{token_indent}##############################################################################\n" - f"{token_indent}# User defined entries added via Graphite Theme Patcher\n" + f"{token_indent}# User defined entries\n" ) else: custom_section = "\n" - updated_content = ( content.rstrip() + custom_section + new_value + "\n" ) - # Atomic write + # Write changes atomically with tempfile.NamedTemporaryFile( mode="w", dir=file_path.parent, delete=False, encoding="utf-8" ) as tmp: @@ -333,7 +298,7 @@ def _process_yaml_file( return False def set_token_value(self, value: Optional[str], create_token: bool = False) -> bool: - """Update token value across theme files.""" + """Set token value across all relevant YAML files.""" if value is None: logger.info("Skipping update: value is None") return True @@ -346,10 +311,8 @@ def set_token_value(self, value: Optional[str], create_token: bool = False) -> b yaml_files = [ path for path in self.theme_path.rglob("*") - if path.suffix in (".yaml") - and self.theme_path in path.resolve().parents + if path.suffix == ".yaml" and self.theme_path in path.resolve().parents ] - if not yaml_files: raise ValidationError(f"No YAML files found in {self.theme_path}") @@ -361,7 +324,6 @@ def set_token_value(self, value: Optional[str], create_token: bool = False) -> b yaml_file, validated_value, create_token ): success = False - return success except Exception as e: @@ -370,7 +332,7 @@ def set_token_value(self, value: Optional[str], create_token: bool = False) -> b def print_version(): - """Print version information and changelog.""" + """Print version info and changelog.""" print(f"\nGraphite Theme Patcher v{__version__}") print(f"Author: {__author__}\n") print("Changelog:") @@ -381,127 +343,93 @@ def print_version(): def validate_args(args: argparse.Namespace) -> bool: - """Validate command line arguments and log any errors.""" - # Determine final "value" from either positional or named argument - final_value = None - if args.named_value: - final_value = args.named_value - elif args.positional_value: - final_value = args.positional_value - - # If user provided neither, log an error + """Check command-line arguments for validity.""" + final_value = args.named_value if args.named_value else args.positional_value if final_value is None: - error_msg = "Missing token value. Provide as positional argument or via --value" - logger.error(f"Argument Error: {error_msg}") + logger.error("Missing token value. Provide as positional argument or --value.") return False - # Convert 'none' string to None if final_value.lower() == "none": args.value = None else: args.value = final_value - # Validate token - if not isinstance(args.token, str) or not args.token.strip(): - error_msg = "Token must be a non-empty string" - logger.error(f"Argument Error: {error_msg}") + if not args.token or not args.token.strip(): + logger.error("Token must be a non-empty string.") return False - # Validate token type valid_types = ["rgb", "size", "opacity", "radius", "generic", "card-mod"] - if not hasattr(args, "type") or args.type not in valid_types: - error_msg = f"Invalid token type. Must be one of: {', '.join(valid_types)}" - logger.error(f"Argument Error: {error_msg}") + if args.type not in valid_types: + logger.error(f"Invalid token type. Must be one of: {', '.join(valid_types)}") return False - # Validate theme - if ( - not hasattr(args, "theme") - or not isinstance(args.theme, str) - or not args.theme.strip() - ): - error_msg = "Theme must be a non-empty string" - logger.error(f"Argument Error: {error_msg}") + if not args.theme or not args.theme.strip(): + logger.error("Theme must be a non-empty string.") return False - # Validate path - if ( - not hasattr(args, "path") - or not isinstance(args.path, str) - or not args.path.strip() - ): - error_msg = "Path must be a non-empty string" - logger.error(f"Argument Error: {error_msg}") + if not args.path or not args.path.strip(): + logger.error("Path must be a non-empty string.") return False - # Check if path exists theme_path = Path(args.path) - if not theme_path.exists(): - error_msg = f"Theme path does not exist: {args.path}" - logger.error(f"Argument Error: {error_msg}") - return False - - if not theme_path.is_dir(): - error_msg = f"Theme path is not a directory: {args.path}" - logger.error(f"Argument Error: {error_msg}") + if not theme_path.exists() or not theme_path.is_dir(): + logger.error(f"Invalid theme path: {args.path}") return False return True def main(): - """Main execution.""" + """Main entry point.""" try: class ArgumentParserWithLogging(argparse.ArgumentParser): def error(self, message): - """Override error method to log before exiting.""" logger.error(f"Argument Error: {message}") super().error(message) parser = ArgumentParserWithLogging( - description=f"Update token values in theme files. (v{__version__})" + description=f"Update token values in theme files (v{__version__})." ) + parser.add_argument( - "--version", action="store_true", help="Show version information and exit" + "-v", "--version", action="store_true", help="Show version info and exit" ) - # Value can be positional... parser.add_argument( - "positional_value", nargs="?", help="Token value to set (positional)" + "positional_value", nargs="?", help="Token value (positional)" ) - # ...or named parser.add_argument( + "-V", "--value", dest="named_value", - help="Token value to set (named). Takes precedence over the positional value.", + help="Token value (named), takes precedence over positional", ) parser.add_argument( + "-t", + "-n", "--token", + "--name", default="token-rgb-primary", help="Token to update (default: token-rgb-primary)", ) parser.add_argument( + "-T", "--type", default="rgb", - choices=[ - "rgb", - "size", - "opacity", - "radius", - "generic", - "card-mod", - ], - help="Type of token (default: rgb)", + choices=["rgb", "size", "opacity", "radius", "generic", "card-mod"], + help="Token type (default: rgb)", ) parser.add_argument( - "--theme", default="graphite", help="Theme name (default: graphite)" + "-m", "--theme", default="graphite", help="Theme name (default: graphite)" ) parser.add_argument( + "-p", "--path", default="/config/themes", - help="Base path for themes directory (default: /config/themes)", + help="Base path for themes (default: /config/themes)", ) parser.add_argument( + "-c", "--create", action="store_true", help="Create token if it doesn't exist", @@ -517,20 +445,14 @@ def error(self, message): if not validate_args(args): sys.exit(1) - # Override token name for special cases - token = args.token - if args.type == "card-mod": - token = "card-mod-root" + # Override token name for card-mod + token = args.token if args.type != "card-mod" else "card-mod-root" - # Log user info logger.info( - f"Theme Patcher v{__version__} - " - f"Updating token: '{token}' " - f"(type: '{args.type}') in theme: '{args.theme}' " - f"to value: '{args.value}'" + f"Patching '{token}' (type: '{args.type}') in theme '{args.theme}' " + f"with value: '{args.value}'" ) - # Instantiate patcher patcher = ThemePatcher( token=token, token_type=args.type, @@ -538,17 +460,14 @@ def error(self, message): base_path=args.path, ) - # Override token creation for special cases - create_token = args.create - if patcher.token_type == TokenType.CARD_MOD: - create_token = True + # card-mod tokens must be created if missing + create_token = args.create or (patcher.token_type == TokenType.CARD_MOD) - # Execute patcher if not patcher.set_token_value(args.value, create_token): - logger.error("Update failed") + logger.error("Update failed.") sys.exit(1) - logger.info("Update completed") + logger.info("Update completed.") except Exception as e: logger.error(f"Error: {str(e)}") From ada5082dcc2e7e978c90eb972e587f17ad3d9589 Mon Sep 17 00:00:00 2001 From: Tilman Griesel Date: Mon, 6 Jan 2025 17:10:47 +0100 Subject: [PATCH 4/4] docs: Update shell commands --- README.md | 12 +++++-- docs/features/graphite-theme-patcher.md | 48 +++++++++++++++++-------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 9a1e2c7..6381770 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ Follow the [Quickstart Guide](https://graphite.tilmangriesel.com/guides/getting- [![Open Graphite in your Home Assistant instance](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=TilmanGriesel&repository=graphite) -
Or read the setup instructions here #### Step 1: Install HACS + If you haven't already, [install HACS](https://hacs.xyz/docs/use/) by following the official guide. #### Step 2: Add the Graphite Theme Repository @@ -28,6 +28,7 @@ If you haven't already, [install HACS](https://hacs.xyz/docs/use/) by following [Open Graphite in your Home Assistant HACS instance](https://my.home-assistant.io/redirect/hacs_repository/?owner=TilmanGriesel&repository=graphite) ##### Alternatively + 1. Open the HACS interface in Home Assistant. 2. Search for **Graphite Theme** or use the direct link below: 3. Click **Install** to add the theme to your setup. @@ -42,6 +43,7 @@ frontend: ``` #### Step 4: Restart Home Assistant + Restart your Home Assistant instance to apply changes. #### Step 5: Select the Graphite Theme @@ -49,8 +51,8 @@ Restart your Home Assistant instance to apply changes. 1. Go to your **User Profile** in Home Assistant. 2. Under **Themes**, select `Graphite` (Light or Dark) from the dropdown menu. - ### Manual Installation + For manual installation, you can follow these steps: 1. Download and copy the `themes` folder into your Home Assistant configuration directory. @@ -67,21 +69,25 @@ For manual installation, you can follow these steps: --- ### Personalize Graphite -Customize the Graphite theme's primary color and more without needing to fork the project by using the [Graphite Theme Patcher](https://graphite.tilmangriesel.com/features/graphite-theme-patcher.html). This tool is designed for advanced users with technical expertise and experience in script and config modification. For detailed setup instructions, refer to the patcher's README. + +Customize the Graphite theme's primary color and more without needing to fork the project by using the [Graphite Theme Patcher](https://graphite.tilmangriesel.com/features/graphite-theme-patcher.html). This tool is designed for advanced users with technical expertise and experience in script and config modification. For detailed setup instructions, refer to the patcher's documentation. --- ### Theme Development Kit + I've created a token abstraction and a script to help maintain Graphite's consistency across theme variants and simplify updates. This setup can also serve as a great starting point for building your own themes in no time. [Theme Development Kit](https://graphite.tilmangriesel.com/features/graphite-theme-development-kit.html) --- ### Examples + If you're curious about the cards from my screenshot, you can [check out my examples](https://graphite.tilmangriesel.com/guides/card-examples.html). --- ### Personal note + Hi there, I'm Tilman, nice to meet you! I'm a product designer and software engineer with a love for blending technology, art, design, and open-source projects. I live in a cozy 16th-century home that I'm gradually turning into a smarter, more connected space. I started designing this theme in 2022 to make our smart home more intuitive and visually harmonious for my partner Sophia and me. The goal was to create an experience that's both user-friendly and aesthetically pleasing, without needing extra complexity or plugins. diff --git a/docs/features/graphite-theme-patcher.md b/docs/features/graphite-theme-patcher.md index e5b5aa2..605e0a3 100644 --- a/docs/features/graphite-theme-patcher.md +++ b/docs/features/graphite-theme-patcher.md @@ -7,6 +7,7 @@ Effortlessly customize Graphite's primary color and more **without forking the p ## Overview The Graphite Theme Patcher allows you to: + - Adjust the primary accent color. - Replace specific theme tokens for deeper customization. @@ -16,7 +17,6 @@ This tool is ideal for tinkerers and power users familiar with script and config Changes made with this patcher are **temporary**. Updates to the Graphite theme will overwrite customizations unless automated workflows are in place. ::: - ## Installation ### **Step 1: Download the Patcher** @@ -38,12 +38,11 @@ wget -O /config/scripts/graphite-theme-patcher.py https://raw.githubusercontent. ```yaml shell_command: - patch_graphite_theme_primary_color: "python3 /config/scripts/graphite-theme-patcher.py {{ rgb_value }}" + patch_graphite_theme_primary_color: "python3 /config/scripts/graphite-theme-patcher.py --value '{{ rgb_value }}'" ``` 3. Save and restart Home Assistant. - --- ### **Step 3: Create a Home Assistant Script** @@ -73,9 +72,9 @@ sequence: - action: shell_command.patch_graphite_theme_primary_color data: rgb_value: "{{ user_primary_color | join(',') }}" + alias: Set Primary Color - action: frontend.reload_themes data: {} - ``` ## Usage @@ -92,7 +91,7 @@ sequence: ## Advanced Customization -The basic installation outlined earlier will likely meet your needs, so you can stop here if you're satisfied. However, if you're ready to explore the patcher's true potential, consider this your gateway to experimentation and powerful customization. The documentation isn't exhaustive, but it equips you with enough knowledge to dive deep, experimenting with RGB and size tokens to unleash their full power. From here on, you're stepping into experimental, uncharted territory. +If your main goal was just to update the primary color, the earlier steps should do the trick. But if you're curious to see what else the patcher can do, this is your chance to experiment and customize to your heart’s content. The documentation isn't super detailed, but it gives you enough to start playing around with RGB and size tokens to see what's possible. From here, it's all about exploring and having fun in uncharted territory. ![graphite_theme_patcher_demo_advanced](/assets/gif/graphite_theme_patcher_demo_advanced.gif) @@ -102,7 +101,7 @@ Add a new shell command to your `configuration.yaml`: ```yaml shell_command: - patch_theme: "python3 /config/scripts/graphite-theme-patcher.py --theme {{ theme }} --token {{ token }} --type {{ type }} {{ value }}" + patch_theme: "python3 /config/scripts/graphite-theme-patcher.py --theme '{{ theme }}' --token '{{ token }}' --type '{{ type }}' --create --value '{{ value }}'" ``` Save and restart Home Assistant. @@ -131,29 +130,50 @@ fields: number: min: 0 max: 100 - step: 4 - name: Large Radius - description: Choose your custom radius. + step: 3 + name: "Large Radius " + description: Choose your custom large radius. default: 18 required: true - sequence: - - action: update.install - target: - device_id: 510699c015423c5fe6211eccfc3fe364 - data: {} - action: shell_command.patch_theme data: theme: graphite token: token-rgb-primary type: rgb value: "{{ user_primary_color | join(',') }}" + alias: Set Primary Color - action: shell_command.patch_theme data: theme: graphite token: token-size-radius-large type: radius value: "{{ user_radius_large }}" + alias: Set Radius + - action: shell_command.patch_theme + data: + theme: graphite + type: card-mod + token: card-mod-root + value: .header{display:none;}#view{padding:0!important;height:100vh!important;} + alias: Hide Header + enabled: false + - action: shell_command.patch_theme + data: + theme: graphite + type: card-mod + token: card-mod-root + value: .header{} + alias: Show Header + enabled: false + - action: shell_command.patch_theme + data: + theme: graphite + type: generic + token: my-token + value: url("/local/animated-icons/clear-night.svg") + alias: Set Custom Generic + enabled: false - action: frontend.reload_themes data: {} ```