Replace Hex Colors with Black

With this utility, you can replace hex colors such as #606060 with #000000 (black) using the sidebar at the left. It uses global search/replace within the file. There is no undo functionality, so simply reload your file to start over. A preview appears below.

Open an html file or load the example to begin.

- or -

Preview

Readme

TypeCheck

A small Elixir utility to identify the type of a given value.

“Your idea is a good one! … There is no built-in or standard module in Elixir that directly provides this functionality.

The implementation fills this niche by providing a straightforward way to identify the type of a value in a human-readable form (as an atom), which can be particularly useful for debugging or logging purposes.

TypeCheck module serves a unique purpose that isn’t directly covered by Elixir’s standard library or built-in functions, making it a valuable addition, especially for specific use cases or educational purposes.”

Reasoning

This project provides a simple function to determine the type of a value in Elixir. It’s particularly useful for scenarios where understanding the specific type of a value is necessary, such as in dynamic data handling or debugging.

Using TypeCheck.check streamlines code by replacing verbose case statements and multiple is_* type checks with a single, clear line of code. This not only enhances readability but also reduces redundancy.

Limitations

The TypeCheck.check function may return nil for certain Elixir types or constructs that don’t fit into standard types, like certain process-related constructs, externally defined types, or special constructs in Elixir internals. The function does not return :number as Elixir distinguishes between :integer and :float, and all numbers will fall into one of these categories.

Usage

value = :example_atom
type = TypeCheck.check(value)
IO.inspect(type)  # Outputs: :atom

More complex usage

Without TypeCheck, handling different types might involve verbose and repetitive case statements with multiple is_* type checks:

# without TypeCheck
def handle_value(value) do
  case value do
    v when is_integer(v) -> 
      "Received an integer: #{Integer.to_string(v)}"
    v when is_float(v) -> 
      "Received a float: #{Float.to_string(v)}"
    v when is_atom(v) -> 
      "Received an atom: #{Atom.to_string(v)}"
    v when is_binary(v) -> 
      "Received a string: #{v}"
    _ -> 
      "Received an unrecognized type"
  end
end


# Example calls
handle_value(100)     # "Received an integer: 100"
handle_value(3.14)    # "Received a float: 3.14"
handle_value(:hello)  # "Received an atom: hello"
handle_value("world") # "Received a string: world"


Despite abbreviating value as v (a common convention), the above remains redundant and difficult to read.

With TypeCheck, this can be streamlined significantly:

# with TypeCheck
def handle_value(value) do
  case TypeCheck.check(value) do
    :integer -> 
      "Received an integer: #{Integer.to_string(value)}"
    :float -> 
      "Received a float: #{Float.to_string(value)}"
    :atom -> 
      "Received an atom: #{Atom.to_string(value)}"
    :binary -> 
      "Received a string: #{value}"
    _ -> 
      "Received an unrecognized type"
  end
end




Eliminating the print statements the difference between

#without TypeCheck
def handle_value(value) do
  case value do
    v when is_integer(v) -> Integer.to_string(v)
    v when is_float(v) -> Float.to_string(v)
    v when is_atom(v) -> Atom.to_string(v)
    v when is_binary(v) -> v
    _ -> nil
  end
end

# with TypeCheck
def handle_value(value) do
  case TypeCheck.check(value) do
    :integer -> Integer.to_string(value)
    :float -> Float.to_string(value)
    :atom -> Atom.to_string(value)
    :binary -> value
    _ -> nil
  end
end

This is more ergonomic, resulting in a reduction of 13% of the code (15 characters).

## Compatibility guarantees
All currently supported types will continue to return their current value.
In the future, specialized types that currently return nil could instead return a new atom, however existing atoms will not be removed.

## Future Compatibility Guarantees

Commitment to reliability and stability is paramount for \`TypeCheck\`. The following guarantees are provided to ensure future compatibility:

- **Consistent Support for Current Types**: All types currently identifiable by \`TypeCheck\` will continue to be supported in future versions. This means any type that \`TypeCheck\` can presently identify will remain identifiable in all future releases.

- **Extensibility for New Types**: Recognizing the dynamic nature of Elixir and potential future developments, \`TypeCheck\` may be updated to include new Elixir types, particularly those currently returning \`nil\`. Any such updates will aim for backward compatibility.

- **No Removal of Types**: There will be no removal of existing type checks in future versions. This ensures that existing code relying on \`TypeCheck\` will continue to function correctly with new versions of the module.

This approach is aimed at providing a dependable and forward-compatible tool that aligns with the growth of the Elixir ecosystem, while maintaining stability for existing projects.



## History

December 2023
Initial proof of concept was made via the simplified function:

defmodule TypeCheck do
  def check(value) do
    type_checks = [
      {&is_integer/1, :integer},
      {&is_float/1, :float},
      {&is_atom/1, :atom},
      {&is_tuple/1, :tuple},
      {&is_list/1, :list},
      {&is_map/1, :map},
      {&is_binary/1, :binary},
      {&is_function/1, :function},
      {&is_pid/1, :pid},
      {&is_reference/1, :reference}
    ]

    Enum.reduce(type_checks, nil, fn {func, type_atom}, acc ->
      if func.(value), do: type_atom, else: acc
    end)
  end
end


However, the list of checks provided by Kernel is incomplete for example it does not include Regex, there is no is_regex() check.
The list is available here: https://hexdocs.pm/elixir/1.15.7/Kernel.html

I made a study of how inspect works, the file is located here:
https://github.com/elixir-lang/elixir/blob/main/lib/elixir/lib/inspect.ex 

and saw that it uses a protocol with different implementations for different types so I decided to do the same thing, even though this meant switching my implementation.

I retained the previous version for cross-validation purposes, since two alternative implementations are more likely to be valid and this way my tests could also catch any errors in implementation of any of the kernel functions, should such errors be introduced.



## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding \`type_check\` to your list of dependencies in \`mix.exs\`:

\`\`\`elixir
def deps do
  [
    {:type_check, "~> 0.1.0"}
  ]
end

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/type_check.

`; document.getElementById('file-contents').value = exampleContent; initializeColorElements(exampleContent); updatePreview(exampleContent); showToast('Example file loaded!'); }); document.getElementById('file-selector-btn').addEventListener('click', function() { let fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.html'; fileInput.onchange = e => { let file = e.target.files[0]; if (!file) return; let reader = new FileReader(); reader.onload = function(e) { const fileContent = e.target.result; document.getElementById('file-contents').value = fileContent; initializeColorElements(fileContent); updatePreview(fileContent); }; reader.readAsText(file); showToast('File loaded!'); }; fileInput.click(); }); function updatePreview(content) { const previewFrame = document.getElementById('preview-frame'); previewFrame.contentDocument.open(); previewFrame.contentDocument.write(content); previewFrame.contentDocument.close(); } function initializeColorElements(content) { const colorRegex = /#[0-9A-Fa-f]{6}/g; let colors = content.match(colorRegex); if (!colors) return; colors = [...new Set(colors)]; const colorContainer = document.getElementById('color-container'); colorContainer.innerHTML = ''; colorElements = {}; colors.forEach(color => { const colorDiv = document.createElement('div'); colorDiv.classList.add('color-container'); const colorSwatch = document.createElement('div'); colorSwatch.classList.add('color-swatch'); colorSwatch.style.backgroundColor = color; const colorText = document.createElement('div'); colorText.classList.add('color-text'); colorText.textContent = color; const colorButton = document.createElement('button'); colorButton.classList.add('btn', 'btn-secondary'); colorButton.textContent = 'Turn to Black'; colorButton.onclick = () => replaceColor(color); colorDiv.appendChild(colorSwatch); colorDiv.appendChild(colorText); colorDiv.appendChild(colorButton); colorContainer.appendChild(colorDiv); colorElements[color] = { swatch: colorSwatch, text: colorText, button: colorButton }; }); } function replaceColor(color) { const contentArea = document.getElementById('file-contents'); let content = contentArea.value; const newContent = content.split(color).join('#000000'); const replacements = content.split(color).length - 1; contentArea.value = newContent; updatePreview(newContent); if (color !== '#000000' && colorElements[color]) { colorElements[color].swatch.style.backgroundColor = '#000000'; colorElements[color].text.textContent = '#000000'; colorElements[color].button.textContent = `Replaced ${replacements}`; colorElements[color].button.classList.remove('btn-secondary'); colorElements[color].button.classList.add('btn-disabled'); colorElements[color].button.disabled = true; colorElements[color].button.onclick = null; // Remove click functionality } showToast('Color replaced!'); } function resizePreviewFrame() { const previewFrame = document.getElementById('preview-frame'); const headerHeight = document.querySelector('.preview-title').offsetHeight; const availableHeight = window.innerHeight - previewFrame.offsetTop - headerHeight - 20; // 20 is an additional margin previewFrame.style.height = `${availableHeight}px`; // Call the original onresize function if it exists if (typeof oldResizeFunction === 'function') { oldResizeFunction(); } } // Call resizePreviewFrame on window load and resize window.onload = resizePreviewFrame; window.onresize = resizePreviewFrame;